0

I have a system in which the end-user is a developer who can create ASP.NET MVC views/controllers and run them on the fly.

Currently, I have two database tables, one to store the view name and code and other to store controller code in C#. I can compile the build an assembly and save a dll file on the server folder.

Step 1: I added a custom controller factory to load my controller from the database, having an area in the project named (QZone).

public class QS_DynamicControllerFactory : DefaultControllerFactory//, IController
{
    QS_DBConnection _db = new QS_DBConnection();
    public QS_DynamicControllerFactory() { }
    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        return (requestContext.RouteData.DataTokens["area"] != null && 
                requestContext.RouteData.DataTokens["area"].ToString().ToLower() == "qzone") ?
            QGetControllerInstance(controllerName) : base.CreateController(requestContext, controllerName);
    }
    internal IController QGetControllerInstance(string controllerName)
    {
        //load controller from the database and compile it then return an instance
    }
    public override void ReleaseController(IController controller)
    {
        base.ReleaseController(controller);
    }
}

Step 2: I created a VirtualPathProvider, VirtualFile

QS_VirtualPathProvider class:

public class QS_VirtualPathProvider : VirtualPathProvider
{
    public QDynamicView GetVirtualData(string viewPath)
    {
        QS_DBConnection _db = new QS_DBConnection();
        QDynamicView view = (from v in _db.QDynamicViews
                             where v.Name.ToLower() == "TestView.cshtml".ToLower()//viewPath.ToLower()
                             select v).SingleOrDefault();
        return view;
    }
    private bool IsPathVirtual(string virtualPath)
    {
        var path = (VirtualPathUtility.GetDirectory(virtualPath) != "~/") ? VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(virtualPath)) : VirtualPathUtility.GetDirectory(virtualPath);
        if (path.ToLower().Contains("/qzone/"))
            return true;
        else
            return false;
    }

    public override bool FileExists(string virtualPath)
    {
        if (IsPathVirtual(virtualPath))
        {
            QS_VirtualFile file = (QS_VirtualFile)GetFile(virtualPath);
            bool isExists = file.Exists;
            return isExists;
        }
        else
            return Previous.FileExists(virtualPath);
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        if (IsPathVirtual(virtualPath))
        {
            QDynamicView vw = GetVirtualData(virtualPath);
            var bytes = Encoding.ASCII.GetBytes(vw.ViewCode);
            return new QS_VirtualFile(virtualPath, bytes);
        }
        else
            return Previous.GetFile(virtualPath);
    }

    public override CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        if (IsPathVirtual(virtualPath))
        {
            return null;
        }
        else
            return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }

    public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
    {
        if (IsPathVirtual(virtualPath))
            return Guid.NewGuid().ToString();
        return base.GetFileHash(virtualPath, virtualPathDependencies);
    }
}

QS_VirtualFile class:

public class QS_VirtualFile : VirtualFile
{
    private string content;
    private QS_VirtualPathProvider spp;

    public bool Exists
    {
        get { return (content != null); }
    }
    public QS_VirtualFile(string virtualPath, QS_VirtualPathProvider provider) : base(virtualPath)
    {
        this.spp = provider;
        GetData(virtualPath);
    }

    public QS_VirtualFile(QDynamicView vw, string virtualPath) : base(virtualPath)
    {
        content = vw.ViewCode;
    }

    private byte[] _BinaryContent;

    public QS_VirtualFile(string virtualPath, byte[] contents) : base(virtualPath)
    {
        this._BinaryContent = contents;
    }

    protected void GetData(string virtualPath)
    {
        QDynamicView QSView = spp.GetVirtualData(virtualPath);

        if (QSView != null)
        {
            content = QSView.ViewCode;
        }
    }

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

Step 3: register the controller factory and the virtual path provider in the in Global.asax** file:

HostingEnvironment.RegisterVirtualPathProvider(new QS_VirtualPathProvider());
        ControllerBuilder.Current.SetControllerFactory(new QS_DynamicControllerFactory());

testing the code in order to test the code above i added a controller named (test) and a view named (testView.cshtml) in the database and requested the url below:

http://localhost:1001/qzone/test/TestView

and I got this error

enter image description here

I guess this mean that the controller factory worked fine but the view was not loaded

Any ideas?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

0

That's because it's looking for your view on the hard drive. The View Engine uses VirtualPathProvidersto resolve your views, so you need to write your own VirtualPathProvider and register it.

You can find the documentation here: https://learn.microsoft.com/en-us/dotnet/api/system.web.hosting.virtualpathprovider?view=netframework-4.8

Unfortunately, it is way too much code for me to copy here, but you can find a full example there. Mind you, the example is for .NET 4.8, so if you're using Core, this may not be applicable.

Steven Lemmens
  • 690
  • 5
  • 13