Mvc: Custom Razor Views cannot inherit RazorPage<T>

Created on 12 Oct 2016  路  19Comments  路  Source: aspnet/Mvc

It's currently not possible to inherit RazorPage<T> - required in order to get Razor Views to inherit a Custom Base class. This is the same issue that was reported in this February Post where trying to inherit the generic RazorPage, i.e:

 public abstract class ViewPage<T> : RazorPage<T> { ... }

Will throw this ArgumentException:

Property 'ViewData' is of type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary1[[RazorRockstars.SearchRockstars, Mvc.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]', but this method requires a value of type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. Parameter name: viewContext

StackTrace

at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper1.Contextualize(ViewContext viewContext) at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.<>c__DisplayClass16_0.<CreateActivateInfo>b__1(ViewContext context) at Microsoft.Extensions.Internal.PropertyActivator1.Activate(Object instance, TContext context) at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.Activate(IRazorPage page, ViewContext context) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) at Microsoft.AspNetCore.Mvc.Razor.RazorView.d__14.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Razor.RazorView.d__13.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at ServiceStack.Mvc.RazorFormat.RenderView(IRequest req, ViewDataDictionary viewData, IView view, String layout) in C:\src\ServiceStack\src\ServiceStack.Mvc\RazorFormat.cs:line 194

It works when you inherit RazorPage<object> however this makes @base.Model a non-useful object Type which is not ideal.

All 19 comments

I suspect everything would Just Work:tm: if you use the type parameter TModel in your @inherits directive e.g.

c# @model Fred @inherits ViewPage<TModel>

Please let us know if that avoids issues.

No I get this error:

The 'inherits' keyword is not allowed when a 'model' keyword is used.
@model SearchRockstars
@inherits ViewPage<TModel>

Likewise when explicitly specifying the Type:

The 'inherits' keyword is not allowed when a 'model' keyword is used.
@model SearchRockstars
@inherits ViewPage<SearchRockstars>

@mythz The @inherits would need to exist in a _ViewImports.cshtml and the @model in your actual view.

@NTaylorMullen ok that works but the intelli-sense is broken as the tooling no longer thinks it's inheriting from ViewPage<TModel> so the development experience is much worse.

I'll have to revert back to using RazorPage<object> until the tooling matches runtime behavior.

Is there a work item here to allow @inherits ViewPage<MyModel> to work as expected or should I close this issue?

@mythz can you be more specific as to what about the development experience is broken?

When using @inherits in _ViewImports.cshtml the Razor View editor in VS.NET doesn't think it's inheriting ViewPage<T> anymore so trying to call anything on the base ViewPage<T> is treated the same way as trying to call a non-existent method or property.

Not sure if it matters but I also have the latest version of ReSharper installed

@mythz It may be a ReSharper issue because I'm able to get

C# public abstract class CustomViewPage<TModel> : RazorPage<TModel> { // .... }

Working at dev time.

@NTaylorMullen ok yeah looks like it was, disabling ASP.NET Razor in Resharper > Options > Products & Features fixed the broken references.

I'll close this issue as the suggested solution works for vanilla VS.NET (i.e. w/o R#), but it would be nice if @inherits ViewPage<MyModel> and class ViewPage<T> : RazorPage<T> { ... } also works as it's an issue devs would run into, e.g. I hit this issue independently from the Extending Razor Views in ASP.NET Core post whose suggestion of inheriting RazorPage<object> is still required when using @inherits in the Razor View.

I have runtime problems:

@inherits DefaultRazorPage<Hs.Frontend.ViewModels.NavBarViewModel> throws errors but @inherits DefaultRazorPage<object> not somehow.

message:

Property 'ViewData' is of type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]', but this method requires a value of type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'.
Parameter name: viewContext

