简体   繁体   中英

Need to pass parameter to multiple Partial Views on single view page

I am trying very hard to rewrite this question better than my previous effort which received no responses. Even though I'm nearly done with this application, I am still a relative newbie at programming and it seems like one challenge just leads to another. I have looked at many posts related to the problem of passing a parameter to several Partial Views in a single view page. So let's take this in order from the AlertPick.cshtml page where the user chooses one of three Alert_Identifier/SelectedAlertIndex parameters from the application database. I'm only showing the @model and Select Tag Form.

@model edxl_cap_v1_2.Models.ContentViewModels.EdxlCapMessageViewModel
@{
    <h4>@Model.Alerts.Count Alerts</h4>

    <form asp-controller="Alerts" asp-action="PickAlert" method="post">
        <select class="cap_select" id="cap_select" style="width:100%;max-width:95%;"
        asp-for="SelectedAlertIndex" asp-items="Model.Alert_Identifiers">
            <option>Select one</option>
        </select>
        <br />
        <input type="submit" name="PickAlert" value="Pick Alert to Assemble EDXL-Cap Message" />
    </form>
}

This takes the user to the PickAlert.cshtml page, a table of five rows where the first four rows are the Data Categories of the application: Alert, Info, Area and Resource each with the Alert_Identifier repeated as a reminder in a text box followed by its own submit button named Check Alert, Check Info, Check Area, and Check Resource , respectively. These submit buttons take the user to a _DetailsAlert.cshtml, _DetailsInfo.cshtml, _DetailsArea.cshtml, and _DetailsResource.cshtml pages and they work correctly, with the data item names and values from the record that matches the Alert_Identifier . The fifth row repeats the Identifier and its button reads Add All , to assemble the whole set together for review and takes the user to the _Assemble.cshtml page below, where the individual data categories are correctly assembled with the data item names, but lack the correct data values that match the record that corresponds to the Alert_Identifier . I'm thinking that I need to add a third parameter for the SelectedAlertIndex or Alert_Identifier to each of the @Html.Partial(...) Views, but I haven't found the correct form/syntax for that, and If someone could supply that or point me to an example similar enough to this, I would deeply appreciate it.

@model edxl_cap_v1_2.Models.ContentViewModels.EdxlCapMessageViewModel

<!DOCTYPE html>

<head>
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="~/css/capv1_2_refimp.css" />
    <title>Assembled EDXL-CAP Message</title>
</head>

<h4>Assemble EDXL-CAP Message</h4>

<!-- DetailsAlert -->
<div class="content-wrapper">
    @Html.Partial("_DetailsAlert", Model.Alert)
</div>
<!-- End of DetailsAlert -->

<!-- DetailsInfo -->
<div class="content-wrapper">
    @Html.Partial("_DetailsInfo", Model.Info)
</div>
<!-- End of DetailsInfo -->

<!-- DetailsArea -->
<div class="content-wrapper">
    @Html.Partial("_DetailsArea", Model.Area)
</div>
<!-- End of DetailsArea -->

<!-- DetailsResource -->
<div class="content-wrapper">
    @Html.Partial("_DetailsResource", Model.Resource)
</div>
<!-- End of DetailsResource -->

Responding to first comment below, I'm showing the InfosController.cs code for _DetailsInfo(int? id) the controller action for the Info Data Category. It is virtually identical for each of the data categories except that the line ... .SingleOrDefaultAsync(m => m.InfoIndex == id); becomes ....SingleOrDefaultAsync(m => m.AlertIndex == id); and the method itself becomes ....SingleOrDefaultAsync(m => m.AlertIndex == id); and the method itself becomes _DetailsAlert(int? id).

// GET: Infos/Details/5
    public async Task<IActionResult> _DetailsInfo(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var info = await _context.Info
            //.Include(e => e.Elements)
            //    .ThenInclude(d => d.DataCategory)
            .AsNoTracking()
            .SingleOrDefaultAsync(m => m.InfoIndex == id);

        if (info == null)
        {
            return NotFound();
        }

        return View(info);
    }

PickAlert method from AlertsController follows:

public IActionResult PickAlert(Alert obj, int? SelectedAlertIndex)
    {
        if (SelectedAlertIndex.HasValue)
        {
            ViewBag.Message = "Alert loaded successfully";
        }
        return View(_context.Alert.Where(x => x.AlertIndex == SelectedAlertIndex));
    }

I am not sure if I got the requirement correctly, but I think you have to create another model for all 4 partial views, eg for Alert, create a new model

class AlertModel:EdxlCapMessageViewModel
{
  int SelectedAlertIndex {get;set;}
}

And then your view would look like:

<!-- DetailsAlert -->
<div class="content-wrapper">
    @Html.Partial("_DetailsAlert",new AlertModel { Alert = Model.Alert,
                                                   SelectedAlertIndex = <ID SOMEHOW>
                                                 });
</div>

In .net core when I need to pass around a lot of data across the views, I usually find it cleanest to use services and DI. First, you can create a class that could store a set of data:

class MyDataForViews {
    // the following is an example. You can have any properties
    public string Info { get; set; }

}

You now have to add this class as a service. To do so go to your startup class and add the following within the services function:

services.AddScoped<MyDataForViews>();

Scoped means that the framework will create a new object of MyDataForViews for each HTTP request. No matter how many places you "inject" an object of MyDataForViews , it would use the same object across the current HTTP request. You can also replace the function with AddSingleton if you want to use the same object throughout the web app. The following is how you inject an object into your controller:

