128
EPiServer Development

Progressive EPiServer Development

Embed Size (px)

DESCRIPTION

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.

Citation preview

Page 1: Progressive EPiServer Development

EPiServer Development

Page 2: Progressive EPiServer Development
Page 3: Progressive EPiServer Development
Page 4: Progressive EPiServer Development

I love building beautifull, fast and robust web sites for high profile customers.

When they have editorial content EPiServer CMS provides us with a solid foundation to build upon.

The boring stuff is already in place. We as developers can focus on delivering business value from the get-go.

Page 5: Progressive EPiServer Development

But, I also have a passion for ”agile” software architecture.

A passion for clean code and flexible systems.

A passion for studying my field.

A passion for learning about principles and practices such as the SOLID principles.

Page 6: Progressive EPiServer Development

But, I also have a passion for ”agile” software architecture.

A passion for clean code and flexible systems.

A passion for studying my field.

A passion for learning about principles and practices such as the SOLID principles.

Page 7: Progressive EPiServer Development

Page 8: Progressive EPiServer Development

While EPiServer is mature after a decade of development it was first released long before many of us had ever heard about Test Driven Development or MVC.

Page 9: Progressive EPiServer Development

While EPiServer is mature after a decade of development it was first released long before many of us had ever heard about Test Driven Development or MVC.

In other words; it’s built in a way that makes it hard to apply modern development practices and principles.

Page 10: Progressive EPiServer Development
Page 11: Progressive EPiServer Development

’ ’

Page 12: Progressive EPiServer Development

EPiServer development revolves around pages, represented as PageData objects.

While these are primarily intended for editorial content they aren’t limited to that.

Page 13: Progressive EPiServer Development

In fact we can use them to model just about anything. Orders, events, comments etc.

When we do that we never have to think about how to persist them.

EPiServer has done an excellent job at abstracting the database to the point we’re we take for granted that saving, reading and deleting will just work.

Page 14: Progressive EPiServer Development

We also instantly get an administrative interface for whatever we have modeled.

And access rights management, support for multiple language versions, versioning and so on.

Of course all of these are optimized for editorial content.

Page 15: Progressive EPiServer Development

Of course all of these are optimized for editorial content.

But the fact that we can create just about anything without having to think about persistence and get at least some sort of administrative UI is powerful.

Page 16: Progressive EPiServer Development

Another strength is its flexibility.

I can often find myself frustrated when I try to extend the system, but the fact of the matter is that compared to many other commercial products it’s highly extendable.

And while I would have preferred a more open architecture where I could just switch out or extend standard components there are extension points for pretty much everything.

Page 17: Progressive EPiServer Development
Page 18: Progressive EPiServer Development
Page 19: Progressive EPiServer Development
Page 20: Progressive EPiServer Development
Page 21: Progressive EPiServer Development

Often our customers have bought EPiServer for a simple public web site.

But once they’ve bought it, they want us to use it for everything.

In those cases there are three things that frustrate especially much.

Page 22: Progressive EPiServer Development
Page 23: Progressive EPiServer Development

Ironically, the thing that frustrate me the most is closely related to the thing that I like the most – the way page types and thereby PageData objects are modeled.

In the database through a UI.

The fact that page types are defined in the database has several negative consequences.

Page 24: Progressive EPiServer Development

While we’re able to model just about anything in terms of data, we can’t have logic, behavior, in our model objects.

The logic still has to go somewhere.

Often it ends up in the presentation layer.

Page 25: Progressive EPiServer Development

We’re also not able to use inheritance or polymorphism.

There’s no relationship between different page types.

Similar data has to be modeled multiple times. So does changes.

Page 26: Progressive EPiServer Development

Deployment is painful.

We can’t just deploy code or scripted database changes. We have to deploy data.

And we have to use magic strings and casts to access data.

Actively bypassing the first test that the compiler otherwise provides us with.

Page 27: Progressive EPiServer Development
Page 28: Progressive EPiServer Development

