App.Web
and App.Views
are my projects in one solution, I put my views in App.Views and precompiled with RazorGenerator. It's working well if I used App.Web
like,
~/Views/Index.cshtml
is virtual path of my view in App.View
It can successfully render this view in App.Web
public ActionResult Index() {
return View("~/Views/Index.cshtml");
}
But when I try to RenderViewToString
, it returns null
.
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://google.com", 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();
}
}
}
And this is how can all it,
FakeController.RenderViewToString("FakeName", "~/Views/Index.csthml", MessageModel);
This is discussed and probably solved in asp.net core, but I'm working with asp.net mvc 5.
Could you please help me to figure out, why it's not working?
You are trying to use Razor View Engine for getting precompiled views. This is a mistake. Razor engine produces views based on cshtml files. However in case of precompiled views, cshtml files have been already compiled by RazorGenerator
to a set of classes derivied from System.Web.Mvc.WebViewPage
. These classes override method Execute()
(autogenerated by RazorGenerator
based on input cshtml) that write html to output TextWriter
.
Original view files (cshtml) are not required anymore and thus are not deployed with the application. When you call Razor that tries to locate cshtml and build view based on it, it expectedly returns null
view.
ASP.NET MVC supports multiple view engines (the classes that implement System.Web.Mvc.IViewEngine
). RazorViewEngine
is one of them. RazorGenerator.Mvc
NuGet package adds its own view engine (PrecompiledMvcEngine
) that works based on precompiled views. Registered view engines are stored in ViewEngines.Engines
collection. When you install RazorGenerator.Mvc
NuGet package, it adds RazorGeneratorMvcStart
class that registers instance of PrecompiledMvcEngine
:
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(App.Views.RazorGeneratorMvcStart), "Start")]
namespace App.Views {
public static class RazorGeneratorMvcStart {
public static void Start() {
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly) {
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
}
You should use this instance of PrecompiledMvcEngine
instead of RazorViewEngine
for accessing precompiled views. Here is adjusted code of RenderViewToString
method:
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://google.com", null), new HttpResponse(null))), routeData, new FakeController());
var viewEngine = ViewEngines.Engines.OfType<PrecompiledMvcEngine>().FirstOrDefault();
if (viewEngine == null)
{
throw new InvalidOperationException("PrecompiledMvcEngine is not registered");
}
var viewResult = viewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, viewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
viewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
One important note: you should install RazorGenerator.Mvc
NuGet package into project with your views (App.Views
), not into Web application project, because PrecompiledMvcEngine
takes current assembly as the source for the precompiled views. Aslo make sure that RazorGeneratorMvcStart
was not added to App.Web
project. It happened for me when I have added reference to RazorGenerator.Mvc.dll
assembly.