A deep dive into Kentico 12 MVC Widgets with Vue.js
Kentico CMS 12 MVC?
Kentico CMS 12 was released in November 2018, bringing along with it a shift in technologies for Kentico developers.
While Kentico has supported ASP.NET MVC for several versions in various shapes and forms, only now in version 12 is it both fully supported and Kentico’s recommended technology choice for building out the delivery side of the content management system. 🤗
This separation of content delivery from content management allows developers to use the modern architectural best practices (SOLID) without having to maneuver around the large infrastructure of the CMS (with some caveats…). 😎
The differences between the traditional Web Forms based Portal Engine development model and the MVC development model can be found on Kentico’s FAQ page.
MVC Widgets are Exciting!
One of the new and exciting features in Kentico CMS 12 MVC (which I will call Kentico MVC from here on) is custom widget development. This feature allows developers to build custom components that content editors can interact with when building out sections in an MVC page within the CMS. 🤩
This means that content editors have the flexibility to customize their content while developers can constrain what content is available to be placed in different areas of the page layout. It’s the best of both worlds!👬
This development side of the MVC Widget component pattern involves two parts.
In the first part, developers create MVC controllers and partial views for rendering the persisted widget content. The widget content is rendered in one of two states: “live” or “editing.”
The live state of a widget is shown when the viewer is viewing the live site (the url in the browser would be the MVC url).
The editing state is shown when the viewer is inside the CMS Pages module and viewing a page on the MVC that has widgets configured within areas of the page (the url in the browser would be the CMS url).
These areas of the page where widgets can appear are called Sections and while we won’t be covering them here you can read more on how to set them up in the Kentico documentation.
The inline editors allow for a rich user experience (UX) for content managers and, as we will see, an exciting place for web developers to bring in their skills and favorite front-end web technologies for building these complex user interfaces. 😀
More about how widgets, inline editors and the overall ‘Page Builder’ technologies work can be found on Kentico’s documentation site.
Kentico’s Dancing Goat site, which can be installed via the Kentico Installation Manager, demos several pre-built widgets and inline editors.
While reading the documentation on widgets I would recommend exploring this codebase to get a better understanding of how all the pieces fit together and what the technology enables.
How can we use MVC Widgets?
The pre-built widgets included in the Dancing Goat site I found to be a great introduction to MVC Widgets and helpful in growing my understanding of how they work, but the use-cases are understandably limited in functionality. 🧐
One feature that I knew would be needed by my company, WiredViews, when we develop Kentico MVC sites for clients, is a way to select media from the CMS media libraries. This is something I tried developing as a custom inline user control in previous versions of the CMS. Unfortunately, I hit too many roadblocks to make it worth investing the time. 😒
Now, let’s build the Media Selection Widget and a reusable Media Selection Inline Editor!! 👨🔧
Let’s start with MVC
Note: You might notice my namespaces don’t match the typical App.Controllers / App.Models folder structure. This is because I’m a proponent of the Feature Folders pattern for any application and avoid building out my app along the lines of Framework Folders where possible. I always keep my Controllers and Models together, and in non-Kentico MVC projects I keep my views with them as well.
I gave a talk on this topic and the slide deck is available online.
Widget Properties, ViewModels, and Controllers
First, we create the widget properties class named
MediaSelectionWidgetProperties which defines that widget data persisted to the database. A single widget can persist multiple values customized by content editors and will leverage separate inline editors to edit each one.
Here we see an
[EditingComponent]attribute which lets us use a pre-defined Kentico forms Input Component (
IntInputComponent) that will show up in a modal when a gear icon on the widget header bar is clicked.
Sometimes not having to build your own custom UI is nice! 😅
You can read more about how to use these already existing components in the Kentico documentation.
We also create the widget view model class named
MediaSelectionWidgetViewModel which defines the shape of the data we are sending to our widget partial view. This is typically a combination of our widget properties class and additional values or settings from the application that we have access to in our widget controller.
We then need to create a widget controller, here named
MediaSelectionWidgetController, and register it with Kentico via the
We also use this class to pull the widget properties from the database (with Kentico’s help!) and populate the widget view model with any data we’d like to have available in our partial view.
I’ve added comments in places that deviate from Kentico’s patterns in their documentation, specifically creating the
MediaSelectionControllerMetaclass and the
Partial Views and Inline Editors
Now we need to create the partial view referenced by the path
Widgets/_MediaSelectionWidget and the inline editor view model
MediaSelectionEditorViewModel that we pass data to when in edit mode.
You can see here that if
Context.Kentico().PageBuilder().EditMode evaluates to true we show our
MediaSelectionEditor inline editor, otherwise we show the image associated with our widget instance by using Kentico’s
ImageUrl() helper method.
Thus in the CMS, when viewing a page that this widget is associated with, we will see an editor but on the live site we render out an image as we would expect.
We cannot (reliably anyway) edit multiple widget properties with the same inline editor. This is why you see the assignment of the widget property name to the
PropertyName property of the inline editor view model. For every widget property we want to edit with an inline editor we will need to ensure this
PropertyName property has the correct value.
Here you can start to see how widgets and inline editors come together. If we had multiple widget properties we wanted to edit with inline editors we could have multiple instances of those editors in our widget partial view. Each inline editor instance would then be assigned a different
Finally, we will create the partial view of our inline editor referenced by the path
There are some things to note as we are finally getting into the Vue.js layer of the widget.
First, our Vue app will need a unique HTML element selector to hook into to render itself, so we generate a unique id string to set as our
<div id="@id"> value. The helper method that does this is pretty simple and uses the .NET
Path.GetRandomFileName() method to get a random string of valid characters.
We could have multiple widgets each with their own instance of this inline editor on the screen at one time or even multiple instances of the same Inline Editor in the same Widget, so one unique
idisn’t enough — we need a unique value for every instance.
Html.Kentico().BeginInlineEditor() . These will become
Fortunately once we get the Vue.js layer of the app we can use even better patterns for state management and information passing.
Finally, you can see that we use a custom HTML element
<media-selection-editor> with several attributes prefixed with a colon. Vue.js will take values we register with the Vue app and bind them to these attributes which makes them available to this custom component. There is also a custom event that the
<media-selection-editor> component emits, which is defined by
v-on:dispatch which we handle in our root Vue.js app with the callback
You can also see that the randomly generated
@id is being used as the value for the
id of our Vue.js app hook point.
const, destructuring, and DOM APIs like
And of course comprehending the Vue.js code will require checking out the docs if you are unfamiliar with the library.
The string passed to
kentico.pageBuilder.registerInlineEditor() needs to match the string passed to
Kentico provides some lifecycle hooks for the inline editors to help manage state and initialization. The most important hook is
editor passed to our
init lifecycle event callback by way of the
options parameter is actually the HTML element created by Kentico with the Razor call to
Html.Kentico().BeginInlineEditor(). The anonymous object, with the snake_case properties above, was mapped to this HTML element’s attributes. Therefore we have access to all of those values via the
data- attributes. 👨💻
Our Vue Application
You can also see that we use the
data-id value as the value for our
Vue.el property. This means this Vue app instance will initialize itself on the HTML element with that (randomly generated) id.
The HTML within that DOM node will be compiled at runtime by Vue.js and any components or attribute bindings will be evaluated and executed. ☝️
We bind the rest of our
data- attribute values to data properties of the Vue instance so they can be used within the instance or bound to the attributes of our
We also define the
onDispatch method that receives data from the
<media-selection-editor> when an image is selected.
widgetEvent is passed to Kentico’s
dispatchEvent method found on the
editor object and we update the value of the
selectedMediaUrl to be the url of the selected image. The value of
selectedMediaUrl will be re-bound to the
mediaSelectionUrl attribute of the
<media-selection-editor> component, and the component will update its template to reflect the change. 💪
When we call
editor.dispatchEventwe are telling Kentico to update the values associated with the specific property of the current widget that this inline editor is editing — in our case it’s the
You’ll notice that I also use the editor’s
destroy lifecycle event callback as an opportunity to destroy our Vue app 💀 to prevent memory leaks. Thanks Kentico! 😉
Our Vue Component
Let’s now take a look at the
<media-selection-editor> component that does all the work. (Below is a snippet, the full file is available here)
This is a Single File Component (SFC) and is the preferred way to encapsulate style, layout, and functionality when working with Vue.js.
A lot of this is normal Vue.js code and is independent of Kentico — but there are a few pieces worth pointing out.
A riddle, wrapped in a mystery, inside an enigma
When the component loads via the
created lifecycle event, we make an XHR request using
fetch . This request is getting the media we want to display so the content editor can select one. But this request is special —it’s not calling the MVC app, it’s authenticated and it’s calling the CMS! 😮
Let’s refresh our minds what’s going on right now in the application. 🤔
We are in the CMS in the Pages module, rendering a specific page on the MVC site in an
<iframe>. The MVC site “knows” we are in edit mode because of some url generation trickery 🧙♂️ performed by Kentico to load the MVC site page. The widget on the page is rendered in edit mode and the inline editor is displayed. 🤯
It’s safe to assume our content editors are going to be authenticated in the CMS, but they are unlikely to be authenticated in the MVC site (remember these are running under different domains and have their own authentication cookies). 😕
We don’t have access to Kentico’s magic url generation, so we can’t call an endpoint in the MVC app to get content that users not authenticated in the MVC site (currently, our content editors) shouldn’t have access to. 😫
We could require content editors to log into the MVC site before working with widgets, but then you have to deal with the issues of two authentication schemes with potentially different cookie lifetimes and an additional workflow step for content editors that just want to select some cool images to update the site! No thanks! 🤬
Our content editors however definitely have an authentication cookie for the CMS. So let’s make the XHR request to an endpoint there! 👍
Unfortunately, browsers try to be secure these days (darn! 😣) so we have to be explicit about sending our authentication cookie with requests from one domain (MVC) to another (CMS). We can do this with a browser technology called Cross-Origin Resource Sharing (CORS).
Setting up CORS
We have to tell the
fetch request call that we want credentials (cookies) included with the
credentials: "include" property/value.
On the CMS side we have to configure the app to send CORS headers in response to a non-standard XHR request. There are multiple ways to accomplish this and I’ll leave it up to the reader to choose their preferred option. 🤠
Adding custom headers elements in the
web.configis the most simplistic approach but only allows for a single domain — this won’t work for multi-site Kentico installs. Using the
BeginRequestevent handler in an
IHttpModuleis more flexible (see a prepared example of the latter here). 🕵️♂️
Once CORS is enabled we need an endpoint in the CMS accept our requests. I’ve blogged previously about setting up Web Api 2 inside Kentico, which goes quite a bit beyond the Kentico documentation. This is helpful when you need many APIs to access CMS data, but here we only need one endpoint to get access to media library files.
I created an
CMS\CMSPages\MediaLibraryApi.ashx. Since this is a simple handler, I’m responsible for url parsing and response formatting (Web Api 2 would normally handle this for me).
I’m also responsible for requiring the request to be authenticated with a call to
Since we included the authentication cookie in our XHR request, and enabled CORS to allow these types of requests, Kentico was able to set the authentication context successfully and has identified me as the whichever account I’m logged into the CMS with currently. 🤴
We are almost at the finish line!🏃♂️
Client Asset Compilation with Webpack
Finally, we need a way to turn our
Don’t worry — it’s not that scary. 👻
Below is the
package.json used to define those client assets I need to build my Vue.js code.
Here is the
webpack.config.js that defines the compilation process for the Vue components.
I put both of the above files at the root of my MVC project folder and I run any
npm commands from that same location.
I use the
.vue files for each of my inline editors, I know I will have all my components in the final output bundle by starting with this file.
I then point my output bundle to
.\Content\InlineEditors\inline-editors-bundle.dist.js which is a location that will be automatically pulled into Kentico’s Page Builder bundles. These are loaded anytime you call
@Html.Kentico().PageBuilderScripts() in a Razor page view.
One thing to note is the
externalssection in my
webpack.config.js. Here I tell Webpack not to bring the Vue.js source into my bundle as a dependency. Webpack will still use it for the compilation process.
This allows me to ensure
window.Vueexists before Kentico loads my
media-selection-editor.jseditor definition file by placing a
<script src="...-vue.js"></script>tag before any other scripts in my MVC app
_Layout.cshtml. If Kentico’s bundling process results in the
media-selection-editor.jsfile to be loaded before Vue.js, it would throw an error that it didn’t know what the
Kentico doesn’t support client dependency management so I’m managing it myself. Take a look at how I’m loading different versions of the Vue.js library in Razor conditionally here.
Let’s run this thing already!
npm run build(you can run
npm start for continuous development), start the MVC, and CMS sites, and then view a page in the CMS where you have Widget Sections defined. (Also be sure to add some media to your media library.)
MVC Widgets in the CMS
When you add the Media Selection Widget to a Widget Section on the page you will see that no media item has been selected and a button to show the library.
If you then click the “Show Library” button you will see the default search results and the search form.
You can change the search parameters and submit the form to see more results. This all happens via XHR so the page won’t refresh. It’s also much faster than the Kentico CMS Media Library UI (there’s much less going on here in our simple use case — we only have to support what we want to support 👍).
If you select an item by clicking on it, the “No media selected” text will change to the selected image.
If you click any of the images in the gallery on the right you can see a larger version of that image in a modal.
The widget can also be customized by clicking the gear icon on the right side of the blue widget bar.
This is where our
MaxSideSize widget property shows up that we defined to use the pre-built Kentico Editing Component.
Now if you save the page, the selected image
Guid will be persisted to the database as the Widget’s properties metadata. When the page reloads, the
MediaLibraryApi.ashx endpoint will be re-queried and the previously selected media will be displayed.
MVC Widgets on the live site
Now go to the MVC site and load the page where you added the widget and you should see the selected media item!
Awesome sauce! 🙌 🤘 👊
In Conclusion! (What year is it?)
It’s been a long journey, but I did title this a ‘deep dive’!
So, congrats — you are now a little more knowledgeable 👩🎓 and ready to concoct your own MVC Widget recipes 👩🍳.
I plan on continuing to work on this Media Selection Widget / Inline Editor and I will make the code available on Github. When it’s available I will post a link here.
Do you have any cool Kentico MVC Widgets you’ve already built or any ideas you’d like to share? Comment below!
I’d love to see the open source MVC Widget ecosystem grow — I think this would be great for the Kentico community.
You can find more information about me on my site https://www.seangwright.me where you can find links to my tech presence on the web. You can also send me a tweet at https://twitter.com/seangwright.
If you need assistance with custom web development in Kentico or other technologies WiredViews might be able to help.