You should be aware that Razor is compiling .NET code, it can and will create security breeches in your application if you allow users to change the templates, you can alleviate some of these security issues by segregating your code and giving the razor section access only to the parts it needs, think this through.
The project contains a few important parts.
1. It should have its own TemplatesController, its just an empty controller for the engine to run against.
2. Templates views directory, this is where we're storing the templates for the engine to execute.
3. Templates.cs is where some of the magic happens.
4. For the sake of the demo, I've added a custom WebViewPage called RazorBaseWebViewPage and a SimpleModel.
Templates.cs contains the following:
1. Execute - executes a view against a controller, with ViewData and Model.
/// <summary> /// Generates a controller and context, hands them off to be rendered by the view engine and /// returns the result string /// </summary> /// <param name="viewName">Template Name</param> /// <param name="model">Model for the view</param> /// <param name="viewData">ViewData</param> /// <returns>rendered string</returns> private static string Execute(string viewName, ViewDataDictionary viewData, object model) { var controller = new TemplatesController(); controller.ControllerContext = new ControllerContext(); controller.ControllerContext.HttpContext = new HttpContextWrapper(HttpContext.Current); controller.RouteData.DataTokens.Add("controller", "Templates"); controller.RouteData.Values.Add("controller", "Templates"); controller.ViewData = viewData; return RenderView(controller, viewName, model); }
2. GetViewName - gets or writes a new template to the templates directory, it uses a hash of the template for the first part of the filename. Same trick as a hash table.
/// <summary> /// Retrieves the view name by template and model /// </summary> private static string GetViewName(string template,Type modelType) { //gets the razor template from a text template var razortemplate = GetViewContentFromTemplate(template, modelType); //gets the hash string from the razor template string hashstring = BitConverter.ToString(BitConverter.GetBytes(razortemplate.GetHashCode())); //check if view exists in folder var files = Directory.GetFiles(ViewDirectory, hashstring + "*.cshtml"); foreach (var file in files) { if (File.ReadAllText(file, Encoding.UTF8) == razortemplate) return Path.GetFileNameWithoutExtension(file); } //if not, add it string filename = Path.Combine(ViewDirectory, hashstring + "_" + Guid.NewGuid().ToString() + ".cshtml"); File.WriteAllText(filename, razortemplate,Encoding.UTF8); return Path.GetFileNameWithoutExtension(filename); }
3. RenderView - calls the razor engine's Render. executed from Execute. (Origin)
/// <summary> /// Renders a PartialView to String /// </summary> private static string RenderView(Controller controller, string viewName, object model) { //origin http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/ if (string.IsNullOrEmpty(viewName)) { return string.Empty; } controller.ViewData.Model = model; try { StringBuilder sb = new StringBuilder(); using (StringWriter sw = new StringWriter(sb)) { IView viewResult = GetPartialView(controller, viewName); ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult, controller.ViewData, controller.TempData, sw); viewResult.Render(viewContext, sw); } return sb.ToString(); } catch (Exception ex) { return ex.ToString(); } }
4. Render - the exposed method to do the actual rendering.
/// <summary> /// Renders a template with parameters to string /// </summary> /// <param name="template">template text to render</param> /// <param name="model">the model to give the template</param> /// <param name="parameters">the ViewData for the execution</param> /// <returns>rendered template</returns> public static string Render(string template, object model, ViewDataDictionary parameters) { //if empty if (string.IsNullOrEmpty(template)) return string.Empty; //if doesn't contain razor code if (template.IndexOf("@") == -1) return template; //get View filename string fileName = GetViewName(template, (model != null) ? model.GetType() : typeof(object)); //Execute template return Execute(fileName, parameters, model); }
I've ran some analysis on the code's performance, a few places might be helpful to optimize is the GetPartialView and GetViewName are slow.
You can find the project here:
https://github.com/drorgl/ForBlog/tree/master/RazorTemplateDemo