Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Razor/Blazor Support #249

Open
oakad opened this issue May 9, 2021 · 14 comments
Open

Razor/Blazor Support #249

oakad opened this issue May 9, 2021 · 14 comments

Comments

@oakad
Copy link

oakad commented May 9, 2021

I would like to build a small blazor server using bazel. The server itself builds just fine, but ends up broken, because razor, cshtml.cs and various data files are not added to the target as required.

Is building blazor servers supported at all? If yes, may there be an example somewhere on how to build those?

If not presently, may the support for those be added in the near enough future?

@purkhusid purkhusid changed the title Support for razor/asp.net files and blazor Razor/Blazor Support Aug 5, 2022
@purkhusid
Copy link
Collaborator

We are currently working on a big refactor of the rules on the next branch and once that is done I think adding Razor/Blazor support shouldn't be that hard. I'm not sure if I'll be able to do it myself since I use neither but we would definitely help guiding/reviewing any contributions!

Keeping this open as a tracking issue for Blazor/Razor

@purkhusid purkhusid reopened this Aug 5, 2022
@oakad
Copy link
Author

oakad commented Aug 6, 2022

The problem with blazor (as described in another issue thread) is tight integration of blazor code generator with msbuild. I have opened an issue regarding decoupling those: code generator, after all, is a separate, albeit totally undocumented binary. Alas, they don't want to do it, as they believe their code generators to be an "implementation detail" of a general, msbuild based system.

To this end, I have made myself some custom rules invoking msbuild with specially crafted csproj files (and a go helper to work around msbuild idiosyncrasies).

Accommodating something like this in a general "rules_dotnet" setup will be rather tricky. :-)

@purkhusid
Copy link
Collaborator

@oakad Interesting, can you link to the issue you mentioned?

@oakad
Copy link
Author

oakad commented Aug 6, 2022

This one: #260 :-)

@oakad
Copy link
Author

oakad commented Aug 6, 2022

dotnet/aspnetcore#35180

My attempt at getting some docs.

@Place1
Copy link
Contributor

Place1 commented Mar 10, 2023

I'm wondering if it's possible to support use-cases like Razor and RazorLight but adding support for <PreserveCompilationContext>true</PreserveCompilationContext>.

I just tried to use razor with runtime compilation services.AddRazorPages().AddRazorRuntimeCompilation(); and it fails because there's no compilation context

// ...
      q2e2iylo.nii(40,34): error CS0234: The type or namespace name 'AspNetCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?)
      q2e2iylo.nii(40,71): error CS0518: Predefined type 'System.Object' is not defined or imported
      q2e2iylo.nii(40,71): error CS1980: Cannot define a class or member that utilizes 'dynamic' because the compiler required type 'System.Runtime.CompilerServices.DynamicAttribute' cannot be found. Are you missing a reference?
      q2e2iylo.nii(40,71): error CS0518: Predefined type 'System.Boolean' is not defined or imported
      q2e2iylo.nii(40,92): error CS0518: Predefined type 'System.Void' is not defined or imported
      q2e2iylo.nii(21,67): error CS0115: 'example_src_Project_Views_Home_Index.ExecuteAsync()': no suitable method found to override
// ...

@oakad
Copy link
Author

oakad commented Mar 10, 2023

Razor support needs 2 obvious issues resolved:

  1. Code generator: not documented, but somewhat straightforward, the codegen itself is a normal, runnable binary with some cli arguments.
  2. Static assets which are part of dependent packages: those may require evaluating arbitrary MSBuild constraints, as they are defined my means of MSBuild XML snippets.

p.2 here is a nastier problem than it may seem, because some of these assets are critical for things like Blazor/WASM; WASM simply won't work unless that stuff is copied over to a deployment bundle. It will also be a problem for runtime compilation of any sort, unless the user feels like maintaining appropriate copies of those elsewhere.

@Place1
Copy link
Contributor

Place1 commented Mar 30, 2023

