I have the following code that is executed by Hangfire (there is no HttpContext) and works perfectly when I run it locally:
class FakeController : ControllerBase
{
protected override void ExecuteCore() { }
public static string RenderViewToString(string controllerName, string viewName, object viewData)
{
using (var writer = new StringWriter())
{
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext = new ControllerContext(
new HttpContextWrapper(
new HttpContext(new HttpRequest(null, "http://nopage.no", null)
, new HttpResponse(null))
),
routeData,
new FakeController()
);
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
}
The way we have our application set up however is as follows:
https://application.net
<- One application
https://application.net/admin
<- The other application that this code runs on.
When I run the code on https://application.net/admin
, I get the following exception:
System.ArgumentException: The virtual path '/' maps to another application, which is not allowed.
It occurs at this line: razorViewEngine.FindView(fakeControllerContext, viewName, "", false)
.
I tried creating my own RazorViewEngine and overrode some of the methods to find the views, to no avail.
class MyViewEngine : RazorViewEngine
{
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
if (!base.FileExists(controllerContext, virtualPath))
return base.FileExists(controllerContext, "~/../admin" + virtualPath.TrimStart('~'));
return true;
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var newViewPath = viewPath;
if (!base.FileExists(controllerContext, viewPath))
newViewPath = "~/../admin/" + viewPath.TrimStart('~');
return base.CreateView(controllerContext, newViewPath, masterPath);
}
}
This failed because it did not want to let me out of the "upmost part of the directory tree".
Is there an easy fix for this, or an alternate way of creating strings from razor views? - The purpose is to create email templates. There will be many emails created, and I don't want to use a HttpClient to ask my own endpoint to create it.
After a lot of trying and failing I ended up abandoning my approach, using Rick Strahl's WestWind.RazorHosting library instead.
Here's the most important part of the documentation I used.
And here is how my code ended up looking at the end.
class FakeController : IDisposable
{
private readonly RazorFolderHostContainer _host;
public FakeController()
{
_host = new RazorFolderHostContainer
{
TemplatePath = $@"{HostingEnvironment.ApplicationPhysicalPath}Views\EmailTemplate\"
};
_host.AddAssemblyFromType(typeof(Controller));
_host.AddAssemblyFromType(typeof(EmailTemplateController.TestViewModel));
_host.Start();
}
public string RenderViewToString(string viewName, object viewData)
{
return _host.RenderTemplate($@"~/{viewName}.cshtml", viewData);
}
public void Dispose()
{
_host.Stop();
}
}
This looks for views in my EmailTemplate
folder that's placed in Views
, finding *.cshtml files that I want to use. Since it gets them from the Views
folder, I'm still able to use the same views with the normal controllers, and hence it will work when I use the views in my endpoints too. Nifty.