Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Single .NET Core Razor C# functions in all Views

I know there is a way to ad c# functions inside a view and call them by using @functions{ ... } method inside my view, but is there a way to create a shared view with those functions to include inside of the controllers view without copying the same line of code on each one? I tried by using @inject and other methods inside the _Layout view, but obviously those methods can't be called. I also tried to create an external class like this, but I want to use views only if it is possible:

public class Functions : RazorPage<dynamic>
{
    public override Task ExecuteAsync()
    {
        throw new NotImplementedException();
    }

    public string GetTabActive(string lang)
    {
        if (ViewBag.lang.ToString() == lang) return "active";
        return "";
    }
}
like image 927
Dr. Roggia Avatar asked Oct 06 '17 10:10

Dr. Roggia


People also ask

What is .NET Core Razor?

Razor Pages can make coding page-focused scenarios easier and more productive than using controllers and views. If you're looking for a tutorial that uses the Model-View-Controller approach, see Get started with ASP.NET Core MVC. This document provides an introduction to Razor Pages. It's not a step by step tutorial.

Is Blazor better than Razor?

Blazor is a framework that leverages the Razor components to produce dynamic HTML. The biggest difference between Razor and Blazor is that Razor is a markup language with C#, while Blazor is the framework that lets you run C# code and use the Razor view engine in the browser.

What is a Razor page C#?

Razor Pages is a newer, simplified web application programming model. It removes much of the ceremony of ASP.NET MVC by adopting a file-based routing approach. Each Razor Pages file found under the Pages directory equates to an endpoint.

Is Cshtml the same as Razor?

cshtml is just a file extension. razor view engine is used to convert razor pages(. cshtml) to html.


3 Answers

I finally found a way to do this, i needed to inject the class inside of the _ViewImports giving a property name, like so:

@inject Functions func

And, in the StartUp, i added a new service pointing to my abstract class like that:

services.AddSingleton<Functions>();

So, inside each view, i can use models and call my functions like that:

<h2>@func.MyFunction</h2>
like image 91
Dr. Roggia Avatar answered Sep 16 '22 17:09

Dr. Roggia


Create an abstract class that inherits WebViewPage

public abstract class TestView<TViewModel> : WebViewPage<TViewModel>
{
    public void TestMethod()
    {
    }
}

In your views use "Inherits"

@inherits TestView<dynamic>

Your method will be available

@TestMethod()

--Side note

You should not use @model in conjunction with @inherits You just want one or the other.

like image 28
MichaelLake Avatar answered Sep 16 '22 17:09

MichaelLake


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>.

  1. In your project, subclass Microsoft.AspNetCore.Mvc.Razor.RazorPage or Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>, e.g. class MyPage<TModel> : RazorPage<TModel>
  2. Add whatever methods you like (protected or public, and static or instance all work).
  3. 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( ... );
        }
    }
}
like image 38
Dai Avatar answered Sep 20 '22 17:09

Dai