12

Is it true that it is possible to load View from anywhere without implementation of custom VirtualPathProvider in MVC 3.0?

If it is true how to do it?

Basically it is not a problem to implement custom VirtualPathProvider which would load the View from anywhere but my implementation working only in MVC 2.0 and not working wih MVC 3.0, fore some reason method GetFile newer called for not existed views in MVC 3.0 and in that case I am getting "Server Error in '/' Application."

I followed the same code for my custom VirtualPathProvider from here: http://buildstarted.com/2010/09/28/mvc-3-razor-view-engine-without-a-controller/

UPDATE 1

OK i did fix my problem with my custom VirtualPathProvider after i put registration of my custom VirtualPathProvider provider first line in the Application_Start()

    protected void Application_Start()
    {
        //Should be first line before routes and areas registration.
        HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());

        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

When registration of custom VirtualPathProvider in Global.asax.cs folowing after AreaRegistration.RegisterAllAreas(); or RegisterRoutes(RouteTable.Routes); method method override VirtualFile GetFile(string virtualPath) wont work for "virtual Views".

UPDATE 2

does it means that the classes RazorView and RazorViewEngineRender is the answer?

UPDATE 3

If i have string representation of my razor view which does not exists in the file system (e.g i store razor views in database) how may i render it using this kind of approach http://buildstarted.com/2010/09/28/mvc-3-razor-view-engine-without-a-controller/

For example string representation of my View looks like this:

"@{
    ViewBag.Title = ""About Us"";
}

<h2>About</h2>
<p>
     Put content here.
</p>"

UPDATE 4

Now i see, to be able to use @Html.<something> custom TemplateBase should be implemented. The sample of implementation of HtmlTemplateBase<T> could be fount here http://www.fidelitydesign.net/?p=239, but it won't work with RazorEngine v2, i am successfully getting template compiled, then after assembly loaded method public override void Execute() won't be executed i am getting an error: The method or operation is not implemented (stacktrace: http://tinypic.com/r/dcow4/7)

To make “public override T Model” happened i did change declaration of “public TModel Model” to “public virtual TModel Model” in “public abstract class TemplateBase : TemplateBase, ITemplate”. May be there is some another changes should be done? Or something in HtmlTemplateBase<T> should be done another way?

angularrocks.com
  • 26,767
  • 13
  • 87
  • 104

4 Answers4

6

