New to Telerik UI for ASP.NET MVC? Download free 30-day trial

OData-v4 Binding

OData (Open Data Protocol) defines best practices when it comes to building and consuming RESTful APIs in a dependable manner. It enables you to mimic a Web API but with built-in support for filtering, selecting, and expanding amongst other capabilities. Binding the ASP.NET MVC Grid through OData-v4, allows you to elevate its REST API by introducing advanced querying options.

For a runnable example, refer to the demo on OData binding of the Grid component.

Installing the OData Package

To install the required dependencies for using OData, install the autonomous Microsoft.AspNet.OData and Microsoft.AspNet.WebApi.WebHost NuGet packages in your project.

Building the Edm Model and Configuring OData in an ASP.NET MVC Environment

To ensure that the application is configured for both WebApi and OData binding capabilities:

  1. Configure Web API by calling GlobalConfiguration.Configure in the Application_Start method.

      public class MvcApplication : System.Web.HttpApplication
      {
          protected void Application_Start()
          {
              GlobalConfiguration.Configure(WebApiConfig.Register);
              RouteConfig.RegisterRoutes(RouteTable.Routes);
          }
      }
    
  2. Create a file named WebApiConfig.cs inside the App_Start folder and configure the Edm model and OData services.

      public static class WebApiConfig
      {
          public static void Register(HttpConfiguration config)
          {
              // Web API configuration and services.
    
              // Web API routes.
              config.MapHttpAttributeRoutes();
    
              config.Routes.MapHttpRoute(
                  name: "DefaultApi",
                  routeTemplate: "api/{controller}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );
    
              var builder = new ODataConventionModelBuilder();
              builder.EntitySet<Product>("Products");
    
              config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
              config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
          }
      }
    

Adding an OData Controller

To support writing and reading data using the OData formats, the ODataController base class needs to be inherited for a given controller instance.

    public class ProductsController : ODataController
    {
        ...
    }

From there, the REST API endpoints need to be decorated with the EnableQuery attribute. This attribute is responsible for applying the query options that are passed in the query string.

    public class ProductsController : ODataController
    {
        [EnableQuery]
        public List<Product> GetProducts()
        {
            var products = GetProducts(); // Call to the database.
            return products;
        }

        [EnableQuery]
        public IHttpActionResult Put([FromODataUri] int key, [FromBody] Product product)
        {
            // Custom update logic and update the Category field.
            return Updated(product);
        }
        [EnableQuery]
        public IHttpActionResult Post([FromBody] Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            // Custom create logic and update the Category field.
            return Created(product);
        }
        public IHttpActionResult Delete([FromODataUri] int key)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            // Custom delete logic.
            return StatusCode(HttpStatusCode.NoContent);
        }
    }

Configuring the Grid for OData-v4 Binding

To implement the OData binding within the boundaries of the Telerik UI for ASP.NET MVC Grid, specify the .Type("odata-v4") configuration method within the DataSource. This ensures that the requests can be sent to the OData endpoint in the expected format and out of the box.

    @(Html.Kendo().Grid<Product>()
         .Name("grid")
         .Columns(columns =>
         {
             columns.Bound(p => p.ProductName);
             columns.Bound(p => p.UnitsInStock);
             columns.Bound(p => p.Discontinued);
             columns.Bound(p => p.UnitsOnOrder);
             columns.Bound(p => p.UnitPrice).Width(150);
         })
         .Pageable()
         .DataSource(dataSource => dataSource
             .Custom()
             .Type("odata-v4")
             .Schema(schema => schema
             .Model(model =>
             {
                 model.Id(t => t.ProductID);
                 model.Field(t => t.ProductID).Editable(false);
                 model.Field(t => t.ProductName);
                 model.Field(t => t.UnitPrice);
                 model.Field(t => t.UnitsInStock);
                 model.Field(t => t.UnitsOnOrder);
                 model.Field(t => t.Discontinued);
             }))
             .Transport(transport =>
             {
                 transport.Read(read => read.Url("/odata/Products"));
             })
             .PageSize(10)
             .ServerPaging(true)
         )
    )

Configuring the CRUD Operations

