Build Dashboards Your Team Can Create, Customize, and Share

Every business reaches a point where spreadsheets and static reports stop being enough. Managers want to see their own numbers. Sales wants a pipeline view. Operations wants real-time warehouse stats. Finance wants a P&L dashboard they can drill into. And nobody wants to wait for a developer every time they need a new chart added.

This is the exact scenario the DevExpress Dashboard component was built for. It lets you create a web-based dashboard platform where authorized users can build their own private dashboards, drag in the data sources they have access to, configure charts, grids, and KPIs, and then share those dashboards with specific colleagues, all without writing a single line of code.

In this article I walk through how this kind of application works end to end: the architecture, the people who use it, the data that powers it, and the sharing and permissions model that keeps everything private and secure. This is based on patterns I have built over 10 years of DevExpress development for real clients across finance, logistics, insurance, and manufacturing.

How the Application Works

1

Users Log In

Each person has a role-based account. They only see dashboards and data sources they are authorized to access.

2

Create a Private Dashboard

Users click "New Dashboard" and get a drag-and-drop designer. They pick data sources, add charts, grids, gauges, and KPI cards.

3

Save and Organize

Dashboards are saved to the server, tagged by owner. Only the creator can see it unless they share it.

4

Share with Colleagues

The owner can share a dashboard as view-only or editable with specific people or teams. Sharing is granular and revocable.

5

View, Filter, and Export

Shared users open the dashboard, apply their own filters, drill into charts, and export to PDF or Excel, all without affecting the original.

Meet the Team: Who Uses This Application

To make this walkthrough concrete, here is the team at a fictional mid-sized distribution company called Meridian Logistics. Each person uses the dashboard application differently.

LK

Linda Kowalski

CEO

Needs a high-level company overview: revenue trends, margin by region, headcount, and customer growth. Shares her executive dashboard with the board. View-only access to all data sources.

JM

James Mokoena

Sales Director

Tracks pipeline value, win rate, rep performance, and quarterly targets. Creates dashboards for each sales region and shares them with regional managers. Access to Sales and Customer data sources.

SP

Sarah Petersen

Operations Manager

Monitors warehouse throughput, order fulfillment, shipping delays, and inventory levels. Her dashboards are shared with warehouse supervisors. Access to Operations and Inventory data sources.

RN

Raj Naidoo

Finance Lead

Builds P&L dashboards, cash flow trackers, and expense breakdowns. Shares monthly finance dashboards with the CEO and department heads. Access to Finance and Billing data sources.

AM

Ahmed Mansour

System Admin

Manages users, data source connections, and permissions. Can see all dashboards for auditing but does not create business dashboards. Full admin access.

Application Architecture

The dashboard application follows a standard ASP.NET MVC pattern with the DevExpress Dashboard component handling the heavy lifting on the front end. Here is the high-level architecture:

Browser
DevExpress Dashboard Viewer
DevExpress Dashboard Designer
Dashboard List & Sharing UI
↓ ↑
ASP.NET MVC
DashboardController
CustomDashboardStorage
CustomDataSourceStorage
↓ ↑
Data Layer
SQL Server (Dashboard Metadata)
Business Database (Sales, Ops, Finance)
Project Structure MVC Pattern
MeridianDashboards/
|-- Controllers/
|   |-- DashboardController.cs        // Main dashboard CRUD + sharing
|   |-- AccountController.cs          // Login, roles, permissions
|   |-- AdminController.cs            // Data source management
|
|-- Models/
|   |-- Dashboard.cs                  // Dashboard entity (owner, name, xml)
|   |-- DashboardShare.cs             // Share record (dashboardId, userId, permission)
|   |-- DataSourcePermission.cs       // Which roles can access which data
|   |-- AppUser.cs                    // User with role
|
|-- Services/
|   |-- CustomDashboardStorage.cs     // Implements IDashboardStorage
|   |-- CustomDataSourceStorage.cs    // Controls data source access per user
|   |-- DashboardSharingService.cs    // Share, revoke, list shared dashboards
|
|-- Views/
|   |-- Dashboard/
|   |   |-- Index.cshtml              // "My Dashboards" list
|   |   |-- Designer.cshtml           // Dashboard designer (create/edit)
|   |   |-- Viewer.cshtml             // View-only dashboard
|   |   |-- SharedWithMe.cshtml       // Dashboards others shared with me
|   |
|   |-- Admin/
|       |-- DataSources.cshtml        // Manage data connections
|       |-- Users.cshtml              // Manage users and roles
|
|-- App_Start/
    |-- DashboardConfig.cs            // Register dashboard routes and storage

