Since January this year, I have been focusing on Sitecore 6.6/7 MVC site development and I have to be honest, it rocks! As I plan to discuss in this post, the unique nature of the MVC (Model View Controller) design pattern as implemented by Sitecore allows for greater separation of concerns which ultimately leads to maximum reuse of code bases, allowing a client to easily create new sites without having to start from square one every time. In this post I am only going to explain the concept; however if enough people are interested, I would be more then happy to provide some code to demonstrate implementation.
One of the largest problems that I have seen in the years that I have been creating Sitecore solutions for a wide array of clients, is that it seems that every time the client wants to take advantage of Sitecore’s ability to host multiple sites, everyone starts from scratch almost. This includes creating a new Visual Studio solution, writing new code and creating new templates. After my first Sitecore MVC implementation I thought, Why? This is especially perplexing when considering what MVC, Razor and even Bootstap bring to the table. So I set out to create my own process for implementing solutions for clients that can accommodate for this. There were several issues that I ran into and I am sure that other developer teams have as well:
- How to structure the code base to be more dynamic instead of static
- Taking into account different forms of content
- Considering all of the different templates that are and will be needed
- Ultimately, how to separate Sitecore content from presentation, as these are often tied tightly together
So in this post I am going to highlight some of the basics and if enough people are interested, I will write a post with sample code.
First: How to structure the code base to be more dynamic instead of static? In web forms, this is not very easy to accomplish; however, with MVC and DI (Dependency Injection) it is. This can be accomplished fairly easily by implementing the Dependency Injection and Ninject (I have a four part post on this, with code). One problem that I have run into is that the Controller, when instantiated, will look for the View according to Microsoft’s implementation of the MVC pattern; i.e., the view is called Content and the Controller is called Home, it is going to look in the /Views/Home/Content.cshtml folder or the shared folder. This causes a problem, as every Controller Rendering created will require both a Controller, and Action and a View. So for example, if I have two sites and they both have different html I would need to create another Action (at the very least) and another View and since they would be in the same folder, as more sites are added, it would be very easy to overwrite another site’s html and/or code. Another issue with this is that often in a Sitecore Web Forms project, the developer would create a folder in the site, like /Presentation, and then create a folder for each site to contain site specific content to ensure nothing gets overwritten. There are of course other issues standing in the way, but now we will look at one way that I have found to resolve this and other issues.
First thing that I do is go into the template for the controller rendering in sitecore and I add a field called View Path. I typically create a repository that stores site settings and other content coming from Sitecore, like this value, but more on the usefulness of this in a moment. Now, the value stored here will be available through the Services layer to the Controller on request. The Controller and its Action are coded only to get the data and then pass it to the View for rendering, so after getting the needed data and inserting it into the View Model, the Action will do one last thing. It takes the value for View Path (ex. ~/Views/UniqueSiteFolderName/Content/YourViewHere.cshtml) and returns it using MVC’s PartialView method, which allows a developer to manually set the location of the View. This takes care of multiple sites using a singular code base and it also does something else. It helps make it possible to reuse that Controller for any site, since it doesn’t care about what the content is or where it comes from. So, in practice, it is possible to create many different renderings for many different sites and there will be no need to change the code base, traditionally something that takes a long time for an enterprise level company to put through the process. Strategic creation of Controllers and Actions can limit the amount of total Controllers and Actions needed and increase their reusability across as many different Views as needed. Another thing that helps, is developing View Models that contain other objects that will be exposed through the Services layer from Sitecore using DI, thereby turning the Controller into more of an API extension for front-end developers that create the html and use razor; again no need to rebuild or otherwise change code, only create a simple cshtml file and use Razor.
Second: Considering all of the different templates that are and will be needed. This presents another problem, how do we create templates that are reusable across many different sites that will not require new code to be written? Over the years I have gotten a pretty good idea of most of the common fields that content authors and clients like to have available for their use. The approach that I use is I create a generic set of very small pieces of templates; these become my base. I then ensure that the Repositories that I use only take into consideration these base templates; they don’t need to know about the templates that the content authors use. Then, for each site created, I create their templates (with no fields) and inherit from my base collection only. This accomplishes several things: adds a layer of separation between the content author and the developer and preserves the code base. So now their site has templates that are created for them, but the Repositories do not need to be changed, as they talk to the base templates. What though, happens when one site of twenty wants a field, that they do not need, removed that other sites use? Just go into the base collection of templates and create a new template that has the same fields as the other base minus the undesired field. Then on their template, change the association, done! If there are other fields that need to be created, just create them on the base and then inherit back to the client’s site-specific template. At this point some additional code will be needed, but instead of writing thousands of lines of code needed for each individual site, it now becomes a matter of adding a few lines to a repository method and adding that to the interface. Then it is just a matter of the front-end developer adding the newly added property to the clients View. So, although there will always need to be new code (job security J ) , we have drastically reduced the amount needed for an addition.
Third: How to separate Sitecore content from presentation on the Page level? This is one that I have struggled with a little in the past, but eventually came up with a solution. The big part of this requires use of Bootstrap, which is a CSS/Html framework that creates a basic grid on the presentation side of things, allowing front-end developers to rapidly create html that often takes advantage of Responsive design. One method that I am often employ is taking more of a rendering based page model, and to clarify, friends don’t let friends implement Sitecore with XSLT, I am referring to Controller Renderings. By encapsulating the small piece of a page as a widget or control, we can then set a datasource to where the content lives. This also makes using Page Editor a lot easier. So now the layout and any sub-layouts should contain placeholders that represent the basic layout of the page, i.e., left rail with wide right area, a wide center and so on. Now the content author can place renderings on the page and point the datasource back to the content contained within the sites content folder. Now you may think that there are a few issues with this like
- With content decoupled from the page item in the content tree, how can a more admin user make content edits from within the Content Editor
- If the content author is on the page in Page Editor, what happens if they want to create new content or even select other content?
For the first question the resolution that I use involves only a couple of steps in the development project. First off, I got some code from another Sitecore blog, (Thanks John!) and implemented it. Basically it will maintain the datasource link in the Link Database (not really a database, it’s a table in a database), so now all linked datasources on a page are available when clicking the link button in the ribbon. Unfortunately everything else is available as well, but that is the second step. I found a way to extend that control and wrote code that gets only relevant content links for that specific site based on settings that can be changed in Sitecore. Now, after creating the new link button right next to the original, the content author clicks it and a list of only content on the page is shown. Simply click one and like magic it appears in the right pane! The second issue is the page editor. The cool thing about Sitecore is that I can completely change whatever I want, so page editor can be configured to do what I want it to. In the development process, I will create various buttons for the ribbon and on the context ribbon (on the actual control) that allow the content author to do what ever they need to do; add content, delete content, pop up a search window (Sitecore 7 only) and so on. I have also found it helpful to add a button on the control that will break it so the author can edit/reach all aspects of the content (like in a carousel) which is only available when in edit mode.
So in this post I have highlighted several methodologies that I like to use to make Sitecore as usable for the client as possible. By starting from small simple pieces and then bringing them together to form more complex obejcts and then eventually a reusable solution is usually the best way to go, after all that’s how the universe is built! Remember, keep calm and code on!