Using Interfaces for ASP.NET MVC Model Binding

Sometimes you may want to support view reuse, where a partial view displays a portion of the application in a reusable fashion. Suppose you had the following entity which has a bunch of properties.

public class Customer
{
   public int CustomerID { get; set; }

   public string FirstName { get; set; }

   public string LastName { get; set; }

   public string AccountNumber { get; set; }

   .
   .
}

Suppose you wanted to use a PartialView because it was an AJAX widget with a JavaScript initialization script, and defining a reusable control made it really easy to support this. MVC defines a strongly typed model reference, so does that mean we have to bind the model directly to Customer, or define a custom type for an editor template?

We actually can use interfaces by adding the following:

public interface IAccountEntity
{
   string AccountNumber { get; set; }
}

We can define this in a partial view:

@model IAccountEntity

@Html.TextBoxFor(i => i.AccountNumber)

   $(function() {
      /* Registration Script */
   });

And then use it in our EditCustomer view, as a partial with the existing model:

@Html.Partial("EditAccountNumber", Model)

Since Model (of type Customer) is also IAccountEntity, this translates just fine with MVC. It works just as well when you use hierarchies too. For instance, Customer implements IAccountEntity, but what if we had a CustomerModel class above Customer like:

CustomerModel
     Customer : IAccountEntity
        AccountNumber

If EditCustomer view looks like the following:

@model CustomerModel
.
.

<div class="form-group">
   <label class="col-md-2 control-label">First Name</label>
   <div class="col-md-10">
       @Html.TextBoxFor(Function(i) i.Customer.FirstName)
   </div>
</div>
<div class="form-group">
   <label class="col-md-2 control-label">Last Name</label>
   <div class="col-md-10">
       @Html.TextBoxFor(Function(i) i.Customer.LastName)
   </div>
</div>
<div class="form-group">
   <label class="col-md-2 control-label">AccountNumber</label>
   <div class="col-md-10">
       @Html.Partial("EditAccountNumber", Model)
   </div>
</div>

Notice how the model is passed in directly; this won’t work for our scenario, because the EditAccountNumber expects an IAccountEntity, and requires that the partial be passed in this way:

@Html.Partial("EditAccountNumber", Model.Customer)

However, that doesn’t work for our needs, if our controller has a post operation of the following:

[HttpPost]
ActionResult Edit(CustomerModel model)
{
    if (string.IsNullOrEmpty(model.AccountNumber))
    {
      //This will always be the case, why?  See below
    }
}

Welcome to one of the challenges of MVC model binding. In the previous form, the first/last name and account number would post back to the server something like:

Customer__FirstName: BOB
Customer__LastName: SMITH
AccountNumber: ABC123

What needs to happen is the “AccountNumber” key also needs to be Customer__AccountNumber for it to successfully bind back to the server. You can add AccountNumber as a parameter to the post method, or we can add another interface that supports our hierarchy, and add this interface to the CustomerModel:

//Interface definition
public interface IAccountNumberModel
{
    IAccountNumberEntity Customer { get; set; }
}

//Update to the model class
public class CustomerModel : IAccountNumberModel
{

   public Customer Customer { get; set; }

   IAccountNumberEntity IAccountNumberModel.Customer { get; set; }

}

Here IAccountNumberModel has the same property name as CustomerModel; that’s important to make sure the model binding name structure matches. Now we can pass in the CustomerModel to the EditAccountNumber view with these modifications:

//EditAccountNumber
@model IAccountNumberModel

@Html.TextBoxFor(i => i.Customer.AccountNumber)
.
.

//EditCustomer:
.
.
@Html.Partial("EditAccountNumber", Model)

Since CustomerModel implements IAccountNumberModel, and this matches the type of model on our partial view, everything binds correctly.

Advertisements

ASP.NET MVC Seamless Routing Encryption

There are certain pages in the application where the data passed in must be encrypted in a GET request. This could be a link redirection from an email or another site, or just an internal redirect where the parameters can’t get exposed. A link like this may actually look like:

/Some/Action?enc=345SFIPOWHG:LNNJE:LKJWET46-DSFSF:KJ

