Example T1136768
Visible to All Users

BI Dashboard for ASP.NET Core - How to Implement a Custom Service and UI for Managing Dashboards List

This example shows how to create a custom list of dashboards similar to the Dashboard Panel.

The DevExtreme ASP.NET Data package's API is used to prepare a list of dashboard names with their IDs. Identifiers are based on the Products table from the Northwind database and accessed through the Entity Framework Core. The DashboardPanelController.Dashboards action method call returns this list.

You need to implement the custom dashboard storage to store dashboards. Call the IDashboardStorage.LoadDashboard method to return the corresponding dashboard from this storage. This example uses a single dashboard XML template (the DashboardTemplate.xml file) and only modifies the dashboard's title to emulate different dashboards. In this particular usage scenario, you can store dashboard layouts in the database and load them from here (for example, see Dashboard for ASP.NET Core - How to load and save dashboards from/to a database).

The List control is used to load and display the list of dashboards. The searchEnabled option of this control is enabled to allow search. You can use other widget options to enable the required functionality. For example, the itemDragging.allowReordering option allows end users to reorder items (see Item Drag & Drop).

NOTE: This example uses the ProductID database field as a dashboard's ID. The field's type is number while the DashboardInfo.ID property and the IDashboardStorage.LoadDashboard method's argument type is string. It is necessary to convert types. In this example, this is done in the NorthwindContext.OnModelCreating method (see NorthwindContext.cs).

Files to Review

Documentation

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

