CDNs and Fallbacks for Angular Modules

I’ve posted before about CDNs and fallbacks in the context of ASP.NET 5 and MVC 6. I discussed the whys and wherefores, so please read the aforementioned post if you want to understand the details of why CDNs and fallbacks are recommended practice when consuming javascript resources in your web applications.

Bottom line is that consuming javascript libraries from CDNs improves performance both on the web server and on the client browsers. But CDNs don’t come with SLAs and so there is no guarantee that the CDN will be operational when a client requests a resource. We therefore need to include a fallback position which directs the client to request the resource from our own servers when the CDN has failed.

Basic Fallback Test

<!-- Load AngularJS from Google CDN -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<!-- Fallback to local file -->
<script>window.angular || document.write('<script src="scripts/angular.min.js">\x3C/script>')</script> 

The above script attempts to load the angular.min.js library from the Google CDNs.

It then executes a simple OR statement which checks to see if the windows.angular object resolves to true or false.

True indicates that the object exists and the library was successfully loaded from the Google CDN. The OR statement is short-circuited and all is well.

False indicates that the library failed to load. The expression on the second half of the OR statement is resolved, and a script element is written to the document object, directing the browser to request the library from our servers.

Note. All javascript objects can be evaluated in a boolean context, and will either evaluate to true (truthy values) or evaluate to false (falsy values). Falsy values include 0, null, undefined, false, “”, and NaN. All other values are truthy.

In short, the fallback position should check for the existence of a globally scoped object specific to the library in question. And if the object does not exist, then direct the browser to request the library from our servers.

But what if the library does not define any globally scoped objects, and instead only extends objects from another library that it depends on. This is the case with many of the angular UI libraries which extend the windows.angular object by injecting modules. In these cases we need to write a javascript statement that checks for the existence of modules and evaluates to true.

Complex Fallback Test

<!-- Load Angular Bootstrap UI from Google CDN -->
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap.min.js"></script>
<!-- Fallback to local file -->
<script>
    (function () {
        try {
            window.angular.module('ui.bootstrap');
        }
        catch (e) {
            return false;
        }
        return true;
    })() || document.write('<script src="scripts/ui-bootstrap.min.js">\x3C/script>')
</script>

In the example above, the fallback position uses a self-invoking anonymous function to check to see if the ui.bootstrap module has been injected into the window.angular.module collection. The function incorporates a try and catch block as attempting to reference a module that does not exist results in an errorThe catch block returns a false.if the module does not exist. Otherwise the function returns a true.

ASP.NET MVC 5 FallBack Test

When registering bundles for minification we can specify a CDN path and fallback position.

The ScriptBundle class has 2 arguments, the second of which we can use to register a CDN path. In addition we can assign a fallback expression string to the cdnFallbackExpression property – which I have done in the examples below using object initialization syntax.

If we then set the bundles.UseCdn property to true, and ensure that the web.config compilation debug flag is set to false – the CDN paths will be used to serve up script libraries, and the fallback expressions will be utilized to provide a fallback position.

using System.Web;
using System.Web.Optimization;

namespace MVC5TestApp
{
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(
                new ScriptBundle(
                    "~/bundles/angular",
                    "//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.min.js") { CdnFallbackExpression = "window.angular" }
                    .Include("~/Scripts/angular.js"));

            bundles.Add(
                new ScriptBundle(
                    "~/bundles/angular-ui-bootstrap",
                    "//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap2.min.js")
                        {
                            CdnFallbackExpression = @"   
                                (function () {
                                    try {
                                        window.angular.module('ui.bootstrap');
                                    }
                                    catch (e) {
                                        return false;
                                    }
                                    return true;
                                })()"
                        }
                    .Include("~/Scripts/angular-ui/ui-bootstrap.js"));

            bundles.UseCdn = true;
        }
    }
}

 ASP.NET MVC 6 FallBack Test

The Environment and Script TagHelper classes and attributes were introduced with MVC 6. They enable us to specify different sources for script, and fallback tests, based on the execution environment. For more details follow the link at the top of this post.

<environment names="Development">
    <script src="~/lib/angular/angular.js"></script>
    <script src="~/lib/angular-bootstrap/ui-bootstrap.js"></script>
</environment>
<environment names="Staging,Production">
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.min.js"
            asp-fallback-src="~/lib/angular/angular.min.js"
            asp-fallback-test="window.angular">
    </script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap.min.js"
            asp-fallback-src="~/lib/angular-bootstrap/ui-bootstrap.min.js"
            asp-fallback-test="
                (function() {
                    try {

                        window.angular.module('ui.bootstrap');
                    } catch(e) {
                        return false;
                    }
                    return true;
                })()">
    </script>
</environment>
Advertisements

Server side paging with DataTables.js and MVC

Source code for this article can be found here – https://github.com/RossWhitehead/ServerSidePagingDataTables

When evaluating the usefulness of a javascript grid library the first thing I do is determine whether it is possible to implement server-side paging. This is a must-have feature for most business scenarios, and you will be backing yourself into a corner if you implement a grid that only supports client-side paging.

In a previous post I demonstrated how to implement server side paging with KoGrid. I’m going to repeat this demonstration, but this time for DataTables.NET.

I like KoGrid. It’s based on Knockout, which in my humble opinion is the bees-knees, and my go-to when developing all but the most basic of MVC views.

I also like DataTables.NET.

Both KoGrid and DataTables.NET are widely used and well respected in the community. They meet most demands placed on them, but they differ in terms of their implementation. There’s a good article by Jason Howard here comparing the two. And I agree with most of the statements with the caveat that I am reserving judgement on the assertion that DataTables.NET works with Knockout. I have not yet managed to get the 2 working together when implementing server-side paging. And I will reserve judgment until I have either succeeded or failed miserably.

So onto the implementation of server side paging with DataTables.NET and MVC (without Knockout).

This is what I’m going to build –

BasicDataTable

When initializing the grid, rather than requesting all products from the server, the client is going to request a single page of products. And when the user interacts with the grid – either changing the number of entries to view, selecting a new page, clicking on a column header to sort, clicking on the refresh button, or typing in the search box – the client will update the grid by requesting products one page at a time. In so doing the server will receive a greater number of requests. However, each request will only be for a small page of data, and so should take significantly less resources to meet. Additionally, the client will not be charged with initializing the page with a huge amount of data.

I will be presenting the solution in the following order –

  1. Model
  2. Controller
  3. View
  4. View Model

1. Model

ProductCategory.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ServerSidePagingDataTables.Models
{
    public class ProductCategory
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int ProductCategoryId { get; set; }

        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        // Navigational Properties
        public virtual ICollection<Product> Products { get; set; }
    }
}

Product.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ServerSidePagingDataTables.Models
{
    public class Product
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int ProductId { get; set; }

        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        [Required]
        [StringLength(1000)]
        public string Description { get; set; }

        [Required]
        public int ProductCategoryId { get; set; }

        // Navigation properties
        public virtual ProductCategory ProductCategory { get; set; }
    }
}

ServerSidePagingDataTablesDbContext.cs

using System.Data.Entity;

namespace ServerSidePagingDataTables.Models
{
    public class ServerSidePagingDataTablesDbContext : DbContext
    {
        public virtual DbSet<Product> Products { get; set; }
        public virtual DbSet<ProductCategory> ProductCategories { get; set; }
    }
}

I am using Entity Framework code-first.

I have 2 entities – ProductCategory and Product, with a one-to-many relationship between them. And I have a DbContext called DataTablesDemoDbContext, which is used to map the model to the database.

2. Controller

using DataTables.Mvc;
using System;
using System.Linq;
using System.Linq.Dynamic;
using System.Web.Mvc;
using ServerSidePagingDataTables.Models;

namespace ServerSidePagingDataTables.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public JsonResult DataTableGet([ModelBinder(typeof(DataTablesBinder))] IDataTablesRequest requestModel)
        {
            var db = new ServerSidePagingDataTablesDbContext();

            IQueryable<Product> query = db.Products;

            var totalCount = query.Count();

            // Apply filters
            if (requestModel.Search.Value != String.Empty)
            {
                var value = requestModel.Search.Value.Trim();
                query = query.Where(p => p.Name.Contains(value) || p.Description.Contains(value));
            }

            var filteredCount = query.Count();

            // Sort
            var sortedColumns = requestModel.Columns.GetSortedColumns();
            var orderByString = String.Empty;

            foreach (var column in sortedColumns)
            {
                orderByString += orderByString != String.Empty ? "," : "";
                orderByString += (column.Data == "Category" ? "ProductCategory.Name" : column.Data) + (column.SortDirection == Column.OrderDirection.Ascendant ? " asc" : " desc");
            }

            query = query.OrderBy(orderByString == String.Empty ? "name asc" : orderByString);

            // Paging
            query = query.Skip(requestModel.Start).Take(requestModel.Length);

            var data = query.Select(p => new
            {
                ProductId = p.ProductId,
                Name = p.Name,
                Description = p.Description,
                Category = p.ProductCategory.Name
            }).ToList();

            return Json(new DataTablesResponse(requestModel.Draw, data, filteredCount, totalCount), JsonRequestBehavior.AllowGet);
        }
    }
}

