There are a few approaches:
Approach 1: Subclass RazorPage<TModel>
You can (ab)use OOP inheritance to add common members (i.e. methods/functions, but also properties) to your Razor page types by subclassing RazorPage<T>
and updating all of your pages to use your new subclass as their base type instead of defaulting to ASP.NET Core's RazorPage
or RazorPage<TModel>
.
- In your project, subclass
Microsoft.AspNetCore.Mvc.Razor.RazorPage
or Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>
, e.g. class MyPage<TModel> : RazorPage<TModel>
- Add whatever methods you like (
protected
or public
, and static
or instance all work).
- In all
.cshtml
files where you want to use them, change your @model MyPageModel
directive to @inherits MyPage<MyPageModel>
.
Note that:
- The
class RazorPage<TModel>
derives from the (non-generic) class RazorPage
class, so subclassing RazorPage<TModel>
will not cause those members to be visible from pages deriving from RazorPage
.
- Note that if your
.cshtml
lacks a @model
directive, the actual page class will derive from RazorPage<dynamic>
instead of RazorPage
.
- You can, of course, subclass
RazorPage<TModel>
with a non-generic class provided you specify a concrete type for TModel
in your subclass; this is useful when you have multiple .cshtml
pages that share the same @model
type and need lots of custom C# logic.
For example:
MyPage.cs:
using Microsoft.AspNetCore.Mvc.Razor;
namespace MyProject
{
public abstract class MyPage<TModel> : RazorPage<TModel>
{
protected String Foobar()
{
return "Lorem ipsum";
}
}
}
ActualPage.cshtml:
@using MyProject // <-- Or import in your _ViewImports
@inherits MyPage<ActualPageViewModel>
<div>
@( this.Foobar() ) <!-- Function is inherited -->
</div>
That said, I'm not a super-huge fan of this approach because (in my opinion) subclassing and inheritance should not be abused as a substitute for mixins (though I appreciate that C#'s lack of mixins is a huge ergonomic issue).
Approach 2: Extension methods
- You can also define extension methods for
RazorPage
and RazorPage<TModel>
, but also for specific TModel
types.
- You could also define them for
IRazorPage
or RazorPageBase
if you really wanted to as well.
- This is the closest thing we have in C# to generic specialization.
- When extending
RazorPage<TModel>
- and you don't care about TModel
, then make it generic type-parameter on your extension-method (see Foobar2
in my example below).
- C# extension methods require
this.
to be used, btw.
- And don't forget to import the extension class' namespace (either in the page with
@using
or in your _ViewImports.cshtml
file).
- Note th
For example:
MyPageExtensions.cs:
using Microsoft.AspNetCore.Mvc.Razor;
namespace MyProject
{
public static class MyPageExtensions
{
public static String Foobar1( this RazorPage page )
{
return "Lorem ipsum";
}
public static String Foobar2<TModel>( this RazorPage<TModel> page )
{
return "Lorem ipsum";
}
}
}
ActualPage.cshtml:
@using MyProject // <-- Or import in your _ViewImports
@model ActualPageViewModel
<div>
@( this.Foobar() ) <!-- Extension Method -->
</div>
Getting IHtmlHelper
, IUrlHelper
, etc.
You might notice that RazorPage<TModel>
does not have IHtmlHelper Html { get; }
nor IUrlHelper Url { get; }
properties - nor other useful ASP.NET MVC-specific members. That's because those members are only defined in the hidden PageName.cshtml.g.cs
file's class (it's in your obj\$(Configuration)\$(TargetPlatform)\Razor\...
directory, if your project builds okay).
In order to get access to those members you can define an interface to expose those properties, and you can direct ASP.NET Core to add that interface to the .cshtml.g.cs
classes by adding @implements
to your _ViewImports.cshtml
file.
This is what I use in my projects:
IRazorPageInjectedProperties.cs:
public interface IRazorPageInjectedProperties
{
ViewContext ViewContext { get; }
Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider ModelMetadataProvider { get; }
Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; }
Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; }
Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; }
Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; }
// Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; }
Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper HtmlHelper { get; }
}
_ViewImports.cshtml:
@using System
@using Microsoft.AspNetCore.Mvc
@* etc *@
@inject Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider ModelMetadataProvider
@inject Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper HtmlHelper
@implements MyProject.IRazorPageInjectedProperties
The IHtmlHelper
type needs an explicit @inject
directive because interface
members have to be public
(or explicit interface implementations), but the Html
property is protected
, but @inject
members are public
. The name needs to be HtmlHelper
instead of Html
otherwise it would conflict.
If subclassing RazorPage
you might notice your subclass can't really implement IRazorPageInjectedProperties
because you'd need to add the properties there (as abstract
), but @inject
properties won't override
them, but you could hack it a bit with some indirect properties, like so:
using Microsoft.AspNetCore.Mvc.Razor;
namespace MyProject
{
public abstract class MyPage<TModel> : RazorPage<TModel>
{
private IRazorPageInjectedProperties Self => (IRazorPageInjectedProperties)this;
private IHtmlHelper Html => this.Self.HtmlHelper;
private IUrlHelper Url => this.Self.Url;
protected IHtmlContent GetAHrefHtml()
{
return this.Html.ActionLink( ... );
}
protected String GetHrefUrl()
{
return this.Url.Action( ... );
}
}
}
If using extension-methods you'll need to either:
- Modify
IRazorPageInjectedProperties
to extend IRazorPage
and make IRazorPageInjectedProperties
the target of your extension-methods.
- See
GetAHrefHtml
in the example below.
- or change your extension methods to be generic over
TPage : RazorPage
add a type-constraint to require IRazorPageInjectedProperties
.
- See
GetHrefUrl1
in the example below.
- to get
TModel
you'll need to make the extensions generic over TPage
and TModel
with IRazorPageInjectedProperties
.
- See
GetHrefUrl2
in the example below.
MyPageExtensions.cs:
using Microsoft.AspNetCore.Mvc.Razor;
namespace MyProject
{
public interface IRazorPageInjectedProperties : IRazorPage
{
// etc
}
public static class MyPageExtensions
{
public static IHtmlContent GetAHrefHtml( this IRazorPageInjectedProperties page )
{
return page.Html.ActionLink( ... );
}
public static String GetHrefUrl1<TPage>( this TPage page )
where TPage : RazorPage, IRazorPageInjectedProperties
{
return page.Url.Action( ... );
}
// to get TModel:
public static String GetHrefUrl2<TPage,TModel>( this TPage page )
where TPage : RazorPage<TModel>, IRazorPageInjectedProperties
{
return page.Url.Action( ... );
}
}
}