Models/NorthwindContext.cs
C#
using Microsoft.EntityFrameworkCore; namespace ASPNETCoreDashboardCustomPanel { public partial class NorthwindContext : DbContext { public NorthwindContext(DbContextOptions<NorthwindContext> options) : base(options) { } public virtual DbSet<Product> Products { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder .Entity<Product>() .Property(e => e.ProductID) .HasConversion<string>(); } } }
Program.cs
C#
using DevExpress.DataAccess.Sql; using DevExpress.AspNetCore; using DevExpress.DashboardAspNetCore; using DevExpress.DashboardCommon; using DevExpress.DashboardWeb; using Microsoft.Extensions.FileProviders; using Microsoft.EntityFrameworkCore; using ASPNETCoreDashboardCustomPanel; using DevExpress.DataAccess.Json; using ASPNETCoreDashboardCustomPanel.Code; var builder = WebApplication.CreateBuilder(args); IFileProvider? fileProvider = builder.Environment.ContentRootFileProvider; IConfiguration? configuration = builder.Configuration; // Add services to the container. builder.Services.AddRazorPages(); builder.Services .AddEntityFrameworkSqlite() .AddDbContext<NorthwindContext>(options => options .UseSqlite("Data Source=App_Data/nwind.db") ); builder.Services.AddDevExpressControls(); builder.Services.AddScoped<DashboardConfigurator>((IServiceProvider serviceProvider) => { DashboardConfigurator configurator = new DashboardConfigurator(); configurator.SetDashboardStorage( new CustomDashboardStorage(fileProvider.GetFileInfo("App_Data/Dashboards").PhysicalPath, serviceProvider.GetService<NorthwindContext>()!)); DataSourceInMemoryStorage dataSourceStorage = new DataSourceInMemoryStorage(); DashboardJsonDataSource jsonDataSourceSupport = new DashboardJsonDataSource("Support"); jsonDataSourceSupport.ConnectionName = "jsonSupport"; jsonDataSourceSupport.RootElement = "Employee"; dataSourceStorage.RegisterDataSource("jsonDataSourceSupport", jsonDataSourceSupport.SaveToXml()); configurator.SetDataSourceStorage(dataSourceStorage); configurator.SetConnectionStringsProvider(new DashboardConnectionStringsProvider(configuration)); configurator.ConfigureDataConnection += (s, e) => { if (e.ConnectionName == "jsonSupport") { Uri fileUri = new Uri(fileProvider.GetFileInfo("App_Data/Support.json").PhysicalPath, UriKind.RelativeOrAbsolute); JsonSourceConnectionParameters jsonParams = new JsonSourceConnectionParameters(); jsonParams.JsonSource = new UriJsonSource(fileUri); e.ConnectionParameters = jsonParams; } }; return configurator; }); builder.Services.AddDevExpressControls(settings => settings.Resources = ResourcesType.None); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseDevExpressControls(); EndpointRouteBuilderExtension.MapDashboardRoute(app, "api/dashboard", "DefaultDashboard"); app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); app.Run();
Controllers/DashboardPanelController.cs
C#
using System.Linq; using System.Threading.Tasks; using ASPNETCoreDashboardCustomPanel.Code; using DevExtreme.AspNet.Data; using Microsoft.AspNetCore.Mvc; namespace ASPNETCoreDashboardCustomPanel.Controllers { [Route("dashboardpanel")] public class DashboardPanelController : Controller { private NorthwindContext nwindContext; public DashboardPanelController(NorthwindContext nwindContext) { this.nwindContext = nwindContext; } [HttpGet("dashboards")] public async Task<IActionResult> Dashboards(DataSourceLoadOptions loadOptions) { var source = nwindContext.Products.Select(item => new { item.ProductID, item.ProductName }); loadOptions.PrimaryKey = new[] { "ProductID" }; loadOptions.PaginateViaPrimaryKey = true; return Json(await DataSourceLoader.LoadAsync(source, loadOptions)); } } }
Code/CustomDashboardStorage.cs
C#
using DevExpress.DashboardCommon; using DevExpress.DashboardWeb; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; namespace ASPNETCoreDashboardCustomPanel.Code { public class CustomDashboardStorage : IDashboardStorage { private string dashboardTemplateFolder; private NorthwindContext nwindContext; public CustomDashboardStorage(string dashboardTemplateFolder, NorthwindContext nwindContext) { this.dashboardTemplateFolder = dashboardTemplateFolder; this.nwindContext = nwindContext; } public IEnumerable<DashboardInfo> GetAvailableDashboardsInfo() { var dashboards = nwindContext.Products.Select(item => new DashboardInfo() { ID = item.ProductID, Name = item.ProductName }); return dashboards; } public XDocument LoadDashboard(string dashboardID) { var dashboard = new Dashboard(); var product = nwindContext.Products.First(product => product.ProductID == dashboardID); dashboard.LoadFromXml(Path.Combine(dashboardTemplateFolder, "DashboardTemplate.xml")); dashboard.Title.Text = product.ProductName; return dashboard.SaveToXDocument(); } public void SaveDashboard(string dashboardID, XDocument dashboard) { throw new NotImplementedException(); } } }
Models/Product.cs
C#
using System.ComponentModel.DataAnnotations; namespace ASPNETCoreDashboardCustomPanel { public class Product { [Key] public string? ProductID { get; set; } public string? ProductName { get; set; } public decimal? UnitPrice { get; set; } public short? UnitsOnOrder { get; set; } public int? CategoryID { get; set; } } }
Pages/Index.cshtml
Razor
@page @model IndexModel @{ Layout = null; } @using DevExpress.AspNetCore @using DevExpress.DashboardWeb @using DevExpress.DashboardAspNetCore <!DOCTYPE html> <html lang="en"> <head> <link href="~/css/site.min.css" rel="stylesheet" /> <script src="~/js/site.min.js"></script> <script> function onSelectionChanged(e) { dashboard1.option("dashboardId", e.component.option("selectedItemKeys")[0]); } </script> </head> <body> <div style="display: flex; height: 650px; border: 1px solid #ccc"> <div style="width: 250px; border-right: 1px solid #ccc"> @(Html.DevExtreme().List() .DataSource(d => d.Mvc() .Key("ID") .LoadAction("Dashboards") .Controller("DashboardPanel") ) .KeyExpr("productID") .DisplayExpr("productName") .SearchExpr("productName") .SearchEnabled(true) .SelectionMode(ListSelectionMode.Single) .PageLoadMode(ListPageLoadMode.ScrollBottom) .OnSelectionChanged("onSelectionChanged") ) </div> <div style="flex-grow: 1"> @(Html.DevExpress().Dashboard("dashboard1") .ControllerName("DefaultDashboard") .Width("100%").Height("100%") .LoadDefaultDashboard(false) ) </div> </div> </body> </html>

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.