I thought it would be interesting if I could use the new MVC Razor View engine as a mail merge technology. It can still be part of an MVC website and does not have to be stand-alone console app.
Example:
string myTemplate = "Hello @Name, How are you today?";
ViewModel.Name = "Billy Boy";
string output = RazorViewEngineRender( myTemplate, ViewModel );
Then the string output = "Hello Billy Boy, How are you today?"
The main thing is I want the template to be driven from a string rather than a view or partialview.
Does anyone know if this is possible ?
UPDATE:
Ben and Matt made a project on codeplex: http://razorengine.codeplex.com/
Warning
This is some ugly ugly code that was hacked together without testing it other than getting it to work properly.
VirtualPathProvider
Since we're not dealing with actual views on the server we have to add our own path provider to tell MVC where to get our dynamically generated templates. There should be more tests like checking the strings Dictionary to see if the view has been added.
public class StringPathProvider : VirtualPathProvider {
public StringPathProvider()
: base() {
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) {
return null;
}
public override bool FileExists(string virtualPath) {
if (virtualPath.StartsWith("/stringviews") || virtualPath.StartsWith("~/stringviews"))
return true;
return base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath) {
if (virtualPath.StartsWith("/stringviews") || virtualPath.StartsWith("~/stringviews"))
return new StringVirtualFile(virtualPath);
return base.GetFile(virtualPath);
}
public class StringVirtualFile : System.Web.Hosting.VirtualFile {
string path;
public StringVirtualFile(string path)
: base(path) {
//deal with this later
this.path = path;
}
public override System.IO.Stream Open() {
return new System.IO.MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(RazorViewEngineRender.strings[System.IO.Path.GetFileName(path)]));
}
}
}
Render Class
This class takes your template as a constructor parameter and adds it to a static Dictionary that is then read by the VirtualPathProvider
above. You then call Render
and you can optionally pass in a model. This will add the fully qualified model type to the @inherits
and prepend that to the file contents.
public class RazorViewEngineRender {
internal static Dictionary<string, string> strings { get; set; }
string guid;
static RazorViewEngineRender() {
strings = new Dictionary<string, string>();
}
public RazorViewEngineRender(string Template) {
guid = Guid.NewGuid().ToString() + ".cshtml";
strings.Add(guid, Template);
}
public string Render() {
return Render(null);
}
public string Render(object ViewModel) {
//Register model type
if (ViewModel == null) {
strings[guid] = "@inherits System.Web.Mvc.WebViewPage\r\n" + strings[guid];
} else {
strings[guid] = "@inherits System.Web.Mvc.WebViewPage<" + ViewModel.GetType().FullName + ">\r\n" + strings[guid];
}
CshtmlView view = new CshtmlView("/stringviews/" + guid);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.TextWriter tw = new System.IO.StringWriter(sb);
ControllerContext controller = new ControllerContext();
ViewDataDictionary ViewData = new ViewDataDictionary();
ViewData.Model = ViewModel;
view.Render(new ViewContext(controller, view, ViewData, new TempDataDictionary(), tw), tw);
//view.ExecutePageHierarchy();
strings.Remove(guid);
return sb.ToString();
}
}
Global.asax
In your global.asax file you'll have to add the following to the Application_Start
System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new Controllers.StringPathProvider());
Calling the code
string Template = "Hello, @Model.Name";
Models.User user = new Models.User() { Name = "Billy Boy" };
RazorViewEngineRender view = new RazorViewEngineRender(Template);
string Results = view.Render(user); //pass in your model
Notes
This only works with typed Models. I attempted to pass in a new { Name = "Billy Boy" } and it's throwing errors. I'm not sure why and didn't really look too deeply into it.
This was fun, thanks for asking this question.
Razor was designed with standalone operation in mind. There isn't much documentation about that mode yet (since it's all still under development) but have a look at this blog post by Andrew Nurse: http://vibrantcode.com/blog/2010/7/22/using-the-razor-parser-outside-of-aspnet.html