Use ASP.NET MVC as Email Template Engine: Good Idea? - asp.net-mvc

Would it be a good idea, and executable, to use the ASP.NET MVC View Engine to render html to be sent out by email?
I know it's possible to let the view render into a string. So that could be use to build the mail message.
Since ASP.NET MVC is already used in the application, I get to use all practical ASP.NET MVC stuff without having to use Brail or NVelocity for my "mail views".
Good idea? Any caveats? Examples :) ?

Yes it is a good idea and relatively easy to implement.

I personally think it's a good idea. Definitely better than putting a piece of markup with placeholders into the database.
The disadvantage is that you will need Visual Studio to edit those templates then recompile and redeploy the project. You wouldn't be able to "outsource" working with templates to other non-technical staff.
And yes, adding new templates would also require your personal intervention.

Here,s my version of the RenderPartialToString as an extension method (which also takes care of paths etc..):
public static class ExtensionMethods
{
public static string RenderPartialToString(this ControllerBase controller, string partialName, object model)
{
var vd = new ViewDataDictionary(controller.ViewData);
var vp = new ViewPage
{
ViewData = vd,
ViewContext = new ViewContext(),
Url = new UrlHelper(controller.ControllerContext.RequestContext)
};
ViewEngineResult result = ViewEngines
.Engines
.FindPartialView(controller.ControllerContext, partialName);
if (result.View == null)
{
throw new InvalidOperationException(
string.Format("The partial view '{0}' could not be found", partialName));
}
var partialPath = ((WebFormView)result.View).ViewPath;
vp.ViewData.Model = model;
Control control = vp.LoadControl(partialPath);
vp.Controls.Add(control);
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
}
usage:
return this.RenderPartialToString("YourPartialView", yourModel);
hope this helps..
jim

You can use MVCMailer NuGet - it uses the MVC view templates and you just write a single line of code to do this!

Related

How to convert my html Razor view to pdf document without any third-party tool

I am just new in MVC 4. I have a html Razor view which contain all some table related data.
I just want to convert that view in to pdf document without third-party tool.
In case you are using ASP.NET Core here is my solution: http://nikolay.it/Blog/2018/03/Generate-PDF-file-from-Razor-view-using-ASP-NET-Core-and-PhantomJS/37
Get HTML string from a Razor view
This step is pretty straight-forward. There is a service called IRazorViewEngine in ASP.NET Core which can be injected and then used to get the view. After providing the view with default ViewDataDictionary and ActionContext we can request the view to be rendered into StringWriter which can be easily converted to string. Here is ready-to-use code for getting a string from given Razor view file:
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, object model);
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine razorViewEngine;
private readonly ITempDataProvider tempDataProvider;
private readonly IServiceProvider serviceProvider;
public ViewRenderService(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
this.razorViewEngine = razorViewEngine;
this.tempDataProvider = tempDataProvider;
this.serviceProvider = serviceProvider;
}
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var httpContext = new DefaultHttpContext { RequestServices = this.serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var sw = new StringWriter())
{
var viewResult = this.razorViewEngine.GetView(null, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
var viewDictionary =
new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary()) { Model = model };
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, this.tempDataProvider),
sw,
new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
}
One important think here: if you use view compilation (pre-compiling views to YourProject.Web.PrecompiledViews.dll) then it is important to get the view using the GetView method instead of FindView. More information here.
Generate the PDF file from HTML using PhantomJS
For this task we are going to use a headless browser which will render the HTML (with all CSS and JS included in it). There are many such tools but I will use PhantomJS (headless WebKit scriptable with a JavaScript API). PhantomJS can save the rendered page to small-sized PDF pretty fast. For the PDF export to work we are going to need a .js file which will use the PhantomJS API to tell the tool that we want to export the file:
"use strict";
var page = require('webpage').create(),
system = require('system'),
address,
output;
console.log('Usage: rasterize.js [URL] [filename] [paperformat]');
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = { format: system.args[3], orientation: 'portrait', margin: '0.5cm' };
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
The next thing is to run the phantomjs.exe process and pass the rasterize.js file along with paths for the HTML file and the output file name for the PDF result. This is done in HtmlToPdfConverter.cs:
public interface IHtmlToPdfConverter
{
byte[] Convert(string htmlCode);
}
public class HtmlToPdfConverter : IHtmlToPdfConverter
{
public byte[] Convert(string htmlCode)
{
var inputFileName = "input.html";
var outputFileName = "output.pdf";
File.WriteAllText(inputFileName, htmlCode);
var startInfo = new ProcessStartInfo("phantomjs.exe")
{
WorkingDirectory = Environment.CurrentDirectory,
Arguments = string.Format(
"rasterize.js \"{0}\" {1} \"A4\"",
inputFileName,
outputFileName),
UseShellExecute = true,
};
var process = new Process { StartInfo = startInfo };
process.Start();
process.WaitForExit();
var bytes = File.ReadAllBytes(outputFileName);
File.Delete(inputFileName);
File.Delete(outputFileName);
return bytes;
}
}
If you are going to deploy your application in Azure it is important to have UseShellExecute set to true.
Use the code together
Since we now have implemented both IViewRenderService and IHtmlToPdfConverter we can start using them by first register them in the Startup.cs file where your ConfigureServices method should be located (services.AddScoped<IViewRenderService, ViewRenderService>() and services.AddScoped<IHtmlToPdfConverter, HtmlToPdfConverter>()). Now lets see the code wrapped up together:
private readonly IViewRenderService viewRenderService;
private readonly IHtmlToPdfConverter htmlToPdfConverter;
public DashboardController(
IViewRenderService viewRenderService,
IHtmlToPdfConverter htmlToPdfConverter)
{
this.viewRenderService = viewRenderService;
this.htmlToPdfConverter = htmlToPdfConverter;
}
[HttpGet]
public async Task<IActionResult> GetPdf(SomeInputModel input)
{
var model = this.GetViewModel(input);
var htmlData = await this.viewRenderService.RenderToStringAsync("~/Views/Dashboard/GetPdf.cshtml", model);
var fileContents = this.htmlToPdfConverter.Convert(htmlData);
return this.File(fileContents, "application/pdf");
}
Since ASP.NET MVC has no built-in PDF functionality, the only way to convert a Razor view to PDF without a third party tool would be to write the conversion code yourself. This would be a massive undertaking and almost certainly not worth the effort. I would start by purchasing the ISO 32000-1 reference document and learning everything I could about how the PDF format works. At the time of this answer, the cost is around 200 Swiss francs.
MVC4 - even if it is the name of your Frameworks should be understood as the pattern called Model-View-Controller (MVC). The Idea of this pattern is to separate the these three components in order to replace them with different channels whenever reqeuired.
In your case, the Razor Template is a tool to generate a VIEW in HTML. Razor is limited to HTML - your pattern isn't. By design of MVC you can use any other tool to replace the VIEW from a HTML to a PDF, XML or whatever you desire.
what you should be looking for is a way, to use your existing MODEL (not VIEW) and generate a PDF output, using any library needed.

