Razor MVC, where to put global variables that'

2019-01-22 08:44发布

问题:

Hello Razor MVC Gurus:

Newbie question.

Background. I have a custom IIdentity that is set in an HttpModule before it gets to controller & views. To use it, i have to do

   MyIdentity myIdentity = (MyIdentity)((GenericPrincipal)context.User).Identity;
   MyComplexUser user = myIdentity.User;
   //user.name //user.location //user.username  //etc

The problem is, I use the object in different places such as

  • master layout
  • Some sub level nested layouts
  • Some partialviews
  • Some views

It really depends on what properties of "MyComplexUser" object the views need.

Currently, in the views, I have to do this really complicated casting to get to a property. For instance, if I want the "Name" of the user, I need to do

@(((MyComplexUser)(((MyIdentity)((GenericPrincipal)context.User).Identity).User)).Name)

I suppose I could put it in the controllers and then populate the ViewBag with a ViewBag.MyUser property, but then

  1. I don't like to use ViewBag. I prefer strongly typed objects
  2. If I use a strongly typed object ("MyUser") for views, then I have to popular all those models with a "MyUser" property. Feels a bit dirty? As I like to keep my models clean and be specific to the views they are involved with. Besides, it gets unnecessarily repetitive.
  3. In places like master_layout.cshtml or partialviews, how do you access "MyUser" if I put them in a controller?
  4. Use RenderAction and build partialviews for each User property is an overkill?

Thanks. Again, I'm a newbie at MVC 4, any suggestion is greatly appreciate it.

回答1:

I'll explain a similar solution that works pretty well for me. With small changes, I believe that it will work for you (and others, hopefully) as well.

Basically, we'll be using inheritance.

Controllers

Let's create a custom base controller, such as

public class BaseController : Controller

and let's change our controllers to inherit from it, as

public class HomeController : BaseController

Models (ViewModels, I say)

You probably have lots of classes inside your Models folder, right? They act as DTOs from the controller to the views, right²? If you answered yes for both, then keep reading.

Let's create a base model class, such as public class BaseVM, and let's change our models to inherit from it, like public class HomeIndex : BaseVM

Important: your layout file (_Layout or whatsoever) must be strongly typed to BaseVM or a child of it.

The hook

Now that everything's beautifuly typed, let's use the request pipeline in our favor. At BaseController, you'll add a method that looks like this:

protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
    if (filterContext.Result is ViewResultBase)//Gets ViewResult and PartialViewResult
    {
        object viewModel = ((ViewResultBase)filterContext.Result).Model;

        if (viewModel != null && viewModel is BaseVM)
        {
            BaseVM baseVM = viewModel as BaseVM;

            baseVM.MyIdentity = (MyIdentity)((GenericPrincipal)context.User).Identity;
            //and so on...
        }
    }

    base.OnActionExecuted(filterContext);//this is important!
}

OnActionExecuted is called after the execution of the action but before the view rendering. That's exactly what we want.

I hope you got it already. =)