We’ve made it to Part 4 in my Creating a new MVC site with Kentico 12 series. We started with the world’s most boring site and added some basic content. Next, we added a snazzy design and the new Page Builder functionality, and then some custom widgets and editors. In this final article, I’m going to take our architecture to the next level. So, let’s get started!
TL;DR Version
-
Dependency Injection simplifies your application and is at the core of MVC development.
-
All DI libraries do essentially the same thing. Choose one that fits your skills/architecture best.
-
ViewModels simplify your models and controllers. You can manipulate your data in your ViewModels, leaving your generated Kentico models intact.
-
Repositories allow you to quickly inject test data into your applications. This can simplify your CI/CD process.
-
You can use the Kentico Caching API within your MVC apps to improve performance and reduce database calls.
- With Kentico 12 MVC, you have tremendous possibilities and functionality available. We will continue to add to the MVC architecture in future releases.
Up until now, my MVC site has been pretty basic, Sure, I added some fancy new widgets and editors, but I wasn’t really architecting the site using best practices. That’s all going to change in this final blog in the series! Get ready for some exciting times, covering Dependency Injection, view models, repositories, and even caching. Oh yes, this is truly a magical time to be alive!
A Very Brief Overview of Dependency Injection
Before we get into the fun code part, let’s take a moment to talk about Dependency Injection. This MVC-centric pattern allows for decoupling of code so dependent objects can be changed without updating the main code. This means your client code does not need to know about the injecting code or its dependencies. You can focus on what you need, leaving the heavy lifting to the DI. Your DI library will handle the registration and passing of any dependent objects/classes. This helps keep controllers simple, as all data retrieval is done in the service.
There are tons of articles on DI, so I added some links here in case you want to read up.
- https://en.wikipedia.org/wiki/Dependency_injection
- https://www.martinfowler.com/articles/injection.html
- https://docs.kentico.com/k12/developing-websites/initializing-kentico-services-with-dependency-injection
Choosing a Dependency Injection Library
So, we know we want DI, but how? Luckily, there are only about 1,000 different DI libraries to choose from. To make it easier for you, know that most of them are essentially doing the same thing in the end. As long as you are using something for DI, you’ll be in good shape. Sure, some have different levels of difficulty to implement and unique capabilities and quirks. Be sure to review a few and choose one that matches your skillset and architecture.
Our sample Dancing Goat uses Autofac, which is totally fine. Other popular libraries are LightInject, Ninject, Castle Windsor, and many others. For my site, I’m going with LightInject.
You can learn about different DI libraries here:
https://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
https://www.lightinject.net/mvc/
TIP
Adding in a third-party DI library is only needed for .NET Framework applications. With .NET Core, this is baked right into the platform! For EMS, we are continuing to work on our .NET Core support, but for now, you'll need to add a separate DI component to use in your Kentico 12 sites.
Adding View Models
Before I get to the DI fun, I want to add some view models to my site. Up until now, I’ve been using simple models to pass to my view. While this is fine to do, it’s not very flexible. By implementing view models, you can easily manipulate your data structures without affecting your generated Kentico models. This keeps your models and controllers clean and separated, which is what MVC is all about.
Here’s a view model I created for my Home page. In my case, my view model is pretty basic and just contains the root PageInfo object. I could easily add in more properties to handle a collection of articles and other properties. By using a view model, I can also change the HomePage class, as needed, without changing the generated code.
public
class
HomePageViewModel
{
public
HomePage PageInfo {
get
;
set
; }
}
Adding Dependency Injection
OK, with my view models in place, I’m ready to add some DI action. Every library will be a little different, however, they almost all follow the same pattern for use:
- Add NuGet packages
- Register your controllers/services/repositories with the library
- Pass the dependencies into your controllers, repositories, etc.
- Profit!
With LightInject, here are the steps in action:
First, I add the LightInject NuGet packages. Note that I chose the LightInject.Mvc.Source because this is an MVC site.
When you add these packages, Visual Studio will create some default files to include in your project. These files add all the LightInject goodness to your solution.
Next, you will want to create a file for your registrations. You could put these in the Global.asax file, but to keep things clean, I create a new file and just call the register method from my Applicaiton_Start method.
Here’s my configuration file.
namespace
KenticoMVCWidgetShowcase.App_Start
{
public
static
class
LightInjectConfig
{
public
static
void
Register()
{
// Register LightBox service container
var container =
new
ServiceContainer();
container.EnableMvc();
// Register the controllers
container.RegisterControllers(Assembly.GetExecutingAssembly());
// Register the services
container.Register<HomePageService>();
container.Register<MVCWidgetsPageService>();
container.Register<MVCWidgetService>();
container.Register<IMVCWidgetRepository, MVCWidgetRepository>();
}
}
}
And here’s the Global.asax file.
public
class
KenticoMVCWidgetShowcaseApplication : System.Web.HttpApplication
{
protected
void
Application_Start()
{
// Register LightInject configuration
LightInjectConfig.Register();
// Enables and configures selected Kentico ASP.NET MVC integration features
ApplicationConfig.RegisterFeatures(ApplicationBuilder.Current);
// Register the script bundles
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Registers routes including system routes for enabled features
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
Next, I create some services to use with LIghtInject, [SPOLER ALERT] My configuration file already has these defined.
I create a HomePageService class to retrieve my data.
public
class
HomePageService
{
public
HomePageViewModel GetHomePage()
{
HomePageViewModel vm =
new
HomePageViewModel();
string
culture =
"en-us"
;
string
siteName = SiteContext.CurrentSiteName;
vm.PageInfo = HomePageProvider.GetHomePage(
"/home"
, culture, SiteContext.CurrentSiteName););
return
vm;
}
}
I then create an interface for the class.
public
class
IHomePageService
{
}
My MVCWidgetService file follows the same pattern, however, it has more functions. This is because this service will return a collection of widgets as well as a single widget item.
public
class
MVCWidgetService
{
public
static
MVCWidget GetMVCWidget(Guid guid)
{
string
culture =
"en-us"
;
string
siteName = SiteContext.CurrentSiteName;
var widget = MVCWidgetProvider.GetMVCWidget(guid, culture, siteName)
.TopN(1)
.FirstOrDefault();
return
widget;
}
public
static
IEnumerable<MVCWidget> GetMVCWidgets(
int
SelectTopN)
{
string
culture =
"en-us"
;
string
siteName = SiteContext.CurrentSiteName;
var widgets = MVCWidgetProvider.GetMVCWidgets()
.OnSite(SiteContext.CurrentSiteName)
.Culture(
"en-us"
)
.TopN(SelectTopN)
.OrderByDescending(
"DocumentPublishFrom"
)
.TypedResult;
// Ensures that the result of the query is saved, not the query itself
return
widgets;
}
}
After creating the services, I’m ready to pass them to my controllers. Here’s an example of HomeController. Note how the service is passed into the controller and assigned to the local variable. This is then used to retrieve the data.
public
class
HomeController : Controller
{
private
HomePageService _service;
public
HomeController(HomePageService service)
{
this
._service = service;
}
// GET: Home
public
ActionResult Index()
{
HomePageViewModel vm = _service.GetHomePage();
// Returns a 404 error when the retrieving is unsuccessful
if
(vm.PageInfo ==
null
)
{
return
HttpNotFound();
}
// Initializes the page builder with the DocumentID of the page
HttpContext.Kentico().PageBuilder().Initialize(vm.PageInfo.DocumentID);
return
View(vm);
}
}
Caching
With the services in place, I want to add some caching to improve performance. By leveraging Kentico’s built-in caching API, we can reduce our DB calls and improve how the site responds.
Here’s an updated HomePageService using the caching API to retrieve and store the data.
public
class
HomePageService
{
public
HomePageViewModel GetHomePage()
{
HomePageViewModel vm =
new
HomePageViewModel();
string
culture =
"en-us"
;
string
siteName = SiteContext.CurrentSiteName;
Func<HomePage> dataLoadMethod = () => HomePageProvider.GetHomePage(
"/home"
, culture, SiteContext.CurrentSiteName);
var cacheSettings =
new
CacheSettings(10, siteName +
"|data|homepage"
, siteName, culture)
{
GetCacheDependency = () =>
{
// Creates caches dependencies. This example makes the cache clear data when any article is modified, deleted, or created in Kentico.
string
dependencyCacheKey = String.Format(
"nodes|{0}|{1}|all"
, siteName, HomePage.CLASS_NAME.ToLowerInvariant());
return
CacheHelper.GetCacheDependency(dependencyCacheKey);
}
};
vm.PageInfo = CacheHelper.Cache(dataLoadMethod, cacheSettings);
return
vm;
}
}
NOTE
Caching the data using the Kentico API only stores the data in the MVC site’s app pool. This means it’s not visible within the Kentico Admin System module.
Repositories
The last update to the site is to add repositories. Repositories allow you to separate your data layer even further but also offer a way to load whatever data you want into your models. While this is typically from the Kentico DB, you can also use test data within your application to simulate the content.
You can learn more about using repositories in your Kentico sites here:
Here’s my MVCWidgetRepository file. Note that I am calling my MVCWidgetService functions inside the repository to leverage that code.
public
sealed
class
MVCWidgetRepository : IMVCWidgetRepository
{
public
MVCWidget GetMVCWidget(Guid guid)
{
return
MVCWidgetService.GetMVCWidget(guid);
}
public
IEnumerable<MVCWidget> GetMVCWidgets(
int
intSelectTopN = 0)
{
// Get the collection of MVC Widgets
return
MVCWidgetService.GetMVCWidgets(intSelectTopN);
}
}
Also, don’t forget to register your repos in your LightInject file!
// Register the repositories
container.Register<IMVCWidgetRepository, MVCWidgetRepository>();
Testing
Wow! That was a lot of code! And the funny thing, there isn't any visual change to the site. We’ve just made our MVC better architected and improved its performance. But just in case, here’s a pic of the Home page using the new code. It looks identical to the previous version but is now leveraging DI, caching, and repositories to load and display the data much more efficiently.
The Future
OK, I warned you this blog would be long. The thing is, with MVC, there’s so many possibilities. It’s very easy to add all sorts of awesome capabilities to your site. It just depends on how much you want to do and what works best for your functionality. Whether it’s a vanilla MVC site, or one with translations, search, and EMS features, building Kentico sites with MVC is the future.
I hope this blog series brought you a little closer to starting your next MVC project. With Kentico 12, you can build amazingly fast and dynamic applications, all while leveraging a ton of great functionality. Good luck friends!