Successfully reported this slideshow.

Progressive EPiServer Development

4,235 views

Published on

Alternative ways of developing web sites using EPiServer CMS. An introduction to three open source frameworks that allow us to better tackle complexity, have a more enjoyable development experience and deliver better, well tested sites using EPiServer CMS.

Published in: Technology
  • Be the first to comment

Progressive EPiServer Development

  1. 1. EPiServerDevelopment
  2. 2. I love building beautifull, fast and robustweb sites for high profile customers.When they have editorial content EPiServerCMS provides us with a solid foundation tobuild upon.The boring stuff is already in place. We asdevelopers can focus on delivering businessvalue from the get-go.
  3. 3. But, I also have a passion for ”agile”software architecture.A passion for clean code and flexiblesystems.A passion for studying my field.A passion for learning about principles andpractices such as the SOLID principles.
  4. 4. But, I also have a passion for ”agile”software architecture.A passion for clean code and flexiblesystems.A passion for studying my field.A passion for learning about principles andpractices such as the SOLID principles. ’
  5. 5.
  6. 6. While EPiServer is mature after a decade ofdevelopment it was first released longbefore many of us had ever heard about TestDriven Development or MVC.
  7. 7. While EPiServer is mature after a decade ofdevelopment it was first released longbefore many of us had ever heard about TestDriven Development or MVC.In other words; it’s built in a way thatmakes it hard to apply moderndevelopment practices and principles.
  8. 8. ’ ’
  9. 9. EPiServer development revolves aroundpages, represented as PageData objects.While these are primarily intended foreditorial content they aren’t limited to that.
  10. 10. In fact we can use them to model just aboutanything. Orders, events, comments etc.When we do that we never have to thinkabout how to persist them.EPiServer has done an excellent job atabstracting the database to the point we’rewe take for granted that saving, reading anddeleting will just work.
  11. 11. We also instantly get an administrativeinterface for whatever we have modeled.And access rights management, support formultiple language versions, versioning andso on.Of course all of these are optimized foreditorial content.
  12. 12. Of course all of these are optimized foreditorial content.But the fact that we can create just aboutanything without having to think aboutpersistence and get at least some sort ofadministrative UI is powerful.
  13. 13. Another strength is its flexibility.I can often find myself frustrated when I tryto extend the system, but the fact of thematter is that compared to many othercommercial products it’s highly extendable.And while I would have preferred a moreopen architecture where I could just switchout or extend standard components thereare extension points for pretty mucheverything.
  14. 14. Often our customers have bought EPiServerfor a simple public web site.But once they’ve bought it, they want us touse it for everything.In those cases there are three things thatfrustrate especially much.
  15. 15. Ironically, the thing that frustrate me themost is closely related to the thing that I likethe most – the way page types and therebyPageData objects are modeled.In the database through a UI.The fact that page types are defined in thedatabase has several negative consequences.
  16. 16. While we’re able to model just aboutanything in terms of data, we can’t havelogic, behavior, in our model objects.The logic still has to go somewhere.Often it ends up in the presentation layer.
  17. 17. We’re also not able to use inheritance orpolymorphism.There’s no relationship between differentpage types.Similar data has to be modeled multipletimes. So does changes.
  18. 18. Deployment is painful.We can’t just deploy code or scripteddatabase changes. We have to deploy data.And we have to use magic strings and caststo access data.Actively bypassing the first test that thecompiler otherwise provides us with.
  19. 19. The Dependency Inversion Principle statesthat our code should “Depend onabstractions, not on concretions”.This is essential for truly flexible systems.And for unit testing.Unfortunately abstractions are scarce in theEPiServer API.
  20. 20. The class we use for saving, retrieving anddeleting PageData object, DataFactory, is asingleton with non-virtual methods.And it doesn’t implement any interface thatdefines many of its members.This makes it hard to isolate our code fromit, and thereby from the rest of the EPiServerAPI, the database and HTTP context.
  21. 21. My final, big frustration with EPiServerCMS is its tight coupling to Web Forms.While I’m not convinced that ASP.NETMVC, or any other MVC framework for thatmatter, is a perfect fit for many of the siteswe build on EPiServer, Web Forms severlylimits our ability to create flexible systemsbuilt using Test Driven Development.
  22. 22. Even if the EPiServer API would have hadabstractions, how would we have gottenconcrete implementations of them into ourcode?If we had been using ASP.NET MVC wecould have create a custom controllerfactory.But with Web Forms the frameworkinstantiates components such as usercontrols.
  23. 23. Not to mention that the page life cycle andon-by-default viewstate functionally isn’texactly architectural beauty or embracinghow the web works.Unfortunately though, the current version ofEPiServer CMS has a few fairly importantfeatures such as XForms and DynamicContent that requires Web Forms.
  24. 24.
  25. 25. In 2009 an open source project called PageType Builder was born.At its core it does two things…
  26. 26. It scans the application domain for classesthat appear to define page types and createsand updates the corresponding page typesin the database.We can create page types using code, andonly code. We don’t have to move datawhen we’re deploying. Only code.And we can use inheritance between pagetypes.
  27. 27. It also listens to events from the EPiServerAPI and intercepts requests for PageDataobjects.If a PageData object that is about to bereturned is of a page type that can bemapped to a class it creates an instance ofthat class and copies the data from theoriginal object.Finally, it swaps out the object beingreturned with the instance of the class.
  28. 28. This means that DataFactory no longerreturns plain PageData objects.It returns instances of classes that we’vecreated.Those still inherit from PageData, meaningthat they play nice with the rest ofEPiServer’s functionality.But they can have methods and logic inproperty getters and setters.
  29. 29. public class Article{}
  30. 30. using PageTypeBuilder;public class Article{}
  31. 31. using PageTypeBuilder;public class Article : TypedPageData{}
  32. 32. using PageTypeBuilder;[PageType]public class Article : TypedPageData{}
  33. 33. using PageTypeBuilder;[PageType]public class Article : TypedPageData{}
  34. 34.
  35. 35. using PageTypeBuilder;[PageType]public class Article : TypedPageData{}
  36. 36. using PageTypeBuilder;[PageType( Filename = "~/Templates/Article.aspx")]public class Article : TypedPageData{}
  37. 37. using PageTypeBuilder;[PageType( Filename = "~/Templates/Article.aspx", Description = "My page type")]public class Article : TypedPageData{}
  38. 38. using PageTypeBuilder;[PageType( Filename = "~/Templates/Article.aspx", Description = "My page type")]public class Article : TypedPageData{}
  39. 39. using PageTypeBuilder;[PageType]public class Article : TypedPageData{}
  40. 40. using PageTypeBuilder;[PageType]public class Article : TypedPageData{ public virtual string MainBody { get; set; }}
  41. 41. using PageTypeBuilder;[PageType]public class Article : TypedPageData{ [PageTypeProperty] public virtual string MainBody { get; set; }}
  42. 42. using PageTypeBuilder;[PageType]public class Article : TypedPageData{ [PageTypeProperty] public virtual string MainBody { get; set; }} ’
  43. 43. using PageTypeBuilder;[PageType]public class Article : TypedPageData{ [PageTypeProperty(Type = typeof(PropertyString))] public virtual string MainBody { get; set; }}
  44. 44. get{ return this.GetPropertyValue(p => p.MainBody);}set{ this.SetPropertyValue(p => p.MainBody, value);}
  45. 45. public partial class ArticlePage : TemplatePage{} ’
  46. 46. using PageTypeBuilder.UI;public partial class ArticlePage : TemplatePage<Article>{} ’
  47. 47. using PageTypeBuilder.UI;public partial class ArticlePage : TemplatePage<Article>{ protected override void OnLoad(EventArgs e) { var bodyText = CurrentPage.MainBody; }}
  48. 48. <html> <body> <%= CurrentPage.MainBody %> </body></html>
  49. 49. Our page types can inherit base classes andimplement interfaces.We’re able to use polymorphism.One page type can return it’s name as linktext in menus while another can returnsomething completely different.While the code that renders the menu onlyknows that they both implementIMenuItem.
  50. 50. Our page types can have behavior as well asdata.They can have methods.Properties can have logic in their getters andsetters.
  51. 51. We can build page providers that utilizethat we’re dealing with different classes.So far I’ve heard about page providers builtusing Fluent NHibernate and Raven DB.
  52. 52. We can query using LINQ to Objects in astrongly typed way.And we can serialize our pages when theyare saved and push them over to a searchserver, to later query for them using astrongly typed, LINQ-like API.
  53. 53. EPiAbstractions is an open source projectthat wraps EPiServer’s API.It provides concrete implementations,facades or adapters, that delegate toEPiServer’s classes.These in turn implement interfaces.Allowing our code to depend onabstractions.
  54. 54. It also provides a simplified API thatdoesn’t map directly to EPiServer’s but iseasier to work with.For this API it also provides in-memory-implementations, allowing us to writeproduction like code in tests.Finally it also provides factory classes forcreating test data.
  55. 55.
  56. 56. [PageType]public class Article : TypedPageData{ public IEnumerable<Comment> GetComments(int max) { return DataFactory.Instance .GetChildren(PageLink) .OfType<Comment>() .Take(max); }}
  57. 57. [PageType]public class Article : TypedPageData{ public IEnumerable<Comment> GetComments(int max) { return DataFactory.Instance .GetChildren(PageLink) .OfType<Comment>() .Take(max); }} ’
  58. 58. [PageType]public class Article : TypedPageData{ IDataFactoryFacade dataFactory; public IEnumerable<Comment> GetComments(int max) { return dataFactory .GetChildren(PageLink) .OfType<Comment>() .Take(max); }} ’
  59. 59.
  60. 60. protected void Application_Start(...){ var container = new Container();}
  61. 61. protected void Application_Start(...){ var container = new Container(); container.Configure(x => x.For<IDataFactoryFacade>() .Singleton() .Use<DataFactoryFacade>());}
  62. 62. protected void Application_Start(...){ var container = new Container(); container.Configure(x => x.For<IDataFactoryFacade>() .Singleton() .Use<DataFactoryFacade>()); PageTypeResolver.Instance.Activator = new StructureMapTypedPageActivator(container);}
  63. 63. [PageType]public class Article : TypedPageData{ IDataFactoryFacade dataFactory; public Article(IDataFactoryFacade dataFactory) { this.dataFactory = dataFactory; } public IEnumerable<Comment> GetComments(int max) { return dataFactory .GetChildren(PageLink) .OfType<Comment>() .Take(max); }}
  64. 64. [PageType]public class Article : TypedPageData{ IPageRepository pageRepository; public Article(IPageRepository pageRepository) { this.pageRepository = pageRepository; } public IEnumerable<Comment> GetComments(int max) { return pageRepository .GetChildren(PageLink) .OfType<Comment>() .Take(max); }}
  65. 65. [PageType]public class Article : TypedPageData{ IPageRepository pageRepository; public Article(IPageRepository pageRepository) { this.pageRepository = pageRepository; } public IEnumerable<Comment> GetComments(int max) { return pageRepository .GetChildren<Comment>(PageLink) .Take(max); }}
  66. 66. [Test]public void GetComments(){}
  67. 67. [Test]public void GetComments(){ var epiContext = FakeEPiServerContext.Create();}
  68. 68. [Test]public void GetComments(){ var epiContext = FakeEPiServerContext.Create(); var article = CreatePage.OfType<Article>( epiContext.PageRepository);}
  69. 69. [Test]public void GetComments(){ var epiContext = FakeEPiServerContext.Create(); var article = CreatePage.OfType<Article>(); var articleReference = epiContext.PageRepository .Publish(article);}
  70. 70. [Test]public void GetComments(){ var epiContext = FakeEPiServerContext.Create(); var article = CreatePage.OfType<Article>(); var articleReference = epiContext.PageRepository .Publish(article); article = epiContext.PageRepository .GetPage<Article>(articleReference);}
  71. 71. [Test]public void GetComments(){ ... var comments = CreateSetOfPageData .Containing(11) .PagesOfType<Comment>() .ChildrenOf(article); epiContext.PageRepository .Publish(comments.Cast<PageData>());}
  72. 72. [Test]public void GetComments(){ ... int max = 10; comments = article.GetComments(max);}
  73. 73. [Test]public void GetComments(){ ... int max = 10; comments = article.GetComments(max); Assert.AreEqual(max, comments.Count());}
  74. 74. In cases where we’re building complexsystems, or a particularly complex part of asystem, we can address the Web Formsproblem by applying the Model ViewPresenter Pattern.Model View Presenter, or MVP, is a dialectof the Model View Controller. It has onesignificant difference compared to MVC; UIevents are routed to the view instead of tothe controller as in MVC.
  75. 75. While it’s fairly easy to create a basicimplementation of MVP with Web Formsthere are a number of projects that can helpus. One of them is Web Forms MVP.Given that we’re using Web Forms MVP wecan use the open source project EPiMVPwhich provides some ready-to-useintegration with EPiServer.
  76. 76. When using Web Forms MVP and EPiMVPthere are four basic components that interactwhen rendering an ASPX page or a usercontrol; A view (an ASPX or ASCX), amodel object (the PageData object), apresenter and a view model.
  77. 77. When a request comes in to a page the WebForms MVP framework instantiates apresenter and view model object.The view, which implements an interfacewhich defines members that are relevant forthe presenter, is passed to the presenter.So is the view model object.The presenter is also aware of the PageDataobject.
  78. 78. With access to the view, view model andPageData object the presenter populates theview model object with data relevant forrendering in the view.It can also interact with the view bysubscribing to events exposed by it or bycalling methods on it.
  79. 79. public class CommentsModel{} ’
  80. 80. public interface ICommentsView : IEPiView<CommentsModel>{ event EventHandler<SubmitCommentEventArgs> SubmitComment;} ’
  81. 81. public class SubmitCommentEventArgs : EventArgs{ public string AuthorName { get; private set; } public string Text { get; private set; } public SubmitCommentEventArgs( string authorName, string text) { AuthorName = authorName; Text = text; }}
  82. 82. public class CommentsPresenter : EPiPresenter<ICommentsView, Article>{} ’
  83. 83. public CommentsPresenter(ICommentsView view, Article pageData) : base(view, pageData){}
  84. 84. private IPageRepository pageRepository;public CommentsPresenter(ICommentsView view, Article pageData, IPageRepository pageRepository) : base(view, pageData){ this.pageRepository = pageRepository;} ’
  85. 85. protected void Application_Start(...){ var container = ...}
  86. 86. protected void Application_Start(...){ var container = ... PresenterBinder.Factory = new StructureMapPresenterFactory(container); ’}
  87. 87. private IPageRepository pageRepository;public CommentsPresenter(ICommentsView view, Article pageData, IPageRepository pageRepository) : base(view, pageData){ this.pageRepository = pageRepository; view.SubmitComment += SubmitComment;} ’ ’
  88. 88. void SubmitComment(object sender, SubmitCommentEventArgs e){}
  89. 89. void SubmitComment(object sender, SubmitCommentEventArgs e){ var comment = pageRepository .GetDefaultPageData<Comment>( CurrentPage.PageLink);}
  90. 90. void SubmitComment(object sender, SubmitCommentEventArgs e){ var comment = pageRepository .GetDefaultPageData<Comment>( CurrentPage.PageLink); comment.Author = e.AuthorName; comment.Text = e.Text; pageRepository.Publish(comment);}
  91. 91. void SubmitComment(object sender, SubmitCommentEventArgs e){ var comment = pageRepository .GetDefaultPageData<Comment>( CurrentPage.PageLink); comment.Author = e.AuthorName; comment.Text = e.Text; pageRepository.Publish(comment); HttpContext.Response.Redirect(CurrentPage.LinkURL);}
  92. 92. [PresenterBinding(typeof(CommentsPresenter))]public partial class Comments : EPiMvpUserControl<CommentsModel>, ICommentsView{ public event EventHandler<SubmitCommentEventArgs> SubmitComment;}
  93. 93. <fieldset> <asp:TextBox ID="CommentAuthor" TextMode="SingleLine" runat="server" /> <asp:TextBox ID="CommentText" TextMode="MultiLine" runat="server" /> <asp:Button OnClick="SubmitComment_Click" Text="Post Comment" runat="server" /></fieldset>
  94. 94. protected void SubmitComment_Click(object sender, EventArgs e){ if (SubmitComment == null) return; SubmitComment(this, new SubmitCommentEventArgs( CommentAuthor.Text, CommentText.Text));}
  95. 95. We’ve seen three frameworks that help usaddress some of the core problems we mayface as progressive developers usingEPiServer.With these in our toolbox we’re betterequipped to tackle complexity whenneeded.Remember to choose the right tools for thejob. Be pragmatic.
  96. 96. Page Type Builder can be used in allsituations and is likely to have the biggestimpact on your overall experience usingEPiServer.
  97. 97. EPiAbstractions allow you to create unittests and to use Test Driven Development.But it can also be misused. EPiServer CMSand Web Forms wasn’t designed for you tohave 100% code coverage.Don’t even try.Use it when drive good design of complexsystems.
  98. 98. Web Forms MVP and EPiMVP probablyseems like it’s complex and hard tounderstand.It is.But it can be a powerful tool to use whenyou have complex user interactions.It’s also great for being able to injectdependencies into code that you want totest.
  99. 99.
  100. 100. http://pagetypebuilder.codeplex.comhttp://epiabstractions.codeplex.comhttps://github.com/joelabrahamsson/EPiServer-MVPhttp://webformsmvp.com/http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOodHand #1: http://www.flickr.com/photos/teacherafael/2243271306/ (with permission)Hand #2: http://www.flickr.com/photos/fabianluque/5220694009/ (with permission)Fire: http://www.flickr.com/photos/olemartin/3951562058/Up/down: http://www.istockphoto.com/stock-photo-4700763-up-down-good-bad-future-past.phpConcrete plant: http://www.istockphoto.com/stock-photo-6416445-young-plant-taking-root-on-a-concrete-footpath.phpPotter: http://www.istockphoto.com/stock-photo-6791142-potter.phpHands in chains: http://www.istockphoto.com/stock-photo-11441892-dependency.phpEnterprise D: http://www.flickr.com/photos/elzey/523568670/Chain: http://www.istockphoto.com/stock-photo-2135970-breaking-free.phpViewstate t-shirt: http://www.flickr.com/photos/piyo/3742166635/Heart: http://www.flickr.com/photos/saraalfred/3199313309/

×