The Dependency Inversion Principle states that our code should “Depend on abstractions, not on concretions”.

This is essential for truly flexible systems.

And for unit testing.

Unfortunately abstractions are scarce in the EPiServer API.

Page 29: Progressive EPiServer Development

The class we use for saving, retrieving and deleting PageData object, DataFactory, is a singleton with non-virtual methods.

And it doesn’t implement any interface that defines many of its members.

This makes it hard to isolate our code from it, and thereby from the rest of the EPiServer API, the database and HTTP context.

Page 30: Progressive EPiServer Development
Page 31: Progressive EPiServer Development

My final, big frustration with EPiServer CMS is its tight coupling to Web Forms.

While I’m not convinced that ASP.NET MVC, or any other MVC framework for that matter, is a perfect fit for many of the sites we build on EPiServer, Web Forms severly limits our ability to create flexible systems built using Test Driven Development.

Page 32: Progressive EPiServer Development

Even if the EPiServer API would have had abstractions, how would we have gotten concrete implementations of them into our code?

If we had been using ASP.NET MVC we could have create a custom controller factory.

But with Web Forms the framework instantiates components such as user controls.

Page 33: Progressive EPiServer Development

Not to mention that the page life cycle and on-by-default viewstate functionally isn’t exactly architectural beauty or embracing how the web works.

Unfortunately though, the current version of EPiServer CMS has a few fairly important features such as XForms and Dynamic Content that requires Web Forms.

Page 34: Progressive EPiServer Development

Page 35: Progressive EPiServer Development

In 2009 an open source project called Page Type Builder was born.

At its core it does two things…

Page 36: Progressive EPiServer Development

It scans the application domain for classes that appear to define page types and creates and updates the corresponding page types in the database.

We can create page types using code, and only code. We don’t have to move data when we’re deploying. Only code.

And we can use inheritance between page types.

Page 37: Progressive EPiServer Development

It also listens to events from the EPiServer API and intercepts requests for PageData objects.

If a PageData object that is about to be returned is of a page type that can be mapped to a class it creates an instance of that class and copies the data from the original object.

Finally, it swaps out the object being returned with the instance of the class.

Page 38: Progressive EPiServer Development

This means that DataFactory no longer returns plain PageData objects.

It returns instances of classes that we’ve created.

Those still inherit from PageData, meaning that they play nice with the rest of EPiServer’s functionality.

But they can have methods and logic in property getters and setters.

Page 39: Progressive EPiServer Development
Page 40: Progressive EPiServer Development

public class Article

{

}

Page 41: Progressive EPiServer Development

using PageTypeBuilder;

public class Article

{

}

Page 42: Progressive EPiServer Development

using PageTypeBuilder;

public class Article : TypedPageData

{

}

Page 43: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

}

Page 44: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

}

Page 45: Progressive EPiServer Development

Page 46: Progressive EPiServer Development
Page 47: Progressive EPiServer Development
Page 48: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

}

Page 49: Progressive EPiServer Development

using PageTypeBuilder;

[PageType(

Filename = "~/Templates/Article.aspx")]

public class Article : TypedPageData

{

}

Page 50: Progressive EPiServer Development

using PageTypeBuilder;

[PageType(

Filename = "~/Templates/Article.aspx",

Description = "My page type")]

public class Article : TypedPageData

{

}

Page 51: Progressive EPiServer Development

using PageTypeBuilder;

[PageType(

Filename = "~/Templates/Article.aspx",

Description = "My page type")]

public class Article : TypedPageData

{

}

Page 52: Progressive EPiServer Development
Page 53: Progressive EPiServer Development
Page 54: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

}

Page 55: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

public virtual string MainBody { get; set; }

}

Page 56: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

[PageTypeProperty]

public virtual string MainBody { get; set; }

}

Page 57: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

[PageTypeProperty]

public virtual string MainBody { get; set; }

}

Page 58: Progressive EPiServer Development

using PageTypeBuilder;

[PageType]

