How to: Register services declaratively using a custom [Service] attribute
January 21st, 2007
This is my third "How to" article I write this month (the other two are How to: inject StateValue objects using constructor injection and How to: add a base class for Web pages). My goal with these articles is to not only help you add a new functionality to the factory, but also to show you behind-the-scenes details of it and different ways of extending it.
In this article, I’ll focus on extending the factory by replacing a core service in the Composite Web Application Block (the ModuleLoaderService). The new service will allow you to register services declaratively using a custom [Service] attribute as shown in the following code:
// Register the service with the type IService1, as a module service [Service(ServiceScope.Module, typeof(IService1))] public class Service1 : IService1 { // … } // Register the service with the type IService1, as a global service [Service(ServiceScope.Global, typeof(IService1))] public class Service1 : IService1 { // … }
If you are familiar with the Composite UI Application Block (CAB), you will probably recognize the [Service] attribute. The idea behind this article is similar to that implemented in CAB, with slight differences.
The [Service] attribute
The implementation of the attribute has two properties:
- Scope. This is the scope of the service. The scope can be Module (only accessible from the current module) and Global (accessible from all modules).
- RegisterAs. This is the type that will be used to register the service with the composition container (this is the same as the RegisterAs property in CAB’s implementation of the attribute).
The following lines show the implementation of the [Service] attribute and the ServiceScope enumeration. Please note that I didn’t include XML comments here to increase readability. The source code you can download below contains the XML comments.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class ServiceAttribute : Attribute { private Type _registerAs; private ServiceScope _scope; public ServiceAttribute() : this(ServiceScope.Global, null) { } public ServiceAttribute(ServiceScope scope) : this(scope, null) { } public ServiceAttribute(Type registerAs) : this(ServiceScope.Global, registerAs) { } public ServiceAttribute(ServiceScope scope, Type registerAs) { _scope = scope; _registerAs = registerAs; } public Type RegisterAs { get { return _registerAs; } } public ServiceScope Scope { get { return _scope; } } } public enum ServiceScope { Global, Module }
The ModuleLoaderService
The ModuleLoaderService is a core service included in the Composite Web Application Block which is in charge of loading modules into the application when it loads. The Composite Web Application Block includes a default implementation you can find in the Services folder of the CompositeWeb project in the block’s solution.
Since we want the services decorated with the [Service] attribute to be registered when the application loads, we will implement a custom ModuleLoaderService that will register services when loading the modules.
To implement the custom ModuleLoaderService, I took the source code of the implementation included in the Composite Web Application Block and made the required changes. The service has two methods: Load and FindInitializer. In this article, we will focus on the Load method, which loads modules into a given container. This is the method’s signature:
/// Loads the specified modules into the given container. /// The into which create the container for the modules. /// The list of modules to load. void Load(ICompositionContainer rootContainer, IModuleInfo[] moduleInfo);
And this is a simplified snapshot of the updated Load method’s implementation.
public void Load( ICompositionContainer compositionContainer, params IModuleInfo[] modules) { // Validate arguments // … foreach (IModuleInfo moduleInfo in modules) { // Load module assembly and, if it is a business // module, create a container for the module // … Type[] moduleTypes = moduleAssembly.GetTypes(); LoadServices(container, moduleTypes); LoadModuleInitializers(container, moduleTypes, moduleInfo.Name); } }
The highlighted lines above are the ones that differ from the original implementation. The LoadModuleInitializers method is simply a wrapper for the original code used to instantiate module initializers and call the Load method of them. I put the code in a separate method to increase readability.
The key is in the LoadServices method, which looks for the [Service] attribute in every type of the module assembly and registers the decorated types as services with the supplied container or with the root container (depending on the services’ scope). The following code is the implementation of the LoadServices method:
private void LoadServices(CompositionContainer container, Type[] moduleTypes) { foreach (Type type in moduleTypes) { foreach (ServiceAttribute attr in type.GetCustomAttributes(typeof(ServiceAttribute), true)) { IServiceCollection serviceCollection; switch (attr.Scope) { case ServiceScope.Global: serviceCollection = container.Parent == null ? container.Services : container.Parent.Services; break; case ServiceScope.Module: serviceCollection = container.Services; break; default: serviceCollection = container.Services; break; } serviceCollection.AddNew(type, attr.RegisterAs ?? type); } } }
When the LoadServices method shown above finds a class with the [Service] attribute, it performs the following tasks:
- Determines which service collection will be used to register the service (switch statement). If the scope of the service is Global, the service collection will be the collection of the root container. If the scope of the service is Module, the service collection used will be the collection of the current container. Note that in case of foundational modules -since they don’t have a specific container and they always use the root container- the services will always get registered with the service collection of the root container no matter the scope specified in the [Service] attribute.
- Registers the service in the service collection calling the AddNew method. If the user specified a type in the RegisterAs property of the attribute, that type will be used to register the service. If the property is null, the concrete type of the service is used.
Replacing the original ModuleLoaderService
The Composite Web Application Block includes a class named WebClientApplication. This class is an ASP.NET global application class and handles the application initialization and Web pages processing (for more information about this class, see the topic Application Class in the documentation).
When a Web client application is started, the WebClientApplication class loads core global services such as the ModuleLoaderService or the WebModuleEnumerator services. To replace a core service, you have to create a custom class that derives from the WebClientApplication class and override the AddRequiredServices method. To do this, follow these steps:
- Add a class to your Web site and make it extend the Microsoft.Practices.CompositeWeb.WebClientApplication class.
- Override the AddRequiredServices method and add services to the root container. To access the root container, use the property RootContainer. The following code shows how to implement the AddRequiredServices method to replace the original ModuleLoaderService with our custom implementation (you will need to add a reference to the CompositeWeb.Extensions assembly in your Web site to make this work):
public class WebClientApplication : Microsoft.Practices.CompositeWeb.WebClientApplication { protected override void AddRequiredServices() { RootContainer.Services.AddNew <CompositeWeb.Extensions.Services.ModuleLoaderService, IModuleLoaderService>(); base.AddRequiredServices(); } }
The call to the AddRequiredServices base method at the end of the method body is required because it registers all the required services for a Web client application. Note that it will only register a service if it is has not been registered yet. Therefore, in our case the original ModuleLoaderService won’t get registered because we already registered an implementation of the IModuleLoaderService service.
-
Update the Global.asax file to use the custom global application class instead of the Composite Web Application Block’s global application class. This is the code required to use the custom class described in the previous step.
<%@ Application Language="C#" Inherits="WebClientApplication" %>
Using the [Service] attribute
Add a reference in your modules to the CompositeWeb.Extensions assembly/project (download below) and simply decorate your classes with the attribute ;). The attribute’s constructor has several overloads you can use.
- For services in foundational modules, you will most likely use the overload that doesn’t require you to specify the scope (because services in foundational modules are always global). If the scope is not specified, ServiceScope.Global is used by default.
// Register the service with the type INavigationService, // as a global service [Service(typeof(INavigationService))] public class RedirectNavigationService : INavigationService { // … }
- For services in business modules, you might want to use the very overload used for foundational modules or any of the following:
// Register the service with the type IService1, as a module service [Service(ServiceScope.Module, typeof(IService1))] public class Service1 : IService1 { // … } // Register the service with the type IService1, as a global service [Service(ServiceScope.Global, typeof(IService1))] public class Service1 : IService1 { // … }
Source code
- You can get the source code of the CompositeWeb.Extensions project (which includes the code for the [Service] attribute and the custom ModuleLoaderService) by downloading the CompositeWeb.Extensions.zip file below. If you face problems building the solution, make sure the reference to the Composite Web Application Block assembly is correct.
- Important: The code is provided "as is" without warranty of any kind.

November 30th, 2007 at 9:54 am
Hi Mariano,
I have to load the Business Module on demand base’s,
.i.e. When user login’s.
So to achive this I have to load Only “Login Module ” when User request the Url First time, On the successful login the based on the user only perticular module has to be loaded.
How to achive this
Thanks
Anil (anil.s.gayakwad@gmail.com)
November 30th, 2007 at 6:13 pm
Anil,
Modules are loaded at the application level and not in a per-user basis. If what you want is to display certain features of modules to a user depending on his role, you can achieve that by using the built-in security features of the WC-SF.
For more information, see
“How to: Add Module Pages to the Site Map” in the WC-SF help and the Hands On Labs:
https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=websf&ReleaseId=6897
There is one lab dedicated to security & authorization.
Mariano