aspnetcore: how to return a view from middleware - razor

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?

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.

Related

Running unit tests with mpirun using ant

I'm trying to run my unit tests through mpirun using ant. I have specified the task as:
<target name="unitTest" depends="buildUnitTest">
<mkdir dir="reports"/>
<junit fork="yes" jvm="mpirun java" printsummary="yes" haltonfailure="yes">
<classpath>
<pathelement location="./bin"/>
<pathelement location="/usr/share/java/junit4.jar"/>
</classpath>
<jvmarg value="-DDIM=3"/>
<jvmarg value="-ea"/>
<formatter type="plain"/>
<batchtest todir="reports">
<fileset dir="test">
<include name="haparanda/utils/*Test.java"/>
<include name="haparanda/iterators/*Test.java"/>
<exclude name="haparanda/iterators/FieldIteratorTest.java"/>
<include name="haparanda/grid/*Test.java"/>
</fileset>
</batchtest>
</junit>
</target>
Running eg:
mpirun java -ea -DDIM=3 -cp ./bin:/usr/share/java/junit4.jar org.junit.runner.JUnitCore haparanda.grid.ComputationalComposedBlockTest
from command line works fine. However, when I run:
ant unitTest
I get the following error:
BUILD FAILED
.../build.xml:28: Process fork failed.
Running ant with the verbose flag I get told that I got an IOException with the error message:
Cannot run program "mpirun java": error=2, No such file or directory
This is the case also when I specify the full path to mpirun and Java:
<junit fork="yes" jvm="/home/malin/bin/openmpi/bin/mpirun /usr/bin/java" printsummary="yes" haltonfailure="yes">
gives me:
.../build.xml:28: Process fork failed.
at ...
Caused by: java.io.IOException: Cannot run program "/home/malin/bin/openmpi/bin/mpirun /usr/bin/java": error=2, No such file or directory
How can i make this work?
This question is quite old and seems to have been successfully addressed in the comments by Gilles Gouaillardet. In the academic setting I am working in, I also attempted to use Junit with Java and MPI. I was unable to successfully use the trick proposed by Gilles Gouaillardet and I ended up with a quite different solution.
Custom Junit4 runner - general idea
An other way of running Junit tests with MPI consists in implementing a custom Junit runner.
In this custom Junit runner, instead of calling the Test methods "directly", you can launch your custom command using a ProcessLauncher. In my implementation, I made every MPI process use the normal Junit4 runtime to run the test methods. However, instead of using the normal RunNotifier of the Junit runtime, the MPI processes use my custom RunNotifier which writes the calls it receives to a file. A file with the calls of
Back in my custom runner, once the mpirun processes have finished, I aggregate the results of each test method of each MPI process and transmit those to the normal RunNotifier.
Benefits
With this system, you are staying within the "normal" Junit4 framework. In my case I was trying to run Junit tests from Maven. I can also integrate the test results with the Eclipse Junit view successfully (this required a few tricks that are not shown in the code excerpt below).
Here is a capture of my Eclipse environment after running the tests (the class names are slightly different than those presented in the excerpts below due to some additional complications for my particular environment).
Some selected code details
Only the most important parts of the custom MpiRunner and MpiTestLauncher are shown. Imports, try/catch structures and numerous details have been removed. I will eventually make the whole code available on GitHub but it isn't quite ready yet.
/** A test class using the custom "MpiRunner" */
#RunWith(MpiRunner.class)
public class TestUsingMpi {
#Test
public void test() {
assertTrue("Should run with multiple processes", MPI.COMM_WORLD.Size() > 1);
}
}
/** Curstom Junit4 Runner */
public class MpiRunner extends Runner {
// some methods skipped, try/catch blocks have been removed
#Override
public void run(RunNotifier notifier) {
// Build the command
final ArrayList<String> command = new ArrayList<>();
command.add("mpirun");
command.add("-np");
command.add(String.valueOf(processCount));
command.add("java");
// Classpath, UserDirectory, JavaLibraryPath ...
command.add("MpiTestLauncher "); // Class with main
command.add(testClass.getCanonicalName()); // Class under test as argument
ProcessBuilder pb = new ProcessBuilder(command);
File mpirunOutFile = new File("MpirunCommandOutput.txt");
pb.redirectOutput(Redirect.appendTo(mpirunOutFile));
pb.redirectError(Redirect.appendTo(mpirunOutFile));
Process p = pb.start(); // Launch the mpirun command
p.waitFor(); // Wait for termination
// Parse the notifications of each MPI process
for (int i = o; i < NbProcesses; i++) {
List<Notification> mpiRankNotifs = parse(i);
//Re-run those notifications on the parameter "notifier" of this method
for (Notification n : notifications) {
//Reconstitute the method call made in the mpi process
Class<?> paramClass = n.parameters[0].getClass();
Method m = RunNotifier.class.getDeclaredMethod(n.method, paramClass);
m.invoke(notifier, n.parameters);
}
}
}
}
/** Main class of the Mpirun java processes */
public class MpiTestLauncher {
public static void main(String[] args) throws Exception {
MPI.Init(args);
commRank = MPI.COMM_WORLD.Rank();
commSize = MPI.COMM_WORLD.Size();
Class<?> testClass = Class.forName(args[0]); // Class that contains the tests
String notificationFileName = testClass.getCanonicalName() + "_" +
commRank;
File f = new File(notificationFileName);
CustomNotifier notifier = new MpiApgasRunNotifier(f);
BlockJUnit4ClassRunner junitDefaultRunner = new BlockJUnit4ClassRunner(testClass);
junitDefaultRunner.run(notifier);
notifier.close(); //Flushes the underlying buffer
MPI.Finalize();
}
}

