Nancy using Razor view engine: Embedded views don't work - c#

I have the following code:
public class Program : NancyModule
{
static void Main(string[] args)
{
using (var host = new NancyHost(new Uri("http://localhost:444"), new CustomConventionsBootstrapper()))
{
host.Start();
Console.ReadLine();
}
}
public Program()
{
Get["/"] = parameter =>
{
dynamic var = new ExpandoObject();
var.Test = "Lol";
return View["RazorView.cshtml", var];
};
}
}
public class CustomConventionsBootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
base.ConfigureApplicationContainer(container);
//This should be the assembly your views are embedded in
var assembly = Assembly.GetEntryAssembly();
ResourceViewLocationProvider.RootNamespaces.Add(assembly, "NancyTest.Views");
}
protected override NancyInternalConfiguration InternalConfiguration
{
get
{
var res = base.InternalConfiguration;
res.ViewLocationProvider = typeof(ResourceViewLocationProvider);
return res;
}
}
void OnConfigurationBuilder(NancyInternalConfiguration x)
{
x.ViewLocationProvider = typeof(ResourceViewLocationProvider);
}
}
And I have RazorView.cshtml in a folder Views in my project set as embedded resource, however each time I open the page it will give me
Nancy.RequestExecutionException: Oh noes! ---> Nancy.ViewEngines.ViewNotFoundException: Unable to locate view 'RazorView.cshtml'
Currently available view engine extensions: sshtml,html,htm,cshtml,vbhtml
Locations inspected: views/Program/RazorView.cshtml-en-GB,views/Program/RazorView.cshtml,Program/RazorView.cshtml-en-GB,Program/RazorView.cshtml,views/RazorView.cshtml-en-GB,views/RazorView.cshtml,RazorView.cshtml-en-GB,RazorView.cshtml
Root path: C:\Users\Student\documents\visual studio 2013\Projects\NancyTest\NancyTest\bin\Debug
If you were expecting raw data back, make sure you set the 'Accept'-header of the request to correct format, for example 'application/json'
bij Nancy.ViewEngines.DefaultViewFactory.GetRenderedView(String viewName, Object model, ViewLocationContext viewLocationContext)
bij Nancy.ViewEngines.DefaultViewFactory.RenderView(String viewName, Object model, ViewLocationContext viewLocationContext)
bij Nancy.Responses.Negotiation.ViewProcessor.Process(MediaRange requestedMediaRange, Object model, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.NegotiateResponse(IEnumerable`1 compatibleHeaders, Object model, Negotiator negotiator, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.ProcessAsNegotiator(Object routeResult, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.InvokeRouteWithStrategy(Object result, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.<>c__DisplayClass9.b__5(Task`1 completedTask)
--- Einde van intern uitzonderingsstackpad ---
bij Nancy.NancyEngine.InvokeOnErrorHook(NancyContext context, ErrorPipeline pipeline, Exception ex)
It is worth noting that this only happens when using Razor views. When I use a simple html file it can find those files fine. I even tried to run old example projects found online on the current (0.22.2) Nancy version, but no luck there either. What's going on?
Thanks

Okay figured this out, but I have to say this is kinda stupid. Checked the source, this is the culprit:
ResourceAssemblyProvider.cs line 31
private static IEnumerable<Assembly> GetFilteredAssemblies()
{
return AppDomainAssemblyTypeScanner.Assemblies
.Where(x => !x.IsDynamic)
.Where(x => !x.GetName().Name.StartsWith("Nancy", StringComparison.OrdinalIgnoreCase));
}
My assembly is called 'NancyTest'. Changed to 'TestNancy', works now. I suggest finding a better way of excluding your own assemblies.

Also, when working with embedded views, one can easily forget to add the view as an embedded resource (a common gotcha that took me a while to get used to):
Right click on view-file -> properties -> Build Action -> select "Embedded Resource"

Related

ASP.NET MVC Modules

I need to create a nuget package which will contain shared views, controllers, js, and css files to be used across multiple other projects. Essentially a modular set of things like checkout or search pages that can be dropped into other site projects.
All the research I've done so far points to utilizing precompiled views with RazorGenerator but doesn't say much about the controllers, js, and css files.
Ideally a module's views and other files should be able to be overridden by the consuming host project but the files themselves should not be directly editable within the host project. Much like dlls referenced when other nuget packages are added.
The answers and posts about this type of subject I've found so far seem a bit dated.
Is there a cleaner, modern solution for creating an ASP.NET MVC module nuget package so that fully working pages are able to be shared across projects?
Controllers
Use area's and register those area's. Possibly this is not natively supported and you might need to overwrite some parts in mvc4. look at:
How do I register a controller that has been created in an AREA
http://netmvc.blogspot.be/2012/03/aspnet-mvc-4-webapi-support-areas-in.html
As long as the dll's are loaded in you can always register all classes that are subclasses of Controller with reflection (on application startup).
Razor
Precompiling is possible, but only really adviseable in dotnet core since it is a first class citizen there.
You could also add the views as content which is injected into the project.
Downside:
On update it overwrites the views (if you changed them you lost changes)
Pros:
On update you can merge both changes in git
Easily change the already existing razor pages
I found a solution that works for us. We have not yet fully implemented this so some unforeseen issues may still arise.
First, create an MVC project with the needed views, controllers, javascript, etc needed to match your package requirements. Each static file and view must be set as embedded resources in the project.
Then, add a class to serve these files on a virtual path provider. This will allow a consuming project to access the static files and views as if they were within the same project.
To enable custom routing an implementation of the RouteBase class will be required. This implementation needs to accept a string property for which virtual routes are based to allow the host to apply whichever route prefix is desired. For our example, the property will default to Booking with the associated architecture of views within our project to match.
Both the RouteBase implementation and the VirtualPath class will be instantiated within a setup method. This will allow a consuming project to call a single method to setup the booking engine. This method will take in the sites Route Collection and dynamic route property to append the custom routes. The method will also register the VirtualPathProvider to the HostingEnvironment object.
The consuming host can also override views and any other static file by simply having a file in a location within the host project that matches the path of the file or view in the booking engine.
Some Code Examples
RouteBase method which returns correct route values if an incoming route matches a virtual route.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
// Trim the leading slash
var path = httpContext.Request.Path.Substring(1);
// Get the page that matches.
var page = GetPageList(httpContext)
.Where(x => x.VirtualPath.Equals(path))
.FirstOrDefault();
if (page != null)
{
result = new RouteData(this, new MvcRouteHandler());
// Optional - make query string values into route values.
AddQueryStringParametersToRouteData(result, httpContext);
result.Values["controller"] = page.Controller;
result.Values["action"] = page.Action;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
RouteBase Virtual Route to NuGet Package Route mapping. New PageInfo objects created with dynamic virtual path string and references to real controller and action names. These are then stored in the http context cache.
private IEnumerable<PageInfo> GetPageList(HttpContextBase httpContext)
{
string key = "__CustomPageList";
var pages = httpContext.Cache[key];
if (pages == null)
{
lock (synclock)
{
pages = httpContext.Cache[key];
if (pages == null)
{
pages = new List<PageInfo>()
{
new PageInfo()
{
VirtualPath = string.Format("{0}/Contact", BookingEngine.Route),
Controller = "Home",
Action = "Contact"
},
};
httpContext.Cache.Insert(
key: key,
value: pages,
dependencies: null,
absoluteExpiration: System.Web.Caching.Cache.NoAbsoluteExpiration,
slidingExpiration: TimeSpan.FromMinutes(1),
priority: System.Web.Caching.CacheItemPriority.NotRemovable,
onRemoveCallback: null);
}
}
}
return (IEnumerable<PageInfo>)pages;
}
Booking Engine class setup method which does all the instantiating needed for the assembly.
public class BookingEngine
{
public static string Route = "Booking";
public static void Setup(RouteCollection routes, string route)
{
Route = route;
HostingEnvironment.RegisterVirtualPathProvider(
new EmbeddedVirtualPathProvider());
routes.Add(
name: "CustomPage",
item: new CustomRouteController());
}
}
EmbeddedVirtualFile
public override CacheDependency GetCacheDependency(string virtualPath, virtualPathDependencies, DateTime utcStart)
{
string embedded = _GetEmbeddedPath(virtualPath);
// not embedded? fall back
if (string.IsNullOrEmpty(embedded))
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
// there is no cache dependency for embedded resources
return null;
}
public override bool FileExists(string virtualPath)
{
string embedded = _GetEmbeddedPath(virtualPath);
// You can override the embed by placing a real file at the virtual path...
return base.FileExists(virtualPath) || !string.IsNullOrEmpty(embedded);
}
public override VirtualFile GetFile(string virtualPath)
{
// You can override the embed by placing a real file at the virtual path...
if (base.FileExists(virtualPath))
return base.GetFile(virtualPath);
string embedded = _GetEmbeddedPath(virtualPath);
if (string.IsNullOrEmpty(embedded))
return null;
return new EmbeddedVirtualFile(virtualPath, GetType().Assembly
.GetManifestResourceStream(embedded));
}
private string _GetEmbeddedPath(string path)
{
if (path.StartsWith("~/"))
path = path.Substring(1);
path = path.Replace(BookingEngine.Route, "/");
//path = path.ToLowerInvariant();
path = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + path.Replace('/', '.');
// this makes sure the "virtual path" exists as an embedded resource
return GetType().Assembly.GetManifestResourceNames()
.Where(o => o == path).FirstOrDefault();
}
Nested Virtual File Class
public class EmbeddedVirtualFile : VirtualFile
{
private Stream _stream;
public EmbeddedVirtualFile(string virtualPath,
Stream stream) : base(virtualPath)
{
if (null == stream)
throw new ArgumentNullException("stream");
_stream = stream;
}
public override Stream Open()
{
return _stream;
}
}
A lot of the code we are using comes from the following links
Embedded Files - https://www.ianmariano.com/2013/06/11/embedded-razor-views-in-mvc-4/
RouteBase implementation - Multiple levels in MVC custom routing

null reference servicestack viewpage

I have started building a controllerless MVC app built on top of Service Stack making use of 'RazorFormat'. I have built a couple of pages successfully. These pages simply map to a service that return a simple single Document object. This is all fine. I have built another view that should be returned when I access a specific route. This service returns an IEnumerable. I am able to step through the code to see that my service is returning correctly but the view never renders, I see a NullReferenceException. During debugging I am able to see that the operation name that throws is ResourceTemplateRequest and the stack trace:
Error CodeNullReferenceException MessageObject reference not set to an
instance of an object. Stack Trace at ASP.___Layout.Execute() at
ServiceStack.Razor.ViewPage1.WriteTo(StreamWriter writer) at
ServiceStack.Razor.Managers.RazorPageResolver.ExecuteRazorPageWithLayout(IHttpRequest
httpReq, IHttpResponse httpRes, Object model, IRazorView page, Func1
layout) at
ServiceStack.Razor.Managers.RazorPageResolver.ExecuteRazorPageWithLayout(IHttpRequest
httpReq, IHttpResponse httpRes, Object model, IRazorView page, Func1
layout) at
ServiceStack.Razor.Managers.RazorPageResolver.ResolveAndExecuteRazorPage(IHttpRequest
httpReq, IHttpResponse httpRes, Object model, RazorPage razorPage) at
ServiceStack.Razor.Managers.RazorPageResolver.ProcessRequest(IHttpRequest
httpReq, IHttpResponse httpRes, Object dto) at
ServiceStack.WebHost.Endpoints.Formats.HtmlFormat.<>c__DisplayClass1.<SerializeToStream>b__0(IViewEngine
x) at System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func`2
predicate) at
ServiceStack.WebHost.Endpoints.Formats.HtmlFormat.SerializeToStream(IRequestContext
requestContext, Object response, IHttpResponse httpRes) at
ServiceStack.WebHost.Endpoints.Extensions.HttpResponseExtensions.WriteToResponse(IHttpResponse
response, Object result, ResponseSerializerDelegate defaultAction,
IRequestContext serializerCtx, Byte[] bodyPrefix, Byte[] bodySuffix)
but I cannot figure out how to fix it. My service is decorated with the [DefaultView("ResourceTemplate")] attribute. Any ideas?
Even when I comment everything out in my view I get the exception. Here is some code
Service
[Authenticate]
public class ResourceTemplateService : Service
{
[DefaultView("ResourceTemplate")]
public ResourceTemplateResponse Get(ResourceTemplateRequest request)
{
var filter = factory.SetUp()
.WithTopN(Paging.GetTopN(request))
.WithGlobalPath(request.NodeAliasPath.StripChannel())
.Value;
var documents = repository.GetDocumentsByPath(filter).ToList();
var currentDocument = repository.GetDocumentByNodeAliasPath(request.NodeAliasPath);
var page = Paging.GetPage(documents, request);
if (documents.None())
throw HttpError.NotFound(string.Format("No children found for path {0}", request.NodeAliasPath));
return new ResourceTemplateResponse
{
Count = documents.Count(),
CurrentDocument = currentDocument,
Documents = page,
CurrentNodeAliasPath = request.NodeAliasPath
};
}
}
Request
[Route("/resource/{NodeAliasPath*}")]
public class ResourceTemplateRequest : DocumentCollectionRequest, IReturn<ResourceTemplateResponse>
{
public ResourceTemplateRequest()
{
View = "ResourceTemplate";
//Set base call properties
}
}
View
#using Zoo.Optus.YesCentral.Mobile.Model
#using Zoo.Optus.YesCentral.Web.Factories
#inherits ServiceStack.Razor.ViewPage<ResourceTemplateResponse>
#{
Layout = "_Layout";
ActivityFactory.LogPageVisit(Model.CurrentDocument.DocumentName, Model.CurrentDocument.NodeId, true);
}
#foreach (var document in #Model.Documents)
{
#document.DocumentName.ToMvcHtmlString()
}

Castle.Windsor 3.1 - ComponentActivator: could not proxy <FactoryName>

I'm in the process of upgrading from version 2.5.1 to version 3.1.0 and what used to work no long does. I'm attempting to resolve a factory that creates components that utilize generics. The error that is thrown is "ComponentActivator: could not proxy " where is the name of the factory that cannot be resolved
public class MyObject { }
public class MyContext { }
public class DummyComponent<T> : IDummyComponent<T> where T : MyObject { }
public interface IDummyComponent<T> { }
public interface IDummyComponentFactory
{
IDummyComponent<T> Create<T>(object o);
}
class Program
{
static void Main(string[] args)
{
var windsorContainer = new WindsorContainer();
windsorContainer.AddFacility<TypedFactoryFacility>()
.Register(
Component.For(typeof(IDummyComponent<>)).ImplementedBy(typeof(DummyComponent<>)),
Component.For<IDummyComponentFactory>().AsFactory());
var factory = windsorContainer.Resolve<IDummyComponentFactory>(); <-- Error Occurs Here
var myDummyComponent = factory.Create<MyObject>(new object());
}
}
This code is utilized in the following fashion within an Entity Framework repository for passing the EntityContext to another repository in order to handle deleted object like so:
public virtual IEnumerable<T> Where(Expression<Func<T, bool>> predicate, bool showDeleted = false, MergeOption mergeOption = MergeOption.AppendOnly)
{
if (typeof(IDeletable).IsAssignableFrom(typeof(T)))
{
var factory = Container.Instance.Resolve<IDeletableRepositoryFactory>();
var repository = factory.GetDeletableRepository<T>(EntityContext);
return repository.Where(predicate, showDeleted, mergeOption);
}
return GetObjectSet(mergeOption).Where(predicate);
}
Update: Including Inner Exception
System.Security.VerificationException
{"Operation could destabilize the runtime."} at
Castle.MicroKernel.Proxy.ProxyOptions.get_MixIns() in
c:\BuildAgent\work\5b096cace0fecb1f\src\Castle.Windsor\MicroKernel\Proxy\ProxyOptions.cs:line 96 at
Castle.Windsor.Proxy.DefaultProxyFactory.CreateProxyGenerationOptionsFrom(ProxyOptions proxyOptions, IKernel kernel, CreationContext context, ComponentModel model) in
c:\BuildAgent\work\5b096cace0fecb1f\src\Castle.Windsor\Windsor\Proxy\DefaultProxyFactory.cs:line 178 at
Castle.Windsor.Proxy.DefaultProxyFactory.Create(IKernel kernel, Object target, ComponentModel model, CreationContext context, Object[] constructorArguments) in
c:\BuildAgent\work\5b096cace0fecb1f\src\Castle.Windsor\Windsor\Proxy\DefaultProxyFactory.cs:line 105 at
Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.CreateInstance(CreationContext context, ConstructorCandidate constructor, Object[] arguments) in
c:\BuildAgent\work\5b096cace0fecb1f\src\Castle.Windsor\MicroKernel\ComponentActivator\DefaultComponentActivator.cs:line 123`
I tried running your code, but it works on my machine
This appears to be an issue with Intellitrace and is resolved by disabling it:
Tools -> Options -> IntelliTrace (uncheck Enable Intellitrace)
Found a reference to others having a similar issue.

TFS Custom Build Template - Get the team project collection without the Uri

Is there anyway to get the collection URI from IBuildDefinition or other related 'services'.
I am trying to avoid having to supply the URI to the collection in the build template as a custom parameter. I am looking for a way to retrieve it programmatically from (in this instance) within UITypeEditor custom class.
Is there a way to query for this without resorting to hardcoding? It seems to me the build process itself (definition, controllers, agents, etc) knows which collection they are dealing with but how can I find out?
UPDATE: Here is sample code when you are inheriting from UITypeEditor. Then you just access the TeamProjectCollection property of the VersionControlService:
public class Editor : UITypeEditor
{
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
IWindowsFormsEditorService service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (service != null)
{
VersionControlServer vcs = provider.GetService(typeof(VersionControlServer)) as VersionControlServer;
// Do what you need to do with it here
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
You can get the TfsTeamProjectProjection object from IBuildServer:
http://msdn.microsoft.com/en-us/library/microsoft.teamfoundation.build.client.ibuildserver.teamprojectcollection.aspx
Then get you Uri from this object.
Inside of the override for UITypeEditor.EditValue the relevant line of code to obtain the TeamProjectCollection is
VersionControlServer vcs = provider.GetService(typeof(VersionControlServer)) as VersionControlServer;
And then it is in the property
vcs.TeamProjectCollection

ASP.NET MVC: Views using a model type that is loaded by MEF can't be found by the view engine

I'm attempting to create a framework for allowing controllers and views to be dynamically imported into an MVC application. Here's how it works so far:
I'm using .NET 4, ASP.NET MVC 3 RC and the Razor ViewEngine
Controllers are exported and imported using MEF per project - I call a set of controllers and views from a given project a "Module"
Assemblies discovered using MEF are dynamically referenced by the BuildManager using a pre-application start method and BuildManager.AddReferencedAssembly.
Binaries (from exporting project) and Views are copied into the target project's folder structure using a build event
Controllers are selected using a custom controller factory which inherits from DefaultControllerFactory and overrides GetControllerType()
Views are selected using a custom view engine which inherits from RazorViewEngine and overrides GetView() and GetPartialView() to allow it to look for views in Module-specific view directories
Everything works so far except for views using a strongly typed model. Views that use the dynamic model work fine, but when I specify a model type using #model, I get a YSOD that says "The view 'Index' or its master was not found".
When debugging my ViewEngine implementation, I can see that:
this.VirtualPathProvider.FileExists(String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"))) returns true, while
this.FileExists(controllerContext, String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"))) returns false.
Looking in Reflector, the RazorViewEngine implementation of FileExists() ultimately winds up doing this:
return (BuildManager.GetObjectFactory(virtualPath, false) != null);
However, I can't view BuildManager.GetObjectFactory() from Reflector because it's hidden somehow.
I'm suspecting that it has something to do with the fact that the model type is a type that is loaded from MEF, but since I'm already referencing the assemblies discovered by MEF from BuildManager, I'm out of leads. Can anyone provide a little more insight into what might be going on?
Update:
Turns out I was using an outdated version of Reflector from before .NET 4. I can see GetObjectFactory() now, but I can't really seem to find anything helpful. I've tried adding this into my FindView() overload:
try
{
var path = String.Format(this.ViewLocationFormats[2], viewName, controllerContext.RouteData.GetRequiredString("controller"));
var objFactory = System.Web.Compilation.BuildManager.GetObjectFactory(virtualPath: path, throwIfNotFound: true);
}
catch
{
}
Unfortunately, objFactory ends up null, and no exception gets thrown. All the bits that deal with compilation errors are part of private methods or types so I can't debug any of that, but it even seems like they'd end up throwing an exception, which doesn't seem to be happening. Looks like I'm at a dead end again. Help!
Update 2
I've discovered that at the point where FindView() is being called, if I call AppDomain.CurrentDomain.GetAssemblies(), the assembly that the model type is in is included. However, I cannot load the type using Type.GetType().
Update 3
Here's what I'm seeing:
Update 4
Here's the ViewEngine implementation:
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Hosting;
using System.Web.Compilation;
namespace Site.Admin.Portal
{
public class ModuleViewEngine : RazorViewEngine
{
private static readonly String[] viewLocationFormats = new String[]
{
"~/Views/{0}/{{1}}/{{0}}.aspx",
"~/Views/{0}/{{1}}/{{0}}.ascx",
"~/Views/{0}/{{1}}/{{0}}.cshtml",
"~/Views/{0}/Shared/{{0}}.aspx",
"~/Views/{0}/Shared/{{0}}.ascx",
"~/Views/{0}/Shared/{{0}}.cshtml"
};
public ModuleViewEngine(IModule module)
{
this.Module = module;
var formats = viewLocationFormats.Select(f => String.Format(f, module.Name)).ToArray();
this.ViewLocationFormats = formats;
this.PartialViewLocationFormats = formats;
this.AreaViewLocationFormats = formats;
this.AreaPartialViewLocationFormats = formats;
this.AreaMasterLocationFormats = formats;
}
public IModule Module { get; private set; }
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache)
{
var moduleName = controllerContext.RouteData.GetRequiredString("module");
if (moduleName.Equals(this.Module.Name, StringComparison.InvariantCultureIgnoreCase))
{
return base.FindPartialView(controllerContext, partialViewName, useCache);
}
else return new ViewEngineResult(new String[0]);
}
public override ViewEngineResult FindView(ControllerContext controllerContext, String viewName, String masterName, Boolean useCache)
{
var moduleName = controllerContext.RouteData.GetRequiredString("module");
if (moduleName.Equals(this.Module.Name, StringComparison.InvariantCultureIgnoreCase))
{
var baseResult = base.FindView(controllerContext, viewName, masterName, useCache);
return baseResult;
}
else return new ViewEngineResult(new String[0]);
}
}
}
Based on Update 2, I'm guessing what you've got is an explicitly loaded copy of your assembly (that is, it was loaded through some other method than Load, like LoadFrom). Explicitly loaded assemblies are set off aside into a special place, because they are not allowed to satisfy implicit type requirements. The rules for Fusion (the assembly loader) can be pretty arcane and hard to understand.
I agree with Matthew's assessment that, to get this to work, your DLL is going to have to be in /bin or else it will never be able to satisfy the implicit type requirement.
The imported libaries aren't in the /bin directory so aren't probed when trying to resolve references. I discovered a work around which I published in my MVC + MEF article (Part 2). Essentially you need to add your directories where your extensions sit to the probing path of the AppDomain.
Essentially where I am building my container:
/// <summary>
/// Creates the composition container.
/// </summary>
/// <returns></returns>
protected virtual CompositionContainer CreateCompositionContainer()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(MapPath("~/bin")));
var config = CompositionConfigurationSection.GetInstance();
if (config != null && config.Catalogs != null) {
config.Catalogs
.Cast<CatalogConfigurationElement>()
.ForEach(c =>
{
if (!string.IsNullOrEmpty(c.Path)) {
string path = c.Path;
if (path.StartsWith("~"))
path = MapPath(path);
foreach (var directoryCatalog in GetDirectoryCatalogs(path)) {
// Register our path for probing.
RegisterPath(directoryCatalog.FullPath);
// Add the catalog.
catalog.Catalogs.Add(directoryCatalog);
}
}
});
}
var provider = new DynamicInstantiationExportProvider();
var container = new CompositionContainer(catalog, provider);
provider.SourceProvider = container;
return container;
}
I register all the directories of catalogs in the current domain:
/// <summary>
/// Registers the specified path for probing.
/// </summary>
/// <param name="path">The probable path.</param>
private void RegisterPath(string path)
{
AppDomain.CurrentDomain.AppendPrivatePath(path);
}
I believe the same should work for MVC3.
UPDATE: Correct me if I am wrong, but I don't believe that ViewEngines are instantiated once per request, you create a single instance that you register with MVC. Because of this only one IModule instance is ever used with your ViewEngine, so if a path doesn't match that first IModule.Name it won't be found? Does that make sense?

Categories