public class Article : TypedPageData

{

[PageTypeProperty(Type = typeof(PropertyString))]

public virtual string MainBody { get; set; }

}

Page 59: Progressive EPiServer Development
Page 60: Progressive EPiServer Development

get

{

return this.GetPropertyValue(p => p.MainBody);

}

set

{

this.SetPropertyValue(p => p.MainBody, value);

}

Page 61: Progressive EPiServer Development

public partial class ArticlePage

: TemplatePage

{

}

Page 62: Progressive EPiServer Development

using PageTypeBuilder.UI;

public partial class ArticlePage

: TemplatePage<Article>

{

}

Page 63: Progressive EPiServer Development

using PageTypeBuilder.UI;

public partial class ArticlePage

: TemplatePage<Article>

{

protected override void OnLoad(EventArgs e)

{

var bodyText = CurrentPage.MainBody;

}

}

Page 64: Progressive EPiServer Development

<html>

<body>

<%= CurrentPage.MainBody %>

</body>

</html>

Page 65: Progressive EPiServer Development
Page 66: Progressive EPiServer Development

Our page types can inherit base classes and implement interfaces.

We’re able to use polymorphism.

One page type can return it’s name as link text in menus while another can return something completely different.

While the code that renders the menu only knows that they both implement IMenuItem.

Page 67: Progressive EPiServer Development

Our page types can have behavior as well as data.

They can have methods.

Properties can have logic in their getters and setters.

Page 68: Progressive EPiServer Development

We can build page providers that utilize that we’re dealing with different classes.

So far I’ve heard about page providers built using Fluent NHibernate and Raven DB.

Page 69: Progressive EPiServer Development

We can query using LINQ to Objects in a strongly typed way.

And we can serialize our pages when they are saved and push them over to a search server, to later query for them using a strongly typed, LINQ-like API.

Page 70: Progressive EPiServer Development
Page 71: Progressive EPiServer Development

EPiAbstractions is an open source project that wraps EPiServer’s API.

It provides concrete implementations, facades or adapters, that delegate to EPiServer’s classes.

These in turn implement interfaces. Allowing our code to depend on abstractions.

Page 72: Progressive EPiServer Development

It also provides a simplified API that doesn’t map directly to EPiServer’s but is easier to work with.

For this API it also provides in-memory-implementations, allowing us to write production like code in tests.

Finally it also provides factory classes for creating test data.

Page 73: Progressive EPiServer Development
Page 74: Progressive EPiServer Development

Page 75: Progressive EPiServer Development

[PageType]

public class Article : TypedPageData

{

public IEnumerable<Comment> GetComments(int max)

{

return DataFactory.Instance

.GetChildren(PageLink)

.OfType<Comment>()

.Take(max);

}

}

Page 76: Progressive EPiServer Development

[PageType]

public class Article : TypedPageData

{

public IEnumerable<Comment> GetComments(int max)

{

return DataFactory.Instance

.GetChildren(PageLink)

.OfType<Comment>()

.Take(max);

}

} ’

Page 77: Progressive EPiServer Development

[PageType]

public class Article : TypedPageData

{

IDataFactoryFacade dataFactory;

public IEnumerable<Comment> GetComments(int max)

{

return dataFactory

.GetChildren(PageLink)

.OfType<Comment>()

.Take(max);

}

}

Page 78: Progressive EPiServer Development

Page 79: Progressive EPiServer Development
Page 80: Progressive EPiServer Development

protected void Application_Start(...)

{

var container = new Container();

}

Page 81: Progressive EPiServer Development

protected void Application_Start(...)

{

var container = new Container();

container.Configure(x =>

x.For<IDataFactoryFacade>()

.Singleton()

.Use<DataFactoryFacade>());

}

Page 82: Progressive EPiServer Development

protected void Application_Start(...)

{

var container = new Container();

container.Configure(x =>

x.For<IDataFactoryFacade>()

.Singleton()

.Use<DataFactoryFacade>());

PageTypeResolver.Instance.Activator

= new StructureMapTypedPageActivator(container);

}

