Online courses in ASP.NET MVC / Core, jQuery, Angular, and Design Patterns conducted by Bipin Joshi. Read more...
Learn ASP.NET MVC / Core, jQuery, Angular, and Design Patterns through our online training programs. Courses conducted by Bipin Joshi on weekends. Read more details here.

Load Views From Database in ASP.NET MVC

ASP.NET MVC organizes view files in the Views folder. This arrangement works fine for most of the applications. However, in some cases you may need to load views from database rather than physical disk file. Consider an example wherein you are building a portal or a content management system. You may want to allow the administrator to create or modify views and then you may want to load these views dynamically. Such dynamically created / modified views may not be a nice fit into the folder based arrangement of MVC. In such cases the recommended way is to store views in a database and load them on the fly. This article discusses how t his can be accomplished.

The Views database table

In order to load views from a database you first need to create a table (I have named it - Views) as shown below:

You can add the Views table in any existing database or create a database in App_Data specifically to house Views table. The Views table has four columns - Id, ViewName, ViewPath and ViewContent. The ViewName column stores a developer friendly name of a view. This name is purely for your own use and identification. The ViewPath column stores the path of a view from the root folder. For example, /Views/Home/Index.cshtml. Note that ViewPath is not a ~ qualified path. This simplifies your code that checks existence of a view (you will see that later). The ViewContent column holds the actual content of a view. This can be HTML markup and / or Razor code.

Entity Framework data model to read view data

Next, you need to have some way to read the data stored in the Views table shown above. You can write a POCO using plain ADO.NET or use EF model to do that. I my example I am using EF generated model. You also need to add a model class for the Employees table of the Northwind database. Again, this can be a POCO or EF designer generated class. I am using EF designer generated class. The two EF entities mentioned above are shown in the following figure:

Controller and view content

Now, add Home controller to the Controllers folder and modify its Index() action method as follows:

public ActionResult Index()
{
    NorthwindEntities db=new NorthwindEntities();
    List<Employee> model = db.Employees.ToList();
    ViewBag.Message = "This view is loaded from database!";
    return View(model);
}

The Index() action simply pulls all the Employee records into a List and passes that List to the view. It also sets Message property on the ViewBag. The Message property has been added just for the sake of testing.

Then add Index view to the project and modify it as shown below:

@model List<ViewsInDbDemo.Models.Employee>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <h1>List of Employees</h1>
    <h2>@ViewBag.Message</h2>
    <table border="1" cellpadding="10">
        @foreach(var item in Model)
        {
            <tr>
                <td>@item.EmployeeID</td>
                <td>@item.FirstName</td>
                <td>@item.LastName</td>
            </tr>
        }
    </table>
</body>
</html>

The Index view is quite straightforward and simply outputs the model data (Employees) and ViewBag message (Message) to the response stream. You can run the application and see whether the view displays the Employee data and Message as expected.

Now comes the important part! Copy this whole view content and paste it into the ViewContent column of the Views table that you created earlier. And then DELETE this Index.cshtml file. That's because we want to load views from database and not from physical disk files. Also set the ViewPath column to /Views/Home/Index.cshtml.

Creating Custom VirtualPathProvider and VirtualFile

The key part of our solution is the creation of a custom VirtualPathProvider class and a VirtualFile class. A VirtualPathProvider does the job of picking the views from disk, database or any other location. In our case it will pick views from database. The views picked from database are wrapped in a custom VirtualFile so that MVC framework can further deal with it.

To create a VirtualPathProvider class, add a new class to the project as shown below:

public class BinaryIntellectVirtualPathProvider : VirtualPathProvider
{
    public override bool FileExists(string virtualPath)
    {
        var view = GetViewFromDatabase(virtualPath);

        if (view == null)
        {
            return base.FileExists(virtualPath);
        }
        else
        {
            return true;
        }
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        var view = GetViewFromDatabase(virtualPath);

        if (view == null)
        {
            return base.GetFile(virtualPath);
        }
        else
        {
            byte[] content = ASCIIEncoding.ASCII.
                             GetBytes(view.ViewContent);
            return new BinaryIntellectVirtualFile
                          (virtualPath,content);
        }
    }

    public override CacheDependency GetCacheDependency
     (string virtualPath,Enumerable virtualPathDependencies, 
      DateTime utcStart)
    {

        var view = GetViewFromDatabase(virtualPath);

        if (view !=null)
        {
            return null;
        }

        return Previous.GetCacheDependency(virtualPath, 
           virtualPathDependencies, utcStart);
    }

    private View GetViewFromDatabase(string virtualPath)
    {
        virtualPath = virtualPath.Replace("~", "");

        ViewsDbEntities db = new ViewsDbEntities();
        var view = from v in db.Views
                    where v.ViewPath == virtualPath
                    select v;
        return view.SingleOrDefault();
    }
}

The BinaryIntellectVirtualPathProvider class inherits from VirtualPathProvider base class. The VirtualPathProvider class resides in System.Web.Hosting namespace. It then overrides a few methods of the base class namely FileExists() and GetFile(). There is also a helper method GetViewFromDatabase().

The GetViewFromDatabase() method retrieves a requested view from the database (Views table) and returns it to the caller. The same method can be used to determine whether a view exists in the database or not. For example, if GetViewFromDatabase() returns null it indicates that a requested view doesn't exist in the Views table.

The FileExists() overridden method checks whether a view exists in the database. If it doesn't then it calls the FileExists() of the base class, otherwise it returns true.

The GetFile() overridden method does the job of retrieving a view from the database and returning it to the caller. The method first checks whether the requested view exists in the Views table. If it doesn't then it calls the GetFile() method of the base class. If the view exists in the database it creates a new instance of  BinaryIntellectVirtualFile class (discussed shortly). The virtual path and the view content (ViewContent column) are passed to the constructor of the BinaryIntellectVirtualFile class. Notice that the view content is passed after converting it into a byte array. This is because BinaryIntellectVirtualFile class writes this content into a MemoryStream and MemortStream requires its content as byte array.

Now add a custom VirtualFile class class as shown below:

public class BinaryIntellectVirtualFile : VirtualFile
{
    private byte[] viewContent;

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

    public override Stream Open()
    {
        return new MemoryStream(viewContent);
    }
}

The BinaryIntellectVirtualFile class inherits from VirtualFile base class (System.Web.Hosting) and overrides Open() method. Notice that the constructor of BinaryIntellectVirtualFile receives view content as a byte array. The Open() method constructs a new MemoryStream based on this byte array and returns the MemoryStream to the caller.

Register the VirtualPathProvider

The final step is to register the custom VirtualPathProvider with the MVC framework. You do this in Global.asax as shown below:

protected void Application_Start()
{
  ...
  HostingEnvironment.RegisterVirtualPathProvider
    (new BinaryIntellectVirtualPathProvider());
}

To register a custom VirtualPathProvider you use HostingEnvironment class from System.Web.Hosting namespace. The RegisterVirtualPathProvider() method accepts a new instance of a VirtualPathProvider class (BinaryIntellectVirtualPathProvider in my example).

 You can run the application and see the view loaded from database in action. Here is a sample run:

That's it for now! Keep coding.




Bipin Joshi is a software consultant, trainer, author and a yogi having 21+ years of experience in software development. He conducts online courses in ASP.NET MVC / Core, jQuery, AngularJS, and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced Yoga way of life he also teaches Ajapa Meditation to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 03 Mar 2015



Tags : ASP.NET Data Access MVC C#