Kentico 12 - Design Patterns (17 Part Series)
Since November 2018, two worlds have been colliding to great effect — ASP.NET MVC and Kentico CMS.
Now, Kentico developers have even more ability to customize the way that content is delivered to site visitors, when stored and organized in Kentico CMS.
With this new room for customization comes an opportunity to identify helpful patterns and best practices.
Below I identify five tips and tricks you can use to help structure and organize your MVC code base when integrating with Kentico 12. 👍🏽
You’ve probably used the Visual Studio “ File -> New Project ” wizard many times when creating new applications, libraries, or test projects.
The code that the wizard produces is helpful because it lets you hit the ground running. Unfortunately, this code is only meant to be an introduction to how the chosen .NET project works.
Does anyone keep the
Class1.cs file that the wizard creates for you when adding a new class library project to your solution? I think not. 😒
Similarly, when we create new ASP.NET MVC5 projects the wizard gives us a predictable folder structure meant to guide us into the MVC paradigm, pointing out all the new significant features when compared to the previous ASP.NET solution — WebForms.
This means, based on the organization of classes, the initial focus is on the Framework (ASP.NET MVC) and not the Features of your application. 😑
Here is an example of the default files and folders created by Visual Studio’s “ File -> New Project ” dialog:
This is OK for a demo application but we aren’t building demos when we sell a Kentico CMS solution to a a client — we are building their site with their business rules and custom content. 💪🧠
To put it simply, don’t follow the pattern that the project template lays out for you because there is a better option — especially as your application grows in scope and complexity.
Instead, take a “Feature Folders” organizational approach which puts the features of your application front and center and treats the framework implementation details as exactly that — implementation details. 👍🏽
I’ve spoken about the “Framework Features Anti-Pattern” and I’m definitely not the first developer to speak up on this topic. Check out the slides from my talk for references or just look at the search results for “MVC5 Feature Folders”.
The below screenshot demonstrates the organization that I recommend for your new Kentico 12 MVC applications.
I believe in this pattern so much that I also recommend it for both general purpose class library and unit test projects. 👊
The above example isn’t fully “Feature Focused” because all Razor view files are still in the
\Views folder. I believe these should also live with the Feature types — ex)
Kentico12WebMVC\Features\Products\Detail.cshtml, but the Kentico MVC integration libraries do not support this approach, yet.
I’m hoping that the Kentico 12 MVC service pack includes support for MVC ViewEngine customization — specifically for the view path conventions so that we can be truly “Feature” focused in our projects.
You can vote for this feature on Kentico’s UserVoice site. Let’s let Kentico know we want this!
Once you start organizing your MVC project with the “Feature Folders” approach you might be wondering, “Where do I put my Kentico Widget, InlineEditor, and Section classes?” 🤔
The answer is simple. Treat them as a Feature!
As seen below, I create a
PageBuilders feature folder and place all my types there.
One issue that you can occasionally run into is a conflict between namespaces and types in C#. This issue might appear more often when using a “Feature Folder” based organizational approach for your types.
For example, if I have a namespace
App.Product and in that namespace I have a class
Product.cs then anywhere I try to use that type I will receive a compiler error. 😩
var product = new Product(); produces
'Product' is a namespace but is used like a type.
The easiest way to avoid this is to follow a simple convention:
As you can see in the above screen shots I’ve pluralized all folder names because folder names in a C# project generate namespaces for the types contained within them.
Sometimes you can end up with some slightly weird folder names (ex. putting your
HomeController in a folder named
Homes). If you don’t like that, try to think of a more descriptive folder/feature name, or make an exception to use a singular folder name knowing the caveat of the above issue.
I recommend using this “plural folders, singular types” convention even if you aren’t following a “Feature Folder” organization in your code base.
Ben Foster has written many great articles about ASP.NET MVC5 and if you are looking for some solutions to help your MVC codebase scale and leverage all the great functionality that MVC5 has to offer I’d recommend his blog as a great starting point.✨🌟
One pattern he recommends that I really like is distributed route configuration. I think it works amazingly well with a “Feature Folder” organization.
The most common routing configuration pattern you will online see involves registering all your routes in one file — usually
As with the “Framework Features” organization mentioned above, this works fine for demo applications but it doesn’t scale well.
Many developers will be editing the same file, which leads to potential merge conflicts. 😨
Also, in the above example, routing for a specific controller is not co-located with that controller class, leading to discoverability issues and mental context switching when working on a feature.
Ben’s recommendation is to create a common
IRouteRegistry interface and then define implementations for each of your controllers. In a “Feature Folder” organization these route registry classes live right next to your controllers in the feature folder.
The implementation for the
HomeRouteRegistry.cs, would be as follows:
You might be noticing the
routes.Map<T>();extension method on
RouteCollectionabove — that’s a custom extension to help me maintain strongly typed route configuration. Here’s a the implementation as a Gist.
You might also notice the
RoutePriorityReference.LOWESTabove. It’s not an int, and it’s not an
Enum— it’s a strongly typed Value Object, coded by our developer Sam, here at WiredViews. Leave a comment if you want to see the source for that as well.
Here is how we bring it all together with an IoC (Inversion of Control) container like Autofac:
I love this pattern and I find it leads to very composable features. I am even able to build
Controller based features in reusable libraries and have their route registration pulled in via assembly scanning.
It’s so nice! 🤗
My final tip is more about coding style than structure, but by following this pattern you’ll be led down the path of success in application structure and organization.
When building an MVC application we want to keep our controllers thin. 🤔
Controllers should act as collections of declarative configuration for endpoints, connecting HTTP requests (action methods) to HTTP responses (HTML via Razor views, JSON, Multipart file responses).
They should not contain business/domain logic and ideally they should not contain service layer composition.
Below is an example of a “thin” controller:
You can read a longer explanation of the benefits of thin controllers here, and there are many more blog posts about them if you do a little searching.
If we want to keep our controllers thin then where does all of our code go? 👋🏿
It’s going to go into new classes — probably lots of classes if we are following the Single Responsibility Principle.
And where do we put those new classes? 👋🏻
Well you could place them in class libraries or in your application project — it all depends on their purpose and re-usability.
If you are following the other tips and tricks above you should have some newly created Feature Folders and that’s exactly where I’d recommend adding these service and domain classes if you aren’t yet ready to create a separate class library.
One great tool for keeping your controllers thin is Mediatr — a message dispatcher that works great in any application.
You can read more about how Mediatr can be used in an MVC application and the benefits it brings. 👍🏿
I hope you found these tips and tricks helpful or at least thought provoking.🤯
Go back to your Kentico 12 MVC code bases, try them out, and see if the ergonomics work as well for you as they have for me.
If they work well or don’t seem to fit your requirements I’d love to get some feedback, so leave a comment below and let’s start a discussion. 📞
Do you have any tips or tricks you recommend for your fellow Kentico developers? Let us know and thanks for reading! 🙏🏻
I plan on sharing more “Tips and Tricks” in my Kentico 12: Design Patterns series so follow me here on Medium or Twitterto catch all my posts.
My next post is going to cover setting up Kentico Automated Integration Tests for CI/CD in Azure Pipelines!
Have you written unit tests for your Kentico projects before? If not, want to know how? Check out my previous post in this series below.