Using DevExtreme Components With the ABP Framework

Hi, in this step by step article, I will show you how to integrate DevExtreme components into ABP Framework-based applications.

both-example-result

(A screenshot from the example application developed in this article)

Create the Project

ABP Framework offers startup templates to get into the business faster. We can download a new startup template using ABP CLI:

abp new DevExtremeSample

After the download is finished, open the solution in the Visual Studio (or your favorite IDE):

initial-project

Run the DevExtremeSample.DbMigrator application to create the database and seed initial data (which creates the admin user, admin role, related permissions, etc). Then we can run the DevExtremeSample.Web project to see our application working.

Default admin username is admin and password is 1q2w3E*

Install DevExtreme

You can follow this documentation to install DevExpress packages into your computer.

Don't forget to add "DevExpress NuGet Feed" to your Nuget Package Sources.

Adding DevExtreme NuGet Packages

Add the DevExtreme.AspNet.Core NuGet package to the DevExtremeSample.Application.Contracts project.

Install-Package DevExtreme.AspNet.Core

Add the DevExtreme.AspNet.Data package to your DevExtremeSample.Web project.

Install-Package DevExtreme.AspNet.Data

Adding DevExtreme NPM Dependencies

Open your DevExtremeSample.Web project folder with a command line and add devextreme and devextreme-aspnet-data NPM packages:

npm install devextreme
npm install devextreme-aspnet-data

Adding Resource Mappings

The devextreme and devextreme-aspnet-data NPM packages are saved under node_modules folder. We need to move the needed files in our wwwroot/libs folder to use them in our web project. We can do it using the ABP client side resource mapping system.

Open the abp.resourcemapping.js file in your DevExtremeSample.Web project and add the following definitions to inside mappings object.

"@node_modules/devextreme/dist/**/*": "@libs/devextreme/",
"@node_modules/devextreme-aspnet-data/js/dx.aspnet.data.js": "@libs/devextreme/js/"

The final abp.resourcemapping.js file should look like below:

module.exports = {
  aliases: {},
  mappings: {
    "@node_modules/devextreme/dist/**/*": "@libs/devextreme/",
    "@node_modules/devextreme-aspnet-data/js/dx.aspnet.data.js": "@libs/devextreme/"
  },
};

Open your DevExtremeSample.Web project folder with a command line and run the abp install-libs command. This command will copy the needed library files into the /wwwroot/libs/devextreme/ folder.

abp install-libs

You can see devextreme folder inside the wwwroot/libs:

wwwroot-lib

Adding DevExtremeStyleContributor

We will add DevExtreme CSS files to the global bundle by creating a bundle contributor.

Create a Bundling folder in the DevExtremeSample.Web project and a DevExtremeStyleContributor.cs file with the following content:

using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;

namespace DevExtremeSample.Web.Bundling
{
    public class DevExtremeStyleContributor : BundleContributor
    {
        public override void ConfigureBundle(BundleConfigurationContext context)
        {
            context.Files.AddIfNotContains("/libs/devextreme/css/dx.common.css");
            context.Files.AddIfNotContains("/libs/devextreme/css/dx.light.css");
        }
    }
}

You can choose another theme than the light theme. Check the /libs/devextreme/css/ folder and the DevExtreme documentation for other themes.

Open your DevExtremeSampleWebModule.cs file in your DevExtremeSample.Web project and add following code into the ConfigureServices method:

Configure<AbpBundlingOptions>(options =>
{
    options
        .StyleBundles
        .Get(StandardBundles.Styles.Global)
        .AddContributors(typeof(DevExtremeStyleContributor));
});

Adding DevExtremeScriptContributor

We can not add DevExtreme js packages to Global Script Bundles, just like done for the CSS files. Because DevExtreme requires to add its JavaScript files into the <head> section of the HTML document, while ABP Framework adds all JavaScript files to the end of the <body> (as a best practice).

Fortunately, ABP Framework has a layout hook system that allows you to add any code into some specific positions in the HTML document. All you need to do is to create a ViewComponent and configure the layout hooks.

Let's begin by creating a DevExtremeScriptContributor.cs file in the Bundling folder by copying the following code inside it:

using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;

namespace DevExtremeSample.Web.Bundling
{
    [DependsOn(
        typeof(JQueryScriptContributor)
        )]
    public class DevExtremeScriptContributor : BundleContributor
    {
        public override void ConfigureBundle(BundleConfigurationContext context)
        {
            context.Files.AddIfNotContains("/libs/devextreme/js/dx.all.js");
            context.Files.AddIfNotContains("/libs/devextreme/js/dx.aspnet.mvc.js");
            context.Files.AddIfNotContains("/libs/devextreme/js/dx.aspnet.data.js");
        }
    }
}

As you see, the DevExtremeScriptContributor depends on JQueryScriptContributor which adds JQuery related files before the DevExpress packages (see the bundling system for details).

Create DevExtremeJsViewComponent

