Is Modular Web Application in vNext Possible? #4572

Closed
frapid opened this Issue May 1, 2016 · 19 comments

Comments

Projects
None yet
10 participants
@frapid

frapid commented May 1, 2016

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.

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 1, 2016

Member

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.

Member

davidfowl commented May 1, 2016

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.

@frapid

This comment has been minimized.

Show comment
Hide comment
@frapid

frapid May 1, 2016

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.

frapid commented May 1, 2016

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.

@Eilon

This comment has been minimized.

Show comment
Hide comment
@Eilon

Eilon May 2, 2016

Member

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

Member

Eilon commented May 2, 2016

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

@Eilon Eilon added this to the Discussion milestone May 2, 2016

@Eilon Eilon added the discussion label May 2, 2016

@sebastienros

This comment has been minimized.

Show comment
Hide comment
@sebastienros

sebastienros May 2, 2016

Member

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.

Member

sebastienros commented May 2, 2016

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.

@frapid

This comment has been minimized.

Show comment
Hide comment
@frapid

frapid May 2, 2016

Is there a documentation on ApplicationParts?

frapid commented May 2, 2016

Is there a documentation on ApplicationParts?

@sebastienros

This comment has been minimized.

Show comment
Hide comment
@sebastienros

sebastienros May 2, 2016

Member

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.

Member

sebastienros commented May 2, 2016

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.

@javiercn

This comment has been minimized.

Show comment
Hide comment
@javiercn

javiercn May 2, 2016

Member

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);
            });
    }
...
Member

javiercn commented May 2, 2016

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);
            });
    }
...
@alexsandro-xpt

This comment has been minimized.

Show comment
Hide comment
@alexsandro-xpt

alexsandro-xpt May 9, 2016

@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?

@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?

@davidfowl

This comment has been minimized.

Show comment
Hide comment
Member

davidfowl commented May 19, 2016

@alexsandro-xpt

This comment has been minimized.

Show comment
Hide comment
@alexsandro-xpt

alexsandro-xpt May 22, 2016

I'm dreamming for this.

alexsandro-xpt commented May 22, 2016

I'm dreamming for this.

@thiennn

This comment has been minimized.

Show comment
Hide comment
@thiennn

thiennn Jun 22, 2016

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

thiennn commented Jun 22, 2016

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

@edwardwilson

This comment has been minimized.

Show comment
Hide comment
@edwardwilson

edwardwilson Sep 8, 2016

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?

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?

@sebastienros

This comment has been minimized.

Show comment
Hide comment
@sebastienros

sebastienros Sep 8, 2016

Member

@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.

Member

sebastienros commented Sep 8, 2016

@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.

@Eilon

This comment has been minimized.

Show comment
Hide comment
@Eilon

Eilon Jun 9, 2017

Member

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.

Member

Eilon commented Jun 9, 2017

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.

@Eilon Eilon closed this Jun 9, 2017

@ADefWebserver

This comment has been minimized.

Show comment
Hide comment
@ADefWebserver

ADefWebserver Jul 9, 2017

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 commented Jul 9, 2017

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);
        }
@alexsandro-xpt

This comment has been minimized.

Show comment
Hide comment
@alexsandro-xpt

alexsandro-xpt Jul 10, 2017

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

Some Controllers and AspNet Assembles dependence?

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

Some Controllers and AspNet Assembles dependence?

@ADefWebserver

This comment has been minimized.

Show comment
Hide comment
@ADefWebserver

ADefWebserver Jul 10, 2017

@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.

ADefWebserver commented Jul 10, 2017

@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.

@alexsandro-xpt

This comment has been minimized.

Show comment
Hide comment

Not bad @ADefWebserver !! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment