A deep dive into Kentico 12 MVC Widgets with Vue.js

Sean G. Wright
Vue.js Developers
Published in
16 min readMar 5, 2019

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 second part of the widget development pattern involves creating inline editor partial views and a Javascript interop layer. These inline editors operate only when viewing the widget through the CMS — in the “editing” state — they are not run on the MVC site in the “live” state.

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. 😒

But now with widgets I was able to build a prototype with a reasonable amount of effort using the Javascript component framework Vue.js to manage state and interactions in a way that is much more maintainable than jQuery spaghetti and much less opaque than server-side Web Form controls. 😍

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 [RegisterWidget] Attribute.

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 MediaSelectionControllerMeta class and the IMediaFileQuery / ISiteContextProvider / CmsConfiguration types.

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 PropertyName. 😮

Somewhere in between .NET and Javascript

Finally, we will create the partial view of our inline editor referenced by the path InlineEditors/_MediaSelectionEditor.

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 id isn’t enough — we need a unique value for every instance.

Now, notice how we pass data from our inline editor view model to the Javascript editor through snake_case properties on the anonymous object passed to Html.Kentico().BeginInlineEditor() . These will become data- attributes on the editor wrapping element and are easily accessible within the Javascript layer of our editor.

data- attributes are a better way of storing state in the DOM than classes. They are intended to hold data and are not typically targeted by CSS or Javascript, which keeps our code from being brittle due to style or functionality refactors.

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 onDispatch($event).

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.

Modern Javascript and Vue.js

Let’s take a look at the Javascript interop layer, where we connect the widget data to Kentico’s Javascript widget APIs and our Vue.js code.

Note: From here on all the Javascript is modern ES2015+. If you aren’t familiar with Javascript language features like const, destructuring, and DOM APIs like fetch then a primer on how Javascript has changed over the past few years might be helpful.

And of course comprehending the Vue.js code will require checking out the docs if you are unfamiliar with the library.

Below is the interop file between our MVC/Razor code, Kentico’s Javascript APIs, and the Vue.js components — it follows Kentico’s naming and location conventions and is found in .\Content\InlineEditors\MediaSelectionEditor\media-selection-editor.js.

The string passed to kentico.pageBuilder.registerInlineEditor() needs to match the string passed to Html.Kentico().BeginInlineEditor() in our inline editor partial view. This is how we register our Javascript functionality and connect it to the rendering of a specific editor view.

Kentico provides some lifecycle hooks for the inline editors to help manage state and initialization. The most important hook is init which is called when our editor is initialized (i.e. the editor DOM element and Page Builder Javascript APIs are ready) and gives us an opportunity to load up any additional Javascript we need for full functionality.

The 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 <media-selection-editor> component.

We also define the onDispatch method that receives data from the <media-selection-editor> when an image is selected.

The 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.dispatchEvent we 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 MediaSelectionItemGuid of the MediaSelectionWidgetProperties class.

Once we save the page in the CMS, the value is persisted in the database, but until then the value is only updated in the Kentico Javascript layer running in the browser.

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.config is the most simplistic approach but only allows for a single domain — this won’t work for multi-site Kentico installs. Using the BeginRequest event handler in an IHttpModule is 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 IHttpHandler at 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 AuthenticationHelper.IsAuthenticated().

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 .vue file into some Javascript that can be loaded in the browser.

Let’s create a Javascript file to pull all our inline editor code into one bundle and register our Vue components with the global Vue instance. I created this at .\Scripts\inline-editors-bundle.js

All this Javascript stuff requires a client-side build process, so we will use Webpack to compile our Vue inline editors and place the compiled output in the correct location so that Kentico picks the file up when it bundles all the widget and inline editor assets together.

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 inline-editors-bundle.js as the root of my Javascript/Vue.js code. Since it imports 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 externals section 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.Vue exists before Kentico loads my media-selection-editor.js editor 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.js file to be loaded before Vue.js, it would throw an error that it didn’t know what the Vue name was.

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!

The final steps to take are to build the Javascript bundle by running 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’!

Whether you use native browser DOM APIs, vanilla Javascript, jQuery, Vue.js or another Javascript library to help you build your MVC Widgets, hopefully now you know the pieces and parts required and have some inspiration towards what might actually be possible.

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.

Edit: Here is the example repository https://github.com/seangwright/kentico-12-mvc-vuejs-widgets. Checkout the code to see a minimal implementation.

Leave an issue on the repo if the app doesn’t run for you. :)

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.

--

--

Sean G. Wright
Vue.js Developers

Web developer passionate about my craft and helping others grow in theirs.