Page 83: Progressive EPiServer Development

[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);

}

}

Page 84: Progressive EPiServer Development
Page 85: Progressive EPiServer Development

[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);

}

}

Page 86: Progressive EPiServer Development

[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);

}

}

Page 87: Progressive EPiServer Development

[Test]

public void GetComments()

{

}

Page 88: Progressive EPiServer Development

[Test]

public void GetComments()

{

var epiContext = FakeEPiServerContext.Create();

}

Page 89: Progressive EPiServer Development

[Test]

public void GetComments()

{

var epiContext = FakeEPiServerContext.Create();

var article = CreatePage.OfType<Article>(

epiContext.PageRepository);

}

Page 90: Progressive EPiServer Development

[Test]

public void GetComments()

{

var epiContext = FakeEPiServerContext.Create();

var article = CreatePage.OfType<Article>();

var articleReference = epiContext.PageRepository

.Publish(article);

}

Page 91: Progressive EPiServer Development

[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);

}

Page 92: Progressive EPiServer Development

[Test]

public void GetComments()

{

...

var comments = CreateSetOfPageData

.Containing(11)

.PagesOfType<Comment>()

.ChildrenOf(article);

epiContext.PageRepository

.Publish(comments.Cast<PageData>());

}

Page 93: Progressive EPiServer Development

[Test]

public void GetComments()

{

...

int max = 10;

comments = article.GetComments(max);

}

Page 94: Progressive EPiServer Development

[Test]

public void GetComments()

{

...

int max = 10;

comments = article.GetComments(max);

Assert.AreEqual(max, comments.Count());

}

Page 95: Progressive EPiServer Development
Page 96: Progressive EPiServer Development

In cases where we’re building complex systems, or a particularly complex part of a system, we can address the Web Forms problem by applying the Model View Presenter Pattern.

Model View Presenter, or MVP, is a dialect of the Model View Controller. It has one significant difference compared to MVC; UI events are routed to the view instead of to the controller as in MVC.

Page 97: Progressive EPiServer Development
Page 98: Progressive EPiServer Development

While it’s fairly easy to create a basic implementation of MVP with Web Forms there are a number of projects that can help us. One of them is Web Forms MVP.

Given that we’re using Web Forms MVP we can use the open source project EPiMVP which provides some ready-to-use integration with EPiServer.

Page 99: Progressive EPiServer Development

When using Web Forms MVP and EPiMVP there are four basic components that interact when rendering an ASPX page or a user control; A view (an ASPX or ASCX), a model object (the PageData object), a presenter and a view model.

Page 100: Progressive EPiServer Development
Page 101: Progressive EPiServer Development

When a request comes in to a page the Web Forms MVP framework instantiates a presenter and view model object.

The view, which implements an interface which defines members that are relevant for the presenter, is passed to the presenter.

So is the view model object.

The presenter is also aware of the PageData object.

Page 102: Progressive EPiServer Development
Page 103: Progressive EPiServer Development
Page 104: Progressive EPiServer Development

With access to the view, view model and PageData object the presenter populates the view model object with data relevant for rendering in the view.

It can also interact with the view by subscribing to events exposed by it or by calling methods on it.

Page 105: Progressive EPiServer Development
Page 106: Progressive EPiServer Development
Page 107: Progressive EPiServer Development

public class CommentsModel

{

}

Page 108: Progressive EPiServer Development

public interface ICommentsView : IEPiView<CommentsModel>

{

event EventHandler<SubmitCommentEventArgs> SubmitComment;

}

Page 109: Progressive EPiServer Development

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;

}

}

Page 110: Progressive EPiServer Development

public class CommentsPresenter : EPiPresenter<ICommentsView, Article>

{

}

Page 111: Progressive EPiServer Development

public CommentsPresenter(ICommentsView view,

Article pageData)

: base(view, pageData)

{

}

Page 112: Progressive EPiServer Development