public class MyController : Controller
{

    MyDataForViews myData;

    // in controllers injection is done using the constructor 
    public MyController(MyDataForViews MyData) => myData = MyData;

    public IActionResult Index()
    {
        myData = ....   // assign all required data here
        View();
    }

}

Once this is done, instead of passing models to each view, you can inject the data into views using the following:

@inject MyDataForViews MyData;

Once you use this line on the top of any view, you can use the MyData object and there is no need to pass models to each partial view.

Here's a bit more detailed answer, since you've said at softwareengineering.stackexchange.com site that you still need help with this.
Let's first make sure you understand the basics correctly.

When it comes to passing data to the view, each controller in ASP.NET MVC has a property named ViewData , which is essentially a dictionary of key-value pairs. The ViewData itself has a property called Model , and this is what you access in the page using the Razor syntax @Model . You can use this property to pass a model that is strongly-typed, to avoid using magic strings for the keys of ViewData.

Note: ViewBag is a dynamic wrapper around the ViewData, so it's essentially the same thing ( ViewBag.SomeProperty is the same as ViewData['SomeProperty'] ); the use of ViewBag is discouraged, though.

In a controller action when you do something like return View() , ASP.NET uses the cshtml page as a template to create actual HTML, and return it as the response to the client (this is all server-side).

There are a few ways to pass data to the view which are equivalent, for example:

ViewData.Model = someObject;
return View();

is the same as:

return View(someObject);   // the View method can accept a model object

When it comes to partial views , by default, they get passed a copy of the parent page ViewData (this includes the reference to the Model ), so you don't have to do anything special to pass this data to a partial view (but you can pass data of your choice if you want to).

The select tag helper renders (generates HTML) for the select element with the options specified. This is then sent as HTML to the client. On the client side, when the user clicks the submit button, a POST request is sent to the server, which is ultimately handled by the method PickAlert method on the AlertsController . If everything is setup correctly, you should get the SelectedAlertIndex as the parameter. Note that this is happening back at the server side, and that you now need to again return a page as the response.

You can pick the corresponding Alert object from your _context . Use the FirstOrDefault method for this instead of Where , as you only need a single item (convert types for comparison if necessary - eg, if you have a string, but you are comparing to an int, or something along those lines).

var selectedAlert = _context.Alert.FirstOrDefault(x => x.AlertIndex == SelectedAlertIndex);

Now, all you need to do is set this selectedAlert and any other data that you need as a property on your model object (or under some key in ViewData), and render the correct view.

Note that if you just return View(model) without specifying the name of the view, the system will look for a view with the same name as your action method (here, PickAlert.cshtml), so use return View("ViewName", model) to change that if necessary.

For example, based on the code you've posted in your question, you could do something like this:

[HttpPost]
public IActionResult PickAlert(int? SelectedAlertIndex)
{
    var model = new EdxlCapMessageViewModel(/* ... params, if any */);

    if (SelectedAlertIndex.HasValue)
    {
        ViewBag.Message = "Alert loaded successfully";
        var selectedAlert = _context.Alert.FirstOrDefault(x => x.AlertIndex == SelectedAlertIndex);            

        // I added a property to your model to store the alert; 
        // if you already have one, just use that one instead.
        model.SelectedAlert = selectedAlert;    
    }
    return View("YourViewName", model);
}

The YourViewName should be the parent view that has the partial views in it (the "Assembled EDXL-CAP Message" view, I presume).

BTW, I know that the way the system is passing the parameters to the action methods in a controller may seem a bit like magic, but it's convention-based. In the example above, it works because the parameter is named SelectedAlertIndex , and the model object has a property with the same name (and because you've specified that property in the select tag helper using asp-for="SelectedAlertIndex" ). You can also modify the method signature so that it receives the entire model object (assuming that the model class is not too complicated - you can read more about how parameter binding works here ):

    [HttpPost]
    public IActionResult PickAlert(EdxlCapMessageViewModel model)
    {
        // extract the index from model.SelectedAlertIndex
        // you can also pass this same model object to the view 
        // (set some properties first if necessary)
        // ...
    }

Now for the partial views. Assuming that you are relying on the default mechanism which passes the parent ViewData to each partial view, you need to modify each partial view so that the code is written under the assumption that you can access the selected alert using @Model.SelectedAlert (the property you've set in the PickAlert action).

For example, a here's a simple partial view:

<div style="border: solid 1px #000000; padding: 30px; margin: 2px 2px 10px 2px;">
    <p>The selected index is: @Model.SelectedAlert.AlertIndex</p>
</div>

Note that I'm just using the same model as in the parent view to access the SelectedAlert object: @Model.SelectedAlert.AlertIndex .

Again, when rendering the partial views, if you pass no additional parameters, they'll get a copy of the ViewData dictionary, and the same Model :

@Html.Partial("_DetailsAlert");

If you pass something else as the model, eg, only the selected alert, then you need to change the partial view code accordingly:

@Html.Partial("_DetailsAlert", Model.SelectedAlert);

<div style="border: solid 1px #000000; padding: 30px; margin: 2px 2px 10px 2px;">
    <p>The selected index is: @Model.AlertIndex</p>
</div>

Note that now, in the partial view, the local @Model refers to what was @Model.SelectedAlert in the parent view. (In other words, here @Model is of type Alert .) This only affects the ViewData.Model property; the key-value pairs stored in ViewData are still the same as those in the parent view.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM