Skip to main content

Basic Data Sources

  • 10 minutes to read

This article explains how to bind DevExtreme components to basic data sources.

Read-Only JSON Data

DevExtreme-based ASP.NET Core controls can access JSON data that an AJAX request returns from a resource.

You can configure access to JSON data in one of the following ways:

  • Use the DataSource(string staticJsonUrl, params string[] key) method.

    This method allows you to pass a JSON data URL and a key (optional).

    The following code shows how to pass only a URL:

    @(Html.DevExtreme().SelectBox()
        .DataSource("https://www.example.com/dataservices/jsondata")
    )
    

    The following code shows how to pass a URL and a key:

    @(Html.DevExtreme().SelectBox()
        .DataSource("https://www.example.com/dataservices/jsondata", "ID")
    )
    
  • Use the DataSourceFactory.StaticJson() method.

    This method opens a chain of methods (Url, Key, CacheAllData, etc.) that allow you to pass a JSON data URL and a number of additional parameters.

    @(Html.DevExtreme().SelectBox()
        .DataSource(ds => ds.StaticJson()
            .Url("https://www.example.com/dataservices/jsondata")
            .Key("ID")
            .CacheAllData(true)
        )
    )
    

Note

These approaches use a data URL. You can specify the URL as described below.

How to Specify a JSON Data URL

You can specify a data URL in the following ways:

  • An absolute URL.

    @(Html.DevExtreme().SelectBox()
        .DataSource(ds => ds.StaticJson()
            .Url("https://www.example.com/dataservices/jsondata")
        )
    )
    

    You can also use a JSONP callback parameter supported by jQuery.ajax().

    @(Html.DevExtreme().SelectBox()
        .DataSource(ds => ds.StaticJson()
            .Url("https://www.example.com/dataservices/jsonpdata?callback=?")
        )
    )
    
  • A virtual path the URL.Content method returns.

    @(Html.DevExtreme().SelectBox()
        .DataSource(ds => ds.StaticJson()
            .Url(Url.Content("~/dataservices/jsondata"))
        )
    )
    
  • Use the following UrlHelper class methods to build a URL:

    • Url.Action() - generates a URL with an absolute path for an action method. Refer to Generating URLs by action name for more information.

      @(Html.DevExtreme().List()
          .DataSource(ds => ds.StaticJson()
              .Url(Url.Action("GetListData"))
          )
          .ItemTemplate(@<text><%- name %></text>)
      )
      
      using Microsoft.AspNetCore.Mvc;
      using Newtonsoft.Json;
      
      namespace MyApp.Controllers {
          public class HomeController : Controller {
              public IActionResult Index() {
                  return View();
              }
      
              [HttpGet]
              public object GetListData() {
                  return new[] {
                      new {
                          name = "John",
                          city = "New York City"
                      },
                      new {
                          name = "Jane",
                          city = "Bangkok"
                      },
                      new {
                          name = "Mike",
                          city = "Baghdad"
                      }
                  };
              }
          }
      }
      
    • Url.Page() - generates a URL for a Razor page. You should specify the page name and the page handler defined in a page model.

      @page
      @model MyApp.Pages.MyPageModel
      
      @(Html.DevExtreme().List()
          .DataSource(ds => ds.StaticJson()
              .Url(Url.Page(null, "ListData"))
          )
          .ItemTemplate(@<text><%- name %></text>)
      )
      
      namespace MyApp.Pages {
          public class MyPageModel : PageModel {
      
              public IActionResult OnGetListData() {
                  var data = new[] {
                      new {
                          name = "John",
                          city = "New York City"
                      },
                      new {
                          name = "Jane",
                          city = "Bangkok"
                      },
                      new {
                          name = "Mike",
                          city = "Baghdad"
                      }
                  };
                  return new JsonResult(data);
              }
          }
      
    • Url.RouteUrl() - generates a URL by route.

CLR Objects

You can bind DevExtreme-based ASP.NET Core controls to a collection of CLR objects: Array, List, or IEnumerable.

These collections can come from a controller (via Model or ViewData) and can be embedded in Razor files (directly in an HTML helper declaration or in an @functions block).

A collection is passed to the client as an ArrayStore. Note that collection data should be JSON-serializable.

Example: Bind a Control to an Array

The following code snippet shows how to bind the Chart control to an array:

@(Html.DevExtreme().Chart()
    //...
    .DataSource(new[] {
        new { Day = "Monday", Oranges = 3 },
        new { Day = "Tuesday", Oranges = 2 },
        new { Day = "Wednesday", Oranges = 3 },
        new { Day = "Thursday", Oranges = 4 },
        new { Day = "Friday", Oranges = 6 },
        new { Day = "Saturday", Oranges = 11 },
        new { Day = "Sunday", Oranges = 4 }
    })
)

View Demo

Example: Bind a Control to a Model

The example code shows how to bind the MultiView control to Model.

@model List<DevExtreme.NETCore.Demos.Models.Store>

@(Html.DevExtreme().MultiView()
    //...
    .DataSource(Model)
)
using System;
using System.Collections.Generic;
using System.Linq;

namespace DevExtreme.NETCore.Demos.Models.SampleData {
    public partial class SampleData {
        public static Store[] Stores = new[] {
            new Store {
                ID = 1,
                CompanyName = "SuprMart",
                Address = "702 SW 8th Street",
                City = "Bentonville",
            },
            new Store {
                ID = 2,
                CompanyName = "El'Depot",
                Address = "2455 Paces Ferry Road NW",
                City = "Atlanta",
            },
            new Store {
                ID = 3,
                CompanyName = "K&S Music",
                Address = "1000 Nicllet Mall",
                City = "Minneapolis",
            }
        };
    }
}
using DevExtreme.NETCore.Demos.Models.SampleData;
using Microsoft.AspNetCore.Mvc;
​
namespace DevExtreme.NETCore.Demos.Controllers {
    public class MultiViewController : Controller {
        public ActionResult Overview() {
            return View(SampleData.Stores);
        }
    }
}

View Demo

The DataSource method overload accepts the string[] key parameter, so your code can look as follows:

.DataSource(Model.YourCollection, "KeyFieldName")

API Controllers

This topic describes how to implement API controllers and bind DevExtreme-based ASP.NET Core controls to them.

Implement an API Controller

Note

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 Core database context.

Pay attention to the Get method:

using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Linq;
using System.Threading.Tasks;

namespace MyApp.Controllers {

    [Route("api/[controller]/[action]")]
    public class OrdersController : Controller {
        NorthwindContext _context;

        public OrdersController(NorthwindContext context) {
            _context = context;
        }

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

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

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

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

            if(!TryValidateModel(newOrder))
                return BadRequest(VALIDATION_ERROR);

            var result = _context.Orders.Add(newOrder);
            await _context.SaveChangesAsync();
            return Json(new { result.Entity.OrderId });
        }

        // Update an order
        [HttpPut]
        public async Task<IActionResult> 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 BadRequest(VALIDATION_ERROR);

            await _context.SaveChangesAsync();
            return Ok();
        }

        // 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(Orders 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"]);
        }
    }
}

