4

I am trying to migrate an MVC 5 Application to ASP.NET 5 MVC 6 (Beta 7). Having problems when using the @inherits and @model directive together. Works fine when they are used separately.

In my _ViewImports i added the @inherits directive to use a base page with some custom user properties.

public abstract class BaseViewPage<TModel> : RazorPage<TModel>
{
    protected MyPrincipal AppUser
    {
        get
        {
            return new MyPrincipal(this.User as ClaimsPrincipal);
        }
    }
}

_ViewImports.cshttml

@inherits CommonWeb.BaseViewPage<TModel>
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"

And then i can go AppUser. in all my views. This works if i dont use a strongly typed view. If i add the @model directive in any view the inherited view page goes away.

Help appreciated

Update:

I did this successfully by using a custom pageBaseType in the web.config in prior versions.

Workaround.

public class ViewHelper
{
    ViewContext _context;

    public ViewHelper(ViewContext context)
    {
        _context = context;
    }

    public MyPrincipal AppUser
    {
        get
        {
            return new MyPrincipal(_context.HttpContext.User as ClaimsPrincipal);
        }
    }

    public string ControllerName
    {
        get
        {
            return _context.RouteData.Values["controller"].ToString();
        }
    }
}

View:

@{ var viewHelper = new ViewHelper(ViewContext);}

A way to achieve this for all views?

  • 3
    you cant use both at the same time http://stackoverflow.com/questions/23854556/mvc4-razor-difference-in-model-and-inherit-in-view-header – kamus Sep 26 '15 at 02:27

1 Answers1

4

There is a better way in MVC 6, which now supports injecting dependencies on the views with the @inject directive. (The directive @inject IFoo Foo allows you to use in your view a property named Foo of type IFoo)

Create a new interface IAppUserAccessor for getting your app user, for example:

public interface IAppUserAccessor
{
    MyPrincipal GetAppUser();
}

Create a class AppUserAccessor implementing it:

public class AppUserAccessor : IAppUserAccessor
{
    private IHttpContextAccessor httpContextProvider;
    public AppUserAccessor(IHttpContextAccessor httpContextProvider)
    {
        this.httpContextProvider = httpContextProvider;
    }

    public MyPrincipal GetAppUser()
    {
        return new MyPrincipal (
                       httpContextProvider.HttpContext.User as ClaimsPrincipal);            
    }
}

Register the new interface in the services container by adding a new entry in the ConfigureServices method of Startup.cs:

services.AddTransient<IAppUserAccessor, AppUserAccessor>();

Finally use the @inject directive to inject the IAppUserAccessor in your views. If you add the directive in ViewImports.cshtml then it will be available on every view.

@inject WebApplication4.Services.IAppUserAccessor AppUserAccessor

With all the pieces above you can now just use it on your view(s):

@AppUserAccessor.GetAppUser()

Update

If you need to inspect the route values, like the controller name, you can inject an IActionContextAccessor into your class and use it as follows:

public AppUserAccessor(IHttpContextAccessor httpContextProvider, IActionContextAccessor actionContextAccessor)
{
    this.httpContextProvider = httpContextProvider;
    this.actionContextAccessor = actionContextAccessor;
}

...

public string ControllerName
{
    get { return actionContextAccessor.ActionContext.RouteData.Values["controller"].ToString(); }            
}

Of course, that doesn't look like an AppUserAccessor anymore and smells like it has different responsabilities. At the very least it needs a more appropriate name :)

I would double check what do I need the controller name for. There might be a better way to accomplish your objective. (For example, if you need it for generating new links/urls you might just use an IUrlHelper)

Accessing ViewContext

Looks like beta8 has added support for injecting the ViewContext, although the implementation details may change before RC. See this question

Community
  • 1
  • 1
Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
  • Thank you @daniel-j-g that is so much cleaner! Almost there now! I need the ViewContext for Route values etc.. Cant figure out to inject that. –  Sep 26 '15 at 23:15
  • You already have the ViewContext available in every view, using `@ViewContext` – Daniel J.G. Sep 27 '15 at 08:46
  • @daniel-j-g Yes, but then i have to new up my ViewHelper in every view like i am doing now right? It would be nice to have the ViewContext injected so i can get a hold of routes to populate my ControllerName and ActionName properties etc (See my update). I cant get that through the plain HttpContext i think? –  Sep 27 '15 at 12:50
  • I am not aware of any `ViewContextAccessor` and you can't just add a new `ViewContext` dependency to your constructor because the framework won't know how to resolve it. What do you need it for? If it is for creating new links/urls or access the current url (but not the current route values) then you could add a dependency for `IUrlHelper` to your helper. You could also create extension methods that could be used in your view – Daniel J.G. Sep 28 '15 at 09:50
  • Actually, I have just realized that you can inject an `IActionContextAccessor` into your class, and use it to get the route values (including the controller name) as in `actionContextAccessor.ActionContext.RouteData.Values["controller"].ToString()`. Will update the answer – Daniel J.G. Sep 28 '15 at 09:59
  • Perfect!! Now it is working exactly how i want. Thank you very much! @daniel-j-g –  Sep 28 '15 at 14:46
  • Excellent @Fredde. By the way, you can upvote the answer if it was of help to you :) – Daniel J.G. Sep 28 '15 at 16:27
  • For sure, as soon as possible. Not enough reputation to vote yet. I am new on SO. @daniel-j-g –  Sep 28 '15 at 19:39