Second DB Context not usable in unit tests

After I managed to get multiple Database Context working in asp.net boilerplate (with some help here ASP.NET Boilerplate multiple databases and DbContexts), I ran into another problem. In the unit tests, when I use one of the additional database contexts like so:
using (var uow = this.UnitOfWorkManager.Begin())
{
var r = this.SlipLineRepository.GetAll().ToList();
}
I get this error:
Abp.AbpException : Could not resolve DbContextOptions for SlipStreamAPI.SlipStreamDB.miSlipLiveContext, SlipStreamAPI.EntityFrameworkCore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
So my questions are:
1) Is it possible to test against in memory DB contexts, if they are the second, third, etc context?
2) If it is, what should I do to make it work?
You should register DbContextOptions in ServiceCollectionRegistrar.cs:
public static void Register(IIocManager iocManager)
{
// ...
var builder = new DbContextOptionsBuilder<miSlipLiveContext>();
builder.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseInternalServiceProvider(serviceProvider);
iocManager.IocContainer.Register(
Component
.For<DbContextOptions<miSlipLiveContext>>()
.Instance(builder.Options)
.LifestyleSingleton()
);
}

Startup.cs error (ASP.Net Core configuration)

I am trying to set up an ASP.Net Core application to read in configuration settings from a json file. I am using VS2015 and .NetCore 1.0 (with .Net Core Tools preview 2). I am having problems getting a simple piece of boiler plate code to compile.
I am using the following code, which was published at
http://asp.net-hacker.rocks/2016/03/21/configure-aspnetcore.html
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
// This will push telemetry data through Application Insights
// pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
}
However, the IDE/compiler complains that 'the name "Configuration" does not exist in the current context' (last line of code). The only suggestion from the IDE is to include Microsoft.Extensions.Configuration. However this is a namespace which does not contain an object or property named "Configuration".
In addition 'AddApplicationInsightsSettings' fails with does IConfigurationBuilder not contain a definition for AddApplicationInsightsSettings and no extension method AddApplicationInsightsSettings accepting a first argument of type IConfigurationBuilder could be found
Any suggestions please ?
Thanks
Simply add Configuration property to your Startup class, tutorial has missed this 'step':
public IConfigurationRoot Configuration { get; set; }
ConfigurationBuilder.Build() method just returns instance of IConfigurationRoot, that you should save, if need to get settings further in Startup class (in ConfigureServices method for example).
Regarding second error, looks like you didn't add the Application Insights dependency:
{
"dependencies": {
"Microsoft.ApplicationInsights.AspNetCore": "1.0.0"
}
}

Razor page can't see referenced class library at run time in ASP.NET Core RC2