The Data Model

The core of the sharing system is three simple tables: Dashboards, Shares, and Data Source Permissions. Here are the models:

Models/Dashboard.cs Entity Framework
public class Dashboard
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    // The DevExpress dashboard XML definition
    public string DashboardXml { get; set; }

    // Ownership
    public int OwnerId { get; set; }
    public virtual AppUser Owner { get; set; }

    // Metadata
    public DateTime CreatedAt { get; set; }
    public DateTime ModifiedAt { get; set; }
    public bool IsArchived { get; set; }

    // Navigation
    public virtual ICollection<DashboardShare> Shares { get; set; }
}

public class DashboardShare
{
    public int Id { get; set; }
    public int DashboardId { get; set; }
    public int SharedWithUserId { get; set; }

    // "View" or "Edit"
    public string Permission { get; set; }

    public DateTime SharedAt { get; set; }
    public int SharedByUserId { get; set; }

    // Navigation
    public virtual Dashboard Dashboard { get; set; }
    public virtual AppUser SharedWithUser { get; set; }
    public virtual AppUser SharedByUser { get; set; }
}

public class DataSourcePermission
{
    public int Id { get; set; }
    public string DataSourceName { get; set; }  // "Sales", "Finance", etc.
    public string Role { get; set; }            // "SalesDirector", "CEO", etc.
    public bool CanAccess { get; set; }
}

Custom Dashboard Storage: The Key to Private Dashboards

DevExpress provides an IDashboardStorage interface. By implementing a custom version, you control which dashboards each user can see. This is where privacy and sharing live:

Services/CustomDashboardStorage.cs IDashboardStorage
public class CustomDashboardStorage : IDashboardStorage
{
    private readonly AppDbContext _db;
    private readonly int _currentUserId;

    public CustomDashboardStorage(AppDbContext db, int currentUserId)
    {
        _db = db;
        _currentUserId = currentUserId;
    }

    // Returns only dashboards the current user owns or has been shared
    public IEnumerable<DashboardInfo> GetAvailableDashboardsInfo()
    {
        var owned = _db.Dashboards
            .Where(d => d.OwnerId == _currentUserId && !d.IsArchived)
            .Select(d => new DashboardInfo
            {
                ID = d.Id.ToString(),
                Name = d.Name
            });

        var shared = _db.DashboardShares
            .Where(s => s.SharedWithUserId == _currentUserId)
            .Select(s => new DashboardInfo
            {
                ID = s.Dashboard.Id.ToString(),
                Name = s.Dashboard.Name + " (shared)"
            });

        return owned.Concat(shared).ToList();
    }

    // Load dashboard XML - only if user has access
    public XDocument LoadDashboard(string dashboardId)
    {
        int id = int.Parse(dashboardId);
        var dashboard = _db.Dashboards.Find(id);

        if (dashboard == null)
            throw new InvalidOperationException("Dashboard not found.");

        if (!UserCanAccess(id))
            throw new UnauthorizedAccessException("Access denied.");

        return XDocument.Parse(dashboard.DashboardXml);
    }

    // Save - only owner or users with Edit permission
    public void SaveDashboard(string dashboardId, XDocument document)
    {
        int id = int.Parse(dashboardId);

        if (!UserCanEdit(id))
            throw new UnauthorizedAccessException("Edit access denied.");

        var dashboard = _db.Dashboards.Find(id);
        dashboard.DashboardXml = document.ToString();
        dashboard.ModifiedAt = DateTime.UtcNow;
        _db.SaveChanges();
    }

    private bool UserCanAccess(int dashboardId)
    {
        return _db.Dashboards.Any(d => d.Id == dashboardId
                   && d.OwnerId == _currentUserId)
            || _db.DashboardShares.Any(s => s.DashboardId == dashboardId
                   && s.SharedWithUserId == _currentUserId);
    }

    private bool UserCanEdit(int dashboardId)
    {
        return _db.Dashboards.Any(d => d.Id == dashboardId
                   && d.OwnerId == _currentUserId)
            || _db.DashboardShares.Any(s => s.DashboardId == dashboardId
                   && s.SharedWithUserId == _currentUserId
                   && s.Permission == "Edit");
    }
}

The Sharing Service

Sharing is handled by a dedicated service. The owner of a dashboard can share it with any user in the system, choose between View and Edit permissions, and revoke access at any time.

Services/DashboardSharingService.cs Share + Revoke
public class DashboardSharingService
{
    private readonly AppDbContext _db;

    public DashboardSharingService(AppDbContext db)
    {
        _db = db;
    }

    public void ShareDashboard(int dashboardId, int ownerUserId,
        int targetUserId, string permission)
    {
        // Only the owner can share
        var dashboard = _db.Dashboards.Find(dashboardId);
        if (dashboard.OwnerId != ownerUserId)
            throw new UnauthorizedAccessException("Only the owner can share.");

        // Prevent duplicate shares
        var existing = _db.DashboardShares
            .FirstOrDefault(s => s.DashboardId == dashboardId
                && s.SharedWithUserId == targetUserId);

        if (existing != null)
        {
            existing.Permission = permission;  // Update permission level
        }
        else
        {
            _db.DashboardShares.Add(new DashboardShare
            {
                DashboardId = dashboardId,
                SharedWithUserId = targetUserId,
                SharedByUserId = ownerUserId,
                Permission = permission,  // "View" or "Edit"
                SharedAt = DateTime.UtcNow
            });
        }

        _db.SaveChanges();
    }

    public void RevokeDashboardAccess(int dashboardId,
        int ownerUserId, int targetUserId)
    {
        var dashboard = _db.Dashboards.Find(dashboardId);
        if (dashboard.OwnerId != ownerUserId)
            throw new UnauthorizedAccessException("Only the owner can revoke.");

        var share = _db.DashboardShares
            .FirstOrDefault(s => s.DashboardId == dashboardId
                && s.SharedWithUserId == targetUserId);

        if (share != null)
        {
            _db.DashboardShares.Remove(share);
            _db.SaveChanges();
        }
    }

    public List<SharedDashboardViewModel> GetDashboardsSharedWithMe(
        int userId)
    {
        return _db.DashboardShares
            .Where(s => s.SharedWithUserId == userId)
            .Select(s => new SharedDashboardViewModel
            {
                DashboardId = s.DashboardId,
                DashboardName = s.Dashboard.Name,
                SharedByName = s.SharedByUser.FullName,
                Permission = s.Permission,
                SharedAt = s.SharedAt
            })
            .OrderByDescending(s => s.SharedAt)
            .ToList();
    }
}

The Dashboard Controller

The controller ties everything together: listing dashboards, opening the designer or viewer, and managing shares.

Controllers/DashboardController.cs MVC Controller
[Authorize]
public class DashboardController : Controller
{
    private readonly AppDbContext _db = new AppDbContext();
    private readonly DashboardSharingService _sharing;

    public DashboardController()
    {
        _sharing = new DashboardSharingService(_db);
    }

    // My Dashboards - shows owned + shared
    public ActionResult Index()
    {
        int userId = GetCurrentUserId();

        var myDashboards = _db.Dashboards
            .Where(d => d.OwnerId == userId && !d.IsArchived)
            .OrderByDescending(d => d.ModifiedAt)
            .ToList();

        var sharedWithMe = _sharing.GetDashboardsSharedWithMe(userId);

        var model = new DashboardIndexViewModel
        {
            MyDashboards = myDashboards,
            SharedWithMe = sharedWithMe
        };

        return View(model);
    }

    // Open designer for creating or editing
    public ActionResult Designer(int? id)
    {
        int userId = GetCurrentUserId();

        if (id.HasValue)
        {
            // Editing existing - check permission
            var dashboard = _db.Dashboards.Find(id.Value);
            if (dashboard.OwnerId != userId)
            {
                var share = _db.DashboardShares
                    .FirstOrDefault(s => s.DashboardId == id.Value
                        && s.SharedWithUserId == userId
                        && s.Permission == "Edit");

                if (share == null)
                    return new HttpStatusCodeResult(403);
            }
        }

        ViewBag.DashboardId = id;
        return View();
    }

    // View-only mode
    public ActionResult Viewer(int id)
    {
        int userId = GetCurrentUserId();
        var storage = new CustomDashboardStorage(_db, userId);

        // This will throw if user has no access
        var dashboard = storage.LoadDashboard(id.ToString());

        ViewBag.DashboardId = id;
        ViewBag.DashboardName = _db.Dashboards.Find(id).Name;
        return View();
    }

    // Share dialog
    [HttpPost]
    public ActionResult Share(int dashboardId,
        int targetUserId, string permission)
    {
        int userId = GetCurrentUserId();
        _sharing.ShareDashboard(dashboardId, userId,
            targetUserId, permission);

        return Json(new { success = true });
    }

    // Revoke access
    [HttpPost]
    public ActionResult Revoke(int dashboardId, int targetUserId)
    {
        int userId = GetCurrentUserId();
        _sharing.RevokeDashboardAccess(dashboardId,
            userId, targetUserId);

        return Json(new { success = true });
    }
}