I have an MVC controller with 2 actions.

Index serves up a .cshtml page that contains the UI markup (view) and the javascript (view model). I do not pass any data to the view – as the view model will be requesting the data once the page has been loaded.

DataTableGet returns a Json response containing the data required to initialize or update the grid. This action will be called by the view model using Ajax when the grid needs updating with new data.

The first thing of note about the DataTableGet action is that it uses a custom model binder, DataTableBinder, to map the request to the IDataTableRequest requestModel parameter. The DataTableGet action also responds with an instance of DataTablesResponse serialized to a Json string.

The aforementioned interfaces and classes can be downloaded from Nuget.

PM> Install-Package datatables.mvc5

The reason we need a custom model binder is that when the DataTable.NET client code makes Ajax requests for new data, the request details, such as the page required, number of records, sort orders, and search strings, are passed using query string parameters. And the naming convention used by DataTables.NET is not compatible with the default MVC model binders, as you can see from the example URL below.

http://localhost:50055/api/datatableproducts?draw=1&columns[0][data]=productId&columns[0][name]=&columns[0][searchable]=true&columns[0][orderable]=true&columns[0][search][value]=&columns[0][search][regex]=false&columns[1][data]=name&columns[1][name]=&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1][search][regex]=false&columns[2][data]=description&columns[2][name]=&columns[2][searchable]=true&columns[2][orderable]=true&columns[2][search][value]=&
columns[2][search][regex]=false&columns[3][data]=category&columns[3][name]=&columns[3][searchable]=true&columns[3][orderable]=true&columns[3][search][value]=&columns[3][search][regex]=false&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&search[regex]=false&_=1437911688049

The DataTableGet action creates an instance of the database context, and constructs a query to grab a page of products, whilst applying the requested filters and sort orders. I have used System.Linq.Dynamic to simplify the construction of the query. This library is not included in an MVC project by default. It needs to be loaded from NuGet.

PM> Install-Package System.linq.Dynamic

3. View

 <link href="//cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.css" rel="stylesheet" /> 

I am creating a DataTables.NET grid styled with bootstrap. I therefore need to include the dataTable.bootstrap.css stylesheet in my view.

<style type="text/css">
    .list-panel {
        margin-top: 20px;
    }

        .list-panel .panel-heading {
            overflow: auto;
            padding: 5px 20px;
        }

        .list-panel .panel-title {
            float: left;
            margin-top: 10px;
        }

        .list-panel .panel-body {
            padding: 20px 20px 10px 20px;
        }

        .list-panel .refresh-button {
            float: right;
        }

    .data-table {
        border-collapse: collapse;
        border-spacing: 0;
    }
</style>

And some styling for the panel that grid will sit in.

<div class="row">
    <div class="col-md-12">
        <div class="panel panel-primary list-panel" id="list-panel">
            <div class="panel-heading list-panel-heading">
                <h3 class="panel-title list-panel-title">Products</h3>
                <button type="button" class="btn btn-default btn-md refresh-button" id="refresh-button">
                    <span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Refresh
                </button>
            </div>
            <div class="panel-body">
                <table id="data-table" class="table table-striped table-bordered" style="width:100%"></table>
            </div>
        </div>
    </div>
</div>

The HTML for the grid is a simple <table> element, which I have placed in a bootstrap panel along with a refresh button.

4. View Model

<script src="//cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js"></script>
<script src="//cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.js"></script>

The view model includes the DataTables.NET JQuery library (jquery.dataTables.min.js), and additional includes the DataTables,NET Bootstrap integration library (dataTables.bootstrap.js). This library can be omitted if you do not want to use Bootstrap with you grid.

<script type="text/javascript">
    $(function () {
        var productListVM = {
            dt: null,

            init: function () {
                dt = $('#data-table').DataTable({
                    "serverSide": true,
                    "processing": true,
                    "ajax": "/home/datatableget",
                    "columns": [
                        { "title": "Product Id", "data": "ProductId", "searchable": false },
                        { "title": "Name", "data": "Name" },
                        { "title": "Description", "data": "Description" },
                        { "title": "Category", "data": "Category" }
                    ],
                    "lengthMenu": [[2, 5, 10, 25], [2, 5, 10, 25]]
                });
            },

            refresh: function () {
                dt.ajax.reload();
            }
        }

        $('#refresh-button').on("click", productListVM.refresh);

        /////////////////////////////////////////////////////////////////
        // Let's kick it all off
        productListVM.init();
    })
</script>

I have encapsulated the view model functionality into a javascript object called productListVM.

productListVM has a init function which initializes the grid by calling the DataTables.NET DataTable function. It passes in a number of configuration options to define how the grid will look and behave. “serverSide”: true enables server side processing, “processing”: true tells DataTable.NET to display a “Processing..” message when the grid is being updated, and “ajax”: “/home/datatableget” defines the data-source for the Ajax requests. These 3 options are all that are required to implement server side paging. The grid will respond to all relevant user interaction – sorting, filtering, and paging – by automatically requesting the relevant data from the server and updating the grid. No other view model coding is required, and this is the beauty of server side processing with DataTables.NET. Compare this with KoGrid where it is necessary to write code to define subscriptions to a number of events and to construct the request data.

productListVM also has a refresh function, which forces a reload of the grid. The refresh button is assigned a click event handler which call the this function.

And finally, everything thing is kicked off, with the grid being initialized, by calling productListVM.init().

Client and Server Validation with Web API and Knockout

In this post I’m going to demonstrate how to implement both client and server side validation with ASP.NET Web API and Knockout. In particular I’m going to demonstrate how to convey server side binding and validation errors from the controller to the view model, and how to utilize Knockout client side validation features to display both client and server side errors in the view.

Model

My model comprises of 2 domain entities, Product and ProductCategory, with a one-to-many relationship between them.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebAPIKo.Models
{
    public class Product
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int ProductId { get; set; }

        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        [Required]
        [StringLength(1000)]
        public string Description { get; set; }

        [Required]
        public int ProductCategoryId { get; set; }

        // Navigation properties
        public virtual ProductCategory ProductCategory { get; set; }
    }
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebAPIKo.Models
{
    public class ProductCategory
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int ProductCategoryId { get; set; }

        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        // Navigational Properties
        public virtual ICollection<Product> Products { get; set; }
    }
}

I am using code first Entity Framework as my ORM. Here’s the DbContext containing DbSets for the aforementioned entities –

using System.Data.Entity;

namespace WebAPIKo.Models
{
    public class WebAPIKoDbContext : DbContext
    {
        public virtual DbSet<Product> Products { get; set; }
        public virtual DbSet<ProductCategory> ProductCategories { get; set; }
    }
}

View

My view is a simple “Create Product” form with product name and description text boxes, and a select list for product categories.

create project 2

Here’s the HTML –

<div class="row">
    <div class="col-md-12">
        <div class="panel panel-info" id="add-panel">
            <div class="panel-heading">
                <h2 class="panel-title">Create Product</h2>
            </div>
            <div class="panel-body">
                <form role="form" data-bind="validationOptions: validationOptions">
                    <div class="form-group">
                        <label for="name">Name</label>
                        <input id="name" type="text" class="form-control" data-bind="value: product.name" placeholder="Product Name" />
                    </div>
                    <div class="form-group">
                        <label for="description">Description</label>
                        <input id="description" class="form-control" data-bind="value: product.description" placeholder="Description" />
                    </div>
                    <div class="form-group">
                        <label for="product-category">Product Category</label>
                        <select id="product-category"
                                data-bind="options: productCategories, optionsText: 'name', optionsValue: 'productCategoryId',
                           optionsCaption: '-- Select a Product Category --', value: product.productCategoryId"></select>
                    </div>
                    <button type="button" class="btn btn-primary" data-bind="click: save, enable: canSave">
                        <span class="glyphicon glyphicon-save" aria-hidden="true"></span> Add
                    </button>
                    <ul id="errors" class="error-message" data-bind="foreach: generalErrors">
                        <li><span data-bind="text: $data"></span></li>
                    </ul>
                </form>
            </div>
        </div>
    </div>
