user5378236
user5378236

Reputation:

MVC 6 @inherit RazorPage

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?

Upvotes: 4

Views: 2324

Answers (1)

Daniel J.G.
Daniel J.G.

Reputation: 35032

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

Upvotes: 4

Related Questions