Using MEF to Work Offline in Silverlight 3

by Administrator 8. December 2009 02:22

Silverlight 3 ships with out of browser support which is a particularly useful feature.  Combining OOB with the ability to detect network status can allow your users to consume your Web app without a connection.  Combining both of these features with Isolated Storage and the right design approach can provide a seamless Occasionally Connected application experience using Silverlight 3.

Wine Research

I will need another sample application to demonstrate these concepts, this time I’m going with HandWaver Wine Research:

wineresearch0

When we navigate to [Questions] we’re going to see a list of questions about things like Wine/Food pairing and similar topics; the questions were submitted using another system.  The green orb in the upper right hand corner indicates the app is online and connected.  I have also included a button to toggle the online/offline status which is easier than pulling my plug or disabling my network device in Windows.  I will be using the HandWaver.AG Silverlight Guidance project from previous articles.

Application Goals

We only have a couple of functions for this application.  We need to read questions people send us about wine, answer those questions, and send a message to the question asker.  Because some of these questions may take careful thought and I don’t always have an internet connection, I want the application to seamlessly work no matter my connection state.  I have only two modules, abstracted behind interfaces, for accomplishing this: IQuestionRepository and IResearchMessageSender.  I would like to use a dependency-injection like approach for configuring these modules, but I must be able to react to environmental changes as well.

Initializing MEF

MEF is much more than an IoC container and in this article we will explore one of the more intriguing features.  Recomposition in MEF is easy to turn on, not straightforward to get working, and you must design for it.  This should not be taken as a general introduction to MEF but we can review:

MEF works by satisfying [Import]s with matching [Export]s.   Instances with either Imports or Exports are typically called Parts in MEFspeak.  Parts can either be handed to MEF of discovered from some type of Catalog.

Since we have two different parts which need to change in concert, the idea of a Catalog works well here.  I am using a Presenter combined with ViewModel to drive the question/answer screen.  The presenter utilizes MEF to satisfy module needs as follows.

namespace HandWaver.AG.WorkOfflineDemo.PresentationModel
{
    public class ResearchPresenter : IPartImportsSatisfiedNotification
    {
        public ResearchPresenter(IResearchView v)
        {
            _view = v;
        }

        IResearchView _view;        
        IQuestionRepository _qRepo;

        [Import(AllowRecomposition = true)]
        public IQuestionRepository QuestionRepo
        {
            get { return _qRepo; }
            set
            {
                _qRepo = value;
                if (null != _view.ViewModel)
                {
                    _view.ViewModel.QuestionRepo = _qRepo; 
                }
            }
        }

        [Import]
        public NetworkStatusMonitor NetworkStatusMonitor { get; set; }

        public void OnImportsSatisfied()
        {
            NetworkStatusMonitor.GoOnline += new Action(NetworkStatusMonitor_GoOnline);
            NetworkStatusMonitor.GoOffline += new Action(NetworkStatusMonitor_GoOffline);
        }


        public void InitView()
        {
            var mdl = new WineResearchViewModel();
            QuestionRepo.FindUnanswered((qs) =>
            {
                mdl.UnansweredCount = qs.Count;
                mdl.Questions.Clear();
                mdl.Questions = new ObservableCollection<Question>();
                qs.ForEach((q) => mdl.Questions.Add(q));
            });

            mdl.QuestionRepo = QuestionRepo;

            _view.ViewModel = mdl;            
        }

        public void SaveSelected()
        {
            var q = _view.ViewModel.SelectedQuestion;
            QuestionRepo.MarkAnswered(q, () => _view.ViewModel.Questions.Remove(q));
        }

There are two Import definitions for this class.  One is a NetworkStatusMonitor that tells us when connection status changes using the baked-in Silverlight 3 APIs and uses the bare Import attribute.  The second is more interesting as it also sets AllowRecomposition to be true.  What this tells MEF is that we can re satisfy this import later if the situation arises.  This will help us meet our goals if we design carefully: when online, I’ll use a WCF based implementation of IQuestionRepository, when offline I’ll save messages to Isolated Storage to be forwarded on later.  Because I’m setting the current IQuestionRepository as a property of the ViewModel I can actually see this at work:

OnlineQuestionRepo

OfflineQuestionRepo

In each case, note the area highlighted in red and the green/red orb indicating connection status .  How did we accomplish this?

Groups of Services and Re Composition

The high level static structure for the different pieces needed to make this work is pictured here. 

staticstructure

Some extra work is needed to translate this into something MEF can help with.  MEF provides Catalogs, Imports, Exports, and of course the CompositionContainer however the out of the box components did not quite do what I wanted in a straightforward way so some extensions were required.  Let’s walk through the code needed to let ServiceFactory switch between OnlineServices and OfflineServices.

When first starting, we setup the MEF container based on the current connection status:

public class ServiceFactory
 {
     [Import]
     public NetworkStatusMonitor StatusMonitor { get; set; }

     [Import]
     public CompositionContainer Container { get; set; }

     IServiceGroup _services;