</div>

Knockout data-bind attributes are used to declare the binding of the controls to the view model.

Of note are the following –

  • The <form> has a data-bind=”validationOptions: validationOptions” attribute, which applies global validation options to all validatable controls in the form.
  • There is a “errors” <ul> element at the bottom of the form, which has a data-bind=”foreach: generalErrors” attribute.  The <ul> contains a <li> element which in turn contains a <span> with a data-bind=”text: $data” attribute. A <li> will therefore be rendered for each error message in the view model’s generalErrors ko.observableArray property. I am using this to display any errors that are not specific to a field.

Here’s some styles to accompany the form –

<style type="text/css">
    select {
        height: 30px;
        display: block;
    }

    .panel {
        width: 450px;
        margin-top: 15px;
    }

    .panel-heading {
        padding: 10px 20px;
    }

    .panel-body {
        padding: 10px 20px 10px 20px;
    }

    .error-message {
        color: red;
    }

    .error-element {
        border-color: red;
    }

    #errors {
        padding-left: 0;
        padding-top: 5px;
    }

        #errors li {
            list-style-type: none;
        }
</style>

Controller

Before I present the view model I want to discuss Web API controller actions, and in particular the HTTP response header and body that will be returned depending on whether a post/put is successful or not.

With Visual Studio 2013 we have a number of Web API controller scaffolding extensions which provide basic implementation for Web API 2 controllers and actions.

newcontroller

createcontroller2

I have selected the “Web API Controller with actions, using Entity Framework” scaffolder. I am prompted to provide the model class – Product – and the data context class – WebAPIKoDbContext. And a controller is generated with working implementations of for get, post, put, and delete actions.

Here’s the basic implementation for the post action (used for creating new products) –

// POST: api/Product
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct([FromBody]Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Products.Add(product);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = product.ProductId }, product);
}

This action uses Web API model binding to bind request parameters, from the body of the request, to an instance of the Product entity.

The Product entity has a number of validation attributes, which are assessed during model binding. Any validation errors are added to the ModelState object, which is an instance of System.Web.Http.ModelBinding.ModelStateDictionary. For each validation error, the property name is added to the ModelState’s Keys collection and the error description is added to the ModelState’s Values collection. Each item in the Values collection is actually an array of strings, thus enabling more than one error description per Key.

If there are model binding or validation errors, the action returns BadRequest(ModelState). This results in a HTTP 400 response being sent to the client, along with the response body containing a JSON serialization of a Message string and the ModelState object. Here are some examples of the response body –

In the first example the model binding was unsuccessful as the parameters in the request body did not match any of the Product properties. The model binder was not able to generate the product object.

{   "Message":"The request is invalid.",
    "ModelState":{
        "product":["Error converting value \"ww\" to type 'WebAPIKo.Models.Product'. Path '', line 1, position 4."]}
}

In this second example, the model binder was able to generate the product object, but the name and description properties failed validation as no values were posted for them, whilst they are required.

{   "Message":"The request is invalid.",
    "ModelState":{
        "product.Name":["The Name field is required."],
        "product.Description":["The Description field is required."]}}

In addition to performing this validation on the server, I will also be performing model validation on the client. Validation is one example of where it is necessary to deviate from the DRY principal. If we were only to validate on the server then the user would have to submit the form and wait for a response before being notified of validation errors. This could be very frustrating for the user, and so it is better to have the browser perform validation whilst the user is interacting with the form. But the client side validation should be an addition to rather than a replacement for the server side validation. Firstly, because we cannot guarantee that the request will be correctly formed, and secondly, because not all validation can easily be performed on the client.

For example, if we have such a business rule stating that product names must be unique, and we have many products then we may need to validate the product against this rule on the server. If the product fails this rule we need to communicate this back to the client, and we can utilize the ModelState object to do this. We can also utilize the ModelState object to convey non-field-specific validation errors as shown below.

ModelState.AddModelError("product.Name", "Product name is already in use");

ModelState.AddModelError("General", "General error 1");
ModelState.AddModelError("General", "General error 2");

And resulting in the following response body –

{   "Message":"The request is invalid.",
    "ModelState":{
        "product.Name":["Product name is already in use"],
        "General":["General error 1","General error 2"]}}

Of note is that because we added 2 model errors with the “General” key, then we have 2 items in the array of error descriptions.

One further thing we need to account for in handling errors, is that it is possible that an error occurs on the server during the processing of the request. For example, there may be a network issue with the application temporarily not being able to connect to the database. In this case a HTTP 500 response will be generated with a response body similar to the following –

{   "Message":"An error has occurred.",
    "ExceptionMessage":"An exception has occurred",
    "ExceptionType":"System.Exception"}

So when we have a validation error or exception then we get a HTTP 400 or 500 response with a response body that always contains a “Message” property, and optionally contains a “ModelState” object. Our client code will therefore need to trap HTTP 400 and 500 responses, and parse the “ModelState” object if one exists. And if not, display the “Message”.

View Model

ko.observable.appendError

Knockout validation, by default, appends validation attributes and an error message span after each input control that is bound to a validateable property. For example, the view incorporates the following input control which is bound to the product.name property –

<input id="name" type="text" class="form-control" data-bind="value: product.name" placeholder="Product Name" />

After the bindings are applied, the following HTML is generated by Knockout –

<input id="name" type="text" class="form-control" data-bind="value: name" placeholder="Product Name" title="This field is required." data-orig-title="">
<span class="error-message" style="display: none;"></span>

When the user interacts with the view, entering or modifying data, the view model’s ko.observable properties are automatically updated. The Knockout validation library extends the ko.observable type to include an error property, and a setError method. Each validateable ko.observable property is validated according to it’s validation rules, and the setError method is used to assign a validation error to the error property. The setError method overwrites the value of the error property, and assigns the ko.observable as being not valid.

The mechanics of displaying client validation errors in the view is therefore available out-of-the-box with Knockout validation. We can also hook into these mechanics in order to display validation errors returned from the server.

The first thing I have done to hook into these mechanics is to extend the ko.observable type to include an appendError function, which appends new errors onto existing ones rather than overwriting them. This is needed because there is the possibility of more than one error per property being returned from the server.

///////////////////////////////////////////////////////////////////
// ko.observable.appendError

ko.observable.fn.appendError = function (error) {
    var thisError = this.error();
    if (thisError) {
        this.setError(thisError += '. ' + error);
    }
    else {
        this.setError(error)
    };
};

ProductCategory and ProductCategoryList

The productCategory and productCategoryList types contain properties and functions for the retrieval of a list of product categories from the server. The product categories are used to populate the form’s product category select control.

///////////////////////////////////////////////////////////////////
// ProductCategory

var productCategory = function (productCategoryId, name) {
    var self = this;

    // Properties
    self.productCategoryId = productCategoryId;
    self.name = name;
}

var productCategoryList = function () {
    var self = this;

    self.productCategories = ko.observableArray([]);

    self.get = function (callBack) {
        $.ajax({
            url: '/api/productCategory',
            type: 'get',
            contentType: 'application/json; charset=utf-8',
            success: function (data) {
                $.each(data, function (key, value) {
                    self.productCategories.push(new productCategory(value.ProductCategoryId, value.Name));
                });

                if (typeof callBack !== "undefined") {
                    callBack();
                };
            }
        });
    }
}

ProductVM

I will be binding an instance of ProductVM to the create product view. It is our view model object.

ProductVM  has a self.product object, which is a wrapper for the name, description, and productCategoryId ko.observable properties. I am using product as a wrapper for these properties as it makes the properties easier to work with as a group.

self.product = {
    name: ko.observable(null),
    description: ko.observable(null),
    productCategoryId: ko.observable(0)
}

ProductVM also has self.productCategories which is a ko.observableArray property for storing product categories. An array of product categories is passed into the ProductVM’s constructor during initialization, and this array is in turn passed into the ko.observableArray’s constructor. self.productCategories is used to populate the view’s product category select list.

self.productCategories = ko.observableArray(productCategories);

Next I have the assignment of Knockout validation validation rules to the ko.observable product properties. And the creation of a validation group, which enables us to display, hide, or remove all validation errors.

self.product.name.extend({
    required: true,
    maxLength: 100
});

self.product.description.extend({
    required: true,
    maxLength: 1000
});

