jqGrid and ASP.NET MVC - Configuration Import/Export

Today I'm going to write few words about configuration import and export in jqGrid. This feature can be very helpful, if you wish to allow your users with some customization (in our example we will add column chooser, so users can influence what they see).
We should start by adding standard placeholders for the grid:
<table id="jqgProducts" cellpadding="0" cellspacing="0"></table>
<
div id="jqgpProducts"></div>

Now we will write all necessary javascript:
<link href="../../Content/jquery-ui-1.7.2.custom.css" rel="stylesheet" type="text/css" />
<
link href="../../Content/ui.jqgrid.css" rel="stylesheet" type="text/css" />
<
link href="../../Content/ui.multiselect.css" rel="stylesheet" type="text/css" />
<
script src="/Scripts/grid.locale-en.js" type="text/javascript"></script>
<
script src="/Scripts/jquery.jqGrid.min.js" type="text/javascript"></script>
<
script src="/Scripts/ui.multiselect.js" type="text/javascript"></script>
<
script type="text/javascript">
  $(document).ready(function() {
    //Calling jqGridImport, which will import configuration
    $('#jqgProducts').jqGridImport({
      //We are expecting data in JSON format
      imptype: 'json',
      //URL to our action, which will return configuration
      impurl: '<%=Url.Action("ProductsGridConfiguration", "Home") %>',
      //Method for the request
      mtype: 'GET',
      //Addidtional data, which will be passed to our action
      impData: { id: 'jqgProducts' },
      //We inform the grid which property in object will hold configuration
      jsonGrid: { config: 'Settings' },
      //Function which will be called when import ends
      importComplete: function() {
        //We set up navigator wihout any standard buttons
        $('#jqgProducts').jqGrid('navGrid', '#jqgpProducts', {
          edit: false,
          add: false,
          del: false,
          search: false
        //because we will add our own button
        }).jqGrid('navButtonAdd', '#jqgpProducts', {
          //Our button text
          caption: 'Columns',
          //Our button icon (jQuery UI class)
          buttonicon: 'ui-icon-wrench',
          //Our button position
          position: 'last',
          //Our button tooltip
          title: 'Select columns',
          //Function which will be called on click
          onClickButton: function() {
            //We are showing column chooser
            $('#jqgProducts').jqGrid('columnChooser', {
              //Function which will be called when column chooser is closed
              done: function(perm) {
                //We check if user has accepted
                if (perm) {
                  //First we are resizing grid
                  var gridWidth = this.jqGrid('getGridParam', 'width');
                  this.jqGrid('setGridWidth', gridWidth);
                  //then we remap columns
                  this.jqGrid('remapColumns', perm, true);
                  //and in the end we want to post configuration
                  $.ajax({
                    type: 'POST',
                    //We are posting configuration in JSON
                    contentType: 'application/json; charset=utf-8',
                    //URL to our action, which store configuration
                    url: '<%=Url.Action("ProductsGridConfiguration", "Home") %>',
                    //Calling jqGridExport for our data
                    data: $('#jqgProducts').jqGridExport({
                      //We are exporting in JSON
                      exptype: 'jsonstring',
                      //We inform the grid what should be the name of property
                      root: 'Settings'
                    })
                  });
                }
              }
            });
          }
        });
      }
    });
  });
</script>

That's a lot of javascript. I hope that comments make it clear enough. We are exporting in JSON but also XML can be used as an alternative. There is also a possibility to import some data with configuration, but I won't dig into this at the moment.
Before we start writing our actions, we have to prepare a model binder for JSON, otherwise we won't be able to accept configuration:
/// <summary>
///
Model binder which allows binding JSON data to objects
/// </summary>
public class JsonModelBinder : IModelBinder
{
  #region IModelBinder Members
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    if (controllerContext == null)
      throw new ArgumentNullException("controllerContext");
    if (bindingContext == null)
      throw new ArgumentNullException("bindingContext");

