Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

aspnetcore: how to return a view from middleware

I'm working at a middleware for aspnetcore2.0 where I want to execute some razor view.
Actually I need a error handling middleware which would show nice pages from razor views. I know that it's possible to do with UseStatusCodePagesWithReExecute based on status codes. But I need a more general approach - handle an exception in my middleware to delegate (in some cases) it to an error view.

I realized that DeveloperExceptionPageMiddleware does something similar to what I need. But I can't understand how it works even after digging into its sources.

Here is the place where that middleware returns a view - https://github.com/aspnet/Diagnostics/blob/dev/src/Microsoft.AspNetCore.Diagnostics/DeveloperExceptionPage/DeveloperExceptionPageMiddleware.cs#L206

But I can't understand what kind of view it is. It's nor a razor page (as it has no @page directive) neither an mvc view (but i'm not sure).

In the project there're two files for that view: ErrorPage.cshtml and ErrorPage.Designer.cs. How that Designer.cs was created? It looks like a generated file. But thanks to it there's a normal class in the project (ErrorPage) which can be used explicitly. It inherits Microsoft.Extensions.RazorViews.BaseView class from Microsoft.Extensions.RazorViews.Sources package.

So the middleware just execute that view:

var errorPage = new ErrorPage(model);
return errorPage.ExecuteAsync(context);

How can it be achieved in my project?

like image 267
Shrike Avatar asked Oct 16 '17 19:10

Shrike


People also ask

How do I return a view in .NET Core?

View discovery The default behavior of the View method ( return View(); ) is to return a view with the same name as the action method from which it's called. For example, the About ActionResult method name of the controller is used to search for a view file named About. cshtml .

How does middleware work in NET Core?

Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component: Chooses whether to pass the request to the next component in the pipeline. Can perform work before and after the next component in the pipeline.

What is difference between middleware and filters in .NET Core?

Middleware only has access to the HttpContext and anything added by preceding middleware. In contrast, filters have access to the wider MVC context, so can access routing data and model binding information for example.

What is the difference between IApplicationBuilder use () and IApplicationBuilder run ()?

Run() is an extension method on IApplicationBuilder instance which adds a terminal middleware to the application's request pipeline. The Run method is an extension method on IApplicationBuilder and accepts a parameter of RequestDelegate.


1 Answers

UPDATE [2018.06]: Please note that the post was written for .NET Core 2.0 times, there're breaking changes for RazorEngine in .NET Core 2.1.

It turned out that it's pretty easy to do. Aspnet prjoect has an internal tool called RazorPageGenerator (see https://github.com/aspnet/Razor/tree/dev/src/RazorPageGenerator) which can be used to compile views. After compilation with this tool we'll get normal classes which can be used in middlewares.

But before we need to get RazorPageGenerator and slightly customize it.

1.Create a new console project

dotnet new console -o MyRazorGenerator

2.put NuGet.config inside this folder

<configuration>
  <config>
    <add key="globalPackagesFolder" value="./packages" />
  </config>
  <packageSources>
    <add key="aspnetcore-dev" value="https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json " />
  </packageSources>
</configuration>

3.Add the following in csprj (as dotnet add package doesn't support installing pre-prelease packages)

<ItemGroup>
  <PackageReference Include="RazorPageGenerator" Version="2.1.0-*" />
  <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" Version="2.1.0-*" />
  <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="2.1.0-*" />
</ItemGroup>

4.restore dotnet restore to check you got RazorPageGenerator

5.add into Program.cs:

    public static int Main(string[] args)
    {
        if (args == null || args.Length < 1)
        {
            Console.WriteLine("Invalid argument(s).");
            return 1;
        }

        var rootNamespace = args[0];
        var targetProjectDirectory = args.Length > 1 ? args[1] : Directory.GetCurrentDirectory();

        var razorEngine = RazorPageGenerator.Program.CreateRazorEngine(rootNamespace, builder => {
            FunctionsDirective.Register(builder);
            InheritsDirective.Register(builder);
            SectionDirective.Register(builder);
        });
        var results = RazorPageGenerator.Program.MainCore(razorEngine, targetProjectDirectory);

        foreach (var result in results)
        {
            File.WriteAllText(result.FilePath, result.GeneratedCode);
        }

        Console.WriteLine();
        Console.WriteLine($"{results.Count} files successfully generated.");
        Console.WriteLine();
        return 0;
    }

6.Now we have our own generator and can compile views

7.Create a Razor View (.cshtml)

8.run our generator to compile view:

dotnet run --project .\MyRazorPageGenerator\MyRazorPageGenerator.csproj Croc.XFW3.Web .\Middleware

here I assume that the view is inside Middleware\Views folder.

9.Generator creates a file like ErrorPage.Designer.cs (if view was ErrorPage.cshtml) which we can use:

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next.Invoke(context);
            if (context.Response.StatusCode == StatusCodes.Status404NotFound)
            {
                var statusCodeFeature  = context.Features.Get<IStatusCodePagesFeature>();
                if (statusCodeFeature == null || !statusCodeFeature.Enabled)
                {
                    if (!context.Response.HasStarted)
                    {
                        var view = new ErrorPage(new ErrorPageModel());
                        await view.ExecuteAsync(context);
                    }
                }
            }
        }
    }

Here we're returning our view in case of 404 error and absense of StatusCodePagesMiddleware. Can be useful for embedded UI in libs.

The generated code uses staff which should be added into your project. To get it we need to acquire nuget package Microsoft.Extensions.RazorViews.Sources. Again it’s not on nuget.org so we need to install it from https://dotnet.myget.org/feed/aspnetcore-dev/package/nuget/Microsoft.Extensions.RazorViews.Sources.

like image 164
Shrike Avatar answered Sep 28 '22 05:09

Shrike