I've come across a little oddity, where on my development machine, everything works fine, but on my server, it does not.
public static string RenderViewToString(this Controller controller, string viewName, object model, string masterName)
{
if (string.IsNullOrEmpty(viewName))
viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
controller.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindView(controller.ControllerContext, viewName, masterName);
ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Using the above code, I am rendering a View to a string, which is then sent via email.
As you can see from the file list above, I have an Emails folder that contains a number of views and a _ViewStart.cshtml file.
The NewUser.cshtml sets #{ Layout = "~/Views/Shared/_email.cshtml" } (_email.cshtml has Layout = "" in the head) as well as uses the _ViewStart.cshtml to define the "master page" for the view.
Now... On my production server, when I try and render the NewUser.cshtml, it throws an error stating that it is missing a required section that is declared within the _public.cshtml layout.
On my development machine (using VS2010's built in server) - it works...
Any idea's?
Cheers
Related
I use this method to render views to html (which is required for generating pdfs).
public static class ControllerContextExtensions
{
public static string RenderViewToHtml(this ControllerContext context, string viewName, object model)
{
context.Controller.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(context,
viewName);
ViewContext viewContext = new ViewContext(context, viewResult.View, context.Controller.ViewData, context.Controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
string html = sw.GetStringBuilder().ToString();
string baseUrl = string.Format("{0}://{1}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority);
html = Regex.Replace(html, "<head>", string.Format("<head><base href=\"{0}\" />", baseUrl), RegexOptions.IgnoreCase);
return html;
}
}
However, when I changed model in view, it always fails (at Render(viewContext, sw); with phrase 'It requires model A, but passed model is of type B', even though partial view has declared model of type B.
Note: "ABC" partial view has #model InvoiceReportModelItem in it
As an experiment, I have passed empty model of BaseReportViewModel descendant type. Now it says it requires model of type which I tried to pass previously.
What can be the cause of the problem? I have tried to rename the file (maybe dictionary is cached or something but it didnt help)
So the problem was not in this view, but in its layout view model which was different... Yea, 'partial with layout', I know.
In my case view had InvoiceReportModelItem type of model, and its Layout - BaseReportViewModel.
I am trying to use a .cshtml template to send an email with RazorEngine. The documentation on their site shows how to use it with a string containing the razor syntax. How would I go about using it by loading a .cshtml file instead?
This is what I have
string templatePath = "~/Templates/InitialApplicationBody.cshtml";
var result = Engine.Razor.RunCompile(templatePath, "templateKey", null, viewModel);
From a MVC Controller, it's easy to generate HTML from a Razor view (CSHTML file).
I have successfully used code from the accepted answer to Render a view as a string, putting it in a base controller.
// Renders a Razor view, returning the HTML as a string
protected string RenderRazorViewToString<T>(string viewName, T model) where T : class
{
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext,
viewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View,
ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
The Mailzory project is a convenient choice for sending emails which have Razor templates. Mailzory uses RazorEngine behind the scene.
// template path
var viewPath = Path.Combine("Views/Emails", "hello.cshtml");
// read the content of template and pass it to the Email constructor
var template = File.ReadAllText(viewPath);
var email = new Email(template);
// set ViewBag properties
email.ViewBag.Name = "Johnny";
email.ViewBag.Content = "Mailzory Is Funny";
// send email
var task = email.SendAsync("mailzory#outlook.com", "subject");
task.Wait()
this project is hosted at Github. Also there is a nuget package available for Mailzory.
I am trying to render a partial view from a server side event (no request).
I found the following code which was able to render partial view without a controller.
Render MVC PartialView into SignalR response
public static string RenderPartialView(string controllerName, string partialView, object model)
{
var context = new HttpContextWrapper(System.Web.HttpContext.Current) as HttpContextBase;
var routes = new System.Web.Routing.RouteData();
routes.Values.Add("controller", controllerName);
var requestContext = new RequestContext(context, routes);
string requiredString = requestContext.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = controllerFactory.CreateController(requestContext, requiredString) as ControllerBase;
controller.ControllerContext = new ControllerContext(context, routes, controller);
var ViewData = new ViewDataDictionary();
var TempData = new TempDataDictionary();
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, partialView);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
This works fine when used after a client request (SignalR/websocket), but if the code is triggered by a server-side event (WCF connection to another app), HttpContext.Current = null which causes an error.
How can I make this work?
PS. I have also tried Razor Engine but I'm getting the error "Unable to compile template. The name 'Html' does not exist in the current context". Since the template also contains "#Html", it seems that the razor engine cannot parse the template outside a controller.
I was able to make it work using RazorEngine by removing all #Html tags from the partial view file.
string template = System.IO.File.ReadAllText(path);
string partialView = RazorEngine.Razor.Parse(template, model, "cachename");
http://forums.asp.net/t/1923611.aspx/2/10?Fake+HttpContext+ASP+NET+MVC+#5459457
So basically I have a partial view which can build a nice table for me. I would like to email this table out every week to my users. Instead of having to basically copy the template again, I would like to forward my model to the controller and receive the corresponding generated HTML as a String.
Is it possible to do this in a Controller, I feel it should be a pretty simple process.
Put this into a Helper file:
public static string RenderViewToString(ControllerContext context, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = context.RouteData.GetRequiredString("action");
ViewDataDictionary viewData = new ViewDataDictionary(model);
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
ViewContext viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
And then from the controller you can call it like this:
var order = orderService.GetOrder(id);
var orderPrint = MyHelper.RenderViewToString(this.ControllerContext, "_OrderView", order);
If you search for rendering partial views to strings you'll also come across some good leads. That's what I did to come up with the following extension method for the ControllerBase class:
public static string RenderPartialViewToString( this ControllerBase controller, string partialPath, ViewDataDictionary viewData = null )
{
if( string.IsNullOrEmpty(partialPath) )
partialPath = controller.ControllerContext.RouteData.GetRequiredString("action");
using( StringWriter sw = new StringWriter() )
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, partialPath);
ViewContext viewContext = new ViewContext(controller.ControllerContext,
viewResult.View,
( viewData == null ) ? controller.ViewData : viewData,
controller.TempData,
sw);
// copy retVal state items to the html helper
foreach( var item in viewContext.Controller.ViewData.ModelState )
{
if( !viewContext.ViewData.ModelState.Keys.Contains(item.Key) )
viewContext.ViewData.ModelState.Add(item);
}
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Conceptually, the procedure to follow involves using the ViewEngines defined for your app to find a partial view by its name. You then create a ViewContext off of that partial, and copy the various model state properties over to it.
The code assigns an optional ViewDataDictionary that you can provide to the ViewContext. If you don't provide the ViewDataDictionary it grabs the ViewDataDictionary defined for the controller that it's being called against.
What this means is that you can either define ViewData values (or ViewBag properties) directly in your controller and then call the extension method -- which will apply those ViewData/ViewBag properties to the partial when it gets rendered -- or you can create a separate ViewDataDictionary object in your action method and pass it to the extension method. The first is quicker/easier, but it "pollutes" the ViewData for your action method, while the second takes a little longer to set up but lets you keep your partial view data separate from your action method's ViewData.
Look into the MvcMailer project. Coupled with a partial view that renders your table, you should be able to pretty easily put together emails with your tables.
Render a view as a string
I use something simple like above but I almost always create separate views for the emails. Mainly due to the need to use absolute links and inserting the CSS into the head.
I'd like to have an html template I'm using for emails. I want to pass a model to it, have it rendered and returned to a string variable.
How can I accomplish this? I'm sure I can use the rendering that comes with asp.net mvc.
I do not want to do this inside my controller or using the controllercontext object.
Using a comment from #Mitch Rosenburg as the answer:
I'm using DotLiquid templates.
I haven't tried it myself, but I found a library called Postal that I'm planning to use for a very similar feature in my own apps.
You can do it like in the following method:
private string ViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
And in your action method you can use it like this
ViewToString("Index", model);
where model is the model that you want to send to your 'template'
I had to do the same thing once, and I ended up using HttpWebRequest to "render" the view and capture the result as a string.
This solution came with a whole host of other issues (getting the authorization cookie, powering through our custom url security layer, etc), and it felt inefficient and sloppy, but deadlines are deadlines, right?
Code looked something like this:
public string GetEmailBody(string url)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream);
return streamReader.ReadToEnd();
}
Hope it helps somebody.