ReBuildAll Blog
Thoughts (mostly) on .NET development

ASP.NET MVC and virtual views   (MVC)   
Update 1.3.2011: I have created another example for virtual views that you might be interested in. It shows you how to use virtual views by serving them directly from a .ZIP file.

In this post I will describe how to create virtual views for ASP.NET MVC. These are views that do not exist as files in the regular place in the file system (~/Views/xxx/yyy.aspx), instead they are stored somewhere. Elsewhere in this case can be a database or perhaps a .ZIP file. This is all made possible by the VirtualPathProvider class (and some supporting classes) in ASP.NET. While the example will use MVC framework and views, the class provides much more. With it you can virtualize any web content (css, gif/jpg, js, aspx, etc) and it can be used in any ASP.NET application, nut just MVC.

Use cases

When would you need such a virtual view system?

If you want users to be able to customize Views, but without them having access to the Web solution or source code. In this case the user could upload the View into the database, where it is stored. When the application wants to display the view, instead of reading the view source from the file system it will be read from the database. MVC will not know the difference and believes it is found under the regular ~/Views/... path.

Another possibility would be to use a ZIP file based virtual path provider. Here the user could create a custom skin by creating or customizing views (.ascx, .aspx) and adding new content (css, images). The user would then pack it into a ZIP, upload into the server, and server would serve the custom skin directly from the .ZIP file. This way with multiple skins installed the file system would not be polluted by a vast amount of files.

There are of course many more possibilities I could think of, but I hope you get the point ;-)

Getting ready

Before we begin, lets create a simple database table that will store the data for our example. It is very badly designed I have to admit :), but will serve our purpose.

I called the table Pages and the idea is that it will serve dynamic pages into my MVC application. Kind of like a very simple CMS. However, I want to customize the view layout (.aspx), and not just the data itself. So I ended up with a table like this:

a picture in a bl0g

The table contains the data for the page (Body, Title) and the name/virtual path of the view (ViewName) and of course the view file itself (ViewData, uploaded as binary). Here is an example row. I uploaded a simple .ASPX view into the row.

a picture in a bl0g

In my example solution I added a LINQ to SQL .dbml to the solution (named MyMvcVp), dragged and dropped my table into the designer. I could then use the generated data context to access the database.

Controller

Next is my PagesController.cs that will serve our dynamic content pages. To display such a page I decided to use default route already available in the MVC template: /controller/action/id. I added a single Display action to the controller which will get the id parameter as a string value. This will have to correspond to the PageId field in the database. For example: /Pages/Display/d9b07a02-7c47-41d9-8c21-bf546841bb6c. The resulting code follows:

    public class PagesController : Controller
    {
        public ActionResult Display ( string id )
        {
            Guid guid = new Guid ( id );

            MyMvcVpDataContext context = new MyMvcVpDataContext ();

            var res =
                (from p in context.Pages
                 where p.PageId == guid
                 select p).SingleOrDefault ();

            if ( res == null )
            {
                return RedirectToAction ( "Index", "Home" );
            }

            ViewData["Title"] = res.Title;
            ViewData["Body"] = res.Body;

            return View ( System.IO.Path.GetFileNameWithoutExtension ( res.ViewName ) );
        }
    }

The code is very simple: it uses LINQ to look for the page, and if found, we extract the data from it, and instruct MVC to show it using our ViewName. To make the example very simple I included the virtual path in the database column, so here I use GetFileNameWithoutExtension() to get just the View name.

Because I wanted to keep the example simple I decided not to support virtual folders (which is also possible). So the folder where the virtual files seem to reside needed to be created also. I created a new Pages folder under /Views in the solution. If you implement virtual folders than this step could be left out.

At this point we still miss the actual VirtualPathProvider implementation. Without that you will get an error message when accessing this action, because MVC will not find the View.

VirtualPathProvider
So I added a new file MyVirtualPathProvider.cs to the solution. This contains the MyVirtualPathProvider class, that derives from VirtualPathProvider. I overrided two methods, FileExists() and GetFile(). Both get the virtual path (application relative). The first checks if a file exists, and the second returns the file from the file system - in this case the database.