The data is nonsensical to the user, but contains identifying information about the user. For instance, the encrypted data may have the following information:

– User ID
– Response Identifier
– Response (Yes/No, Agree/Disagree, text description, whatever else)

The system can then check that the user logged in matches the user in the URL request. It can lookup the associated response database record, and record the response provided. This feature is not native to ASP.NET MVC, but is relative easy to build in. This post will illustrate how to do so, using the following steps:

1) The first step is to create an action method to define a URL. We’ll want to create two forms: one method as a redirection, and one method as an URL generation. Both of these will be extension methods on different objects:

private static string GetEncryptedData(object args)
{
    var encryption = new EncryptionService();
    var list = new RouteValueDictionary(args);
    var items = new List();

    foreach (var entry in list)
    {
         items.Add(entry.Key + "=" + (!string.IsNullOrEmpty(entry.Value) ? entry.Value : ""));
    }

    return encryption.Encrypt(String.Join("&", items));
}

public static ActionResult RedirectToActionSecure(this ControllerBase controller,
                                                  string action, 
                                                  string controller, 
                                                  object args)
{
    var encText = GetEncryptedData(args);
    return controller.RedirectToAction(action, controller, new { enc = encText });
}

public static IHtmlString ActionSecure(this UrlHelper url, 
                                       string action, 
                                       string controller, 
                                       object args)
{
    var encText = GetEncryptedData(args);
    return url.Action(action, controller, new { enc = encText });
}

Certainly, you can create overloads without the action and/or controller too, which is easy to do. Additionally, this code should do more null checking; it assumes that the data exists. The first step is get a reference to the encryption service you are using for your application. I’m leaving that out of this post. Using this service, a querystring is generated and then encrypted together. The value is stored in the “enc” querystring value when routed. This looks like in the code:

public ActionResult X()
{
   return RedirectToActionSecure("Action", "ctlr", new { a = 1, b = 2 });
}

Or in the view:

@Url.ActionSecure("Action", "ctlr", new { a = 1, b = 2 });

When a request is made to that controller and action, MVC checks for the “enc” querystring parameter. It does this in an Action filter attribute, on ActionExecuting.

public class EncryptionActionAttribute : ActionFilterAttribute
{
   public override void OnActionExecuting(ActionExecutingContext context)
   {
        var encryption = new EncryptionService();

       // Step 1: parse the URL
       var url = context.HttpContext.Request.Url.ToString();
       var index = url.IndexOf("enc=");

       if (index > 0) {
         //Get the encrypted value and decrypt it.
         var encText = context.HttpContext.Server.Decode(url.Substring(index + 4));
         var groups = encryption.Decrypt(encText).Split('&');
         var actionParams = filterContext.ActionDescriptor.GetParameters();

         //Parse all of the "KEY=VALUE" groups
         foreach (var group in groups)
         {
            var pair = group.Split('=');

               if (pair.Value == null)
                 continue;

               //Make sure the action has the parameter of the given name
               var actionParam = actionParams.FirstOrDefault(i => i.Name == pair.Key);
               if (actionParam != null) {
                  var nullType = Nullable.GetUnderlyingType(actionParam.ParameterType);

                  //If a nullable type, make sure to use changetype for that type instead; 
                  //nullable types are not supported
                  if (nullType != null)
                     context.ActionParameters[pair.Key] = 
                          Convert.ChangeType(pair.Value, nullType);
                  //Otherwise, assign and cast the value accordingly
                  else
                     context.ActionParameters[pair.Key] = 
                          Convert.ChangeType(pair.Value, actionParam.ParameterType);

               }

         }
       }
   }
}

There’s a lot to it, so let’s break it down:

– Again, missing null checks and conversion error recovery, so you’ll want to ensure that’s there.
– The first step is to grab the encrypted value from the URL and decode it; once we have this value, the encrypted value is decrypted to get the key/value pairs.
– The filter context has two parameter types: ActionDescription, which GetParameters() returns the metadata of the action method, and ActionParameters, which is a collection of values about to be injected into the action. The ActionParameters collection can be added or modified as you like.
– Each key/value pair is evaluated, and injected into the ActionParameters collection; the values that were encrypted were injected in.
– We first have to check that a nullable type is converted correctly; otherwise, the cast to a nullable type through ChangeType raises an exception.

Some/Action?enc=23442q5wetew

It’s decrypted to:

UserID: 1
ResponseID: 3
Response: YES

The values are inserted into an action method like this:

VB.NET Inline Functions in ASP.NET MVC and Razor

I’ve seen various debate about whether inline markup expressions work in VB, and I want to tell you that they do. Here is how I did it. Define a method like the following, which will be an HTML helper extension. Please see my introductory post on how to do that:


<Extension>
Public Sub Inline(Of TModel)(html As HtmlHelper(Of TModel), template As Action(OF TModel))

In a view, you can define an implementation of that method like the following:


@Code

Html.Inline(Sub(m)
@<span>This is my template</span>
End Sub)

End Code

This approach has worked for me, as long as any helper using the template is inside an @Code block, and the Sub(m) declaration is a multi-line sub, not an inline statement.

Custom ASP.NET MVC Helper Extensions

As you probably are aware of, ASP.NET MVC was a divergence from the approach developers took to developing ASP.NET web forms applications. For web forms developers, any level of customizations occurred by creating custom ASP.NET server controls, inheriting from a particular base class and adding some functionality, something like:

public class SuperLabel : Label
{
   // Enhanced functionality
}

There are many benefits to doing this; you can initialize the control’s properties for your web application, or encapsulate repeatable code into a common class that’s reused throughout the application. For instance, if we know that we will always render our form labels in a specific manner, I could create a custom control to do this following:

public class FormLabel : WebControl
{

   protected override void Render(HtmlTextWriter writer)
    {
         writer.Write("<div>" + this.Text + "</div>");
    }

}

And that way, every label is wrapped in a div with a FormLabel class. This is a simple example of standarizing your control’s output and thus your client’s markup, but you get the idea. This is also possible in ASP.NET MVC, with the help of your own helper methods.

Note that there are two kinds of helper methods in MVC: ones who return an MvcHtmlString object and render the text inline, and ones that render inside the method and are defined as @Html.DoSomething(); (with a semi-colon at the end; in VB this method can be called in a @Code block). I’m going to talk about helpers that return an MvcHtmlString in this blog post. As a simple example of a helper we may want to standardize on, we can, for instance, wrap a DIV with a special class around the Html.EditorFor helper as in the following example:

public static class HtmlHelperExtensions
{

  public static MvcHtmlString CustomEditorFor(this HtmlHelper html, Expression<Func> expr)
  {
     return new MvcHtmlString("<div>" + html.EditorFor(expr) + "</div>");
  }

}

As you can see, we have a custom editor with a wrapper DIV around it now, which we can put in our UI by doing:

@Html.CustomEditorFor(i => i.ModelProperty)

The TModel and TProp generic references above are inferred from the current view’s model, and the property expressed in the lambda expression. If you look in other MVC helpers, they are all defined with these two generic references. The HtmlHelper class has a lot of useful features; first it has access to all the extension methods as you would use them in the UI (most defined in the System.Web.Mvc.Html namespace, but there are additional namespaces too). It also has a reference to the ViewContext and to the current model for the given view, via the HtmlHelper.ViewData.Model property (remember HtmlHelper is specific to the model, therefore it maintains an explicit, strongly-typed reference to said model).

I’ve only scratched the surface, but you can see a lot of options are available to you. For instance, if you use Twitter bootstrap, maybe you want your editor helper to look like:

 public static MvcHtmlString CustomEditorFor(this HtmlHelper html, Expression<Func> expr)
  {

     return new MvcHtmlString(
  @"<div class=\"control-group\">" + 
    html.LabelFor(
        expr, 
        new { @class = "col-md-2 control-label" }
     ).ToHtmlString() +
   @"</div>
     <div class=\"col-md-10\">" + 
    html.EditorFor(expr) + 
    "</div>");

  }

And thus, this saves you from writing a lot of HTML. In the future, I may write on some of the extensions I’ve used for twitter bootstrap to save time. I hope this is a good overview of how you can customize HTML helpers in ASP.NET MVC, which I am using this technique in a current ASP.NET MVC 5 application.