MVC2 to MV4 conversion error

I have a project in MVC2, I have converted it to MVC3 then I have converted it to MVC4, I have converted aspx views to Razor Views using this tool,
I also installed MVC3 and MVC4, after converting my application compiled successfully,But when I run the application its showing error
Unable to cast object of type 'Microsoft.Web.Mvc.FixedRazorViewEngine'
to type 'System.Web.Mvc.WebFormViewEngine'.
In Global.asax
private static void AddViewPaths()
{
foreach (var engine in ViewEngines.Engines)
{
((WebFormViewEngine)engine).PartialViewLocationFormats =
((WebFormViewEngine)engine).PartialViewLocationFormats.Union(
new string[] { "~/Views/Sample/{0}.aspx", "~/Views/Sample/{0}.ascx" }).ToArray();
((WebFormViewEngine)engine).ViewLocationFormats =
((WebFormViewEngine)engine).ViewLocationFormats.Union(
new string[] { "~/Views/Sample/{0}.aspx", "~/Views/Sample/{0}.ascx" }).ToArray();
}
}
I tried changing these lines to this,but still same issue,what should I do?
((WebFormViewEngine)engine).PartialViewLocationFormats =
((WebFormViewEngine)engine).PartialViewLocationFormats.Union(
new string[] { "~/Views/Sample/{0}.cshtml", "~/Views/Sample/{0}.cshtml" }).ToArray();
((WebFormViewEngine)engine).ViewLocationFormats =
((WebFormViewEngine)engine).ViewLocationFormats.Union(
new string[] { "~/Views/Sample/{0}.cshtml", "~/Views/Sample/{0}.cshtml" }).ToArray();
The problem is I am having both Razor and WebForms in ViewEngines.Engines Collection,How can I only get Razor Views
Since you have moved to razor (which was not available in MVC 2), you now have more than just the WebFormsViewEngine available in your for loop. Your code, as written now, it attempting to cast a Razor engine to a WebForms engine, which cannot be done. If you update the hard casting and remove all other engines, it should work and you can get rid of the for loop - you only have one engine in your collection.
Application_Start
//clear all but RazorViewEngine
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
Now your updated code would simply be
var engine = ViewEngines.Engines.First();
((RazorViewEngine)engine).PartialViewLocationFormats =
((RazorViewEngine)engine).PartialViewLocationFormats.Union(
new string[] { "~/Views/Sample/{0}.cshtml", "~/Views/Sample/{0}.cshtml" }).ToArray();
((RazorViewEngine)engine).ViewLocationFormats =
((RazorViewEngine)engine).ViewLocationFormats.Union(
new string[] { "~/Views/Sample/{0}.cshtml", "~/Views/Sample/{0}.cshtml" }).ToArray();
If you decide not to remove all of the engines, then you are going to have to do an if/else inside of your for loop to determine which way to cast the engine.

