问题
I'm creating an ASP.NET MVC4 web site with Forms authentication and am encountering difficulties in the correct way to include multiple models within a view.
Specifically, there is a model that belongs to a given view, say "CartModel". However, given the current UI of the site, there is a model used within a partial view that is included in the master layout view with information about the currently logged in user.
My current approach for solving this issue is to include the LoginModel as a part of every view model. However, this seems repetitive and doesn't work correctly.
What is the most correct way to resolve an issue such as this?
回答1:
There are two ways to make it accessible.
- Use a filter (or override the
OnActionExecuting
method in a base controller your controllers all derive from) that add a models you need toViewBag
/ViewData
. - Use your own base WebViewPage to expose the models, but you'll still need to populate it. Thus, it may need to access your APIs, and done correctly, would probably require some dependency injection.
Method 1: OnActionExecuting
Override from base controller
public abstract MyBaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (User != null && User.Identity.IsAuthenticated) // check if user is logged in if you need to
{
ViewBag.LoginModel = /* add data here */;
}
}
}
Then use it as the base class (or somewhere further up the inheritance):
public MyController : MyBaseController
{
//etc.
}
Or use a filter
PopulateLoginModelAttribute : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User != null && filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Controller.ViewBag.LoginModel = /* add data here */;
}
this.OnActionExecuting(filterContext);
}
}
Then decorate a controller class, action method, or add as a global filter.
[PopulateLoginModel] // will be applied to all actions in this controller
public class MyController : Controller // note you can use the normal base type, or whatever you need
{
public ActionResult MyView()
{
return View(new CartModel());
}
}
Then use the model you placed in the ViewBag
in referenced partials, layouts, etc., but keep using Model
for the action's view.
Access the ViewBag
normally in your view (also accessible to layouts, and I made up a property value on LoginModel type to illustrate):
<span>@ViewBag.LoginModel.Name</span> <!-- available because of the filter !-->
Number of items in your cart: @Model.Items.Count <!-- the model provided by the action method !-->
ViewBag.LoginModel
will be available for all actions that derive from this controller without extra work. I'd recommend making it an attribute as it will give you more flexibility with base classes and to which controllers/actions you want to apply it to.
Method 2: Providing your own WebViewPage base class
It is less likely you'll want to use your own WebPageView base class. If you want to add members to assist in working through data or whatever for a view, it's great for that. But it's not the right place to add to or otherwise manipulate viewdata or the model, although it's possible.
Create the view base class
public abstract class MyWebViewPage<T> : WebViewPage<T>
{
protected LoginModel GetLoginModel()
{
// you could resolve some dependency here if you need to
return /* add data here */
}
}
Then use the members in your views, partials, and layouts
Make sure your web.config in the views folder is updated correctly.
<span>@GetLoginModel().Name</span>
回答2:
You can include multiple models in your view by several methods:
Using
ViewBag
Action:
public ActionResult Index() { ViewBag.CartData = _repository.GetCartData(); ViewBag.LoginData = _repository.GetLoginData(); return View(); }
View:
@foreach (var item in ViewBag.CartData) { // Do something here with item in CartData model } @foreach (var item in ViewBag.LoginData) { // Do something here with item in LoginData model }
Using
ViewData
Action:
public ActionResult Index() { ViewData["CartData"] = _repository.GetCartData(); ViewData["LoginData"] = _repository.GetLoginData(); return View(); }
View:
@foreach (var item in ViewData["CartData"] as List<Cart>) { // Do something here with item in CartData model } @foreach (var item in ViewData["LoginData"] as List<Login>) { // Do something here with item in LoginData model }
回答3:
Assuming that the "user info" is read from, say, User.Identity
, you don't actually need to create a model instance for use within the current view.
Let's say you're trying to do this:
@model MyMasterViewModel
<div>
@* Displaying some property of the primary model *@
@Model.SomeProperyOrAnother
@* Displaying some property of the model that is a member of the primary model *@
@Model.InnerModel.SomeOtherProperty
</div>
If the secondary model has nothing to do with the primary model, you can just call the @Html.Action()
method, like so:
@model MyMasterViewModel
<div>
@* Displaying some property of the primary model *@
@Model.SomeProperyOrAnother
@Html.Action("LoginInfo")
</div>
The result of the call is injected into the current view. I do something like this for my account menus, since I'm just reading the Identity
info from the Request
object and using that to get whatever else I need. If you want to make sure the "helper" action is never called direct, you can decorate it with [ChildActionOnly]
来源:https://stackoverflow.com/questions/20672575/including-user-model-in-every-view