Precompiled Views and CompilationCallback - asp.net

I've defined a compilation callback for my MVC views like
var mvc = services.AddMvc();
mvc.AddRazorOptions(razorSetup => {
razorSetup.CompilationCallback = context => {
// context.Compilation = context.Compilation.AddReferences(...)
};
});
This works great - and while debugging, there's no problem - but when the build-tools are precompiling the views (and as of asp.net core 2, precompiling is the default behavior), the compilation callback is just not used anymore.
While this makes perfect sense to me (after a bit of struggling), I wan't to know how/where I can configure the precompilation task.

I finally found a solution...
.net core 2
In .net core 2 you can add a --configure-compilation-type=-Option to the respone file used during compilation. The value should be a fully qualified type that implements Microsoft.AspNetCore.Mvc.IDesignTimeMvcBuilderConfiguration (not sure if it's necessarry or if it's sufficient to have a Configure-method here)
Adding can be done via
<Target Name="_AddMvzRazorPrecompileOptions" AfterTargets="_CreateResponseFileForMvcRazorPrecompile">
<Error Condition="!Exists($(_MvcRazorResponseFilePath))" Text="File $(_MvcRazorResponseFilePath) does not exist" />
<ItemGroup>
<_Option Include="--configure-compilation-type=KlugeSoftware.Web.ViewTranslator.PrecompileFactory, KlugeSoftware.Web.ViewTranslator" />
</ItemGroup>
<WriteLinesToFile File="$(_MvcRazorResponseFilePath)" Lines="#(_Option)" Overwrite="false" />
</Target>
.net core 3
Not that easy (at least I didn't find anything so far). But you can use a similar way to get your code between razor generation (creation of *.g.cs - files) and compiling them.
<Target Name="_DoSomethingSpecial" AfterTargets="PrepareForRazorCompile" BeforeTargets="RazorCoreCompile">
<!-- ... -->
</Target>
At this point, you can use a custom msbuild task to inspect or modify the generated code (and/or the source code).
Source files are stored in #(RazorCompile), dependencies in #(RazorReferencePath)....

Related

How to run dotnet tool in prebuild phase of an sdk project

As part of my build process I want to run a dotnet tool before the compile.
I can add this section to my sdk project file:
<ItemGroup>
<PackageDownload Include="MyTool" Version="[1.0.1]" />
</ItemGroup>
Then the tool is downloaded and is available inside:
\Users\me\.nuget\packages\MyTool\1.0.1\tools\netcoreapp3.1\any\
I can then add a prebuild target like this:
<Target Name="PreBuild" BeforeTargets="CoreCompile">
<Exec Command="dotnet C:\Users\me\.nuget\packages\MyTool\1.0.1\tools\netcoreapp3.1\any\MyTool.dll <MyOptions> />
</Target>
This works, but obviously I do not want absolute references to my user profile (or version) in the path.
Is there a way to substitute path with an environment variable?
I have tried adding GeneratePathProperty="true" to the PackageDownload but $(PkgMyTool) is undefined.
I also tried referencing the tool with <PackageReference> but this fails due to SDK incompatibility. My Tool is netcore3.1 and this project is netstandard2.0.
You can use only the macros provided by the framework. You can find them here. Almost all of them are referring to the relative path of your project. I suggest you to copy your tool inside a project folder and you can make use of these macros.
The best solution I had success with thus far is this:
<Target Name="PreBuild" BeforeTargets="CoreCompile">
<Exec Command="dotnet tool update MyTool --tool-path=$(TargetDir)\tools --version=1.0.1" />
<Exec Command="$(TargetDir)\tools\MyTool <MyOptions> />
</Target>
And it DOES save me from the nitty gritty details like \netcoreapp3.1\any but it does not reuse the tool from NuGet cache, meaning it has to be downloaded on every build.
I still hope someone will provide a better answer.

What Dotnet native directives to use for AsyncAwaitBestPractices.MVVM?

I'm using AsyncAwaitBestPractices.MVVM's IAsyncCommand and AsyncCommand in my Xamarin.Forms app. The UWP version of the app is compiled with .NET Native tool chain. When I do SomeAsyncCommand.RaiseCanExecuteChanged(), I get an exception:
System.Reflection.MissingMetadataException: 'This operation cannot be carried out because metadata for the following object was removed for performance reasons:\n\n EETypeRva:0x000976A0\n\nNo further information is available. Rebuild in debug mode for better information.\n\n'
Note that this was a debug build. When I added a local copy of the library, I was able to find the line that triggers the exception:
static bool IsLightweightMethod(this MethodBase method)
{
var typeInfoRTDynamicMethod = typeof(DynamicMethod).GetTypeInfo().GetDeclaredNestedType("RTDynamicMethod");
return ...
}
The exception is triggered by GetDeclaredNestedType("RTDynamicMethod"). So the binaries do include the metadata of DynamicMethod, but not that of it's child type RTDynamicMethod. They have been removed because of .NET Native tool chain.
Now, I read that you can whitelist classes / namespaces / assemblies in project properties -> Default.rd.xml. But I can't seem to get the right element to whitelist the nested class. Here's what I tried:
<Assembly Name="System.Private.CoreLib" Dynamic="Required All" />
<Namespace Name="System.Reflection.Emit" Dynamic="Required All" />
<Type Name="System.Reflection.Emit.DynamicMethod">
<Type Name="RTDynamicMethod" Dynamic="Required All"/>
</Type>
Here System.Private.CoreLib is the assembly of DynamicMethod, System.Reflection.Emit is the namespace of DynamicMethod and RTDynamicMethod. As far as I understand, either of the three should work, yet none of them do. Edit: the type one gives me a warning: Default.rd.xml(35): warning : ILTransform : warning ILT0027: Type 'System.Reflection.Emit.DynamicMethod' could not be found.
I also tried variations using Type Name="System.Reflection.Emit.DynamicMethod+RTDynamicMethod", using <Library>, with or without namespaces in type, etc.
At first, you can try to use the following code in the Default.rd.xml:
<Type Name="System.Reflection.Emit.DynamicMethod" Dynamic="Required All">
<Type Name="System.Reflection.Emit.RTDynamicMethod" Dynamic="Required All">
For more information, you can check the similar case and the issue on the github.
In addition, why did you add the tag .net 6.0? I have checked the package named System.Reflection.Emit, the last update time is 12/4/2019 and it does't support the .net 6.0.

How to precompile an asp.net web application with msbuild 16 and above?

The current state of affairs in our build:
We are building locally with a shared bin directory, just like a CI server build.
As a result web applications are published into $OutDir\_PublishedWebsites\<ProjectName>
We have manual implementation of the target to build the Asp.Net views just for the sake of validating them. It is not precompiling.
Here is the target:
<Target Name="Build"
DependsOnTargets="ResolveProjectReferences"
Condition="$(MvcBuildViews) != False And '$(MasterProject)' != '' And '$(MasterAsmName)' != ''"
Inputs="#(MvcBuildViewsInput)"
Outputs="$(MvcBuildViewsOutput)">
<PropertyGroup>
<WebProjectOutputDir>$(OutDir)_PublishedWebsites\%(ProjectReference.Filename)</WebProjectOutputDir>
</PropertyGroup>
<Message Text="Running AspNetCompiler for $(WebProjectOutputDir)" Importance="High" />
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
<WriteLinesToFile File="$(MvcBuildViewsOutput)" Lines="#(MvcBuildViewsInput)" Overwrite="True"/>
</Target>
This is, of course, an incomplete example, but it shows that we have our own ad-hoc implementation building the views to verify.
I would like to precompile the views and deploy them alongside the other binaries. Ideally, I would like to use the standard build targets and dump our ad-hoc target, but if it is too much pain, I am OK with the ad-hoc implementation.
What are the most recent instructions on the subject? I do not care about pre VS 2019.

How to compile Razor views at runtime

ASP.NET 5 MVC compiles Razor views at build time into assambly.
Some views can modifified by users after application is published.
How to include views in source code form (as cshtml files so that they can modified and compiled at runtime after modification ?
Update
Runtime compilation is enabled in Starup.cs.
Views directory does not appear in published output.
How to add Views as cshtml files so that they are compiled automatically at runtime.
Should Views directory created manually and cshtml files copied into it ? How to use those views in runtime by file name ?
How to update cshtml file in runtime and force application to use updated version?
You need to be a bit more specific with your question.
cshtml Views by default CAN be modified at runtime and do not need to be compiled for the changes to take effect,
But your question asks if they can be "compiled" at "runtime". Runtime happens after compilation, so you cannot compile at runtime.
I think the answer you're looking for is that cshtml files do not need to be compiled again if changed at runtime
Select "Enable Razor runtime compilation" while creating the project:
And here is the .csproj file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.5" />
</ItemGroup>
</Project>

ASP.NET MVC 1.0 AfterBuilding Views fails on TFS Build

I've upgraded from ASP.NET MVC Beta to 1.0 and did the following changes to the MVC project (as descibed in the RC release notes):
<Project ...>
...
<MvcBuildViews>true</MvcBuildViews>
...
<Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
...
</Project>
While the build runs fine on our local dev boxes, it fails under TFS 2008 Build with "Could not load type 'xxx.MvcApplication'", see below build log:
...
using "AspNetCompiler" task from assembly "Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "AspNetCompiler"
Command:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v temp -p D:\Builds\xxx\Continuous\TeamBuild\Sources\UI\xxx.UI.Dashboard\\..\xxx.UI.Dashboard
The "AspNetCompiler" task is using "aspnet_compiler.exe" from "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe".
Utility to precompile an ASP.NET application
Copyright (C) Microsoft Corporation. All rights reserved.
/temp/global.asax(1): error ASPPARSE: Could not load type 'xxx.UI.Dashboard.MvcApplication'.
The command exited with code 1.
Done executing task "AspNetCompiler" -- FAILED.
...
MVC 1.0 is installed on TFS and the solution compiles when built within a Visual Studio instance on the same TFS server.
How can I resolve this TFS Build issue?
Actually, there's a better solution to this problem. I've tested it with VS/TFS 2010 but it should also work with VS/TFS 2008.
<Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
I'm going to work with the MVC team to update their project template to to use this approach along with a custom target (rather than overriding AfterBuild).
I've published a blog post on How to Turn on Compile-time View Checking for ASP.NET MVC projects in TFS Build 2010.
The problem stems from the fact that the AspNetCompiler MSBuild task used within the AfterBuild target of an ASP.NET MVC project expects to reference the dll's in the bin folder of the Web project.
On a desktop build the bin folder is where you would expect it under your source tree.
However TFS Teambuild compiles the output of your source to a different directory on the build server. When the AspNetCompiler task starts it cannot find the bin directory to reference the required DLL and you get the exception.
Solution is to modify the AfterBuild target of the MVC Project to be as follows:
<Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler Condition="'$(IsDesktopBuild)' != 'false'" VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
<AspNetCompiler Condition="'$(IsDesktopBuild)' == 'false'" VirtualPath="temp" PhysicalPath="$(PublishDir)\_PublishedWebsites\$(ProjectName)" />
</Target>
This change enables you to compile Views on both the desktop, and the TFS build server.
Jim Lamb's solution didn't work for us when I built our web .csproj with
/p:UseWPP_CopyWebApplication=true;PipelineDependsOnBuild=False
because the target was being executed AfterBuild and the application has not been copied into the WebProjectOutputDir yet. (BTW, I pass those properties to the web project build cos I want the build to create a OutDir folder with only my binaries and cshtml files suitable for zipping, ie not an in-place build)
To get around this issue and honour the intent of his original target, I did the following:
<PropertyGroup>
<OnAfter_WPPCopyWebApplication>
MvcBuildViews;
</OnAfter_WPPCopyWebApplication>
</PropertyGroup>
<Target Name="MvcBuildViews" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
I assume you meant you changed the following setting in the .csproj file:
<MvcBuildViews>true</MvcBuildViews>
The setting you posted in your question shouldn't be touched.
If it works on your local machine, then obviously you can pre-build an ASP.NET MVC application.
I think you need to track down what's different between your TFS build environment and your local VS machines. Maybe it's using a different version of MsBuild or something.
Try performing both builds with verbose output and compare the two to see what's different.
We are still testing this out, but it appears that you can move the false/true from the tag set, into the property group for your DEBUG build version, you can still set it to true and MSBuild will compile (assuming MSBuild TfsBuild.proj file is setup to use something other than debug configuration). You will need to edit the csproj file using Notepad to accomplish this.
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<MvcBuildViews>true</MvcBuildViews>
....
You need to move the MVCBuildViews tag from the default property group above, to the debug configuration property group (below). Again, when we get the TFS / MSBuild setup, I'll try to post the step we added to our TFSBuild.proj file in TFS.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<MvcBuildViews>true</MvcBuildViews>
<DebugSymbols>true</DebugSymbols>
....
This problem seems similar to the one talked about here:
http://blogs.msdn.com/aaronhallberg/archive/2007/07/02/team-build-and-web-deployment-projects.aspx
it seems the invocation of aspnet_compiler.exe fails to locate the binaries because the are not in the bin folder of the MVC project on the build machine. I haven't worked out a solution yet.
The accepted answer didn't work for me. The $(PublishDir) parameter did not point to the correct location. Instead I had to use:
<Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler Condition="'$(IsDesktopBuild)' != 'false'" VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
<AspNetCompiler Condition="'$(IsDesktopBuild)' == 'false'" VirtualPath="temp" PhysicalPath="$(OutDir)\_PublishedWebsites\$(ProjectName)" />
</Target>
I had some old folders in my source control that were not visible in the Solution.
You cannot pre-build an ASP.NET MVC application.

Resources