    var serializer = new DataContractJsonSerializer(bindingContext.ModelType);
    return serializer.ReadObject(controllerContext.HttpContext.Request.InputStream);
  }
  #endregion
}

Now we can prepare classes for storing jqGrid configuration:
/// <summary>
///
jqGrid configuration container
/// It can have two properties:
/// - one for settings
/// - one for data
/// </summary>
[DataContract]
[ModelBinder(typeof(JsonModelBinder))]
public sealed class JqGridConfiguration
{
  #region Properties
  [DataMember]
  public JqGridSettings Settings { get; set; }
  #endregion
}

/// <summary>
///
jqGrid settings container
/// Description for every option is available here:
/// http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options
/// Not all jqGrid options are included in this class (it can be extended)
/// </summary>
[DataContract]
public sealed class JqGridSettings
{
  #region Properties
  [DataMember]
  public JqGridColumnModel[] colModel { get; set; }

  ...
  #endregion

  ...
}

/// <summary>
///
jqGrid colModel options
/// Description for every option:
/// http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options
/// Not all jqGrid colModel options are included in this class (it can be extended)
/// </summary>
[DataContract]
public sealed class JqGridColumnModel
{
  ...
}

As you can see, I have skipped most of JqGridSettings and JqGridColumnModel implementation. Those classes contain only properties which represent jqGrid options.
Finally, we can write controller actions. Data will be provided by the action written in basics sample, so I will focus on actions which import and export configuration:
/// <summary>
///
Exports configuration for jqGrid in json
/// </summary>
/// <param name="id">
The jqGrid identifier</param>
/// <returns>The jqGrid configuration in json format</returns>
[NoCache]
public JsonResult ProductsGridConfiguration(string id)
{
  JqGridConfiguration configuration = null;
  //Do we already have configuration in Session
  if (Session[id] != null)
    //If yes, the use it
    configuration = (JqGridConfiguration)Session[id];
  else
    //If no, then create one
    configuration = new JqGridConfiguration()
    {
      Settings = new JqGridSettings
      {
        id = id,
        colNames = new string[] { "ProductID", "ProductName", "Supplier", "Category", "QuantityPerUnit", "UnitPrice", "UnitsInStock" },
        colModel = new JqGridColumnModel[]
        {
          new JqGridColumnModel() { name = "ProductID", align = "left" },
          new JqGridColumnModel() { name = "ProductName", align = "left" },
          new JqGridColumnModel() { name = "Supplier", align = "left" },
          new JqGridColumnModel() { name = "Category", align = "left" },
          new JqGridColumnModel() { name = "QuantityPerUnit", align = "left" },
          new JqGridColumnModel() { name = "UnitPrice", align = "left" },
          new JqGridColumnModel() { name = "UnitsInStock", align = "left" }
        },
        remapColumns = new int[] { 0, 1, 2, 3, 4, 5, 6 },
        rowNum = 10,
        sortname = "ProductID",
        sortorder = "asc",
        viewrecords = true
      }
    };

  //Those settings should be set always "just in case"
  configuration.Settings.url = Url.Action("ProductsGridData", "Home");
  configuration.Settings.datatype = "json";
  configuration.Settings.mtype = "GET";
  configuration.Settings.pager = "#jqgpProducts";
  configuration.Settings.width = "auto";
  configuration.Settings.height = "auto";

  //Return configuration
  return Json(configuration);
}

/// <summary>
///
Imports configuration for jqGrid
/// </summary>
/// <param name="configuration">
The jqGrid configuration</param>
/// <returns>An empty result</returns>
[AcceptVerbs(HttpVerbs.Post)]
public EmptyResult ProductsGridConfiguration(JqGridConfiguration configuration)
{
  //Store configuration in Session
  Session[configuration.Settings.id] = configuration;

  //Return empty result, it will be ignored
  return new EmptyResult();
}

Now we can go ahead and test it. You can download sample application from here, I hope you will find it useful.