What the Dashboards Look Like

Below are examples of the dashboards each team member at Meridian Logistics has built. These demonstrate the kinds of widgets, data, and layouts that users create with the DevExpress Dashboard designer.

LK
Linda Kowalski CEO
Shared with Board (View Only)
Executive Overview - Q1 2026
Revenue YTD $4.2M +12.3% vs LY
Gross Margin 34.8% +2.1pp vs LY
Active Customers 1,847 +156 new
Headcount 312 +8 this quarter
Monthly Revenue Trend
$1.28M
Jan
$1.41M
Feb
$1.54M
Mar
Revenue by Region
North America 35%
Europe 23%
APAC 17%
Other 25%
JM
James Mokoena Sales Director
Shared with Regional Managers (View Only)
Sales Pipeline - March 2026
Pipeline Value $2.8M +18% vs Feb
Win Rate 38.2% +3.4pp
Avg Deal Size $24.5K Flat
Deals Closing This Month 47 +9 vs Feb
Top Deals by Value
Company Rep Value Stage Expected Close
Hartley Manufacturing K. van Wyk $185,000 Negotiation 28 Mar 2026
Pinnacle Group T. Osei $142,000 Proposal 04 Apr 2026
Oceanic Foods A. Singh $98,500 Qualified 15 Apr 2026
Redstone Logistics K. van Wyk $87,200 Negotiation 31 Mar 2026
Atlas Construction B. Dlamini $76,000 Proposal 10 Apr 2026
SP
Sarah Petersen Operations Manager
Shared with Warehouse Leads (Edit)
Warehouse Operations - Live
Orders Today 284 +22 vs avg
Fulfillment Rate 96.4% Target: 95%
Avg Ship Time 1.8 hrs +0.3 vs target
Backorders 23 +7 vs yesterday
Fulfillment Rate by Warehouse
Johannesburg
98.1%
Cape Town
96.3%
Durban
95.2%
Nairobi
89.7%
RN
Raj Naidoo Finance Lead
Shared with CEO + Dept Heads (View Only)
Financial Summary - March 2026
Net Profit MTD $387K +8.4% vs budget
Operating Expenses $1.14M On budget
Cash Position $2.6M Healthy
AR Overdue $218K 14 invoices
Expense Breakdown by Department
Department Budget Actual Variance Budget Used
Sales & Marketing $320,000 $298,400 -$21,600
Operations $480,000 $472,100 -$7,900
Technology $185,000 $201,300 +$16,300
Human Resources $95,000 $88,700 -$6,300
Facilities $60,000 $57,200 -$2,800
Total $1,140,000 $1,117,700 -$22,300

Dashboard Sharing and Permissions at a Glance

Here is a complete view of who owns what and who can see what across the Meridian Logistics dashboard application:

Dashboard Owner Shared With Permission Data Sources Used
Executive Overview LK Linda K. Board Members (4) View Sales, Finance, HR
Sales Pipeline JM James M. Regional Managers (3) View Sales, Customers
North Region Sales JM James M. K. van Wyk Edit Sales
Warehouse Operations SP Sarah P. Warehouse Leads (3) Edit Operations, Inventory
Financial Summary RN Raj N. Linda K., Dept Heads (4) View Finance, Billing
Cash Flow Tracker RN Raj N. Not shared Private Finance

Data Source Security: Who Can Access What

Even if a dashboard is shared, users can only see data from sources their role permits. The CustomDataSourceStorage enforces this server-side. A sales manager cannot accidentally drag a Finance data source onto their dashboard because the system never exposes it to them.

Services/CustomDataSourceStorage.cs Role-based filtering
public class CustomDataSourceStorage : IDataSourceStorage
{
    private readonly AppDbContext _db;
    private readonly string _userRole;

    public CustomDataSourceStorage(AppDbContext db, string userRole)
    {
        _db = db;
        _userRole = userRole;
    }

    public IEnumerable<string> GetAvailableDataSourceIds()
    {
        // Only return data sources this role can access
        return _db.DataSourcePermissions
            .Where(p => p.Role == _userRole && p.CanAccess)
            .Select(p => p.DataSourceName)
            .ToList();
    }

    public DataSourceDocument GetDataSource(string dataSourceId)
    {
        // Verify permission before returning
        var hasAccess = _db.DataSourcePermissions
            .Any(p => p.Role == _userRole
                && p.DataSourceName == dataSourceId
                && p.CanAccess);

        if (!hasAccess)
            throw new UnauthorizedAccessException(
                $"Role '{_userRole}' cannot access '{dataSourceId}'.");

        return LoadDataSourceDefinition(dataSourceId);
    }
}

