ASP.NET MVC, Bootstrap and Knockout.js with a ~twist~

knowventBanner

I have seen many great implementations of CSS and HTML through the Twitter BootStrap community along with themes like Bootstrap-Admin-Theme by Vincent Gabriel. However, there are many projects that require multiple technology implementations like ASP.NET MVC, REST API support and a responsive design that is professional and pleasing to the eye. Also, there is almost always a need for a membership provider, database implementation, user security management UI, user registration UI, etc.

The problem: Getting a project jump-started with the design and application architecture already built done and coding patterns already defined.

The Solution: Take all of the best out there put it together and you get Shoelace

What you get before writing a sign line of code

  • User Login
  • Admin User Profile Management
  • Create a Profile
  • User List Management
  • Role Base security
  • Themed Modals, forms, tables, site layout and other layouts examples found in the Samples pages

Brought to you by:

  • ASP.NET MVC 4 using Razor (CSHTML)
  • jQuery
  • Knockout.js (using client bindings/templates instead of Razor bindings)
  • Knockout.Validation.js
  • Knockout.mapping.js
  • Twitter Bootstrap Responsive Design
  • Bootstrap-Admin-Theme by Vincent Gabriel
  • The WebMatrix SimpleMembership Implementation
  • Entity Framework 5 with Code First Data Modeling

ASP.NET MVC & Twitter bootstrap together

To bring a visual studio ASP.NET MVC 4 project structure and the twitter bootstrap resource inline with each other was not difficult. As seen below,

~/Views/Shared/_Layout.cshtml

<body>
<form id="__AjaxAntiForgeryForm" action="#" method="post">@Html.AntiForgeryToken()</form> 
@Html.Action("_NavbarTop", "Navigation")
<div class="container-fluid">
    <div class="row-fluid">
        <div id="sidebar" class="span3">
            @Html.Action("_NavbarSide", "Navigation")
        </div>
        <div id="content" class="span9">
            @RenderBody()
        </div>
    </div>
    <hr />
    <footer>
        <p>© Company Name 2013</p>
    </footer>
</div>



@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap/js")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/App")

@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/bundles/jqueryval")
@RenderSection("scripts", required: false)
@Html.RenderPartialResources("js")
</body>

As you can see the _Layout.cshtml page has implemented the sidebar container, @NavbarTop partial view and the content container. This constructs the skeleton of the portal that implements the Bootstrap-Admin-Theme.


The Client – Knockout.js, Javascript and the patterns

This is where the twist comes in. I wanted to employ the single page JavaScript pattern across all rendered pages. I am using a var APP = window.app || {}; in the ~/Scripts/app/app.js file to instantiate a application level object that will house all Knockout view models, helpers, Global Vars and other application level objects needed to managed the view (like the knockout.validation.js).

~/Scripts/app/app.js

APP.init = function () {
    ko.bindingHandlers.stopBindings = {
        init: function () {
            return { 'controlsDescendantBindings': true };
        }
    };
    ko.validation.init({
        insertMessages: false,
        decorateElement: true,
        errorElementClass: 'error'
    }, true);
}
$(function () { APP.init(); });

The view model itself is defined in the ~/Scripts/app/Views/* folder. This folder roughly corresponds with the folder structure of the MVC server side cshtml razor views. This is because every cshtml view may have a accompanying JavaScript view Model.

To tie the cshtml razor view and the JavaScript View Model I have added an Extension method @Html.PartialResource() to handle loading the razor’s Knockout View Model counterpart.

~/Views/Account/UserList.cshtml

@Html.PartialResource(@<script src="@Url.Content("~/Scripts/app/Views/Account/userlist.viewmodel.js")" type="text/javascript"></script>, "js")
@Html.PartialResource(
@<script type="text/javascript">
     $(function () {
         var viewModelObj = APP.userListViewModel(ko.mapping.fromJS(@Html.Raw(Model.ToJson())));
            ko.applyBindings(viewModelObj, document.getElementById("cntnrUserList"));
        });

    </script>, "js")

Note: The custom method @Html.PartialResource() is used because the @section Scripts {} razor function doesn’t support defining scripts on Partial views. This was done to maintain a consistent pattern.

This inline JavaScript function seen above is used do the following: * Initialize the APP.userListViewModel function * Using ko.mapping.fromJS(@Html.Raw(Model.ToJson())) to output a JSON representation of the MVC model and map it to a knockout model * Apply Knockout bindings to the view’s container using document.getElementById("cntnrUserList"). Scoping the binding context to a container allows for other child views to user their own separate view model.

~/Scripts/app/Views/Account/userlist.viewmodel.js

APP.userListViewModel = function (model) {
    var self = model;
    $.extend(APP.userListViewModel, model);
    self.selectedUser = ko.observable({});
    self.confirmDelete = function () {
        $("#hdDeleteUserId").val(this.userId());
    };
    self.saveUserPermissions = function () {
        var url = APP.helpers.prepareRelativeUrl("Account/SaveUserRoles");
        APP.helpers.performAjaxPost(url,
                ko.toJSON(self.selectedUser),
                function (result) {
                    //Do nothing.
                },
                function () {
                    alert('An error has occured. Please try again.');
                });
    };
    return ko.validatedObservable(self);
};

This js method is called from the razor template inline code. It accepts the JSON representation of the MVC Model defined on the server, allowing for the model properties to be defined and managed in one place and extends the view model with the server side properties.

~/Controllers/AccountController.cs

[Authorize(Roles=Common.Constants.ROLES_ADMINISTRATOR)]
public ActionResult UserList()
{
    UserListModel mdl = new UserListModel();
    mdl.logedInUserName = WebSecurity.CurrentUserName;
    using (var cxt = new Repository.RepoContext())
    {
        cxt.UserProfiles.ToList().ForEach(f1 => {
            UserListModel.UserListItem item = new UserListModel.UserListItem()
            {
                userId = f1.UserId,
                userName = f1.UserName,
                firstName = f1.FirstName,
                lastName = f1.LastName
            };
            item.userRoles = new List<UserListModel.UserListItem.UserRole>();
            Common.Constants.SystemRoles().ForEach(f2 => {
                item.userRoles.Add(new UserListModel.UserListItem.UserRole()
                {
                    roleName = f2,
                    roleDisplayName = f2,
                    isMember = Roles.IsUserInRole(f1.UserName, f2)

                });
            });
            mdl.users.Add(item);

        });
    }


    return View(mdl);
}

This controller method returns the View with the loaded model data which is then loaded in the Knockout view model via @Html.Raw(Model.ToJson())) in the inline JavaScript seen previously.

Feel free to download the latest zip for Shoelace HERE

Or view it on GitHub

Posted in .NET and tagged , , , , , .