I did a bit of a dive into this and here's what I found for Razor specifically:

  • latest cshtml compilation is done with a source generator that ships with the dotnet SDK: external/dotnet_x86_64-unknown-linux-gnu/sdk/7.0.200/Sdks/Microsoft.NET.Sdk.Razor/source-generators/*.dll
  • the razor source generator needs to be passed to csc as an analyzer: /analyzer:/path/to/Microsoft.NET.Sdk.Razor.SourceGenerators.dll
  • the generator requires an .editorconfig to configure additional metadata for cshtml files; passed to csc as an analyzer config file /analyzerconfig:/path/to/.editorconfig

the analyzer config can be found in a traditional csproj/msbuild obj folder if <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> is set; generated at obj/Debug/net7.0/<project>.GeneratedMSBuildEditorConfig.editorconfig.

the only part of the analyzer config that seems to matter in my testing is the following section

[/home/me/example-project/Project/Templates/Example.cshtml]
build_metadata.AdditionalFiles.TargetPath = VGVtcGxhdGVzL0V4YW1wbGUuY3NodG1s
build_metadata.AdditionalFiles.CssScope = 

The TargetPath is the cshtml filepath encoded with base64:

$ echo 'VGVtcGxhdGVzL0V4YW1wbGUuY3NodG1s' | base64 --decode
Templates/Example.cshtml

The source generator requires this config else the cshtml will be ignored when the analyzer runs: https://github.com/dotnet/razor/blob/main/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs#L73-L81

The metadata is managed by msbuild: https://github.com/dotnet/sdk/blob/main/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.SourceGenerators.targets#L48

Finally, the consuming project needs an assembly attribute otherwise MVC will not attempt to load templates from the assembly; this is added by msbuild: https://github.com/dotnet/sdk/blob/main/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets#L59

Seems like a bit of work is needed to replicate what msbuild does in the bazel rules; but might be helpful to support source generators in general because they can be used for other things as well like GRPC.

@purkhusid
Copy link
Collaborator

Thanks for looking into this @Place1! This looks like it should be decent amount of work but nothing crazy. I think the first step could be to get source generator support in place. I have limited time on my hands currently so I don't think I'll be working on an implementation anytime soon but I can help with this if anybody is up to the task.

@Place1
Copy link
Contributor

Place1 commented Aug 17, 2023

I'm still quite interested in this but I'm not particularly confident with bazel rule implementations. @purkhusid would you be able to write up some guidance for implementing this feature?

  • how would we reference the source generator dll from the sdk in bazel?
  • where would a user pass in their cshtml files (i.e. csharp_binary and csharp_library)?
  • how would the rules process the cshtml files to produce the required .editorconfig analyzer config file?
  • how would the final set of cshtml, editorconfig and source generators all be passed to the csc invocation?

@purkhusid
Copy link
Collaborator

I'm not too familiar with Razor nor Blazor but I think we need to do the following:

  • Make sure that source generators are working with the rules
    • This could be verified by adding a test that uses source generators
  • The razor/blazor source generator DLL can be added to the toolchain that contains the compilers and other SDK related files
  • I think we would need a separate rule for processing the cshtml files and then we could use the output of that rule as a input to the other csharp_* rules. If we can't split up the cshtml processing from the normal compilation rules then I think we can just allow .cshtml files in the srcs attribute of the csharp_* rules and then there would be special handling of them.

I unfortunately don't have time to do a deep dive on how the Razor compiler works but I can do my best to review.

@Siphonophora
Copy link

Siphonophora commented Sep 13, 2023

Hi I think I can add a little context on whats happening under the hood in blazor, or other source generators.

The nice part of whats happening with the blazor source generator is that it behaves just like any other source generator. And has the same constraints. In fact a lot of the way source generators were designed was driven by blazor tooling which was apparently very complicated before. In a source generator you get access to compiled c# and additional files. For a custom generator, you would be adding those additional files explicitly in the csproj. For blazor, it seems those are all implicitly included.

So, the source generators are all technically analyzers and get registered as such (like this, might be this lib specifically /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\source-generators\Microsoft.AspNetCore.Razor.Language.dll"). The generator gets access to the .razor and .cshtml as additional files (/additionalfile:App.razor).

Output below is from dotnet build --verbosity detailed. which I ran on a new blazor sever project with most files removed so it was just one page.

@purkhusid I'm just getting oriented to bazel, but I see you already have additionalfiles defined for these rules. It looks like you support analyzers too, so i'm wondering if the current rules really can't support blazor

   1:7>Target "CoreCompile" in file "C:\Program Files\dotnet\sdk\7.0.400\Roslyn\Microsoft.CSharp.Core.targets" from project "C:\Users\mjc82\source\repos\BlazorApp8\BlazorApp8\BlazorApp8.csproj" (target "Compi
       le" depends on it):
       Building target "CoreCompile" completely.
       Output file "obj\Debug\net7.0\BlazorApp8.dll" does not exist.
       Using "Csc" task from assembly "C:\Program Files\dotnet\sdk\7.0.400\Roslyn\Microsoft.Build.Tasks.CodeAnalysis.dll".
       Task "Csc"
         C:\Program Files\dotnet\dotnet.exe exec "C:\Program Files\dotnet\sdk\7.0.400\Roslyn\bincore\csc.dll" /noconfig /unsafe- /checked- /nowarn:1701,1702,1701,1702 /fullpaths /nostdlib+ /errorreport:prompt
          /warn:7 /define:TRACE;DEBUG;NET;NET7_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GR
         EATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER /highentropyva+ /nullable:enable /reference:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.10\ref\net
         7.0\Microsoft.AspNetCore.Antiforgery.dll" /reference:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.10\ref\net7.0\Microsoft.AspNetCore.Authentication.Abstractions.dll" /reference:"C:
         \Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.10\ref\net7.0\Microsoft.AspNetCore.Authentication.Cookies.dll" /reference:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.
         10\ref\net7.0\Microsoft.AspNetCore.Authentication.Core.dll" /reference:"C:\Program 

.... shortened.

Files\dotnet\
         packs\Microsoft.NETCore.App.Ref\7.0.10\ref\net7.0\WindowsBase.dll" /debug+ /debug:portable /filealign:512 /optimize- /out:obj\Debug\net7.0\BlazorApp8.dll /refout:obj\Debug\net7.0\refint\BlazorApp8.dl
         l /target:exe /warnaserror- /utf8output /deterministic+ /langversion:11.0 /analyzerconfig:obj\Debug\net7.0\BlazorApp8.GeneratedMSBuildEditorConfig.editorconfig /analyzerconfig:"C:\Program Files\dotne
         t\sdk\7.0.400\Sdks\Microsoft.NET.Sdk\analyzers\build\config\analysislevel_7_default.editorconfig" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Web\analyzers\cs\Microsoft.AspN
         etCore.Analyzers.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Web\analyzers\cs\Microsoft.AspNetCore.Mvc.Analyzers.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdk
         s\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnaly
         sis.NetAnalyzers.dll" /analyzer:"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.10\analyzers/dotnet/cs/Microsoft.Interop.JavaScript.JSImportGenerator.dll" /analyzer:"C:\Program Files\dot
         net\packs\Microsoft.NETCore.App.Ref\7.0.10\analyzers/dotnet/cs/Microsoft.Interop.LibraryImportGenerator.dll" /analyzer:"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.10\analyzers/dotnet
         /cs/Microsoft.Interop.SourceGeneration.dll" /analyzer:"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.10\analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll" /analyzer:"C:\Program
         Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.10\analyzers/dotnet/cs/System.Text.RegularExpressions.Generator.dll" /analyzer:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.10\anal
         yzers/dotnet/cs/Microsoft.AspNetCore.App.Analyzers.dll" /analyzer:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.10\analyzers/dotnet/cs/Microsoft.AspNetCore.App.CodeFixes.dll" /analy
         zer:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\7.0.10\analyzers/dotnet/cs/Microsoft.AspNetCore.Components.Analyzers.dll" /analyzer:"C:\Program Files\dotnet\packs\Microsoft.AspNetCore
         .App.Ref\7.0.10\analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\source-generators\Mic
         rosoft.AspNetCore.Mvc.Razor.Extensions.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\source-generators\Microsoft.AspNetCore.Razor.Language.dll" /analyzer
         :"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\source-generators\Microsoft.AspNetCore.Razor.Utilities.Shared.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Mi
         crosoft.NET.Sdk.Razor\targets\..\source-generators\Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\sour
         ce-generators\Microsoft.CodeAnalysis.Razor.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\source-generators\Microsoft.Extensions.ObjectPool.dll" /analyzer
         :"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.Sdk.Razor\targets\..\source-generators\Microsoft.NET.Sdk.Razor.SourceGenerators.dll" /analyzer:"C:\Program Files\dotnet\sdk\7.0.400\Sdks\Micro
         soft.NET.Sdk.Razor\targets\..\source-generators\System.Collections.Immutable.dll" /additionalfile:App.razor /additionalfile:Pages\Index.razor /additionalfile:_Imports.razor /additionalfile:Pages\_Hos
         t.cshtml Program.cs obj\Debug\net7.0\BlazorApp8.GlobalUsings.g.cs "obj\Debug\net7.0\.NETCoreApp,Version=v7.0.AssemblyAttributes.cs" obj\Debug\net7.0\BlazorApp8.AssemblyInfo.cs obj\Debug\net7.0\Blazor
         App8.RazorAssemblyInfo.cs /warnaserror+:NU1605,SYSLIB0011

@Place1
Copy link
Contributor

Place1 commented Oct 19, 2023

One issue i've run into while hacking on this is the editorconfig file which needs to be passed to the /analyzerconfig: flag to configure the razor source generator. It needs to contain a section for each cshtml source file but must have the absolute path.

In my previous comment the path was [/home/me/example-project/Project/Templates/Example.cshtml] but under bazel this will be a very long sandboxed path that changes between bazel actions; so I assume razor/blazor support must be added into the existing csharp_binary/library() rules so that this file can be part of the same execution sandbox that the csc action runs in.

The absolute path requirement seems to be by-design for analyzer config files: dotnet/roslyn#47707 (comment)

Can contain section headers, as long as the headers specify an absolute file path. Section headers based on globbing or relative file paths are ignored.

@purkhusid
Copy link
Collaborator

I think the compiler wrapper scripts we already have in place could be used to read the file that is passed to the /analyzerconfig and add the cshtml files to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants