Controllers

  • 12 minutes to read
NOTE

You should be familiar with the basic concepts of ASP.NET Web API and ASP.NET MVC to use this documentation.

You can bind DevExtreme ASP.NET MVC controls to Web API controllers or MVC 5 controllers. Refer to this StackOverflow thread for information on the difference between these controllers.

We recommend that you use Web API controllers because they are specialized in returning data. You can implement these controllers or use DevExtreme Scaffolder to generate them.

Web API Controllers

Use the WebApi() method the DataSource()'s lambda parameter exposes to configure access to Web API controllers. The WebApi() exposes methods to specify a controller and action names.

The following example shows the DataGrid control that accesses the OrdersController whose actions (Get, Post, Put, and Delete) implement CRUD operations:

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.WebApi()
        .Controller("Orders")
        .Key("OrderID")
        .LoadAction("Get")
        .InsertAction("Post")
        .UpdateAction("Put")
        .DeleteAction("Delete")
    )
)

You can also pass true to the UpdateAction, InsertAction, and DeleteAction methods if you use routing based on Http[Verb] attributes.

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.WebApi()
        .Controller("OrdersWebApi")
        .Key("OrderID")
        .LoadAction(true)
        .UpdateAction(true)
        .InsertAction(true)
        .DeleteAction(true)
    )
)

The code below shows how to implement the OrdersController. This is a shortened version of the controller code the DevExtreme Scaffolder generates. CRUD operations are performed on the Northwind database's Orders table via Entity Framework database context.

Pay attention to the Get method:

using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web.Http;

namespace MyApp.Controllers {

    public class OrdersController : ApiController {
        NorthwindEntities _context = new NorthwindEntities();

        const string VALIDATION_ERROR = "The request failed due to a validation error";

        // Load orders according to load options
        [HttpGet]
        public async Task<HttpResponseMessage> Get(DataSourceLoadOptions loadOptions) {
            var orders = _context.Orders.Select(i => new {
                i.OrderID,
                i.OrderDate,
                i.ShipCity
            });

            return Request.CreateResponse(await DataSourceLoader.LoadAsync(orders, loadOptions));
        }

        // Insert a new order
        [HttpPost]
        public async Task<HttpResponseMessage> Post(FormDataCollection form) {
            var newOrder = new Order();
            PopulateModel(newOrder, JsonConvert.DeserializeObject<IDictionary>(form.Get("values")));

            Validate(newOrder);
            if(!ModelState.IsValid)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, VALIDATION_ERROR);

            var result = _context.Orders.Add(newOrder);
            await _context.SaveChangesAsync();
            return Request.CreateResponse(HttpStatusCode.Created, result.OrderID);
        }

        // Update an order
        [HttpPut]
        public async Task<HttpResponseMessage> Put(FormDataCollection form) {
            var key = Convert.ToInt32(form.Get("key"));
            var order = await _context.Orders.FirstOrDefaultAsync(item => item.OrderID == key);
            PopulateModel(order, JsonConvert.DeserializeObject<IDictionary>(form.Get("values")));

            Validate(order);
            if(!ModelState.IsValid)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, VALIDATION_ERROR);

            await _context.SaveChangesAsync();
            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // Remove an order
        [HttpDelete]
        public async Task Delete(FormDataCollection form) {
            var key = Convert.ToInt32(form.Get("key"));
            var model = await _context.Orders.FirstOrDefaultAsync(item => item.OrderID == key);
            _context.Orders.Remove(model);
            await _context.SaveChangesAsync();
        }

        void PopulateModel(Order order, IDictionary values) {
            if(values.Contains("OrderID"))
                order.OrderID = Convert.ToInt32(values["OrderID"]);

            if(values.Contains("OrderDate"))
                order.OrderDate = values["OrderDate"] != null ? Convert.ToDateTime(values["OrderDate"]) : (DateTime?)null;

            if(values.Contains("ShipCity"))
                order.ShipCity = Convert.ToString(values["ShipCity"]);
        }

        protected override void Dispose(bool disposing) {
            if(disposing) {
                _context.Dispose();
            }
            base.Dispose(disposing);
        }
    }

}

MVC 5 Controllers

Use the Mvc() method the DataSource()'s lambda parameter exposes to configure access to MVC controllers. The following example shows the DataGrid control that accesses the OrdersController whose actions (Get, Post, Put, and Delete) implement CRUD operations:

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.Mvc()
        .Controller("Orders")
        .Key("OrderID")
        .LoadAction("Get")
        .InsertAction("Post")
        .UpdateAction("Put")
        .DeleteAction("Delete")
    )
)

The code below shows how to implement the OrdersController. CRUD operations are performed on the Northwind database's Orders table via Entity Framework database context. The Get method uses the DevExtreme.AspNet.Data library's DataSourceLoader class to load orders.

using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace MyApp.Controllers {

    public class OrdersController : Controller {
        NorthwindEntities _context = new NorthwindEntities();

        const string VALIDATION_ERROR = "The request failed due to a validation error";

        // Load orders according to load options
        [HttpGet]
        public async Task<ActionResult> Get(DataSourceLoadOptions loadOptions) {
            var orders = _context.Orders.Select(i => new {
                i.OrderID,
                i.OrderDate,
                i.ShipCity
            });

            return NewtonsoftJson(await DataSourceLoader.LoadAsync(orders, loadOptions));
        }

        // Insert a new order
        [HttpPost]
        public async Task<ActionResult> Post(string values) {
            var newOrder = new Order();
            PopulateModel(newOrder, JsonConvert.DeserializeObject<IDictionary>(values));

            if(!TryValidateModel(newOrder))
                return NewtonsoftJson(VALIDATION_ERROR, 400);

            var result = _context.Orders.Add(newOrder);
            await _context.SaveChangesAsync();
            return NewtonsoftJson(result.OrderID);
        }

        // Update an order
        [HttpPut]
        public async Task<ActionResult> Put(int key, string values) {
            var order = await _context.Orders.FirstOrDefaultAsync(item => item.OrderID == key);
            PopulateModel(order, JsonConvert.DeserializeObject<IDictionary>(values));

            if(!TryValidateModel(order))
                return NewtonsoftJson(VALIDATION_ERROR, 400);

            await _context.SaveChangesAsync();
            return new EmptyResult();
        }

        // Remove an order
        [HttpDelete]
        public async Task Delete(int key) {
            var order = await _context.Orders.FirstOrDefaultAsync(item => item.OrderID == key);
            _context.Orders.Remove(order);
            await _context.SaveChangesAsync();
        }

        void PopulateModel(Order order, IDictionary values) {
            if(values.Contains("OrderID"))
                order.OrderID = Convert.ToInt32(values["OrderID"]);

            if(values.Contains("OrderDate"))
                order.OrderDate = values["OrderDate"] != null ? Convert.ToDateTime(values["OrderDate"]) : (DateTime?)null;

            if(values.Contains("ShipCity"))
                order.ShipCity = Convert.ToString(values["ShipCity"]);
        }

        ActionResult NewtonsoftJson(object obj, int statusCode = 200) {
            Response.StatusCode = statusCode;
            return Content(JsonConvert.SerializeObject(obj), "application/json");
        }

        protected override void Dispose(bool disposing) {
            if(disposing) {
                _context.Dispose();
            }
            base.Dispose(disposing);
        }
    }

}

Controller on a Different Site

Previous examples show use-cases in which a control is on the same site as the data it accesses, and routing finds the right controller and actions. If the data is on a different site, use the RemoteController() method the DataSource()'s lambda parameter exposes. This method opens a chain whose members mirror the client-side API of the DevExtreme.AspNet.Data library. In the LoadUrl, InsertUrl, UpdateUrl, and DeleteUrl methods, specify the URLs that handle the corresponding CRUD operations.

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.RemoteController()
        .Key("OrderID")
        .LoadUrl("http://www.example.com/Orders/Get")
        .InsertUrl("http://www.example.com/Orders/Post")
        .UpdateUrl("http://www.example.com/Orders/Put")
        .DeleteUrl("http://www.example.com/Orders/Delete")
    )
)

Controller in a Different Area

In an app organized into areas, a control from a view that belongs to one area may need to access a data controller that belongs to another. Use the Area() method to specify the data controller's area in this case.

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.WebApi()
        .Area("DifferentArea")
    )
)

Load Mode

You can use two modes to load data:

  • Processed (default) - all data shaping operations are performed on the server.
  • Raw - all data shaping operations are performed on the client.

    Use the raw mode when a control operates with a small amount of data. Once loaded, data is stored and processed on the client. This should reduce network round-trips, and therefore network latency.