To configure CRUD operations that support OData-v4 Binding, explicitly add a ClientHandlerDescriptor that will be responsible for mapping the OData-v4 endpoints.

  @(Html.Kendo().Grid<Product>()
      .Name("grid")
      .Columns(columns =>
      {
          columns.Bound(p => p.ProductName);
          columns.Bound(p => p.UnitsInStock);
          columns.Bound(p => p.Discontinued);
          columns.Bound(p => p.UnitsOnOrder);
          columns.Bound(p => p.UnitPrice).Width(150);
          columns.Command(c => {c.Edit(); c.Destroy();}).Width(150);
      })
      .Toolbar(toolbar => toolbar.Create())
      .Pageable()
      .Editable(editable => editable.Mode(GridEditMode.InLine))
      .DataSource(dataSource => dataSource
          .Custom()
          .Type("odata-v4")
          .Schema(schema => schema
          .Model(model =>
          {
              model.Id(t => t.ProductID);
              model.Field(t => t.ProductID).Editable(false);
              model.Field(t => t.ProductName);
              model.Field(t => t.UnitPrice);
              model.Field(t => t.UnitsInStock);
              model.Field(t => t.UnitsOnOrder);
              model.Field(t => t.Discontinued);
          }))
          .Transport(transport =>
          {
              transport.Read(read => read.Url("/odata/Products"));
              transport.Update(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "update" } });
              transport.Destroy(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "destroy" } });
              transport.Create(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "create" } });
          })
          .PageSize(10)
          .ServerPaging(true)
      )
    )

    <script>
        function update(dataItem){
            return "/odata/Products(" + dataItem.ProductID + ")"
        }
        function create(){
            return "/odata/Products"
        }
        function destroy(dataItem){
            return "/odata/Products(" + dataItem.ProductID + ")"
        }
    </script>

Configuring Batch Editing

To enable batch editing capabilities in an ASP.NET MVC Environment:

  1. Add a default batch handler inside the WebApiConfig file.

      public static class WebApiConfig
      {
          public static void Register(HttpConfiguration config)
          {
              // Web API configuration and services
    
              // Web API routes
              config.MapHttpAttributeRoutes();
    
              config.Routes.MapHttpRoute(
                  name: "DefaultApi",
                  routeTemplate: "api/{controller}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );
    
              var builder = new ODataConventionModelBuilder();
              builder.EntitySet<Product>("Products");
    
              config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
              config.MapODataServiceRoute("odata", "odata", new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
          }
      }
    
  2. Within the Grid, set the Batch operation with ClientHandlerDescriptor to support batching and enable the Batch() option of the Grid's DataSource.

        @(Html.Kendo().Grid<ProductViewModel>()
            .Name("grid")
             .Columns(columns =>
                {
                    columns.Bound(p => p.ProductName);
                    columns.Bound(p => p.QuantityPerUnit);
                    columns.Bound(p => p.UnitsInStock);
                    columns.Bound(p => p.Discontinued);
                    columns.Bound(p => p.UnitsOnOrder);
                    columns.Bound(p => p.UnitPrice).Width(150);
                    columns.Command(command => command.Destroy()).Width(150);
                })
                .ToolBar(toolBar =>
                {
                    toolBar.Create();
                    toolBar.Save();
                })
                .Editable(editable => editable.Mode(GridEditMode.InCell))
                .Pageable()
                .Sortable()
                .Scrollable()
                .HtmlAttributes(new { style = "height:550px;" })
                .DataSource(dataSource => dataSource
                .Custom()
                .Batch(true)
                .Type("odata-v4")
                .Schema(schema => schema
                .Model(m =>
                {
                    m.Id(t => t.ProductID);
                    m.Field(t => t.ProductID).Editable(false);
                    m.Field(t => t.ProductName);
                    m.Field(t => t.QuantityPerUnit);
                    m.Field(t => t.UnitPrice);
                    m.Field(t => t.UnitsInStock);
                    m.Field(t => t.UnitsOnOrder);
                    m.Field(t => t.Discontinued);
                }))
                .Transport(t =>
                {
                    t.Read(read => read.Url("/odata/Products"));
                    t.Update(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "update" } });
                    t.Destroy(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "destroy" } });
                    t.Create(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "create" } });
                    t.Batch(new { url = new Kendo.Mvc.ClientHandlerDescriptor() { HandlerName = "batch" } });
                })
                .PageSize(10)
                .ServerPaging(true)
             )
        )
        <script>
            function batch(){
                return "/odata/$batch"
            }
            function update(dataItem){
                return "/odata/Products(" + dataItem.ProductID + ")"
            }
            function create(){
                return "/odata/Products"
            }
            function destroy(dataItem){
                 return "/odata/Products(" + dataItem.ProductID + ")"
            }
        </script>
    

See Also

In this article