call stack:

   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper`1.Contextualize(ViewContext viewContext)
   at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.<>c__DisplayClass16_0.<CreateActivateInfo>b__1(ViewContext context)
   at Microsoft.Extensions.Internal.PropertyActivator`1.Activate(Object instance, TContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.Activate(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderPageAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Hs.Core.Web.Razor.DefaultRazorPage`1.RenderPartial[T](String partialViewName, T model) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 203
   at Hs.Core.Web.Razor.DefaultRazorPage`1.<RenderZones>b__33_0(ZoneRegistration reg) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 179
   at ServiceStack.EnumerableExtensions.Each[T](IEnumerable`1 values, Action`1 action) in /opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack.Common/EnumerableExtensions.cs:line 28
   at Hs.Core.Web.Razor.DefaultRazorPage`1.RenderZones(List`1 registrations) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 160
   at Hs.Core.Web.Razor.DefaultRazorPage`1.Zone(String zoneName) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 216
   at AspNetCore._Views___Layout_cshtml.<ExecuteAsync>d__21.MoveNext() in /Views//_Layout.cshtml:line 53
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderPageAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderLayoutAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ServiceStack.Mvc.RazorFormat.RenderView(IRequest req, IResponse res, ViewDataDictionary viewData, IView view, String layout) in /opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack.Mvc/RazorFormat.cs:line 223

If i do as suggested above:

in _viewImports

@inherits DefaultRazorPage
Or
@inherits DefaultRazorPage<object>

And in the view:

@model MyModel

I Get an error property does not exist, exception:

One or more compilation failures occurred:
/Views/Shared/NavBar.cshtml(12,40): error CS1061: 'object' does not contain a definition for 'DebugMode' and no extension method 'DebugMode' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

Stack:

   at Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationResult.EnsureSuccessful()
   at Microsoft.AspNetCore.Mvc.Razor.Internal.CompilerCache.CreateCacheEntry(String relativePath, String normalizedPath, Func`2 compile)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.Internal.CompilerCache.GetOrAdd(String relativePath, Func`2 compile)
   at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorPageFactoryProvider.CreateFactory(String relativePath)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.CreateCacheResult(HashSet`1 expirationTokens, String relativePath, Boolean isMainPage)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.OnCacheMiss(ViewLocationExpanderContext expanderContext, ViewLocationCacheKey cacheKey)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.LocatePageFromViewLocations(ActionContext actionContext, String pageName, Boolean isMainPage)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.FindView(ActionContext context, String viewName, Boolean isMainPage)
   at Hs.Core.Web.Razor.DefaultRazorPage`1.RenderPartial[T](String partialViewName, T model) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 193
   at Hs.Core.Web.Razor.DefaultRazorPage`1.<RenderZones>b__33_0(ZoneRegistration reg) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 179
   at ServiceStack.EnumerableExtensions.Each[T](IEnumerable`1 values, Action`1 action) in /opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack.Common/EnumerableExtensions.cs:line 28
   at Hs.Core.Web.Razor.DefaultRazorPage`1.RenderZones(List`1 registrations) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 160
   at Hs.Core.Web.Razor.DefaultRazorPage`1.Zone(String zoneName) in C:\Git\trancon.cartwiseng\Frontend\Source\Hs.Frontend.SelfHost\Hs.Frontend.Core.Web\Razor\DefaultRazorPage.cs:line 216
   at AspNetCore._Views___Layout_cshtml.<ExecuteAsync>d__21.MoveNext() in /Views//_Layout.cshtml:line 86
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderPageAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderLayoutAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.<RenderAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ServiceStack.Mvc.RazorFormat.RenderView(IRequest req, IResponse res, ViewDataDictionary viewData, IView view, String layout) in /opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack.Mvc/RazorFormat.cs:line 223

@joelharkes The recommendation here is to specify @inherits ViewPage<TModel> in your _ViewImports.cshtml, e.g. here's the _ViewImports.cshtml used from https://github.com/NetCoreApps/RazorRockstars/

@inherits ViewPage<TModel>

@using ServiceStack
@using ServiceStack.Mvc
@using ServiceStack.Text
@using RazorRockstars
@using RazorRockstars

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@mythz im trying it now, why did you put this file in the wwwroot instead of Views folder?

Thanks mythz, its totally working, but ya resharper is still broken

It's unclear based on this, and the fact that the issue is closed, what the future direction is.

The inability to have custom Razor Views inherit from RazorPage\ Admittedly I am converting some huge MVC5 apps and overall this is fairly simple, but not being able to inherit RazorPage\

While I understand the use of _ViewImports.cshtml and model, and I confirmed that it works, it is at best a workaround. And may be usable for trivial or average MVC apps.

MVC5 was able to inherit from WebViewPage\

public class MyRazorView<TModule, TModel> : WebViewPage<TModel>
    where TModule : ModuleDefinition
    where TModel : class {

    public RazorView();

    ...
    public TModel Model { get; }
    public TModule Module { get; }
    ...
}

The corresponding view would look like this (note, no model required (or even allowed)).

@inherits MyRazorView<TextModule, TextModelDisplay>
@Html.DisplayFor(m => Module.Contents)

This custom class MyRazorView\ The difficulty in providing TYPED properties (which are different for each class) in MVC6 is where the lack of derived custom RazorViews is causing problems.

MVC6's lack of support for inheriting from RazorPage\ Clearly with this approach all views in the same folder must use the same _ViewImports.cshtml, which means they are all based on the same class. (Not good).
And the model must now be explicitly listed in the view. So now we define the class outside the view in _ViewImports.cshtml and the model in the view. (ugly?).

Obviously it's not possible to use inherits and model both in the same view. (Error: The 'inherits' keyword is not allowed when a 'model' keyword is used.)
This was correct in MVC5 because WebViewPage\ MVC6 also doesn't allow inherits and model in the same view. This must be a left-over from MVC5 because MVC6 makes no use whatsoever of the inherits keyword to determine the model type.
Allowing inherits and model in the same view in MVC6 would seem an OK solution, although somewhat hacky... as both the inherits and model would specify the model type. At least it would eliminate the unnecessarily awkward separation of class and model definition.

Further, in MVC5 the Html property was a properly typed HtmlHelper\ In MVC6 only a model keyword results in a property typed HtmlHelper\

Property 'ViewData' is of type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[[YetaWF.Modules.Text.Controllers.TextModuleController+TextModelDisplay, YetaWF.Text, Version=1.1.1.0, Culture=neutral, PublicKeyToken=null]]', but this method requires a value of type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.
Parameter name: viewContext

While I appreciate the new features and enhancements in MVC6, this is an area that was severely clipped in MVC6 and makes upgrading to MVC6 really unnecessarily difficult.

Maybe there is a solution to somehow add typed properties to a view and my inexperience with MVC6 is preventing me from finding another solution, other than custom RazorPage\

/cc @pranavkm

Is this still an issue for anyone? Or did anyone find issues with the workaround? I'm about to try this myself. Just thought I'd post this inquiry in the mean time (to find things to watch out for).

I've just run into this issue. Using _ViewImports.cshtml is a work-around so the issue still needs resolving. Can this be re-opened?

Our use-case is that we use a handful of razor base page types to expose functionality in views and templates used by our CMS. We have docs that details this usage if you need some background (generic view helper, page templates, page module types, custom entity details pages).

The workaround is limiting and non-intuitive in that:

  1. we can only using a single base class per folder
  2. having the definition split between two files makes it more difficult to explain and provide examples
  3. you would expect it to just work in the same way as it did in MVC5
  4. not being able to contain all the definition information in a single view file makes it less portable when using our modular framework (e.g. we can have views defined in plugin assemblies)

We're trying to port to asp.net core, and so to get this working I've had to use the work around suggested here, but I've also had to remove generic type constraints on the on the razor base classes as this now causes issues:

An error occurred during the compilation of a resource required to process this request. Please review the following specific error details and modify your source code appropriately.
Generated Code

The type 'dynamic' cannot be used as type parameter 'TModel' in the generic type or method 'CofoundryTemplatePage<TModel>'. There is no implicit reference conversion from 'dynamic' to 'Cofoundry.Web.IEditablePageViewModel'.
-
    using System.Collections.Generic;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using System.Threading.Tasks;

    public class _Cofoundry_PageTemplates__ViewStart_cshtml : CofoundryTemplatePage<dynamic>
    {
        #line hidden
        public _Cofoundry_PageTemplates__ViewStart_cshtml()
        {
        }
        #line hidden

We may end up changing our approach, but it's difficult to see how we can achieve the same thing through other razor features. We make use of the model and the view context in our template helpers and I haven't seen a way to inject this into an injected service yet.

@YetaWF \ @HeyJoel - I'm moving your issue to a separate work item. The original work issue was based on being able to specify a base type and a model and that works with _ViewImports. However clearly the implementation has issues and doesn't work in the scenarios you listed. I filed separate work items for these so they can be triaged and tracked:

The problem is putting the imports in the ViewImports file is that you are making the assumption that all my pages are going to inherit that page, and due to the folder structure I would have to put a ViewImports in every views folder, that's not very efficient or intuitive

Was this page helpful?
0 / 5 - 0 ratings