Asynchronous Form in ASP.NET MVC

This time I will create an asynchronous form with validation (using jQuery Validation plugin for client-side) and AntiForgeryToken. I haven't dig into new ASP.NET MVC 2 model validation features yet, so the sample will not use any of them (when I take a closer look at them I might write a new version of this post). You should also know, that this solution doesn't perfectly follow DRY principle - we will have to write validation rules twice (this is where ASP.NET MVC 2 might make the biggest difference).
We will start by creating the form:
<div id="dvRegister">
  <% using (Html.BeginForm("AsynchronousForm", "Home", FormMethod.Post, new { id = "frmRegister" })) { %>
    <%= Html.AntiForgeryToken() %>
    <div>
      <fieldset>
        <legend>Account Information</legend>
        <p>
          <label for="userName">Username:</label>
          <%= Html.TextBox("UserName") %>
        </p>
        <p>
          <label for="email">Email:</label>
          <%= Html.TextBox("Email") %>
        </p>
        <p>
          <label for="password">Password:</label>
          <%= Html.Password("Password") %>
        </p>
        <p>
          <label for="confirmPassword">Confirm password:</label>
          <%= Html.Password("ConfirmPassword") %>
        </p>
        <p>
          <input type="submit" value="Register" />
        </p>
      </fieldset>
    </div>
  <% } %>
</div>

Someone may ask why I'm not using Ajax.BeginForm. That's because it doesn't work out-of-the-box with jQuery validation. We would have to add some code, so the plugin can prevent form from submitting. Instead of that we will handle submitting within validation plugin and avoid using two javascript libraries:
<script src="<%= Url.Content("~/Scripts/jquery-1.3.2.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script type="text/javascript">
  $(document).ready(function() {
    $("#frmRegister").validate({
      rules: {
        UserName: {
          required: true,
          minlength: 5,
          remote: '<%=Url.Action("ValidateUserName", "Home") %>'
        },
        Email: {
          required: true,
          email: true
        },
        Password: {
          required: true,
          remote: '<%=Url.Action("ValidatePassword", "Home") %>'
        },
        ConfirmPassword: {
          required: true,
          equalTo: "#Password"
        }
      },
      messages: {
        UserName: {
          required: "Please enter username",
          minlength: $.format("Please enter at least {0} characters"),
          remote: $.format("{0} is already in use")
        },
        Email: {
          required: "Please enter email address",
          email: "Please enter valid email address"
        },
        Password: {
          required: "Please enter password"
        },
        ConfirmPassword: {
          required: "Please repeat password",
          equalTo: "Please enter the same password as above"
        }
      },
      submitHandler: function() {
        var registerData = $("#frmRegister").serialize();
        $.ajax({
          type: 'POST',
          url: '<%=Url.Action("AsynchronousForm", "Home") %>',
          dataType: 'json',
          data: registerData,
          success: function(registerResult) {
            if (registerResult.Success) {
              $('#dvRegister').empty().text('User successfully registered.');
            }
            else {
              var errorsContainer = $('#registerErrors');
              if (errorsContainer.length > 0) {
                errorsContainer.empty();
              }
              else {
                $('#frmRegister').after('<ul id="registerErrors" class="validation-summary-errors"></ul>');
                errorsContainer = $('#registerErrors');
              }
              for (error in registerResult.Errors) {
                errorsContainer.append('<li>' + registerResult.Errors[error] + '</li>');
              }
            }
          }
        });
      }
    });
  });
</script>

Let's take a look at validate options.
First we define validation rules for our inputs. The complete list of available rules can be found here. All rules are pretty obvious.
Next we set error messages. Two things are worth mentioning here:
- in some cases you can use additional parameters to create your message (like for remote and minlength in our sample)
- for remote validators, you can return message from server (we will do that for password)