     public void Initialize()
     {
         if (StatusMonitor.IsOnline)
         {
             _services = new OnlineServices();
         }
         else
         {
             _services = new OfflineServices();
         }
         AddAll(_services);

AddAll performs the initial composition based on whichever group of services (Modules) were applicable at the time.  Things get interesting when we need to recompose based on an environment change.

void StatusMonitor_GoOnline()
{
    var oldSvc = _services;
    _services = new OnlineServices();
    RecomposeAll(oldSvc, _services);
}

void StatusMonitor_GoOffline()
{
    var oldSvc = _services;
    _services = new OfflineServices();
    RecomposeAll(oldSvc, _services);
}

The intent should be clear at this point.  We are going to remove the “old services” from the Composition and add the “new services” to the composition.  The rest of the code here is a little bit more verbose than need be in order to make it very clear what’s going on.

void RecomposeAll(IServiceGroup old, IServiceGroup @new)
{
    var exportQR = GetDefaultExport(typeof(IQuestionRepository), old.QuestionRepo, false);
    var exportIRM = GetDefaultExport(typeof(IQuestionRepository), old.ResearchMessageSender, false);

    var addQR = GetDefaultExport(typeof(IQuestionRepository), @new.QuestionRepo, true);
    var addIRM = GetDefaultExport(typeof(IResearchMessageSender), @new.ResearchMessageSender, true);

    var batch = new CompositionBatch();
    batch.RemovePart(new SingleExportPart(exportQR, old.QuestionRepo.Name));
    batch.RemovePart(new SingleExportPart(exportIRM, old.ResearchMessageSender.Name));

    batch.AddPart(new SingleExportPart(addQR, @new.QuestionRepo.Name));
    batch.AddPart(new SingleExportPart(addIRM, @new.ResearchMessageSender.Name));
    
    Container.Compose(batch);
}

In each case, we must provide enough information for the CompositionContainer to:

  1. Find the currently exported Part for a given contract
  2. Create a ComposablePart that maps to each
  3. Create new Exports for the new Parts
  4. Create a ComposablePart for the new Exports
  5. Create a CompositionBatch to do the Add/Remove as an atomic operation

I found that the code below was needed in order to successfully accomplish the aim.

protected Export GetDefaultExport(Type contractType, Type implType, bool add)
{
    string contractName = contractType.ToString();
    var metadata = new Dictionary<string, object>();
    metadata.Add("ExportTypeIdentity", contractName);
    Func<object> valueFinder = null;

    if (add)
    {
        valueFinder = () => Activator.CreateInstance(implType);
    }
    else
    {
        valueFinder = () => (from p in Container.Catalog.Parts where p.Metadata["ExportTypeIdentity"] == contractType select p).FirstOrDefault();
    }
    var export = new Export(contractName, metadata, valueFinder);
    return export;
}

/// <summary>
/// A simple container to work with MEF container constructs
/// </summary>
public class SingleExportPart : ComposablePart
{
    public SingleExportPart(Export e, string implVal)
    {
        _export = e;
        _implVal = implVal;
    }

    Export _export;
    string _implVal;

    public override IEnumerable<ExportDefinition> ExportDefinitions
    {
        get { return new ExportDefinition[] { _export.Definition }; }
    }

    public override IEnumerable<ImportDefinition> ImportDefinitions
    {
        get { return Enumerable.Empty<ImportDefinition>(); }
    }

    public override object GetExportedValue(ExportDefinition definition)
    {
        return _export.Value;
    }

    public override void SetImport(ImportDefinition definition, IEnumerable<Export> exports)
    {
        
    }
    public override IDictionary<string, object> Metadata
    {
        get
        {
            return new Dictionary<string, object>();
        }
    }

    public override bool Equals(object obj)
    {
        return GetHashCode().Equals(obj.GetHashCode());
    }

    public override int GetHashCode()
    {                
        return ("SingleExportPart" + _implVal).GetHashCode();
    }

}

Without the source for the MEF Preview I would have had a more difficult time with this.  It seems like it should be easier to remove Parts from the composition.  To be fair, this is easier if you are using [ImportMany] instead of [Import].  The final part needed to tie everything together is to add a PartCreationPolicy to the Parts being added & removed from the composition batch:

[Export(typeof(IQuestionRepository))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class QuestionRepository :   IQuestionRepository
{

Without specifying the NonShared CreationPolicy MEF will by default create a single instance of the Part.  When trying to remove the part from the CompositionContainer MEF will throw an exception basically stating that the Part couldn’t be removed because it was still being used elsewhere to satisfy another Import.

Further Research

The example shown here is somewhat incomplete.  Because service calls in SilverLight are always asynchronous, we would probably want some thread safety as we swap Imports in and out.  It would also be appropriate to enhance the interfaces used here to notify Parts as they are being added/removed such that the Offline/Online families of services can do appropriate cleanup.

Tags:

About the author

Damon Payne is a Microsoft MVP specializing in Smart Client solution architecture. 

INETA Community Speakers Program

Month List

Page List

flickr photostream