private IPageRepository pageRepository;

public CommentsPresenter(ICommentsView view,

Article pageData,

IPageRepository pageRepository)

: base(view, pageData)

{

this.pageRepository = pageRepository;

}

Page 113: Progressive EPiServer Development

protected void Application_Start(...)

{

var container = ...

}

Page 114: Progressive EPiServer Development

protected void Application_Start(...)

{

var container = ...

PresenterBinder.Factory

= new StructureMapPresenterFactory(container);

} ’

Page 115: Progressive EPiServer Development

private IPageRepository pageRepository;

public CommentsPresenter(ICommentsView view,

Article pageData,

IPageRepository pageRepository)

: base(view, pageData)

{

this.pageRepository = pageRepository;

view.SubmitComment += SubmitComment;

}

Page 116: Progressive EPiServer Development

void SubmitComment(object sender, SubmitCommentEventArgs e)

{

}

Page 117: Progressive EPiServer Development

void SubmitComment(object sender, SubmitCommentEventArgs e)

{

var comment = pageRepository

.GetDefaultPageData<Comment>(

CurrentPage.PageLink);

}

Page 118: Progressive EPiServer Development

void SubmitComment(object sender, SubmitCommentEventArgs e)

{

var comment = pageRepository

.GetDefaultPageData<Comment>(

CurrentPage.PageLink);

comment.Author = e.AuthorName;

comment.Text = e.Text;

pageRepository.Publish(comment);

}

Page 119: Progressive EPiServer Development

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);

}

Page 120: Progressive EPiServer Development

[PresenterBinding(typeof(CommentsPresenter))]

public partial class Comments : EPiMvpUserControl<CommentsModel>,

ICommentsView

{

public event EventHandler<SubmitCommentEventArgs> SubmitComment;

}

Page 121: Progressive EPiServer Development

<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>

Page 122: Progressive EPiServer Development

protected void SubmitComment_Click(object sender, EventArgs e)

{

if (SubmitComment == null)

return;

SubmitComment(this,

new SubmitCommentEventArgs(

CommentAuthor.Text,

CommentText.Text));

}

Page 123: Progressive EPiServer Development

We’ve seen three frameworks that help us address some of the core problems we may face as progressive developers using EPiServer.

With these in our toolbox we’re better equipped to tackle complexity when needed.

Remember to choose the right tools for the job. Be pragmatic.

Page 124: Progressive EPiServer Development

Page Type Builder can be used in all situations and is likely to have the biggest impact on your overall experience using EPiServer.

Page 125: Progressive EPiServer Development

EPiAbstractions allow you to create unit tests and to use Test Driven Development.

But it can also be misused. EPiServer CMS and Web Forms wasn’t designed for you to have 100% code coverage.

Don’t even try.

Use it when drive good design of complex systems.

Page 126: Progressive EPiServer Development

Web Forms MVP and EPiMVP probably seems like it’s complex and hard to understand.

It is.

But it can be a powerful tool to use when you have complex user interactions.

It’s also great for being able to inject dependencies into code that you want to test.

Page 127: Progressive EPiServer Development

Page 128: Progressive EPiServer Development

http://pagetypebuilder.codeplex.com

http://epiabstractions.codeplex.com

https://github.com/joelabrahamsson/EPiServer-MVP

http://webformsmvp.com/

http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

Hand #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.php

Concrete plant: http://www.istockphoto.com/stock-photo-6416445-young-plant-taking-root-on-a-concrete-footpath.php

Potter: http://www.istockphoto.com/stock-photo-6791142-potter.php

Hands in chains: http://www.istockphoto.com/stock-photo-11441892-dependency.php

Enterprise D: http://www.flickr.com/photos/elzey/523568670/

Chain: http://www.istockphoto.com/stock-photo-2135970-breaking-free.php

Viewstate t-shirt: http://www.flickr.com/photos/piyo/3742166635/

Heart: http://www.flickr.com/photos/saraalfred/3199313309/