Don't be confused by Ben's (@BuildStarted) sample code in his article. He is detailing how to use an early version of the Razor ViewEngine to render templates without using a controller action. The intention was to be able to render templates in a generic fashion, rather than as specific page views. (This is what has evolved into our RazorEngine templating framework @ http://razorengine.codeplex.com).

The VirtualPathProvider is still a core part of ASP.NET. There appears to be a general confusion about MVC 3's DependencyResolver being a replacement of a VirtualPathProvider but this is not the case, you still require a provider to be able to access content on a virtual path (which incidentally, all paths in ASP.NET are virtual).

Reverting my original answer, you should be able to achieve what you want purely through subclassing the RazorViewEngine and using that to create your views.

Have a look at this topic: http://coderjournal.com/2009/05/creating-your-first-mvc-viewengine/

Matthew Abbott
  • 60,571
  • 9
  • 104
  • 129
2

No, loading a view from the database is not supported by default. You need to write your own VirtualPathProvider.

Note that Ben's blog post does not actually address directly the problem that you are trying to solve. The following blog post looks a lot closer to what you want: http://rebuildall.umbraworks.net/2009/11/17/ASP_NET_MVC_and_virtual_views. Note that it does not matter if you are trying to store razor or aspx views in the database. Virtual path providers in Asp.Net are simply about mapping a path to a stream of bytes that are the contents of the file represented by that path.

marcind
  • 52,944
  • 13
  • 125
  • 111
2

I ran into a similar issue implementing a VirtualPathProvider for embedded resource views. The solution was to implement GetFolder as well as GetFile. The view engine doesn't just call GetFile when you request that view. On the first request it looks through the views folder to find all available views. If that call doesn't include your database views in the list, they won't be found when you try to load them.

Tom Clarkson
  • 16,074
  • 2
  • 43
  • 51
1

Everyone is correct. My post was not how to load Razor as a replacement but as a way to call razor without using MVC. Now...what you want is most likely related to my post here How to Download Razor View Engine Where I show how to create your own ViewEngine to host a razor page. It uses the same engine @Matthew Abbott and I use for the RazorEngine - which you can get from CodePlex. Unfortunately it's not complete but it should give you an idea on how to do it. (I'll post it here too)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Hosting;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace RazorViewEngine {
    /// <summary>
    /// ViewEngine for the RazorView. Provides basic file handling to load views. 
    /// </summary>
    public class RazorViewEngine : IViewEngine {

        string[] SearchLocations { get; set; }
        Tuple<string, string, RazorView> Cache { get; set; }
        VirtualPathProvider VirtualPathProvider { get; set; }

        public RazorViewEngine() {
            //{1} == Controller name
            //{0} == View name
            SearchLocations = new string[] {
                "~/Views/{1}/{0}.cshtml",
                "~/Views/Shared/{0}.cshtml",
            };

            VirtualPathProvider = HostingEnvironment.VirtualPathProvider;
        }

        #region IViewEngine Members

        public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) {
            return CreateView(controllerContext, partialViewName, null, null, useCache);
        }

        public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
            return CreateView(controllerContext, viewName, masterName, GetLayoutPath(controllerContext), useCache);
        }

        /// <summary>
        /// Meat of the FindView methods.
        /// </summary>
        /// <param name="controllerContext">The current controller context for this request.</param>
        /// <param name="viewName">The requested view name. </param>
        /// <param name="masterName">The master page view name (currently unused)</param>
        /// <param name="layoutPath">The layout path location (Replaces the masterpage in other view engines)</param>
        /// <param name="useCache">Cache the viewpage?</param>
        /// <remarks>The layout path is currently hardcoded to "Layout" and will look in the SearchLocations for that path</remarks>
        /// <returns>Returns a ViewEngineResult with the requested view</returns>
        public ViewEngineResult CreateView(ControllerContext controllerContext, string viewName, string masterName, string layoutPath, bool useCache) {
            //grab the current controller from the route data
            string controllerName = controllerContext.RouteData.GetRequiredString("controller");

            //for proper error handling we need to return a list of locations we attempted to search for the view
            string[] SearchedLocations;

            //get the actual path of the view - returns null if none is found
            string viewPath = GetViewPath(viewName, controllerName, out SearchedLocations);

            if (viewPath != null) {
                RazorView view = new RazorView(this, controllerContext, viewPath, layoutPath);
                return new ViewEngineResult(view, this);
            }

            //we couldn't find the view - return an array of all locations we've looked in
            return new ViewEngineResult(SearchedLocations);
        }

        /// <summary>
        /// Look for the view in the current file system
        /// </summary>
        /// <param name="viewName">The name of the View you're looking for</param>
        /// <param name="controllerName">Current controller name</param>
        /// <param name="SearchedLocations">out a list of locations searched</param>
        /// <returns>A string value of the relative path</returns>
        public string GetViewPath(string viewName, string controllerName, out string[] SearchedLocations) {
            return FindPath(viewName, controllerName, out SearchedLocations);
        }

        /// <summary>
        /// Look for the view in the current file system
        /// </summary>
        /// <param name="viewName">The name of the View you're looking for</param>
        /// <param name="controllerName">Current controller name</param>
        /// <param name="SearchedLocations">out a list of locations searched</param>
        /// <returns>A string value of the relative path</returns>
        public string FindPath(string viewName, string controllerName, out string[] SearchedLocations) {
            SearchedLocations = new string[SearchLocations.Length];

            for (int i = 0; i < SearchLocations.Length; i++) {
                string virtualPath = string.Format(SearchLocations[i], viewName, controllerName);

                SearchedLocations[i] = virtualPath;

                //check the active VirtualPathProvider if the file exists
                if (VirtualPathProvider.FileExists(virtualPath)) {
                    //add it to cache - not currently implemented
                    return VirtualPathProvider.GetFile(virtualPath).VirtualPath;
                }
            }

            return null;
        }

        /// <summary>
        /// Get the layout virtual path
        /// </summary>
        /// <param name="controllerContext">The current Controller context for this request</param>
        /// <returns>A string virtual path</returns>
        public string GetLayoutPath(ControllerContext controllerContext) {
            //This should probably be added to a list of locations - I'm not sure exactly
            //what I need to do with this yet.
            string[] locations;

            return FindPath("Layout", controllerContext.RouteData.GetRequiredString("controller"), out locations);
        }

        /// <summary>
        /// Current irrelevant
        /// </summary>
        /// <param name="controllerContext">The active controller context</param>
        /// <param name="view">View to release</param>
        public void ReleaseView(ControllerContext controllerContext, IView view) {
            IDisposable disposable = view as IDisposable;
            if (disposable != null) {
                disposable.Dispose();
            }
        }

        #endregion
    }

    /// <summary>
    /// Implements IView and renders a Razor
    /// </summary>
    public class RazorView : IView {

        ControllerContext ControllerContext;
        string ViewPath;
        string LayoutPath;
        RazorViewEngine Engine;

        public RazorView(RazorViewEngine engine, ControllerContext controllerContext, string viewPath, string layoutPath) {
            //load the file
            this.ControllerContext = controllerContext;
            this.ViewPath = viewPath;
            this.LayoutPath = layoutPath;
            this.Engine = engine;
        }

        #region IView Members

        /// <summary>
        /// Converts Razor to html and writes it to the passed in writer
        /// </summary>
        /// <param name="viewContext"></param>
        /// <param name="writer"></param>
        public void Render(ViewContext viewContext, System.IO.TextWriter writer) {
            //View contents
            string contents = new StreamReader(VirtualPathProvider.OpenFile(ViewPath)).ReadToEnd();
            string layoutContents = LayoutPath == null
                ? null
                : new StreamReader(VirtualPathProvider.OpenFile(LayoutPath)).ReadToEnd();

            contents = Parse(contents);

            string output;
            output = contents;

            writer.Write(output);
        }

        /// <summary>
        /// Converts Razor to html
        /// </summary>
        /// <param name="Razor">Razor text</param>
        /// <returns>Html formatted Razor text</returns>
        string Parse(string Razor) {

            //Where do I get the model From

            return RazorEngine.Razor.Parse(Razor);
        }

        #endregion
    }

}
Community
  • 1
  • 1