self.product.productCategoryId.extend({
    required: true
});

self.errors = ko.validation.group(self.product);

I then define self.generalErrors which is a ko.observableArray that will store all validation errors that are not specific to one of the validatable properties. This array is bound to the errors list towards the bottom of the view using a foreach binding. As such, when it is populated with one or more validation errors, a list item element is created for each, and the messages are displayed.

self.generalErrors = ko.observableArray([]);

The next thing I do is define some validation options. These options are bound, using a validationOptions binding to the form. They are therefore applicable to all input controls in the form.

self.validationOptions = {
    decorateInputElement: true,
    errorElementClass: 'error-element',
    errorMessageClass: 'error-message'
};

Next we have some properties that manage state. The first of these, self.isSaving, is set to true at the start of a save, and then set to false once the save is complete. The next, self.isValid, is a ko.computed property that is set to true when there are no validation error in the errors group, and false when there are. The third property, self.canSave, is a ko.computed property that is set to true when self.isValid, and !self.isSaving. self.canSave is bound to the enable attribute of the view’s add button.

self.isSaving = ko.observable(false);

self.isValid = ko.computed(function () {
    return self.errors().length == 0;
});

self.canSave = ko.computed(function () {
    return self.isValid() && !self.isSaving();
});

The final thing we have in the ProductVM is the save method.

If there are no validation errors the save method sets self.isSaving to true, which has the effect of disabling the view’s add button. This stops the user from invoking concurrent saves. The save method then uses ko.toJSON to generate a JSON string containing the product properties.

The save method then performs an ajax POST to the /api/products url, passing the JSON string in the request body, and if the ajax request is successful then self.isSaving to false.

However, if the ajax request is not successful, and a HTTP 400 response is returned from the server then the following logic is performed –

  • Check to see if there is a ModelState object in the response body.
  • If there is a ModelState object then loop through the keys, and for each key loop through the errors.
  • For each error, if the key matches a validateable property name then append the error to the property.
  • If the key does not match a property name then push the error to the generalErrors observableArray.
  • If the response body does not contain a ModelState object – which is optional – then push the Message string – which is required – to the generalErrors array.

And if a HTTP 500 response is returned then the following logic is performed –

  • The response body will not contain a ModelState object. Push the Message string to the generalErrors array.
self.save = function () {
    if (self.errors().length == 0) {
        self.isSaving(true);

        var dataObject = ko.toJSON(self.product);

        $.ajax({
            url: '/api/products',
            type: 'post',
            data: dataObject,
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            success: function (data) {
                self.isSaving(false);
            },
            statusCode: {
                400: function (data) {
                    if (typeof data.responseJSON.ModelState !== 'undefined') {
                        $.each(data.responseJSON.ModelState, function (key, errors) {
                            $.each(errors, function (index, error) {
                                switch (key) {
                                    case 'Name':
                                        self.product.name.appendError(error);
                                        break;
                                    case 'Description':
                                        self.product.description.appendError(error);
                                        break;
                                    case 'ProductCategory':
                                        self.product.productCategoryId.appendError(error);
                                        break;
                                    default:
                                        self.generalErrors.push(error);
                                        break;
                                };
                            });
                        });
                    }
                    else {
                        self.generalErrors.push(data.responseJSON.Message);
                    };
                },
                500: function (data) {
                    self.generalErrors.push(data.statusText + '. Please try again.');
                }
            }
        });
    }
    else {
        self.errors.showAllMessages(true);
    }
};

Because we are appending and pushing the errors to the validatable ko.observable properties and the generalErrors ko.observableArray, and these are bound to the form, then the validation errors will automatically be displayed in the appropriate places in the view.

This is what we will see.

createformerrors

Kick-off

The final thing I have in the view model is the code to kick it all off –

/////////////////////////////////////////////////////////////////
// Let's kick it all off

var productCategoryList = new productCategoryList();

productCategoryList.get(function () {
    var vm = new productVM(productCategoryList.productCategories());
    ko.applyBindings(vm, $addPanel[0]);
    vm.errors.showAllMessages(false);
});

 

For completeness, here’s the full view model.

<script type="text/javascript">
    ///////////////////////////////////////////////////////////////////
    // ko.observable.appendError

    ko.observable.fn.appendError = function (error) {
        var thisError = this.error();
        if (thisError) {
            this.setError(thisError += '. ' + error);
        }
        else {
            this.setError(error)
        };
    };

    $(function () {
        var $addPanel = $('#add-panel');
        ///////////////////////////////////////////////////////////////////
        // ProductCategory

        var productCategory = function (productCategoryId, name) {
            var self = this;

            // Properties
            self.productCategoryId = productCategoryId;
            self.name = name;
        }

        var productCategoryList = function () {
            var self = this;

            self.productCategories = ko.observableArray([]);

            self.get = function (callBack) {
                $.ajax({
                    url: '/api/productCategory',
                    type: 'get',
                    contentType: 'application/json; charset=utf-8',
                    success: function (data) {
                        $.each(data, function (key, value) {
                            self.productCategories.push(new productCategory(value.ProductCategoryId, value.Name));
                        });

                        if (typeof callBack !== "undefined") {
                            callBack();
                        };
                    }
                });
            }
        }

        ///////////////////////////////////////////////////////////////////
        // ProductVM

        var productVM = function (productCategories) {
            var self = this;

            // Properties

            self.product = {
                name: ko.observable(null),
                description: ko.observable(null),
                productCategoryId: ko.observable(0)
            }

            self.productCategories = ko.observableArray(productCategories);

            // Validation

            self.product.name.extend({
                required: true,
                maxLength: 100
            });

            self.product.description.extend({
                required: true,
                maxLength: 1000
            });

            self.product.productCategoryId.extend({
                required: true
            });

            self.errors = ko.validation.group(self.product);

            self.generalErrors = ko.observableArray([]);

            self.validationOptions = {
                decorateInputElement: true,
                errorElementClass: 'error-element',
                errorMessageClass: 'error-message'
            };

            // State

            self.isSaving = ko.observable(false);

            self.isValid = ko.computed(function () {
                return self.errors().length == 0;
            });

            self.canSave = ko.computed(function () {
                return self.isValid() && !self.isSaving();
            });

            // Methods

            self.save = function () {
                if (self.errors().length == 0) {
                    self.isSaving(true);

                    var dataObject = ko.toJSON(self.product);

                    $.ajax({
                        url: '/api/products',
                        type: 'post',
                        data: dataObject,
                        dataType: 'json',
                        contentType: 'application/json; charset=utf-8',
                        success: function (data) {
                            self.isSaving(false);
                        },
                        statusCode: {
                            400: function (data) {
                                if (typeof data.responseJSON.ModelState !== 'undefined') {
                                    $.each(data.responseJSON.ModelState, function (key, errors) {
                                        $.each(errors, function (index, error) {
                                            switch (key) {
                                                case 'product.Name':
                                                    self.product.name.appendError(error);
                                                    break;
                                                case 'product.Description':
                                                    self.product.description.appendError(error);
                                                    break;
                                                case 'product.ProductCategory':
                                                    self.product.productCategoryId.appendError(error);
                                                    break;
                                                default:
                                                    self.generalErrors.push(error);
                                                    break;
                                            };
                                        });
                                    });
                                }
                                else {
                                    self.generalErrors.push(data.responseJSON.Message);
                                };
                            },
                            500: function (data) {
                                self.generalErrors.push(data.statusText + '. Please try again.');
                            }
                        }
                    });
                }
                else {
                    self.errors.showAllMessages(true);
                }
            };
        }

        /////////////////////////////////////////////////////////////////
        // Let's kick it all off

        var productCategoryList = new productCategoryList();

        productCategoryList.get(function () {
            var vm = new productVM(productCategoryList.productCategories());
            ko.applyBindings(vm, $addPanel[0]);
            vm.errors.showAllMessages(false);
        });
    });
</script>

 

Unit Testing ASP.NET MVC Authorization

To restrict access to an ASP.NET MVC view we restrict access to the controller action that renders the view. We do this by decorating the controller and/or controller action with [Authorize] and [AllowAnonymous] attributes.

Note. We can also apply the Authorize filter globally by adding it to applications GlobalFiltersCollection.

