Update: To learn more, we're now offering an instructor-led Foundations of Sitefinity 5.1 class, where you'll learn how to use ASP.NET MVC + Razor, JavaScript and Kendo UI in Sitefinity 5.1.
Sitefinity joins the MVC ranks with its 5.1 release and boy is it impressive. What makes it so unique is that Web Forms is still offered in the platform for developers to use as an alternative or hybrid approach. You heard that right, mix Web Form and MVC controls on the same page! Furthermore, with the MVC-only implementation, page templates are rendered as pure, unpolluted HTML pages. This is welcomed by JavaScript developers and SEO marketers. Try for yourself and look at the HTML source. Isn't it a thing of beauty?
In this post, I would like to show you how to create an MVC widget in Sitefinity 5.1. I will be using the
List Browser widget we just released into the marketplace (which uses
Kendo UI by the way). Note that this technique will be improved by the community, documentation and updates, so here it goes…
First, let's create our folder structure within the "~/Mvc" folder. In MVC, everything is about convention and patterns, so we need to have a vision for a structure that will serve all our MVC widgets from here on out. This is what I propose:
Notice I am using an "Areas" folder which will be used to house all our widgets. If you are familiar with MVC areas, then you know it works really well in isolating parts of your project. This way, distributing and maintaining widgets will be relieved tremendously. Also worth noting is I added 2 placeholder folders called "Api" and "Helpers". In the "Api" folder, we will keep our custom REST services using Microsoft's delicious Web API (see
this post for more info). In the "Helpers" folder, we will keep our utilities and MVC helpers there to share across all widgets.
For the widget itself, it will act as a stand-alone MVC application since it also has its own MVC structure:
Content, Controllers, Designers, Models and Views. Just like any MVC application, the point of entry for our widget is the controller. Create the controller class and inherit from "System.Web.Mvc.Controller". Split the class into regions:
Fields and Constants, Designer Properties, Controller Properties, Actions and Methods.
SitefinityWebApp.Mvc.Areas.ListBrowser.Controllers.ListsController: [ControllerToolboxItem(Name = "ListBrowser", Title = "List Browser", SectionName = "CustomToolboxSection")]
[ControlDesigner(typeof(Designer))]
public class ListsController : Controller
{
#region Fields and Constants
#endregion
#region Designer Properties
[Category("List")]
public string ListId { get; set; }
[Category("List")]
public string ExpandMode { get; set; }
[Category("List")]
public bool Collapsible { get; set; }
#endregion
#region Controller Properties
#endregion
public ActionResult Index()
{
//INITIALIZE VIEW MODEL FROM CONTROL DESIGNER UPDATES
var model = new ListsViewModel
{
ListId = ListId,
ExpandMode = ExpandMode,
Collapsible = Collapsible
};
//RETURN AREA VIEW WITH LIST VIEW MODEL
return View("../../Areas/ListBrowser/Views/Lists/Index", model);
}
}
Let me explain a few things here. The controller has an attribute for automatically placing it into the page toolbox without having to go to Administration > Settings > Toolbox manually. Another attribute is for the control designer which is used to interface with the controller properties. As you are aware, the control designer is the bridge between the admin and the properties of your widget. Usually I would initialize the property defaults in the class constructor, but since this did not work with the controller, I ended up placing the property defaults in the designer's JavaScript. I am guessing there may be some Dependency Injection going on that interferes with setting my property defaults there.
Index() in the Actions region is where your widget will begin its action. This is where you call all your logic for the widget. Populate your view model with any data results you come up with, then use this view model as the strong type in the view. Notice I am using the path "../../Areas/ListBrowser/Views/Lists/Index". This is because the views location seems configured to "~/Mvc/Views", so I am telling Sitefinity to go 2 levels up to find it in my "Areas" section. Here is some more code for illustration:
SitefinityWebApp.Mvc.Areas.ListBrowser.Models.ListsViewModel: public class ListsViewModel
{
public List List { get; set; }
public string ListId { get; set; }
public string ExpandMode { get; set; }
public bool Collapsible { get; set; }
}
Index.cshtml (Razor!): @model SitefinityWebApp.Mvc.Areas.ListBrowser.Models.ListsViewModel
<ul class="list-content-panelbar">
@foreach (var item in Model.List.ListItems.Where(i => i.Status == ContentLifecycleStatus.Live))
{
<li>@Html.Raw(item.Title)
<div>@Html.Raw(item.Content)</div>
</li>
}
</ul>
There you have it - MVC sweetness in a Sitefinity package. It is indeed the best thing since the introduction of the Module Builder. If you have not done so already,
download the ListBrowser and check it out. Also be sure to sign up for our
Foundations of Sitefinity class where I'll show you how to get the most out of Sitefinity 5.1 using MVC, JavaScript and Kendo UI.
Happy Coding!!