Proper way to initialize gettext in runtime in asp.net / monodevelop

I am trying to get localization to work in an asp.net mvc project using monodevelop on mac. I have added a translation project and translated the text 'Welcome' in danish.
public class HomeController : Controller
{
public ActionResult Index ()
{
var culture = CultureInfo.CreateSpecificCulture("da");
System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
System.Threading.Thread.CurrentThread.CurrentCulture = culture;
Mono.Unix.Catalog.Init("i8n1", "./locale");
ViewData ["Message"] = Mono.Unix.Catalog.GetString("Welcome");
return View ();
}
}
But the text does not get translated.
Any ideas?
You'll need the full path to your locale folder.
MonoDevelop does something like this (edited for brevity)
string location = System.Reflection.Assembly.GetExecutingAssembly().Location;
location = Path.GetDirectoryName(location);
string catalogPath = Path.Combine (location, "locale");
Catalog.Init ("monodevelop", catalogPath);
Mono.Unix.Catalog is not suitable for ASP.NET. It uses a 'per environment' approach whereas for ASP.NET you need a 'per thread' approach.
This library is definitely worth a look as an alternative http://sourceforge.net/p/gettextnet/
For reference: http://lists.ximian.com/pipermail/mono-devel-list/2008-March/027174.html
The answer is here: http://mono.1490590.n4.nabble.com/Mono-Unix-Catalog-Init-where-does-it-get-the-locale-from-td1532586.html
public static void Main (string[] args)
{
var culture = CultureInfo.CreateSpecificCulture ("de");
Thread.CurrentThread.CurrentCulture = culture;
Environment.SetEnvironmentVariable ("LANGUAGE", "de_DE");
Catalog.Init ("i8n1", "./locale");
Console.WriteLine (Catalog.GetString("Hello World!"));
}
And it works for me on Ubuntu/Mono. Thanks to Vladimir for good question and to Jonathan for great answer.