Below is an example of a controller where I have restricted access as follows –

  1. Decorated the controller with the [Authorize] attribute.
  2. Decorated the Index() action with the [AllowAnonymous] attribute, which overrides the controller’s [Authorize] attribute, allowing all users to access the Index view.
  3. Not decorated the Details(int id) action. It therefore inherits the controller’s [Authorize] attribute, allowing only authenticated users to access the Details view.
  4. Decorated both the Create() and Create(FormCollection collection) actions with the [Authorize(Roles = “Admin”, Users = “Ross”)] attribute. This further restricts access to only authenticated users who are either associated with the Admin role or whose user name is Ross.
using System.Web.Mvc;

namespace BikeStore.Controllers
{
    [Authorize]
    public class WidgetController : Controller
    {
        // GET: Widget
        [AllowAnonymous]
        public ActionResult Index()
        {
            return View();
        }

        // GET: Widget/Details/5
        public ActionResult Details(int id)
        {
            return View();
        }

        // GET: Widget/Create
        [Authorize(Roles = "Admin", Users = "Ross")]
        public ActionResult Create()
        {
            return View();
        }

        // POST: Widget/Create
        [HttpPost]
        [Authorize(Roles = "Admin", Users = "Ross")]
        public ActionResult Create(FormCollection collection)
        {
            try
            {
                // TODO: Add insert logic here

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }
    }
}

Security is a high priority for virtually all applications. When I deploy code for user acceptance testing I want to do so having fully unit tested the authorization.

When a request enters the MVC pipeline, the authorization filters are applied prior to a controller action being executed. The authorization filters are part of the MVC framework, and as such we do not need to test them. We can assume that they work as described. Instead what we need to do is test that the controller actions have had the authorization attributes correctly applied.

The AuthorizationTest class – shown below – contains a number of helper methods that utilize reflection to validate controller action’s authorization.

The IsAnonymous method validates whether a controller action method allows anonymous access. The method receives an instance of the controller, a method name, and an array of parameter types. The array of parameter types is used to differentiate between overloaded methods – a common practice in MVC. If a method is not overloaded then this parameter can be set to null.

For a controller action to allow anonymous access, either,

  1. the action must be decorated with the [AllowAnonymous] attribute, or
  2. neither the action nor the controller is decorated with the [Authorize] attribute.

Please note that this code assumes that the Authorize filter has not been applied globally.

There are 2 overloads of the IsAuthorized method, which validate whether a controller is authorized. The 1st overload receives an instance of the controller, a method name, and an array of parameter types.

This overload of the method applies the following logic to determine if the controller action is authorized. Either,

  1. the method is decorated with a [Authorize] attribute, or
  2. the controller is decorated with a [Authorize] attribure whilst the method is not decorated with a [AllowAnonymous] attribute.

The second overload of the IsAuthorized method add 2 parameters. An array of role names and an array of user names. This overload validates whether the controller action is authorized – as above – and then validates whether it is authorized for the roles and users.

using System;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;

namespace Framework.Tests.Mvc
{
    public static class AuthorizationTest
    {
        /// <summary>
        /// Check to see if a method allows anonymous access -
        /// 1. A method is anonymous if it is decorated with the AllowAnonymousAttribute attribute.
        /// 2. Or, a method is anonymous if neither the method nor controller are decorated with the AuthorizeAttribute attribute.
        /// </summary>
        /// <param name="controller"></param>
        /// <param name="methodName"></param>
        /// <param name="methodTypes">Optional</param>
        /// <returns>true is method is anonymous</returns>
        public static bool IsAnonymous(Controller controller, string methodName, Type[] methodTypes)
        {
            return GetMethodAttribute<AllowAnonymousAttribute>(controller, methodName, methodTypes) != null ||
                (GetControllerAttribute<AuthorizeAttribute>(controller) == null &&
                    GetMethodAttribute<AuthorizeAttribute>(controller, methodName, methodTypes) == null);

        }

        /// <summary>
        /// Check to see if a method requires authorization -
        /// 1. A method is authorized if it is decorated with the Authorize attribute.
        /// 2. Or, a method is authorized if the controller is decorated with the AuthorizeAttribute attribute, and
        /// the method is not decorated with the AllowAnonymousAttribute attribute.
        /// </summary>
        /// <param name="controller"></param>
        /// <param name="methodName"></param>
        /// <param name="methodTypes">Optional</param>
        /// <returns></returns>
        public static bool IsAuthorized(Controller controller, string methodName, Type[] methodTypes)
        {
            return GetMethodAttribute<AuthorizeAttribute>(controller, methodName, methodTypes) != null ||
                (GetControllerAttribute<AuthorizeAttribute>(controller) != null &&
                    GetMethodAttribute<AllowAnonymousAttribute>(controller, methodName, methodTypes) == null);
        }

        /// <summary>
        /// Check to see if a method requires authorization for the roles and users specified
        /// </summary>
        /// <param name="controller"></param>
        /// <param name="methodName"></param>
        /// <param name="methodTypes">Optional</param>
        /// <param name="roles"></param>
        /// <param name="users"></param>
        /// <returns></returns>
        public static bool IsAuthorized(Controller controller, string methodName, Type[] methodTypes, string[] roles, string[] users)
        {
            if (roles == null && users == null)
                return IsAuthorized(controller, methodName, methodTypes);

            if (!IsAuthorized(controller, methodName, methodTypes))
                return false;

            AuthorizeAttribute controllerAttribute = GetControllerAttribute<AuthorizeAttribute>(controller);
            AuthorizeAttribute methodAttribute = GetMethodAttribute<AuthorizeAttribute>(controller, methodName, methodTypes);

            // Check to see if all roles are authorized
            if (roles != null)
            {
                foreach (string role in roles)
                {
                    string lowerRole = role.ToLower();

                    bool roleIsAuthorized =
                        (controllerAttribute != null ?
                            controllerAttribute.Roles.ToLower().Split(',').Any(r => r == lowerRole) : false) ||
                        (methodAttribute != null ?
                            methodAttribute.Roles.ToLower().Split(',').Any(r => r == lowerRole) : false);

                    if (!roleIsAuthorized)
                        return false;
                }
            }

            // Check to see if all users are authorized
            if (users != null)
            {
                foreach (string user in users)
                {
                    string lowerUser = user.ToLower();

                    bool userIsAuthorized =
                        (controllerAttribute != null ?
                            controllerAttribute.Users.ToLower().Split(',').Any(u => u == lowerUser) : false) ||
                        (methodAttribute != null ?
                            methodAttribute.Users.Split(',').Any(u => u.ToLower() == lowerUser) : false);

                    if (!userIsAuthorized)
                        return false;
                }
            }

            return true;
        }

        private static T GetControllerAttribute<T>(Controller controller) where T : Attribute
        {
            Type type = controller.GetType();
            object[] attributes = type.GetCustomAttributes(typeof(T), true);
            T attribute = attributes.Count() == 0 ? null : (T)attributes[0];
            return attribute;
        }

        private static T GetMethodAttribute<T>(Controller controller, string methodName, Type[] methodTypes) where T : Attribute
        {
            Type type = controller.GetType();
            if (methodTypes == null)
            {
                methodTypes = new Type[0];
            }
            MethodInfo method = type.GetMethod(methodName, methodTypes);
            object[] attributes = method.GetCustomAttributes(typeof(T), true);
            T attribute = attributes.Count() == 0 ? null : (T)attributes[0];
            return attribute;
        }
    }
}

And here are some tests which utilize the AuthorizationTest class to validate whether the WidgetController actions will be correctly authorized based on the authorization attributes –

using BikeStore.Controllers;
using Framework.Tests.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Web.Mvc;

namespace BikeStore.Tests.Controllers
{
    [TestClass]
    public class WidgetControllerTests
    {
        [TestMethod]
        public void Index_IsAnonymous()
        {
            // Arrange
            WidgetController controller = new WidgetController();

            // Assert
            Assert.IsTrue(AuthorizationTest.IsAnonymous(
                controller,
                "Index",
                null));
        }

        [TestMethod]
        public void Details_IsAuthorized()
        {
            // Arrange
            WidgetController controller = new WidgetController();

            // Asset
            Assert.IsTrue(AuthorizationTest.IsAuthorized(
                controller,
                "Details",
                new Type[] { typeof(int) }));
        }

        [TestMethod]
        public void Create_Get_IsAuthorized()
        {
            // Arrange
            WidgetController controller = new WidgetController();

            // Assert
            Assert.IsTrue(AuthorizationTest.IsAuthorized(
                controller,
                "Create",
                null,
                new string[] { "Admin" },
                new string[] { "Ross" } ));
        }

