Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I see the code generated for a Razor Page when using Asp.Net Core?

With Asp.Net it was easy to see the code generated by the Razor View Engine: Add a compile error and the error page will give access to the source of the Razor Page.

This changed with Asp.Net Core, which I read somewhere creates the code in memory and does not allow access to that code easily.

Question: Someone knows a trick how to access generated Razor source code with Asp.Net Core?

like image 516
citykid Avatar asked Mar 24 '17 16:03

citykid


People also ask

What is razor view in ASP.NET Core?

Razor markup is code that interacts with HTML markup to produce a webpage that's sent to the client. In ASP.NET Core MVC, views are .cshtml files that use the C# programming language in Razor markup. Usually, view files are grouped into folders named for each of the app's controllers.

Do Cshtml files get compiled?

Razor files with a . cshtml extension are compiled at both build and publish time using the Razor SDK. Runtime compilation may be optionally enabled by configuring your project.

Which keyword of Razor syntax is used to render the view content in the layout page as body content?

If you're from Web Forms world, the easiest way to think of RenderBody is it's like the ContentPlaceHolder server control. The RenderBody method indicates where view templates that are based on this master layout file should “fill in” the body content.


2 Answers

Add the following class to your ASP.NET Core MVC project:

using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

public class CustomCompilationService : DefaultRoslynCompilationService, ICompilationService
{
    public CustomCompilationService(ApplicationPartManager partManager, 
        IOptions<RazorViewEngineOptions> optionsAccessor, 
        IRazorViewEngineFileProviderAccessor fileProviderAccessor, 
        ILoggerFactory loggerFactory) 
        : base(partManager, optionsAccessor, fileProviderAccessor, loggerFactory)
    {
    }

    CompilationResult ICompilationService.Compile(RelativeFileInfo fileInfo, 
        string compilationContent)
    {
        return base.Compile(fileInfo, compilationContent);
    }
}

Override the ICompilationService added by MVC with the above class;

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<ICompilationService, CustomCompilationService>();
}

Set a break point on the Compile method of CustomCompilationService and view compilationContent.

enter image description here

Notes

View lookups are case sensitive. If your controller routing seeks a view named Index (Index.cshtml) but you've named your view file index (index.cshtml), you'll receive an exception:

InvalidOperationException: The view 'Index' was not found.

like image 136
In Vladimir Putin We Trust Avatar answered Oct 05 '22 03:10

In Vladimir Putin We Trust


Artificial Stupidity provided the correct answer for ASP.NET core 1.x. For version 2.0 of the framework, one can instead use a custom razor template engine:

using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;

public class CustomMvcRazorTemplateEngine : MvcRazorTemplateEngine
{
  public CustomMvcRazorTemplateEngine(RazorEngine engine, RazorProject project) : base(engine, project)
  { }

  public override RazorCSharpDocument GenerateCode(RazorCodeDocument codeDocument)
  {
    RazorCSharpDocument razorCSharpDocument = base.GenerateCode(codeDocument);
    // Set breakpoint here for inspecting the generated C# code in razorCSharpDocument.GeneratedCode
    // The razor code can be inspected in the Autos or Locals window in codeDocument.Source._innerSourceDocument._content 
    return razorCSharpDocument;
  }
}

Then override the RazorTemplateEngine of the framework:

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();
  services.AddSingleton<RazorTemplateEngine, CustomMvcRazorTemplateEngine>();
}

In version 2.1 of ASP.NET Core, the RazorTemplateEngine seems to be legacy, and the above mechanism does not work anymore. The changes may have to do with the move towards precompilation of razor views, but since I am not involved in the development, I can only guess at the developers' motives.

I would now recommend inspecting the precompiled views in the **.Views.dll generated at build or publish time, depending on your project settings. I personally use Telerik's JustDecompile for this purpose.

If you really need to have a programmatic solution, you can hook into the RazorProjectEngine with a custom phase:

using Microsoft.AspNetCore.Razor.Language;

namespace Econet.PAG.UI
{
  internal class DebugRazorEnginePhase : IRazorEnginePhase
  {
    public RazorEngine Engine { get; set; }

    public void Execute(RazorCodeDocument codeDocument)
    {
      RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument();
      // Set breakpoint here for inspecting the generated C# code in razorCSharpDocument.GeneratedCode
      // The razor code can be inspected in the Autos or Locals window in codeDocument.Source._innerSourceDocument._content 
    }
  }
}

and register it in the creation of the RazorProjectEngine

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton(s =>
    {
      var fileSystem = s.GetRequiredService<RazorProjectFileSystem>();
      var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
      {
        RazorExtensions.Register(builder);

        // Roslyn + TagHelpers infrastructure
        var metadataReferenceFeature = s.GetRequiredService<LazyMetadataReferenceFeature>();
        builder.Features.Add(metadataReferenceFeature);
        builder.Features.Add(new CompilationTagHelperFeature());

        // TagHelperDescriptorProviders (actually do tag helper discovery)
        builder.Features.Add(new DefaultTagHelperDescriptorProvider());
        builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());

        builder.Phases.Add(new DebugRazorEnginePhase());
      });
}

Note that except for the line adding the custom phase, the code inside AddSingleton is copied from Microsoft.Extensions.DependencyInjection.MvcRazorMvcCoreBuilderExtensions.AddRazorViewEngineServices(IServiceCollection services) in the Microsoft.AspNetCore.Mvc.Razor sources.

like image 37
Michael Spranger Avatar answered Oct 05 '22 02:10

Michael Spranger