XAML Playground
about XAML and other Amenities

Inject ApplicationServices with MEF reloaded: supporting recomposition

2011-02-17T16:35:03+01:00 by Andrea Boschin

In my I've presented a simple solution that allows people to adds instances of ApplicationServices, without the need of adding the markup to the App.xaml file. The solution was interesting as a demonstration of how MEF works, but after some tests and some feedbacks I had I realized it is not applicable in a real world solution because of a subtle problem.

The problem is given by a conflict in the lifetime of the application between the Application_Start event and the StartService method. If you put the code to inizialize the CompositionContainer inside of the Application_Start event you may get an error stating that the composition has already initialized, just because when the StartService method call the CompositionInitializer method, to compose the services, the container is implicitly initialized. Unfortunately the Application_Start method is called after the StartService so here is the reason of the error you get.

For a long while I've thinked this may remain only a beautiful example because I do not discovered a valid solution, but today I convinced myself that a correction to this problem is possible and that it also add the ability of supporting recomposition to the HostService. As far as I know there is not any concern in calling the methods of the service with a delay respect the runtime raises the corrisponding method, almost until the method is not one of the Exit flavour. During the exit we have to free resources, stop threads, and so on and it is important to make these actions as soon as possible to let the runtime able to close the application faster. But the initialization phase may start in a later time, with the sole constraint that the sequence of the methods should be the same.