Here is the code I used. Note that Page in this case refers to the LINQ class that was generated for the Pages table, and not the ASP.NET Page class.

    public class MyVirtualPathProvider : VirtualPathProvider
    {
        public override bool FileExists ( string virtualPath )
        {
            var page = FindPage ( virtualPath );
            if ( page == null )
            {
                return base.FileExists ( virtualPath );
            }
            else
            {
                return true;
            }
        }

        public override VirtualFile GetFile ( string virtualPath )
        {
            var page = FindPage ( virtualPath );
            if ( page == null )
            {
                return base.GetFile ( virtualPath );
            }
            else
            {
                return new MyVirtualFile ( virtualPath, page.ViewData.ToArray () );
            }
        }

        private Page FindPage ( string virtualPath )
        {
            MyMvcVpDataContext context = new MyMvcVpDataContext ();

            var page =
                (from p in context.Pages
                 where p.ViewName == virtualPath
                 select p).SingleOrDefault ();
            return page;
        }
    }

As you can see if I don't find the virtual path in question from the database, the base implementation is called. That will just try to look up the file from the regular file system location.

You should notice that I used a class that I did not yet talk about: MyVirtualFile. This is just a simple implementation of the VirtualFile abstract class. Its purpose is to return a Stream where the file can be read. In my example I will just return a MemoryStream using the data from the database table. But this is where you would read the file from the actual place and pass it back to the framework.

    public class MyVirtualFile : VirtualFile
    {
        private byte[] data;

        public MyVirtualFile ( string virtualPath, byte[] data ) : base ( virtualPath )
        {
            this.data = data;
        }

        public override System.IO.Stream Open ()
        {
            return new MemoryStream ( data );
        }
    }

If you wanted to support virtual directories you would also override the DirectoryExists() and GetDirectory() methods from the VirtualPathProvider. You would also need a custom VirtualDirectory derived class.

Registering the custom VirtualPathProvider

As a last step the MyVirtualPathProvider must be registered with ASP.NET. To accomplish this you need to add the following call to the application:

HostingEnvironment.RegisterVirtualPathProvider ( new MyVirtualPathProvider () );

The best place is probably in the Init() override of the application class or perhaps the Application_Start event handler (global.asax, unless you coded a custom application class somewhere in which case that would be the ideal place).

Conclusion

Virtual files and directories can be used to extend an existing MVC application (or a Web Forms application) by allowing you to store files in a place that is different from the regular file system. Be it a database, ZIP file or a remote network location, by using VirtualPathProvider the rest of the application will not have to know anything about the actual storage strategy.

 

Comments

Vignesh Re: ASP.NET MVC and virtual views
how to create cshtml page dynamically in C#
kochobay Re: ASP.NET MVC and virtual views
Dear Lenard,

Does this work for partial views as well?

Best regards
Jake O Re: ASP.NET MVC and virtual views
Anyone have any luck doing this with Razor HTML?
Andreas Re: ASP.NET MVC and virtual views
Was able to get it to work:

http://forums.asp.net/p/1993252/5721960.aspx?p=True&t=635391100341768325&pagenum=1

Andreas Re: ASP.NET MVC and virtual views
Hello,

I agree with Wednell, I have not been able to get it to work with MVC5.

I posted this sample with Mvc5:
http://forums.asp.net/t/1990471.aspx?GetFile+not+beeing+called+in+VirtualPathProvider+Mvc5+

GetFile is never called and an exception is thrown as soon as FileExists returns true and a File does not exist.

I do not know where to go from here, Kind blessings

ali Re: ASP.NET MVC and virtual views
hi
my virtualpathprovider work when i run project with visual studio and iis express
but not work when i publish this on iis
plz help
Lenard Gunda Re: ASP.NET MVC and virtual views
Hi Wednell,

I played around in MVC 5 recently with compiling and serving Razor views from a .DLL. I will have to check what I did, because I did not run into any trouble there. Of course there I did much more than just serve views as I needed to compile them as well, but the basics should be the same. Let me get back to you on this one.

/Lenard
Wednell Re: ASP.NET MVC and virtual views
Maybe it worked in mvc4 but in mvc5 this does not work GetFile is never called. If FileExits returns true asp.net is bypassed and IIS assumes it can get it which results in a:

HTTP Error 404.0 - Not Found

The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

Any suggestions?
amir Re: ASP.NET MVC and virtual views
LIKEEEEEE
Re: ASP.NET MVC and virtual views
Could u please post the source code of the example
Lenard Gunda Re: ASP.NET MVC and virtual views
Hi Mohsen,

Thank you for your feedback! I will try to fit in the time to publish the source code for this article sometime in the future.

/Lenard
Mohsen Re: ASP.NET MVC and virtual views
Hi Lenard
Your articles are great but I can't use them!
Is it possible put sample application with download link in your articles?
Lenard Gunda Re: ASP.NET MVC and virtual views
Hi Anthony,

I don't think you should use my approach of virtual views to create dynamic menus for users.

You are talking about menus, so I assume you have a repeating list of elements, maybe on multiple levels (sub-menus). I would recommend doing only one view to display your menu structure.

What I would then do in the model or controller, is filter the list based on the role of the user. Maybe even load only those elements (from the datastore) which the current user has rights to.

By including this kind of logic in your Views, you are going against the Model-View-Controller pattern. Your view should be dumb - just display the things it gets. Simple if statements are acceptable or things that affect how things look ("is this a green button or a blue button").

But doing conditional logic that affects WHAT is displayed is something I would leave to the Model itself. The Menu view should already get a filtered list of the items to be displayed.
anthony fernandes Re: ASP.NET MVC and virtual views
Very interesting and thank you for the article.
I wanted to use this for creating dynamic menus and needed your advice
Option 1:
If (role==admin)
load partial_view_admin
if(role==user)
load partial_view_user
if(role==agent)
load partial_view_agent
This requires me to create x views for x roles. So this is out

Method 2:

The other approach was to render the menu options dynamically in the partial view

MenuObj = getmenudetailsformdb() /// cache once read from the db
if(role==admin)
menu.show
menu.action = db.action;
menu.controller = db.controller


Method 3:

and the third is the way you have shown in the example above

if(role==admin)
load partial view dynamically for admin from the db and cache the result.

I was wondering if you could help me decide between method 2 and 3 with regards to performance and ease of maintenance. With method 2, I still need to create the menu object from the cache well as in case of method 3, I load the partial view itself from the cache. Hence the confusion.
Please advise.

Thanks!
Lenard Gunda Re: ASP.NET MVC and virtual views
Kevin, I did not try this with Razor yet, so I cannot say how you would do that.

wirol, I don't understand your question. Validation would work the same way in the views that are served from the database as they would work with regular views?

Andrej, good idea to try with partial views.

Maybe I will revisit the subject given how much time passed since the article was published and add support for Razor and partial views :)
Kevin Boss Re: ASP.NET MVC and virtual views
Hey, I just wanted to ask you if there is any solution to implement the namespaces which are usually served by the Web.config file. I have to call methods from Razor but i can't since i don't have anything to call.
wirol Re: ASP.NET MVC and virtual views
This is a very nice approach to use dynamic virtual views.
But how could we use the dataannotaion/validation attributes in this scenarios?

Or how should we design the model validation for virtual views?

Thanks.
Marcos Lima (marcoslimagon) Re: ASP.NET MVC and virtual views
I will try to implement this post using MVC 3 + RAZOR + caching and also trying run the controller by some kind of F# "Engine", to make a easy to go approach... this post gave me useful crazy ideas =)

Thanks!
Andrej Re: ASP.NET MVC and virtual views
Have you thought about extending the code to render partial views?
Lenard Gunda Re: ASP.NET MVC and virtual views
@Brian,

Are you using IIS hosting or the VS development web server? Or perhaps IIS Express?

