Modularize ASP.NET MVC App

Nov 9, 2011 at 4:17 PM

Hi, i have an ASP.NET MVC application and register my container in the Application_Start event of my Global.asax file, e.g.:

// Create the ioc container
var container = new UnityContainer();

// Configure the container
container.RegisterType<IAuthenticationService, AuthenticationService>();
...

// Setup the dependency resolver
DependencyResolver.SetResolver(new UnityDependencyResolver(container));

However i'm now trying to modularize my application a little better. Therefore i have split each ASP.NET MVC Areas out into their own sub project. I was wondering what is the best way i can register types which are specific to the certain module.

I'm still in the early planning stage so if i've not explained that clearly enough please let me know and i'll try and give you as much information to help. Thanks

Nov 10, 2011 at 7:19 AM
Edited Nov 10, 2011 at 7:21 AM

One strategy to bundle configuration for Unity is to use derivatives of UnityContainerExtension.

 

public class Configuration : UnityContainerExtension
{
  protected override void Initialize()
  {
    this.Container.RegisterType<IService, Service>();
    //...
  }
}

 

You could then just add those extensions in your Global.asax file.

Or, if you prefer a more dynamic approach, you could decide on some naming convention for those configuration classes and then it would be trivial to loop over all assemblies in your application folder (or any other folder you might want to use), find those classe that contain configuration information and add them to your container.

 

foreach(Type type in someAssembly.GetExportedTypes())
{
  if(typeof(UnityContainerExtension).IsAssignableFrom(type) && type.Name == "Configuration")
  {
    var config = (UnityContainerExtension)Activator.CreateInstance(type);
    theGlobalContainer.AddExtension(config);
  }
}
Nov 10, 2011 at 1:53 PM
Edited Nov 10, 2011 at 2:05 PM

Hi thanks, i like the second approach. It feels very similar to an article i have just read on how ASP.NET MVC areas are registered.

http://suhair.in/Blog/aspnet-areas-in-depth

I was wondering if there was a way i could create an interface (e.g. IModule) that my module would implement. This would register any types/dependencies and routes. I guess then i could look over any assemblies/types that implement the IModule interface within the Global.asax file and register the types/dependencies and routes for that module. This should allow me to remove the current area registration stuff and tidy things up.

This is pure speculation as i would have no idea how to implement it. I'd appreciate it if someone could show how this can be done, if it can be done of course.

Thanks

Nov 10, 2011 at 2:22 PM

What exactly is your problem? Locating and loading the assemblies to search? You can use

string path = Directory.GetCurrentDirectory();

to get the path to your application directory. A call to

string[] assemblyFiles = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories);

would give you all DLLs (you can decide to do the same for *.exe) in that folder and all subfolders. And then you only have to load these assemblies to get the input for the loop from my first post.

var assemblies = assemblyFiles.Select(af => Assembly.Load(af));

foreach(var someAssembly in assemblies)
{
  // paste first loop here
}

The same approach should work for any other type you would want to process. As long as you use these types either with default parameterless constructors or a well defined set of constructor parameters that you can hand to Activator.Create() at the time of their instantiation.

Nov 10, 2011 at 3:33 PM

Hi thanks again. So far i have created the following interface:

public interface IModule {
    string ModuleName { get; }
    void Initialize(RouteCollection routes, IUnityContainer container);
}

 Now i have implemented this interface like:

public class BlogsModule : IModule {
    public string ModuleName { get { return "Blogs"; } }

    public void Initialize(RouteCollection routes, IUnityContainer container) {
        ... Container register stuff here
        // Register routes
        routes.MapRoute(
            "Blogs_Admin",
            "Admin/Blogs/{action}/{id}",
            new { area = ModuleName, controller = "Admin", action = "Index", id = UrlParameter.Optional },
            new string[] { "Modules.Blogs.Controllers" }
        );
        routes.MapRoute(
            "Blogs_Default",
            "Blogs/{controller}/{action}/{id}",
            new { area = ModuleName, controller = "Home", action = "Index", id = UrlParameter.Optional },
            new string[] { "Modules.Blogs.Controllers" }
        );
    }
}

And in my Global.asax file i have added the following to the Application_Start event (before RegisterRoutes is called and after the container is created):

// Loop over the modules
foreach (var file in Directory.GetFiles(HttpContext.Current.Server.MapPath("~/bin"), "Modules.*.dll")) {
    foreach (Type type in Assembly.LoadFrom(file).GetExportedTypes()) {
        if (typeof(IModule).IsAssignableFrom(type)) {
            var module = (IModule)Activator.CreateInstance(type);
            module.Initialize(RouteTable.Routes, container);
        }
    }
}

When i launch my application the correct url is rendered for the "Blogs" area when i say:

@Html.ActionLink("Blogs", "Index", "Home", new { area = "Blogs" }, null)

But when i click on the link it displays the view from the HomeController in the main application and not the one in my Blogs Module. Please note that i have also removed the following line from the Global.asax file:

AreaRegistration.RegisterAllAreas();

I'd appreciate it if you could share any knowledge which may help. The initial container problem has been successfully solved but if i can tie it together with the area registration then it makes it more attractive.

Thanks

Nov 10, 2011 at 7:54 PM

Sorry, I don't have any experience with MVC in particular. Just some general hints: Does the code in your Global.asax run for all Modules.*.dll files you expect it to run? If you put 2 modules in that path, does the loop run twice? Does the inner loop run over all public classes and structs in your module DLLs? Maybe you should disentangle the code in the loops a bit. It would increase readability and debugability from my perspective. Put breakpoints in the initialize methods of your modules and see if they are all hit the way you expect them to be hit.

HIH

Nov 14, 2011 at 1:04 PM

Hi thanks for your help. I managed to solve the problem. If anyone is interested the fix is posted at http://stackoverflow.com/questions/8083045/asp-net-mvc-areas-in-individual-projects-refactor-arearegistration-stuff/8122002#8122002. Hope this helps.