Connect the DevExpress WinForms Data Grid to a Backend using a Middle Tier Server (EF Core without OData)
This example uses the Middle Tier Server that ships as part of the DevExpress XAF (Cross-Platform .NET App UI) to securely communicate with a backend. By using a middle tire server, you can reduce security-related risks associated with direct database connections. When a middle tier is used, desktop UI clients cannot access database connection information or modify database tables directly (middleware can prevent SQL injection and other database-related threat vectors).
EF Core developers can retain their standard DbContext and initiate remote and secure connections to a database from a .NET client. Our Middle Tier solution "replaces" direct database connections with middleware. Once implemented only authorized users can access specific/sensitive data or perform specific actions. This implementation also helps reduce code duplication, as the same logic can be reused by multiple clients.
The example in this repository demonstrates the following:
- How to build a data model for application business entities and security policies with EF Core.
- How to use the DevExpress ASP.ENT Core Middle Tier Security Server to connect a Windows Forms .NET 8 client application to a backend.
- How to define access permissions and activate authentication/authorization for a .NET 8 WinForms app.
- How to create a login form.
- How to customize UI/UX for a given user based on associated access permissions.
- How to create an edit form to modify and save data (CRUD).
- Visual Studio 2022 v17.0+
- .NET SDK 8.0+
- Download and run our Unified Component Installer or add NuGet feed URL to Visual Studio NuGet feeds.
We recommend that you select all products when you run the DevExpress installer. Doing so will register local NuGet package sources and item/project templates. You can uninstall unnecessary components at a later time.
-
Open MiddleTierExample.sln. Register a DevExpress NuGet feed in the Visual Studio IDE. Skip this step if you have already registered your DevExpress NuGet feed.
-
Restore NuGet packages in the solution.
-
Run the MiddleTier.Server project. Right-click MiddleTier.Server in the Solution Explorer and select Debug | Start New Instance in the menu. Your default web browser will open. Starting the server for the first time can take up to 20 seconds.
Note: Do not close the web browser.
-
Run the WinForms.Client project. Right-click WinForms.Client in the Solution Explorer and select Debug | Start New Instance in the menu.
-
Click "Log in" to proceed. The password for Admin and User accounts is an empty string. You can sort, filter, and group grid rows (records) as needed. Double-click a grid row to open the edit form and modify cell values.
The RemoteContextUtils
class implements utility APIs related to remote DbContext
and secured communication within client applications.
The CreateSecuredClient
method securely connects to an EF Core Middle Tier Security application:
public static WebApiSecuredDataServerClient CreateSecuredClient(string url, string login, string password) {
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(url);
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
var securedClient = new WebSocketSecuredDataServerClient(httpClient, XafTypesInfo.Instance);
securedClient.CustomAuthenticate += (sender, arguments) => {
arguments.Handled = true;
HttpResponseMessage msg = arguments.HttpClient.PostAsJsonAsync("api/Authentication/Authenticate", (AuthenticationStandardLogonParameters)arguments.LogonParameters).GetAwaiter().GetResult();
string token = (string)msg.Content.ReadFromJsonAsync(typeof(string)).GetAwaiter().GetResult();
if(msg.StatusCode == HttpStatusCode.Unauthorized) {
throw new UserFriendlyException(token);
}
msg.EnsureSuccessStatusCode();
arguments.HttpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("bearer", token);
};
securedClient.Authenticate(new AuthenticationStandardLogonParameters(login, password));
((IMiddleTierServerSecurity)securedClient).Logon();
return securedClient;
}
The RemoteContextUtils.GetDBContext()
method returns a secured DbContext for CRUD operations:
public static DXApplication1EFCoreDbContext GetDBContext() {
return new DXApplication1EFCoreDbContext(Options);
}
public static DbContextOptions<DXApplication1EFCoreDbContext> CreateDbContextOptions(WebApiSecuredDataServerClient securedClient) {
var builder = new DbContextOptionsBuilder<DXApplication1EFCoreDbContext>();
return builder
.UseLazyLoadingProxies()
.UseChangeTrackingProxies()
.UseMiddleTier(securedClient)
.Options as DbContextOptions<DXApplication1EFCoreDbContext>;
}
This example creates Admin and User roles and defines the following permissions:
- Admin: Read/Write (full access)
- User Read Only (data editing is not permitted)
public class Updater : ModuleUpdater {
private const string AdministratorUserName = "Admin";
private const string AdministratorRoleName = "Administrators";
private const string DefaultUserName = "User";
private const string DefaultUserRoleName = "Users";
//...
private PermissionPolicyRole GetUserRole() {
PermissionPolicyRole userRole = ObjectSpace.FirstOrDefault<PermissionPolicyRole>(u => u.Name == DefaultUserRoleName);
if(userRole == null) {
userRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
userRole.Name = DefaultUserRoleName;
userRole.AddTypePermission<Employee>(SecurityOperations.Read, SecurityPermissionState.Allow);
userRole.AddTypePermission<Employee>(SecurityOperations.Write, SecurityPermissionState.Deny);
}
return userRole;
}
//...
}
LoginForm contains userNameEdit
and passwordEdit
text editors for user credentials. The "Log In" button attempts to log the user into the security system and returns DialogResult.OK
if a login was successful:
namespace WinForms.Client {
internal static class Program {
[STAThread]
static void Main() {
//…
while(!RemoteContextUtils.IsLogin)
using(AuthForm authForm = new AuthForm()) {
if(authForm.ShowDialog() == DialogResult.OK) {
MiddleTierStartupHelper.WaitMiddleTierServerReady(MiddleTierStartupHelper.EFCoreWebApiMiddleTierInstanceKey, TimeSpan.MaxValue);
// User authorization.
var securedClient = RemoteContextUtils.CreateSecuredClient(System.Configuration.ConfigurationManager.AppSettings["endpointUrl"], authForm.Login, authForm.Password);
RemoteContextUtils.SecuredDataServerClient = securedClient;
DbContextOptions<DXApplication1EFCoreDbContext> options = RemoteContextUtils.CreateDbContextOptions(securedClient);
RemoteContextUtils.Options = options;
Application.Run(new MainForm());
}
else
break;
}
}
}
}
The SetUpBinding
method creates a DbContext, uses it as a queryable data source for the server mode source (EntityServerModeSource
), and binds the grid to the server mode source.
namespace WinForms.Client {
public partial class MainForm : RibbonForm {
EntityServerModeSource serverModeSource = new EntityServerModeSource();
DXApplication1EFCoreDbContext dbContext = null;
public MainForm() {
InitializeComponent();
SetUpBinding();
}
void SetUpBinding() {
dbContext?.Dispose();
dbContext = RemoteContextUtils.GetDBContext();
serverModeSource = new EntityServerModeSource() { ElementType = typeof(Employee), KeyExpression = "ID" };
serverModeSource.QueryableSource = dbContext.Employees;
gridControl.DataSource = serverModeSource;
}
}
}
Highlighted code snippets enable/disable Ribbon commands (New, Edit, Delete) based on access permissions for the current (logged in) user:
public MainForm() {
InitializeComponent();
SetUpBinding();
bbiNew.Enabled = RemoteContextUtils.IsGranted(typeof(Employee), SecurityOperations.Create);
bbiDelete.Enabled = RemoteContextUtils.IsGranted(typeof(Employee), SecurityOperations.Delete);
bbiEdit.Enabled = RemoteContextUtils.IsGranted(typeof(Employee), SecurityOperations.Write);
}
- Middle Tier Security with EF Core
- Create a WinForms Application (.NET) with EF Core Middle Tier Security
- WinForms — Connect a .NET Desktop Client to a Secure Backend Web API Service (EF Core with OData)
(you will be redirected to DevExpress.com to submit your response)