Tuesday 26 April 2011

Model View Presenter (MVP) and Sitecore

There are various ways to try and abstract our interaction with sitecore. The two most common that we have used are the Model View Presenter (MVP)  and MVC ( Model View Controller) patterns. In this post I will talk about how to use MVP with sitecore.

FIrst of: Don't use XSLT. It is untestable.

We start of with a base user control that all our controls will extend. All we are doing here is injecting the Presenter using structure map ( abstracted away inside DependencyInjector) and then calling a common method that will exist on all presenters, Init(). It also provides some common properties that we will be using later on.

 public class MVPUserControl<TPresenter> : BaseUserControl  
     where TPresenter : MVPPresenter  
   {  
     protected override void OnInit(EventArgs e)  
     {  
       var presenter = DependencyInjector.Instance.GetDefaultInstance<TPresenter>();  
       presenter.View = this;  
       IntegrateWithPresenter(presenter);  
       presenter.Init();  
       base.OnInit(e);  
     }  
     public bool IsFirstViewing  
     {  
       get { return !IsPostBack; }  
     }  
     public Guid CurrentItemId  
     {  
       get { return Sitecore.Context.Item.ID.ToGuid(); }  
     }  
     public string CurrentItemName  
     {  
       get { return Sitecore.Context.Item.Name; }  
     }  
     public string LanguageCode { get { return "UK"; } }  
     public string RegionCode { get { return "2"; } }  
     public virtual void IntegrateWithPresenter(TPresenter presenter) { }  
   }  


In the user control ( the view) we keep it very simple and it should just be used to pass information from and to. This way we can make it conform to an interface and as a result we can mock it using different frameworks.

The interface:

 public interface IView : IMVPView  
   {  
     string SectionTitle { set; }  
     IList<FaqItem> Questions { set; }  
   }  


The Implementation:

 public partial class FaqSection : MVPUserControl<Presenter>, IView  
   {  
     public string SectionTitle { protected get; set; }  
     public IList<FaqItem> Questions  
     {  
       set  
       {  
         rptFaqs.DataSource = value;  
         rptFaqs.DataBind();  
       }  
     }  
   }  

As you probably saw from our base user control, our presenter get magically wired up and it calls the Init() method when loaded.

The Presenter base class:

 public class MVPPresenter<TView> : MVPPresenter where TView : IMVPView  
   {  
     public new TView View  
     {  
       get { return (TView)base.View; }  
       set { base.View = value; }  
     }  
     protected internal override void CheckViewType(object view)  
     {  
       Type viewType = view.GetType();  
       Type allowedType = typeof(TView);  
       if (viewType != allowedType && allowedType.IsAssignableFrom(viewType) == false)  
         throw new InvalidOperationException(Resources.Constants.ObjectType + " " + viewType.Name + Resources.Constants.NotAllowed + " " + GetType().Name);  
     }  
   }  
   public abstract class MVPPresenter  
   {  
     private object m_view;  
     public object View  
     {  
       get { return m_view; }  
       set  
       {  
         CheckViewType(value);  
         m_view = value;  
       }  
     }  
     public virtual void Init() { }  
     protected internal virtual void CheckViewType(object view) { }  
   }  



The Presenter:


  public class Presenter :MVPPresenter<IView>  
   {  
     private readonly IFaqRepository m_repository;  
     public Presenter(IFaqRepository repository)  
     {  
       m_repository = repository;  
     }  
     public override void Init()  
     {  
       base.Init();  
       var section = m_repository.GetFaqSection(View.CurrentItemId);  
       View.Questions = section.Questions;  
       View.SectionTitle = section.SectionTitle;  
     }  
   }  
As you can see, all the asp.net and sitecore nastiness (Sitecore.Context.Item) is hidden in the view. The presenter just deals with initiating the repositories and doing some simple logic and passing it down to the view.

I can test this code. Happy days!

3 comments:

  1. Great stuff! I've been using the same MVP approach with Sitecore, now I am looking into using Razor+MVC+Sitecore.
    What I have seen so far is that still there are some limitations:
    - limited helper class for inline editing
    - no razor-way for (DMS) so far

    Maybe it's too early to go with MVC+Razor

    ReplyDelete
  2. So are you guys just using this for sections of the site that contain data that is not stored in Sitecore or have you managed to create views that are able to access Sitecore data?

    I think it would be possible if you were using Glass Mapper as you could just pass the presenter constructor the interface that it uses

    ReplyDelete
  3. We are using it for data stored in sitecore. The IFaqRepository is a sitecore repository. The view exposes the Sitecore.Context.Item.Id ...

    ReplyDelete