Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP MVC3 - How to load a custom user defined layout for the page from a database?

I have online form builder appplication in ASP.NET MVC3 with Razor views. It is similar to this - https://examples.wufoo.com/forms/workshop-registration/

I need users to be able to customize the page design. Not only to upload a custom css, but also to customize the HTML page template. Let's say users should have complete control on Layout's HTML for their custom webform page. User should be able to edit any HTML on the page, beside the form that is included into the layout.

I'm not sure how to do that with Razor and ASP.NET MVC 3. Is it possible to:

  1. load layout somewhere from database as string or whatever
  2. replace some custom tags like "FORM1_INCLUDE" to @Html.Partial("some_non_customizable_layout_for_form1")
  3. use the result as a valid Layout file for the user's form page

Maybe 1-3 is not the best way to do what I need. What can you suggest for such user defined page layout approach in ASP.NET MVC 3 with Razor views?

UPDATE 1 Using VirtualPathProvider I was able to load View from database, but it just returns text like:

@inherits System.Web.Mvc.WebViewPage
<body>
@Html.EditorFor(z => z.Customer)
</body>

and doesn't process any Razor syntax at all. What could be the problem with it?

SOLVED:

Needed to place this line as the first one in Application_Start() method:

HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());

UPDATE 2 Custom View Provider is registered in Global.asax.cs as:

protected void Application_Start()
        {
            HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());

            AreaRegistration.RegisterAllAreas();

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

MyVirtualPathProvider code is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.IO;
using System.Text;

namespace new_frontend
{
    public class MyVirtualPathProvider : VirtualPathProvider
    {
        public override bool FileExists(string virtualPath)
        {
            var td = FindTemplate(virtualPath);
            if (td == null)
            {
                return true;
                //return base.FileExists(virtualPath);
            }
            else
            {
                return true;
            }
        }

        public override VirtualFile GetFile(string virtualPath)
        {
            var td = FindTemplate(virtualPath);
            if (td == null)
            {
                return new MyVirtualFile(virtualPath, "");
                //return base.GetFile(virtualPath);
            }
            else
            {
                return new MyVirtualFile(virtualPath, td.ContentStep1);
            }
        }

        private Facade.Dto.TemplateData FindTemplate(string virtualPath)
        {
            string prefix = "Template#";

            int id = 0;
            Facade.Dto.TemplateData td = null;

            string fileName = System.IO.Path.GetFileNameWithoutExtension(virtualPath);
            if (fileName.StartsWith(prefix))
                Int32.TryParse(fileName.Substring(prefix.Length), out id);

            if (id > 0)
                td = Facade.FrontEndServices.GetTemplate(id);

            return td;
        }
    }

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

        public MyVirtualFile(string virtualPath, string body)
            : base(virtualPath)
        {                      // 'System.Web.WebPages.ApplicationStartPage
            string _body = /*body +*/ @"

@inherits System.Web.Mvc.WebViewPage
@using (Ajax.BeginForm(""Submit"", new AjaxOptions { UpdateTargetId = ""main"" }))
{
}
<!-- <PERSONAL_INFO> -->
<div id=""personal_info"" class=""op2-block"">

</div>
<!-- <PERSONAL_INFO> -->";

            this.data = Encoding.UTF8.GetBytes(_body);
        }

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

And now for the Razor view code defined as a string above I get this exception:

"Compiler Error Message: CS1061: 'System.Web.Mvc.AjaxHelper' does not contain a definition for 'BeginForm' and no extension method 'BeginForm' accepting a first argument of type 'System.Web.Mvc.AjaxHelper' could be found (are you missing a using directive or an assembly reference?)"

And when I changed Razor View code to:

string _body = /*body +*/ @"

@using System.Web.WebPages;
@using System.Web.Mvc;
@using System.Web.Mvc.Ajax;
@using System.Web.Mvc.Html;
@using System.Web.Routing;

@inherits System.Web.Mvc.WebViewPage<dynamic>

@using (Ajax.BeginForm(""Submit"", new AjaxOptions { UpdateTargetId = ""main"" }))
{
}
<!-- <PERSONAL_INFO> -->
<div id=""ppg_op2_personal_info"" class=""op2-block"">

</div>
<!-- <PERSONAL_INFO> -->";

I got a different error:

Type 'ASP._Page__appstart_cshtml' does not inherit from 'System.Web.WebPages.ApplicationStartPage'

When I change

@inherits System.Web.Mvc.WebViewPage

to

@inherits System.Web.WebPages.ApplicationStartPage

to fix the error above, I get a new one:

"Compiler Error Message: CS0103: The name 'Ajax' does not exist in the current context"

UPDATE3: I tried to use base.XXX:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.IO;
using System.Text;

namespace new_frontend
{
    public class MyVirtualPathProvider : VirtualPathProvider
    {
        public override bool FileExists(string virtualPath)
        {
            var td = FindTemplate(virtualPath);
            if (td == null)
            {
                //return true;
                return base.FileExists(virtualPath);
            }
            else
            {
                return true;
            }
        }

