3
votes

To the mods: not a duplicate, other answers (mainly "check the web.config in the Views folder") didn't work.

I have a main asp.net MVC application which discovers controllers and views in a separate assembly: controllers with MEF and a custom ControllerFactory, views with a custom VirtualPathProvider which discovers the views embedded in the same (of the controller) assembly.

The discovery of both parts works good: the controller factory finds the controller and initializes it using Ninject, the VirtualPathProvider recognizes that it has to look for the view in the assembly and loads it.

The web.config in the Views folder of the separate assembly is the one VS2013 generated when creating a new asp.net mvc project:

<?xml version="1.0"?>

<configuration>
    <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="Fos.WS.DemoCustomization" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>
  <appSettings>
    <add key="webpages:Enabled" value="false" />
  </appSettings>

  <system.webServer>
    <handlers>
      <remove name="BlockViewHandler"/>
      <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
    </handlers>
  </system.webServer>
</configuration>

Controller:

public ActionResult Index()
    {
        var dll = "~/Plugins/" + this.AssemblyName; //"~/Plugins/" is to instruct the custom viewpathprovider to discover the view from the assembly
        return View(dll + "/Fos.WS.DemoCustomization.Views.Demo.Index.cshtml"); 
    }

View:

@{
ViewBag.Title = "Demo";
}

<h2>Hi, Demo</h2>

The error i keep getting is

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

Source Error:




Line 38:         public override void Execute() {
Line 39:   
Line 40:     ViewBag.Title = "Demo";
Line 41: 
Line 42: BeginContext("~/Plugins/Fos.Ws.DemoCustomization.dll/Fos.WS.DemoCustomization.Views.Demo.Index." +


Source File: c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root\482869c5\3f1d4579\App_Web_fos.ws.democustomization.views.demo.index.cshtml.41ce77d0.bg7gacz2.0.cs    Line: 40 

Thanks

Edit: tried to remove the viewbag part and keep just the , now the error is different: The view at '~/Plugins/Fos.Ws.DemoCustomization.dll/Fos.WS.DemoCustomization.Views.Demo.Index.cshtml' must derive from WebViewPage, or WebViewPage.

It looks like the web.config isn't there.

1

1 Answers

2
votes

Answers a little late, but I think I may be able to add a bit of insight. So i haven't worked in this exact scenario, but i have worked with MEF, and dynamic View rendering separately.

Obvious

So an obvious issue if i'm reading your setup correctly is you're relying on that web.config defined above but it is in the remote assembly. I don't believe your IOC containers are able to access the assembly definitions from the remote config.

You even recognize that it is not picking up the web config values in your question. I know you said you had tried other web config resolutions without success, but I think that will be your best bet because it needs to pull in those definitions somehow, and trying to define those manually is going to be a hack.

Less Obvious

The reason you're seeing those exceptions is the ViewData object is defined by the WebViewPage base class, your config as you can see defines that as your base class for all views. without that base class, it's not a view and not parsable.

The reason I think it was hiding this error when accessing the ViewBag explicitly is the ViewBag is a wrapper in the ViewContext, passing ViewData to a DynamicViewDataDictionary. The ViewContext won't build if you pass in null ViewData, I think by calling the ViewBag explicitly on a null ViewContext it was just blowing up earlier in the chain, or rather the compiler recognized the issue this way.

Utility Helper

Since I am sorry I can't offer a silver bullet for this issue. Just more info pointing to the web.config not pulling in. I'm going to add a fun helper, that is also a hack for getting remote/dynamic Razor Views to parse manually from a file. You can also use this to make sure remote Views are well formatted and see how they will output. If you plugged this in I bet since it's defining a RazorView which manages the WebPageView initialization in this case. It'll work for your remote Views. worth noting this is a hack and not ideal for handling all of your View Rendering but is certainly handy in some sticky corner cases.

using System.IO;
using System.Web.Mvc;

namespace Utils
{
    public class RazorUtility
    {
        /// <summary>
        /// Takes a file path to a Razor View and a controller context and renders the the View to a string
        /// </summary>
        /// <param name="path">file path to view</param>
        /// <param name="context">controller context to render against</param>
        /// <returns></returns>
        public static string RenderView(string path, ControllerContext context)
        {
            //You need to include the Model in the ViewDate of the context passed in.
            var st = new StringWriter();
            var razor = new RazorView(context, path, null, false, null);
            razor.Render(new ViewContext(context, razor, context.Controller.ViewData, context.Controller.TempData, st), st);
            return st.ToString();
        }
    }
}