I created this example some while back, but created a new one with .zip files that I published yesterday. As I was testing that (the .zip version) I specifically used features like ViewBag to verify functionality was not lost. Have you tried running that example? You can download that in source code format as well.
Brian Re: ASP.NET MVC and virtual views
The loading part works great, but when the view executes, it can't find Razorish concepts like ViewBag.

The name 'ViewBag' does not exist in the current context

Any suggestions?
Lenard Gunda Re: ASP.NET MVC and virtual views
Now you can also check out another VirtualPathProvider example by me that uses .zip files for storage.
Lenard Gunda Re: ASP.NET MVC and virtual views
@Jayakumar,
You probably wanted to ask me about the working code? I don't know if I have the original code stored that I used for this article. If I can find it, I will try to post it online.
Jayakumar Re: ASP.NET MVC and virtual views
Hi Khalid, nice article, is there any you can give us working project as an attachment with this article please?

Thank you
AJ Hoffer Re: ASP.NET MVC and virtual views
Adam Lawrence:

Have you tried overriding both the "GetCacheDependency" AND "GetFileHash"?

http://stackoverflow.com/questions/3747858/asp-net-mvc-2-virtualpathprovider-getfile-every-time-for-every-request
Lenard Gunda Re: ASP.NET MVC and virtual views
Hi Andrew!

Thanks for the heads up, nice to hear you found this useful! Looking forward to reading the book :)
Andrew Siemer Re: ASP.NET MVC and virtual views
This seems to hit the database quite frequently. I am thinking that a list of excluded paths and/or file extensions should be stored somewhere to bypass the number of queries made against the db. Anyways - very nice post. I will probably include some parts of this (along with your name and a link back to this article) in my upcomming book "ASP.NET MVC Cookbook". You can find a public preview of the book here if your interested: http://groups.google.com/group/aspnet-mvc-2-cookbook-review Your bits will be covered in chapter 7!

Thanks!
Adam Lawrence Re: ASP.NET MVC and virtual views
I have implemented something just like this for a CMS system, the idea being that the virtual view would always be refreshed from the Database, this works nicely when my application is first strated after that the open() overriden method in my virtualfile class is never hit when I put a break point in after the initial time. I have tried adding a cachedependency to expire the cached item but nothing works. Any ideas?
Adrian Re: ASP.NET MVC and virtual views
Apologies - my mistake. My data store / VPP combination was returning a file when it should have fallen through to the base methods, therefore bypassing the controller.

Next time, I'll test a bit more thorougly before I shout for help.

Thanks for the post!
Lenard Gunda Re: ASP.NET MVC and virtual views
Abhi:
I will try to make an example with ZIP files, sometime in the future.

Adrian:
As far as I recall, it should be possible to put view logic into the views using this approach. I will have to get back to you on this one, as soon as I get back home on friday.
Adrian Re: ASP.NET MVC and virtual views
Hi - Found this post potentially useful, but, my stumbling block is that I don't appear to be able to put View Logic inside my View (as stored in the DB), ie. <%=DateTime.Now%> renders as <%=DateTime.Now%> and doen't show the current datetime.

Is this possible using this approach - for the record, I am using the current beta MVC 2.

Apert from my method of storage, all my code is identical to that provided in this post.
Abhi Re: ASP.NET MVC and virtual views
Thanks for the post; a very thoughtful idea - storing MVC views in the DB. Although I found the idea to read views from the zip file for dynamic themes to be a more practical implementation. But yeah this could be a good beginning for creating a CMS based on ASP.NET MVC on the lines of gosted/unghosted pages in MS Sharepoint.
Alex Re: ASP.NET MVC and virtual views
Khalid, what were the issues you experienced? I am curious as I have done this on several occasions and been happy but may not have run into all the potential issues. One must be careful to use caching appropriately for decent performance since pages change rarely and thus the hits to the database should be rare as well.
Khalid Abuhakmeh Re: ASP.NET MVC and virtual views
Pretty good example, terrible idea to store your views in the database.

I'm having flashbacks now to previous projects where the idea of storing ASP.NET pages in a database sounded like a good idea.

I would have liked to see the implementation of the views stored in a zip file, that would be kind of cool.