This example uses a loosely coupled architecture to share codebase between desktop (WPF or WinForms) and mobile (.NET MAUI) UI clients. The application displays orders loaded from a Web service and allows users to generate a report. For more information, review the following blog posts:
- Choosing a Framework/App Architecture for Desktop & Mobile Cross-Platform Apps
- Modern Desktop Apps and Their Complex Architectures
- Rebuild the solution.
- Start the WebApiService project without debugging.
- Start the WPFDesktopClient, WinFormsDesktopClient or MobileClient project.
The following schema outlines application architecture:
The application includes the following projects:
- Client.Shared. Contains client-side services and common helpers.
- DataModel. A model for database objects.
- WPFDesktopClient - WPF application.
- WinFormsDesktopClient - WinForms application.
- MobileClient - .NET MAUI application.
- WebApiService - a web service that handles access to a database.
Note: Although this example does not include authentication and role-based data access, you can use the free DevExpress Web API Service to generate a project with this capability.
Desktop and mobile clients reuse the following classes:
OrderDataService
. Incorporates logic to communicate with the Web API service.ReportService
. Generates a report based on the selected order.
We use Dependency Injection to introduce these services into desktop and mobile projects.
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
var builder = new ContainerBuilder();
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
builder.RegisterType<ReportService>().As<IReportService>().SingleInstance();
builder.RegisterInstance<IOrderDataService>(new OrderDataService(new HttpClient() {
BaseAddress = new Uri("https://localhost:7033/"),
Timeout = new TimeSpan(0, 0, 10)
}));
IContainer container = builder.Build();
DISource.Resolver = (type) =>
{
return container.Resolve(type);
};
}
static void Main() {
//...
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
builder.RegisterType<ReportService>().As<IReportService>().SingleInstance();
builder.RegisterInstance<IOrderDataService>(new OrderDataService(new HttpClient() {
BaseAddress = new Uri("https://localhost:7033/"),
Timeout = new TimeSpan(0, 0, 10)
}));
container = builder.Build();
MVVMContextCompositionRoot.ViewModelCreate += (s, e) =>
{
e.ViewModel = container.Resolve(e.RuntimeViewModelType);
};
//...
}
public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder) {
mauiAppBuilder.Services.AddTransient<OrdersViewModel>();
mauiAppBuilder.Services.AddTransient<InvoicePreviewViewModel>();
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder) {
mauiAppBuilder.Services.AddTransient<OrdersPage>();
mauiAppBuilder.Services.AddTransient<InvoiceReportPreviewPage>();
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder) {
mauiAppBuilder.Services.AddTransient<IOrderDataService>(sp => new OrderDataService(new HttpClient(MyHttpMessageHandler.GetMessageHandler()) {
//OS-specific URLs are used because Android and iOS emulators use different addresses to access the local machine: https://learn.microsoft.com/en-us/dotnet/maui/data-cloud/local-web-services?view=net-maui-7.0#local-machine-address
BaseAddress = new Uri(ON.Platform(android: "https://10.0.2.2:7033/", iOS: "https://localhost:7033/")),
Timeout = new TimeSpan(0, 0, 10)
})); ;
mauiAppBuilder.Services.AddTransient<IReportService, ReportService>();
mauiAppBuilder.Services.AddTransient<INavigationService, NavigationService>();
return mauiAppBuilder;
}
The Web API service includes basic endpoints to retrieve orders from a database connected with Entity Framework Core:
public class OrdersController : ControllerBase {
//...
[HttpGet]
public async Task<ActionResult<IEnumerable<Order>>> GetOrders() {
return await _context.Orders.Include(order => order.Customer)
.Include(order => order.Items)
.ThenInclude(orderItem => orderItem.Product)
.ToListAsync();
}
}
Both client and server sides use the same model to work with business objects.
public class Customer {
//...
}
public class Order {
//...
}
public class Product {
//...
}
- OrderDataService.cs
- ReportService.cs
- OrdersController.cs
- DesktopClient/App.xaml.cs
- MobileClient/MauiProgram.cs
- Get Started with DevExpress Controls for .NET Multi-platform App UI
- Get Started with DevExpress WPF Controls
- Get Started with DevExpress WinForms Controls
(you will be redirected to DevExpress.com to submit your response)