Buildstarted
  • 26,529
  • 10
  • 84
  • 95
  • @BuildStarted Reading updated @Matthew Abbott answer i thought that I'll need to do something like that, thanks for all details. – angularrocks.com Jan 19 '11 at 22:35
  • First what i did is to add custom engine(your implementation) like it is, after running my MVC 3.0 Web app i get an error Unable to compile template. Check the Errors list for details. Then i found that i am using not updated version of RazorEngine then after i downloaded v 2.0 i am getting the same error. Also i did read this thread http://razorengine.codeplex.com/Thread/View.aspx?ThreadId=235751, i have Microsoft.CSharp and System.Core referenced in my App and also System.Web.Razor.dll i set true to Copy Local. The template which won't compiled it is just default MVC3.0 Home/Index View. – angularrocks.com Jan 19 '11 at 23:17
  • Than i referenced RazorEngine as a project to step in... here is printscrin with more details about an error http://tinypic.com/r/rlx8cj/7, And also i found this error in results.Errors: CompiledAssembly = 'results.CompiledAssembly' threw an exception of type 'System.IO.FileNotFoundException' Is it somehow connected with full trust? – angularrocks.com Jan 19 '11 at 23:47
  • There's a new version of the RazorEngine that will be out soon that supports medium trust - if that's your issue. Though based on the exception it doesn't look like it. – Buildstarted Jan 20 '11 at 00:01
  • I did check PathToAssembly "C:\\Users\\\\AppData\\Local\\Temp\\.dll" this file is newer existed there. Currently i am looking/step in to the method private CompilerResults Compile(TypeContext context) but still have no idea why it is cant compile. – angularrocks.com Jan 20 '11 at 00:38
  • Ah you know what it could be. check the exception and see if there's a property called "Errors". It could be that the file can't be compiled. (Shouldn't result in that exception but it might be your virtual path provider not mapping correctly - hard to say without seeing the project) – Buildstarted Jan 20 '11 at 00:59
  • I think that is it. c:\Users\\AppData\Local\Temp\boozdpzi.0.cs(35,7): error CS0103: The name 'ViewBag' does not exist in the current context Thats why compiler cant compile that. I step though .NET Framework source code of CodeDomProvider. But ViewBag living in System.Web.Mvc namespace and looks like this assembly referenced i did check the value assemblies in here @params.ReferencedAssemblies.AddRange(assemblies); so CodeDomProvider should know about that ViewBag? – angularrocks.com Jan 20 '11 at 01:10
  • 1
    Yeah, it's because our Razor Engine is not the same as the one provided by MVC so you don't get any of the features of the MVC razor engine. You'd need to develop your own TemplateBase if you want to duplicate all the features - and at that point you might as well use the real Razor Parser – Buildstarted Jan 20 '11 at 01:37
  • Could you look at UPDATE 4, any thoughts? – angularrocks.com Jan 21 '11 at 05:19