Last thing we provide is submitHandler. This is where we make our form asynchronous. To achieve this, we will use jQuery.ajax. First we serialize our form. Then we set parameters for our request (type, url, dataType, data) and function which will be called when the request succeed. We should use this function for providing some feedback to user. In our sample we do one of two things. If operation was successful, the form is cleared and user gets success message. If there was an error on the server, we create the same markup as Html.ValidationSummary would.
Now we should add some controller actions:
/// <summary>
///
Handles the initial GET request
/// </summary>
/// <returns>
AsynchronousForm view</returns>
public ViewResult AsynchronousForm()
{
  return View();
}

/// <summary>
///
Handles the asynchronous POST request
/// </summary>
/// <param name="user">
The user data from the form</param>
/// <returns>
SubmitResult</returns>
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public JsonResult AsynchronousForm(UserRegisterData user)
{
  SubmitResult result = new SubmitResult() { Success = true };

  List<string> validationErrors = user.Validate(_usersRepository);
  if (validationErrors.Count == 0)
  {
    try
    {
      _usersRepository.RegisterUser(user);
    }
    catch (Exception ex)
    {
      result.Success = false;
      result.Errors = new string[] { ex.Message };
    }
  }
  else
  {
    result.Success = false;
    result.Errors = validationErrors.ToArray();
  }
  return Json(result);
}

/// <summary>
///
Validates username
/// </summary>
/// <param name="UserName">
The username</param>
/// <returns>
true or false</returns>
public JsonResult ValidateUserName(string UserName)
{
  return Json(_usersRepository.ValidateUserNameUnique(UserName));
}

/// <summary>
///
Validates password
/// </summary>
/// <param name="Password">
The password</param>
/// <returns>
true or error message</returns>
public JsonResult ValidatePassword(string Password)
{
  string passwordInvalidMessage = _usersRepository.ValidatePassword(Password);
  if (String.IsNullOrEmpty(passwordInvalidMessage))
    return Json(true);
  else
    return Json(passwordInvalidMessage);
}

Action for initial GET request is very simple. Validation actions aren't too complicated either. You should only notice, that ValidatePassword action returns true if password is valid, or error message if not. The real place of interest is action for our asynchronous POST request. ASP.NET MVC will put our post data into UserRegisterData object:
public class UserRegisterData
{
  public string UserName { get; set; }
  public string Email { get; set; }
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }

  public List<string> Validate(IUsersRepository repository)
  {
    List<string> validationErrors = new List<string>();

    if (String.IsNullOrEmpty(UserName) || UserName.Trim().Length == 0)
      validationErrors.Add("Username is required");
    else if (UserName.Length < 5)
      validationErrors.Add("Username should have at least 5 characters");
    else if (!repository.ValidateUserNameUnique(UserName))
      validationErrors.Add(String.Format("Username {0} is already in use", UserName));

    if (String.IsNullOrEmpty(Email) || Email.Trim().Length == 0)
      validationErrors.Add("Email is required");
    else if (!Regex.IsMatch(Email, @"^...$"))
      validationErrors.Add("Email is invalid");

    if (String.IsNullOrEmpty(Password) || Password.Trim().Length == 0)
      validationErrors.Add("Password is required");
    else
    {
      string passwordInvalidMessage = repository.ValidatePassword(Password);
      if (!String.IsNullOrEmpty(passwordInvalidMessage))
        validationErrors.Add(passwordInvalidMessage);
    }

    if (String.IsNullOrEmpty(ConfirmPassword) || ConfirmPassword.Trim().Length == 0)
      validationErrors.Add("Confirm password is required");
    else if (!ConfirmPassword.Equals(Password))
      validationErrors.Add("Confirm password does not match password");

    return validationErrors;
  }
}

As you can see, we were forced to repeat our validation logic (you can never trust client-side validation). After validating our data and performing required operation, we send SubmitResult object in response (this is the object we are using in our submitHandler):
public class SubmitResult
{
  /// <summary>
  ///
Success flag
  /// </summary>
  public bool Success { get; set; }