Important

Starting from version 2.1, ASP.NET Core provides the ApiController attribute. If you add this attribute to your controller, specify the [FromForm] binding source attribute in the Insert, Update and Delete actions. See the example code below.

public IActionResult Insert([FromForm]string values) {
    // ...
}

public IActionResult Update([FromForm]int key, [FromForm]string values) {
    // ...
}

public void Delete([FromForm]int key) {
    // ...
}

Bind to a Controller

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

@(Html.DevExtreme().DataGrid()
    .DataSource(ds => ds.Mvc()
        .Controller("Orders")
        .Key("OrderId")
        .LoadAction("Get")
        .InsertAction("Insert")
        .UpdateAction("Update")
        .DeleteAction("Delete")
    )
)

Bind to a 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 that 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 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/Insert")
        .UpdateUrl("http://www.example.com/Orders/Update")
        .DeleteUrl("http://www.example.com/Orders/Delete")
    )
)

Note

A remote controller should be implemented in the way described above.

Bind to a 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.Mvc()
        .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.Mvc()
        .Controller("Orders")
        .Key("OrderId")
        .LoadAction("Get")
        .LoadMode(DataSourceLoadMode.Raw) // specifies the raw mode
        // ...
    )
)

Pass Additional Load Parameters

DevExtreme-based ASP.NET Core 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.Mvc().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.Mvc()
                    .Controller("Employees")
                    .LoadAction("TasksDetails")
                    .LoadParams(new { id = new JS("key") })
                )
            )
        </text>)
    )
)

If the control is not 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.Mvc()
        .Controller("Orders")
        .LoadAction("GetOrdersByDate")
        .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.Mvc()
        .Controller("Orders")
        .LoadAction("GetOrdersByDate")
        //...
        .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 use request verification to prevent XSRF/CSRF attacks, you should pass an anti-forgery token as part of the 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.

    @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
    
    <script>
        function beforeSend(operation, ajaxSettings) {
            ajaxSettings.headers = {
                RequestVerificationToken: "@Xsrf.GetAndStoreTokens(Model.HttpContext).RequestToken"
            };
        }
    </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(...);  
        }  
    }