I started a new MVC Web Application project for the RC2 release and I'm trying to add a class library as a project reference.
I added a simple class library to my project and referenced it and got the following in the project.json file:
"frameworks": {
"net452": {
"dependencies": {
"MyClassLibrary": {
"target": "project"
}
}
}
},
I can use this library in any of the Controllers and the Startup.cs files without any trouble but I get the following error at run time when I try and use the library from a Razor page:
The name 'MyClassLibrary' does not exist in the current context
Output.WriteLine(MyClassLibrary.MyStaticClass.SomeStaticString);
It's weird because I'm getting intellisense for the class library when I'm editing the Razor page, and I can't find anything that says you can't use project references from here.
I thought it was hard enough getting this running under RC1 with the "wrap folder" in the class library project but this has me stumped.
A workaround has been posted on the issue page (cred to pranavkm and patrikwlund)
https://github.com/aspnet/Razor/issues/755
Apparently you need to explicitly add references to Razor compilation using RazorViewEngineOptions.CompilationCallback.
Add the following to your ConfigureServices method in your Startup class:
var myAssemblies = AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)).ToList();
services.Configure((RazorViewEngineOptions options) =>
{
var previous = options.CompilationCallback;
options.CompilationCallback = (context) =>
{
previous?.Invoke(context);
context.Compilation = context.Compilation.AddReferences(myAssemblies);
};
});
I had to filter out dynamic assemblies to avoid this runtime exception:
The invoked member is not supported in a dynamic assembly.
This worked for me:
var myAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => !x.IsDynamic)
.Select(x => Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(x.Location))
.ToList();
services.Configure((Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions options) => {
var previous = options.CompilationCallback;
options.CompilationCallback = (context) => {
previous?.Invoke(context);
context.Compilation = context.Compilation.AddReferences(myAssemblies);
};
});

How to fix "type exists in both assemblies" failure when using DynamicProxy types in an assembly referencing NSubstitute?

I have an application that uses DynamicProxy 3.1 to do runtime interception. I have a test assembly that uses NSubstitute for mocking. I just wrote some "integration" tests against my fully bootstrapped container (StructureMap using InterceptWith to do the interception) so that I can assert that certain types coming out of the container are proxied properly.
[Subject(typeof(RobotBoard))]
public class When_resolving_an_intercepted_type : WithContainer<IRobotBoard>
{
It should_have_recovery = () => Subject.ShouldHaveInterceptor<RecoveryInterceptor>();
}
public static class TestExtensions
{
public static void ShouldHaveInterceptor<T>(this object obj)
where T : IInterceptor
{
((IProxyTargetAccessor)obj)
.GetInterceptors()
.ToList()
.Exists(x => x is T)
.ShouldBeTrue();
}
}
However, I get this error, indicating that DynamicProxy references are inside the NSubstitute assembly, too! (it appears to be ilmerged).
Error 11 MyCompany.MyModule.Specifications D:\code\source\tests\When_resolving_an_intercepted_type.cs
The type 'Castle.DynamicProxy.IProxyTargetAccessor' exists in both 'd:\code\packages\Castle.Core.3.1.0\lib\net40-client\Castle.Core.dll' and 'd:\code\packages\NSubstitute.1.4.2.0\lib\NET40\NSubstitute.dll'
Is there anyway around this conflict?
You could grab the NSubstitute source code and remove the ilmerge commands from the project's targets. I've opened issue 86 on their repository to address this.
<exec command=""$(MSBuildProjectDirectory)\..\..\ThirdParty\Ilmerge\ILMerge.exe" /internalize:"$(MSBuildProjectDirectory)\ilmerge.exclude" /keyfile:$(AssemblyOriginatorKeyFile) /out:#(MainAssembly) "#(IntermediateAssembly)" #(AssembliesToMerge->'"%(FullPath)"', ' ')" Condition=" '$(TargetFrameworkVersion)' == 'v3.5'" />
<exec command=""$(MSBuildProjectDirectory)\..\..\ThirdParty\Ilmerge\ILMerge.exe" /internalize:"$(MSBuildProjectDirectory)\ilmerge.exclude" /keyfile:$(AssemblyOriginatorKeyFile) /out:#(MainAssembly) /targetplatform:"v4,$(FrameworkReferenceAssemblyPath)." "#(IntermediateAssembly)" #(AssembliesToMerge->'"%(FullPath)"', ' ')" Condition=" '$(TargetFrameworkVersion)' == 'v4.0'" />
You could try using an alias to reference the NSubstitute or DynamicProxy assemblies.
See MSDN How to: Use the Global Namespace Alias (C# Programming Guide) for more info.
You can use the 'extern alias' directive as explained here:
http://blogs.msdn.com/b/ansonh/archive/2006/09/27/774692.aspx
basically
(1) in VS, go to the assembly reference for FooVersion1, and right click > Properties.
(2) change 'aliases' value to 'FooVersion1'
(3) in your .cs file use:
extern alias FooVersion1;
using foo = FooVersion1::FooVersion1;
...
var something = foo.FooClass();