In this tutorial, you will learn some handy tips on how to structure your Episerver powered CMS projects. I strongly recommend that at the beginning of every project you sit down and make a conscious decision on how you will structure your class libraries, folders and files. If you use the Alloy sample site as your websites base project, all the code is provided to you in the web solution. For smaller brochureware websites that will never grow too large, there is nothing really wrong with this approach. For large projects, there are a few downsides of simply adding all the code into the main website project 😔
My first tip is to resist the temptation to simply add all of your custom code files within the main web project. My justification for this tip is that you will not get a lot of re-use out of your code. If you build a lot of Episerver powered websites, being able to re-use your helper and utility methods within these different projects can be a massive time-saver. If you add all your code within the main web project, you will not get this benefit. This means that on the next project you will waste more time doing those boring set-up chores. Separating your code into one or more separate class libraries means it is easier to drop your libraries into future projects.
The second pain point that you will encounter with a single web project pattern, will be around your unit tests. It is standard development practice to create one unit test project per class library. I have found that when you mangle all your code into a single file project, you also need to mangle all your unit tests into a single library. When you mangle tests together, it is impossible to simply copy code and the unit tests into another project easily. It usually takes longer to unpick the tests compared to starting from scratch. So if we are not going to stick everything into the web project, what are we going to do?
In my projects, I tend to create a class library called Project.Core
. In this library, I add all my custom code, except any .NET framework start-up code. The reason why I leave things like startup.cs
, or route.config
within the main website project are future upgrades. For example, in the future, if you wanted to upgrade from .NET Framework to .NET Core, having all the MVC related files separated from your custom code will make life easier. I have proved this pattern is useful whenever I have needed to do a Sitecore upgrade 😔 . You can simply move the class library and its corresponding unit test library without having to waste time picking out code. My general advice is to put all your domain and model logic within this Core
library.
Folder Structure
The next question is what folders should you create within this Core
library? The types of folders that I would expect to see in this library would include
Enums`
Blocks
Pages
Helpers
Validators
Extensions
Repositories
View models
Config
Attributes
These folders will cover the majority of your custom code needs. You will need to decide where you want to put any initialization modules, structure map dependencies, and your custom route definitions. Should they live in the web project or your Core
library? I have used both approaches during my career, both work OK. I have never needed to copy any of these project-specific things in between projects, so do what makes you happy. If you are on the fence, leave them in the web project as this way slightly reduces the chances of you bumping into unit test project copying pains 😊
The Domain/Model/Shared Interface Approach
Having a single class library is great, however, for more complex sites you may want a more fine-grained abstraction. In complex projects, I tend to structure my code into three main class libraries, Core
/Models
, Global
and Interfaces
. First, we have the Models
project. In this project, I store things like, Pages
, Blocks
, Pocos
, View Models
etc... In the domain library, I add the non-project specific code like, Attributes
, Dependency Resolvers
, Extensions
, Guards
, Helpers
, and Renderings
. The Global
project is where I add the non-project specific code, e.g. the bits I will copy in between different projects. Lastly, we have a third class library, the Interface library
. This library is needed to enforce a good separation of concerns within the project. Any Interface
used in the project is added here. The interface project is the contract on how the libraries can talk to each other. This approach might sound odd at first, however, it has some useful benefits. First, on most projects, I work with junior developers who are new to dependency injection. Having a rule that forces people to add interfaces in a class library, ensures they need to use dependency injection correctly within the solution 😊
I find that having a separate Interfaces
library makes code reviews much easier. I have a single rule to check and feedback on. Next, having a contract specific library can help eliminate any project circular reference issues. If you bump into some strange cyclic dependency issue later down the tracks, this will avoid you needing to refactor lots of code. Trust me, I've been there and got the T-Shirt 😔
In case you've never come across the cyclic dependency problem, let me explain. This issue occurs when one project relies on code found in another project in order to build, however, that project has a reference to the first project. When Visual Studio compiles, the compiler would get in an infinite loop. By moving all interfaces
into a shared library it breaks the cyclic chain 🔥🔥🔥
A valid objection to this pattern is complexity. Some developers believe that splitting up your project into additional libraries only creates extra overhead. My counter-arguments are time and effort. If you have more than one website being able to copy code quickly saves time. If you convert your Global
library into an internal Nuget package, you can also fix code once and get the benefits in all your projects. This can save you a lot of time in your future projects, as you don't have to copy and re-fix the same bugs over and over. The second benefit of having three libraries is that it forces the developer to write more flexible code. Using an Interface
project makes the code more flexible. It means you can use SOLID principles anywhere in your codebase. You are writing all your code so you can use the Open/Closed principle later if you need to. In this approach Models
references Interface
and Global
. Interface
references Models
and Global
. Global
only references the packages it needs.
Some food for thought if you want to use this approach, sometimes you may have to slightly compromise a bit on where you want to put code. You may end up with a file that you ideally would like to put in Global
, however, you get forced into adding it within Models
instead (due to references). One example are custom Episerver selection factories. You might want to make these global, however, they end up being project scoped as they usually need to reference a page or block type.
My personal preference for larger projects is to use this latter approach. I have used the Core
library pattern a lot over the years, successfully. If you have more than one website to build, the Models
, Global
, Interface
approach will result in more flexibility, as code can be more easily shared between projects and fewer unit tests need to be refactored to allow for this. The downside is that you may have to compromise on the placement of a few files.
What are your thoughts on this? How do you structure your project and which way do you prefer to implement the structure of your project? Happy Coding 🤘