aspnet / Mvc

[Archived] ASP.NET Core MVC is a model view controller framework for building dynamic web sites with clean separation of concerns, including the merged MVC, Web API, and Web Pages w/ Razor. Project moved to https://github.com/aspnet/AspNetCore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is Modular Web Application in vNext Possible?

opened this issue · comments

Scenario

  • There are two web applications: MainWebApplication and ModuleA.
  • There is a controller on ModuleA called DefaultController with an action foo which returns string "bar".

When you add a reference of ModuleA on MainWebApplication and compile, you can see that the action foo works fine.

What's wrong with this?

ModuleA should depend on MainWebApplication because it is an extension of the MainWebApplication. If you add a reference of MainWebApplication on ModuleA, the controller action is not found by ASP.NET Core.

Why should you address this issue?

In my humble opinion, we have always needed to add a lot of plumbing code to achieve modular functionality in classic ASP.net. In ASP.net Core, it's more cryptic now. For example, please have a look on the ExtCore repository which tries to solve this problem.

https://github.com/ExtCore/ExtCore

This is a bad approach because

  • Because this does not feel like a solution, but rather a workaround trying to fill in the gaps which should not have been there in the first place.
  • Involves a lot black box magic.
  • You must produce output during build for this to work.
  • You must copy/move output (libraries) from one place to another after compiling.
  • Everything is too slow now because of the above two.
  • You must depend on an extra framework.

Finally, I apologize if I missed something. Please do provide an official solution because it would ease the life of a lot of web app developers.

In RC2, MVC added a concept called ApplicationParts to help address some of these scenarios. Also with the .NET CLI things will always be on disk so "produce outputs on build" will go away as a concept.

Involves a lot black box magic.

Can you be more specific?

You must copy/move output (libraries) from one place to another after compiling.

The layout is simplified in RC2 as well, it'll be a flat list of assemblies like it was before. However, it's still your job to copy the right things into the right places. If you don't reference a project the tool chain has no idea that it should be copied side by side. Specifically:

ModuleA should depend on MainWebApplication because it is an extension of the MainWebApplication. If you add a reference of MainWebApplication on ModuleA, the controller action is not found by ASP.NET Core.

If there's no dependency between MainWebApplication -> ModuleA, then you are responsible for finding and dynamically loading ModuleA into MainWebApplication's dependencies, yourself. That's no different to .NET Framework really.

You must depend on an extra framework.

Don't know what this means.

Thank you for the answer @davidfowl.

Can you be more specific?
Don't know what this means.

Sorry, I was referring to the cons of using a framework.

If there's no dependency between MainWebApplication -> ModuleA, then you are responsible for finding and dynamically loading ModuleA into MainWebApplication's dependencies, yourself. That's no different to .NET Framework really.

As of now (RC1), locating a documentation on how to do that is difficult.

The layout is simplified in RC2 as well, it'll be a flat list of assemblies like it was before. However, it's still your job to copy the right things into the right places. If you don't reference a project the tool chain has no idea that it should be copied side by side.

Thanks, this is a great news. Eagerly waiting for the RC2 release.

@javiercn @sebastienros do you guys have any additional thoughts on this?

Exactly what @davidfowl is saying. Orchard detects these dependencies at runtime then registers them using ApplicationParts. And it's Orchard's responsibility to copy the binaries in a probing folder. And optionally is can compile the projects if it was not done by the tooling.

Is there a documentation on ApplicationParts?

Here is how we add the assemblies for the modules we have discovered in the case of Orchard:
https://github.com/OrchardCMS/Orchard2/blob/44603d4b3593201cd16c31ec1c31256b38675104/src/Orchard.Hosting.Web/Extensions/ApplicationBuilderExtensions.cs

IExtensionManager is the service responsible for listing modules. ApplicationParts can accept assemblies, which is what we use in this case.

Yes, ApplicationParts are designed for this scenario. You can access the application part manager on IMvcBuilder. Here are a couple of examples on how to add and remove assemblies to the list of application parts

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        var builder = services
            .AddMvc()
            .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
    }
...

Remove an assembly from the list of application parts:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        var builder = services
            .AddMvc()
            .ConfigureApplicationPartManager(manager => {
                var assembly = typeof(TypeInAssembly).GetTypeInfo().Assembly;
                var part = manager.ApplicationParts.OfType<AssemblyPart>().First(p => p.Assembly == assembly);
                manager.ApplicationParts.Remove(assembly);
            });
    }
...

@davidfowl With ApplicationParts I will add a full Module(some controllers, some .cshtmls, some .css and .js) at my main asp.net application at run time only just loading Module assemblies?

I'm dreamming for this.

I have made a sample app, using ApplicationParts to add assemblies, wrote ModuleViewLocationExpander to help looking up the right folder for views, and serve static content for each modules. https://github.com/thiennn/trymodular. Comments are welcomed

How would you go about prefixing routes? For example module A and module B have controllers called home. How do you specify module route prefixes in the application parts so you get the following routes?

Localhost/b/home
Localhost/a/home

By adding the application part both controllers would route to Localhost/home unless each and every controller had an attribute route applied?

@edwardwilson you can create a default route for each module like here: https://github.com/OrchardCMS/Orchard2/blob/master/src/Orchard.Hosting.Web/Routing/OrchardRouterMiddleware.cs#L107
On the same file you'll also see that the routes are harvested from the modules, then registered with a prefix, but in the case I linked to the prefix is using the tenant name.

A custom router implementation can do that.

We are closing this issue because no further action is planned for this issue. If you still have any issues or questions, please log a new issue with any additional details that you have.

Adding this to the Startup.cs in .Net Core 1.1 actually works:

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Load assembly from path
            // Note: The project that creates this assembly must reference
            // the parent project or the MVC framework features will not be 
            // 'found' when the code tries to run
            // This uses ApplicationParts
            // https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts
            // Also see: https://github.com/aspnet/Mvc/issues/4572
            var path = Path.GetFullPath(@"CustomModules\CustomClassLibrary.dll");
            var CustomClassLibrary = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
        
            // Add framework services.
            services.AddMvc()
                .AddApplicationPart(CustomClassLibrary);
        }

@ADefWebserver What is the content of CustomClassLibrary.dll and her dependences ?

Some Controllers and AspNet Assembles dependence?

@alexsandro-xpt - CustomClassLibrary.dll contains my controllers and some classes that it needs. I posted an article here: An Angular 4 .Net Core Application Updater - That shows how you can allow your application end users to fully update their version of your application by simply uploading a .zip file.

Not bad @ADefWebserver !! Thank you!