        [TestMethod]
        public void Create_Post_IsAuthorized()
        {
            // Arrange
            WidgetController controller = new WidgetController();

            // Assert
            Assert.IsTrue(AuthorizationTest.IsAuthorized(
                controller,
                "Create",
                new Type[] { typeof(FormCollection) },
                new string[] { "Admin" },
                new string[] { "Ross" } ));
        }
    }
}

Populating Parameter Lists Using MDX

In a previous post I demonstrated how to surface SSAS data in an ASP.NET MVC website with Google charts.

If you have access to SQL Server Reporting Services (SSRS) then this should be your first port of call when considering how to incorporate charts in your website. It’s an enterprise level solution, providing enterprise level features for monitoring and optimizing report delivery. It also provides a comprehensive set of report authoring tools, and gives the report authors detailed control over most aspects of the reports, through the use of report expressions.

However, there a number of significant limitations with SSRS. Report parameters are restricted to Boolean, DateTime, Integer, Float, and Text. And the controls used to render the parameters are quite basic and cannot be customized. Also, interactivity is limited to drill-throughs and drill-downs. Business users are becoming accustomed to and are demanding rich user experiences. SSRS does not really offer the flexibility to meet these demands, hence the need to consider alternative solutions such as Google Charts.

What I want to do is take the next step from the previous post – where I populated Google graphs – by discussing how to populate parameter lists using MDX. These parameter lists can be rendered in an MVC view using one of a multitude of JQuery UI components, with the richness of the UI and interactivity being only limited by the developer ability and imagination.

The first steps is to query the database for the parameter captions and values. The following script does this for the [Data].[Calendar].[Calendar Year] set –

WITH
	MEMBER [Measures].[ParameterCaption] AS [Date].[Calendar].CURRENTMEMBER.MEMBER_CAPTION
	MEMBER [Measures].[ParameterValue] AS [Date].[Calendar].CURRENTMEMBER.UNIQUENAME
SELECT
	{ [Measures].[ParameterCaption], [Measures].[ParameterValue] } ON COLUMNS,
	{ [Date].[Calendar].[Calendar Year] } ON ROWS
FROM
	[Adventure Works]

Returning the following –

paramlist1

I will be using the above query for the rest of this post. However, if we require members from different levels we can additionally return the level ordinal. This can then be used to help us populate a tree-view for example. The following script does this for the [Date].[Calendar] set, and ensures that only the [All Periods], [Calendar Year], and [Month] levels are returned. Please note that I exclude the unwanted levels, rather than build a set using the wanted levels, so that the remaining members will be returned in the correct order –

WITH
	MEMBER [Measures].[ParameterCaption] AS [Date].[Calendar].CURRENTMEMBER.MEMBER_CAPTION
	MEMBER [Measures].[ParameterValue] AS [Date].[Calendar].CURRENTMEMBER.UNIQUENAME
	MEMBER [Measures].[ParameterLevel] AS [Date].[Calendar].CURRENTMEMBER.LEVEL_NUMBER
SELECT
	{	[Measures].[ParameterCaption],
		[Measures].[ParameterValue],
		[Measures].[ParameterLevel] } ON COLUMNS,
	{	[Date].[Calendar].MEMBERS
		- [Date].[Calendar].[Calendar Semester]
		- [Date].[Calendar].[Calendar Quarter]
		- [Date].[Calendar].[Date] } ON ROWS
FROM
	[Adventure Works]

Which returns –

paramlist2

I want to bind the results to a JQuery control in my view. And I will do this by retrieving the parameter data using a Ajax Get call – as I did to populate the charts in the previous post.

I therefore want a Controller action method as follows –

public JsonResult CalendarYears()
{
    Dictionary<string, string> data = ReportData.CalendarYears();
    return Json(data, JsonRequestBehavior.AllowGet);
}

The controller action method calls the ReportData.CalendarYears method to return a dictionary containing the key and value pairs for the parameter. It then serializes the dictionary into a Json response.

The ReportData.CalendarYears method is shown below –

using Microsoft.AnalysisServices.AdomdClient;
using System.Collections.Generic;
using System.Data;

namespace Charts.Reports
{
    public class ReportData
    {
        public static Dictionary<string, string> CalendarYears()
        {
            string command = @" WITH
                                MEMBER [Measures].[ParameterCaption] AS [Date].[Calendar].CURRENTMEMBER.MEMBER_CAPTION
                                MEMBER [Measures].[ParameterValue] AS [Date].[Calendar].CURRENTMEMBER.UNIQUENAME
                            SELECT
                                { [Measures].[ParameterCaption], [Measures].[ParameterValue] } ON COLUMNS,
                                { [Date].[Calendar].[Calendar Year] } ON ROWS
                            FROM
                                [Adventure Works]";

            return GetData(command).ToDictionary("[Measures].[ParameterValue]", "[Measures].[ParameterCaption]");
        }

        private static DataTable GetData(string command)
        {
            DataSet ds = new DataSet();

            using (AdomdConnection conn = new AdomdConnection("Data Source=localhost"))
            {
                conn.Open();
                using (AdomdCommand cmd = new AdomdCommand(command, conn))
                {
                    AdomdDataAdapter adapter = new AdomdDataAdapter(cmd);
                    adapter.Fill(ds);
                }
                conn.Close();
            }

            return ds.Tables[0];
        }
    }
}

The ReportData.CalendarYears method passes the MDX statement to the GetData method, which in turn connects to the database, executes the command, and fills a DataSet with the results. The GetData method then returns the first DataTable in the DataSet.

The ReportData.CalendarYears method converts the DataTable into a Dictionary<string, string> by calling a ToDictionary DataTable extension method. It specifies which columns should be used for the key and value data by specifying the respective column names.

Here’s the ToDictionary Datable extension method –

using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace Charts.Reports
{
    public static class DataTableExtensionMethods
    {

        public static Dictionary<string, string> ToDictionary(this DataTable dt, string keyColumnName, string valueColumnName)
        {
            Dictionary<string, string> dictionary = new Dictionary<string, string>();

            int keyColumn = dt.Columns.IndexOf(keyColumnName);
            int valueColumn = dt.Columns.IndexOf(valueColumnName);

            foreach (DataRow row in dt.Rows)
            {
                dictionary.Add(row[keyColumn].ToString(), row[valueColumn].ToString());
            }

            return dictionary;
        }
    }
}

I’m not going to demonstrate constructing the view. The view will request the parameter list data using an Ajax Get request. And when the user has made their selection, will use Ajax Post requests to get the chart data based on the selected key values. The selected key values will be used to construct a MDX query – in order to filter the results set. Here’s an example of such a query, which has a WHERE clause restricting the results to the years 2012 and 2013 –

SELECT
    NON EMPTY [Product].[Product Categories].[Category] ON ROWS,
    {[Measures].[Internet Sales Amount]} ON COLUMNS
FROM
    [Adventure Works]
WHERE
    {[Date].[Calendar].[Calendar Year].&[2012],[Date].[Calendar].[Calendar Year].&[2013]}

Surfacing SSAS Data With Google Charts

In this post I’m going to demonstrate how to surface SSAS data in an MVC view using ADOMD.NET and Google Charts.

This is what I’m going to create –

charts

1. ASP.NET MVC Project

First step is to create a new ASP.NET MVC Web Application project.

Then add a reference to the ADOMD.NET client components library (microsoft.analysisservices.adomdclient.dll).

Unfortunately, the library is not available through NuGet – don’t ask me why. Instead we need to download and install the SQL Server feature pack from the following link –

https://www.microsoft.com/en-gb/download/details.aspx?id=42295

And then reference the library in the following folder –

C:\Program Files\Microsoft.NET\ADOMD.NET\120\Microsoft.AnalysisServices.AdomdClient.dll

2. ReportData data-access class

Next step is to create a Data folder and then add the following data-access class  –

using Microsoft.AnalysisServices.AdomdClient;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace Charts.Data
{
    public class ReportData
    {
        public static List<object> SalesByYear()
        {
            string command = @" SELECT 
                                    NON EMPTY [Date].[Calendar].[Calendar Year] ON ROWS, 
                                    {[Measures].[Reseller Sales Amount], [Measures].[Internet Sales Amount]} ON COLUMNS
                                FROM 
                                    [Adventure Works]";

            return GetData(command);
        }

        public static List<object> InternetSalesByCategory()
        {
            string command = @" SELECT 
                                    NON EMPTY [Product].[Product Categories].[Category] ON ROWS, 
                                    {[Measures].[Internet Sales Amount]} ON COLUMNS
                                FROM 
                                    [Adventure Works]
                                WHERE
                                    [Date].[Calendar].[Calendar Year].&[2013]";

            return GetData(command);
        }

        public static List<object> ResellerSalesByCategory()
        {
            string command = @" SELECT 
                                    NON EMPTY [Product].[Product Categories].[Category] ON ROWS, 
                                    {[Measures].[Reseller Sales Amount]} ON COLUMNS
                                FROM 
                                    [Adventure Works]
                                WHERE
                                    [Date].[Calendar].[Calendar Year].&[2013]";

            return GetData(command);
        }

        private static List<object> GetData(string command)
        {
            DataSet ds = new DataSet();

            using (AdomdConnection conn = new AdomdConnection("Data Source=localhost"))
            {
                conn.Open();
                using (AdomdCommand cmd = new AdomdCommand(command, conn))
                {
                    AdomdDataAdapter adapter = new AdomdDataAdapter(cmd);
                    adapter.Fill(ds);
                }
                conn.Close();
            }

            return ConvertDataTableToObjectList(ds.Tables[0]);
        }

        private static List<object> ConvertDataTableToObjectList(DataTable dt)
        {
            List<object> data = new List<object>();
            int columnCount = dt.Columns.Count;

            string[] columnObject = new string[columnCount];
            for (int i = 0; i < columnCount; i++)
            {
                string name = dt.Columns[i].ColumnName.Replace(".[MEMBER_CAPTION]", "");
                string[] nameParts = name.Split(new char[] { '.' });

                columnObject[i] = nameParts[nameParts.Count() - 1].Replace("[", "").Replace("]", "");
            }
            data.Add(columnObject);

            foreach (DataRow row in dt.Rows)
            {
                data.Add(row.ItemArray);
            }

            return data;
        }
    }
}

There are 3 data-access methods, 1 per chart –

  1. SalesByYear
  2. InternetSalesByCategory
  3. ResellerSalesByCategory

Each of these methods defines an MDX statement, and then passes the statement to the GetData method.

The GetData method opens a connection to the SSAS database, and then executes the MDX statement, utilizing the AdomdDataAdapter.Fill method to populate a DataSet with the results.

The DataSet contains a collection of DataTable objects. The code assumes that the first in the collection is populated with the results from the query. I’m not sure under what circumstances there would be more than one DataTable?

The ConvertDataTableToObjectList method converts the results into a format that Google charts will understand, populating a list of objects with column names and values from the DataTable.

The column names contain dimension, hierarchy, level information. And additionally are surrounded by curly brackets, and may be suffixed with “‘[MEMBER_CAPTION]”. For example, here are the column names from the 1st query –

  • {[Date].[Calendar].[Calendar Year].[MEMBER_CAPTION]}
  • {[Measures].[Reseller Sales Amount]}
  • {[Measures].[Internet Sales Amount]}

The ConvertDataTableToObjectList strips off the superfluous information, leaving the level names –

  • Calendar Year
  • Reseller Sales Amount
  • Internet Sales Amount

3. Home Controller

using Charts.Data;
using System.Collections.Generic;
using System.Web.Mvc;

namespace Charts.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public JsonResult SalesByYear()
        {
            List<object> data = ReportData.SalesByYear();
            return Json(data, JsonRequestBehavior.AllowGet);
        }

        public JsonResult InternetSalesByCategory()
        {
            List<object> data = ReportData.InternetSalesByCategory();
            return Json(data, JsonRequestBehavior.AllowGet);
        }

        public JsonResult ResellerSalesByCategory()
        {
            List<object> data = ReportData.ResellerSalesByCategory();
            return Json(data, JsonRequestBehavior.AllowGet);
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

The following action methods have been added to the Home controller –

  • SalesByYear
  • InternetSalesByCategory
  • ResellerSalesByCategory

Each of the methods gets a List<object> containing the chart data from the appropriate data access method, and then returns this data serialized as JSON.

4.Home Index View

@{
    ViewBag.Title = "Home Page";
}

<h1>Google Charts Demo</h1>

<div class="row">
    <div class="col-md-12">
        <div class="chart"
             data-url="/Home/SalesByYear"
             data-chart-type="Line"
             data-options='{ "title": "Sales by Year", "vAxis": { "format": "$#,###" } }'>
        </div>
    </div>
</div>
<div class="row">
    <div class="col-md-6">
        <div class="chart"
             data-url="/Home/InternetSalesByCategory"
             data-chart-type="Pie"
             data-options='{ "title": "2013 Internet Sales by Category", "is3D": "true" }'>
        </div>
    </div>
    <div class="col-md-6">
        <div class="chart"
             data-url="/Home/ResellerSalesByCategory"
             data-chart-type="Pie"
             data-options='{ "title": "2013 Reseller Sales by Category", "is3D": "true" }'>
        </div>
    </div>
</div>

@section scripts
{
    <script type="text/javascript"
            src="https://www.google.com/jsapi?autoload={
            'modules':[{
              'name':'visualization',
              'version':'1',
              'packages':['corechart']
            }]
          }"></script>

    <script type="text/javascript">
        $().ready(function () {
            google.setOnLoadCallback(drawCharts);
        });

        function drawCharts() {
            var chart = $('div.chart');
            $.each(chart, drawChart);
        }

        function drawChart() {
            var $this = $(this);
            var url = $this.data('url');
            var chartType = $this.data('chart-type');
            var options = $this.data('options');
            var chartArea = $this[0];

            $.get(url)
             .done(function (jsonArray) {
                 var chart;
                 var data = google.visualization.arrayToDataTable(jsonArray);
                 var defaultOptions = {
                     title: 'Chart',
                     chartArea: { left: '100' },
                     height: '400'
                 };
                 $.extend(true, defaultOptions, options);

                 switch (chartType) {
                     case "Pie":
                         chart = new google.visualization.PieChart(chartArea);
                         break;
                     default:
                         chart = new google.visualization.LineChart(chartArea);
                 }

                 chart.draw(data, defaultOptions);
             });
        }
    </script>
}

After the DOM has loaded, the google.setOnLoadCallback(drawCharts) function is called, ensuring that the Google API is fully loaded prior to calling the drawCharts function.

The drawCharts function gets all <div> elements with the “chart” class, and for each calls the drawChart function.

The drawChart function interrogates the target <div> elements data- attributes to get the following information –

  • data-url
    • the controller action to call to get the data for the chart
  • data-chart-type
    • currently either “Pie” or “Line”, but can easily be extended to include other Google chart types.
  • data-options
    • A Json string representing an options object. Used to specify chart specific options.

The drawChart function then makes an Ajax get request to the url, specifying a callback function which receives the JSON result.

The callback function calls the google.visualization.arrayToDataTable function to convert the JSON result to a google.visualization.DataTable object.

It then uses the chart specific options to extend a default set of options, and then initializes either a Pie or Line chart before drawing the chart based on the data and the options.

And bobs your uncle.

 

 

Service Layer, MVC Controllers, and Dependency Injection

This is part 2 of a 3 part series –

1)  Unit of Work, Repository, Entity Framework, and Persistence Ignorance

2) Service Layer, MVC Controllers, and Dependency Injection

3) Unit Testing using Moq

In part 1, I discussed how we can enable persistence ignorance in our MVC architecture through the implementation of the Unit of Work and Repository design patterns. And in so doing we can improve the maintainability and extensibility of our architecture by loosely coupling the controllers and the persistence layer. The controllers are not dependent on a specific data-access technology. We can switch technologies without needing to change any controller coding or references. And we can more easily isolate the controllers when performing unit testing.

With MVC, when a request is received it is routed to a controller action method. The action method interprets the user input, determines what logic to perform, and what action result to return. Having all application logic contained in the controller actions works well for small applications. However, when an application grows in complexity we may find that our action methods become quite complex and lengthy. Also, we may find that application logic gets duplicated if it is used in more than one controller.

For example, if we are building a store application then we may have to accommodate the following logic when the customer has completed the checkout process and confirms the placement of an order –

  1. Validate the order details
  2. Change the status of the order to confirmed
  3. Generate an order confirmation reference
  4. Send an order confirmation email to the customer, including the reference
  5. Empty the customer’s basket
  6. Assign the order to the order processing pipeline
  7. Return a confirmation view as a response to the request, including the reference

This translates into very many lines of code, and so the action method will be quite lengthy. With even moderately complex applications, our controllers soon become bloated and difficult to maintain.

Also, step 5 above involves emptying the customer’s basket. In addition, to the checkout process, we may have a BasketController with an “Empty” action which may respond to the user clicking on an Empty Basket link. This specific piece of logic is therefore required in more than one controller.