Create a new view component, named DevExtremeJsViewComponent inside the /Components/DevExtremeJs folder of the Web project, by following the steps below:

  1. Create a DevExtremeJsViewComponent class inside the /Components/DevExtremeJs (create the folders first):
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;

namespace DevExtremeSample.Web.Components.DevExtremeJs
{
    public class DevExtremeJsViewComponent : AbpViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View("/Components/DevExtremeJs/Default.cshtml");
        }
    }
}
  1. Create Default.cshtml file in the same folder with the following content:
@using DevExtremeSample.Web.Bundling
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling

<abp-script type="typeof(DevExtremeScriptContributor)" />

Your final Web project should be like the following:

devextreme-js

  1. Now, we can add this view component to <head> section by using the layout hooks.

Open your DevExtremeSampleWebModule.cs file in your DevExtremeSample.Web project and add following code into the ConfigureServices method:

Configure<AbpLayoutHookOptions>(options =>
{
    options.Add(
        LayoutHooks.Head.Last, //The hook name
        typeof(DevExtremeJsViewComponent) //The component to add
    );
});

Known Issue: Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.

This issue does exist in the ABP Framework v3.0 and earlier versions. If you are using ABP Framework v3.1 or a later version, you can skip this section.

When you run your *.Web project, you will see an exception (Uncaught TypeError: MutationObserver.observe: Argument 1 is not an object.) at your console.

To fix that issue, download this file abp.jquery.js and replace with the wwwroot/libs/abp/jquery/abp.jquery.js file of your Web project.

Result

The installation step was done. You can use any DevExtreme component in your application.

Example: A button and a progress bar component:

devexp-result

This example has been created by following this documentation.

The Sample Application

We have created a sample application with Tree List and Data Grid examples.

The Source Code

You can download the source code from here.

Data Grid

You can see the full working example of Data Grid.

data-grid-final

The related files for this example are highlighted at the following screenshots.

data-grid-app-contract

data-grid-application

data-grid-web

Tree List

You can see the full working example of Tree List.

tree-list-final

The related files for this example are highlighted at the following screenshots.

tree-list-app-contract

tree-list-application

tree-list-web

Additional Notes

Data Storage

I've used an in-memory list to store data for this example, instead of a real database. Because it is not related to DevExpress usage. There is a SampleDataService.cs file in Data folder at .Application.Contracts project. All the data is stored here.

JSON Serialization

You can see some JsonProperty attributes on the DTO properties. I use these attributes because DevExtreme example expects PascalCase property names in the serialized JSON that is sent to the client. But ABP Framework & ASP.NET Core conventionally uses camelCase property names on JSON serialization. Adding these JsonProperty attributes ensures that the related properties are serialized as PascalCase.

DevExtreme Components vs Application Service Methods

ABP Framework conventionally converts application services to API Controllers. For example, see the application service below:

public class OrderAppService : DevExtremeSampleAppService, IOrderAppService
{
    public async Task<LoadResult> GetOrdersAsync(DataSourceLoadOptions loadOptions)
    {
        ...
    }

    public async Task<Order> InsertOrder(string values)
    {
        ...
    }
    ...
}

You can use these service methods for your DevExtreme components as shown below:

Html.DevExtreme().DataGrid<Order>()
            .DataSource(d => d.Mvc()
                .Controller("Order") // Application Service Name without 'AppService'
                .LoadAction("GetOrders") // Method Name without 'Async'
                .InsertAction("InsertOrder")
                .UpdateAction("UpdateOrder")
                .DeleteAction("DeleteOrder")
                .Key("OrderID")
            )

Conclusion

In this article, I've explained how to use DevExtreme components in your application. ABP Framework is designed so that it can work with any UI library/framework.

Halil İbrahim Kalkan 99 weeks ago

Great article. Thanks for sharing.

EL HADI Abdelhalim 97 weeks ago

Great... can you provide an example with Abp + Angular + Devexterme. Regards

rcalv002 74 weeks ago

The only issue I found is that in your example you've created a model for Order which uses a customer OrderID, you then set the json property so it becomes PascalCase and therefore works with the datasource from devextreme, but really only because you're taking the input (for create forexample) as a string values which you then convert to object. But that's not how the documentation says to create our services, instead we should use strongly typed dtos to take data and give data.

rcalv002 74 weeks ago

Also most of the time we are using entities in the system that inherit the Id property for the abp entity, in this case, how do we get around this issue? Could you please speak to these points to have a real example where you can both retrieve data from the entities in the system and display on devextreme, as well as push back data using the regular services built using dtos?

pdpalma@hotmail.com 65 weeks ago

Hi! Great article. I wondering how integrate with database. I'm trying to implement it on this tutorial https://docs.abp.io/en/abp/latest/Tutorials/Part-3?UI=MVC&DB=EF

Piseth San 53 weeks ago

Very nice! Thanks for all these. By the way, right now i cannot find DevExtreme.AspNet.Core in Nuget. How can i have it?