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
Users Log In
Each person has a role-based account. They only see dashboards and data sources they are authorized to access.
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.
Save and Organize
Dashboards are saved to the server, tagged by owner. Only the creator can see it unless they share it.
Share with Colleagues
The owner can share a dashboard as view-only or editable with specific people or teams. Sharing is granular and revocable.
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.
Linda Kowalski
CEONeeds 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.
James Mokoena
Sales DirectorTracks 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.
Sarah Petersen
Operations ManagerMonitors warehouse throughput, order fulfillment, shipping delays, and inventory levels. Her dashboards are shared with warehouse supervisors. Access to Operations and Inventory data sources.
Raj Naidoo
Finance LeadBuilds 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.
Ahmed Mansour
System AdminManages 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:
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:
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:
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.
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.
[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.
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:
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.
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);
}
}
Wiring It All Together in Startup
The final piece is registering the custom storage providers and the DevExpress dashboard endpoint in your application startup:
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
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.