        public override VirtualFile GetFile(string virtualPath)
        {
            var td = FindTemplate(virtualPath);
            if (td == null)
            {
                //return new MyVirtualFile(virtualPath, "");
                return base.GetFile(virtualPath);
            }
            else
            {
                return new MyVirtualFile(virtualPath, td.ContentStep1);
            }
        }

        private Facade.Dto.TemplateData FindTemplate(string virtualPath)
        {
            string prefix = "Template#";

            int id = 0;
            Facade.Dto.TemplateData td = null;

            string fileName = System.IO.Path.GetFileNameWithoutExtension(virtualPath);
            if (fileName.StartsWith(prefix))
                Int32.TryParse(fileName.Substring(prefix.Length), out id);

            if (id > 0)
                td = Facade.FrontEndServices.GetTemplate(id);

            return td;
        }
    }

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

        public MyVirtualFile(string virtualPath, string body)
            : base(virtualPath)
        {                      // 'System.Web.WebPages.ApplicationStartPage
            string _body = /*body +*/ @"

@inherits System.Web.Mvc.WebViewPage<PPG.Facade.Dto.NewOrderPageData>
@using (Ajax.BeginForm(""Submit"", new AjaxOptions { UpdateTargetId = ""main"" }))
{
}
<!-- <PERSONAL_INFO> -->
<div id=""personal_info"" class=""op2-block"">

</div>
<!-- <PERSONAL_INFO> -->";

            this.data = Encoding.UTF8.GetBytes(_body);
        }

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

In this case I get a view that is not parsed at all, this is what I get in the web browser:

@using System.Web.WebPages;    
@using System.Web.Mvc;    
@using System.Web.Mvc.Ajax;    
@using System.Web.Mvc.Html;    
@using System.Web.Routing;    
@inherits System.Web.Mvc.WebViewPage<PPG.Facade.Dto.NewOrderPageData>
@using (Ajax.BeginForm("Submit", new AjaxOptions { UpdateTargetId = "main" }))
{
}
<!-- <PERSONAL_INFO> -->
<div id="ppg_op2_personal_info" class="op2-block">
</div>
<!-- <PERSONAL_INFO> -->
like image 221
Zelid Avatar asked Oct 28 '11 12:10

Zelid


1 Answers

You should create a virtual path provider which fetches your custom views from a database.

There are several questions here about them. Just search for VirtualPathProvider

Updates (from my comments to the question discussion)

  • The VirtualPathProvider must be registered in Application_Start using HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());

  • The base class MUST be called for all files that you can't currently serve. This is required since there can only be one VirtualPathProvider. (You'll see lots of strange errors otherwise)

  • @model directive doesn't work in files that you serve. You must use @inherits System.Web.Mvc.WebViewPage<YourNameSpace.YourModelName> instead.

  • IIRC you also need to override GetCacheDependency and return null for your own resources.

like image 75
jgauffin Avatar answered Apr 28 '23 07:04

jgauffin