So this morning I've worked in this direction and in the following box you can see the resulting solution:

   1: public class HostService : IApplicationService, IApplicationLifetimeAware, IPartImportsSatisfiedNotification
   2: {
   3:     /// <summary>
   4:     /// Gets or sets the context.
   5:     /// </summary>
   6:     /// <value>The context.</value>
   7:     public ApplicationServiceContext Context { get; set; }
   8:     /// <summary>
   9:     /// Gets or sets the current.
  10:     /// </summary>
  11:     /// <value>The current.</value>
  12:     public static HostService Current { get; private set; }
  13:  
  14:     /// <summary>
  15:     /// Gets or sets the initialized.
  16:     /// </summary>
  17:     /// <value>The initialized.</value>
  18:     private List<IApplicationService> Initialized { get; set; }
  19:  
  20:     /// <summary>
  21:     /// Gets or sets the services.
  22:     /// </summary>
  23:     /// <value>The services.</value>
  24:     [ImportMany(AllowRecomposition = true, RequiredCreationPolicy = CreationPolicy.Shared)]
  25:     public List<IApplicationService> Services { get; set; }
  26:  
  27:     /// <summary>
  28:     /// Initializes a new instance of the <see cref="HostService"/> class.
  29:     /// </summary>
  30:     public HostService()
  31:     {
  32:         if (HostService.Current != null)
  33:             throw new InvalidOperationException("Service already exists");
  34:  
  35:         HostService.Current = this;
  36:         this.Initialized = new List<IApplicationService>();
  37:     }
  38:  
  39:     /// <summary>
  40:     /// Called by an application immediately after the <see cref="E:System.Windows.Application.Exit"/> event occurs.
  41:     /// </summary>
  42:     public void Exited()
  43:     {
  44:         foreach (IApplicationLifetimeAware service in this.Services.OfType<IApplicationLifetimeAware>())
  45:             service.Exited();
  46:     }
  47:  
  48:     /// <summary>
  49:     /// Called by an application immediately before the <see cref="E:System.Windows.Application.Exit"/> event occurs.
  50:     /// </summary>
  51:     public void Exiting()
  52:     {
  53:         foreach (IApplicationLifetimeAware service in this.Services.OfType<IApplicationLifetimeAware>())
  54:             service.Exiting();
  55:     }
  56:  
  57:     /// <summary>
  58:     /// Called by an application immediately after the <see cref="E:System.Windows.Application.Startup"/> event occurs.
  59:     /// </summary>
  60:     public void Started()
  61:     {
  62:         CompositionInitializer.SatisfyImports(this);
  63:     }
  64:  
  65:     /// <summary>
  66:     /// Called by an application immediately before the <see cref="E:System.Windows.Application.Startup"/> event occurs.
  67:     /// </summary>
  68:     public void Starting()
  69:     {
  70:     }
  71:  
  72:     /// <summary>
  73:     /// Called by an application in order to initialize the application extension service.
  74:     /// </summary>
  75:     /// <param name="context">Provides information about the application state.</param>
  76:     public void StartService(ApplicationServiceContext context)
  77:     {
  78:         this.Context = context;
  79:     }
  80:  
  81:     /// <summary>
  82:     /// Called by an application in order to stop the application extension service.
  83:     /// </summary>
  84:     public void StopService()
  85:     {
  86:         foreach (IApplicationService service in this.Services)
  87:             service.StopService();
  88:     }
  89:  
  90:     /// <summary>
  91:     /// Called when a part's imports have been satisfied and it is safe to use.
  92:     /// </summary>
  93:     public void OnImportsSatisfied()
  94:     {
  95:         // buffer for services that raises exception during initialization
  96:         List<IApplicationService> errors = new List<IApplicationService>();
  97:  
  98:         // call StartService
  99:         foreach (IApplicationService service in this.Services.OfType<IApplicationService>().Except(this.Initialized))
 100:         {
 101:             try
 102:             {
 103:                 service.StartService(this.Context);
 104:             }
 105:             catch
 106:             {
 107:                 errors.Add(service);
 108:             }
 109:         }
 110:  
 111:         // call Starting
 112:         foreach (IApplicationLifetimeAware service in 
 113:             this.Services.OfType<IApplicationLifetimeAware>()
 114:             .Except(this.Initialized.OfType<IApplicationLifetimeAware>())
 115:             .Except(errors.OfType<IApplicationLifetimeAware>()))
 116:         {
 117:             try
 118:             {
 119:                 service.Starting();
 120:             }
 121:             catch
 122:             {
 123:                 errors.Add((IApplicationService)service);
 124:             }
 125:         }
 126:  
 127:         // call Started
 128:         foreach (IApplicationLifetimeAware service in 
 129:             this.Services.OfType<IApplicationLifetimeAware>()
 130:             .Except(this.Initialized.OfType<IApplicationLifetimeAware>())
 131:             .Except(errors.OfType<IApplicationLifetimeAware>()))
 132:         {
 133:             try
 134:             {
 135:                 service.Started();
 136:             }
 137:             catch
 138:             {
 139:                 errors.Add((IApplicationService)service);
 140:             }
 141:         }
 142:  
 143:         // register ad Initialized to avoid duplicated initialization in case of recomposition
 144:         foreach (IApplicationService service in 
 145:             this.Services.OfType<IApplicationService>()
 146:             .Except(this.Initialized)
 147:             .Except(errors))
 148:             this.Initialized.Add(service);
 149:  
 150:         // remove services that raised exceptions
 151:         foreach (IApplicationService service in errors)
 152:             this.Services.Remove(service);
 153:     }
 154: }

As you can see the structure of the service remains the same, and also the management of the Exit, Exited and StopService is not changed. The very important change is in the Started method, that is called after the Application_Start event. In this method I only perform the composition. Then, in the OnImportsSatisfied method I start a sequence to emulate the startup of the service, repeating the flow that an ApplicationService usually follow during it initialization. I've also added some code to manage exceptions raised during the initialization. In these cases the services that raised the errors do not follow the remaining parts of the initialization and finally are removed from the Services collection.

In the service I maintain a collection of Initialized services because if the recomposition happen I must not repeat the inizialization of services that were already initialized previously. This is also the reason that make me asking a Shared CreationPolicy, because when the composition container compose the Services property does not have to create new instances but only have to add the new services coming from the recomposition.

I hope this solution to be more reliable than the previous one. As usual I will be glad to have feedback from you to continue improving the code..

Categories:   TIPS
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed