3

I have a code example from KendoUI.

public ActionResult Customers_Read([DataSourceRequest]DataSourceRequest request)
{
    return Json(GetCustomers().ToDataSourceResult(request));
}

private static IEnumerable<CustomerViewModel> GetCustomers()
{
    var northwind = new SampleEntities();
    return northwind.Customers.Select(customer => 
        new CustomerViewModel
        {
            CustomerID  = customer.CustomerID,
            CompanyName = customer.CompanyName,
            ContactName = customer.ContactName,
            // ...
        });
}

This example works fine.

I am confused about [DataSourceRequest] in Customers_Read method...

When I remove (the Attribute?) [DataSourceRequest], the properties from request are empty (null)... (they aren't bound) -> result: filters doesn't work.

What is the [DataSourceRequest]? Is it like an attribute on properties?

Code Example -> IndexController.cs Code Example

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Edi G.
  • 2,432
  • 7
  • 24
  • 33
  • I'm assuming that `DataSourceRequestAttribute` is not the same as `DataSourceRequest` here. – DavidG Oct 01 '14 at 08:15

1 Answers1

8

What you are seeing is a model binder attribute. The DataSourceRequest is actually DataSourceRequestAttribute and extends the CustomModelBinderAttribute class. Creating such an attribute is fairly simple:

First we need a model:

public class MyModel
{
    public string MyProp1 { get; set; }

    public string MyProp2 { get; set; }
}

We need to be able to create the binding, by creating a custom model binder. Depending on how your values are sent to the server, get the values either from the form or the query string:

public class MyModelBinder : IModelBinder
{
     public object BindModel (ControllerContext controllerContext, ModelBindingContext bindingContext)
     {
         MyModel model = new MyModel();

         //model.MyProp1 = controllerContext.HttpContext.Request.Form["MyProp1"];
         //model.MyProp2 = controllerContext.HttpContext.Request.Form["MyProp2"];
         //or
         model.MyProp1 = controllerContext.HttpContext.Request.QueryString["MyProp1"];
         model.MyProp2 = controllerContext.HttpContext.Request.QueryString["MyProp2"];

         return model;
     }
}

The last thing we need to do is create the model binder attribute which can be set inside the action result signature. Its sole purpose is specifying the model binder which must be used for the parameter it decorates:

public class MyModelBinderAttribute : CustomModelBinderAttribute
{
     public override IModelBinder GetBinder()
     {
          return new MyModelBinder();
     }
}

The custom binding can be tested by creating a simple ActionResult and calling it with the parameters in the query string (since my implementation above looks for parameters in the query string):

public ActionResult DoBinding([MyModelBinder]MyModel myModel)
{
    return new EmptyResult();
}

//inside the view
<a href="/Home/DoBinding?MyProp1=value1&MyProp2=value2">Click to test</a>

As DavidG noted, the DataSourceRequestAttribute is different than DataSourceRequest. They appear to have the same names due to the Attribute name convention, i.e. DataSourceRequestAttribute looses the Attribute part when decorating an object or a property.

As a conclusion, the DataSourceRequestAttribute just tells the framework that a custom model binder (probably DataSourceRequestModelBinder or something similar) should be used for the DataSourceRequest request parameter.

Please see the following links for additional information: source, source.

Andrei V
  • 7,306
  • 6
  • 44
  • 64
  • ah, now i understand it. Thank you for your very detailed answer! – Edi G. Oct 01 '14 at 09:20
  • 1
    @EdiG. it bugged me also for some time. I've done a little digging (both on line and with a decompiler) and proved to be a nifty trick. Test it and play with it. – Andrei V Oct 01 '14 at 09:24
  • thanks. can you tell me why other action parameters are binding just fine using default (implied?) binders without attribute [decimal] or [fooDto(attribute)]? – Michael Brennt Mar 16 '18 at 11:10
  • @MichaelBrennt you usually won't need a custom model binder for basic types (strings, dates, decimals); the standard model binder takes care of that for you. It even binds complex objects (classes). There are, however, some restrictions: you basically have to have the same properties (i.e. names) and types. This means that the (client) data sent to your (server) action *must* match. Now, if you have scenarios where the data does't match (different names and/or structure), or simply want more control over how the model will be bound, it makes sense to write your own model binder. – Andrei V Mar 16 '18 at 11:56
  • @MichaelBrennt as a short example, since you seam to be familiar with Kendo, consider the `Kendo.Mvc.UI.DataSourceRequest` class, which has a `IList Sorts` property. The `SortDescriptor` class has a bit of code inside but basically it has two properties: `string Member` and `ListSortDirection SortDirection`, where `ListSortDirection ` is an `enum`. See how the structure gets relatively complex? Well, if you look at the data being sent when you sort a grid, you'll see that the request has a `sort` property, which is string in the form *field*-*direction*. Simple and different. – Andrei V Mar 16 '18 at 12:04