  /// <summary>
  ///
Errors array
  /// </summary>
  public string[] Errors { get; set; }
}

So our asynchronous form is ready. Of course everything I have showed is just an idea, and you can do it completely different. I only wanted to show you the possibility. As I said in the beginng, I will try to return to this subject after taking a deep look into ASP.NET MVC 2 model validation.

Asynchronous TreeView in ASP.NET MVC

Today I'm going to show how to create a completely asynchronous TreeView in ASP.NET MVC. To achieve this I will use jQuery TreeView plugin from bassistance.de. General purpose of this plugin is converting unordered list into an expandable and collapsible tree. I'm not going to write about converting static list, because that is really straightforward. I will focus on asynchronous features of this plugin.
We will start by referencing necessary files:
<head>
  <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
  <link href="../../Content/jquery.treeview.css" rel="stylesheet" type="text/css" />
  <script src="/Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
  <script src="/Scripts/jquery.treeview.min.js" type="text/javascript"></script>
  <script src="/Scripts/jquery.treeview.async.js" type="text/javascript"></script>
</head>

Now we need to add placeholder (an empty unordered list) for TreeView:
<ul id="trFileBrowser"></ul>
After that we can write initialization javascript:
<script type="text/javascript">
  $(document).ready(function() {
    $('#trFileBrowser').treeview({
      url: '/Home/FileBrowserData/'
    })
  });
</script>

Let's move on to controller action. TreeView makes a GET request with one parameter: root.
public ActionResult FileBrowserData(string root)
{
}

If the request comes from empty TreeView root has a constant value 'source', otherwise it contains id of expanding node:
public ActionResult FileBrowserData(string root)
{
  DirectoryInfo rootDirectory = null;
  if (root == "source")
    rootDirectory = new DirectoryInfo(@"D:\Books\");
  else
    rootDirectory = new DirectoryInfo(root);
  var directoryChildren = from child in rootDirectory.GetFileSystemInfos()
                          orderby child is DirectoryInfo descending
                          select child;
}

In the response we should return an array of objects representing TreeView nodes. I have prepared a TreeViewNode class (it serves better for presentation purposes) but you are free to use an anonymous type with only those properties which you need (basically only the text property is required):
/// <summary>
/// jQuery TreeView plugin node
/// </summary>
public class TreeViewNode
{
  ...

  #region Properties
  /// <summary>
  /// Node identifier
  /// </summary>
  public string id
  {
    ...
  }

  /// <summary>
  /// Node text
  /// </summary>
  public string text
  {
    ...
  }

  /// <summary>
  /// Whether node is expanded
  /// </summary>
  public bool expanded
  {
    ...
  }

  /// <summary>
  /// Whether node has children
  /// </summary>
  public bool hasChildren
  {
    ...
  }

  /// <summary>
  /// CSS classes for node
  /// </summary>
  public string classes
  {
    ...
  }

  /// <summary>
  /// Node children's
  /// </summary>
  public TreeViewNode[] children
  {
    ...
  }
  #endregion
}

We can finish our controller action:
public ActionResult FileBrowserData(string root)
{
  DirectoryInfo rootDirectory = null;
  if (root == "source")
    rootDirectory = new DirectoryInfo(@"D:\Books\");
  else
    rootDirectory = new DirectoryInfo(root);
  var directoryChildren = from child in rootDirectory.GetFileSystemInfos()
                          orderby child is DirectoryInfo descending
                          select child;

  List nodes = new List();
  foreach (FileSystemInfo directoryChild in directoryChildren)
  {
    bool isDirectory = directoryChild is DirectoryInfo;
    nodes.Add(new TreeViewNode() {
      id = directoryChild.FullName,
      text = directoryChild.Name,
      classes = isDirectory ? "folder" : "file",
      hasChildren = isDirectory
    });
  }

  return Json(nodes.ToArray());
}

Once again you can take a look at my Books directory:

Source code can be downloaded here