Data Source Access Matrix

Data Source CEO Sales Director Ops Manager Finance Lead Admin
Sales
Customers
Operations
Inventory
Finance
Billing
HR

Wiring It All Together in Startup

The final piece is registering the custom storage providers and the DevExpress dashboard endpoint in your application startup:

App_Start/DashboardConfig.cs Registration
public static class DashboardConfig
{
    public static void RegisterService(RouteCollection routes)
    {
        routes.MapDashboardRoute("dashboardControl",
            "DefaultDashboard");

        // Configure the dashboard with custom storage
        DashboardConfigurator.Default.SetDashboardStorage(
            new CustomDashboardStorageFactory());

        DashboardConfigurator.Default.SetDataSourceStorage(
            new CustomDataSourceStorageFactory());

        // Restrict designer features based on role
        DashboardConfigurator.Default.CustomizeDashboardArea +=
            (sender, e) =>
            {
                var user = HttpContext.Current.User;

                // View-only users cannot modify the layout
                if (!CanEditCurrentDashboard(user))
                {
                    e.DashboardArea.DashboardDesignerEnabled = false;
                }
            };
    }
}

// Factory creates per-request instances with the current user
public class CustomDashboardStorageFactory
    : IDashboardStorage
{
    public IEnumerable<DashboardInfo> GetAvailableDashboardsInfo()
    {
        var db = new AppDbContext();
        int userId = GetCurrentUserId();
        return new CustomDashboardStorage(db, userId)
            .GetAvailableDashboardsInfo();
    }

    // ... delegate other methods similarly
}

Key Features Summary

Here is what this architecture gives you out of the box:

  • Private by default: Every dashboard is visible only to its creator until explicitly shared
  • Granular sharing: Share with individual users or roles, with View or Edit permission, revocable at any time
  • Data source security: Users can only access data sources their role permits, enforced server-side regardless of dashboard sharing
  • Self-service design: Non-technical users build dashboards with a drag-and-drop designer, no developer needed for new charts or layouts
  • Interactive filtering: Viewers can apply personal filters and drill into charts without affecting the original dashboard
  • Export: Any dashboard can be exported to PDF, image, or Excel by any user who has view access
  • Audit trail: The admin can see all dashboards, sharing activity, and data source usage for compliance
  • Scalable: Dashboards are stored as XML definitions and rendered on demand. The system handles hundreds of users and dashboards without performance issues

Need This Built for Your Business?

This is the kind of application I build for clients using DevExpress. Whether you need a full private dashboard platform like the one described here, or you need to add self-service dashboard capabilities to an existing ASP.NET application, I can deliver it. With over 10 years of DevExpress experience, I have built dashboard applications for finance teams, operations departments, sales organizations, and executive leadership across multiple industries. The DevExpress Dashboard component is powerful, but it takes a DevExpress specialist to wire up the security, storage, sharing, and data access correctly. Let's build your dashboard.

Technologies I Use

.NET Core ASP.NET MVC C# SQL Server Azure Entity Framework

Frequently Asked Questions

Can non-technical users really build their own dashboards?

+

Yes. The DevExpress Dashboard Designer provides a drag-and-drop interface. Users pick a data source, drag fields onto chart axes, and configure widgets visually. No code is required. I typically run a 30-minute training session and most users are building their own dashboards within the first hour.

How secure is the dashboard sharing model?

+

Security is enforced at three levels: authentication (who can log in), data source access (role-based, server-side), and dashboard visibility (owner or explicitly shared). Even if someone guesses a dashboard ID, the custom storage layer blocks access unless they have a valid share record.

Can this work with our existing SQL Server database?

+

Absolutely. The DevExpress Dashboard connects to SQL Server, MySQL, PostgreSQL, Oracle, and other sources via standard .NET data providers. I configure the data source connections so users see friendly names like "Sales Data" or "Inventory" rather than raw table names.

Can dashboards update with live data?

+

Yes. Dashboards query the database when they are opened and can be configured to auto-refresh at intervals. For real-time scenarios like warehouse operations, I set refresh intervals as low as 30 seconds. The DevExpress component handles partial updates efficiently.

What does it cost to build a dashboard application like this?

+

The scope depends on how many data sources, how many user roles, and whether you need features like scheduled email reports or embedded dashboards. Contact me with your requirements and I will provide a clear estimate. Most projects of this type are completed within 4 to 8 weeks.