Use the DataSource.LoadMode method to specify the mode:

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.WebApi()
        .Controller("Orders")
        .Key("OrderID")
        .LoadAction("Get")
        .LoadMode(DataSourceLoadMode.Raw) // specifies the raw mode
    )
)

Pass Additional Load Parameters

DevExtreme ASP.NET MVC controls internally transform your Razor code into HTML and JavaScript. However, you cannot use Razor to pass load parameters. Instead, you need to access JavaScript variables directly.

The following code example implements the master-detail interface in the DataGrid control. This interface is a grid inside a grid. The inner grid filters its data source according to the master row key. The API reference states that the master row key and data are available in the master-detail template through the key and data JavaScript variables. Only the key's value is needed for the master-detail, and it is accessed using the new JS() expression.

@(Html.DevExtreme().DataGrid()
    .DataSource(d => d.WebApi().Controller("Employees").Key("ID"))
    .Columns(columns => {
        columns.Add().DataField("FirstName");
        columns.Add().DataField("LastName");
    })
    .MasterDetail(md => md
        .Enabled(true)
        .Template(@<text>
            @(Html.DevExtreme().DataGrid()
                .DataSource(d => d.WebApi()
                    .Controller("Employees")
                    .LoadAction("TasksDetails")
                    .LoadParams(new { id = new JS("key") })
                )
            )
        </text>)
    )
)
NOTE

Razor VB: When you use the @<text> block:

  • enclose the control configuration with @Code/End Code;
  • end the control configuration with Render().

If the control is not declared in a template and no variables are provided, you can use a JavaScript function accessed with the same new JS() expression to get data for a load parameter. In the following code, the DataGrid control uses the getDateBoxValue() function to get a value for the orderDate load parameter from the DateBox control. The refreshGrid() function is called each time the DateBox value changes. This function calls the DataGrid's refresh method that makes a new load query with the updated load parameter.

@(Html.DevExtreme().DateBox()
    .ID("orderDate")
    .Type(DateBoxType.Date)
    .Value(DateTime.Now)
    .OnValueChanged("refreshGrid")
)

@(Html.DevExtreme().DataGrid()
    .ID("targetDataGrid")
    .DataSource(ds => ds.WebApi()
        .Controller("Orders")
        .LoadAction("GetByDate")
        .LoadParams(new { orderDate = new JS("getDateBoxValue") })
    )
    .Columns(cols => {
        cols.Add().DataField("OrderID");
        cols.Add().DataField("OrderDate");
    })
)

<script type="text/javascript">
    function getDateBoxValue() {
        return $("#orderDate").dxDateBox("option", "text");
    }

    function refreshGrid() {
        $("#targetDataGrid").dxDataGrid("refresh");
    }
</script>

To pass additional parameters to other requests (insert, update, etc.), use the OnBeforeSend() method. See Customize Requests for more information.

Customize Requests

Use the DataSource.OnBeforeSend() method to customize HTTP requests. This method takes a JavaScript function that makes changes to a request before it is sent. This function is transformed internally into the DevExtreme.AspNet.Data library's client-side onBeforeSend function.

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.WebApi()
        .Controller("Orders")
        .LoadAction("GetByDate")
        .OnBeforeSend("dataGrid_beforeSend")
    )
)
<script>
    function dataGrid_beforeSend(operation, ajaxOptions) {
        //...
    }
</script>

You can customize requests in the following ways:

  • Specify HTTP headers.

    For example, if you configure XSRF protection as described in this tutorial, you should pass an anti-forgery token as part of a request header. The code below shows how to implement the beforeSend function that adds an anti-forgery token to a request header. Then this function can be used in the DataSource.OnBeforeSend method.

    @Html.AntiForgeryToken()
    
    <script>
        function beforeSend(operation, ajaxSettings) {
            var verificationToken = $("input[name=__RequestVerificationToken]").val();
            ajax.headers = {
                __RequestVerificationToken: verificationToken
            };
        }
    </script>
    
  • Pass additional parameters to requests.

    The following code shows the beforeSend function that adds a new parameter to all requests (excluding the load operation):

    function beforeSend(operation, ajaxSettings) {  
        if (operation !== "load") {  
            ajaxSettings.data.extraParam = "extraValue";  
        }  
    }
    
  • Modify a URL.

    function beforeSend(operation, ajaxSettings) {  
        if (operation !== "load") {  
            ajaxSettings.url = ajaxSettings.url.replace(...);  
        }  
    }