We will therefore benefit from refactoring the code to make it more modular and easier to understand. 

We can create one or more helper classes in our MVC project, and create helper methods that can be utilized by the controller actions.

A more extensible way of refactoring the code is to attempt to separate the logic into presentation logic and business logic layer. Keep the presentation logic in the controller actions, and accompanying helper classes where required. But move the business logic into a separate class library. By separating the business logic from the UI, we are able to implement alternative UIs – a Web API for example – that utilize the same logic. We are adhering to the single responsibility principle, and as such we are making our code easier to understand, maintain and extend.

What I am going to outline in this post is how to implement a business logic layer. I will refer to the layer from now on as a service layer. I think this is a good pragmatic description, although there is a lot of debate on whether this should be called a service layer, or business logic layer, or even application domain layer. There is further debate on the definition of application domain, which leads into debate on whether the business logic should be encapsulated with the model. Bottom line is that there is more than one way to skin a cat. I don’t really care about the semantics. What I care about is having a set of workable patterns that are easy to understand, implement, extend, and maintain.

To quote Fowler, a Service Layer –

Defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation.

Enterprise applications typically require different kinds of interfaces to the data they store and the logic they implement: data loaders, user interfaces, integration gateways, and others. Despite their different purposes, these interfaces often need common interactions with the application to access and manipulate its data and invoke its business logic. The interactions may be complex, involving transactions across multiple resources and the coordination of several responses to an action. Encoding the logic of the interactions separately in each interface causes a lot of duplication.

The implementation of the Service Layer pattern that I use is pretty much identical to the implementation outlined in the following guide by Stephen Walther –

http://www.asp.net/mvc/overview/older-versions-1/models-%28data%29/validating-with-a-service-layer-cs

In particular I have adopted the use of the ModelStateWrapper class to pass business logic errors from the service layer to the client.  There are some small differences, which I will outline below.

IService

The first thing is to create a contract for the service layer classes. IService is this contract, and contains a single method Initialize, which accepts an instance of a class that implements IValidationDictionary. It is this class that will be used to pass business logic validation errors back from the service layer to the client – MVC controller in my case.

namespace Framework.Service
{
    public interface IService
    {
        void Initialize(IValidationDictionary validationDictionary);
    }
}

IValidationDictionary

namespace Framework.Service
{
    public interface IValidationDictionary
    {
        void AddError(string key, string errorMessage);
        bool IsValid { get; }
    }
}

ValidationDictionary

using Framework.Service;
using System.Web.Mvc;

namespace Framework.Mvc
{
    public class ValidationDictionary : IValidationDictionary
    {
        private ModelStateDictionary _modelState;

        public ValidationDictionary(ModelStateDictionary modelState)
        {
            _modelState = modelState;
        }
        public void AddError(string key, string errorMessage)
        {
            _modelState.AddModelError(key, errorMessage);
        }

        public bool IsValid
        {
            get { return _modelState.IsValid; }
        }
    }
}

Validation dictionary is an MVC specific implementation of IValidationDictionary. It has a constructor that accepts an instance of System.Web.Mvc.ModelStateDictionary. The service layer can call the AddModelError method to add business logic validation errors to this dictionary. MVC can then utilize this dictionary to pass these errors back to the view – as you will see later.

An alternative method for communicating business validation errors back to controller, is to derive a ValidationException class from System.Exception, and have the service raise this exception when a validation error occurs. The controller action will Catch this exception and then deal with it appropriately. This is widely adopted, but I’ve always followed the practice of only utilizing exceptions for ‘exceptional’ not-expected occurrences. I’ve seen too many applications in my time where developers have attempted to use exceptions for implementing logic, and then ended up obnubilating the sources of real errors.

IOrderService

using BikeStore.Model;
using Framework.Service;
using System;
using System.Collections.Generic;

namespace BikeStore.Service
{
    public interface IOrderService : IService
    {
        string CreateOrder(int customerId, int basketId, int customerAddressId, int paymentCardId);
        List<Order> GetOrdersByCustomer(int customerId);
    }
}

I create an interface for each service, in part to help me mock the services when unit testing controllers.

IOrderService contains IService. This is to ensure that any concrete implementation of IProductService implement the Initialize method. If I was sure that all implementations of IProductService were to utilize unmanaged resources then I could also have IOrderService contain IDisposible. However, this may not always be the case, so I have not done this. Instead I have implemented IDisposible directly in the concrete OrderService class.

The IOrderService code shown above contains 2 methods, CreateOrder and GetOrdersByCustomer. In practice it will contain many more methods, including CRUD methods that do not include any business logic. These methods will simply pass through the calls to the respective repository. There may be service instances that only contain these pass-through calls, and do not include any business logic at all. Arguably there is some redundancy in this case. But I prefer to have this redundancy rather than have the client code directly referencing the repositories and unit of work. By having the controllers always dealing with service classes, and never with the repositories or unit of work, we are being consistent throughout the application. Also we are adding extensibility points into our design, hence we are future proofing the application.

OrderService

using BikeStore.Data;
using BikeStore.Model;
using Framework.Service;
using System;
using System.Collections.Generic;
using System.Linq;

namespace BikeStore.Service
{
    public class OrderService : IOrderService
    {
        private IBikeStoreUnitOfWork _unitOfWork;
        private IValidationDictionary _validatonDictionary;

        #region Constructors and Initializers
        public OrderService(IBikeStoreUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public void Initialize(IValidationDictionary validationDictionary)
        {
            _validatonDictionary = validationDictionary;
        }
        #endregion

        #region Order
        public string CreateOrder(int customerId, int basketId, int customerAddressId, int paymentCardId)
        {
            // Validate payment details
            PaymentCard paymentCard = _unitOfWork.PaymentCardRepository.Get(pc => pc.PaymentCardId == paymentCardId).First();

            if (paymentCard.ExpiryDate > DateTime.Now)
            {
                _validatonDictionary.AddError("PaymentCard", "The payment card has expired.");
                return null;
            }
            else
            {
                // Process the order
                .......
                return order.OrderReference; 

            }
        }

        public IEnumerable<Order> GetOrdersByCustomer(int customerId)
        {
            return _unitOfWork.OrderRepository.Get(o => o.CustomerId == customerId);
        }
        #endregion

        #region Dispose
        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _unitOfWork.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}

OrderService implements IOrderService, and also IDisposible as it utilizes BikeStoreUnitOfWork, which is disposible.

BikeStoreUnitOfWork is injected into the constructor – using Unity in my case – as discussed below.

I have implemented only part of the CreateOrder method, but enough to show how business validation errors can be added to the ValidationDictionary instance. In this case I am checking to see if the payment card has expired. If yes, then an appropriate error is added to the dictionary, and the method returns null.

CheckOutController

using AutoMapper;
using BikeStore.Helpers;
using BikeStore.Service;
using Framework.Mvc;
using System.Web.Mvc;

namespace BikeStore.Controllers
{
    [Authorize]
    public class CheckOutController : Controller
    {
        private IOrderService _orderService;

        public CheckOutController(IOrderService orderService)
        {
            _orderService = orderService;
            _orderService.Initialize(new ValidationDictionary(this.ModelState));
        }

        // POST: CheckOut/Payment
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Payment([Bind(Include = "customerAddressId,paymentCardId")] int customerAddressId, int paymentCardId)
        {
            if (ModelState.IsValid)
            {
                // Create Order (includes sending confirmation email)
                string orderRef = _orderService.CreateOrder(GetCustomer().CustomerId, (int)BasketHelper.GetBasketId(this.ControllerContext), customerAddressId, paymentCardId);

                if (orderRef == null)
                    return View();
                else
                    return View("Confirm");
            }

            return View();
        }

        // Dispose
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _orderService.Dispose();
                _mapper.Dispose();

            }
            base.Dispose(disposing);
        }
    }
}

CheckOutController has an instance of OrderService injected into it’s constructor. The constructor calls the OrderService.Initialize method, and passes in a ValidationDictonary which in turn contains the controller’s ModelState object. Validation errors are therefore added directly into the ModelState object.

The Payment action method call the OrderService.CreateOrder method. If there is a validation error, then the CreateOrder method returns null, the action method responds with the Payment view, passing the ModelState to the view.

Depedency Injection

I use Unity to inject instances of IBikeStoreUnitOfWork into the service classes constructors, and to inject instances of the service classes into the controllers.

I outline the steps required to set up Unity in the following blog post – https://datatellblog.wordpress.com/2015/02/05/asp-net-5-vnext-dependency-injection/