Using HtmlTextWriter in ASP.NET MVC

I am migrating some old code where HtmlTextWriter is used extensively to render UI elements.
I am migrating the code to use ASP.NET MVC 1.0. As far as I am aware, I am not using any of the HtmlTextWriter specific function (such as indentation).
Currently, I am using a wrapper method to return string generated by the HtmlTextWriter as follow:
var sw = new StringWriter();
var xhtmlTextWriter = new XhtmlTextWriter(sw);
GenerateHtml(xhtmlTextWriter);
return sw.ToString();
My questions are:
I am trying to get HtmlTextWriter instance from ASP.NET MVC View, but apparently even the HtmlHelper does not use this. Do I miss anything?
Each call to GenerateHtml will generated small HTML pieces, generally not bigger than 1000 characters, but there can be a lot of calls. Is it worth rewriting the HtmlTextWriter dependent code into StringBuilder? Or instead, what about creating a HtmlTextWriter instance which will be used on all calls (and flushed at the end of the iterations).
Instead of creating a StringBuider and StringWriter I think using the helper.ViewContext.writer will work.
Then the above sample code would be:
var calendar = new DayPilotCalendar();
if( model != null )
{
model.CopyTo( calendar );
}
if( options != null )
{
options.CopyTo( calendar );
}
HtmlTextWriter writer = new HtmlTextWriter( helper.ViewContext.Writer );
writer.AddAttribute( HtmlTextWriterAttribute.Class, "dayPilot" );
writer.RenderBeginTag( HtmlTextWriterTag.Div );
calendar.RenderControl( writer );
writer.RenderEndTag(); // Close DIV
return( null ); // Don't need to return anything.
Disclaimer: So far I've only tried using the helper.ViewContext.Writer to produce a <UL> list. It worked fine. Haven't tried it to render controls.
I have a demo app here which shows how to do this in an MVC app.
Here's a code sample, from that post.
public static string DayPilot(
this HtmlHelper helper,
DayPilotData model,
DayPilotViewOptions options)
{
var calendar = new DayPilotCalendar();
if (model != null)
{
model.CopyTo(calendar);
}
if (options != null)
{
options.CopyTo(calendar);
}
var sb = new System.Text.StringBuilder();
sb.Append("<div class=\"dayPilot\">"); // allows working around td cellpadding bug in css
using (var sw = new System.IO.StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
calendar.RenderControl(tw);
}
}
sb.Append("</div>");
return sb.ToString();
}
Regarding #2, if it ain't broke...

Using ASP.NET MVC Html Helpers in a standard web forms project

So for example in the code behind of a web form aspx page I would like to be able to do things like
string textBoxHtml = Html.TextBox("MyTextBox");
Is this possible?
Is the source code available to fork for webforms?
Possible? Yes.
The entire MVC source is available at:
http://www.microsoft.com/downloads/details.aspx?FamilyID=53289097-73ce-43bf-b6a6-35e00103cb4b&displaylang=en
Good luck!
You'll quickly find that pulling bits of code out of MVC is like only wanting a banana and getting the gorilla holding it. ;)
Here's something that is working for me so far.
public static class PageCommon
{
public static System.Web.Mvc.UrlHelper GetUrlHelper(this System.Web.UI.Control c)
{
var helper = new System.Web.Mvc.UrlHelper(c.Page.Request.RequestContext);
return helper;
}
class ViewDataBag : IViewDataContainer
{
ViewDataDictionary vdd = new ViewDataDictionary();
public ViewDataDictionary ViewData
{
get
{
return vdd;
}
set
{
vdd = value;
}
}
}
public static System.Web.Mvc.HtmlHelper GetHtmlHelper(this System.Web.UI.Control c)
{
IViewDataContainer x;
var v = new System.Web.Mvc.ViewContext();
var helper = new System.Web.Mvc.HtmlHelper(v, new ViewDataBag());
return helper;
}

Resources