views:

280

answers:

2

I am just learning MVVM with MEF and already see the benefits but I am a little confused about some implementation details. The app I am building has several Models that do the same with with different entities (WCF RIA Services exposing a Entity framework object) and I would like to avoid implementing a similar interface/model for each view I need and the following is what I have come up with though it currently doesn't work.

The common interface has a new completed event for each model that implements the base model, this was the easiest way I could implement a common class as the compiler did not like casting from a child to the base type.

The code as it currently sits compiles and runs but the is a null IModel being passed into the [ImportingConstructor] for the FaqViewModel class.

I have a common interface (simplified for posting) defined as follows, this should look familiar to those who have seen Shawn Wildermuth's RIAXboxGames sample.

public interface IModel
{
    void GetItemsAsync();
    event EventHandler<EntityResultsArgs<faq>> GetFaqsComplete;
}

A base method that implements the interface

public class ModelBase : IModel
{
    public virtual void GetItemsAsync() { }
    public virtual event EventHandler<EntityResultsArgs<faq>> GetFaqsComplete;
    protected void PerformQuery<T>(EntityQuery<T> qry, EventHandler<EntityResultsArgs<T>> evt) where T : Entity
    {
        Context.Load(qry, r =>
        {
            if (evt == null) return;
            try
            {
                if (r.HasError)
                {
                    evt(this, new EntityResultsArgs<T>(r.Error));
                }
                else if (r.Entities.Count() > 0)
                {
                    evt(this, new EntityResultsArgs<T>(r.Entities));
                }
            }
            catch (Exception ex)
            {
                evt(this, new EntityResultsArgs<T>(ex));
            }
        }, null);
    }

    private DomainContext _domainContext;
    protected DomainContext Context
    {
        get
        {
            if (_domainContext == null)
            {
                _domainContext = new DomainContext();
                _domainContext.PropertyChanged += DomainContext_PropertyChanged;
            }

            return _domainContext;
        }
    }

    void DomainContext_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "IsLoading":
                AppMessages.IsBusyMessage.Send(_domainContext.IsLoading);
                break;
            case "IsSubmitting":
                AppMessages.IsBusyMessage.Send(_domainContext.IsSubmitting);
                break;
        }
    }
}

A model that implements the base model

[Export(ViewModelTypes.FaqViewModel, typeof(IModel))]
public class FaqModel : ModelBase
{
    public override void GetItemsAsync()
    {
        PerformQuery(Context.GetFaqsQuery(), GetFaqsComplete);
    }

    public override event EventHandler<EntityResultsArgs<faq>> GetFaqsComplete;
}

A view model

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export(ViewModelTypes.FaqViewModel)]
public class FaqViewModel : MyViewModelBase
{
    private readonly IModel _model;

    [ImportingConstructor]
    public FaqViewModel(IModel model)
    {
        _model = model;
        _model.GetFaqsComplete += Model_GetFaqsComplete;

        _model.GetItemsAsync(); // Load FAQS on creation
    }

    private IEnumerable<faq> _faqs;
    public IEnumerable<faq> Faqs
    {
        get { return _faqs; }
        private set
        {
            if (value == _faqs) return;

            _faqs = value;
            RaisePropertyChanged("Faqs");
        }
    }

    private faq _currentFaq;
    public faq CurrentFaq
    {
        get { return _currentFaq; }
        set
        {
            if (value == _currentFaq) return;

            _currentFaq = value;
            RaisePropertyChanged("CurrentFaq");
        }
    }

    public void GetFaqsAsync()
    {
        _model.GetItemsAsync();
    }

    void Model_GetFaqsComplete(object sender, EntityResultsArgs<faq> e)
    {
        if (e.Error != null)
        {
            ErrorMessage = e.Error.Message;
        }
        else
        {
            Faqs = e.Results;
        }
    }
}

And then finally the Silverlight view itself

public partial class FrequentlyAskedQuestions
{
    public FrequentlyAskedQuestions()
    {
        InitializeComponent();
        if (!ViewModelBase.IsInDesignModeStatic)
        {
            // Use MEF To load the View Model
            CompositionInitializer.SatisfyImports(this);
        }
    }

    [Import(ViewModelTypes.FaqViewModel)]
    public object ViewModel
    {
        set
        {
            DataContext = value;
        }
    }
}
A: 

It seems as though I am going down the wrong path with trying to refactor into multiple models. As seen here, http://msdn.microsoft.com/en-us/magazine/dd458800.aspx#id0090019 if would appear the best thing to do would be to think about the model as an instance of the EDMX class referenced via RIA Services. As such, the model should contain all methods and event handlers needed to access the DomainContext.

If anybody has other thoughts I would be open to them.

Brian
A: 

As a fellow noob, I am just starting to play with MEF, and I think I have identified a possible problem with your code. From your question, it sounds like your main problem is the null IModel reference.

Try changing this:

private readonly IModel _model;

to this:

[Import]
public IModel _model { get; set; }

I haven't yet played with how MEF likes private and readonly properties, so try setting to public and then verify that _model isn't null when you first try to use it.

Dave