简体   繁体   中英

How to include controllers and views from an external project into MVC6?

I have some modules which has controllers and views. It is basically an extension for my web application. Each module is in a class library.

I want to load these assemblies from my web application. But I'm without luck here.


My solutions file structure is like:

src
|
|-- Web.Common  (Class Library Project)
|   |- Files like: filters, my own controller etc...
|    
|-- WebApplication (ASP.NET5 WebSite)
|   |- wwwroot
|   |- Controllers
|   |- Views
|   |- etc...
|
|-- Module 1 (Class Library Project)
|   |- Controllers
|   |- Views
|
|-- Module 2 etc...

These are what I tried:


I tried to implement my own IViewLocationExpander

public class CustomViewLocationExpander : IViewLocationExpander
{
    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        yield return "/../Module1.Web/Views/Home/TestView.cshtml";
        yield return "../Module1.Web/Views/Home/TestView.cshtml";
        yield return "/Module1.Web/Views/Home/TestView.cshtml";
        yield return "~/../Module1.Web/Views/Home/TestView.cshtml";
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {

    }
}

I tried all kind of paths that came to my mind but no luck :(

I get:

InvalidOperationException: The view 'TestView' was not found. The following locations were searched:

~/Module1.Web/Views/Home/TestView.cshtml ~/../Module1.Web/Views/Home/TestView.cshtml /Module1.Web/Views/Home/TestView.cshtml /../Module1.Web/Views/Home/TestView.cshtml


So I thought maybe the default IFileProvider doesn't look outside the WebApp's root path and decided to try implementing my own IFileProvider.

But here I didn't have any success neither.


Maybe there is a feature to achieve this by calling some ASP.NET methods but I don't know it.

Any suggests?

Controllers will get loaded automatically. To load views, you will need EmbeddedFileProvider and CompositeFileProvider , both of which are new, so you'll need to get them from the aspnetvnext feed.

Reference them in your startup MVC6 project's project.json :

"Microsoft.AspNet.FileProviders.Composite": "1.0.0-*",
"Microsoft.AspNet.FileProviders.Embedded": "1.0.0-*",

Update your service registration in Startup.cs :

    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.FileProvider = new CompositeFileProvider(
            new EmbeddedFileProvider(
                typeof(BooksController).GetTypeInfo().Assembly,
                "BookStore.Portal" // your external assembly's base namespace
            ),
            options.FileProvider
        );
    });

In project.json of your external assembly, add this:

  "resource": "Views/**"

Here's a sample implementation that you can clone and run to see it in action: https://github.com/johnnyoshika/mvc6-view-components

I think the views must live in the main web app unless you want to use some non-standard 3rd party solutions

My understanding is that in beta7 we will be able to package views and other content files in a class library nuget that is created when we build the class library with VS 2015. But my understanding is that when the main web app adds a dependency on such a nuget, the content files will be added to the main web app, ie my views will be added beneath Views/MyModule or something like that so they are usable from the main web application.

At least this is the approach I expect to take so that my views can be read and/or modified later by others.

I think the other option is to precompile the views such that they don't exist on disk as .cshtml files but this would make it more difficult for others to customize the views.

I achieved this by using IViewLocationExpander and PhysicalFileProvider

I used IFileProvider of the razor's engine to set the root path to the src folder. By appending the assembly name, I get the path of the projects root path.

public class MultiAssemblyViewLocationExpander : IViewLocationExpander
{
    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        var actionContext = (ResultExecutingContext)context.ActionContext;
        var assembly = actionContext.Controller.GetType().Assembly;
        var assemblyName = assembly.GetName().Name;

        foreach (var viewLocation in viewLocations)
            yield return "/" + assemblyName + viewLocation;
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {

    }
}

And in ConfigureServices method:

services.Configure<RazorViewEngineOptions>(options =>
{
    options.FileProvider = new PhysicalFileProvider(HostingEnvironment.WebRootPath + "..\\..\\");
    options.ViewLocationExpanders.Add(new MultiAssemblyViewLocationExpander ());
});

PS: I didn't test this on a published application. But it will be easy to fix it if some path problems occur ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM