Example T1253821
Visible to All Users

Connect the DevExpress WinForms Data Grid to a .NET Core Service and Enable Data Editing

NOTE
This example extends the capabilities (introduces data editing support when using the WinForms Data Grid through accessible endpoints) of the following example: Connect the DevExpress WinForms Data Grid to a .NET Core Service. This example implements a REST-style access pattern.

Review the following step-by-step tutorial to run the example: Getting Started.

POST, PUT, and DELETE Endpoints

The ASP.NET Core MapPost, MapPut, and MapDelete methods manage different HTTP methods when processing incoming requests.

  • MapPost: Manages HTTP POST requests, generally used to create new data.
  • MapPut: Processes HTTP PUT requests, used to update existing data.
  • MapDelete: Manages HTTP DELETE requests, used to remove records.
C#
app.MapPost("/data/OrderItem", async (DataServiceDbContext dbContext, OrderItem orderItem) => { dbContext.OrderItems.Add(orderItem); await dbContext.SaveChangesAsync(); return Results.Created($"/data/OrderItem/{orderItem.Id}", orderItem); }); app.MapPut("/data/OrderItem/{id}", async (DataServiceDbContext dbContext, int id, OrderItem orderItem) => { if (id != orderItem.Id) { return Results.BadRequest("Id mismatch"); } dbContext.Entry(orderItem).State = EntityState.Modified; await dbContext.SaveChangesAsync(); return Results.NoContent(); }); app.MapDelete("/data/OrderItem/{id}", async (DataServiceDbContext dbContext, int id) => { var orderItem = await dbContext.OrderItems.FindAsync(id); if (orderItem is null) { return Results.NotFound(); } dbContext.OrderItems.Remove(orderItem); await dbContext.SaveChangesAsync(); return Results.NoContent(); });

Data Editing Form

This example includes a standalone data editing form. The edit form is used to modify existing data rows and add new rows.

Data Editing Form

The example uses the DevExpress WinForms Form Layout component to automatically arrange DevExpress Data Editors and eliminate the hassles associated with pixel-based form design.

DataServiceClient

The DataServiceClient class is an abstraction designed to simplify data service interactions (by encapsulating common HTTP operations).

Fetch Data

The following code in VirtualServerModeDataLoader.cs calls the DataServiceClient.GetOrderItemsAsync method to fetch data. The GetOrderItemsAsync method fetches a list of order items by specifying row counts, batch sizes, and sorting parameters:

C#
public Task<VirtualServerModeRowsTaskResult> GetRowsAsync(VirtualServerModeRowsEventArgs e) { return Task.Run(async () => { Debug.WriteLine($"Fetching data rows {e.CurrentRowCount} to {e.CurrentRowCount + BatchSize}, sorting by {SortField} ({(SortAscending ? "asc" : "desc")})"); var dataFetchResult = await DataServiceClient.GetOrderItemsAsync(e.CurrentRowCount, BatchSize, SortField, SortAscending); if (dataFetchResult is null) return new VirtualServerModeRowsTaskResult(); var moreRowsAvailable = e.CurrentRowCount + dataFetchResult.Items.Count < dataFetchResult.TotalCount; Debug.WriteLine($"Returning {dataFetchResult.Items.Count} items, more rows available: {moreRowsAvailable}"); return new VirtualServerModeRowsTaskResult(dataFetchResult.Items, moreRowsAvailable); }, e.CancellationToken); }

Update Data

The DataServiceClient.UpdateOrderItemAsync method encodes the transfer object as JSON and sends it to the service URL using a PUT request. The UpdateOrderItemAsync method invokes EnsureSuccessStatusCode() after the PUT request to ensure the object is successfully updated.

C#
public static async Task UpdateOrderItemAsync(OrderItem orderItem) { using var client = CreateClient(); var response = await client.PutAsync($"{baseUrl}/data/OrderItem/{orderItem.Id}", new StringContent(JsonSerializer.Serialize(orderItem), Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); }

The edit form is displayed when a user double-clicks a data row:

C#
async void gridView1_DoubleClick(object sender, EventArgs e) { if (sender is GridView view) { if (view.FocusedRowObject is OrderItem oi) { var editResult = EditForm.EditItem(oi); if (editResult.changesSaved) { await DataServiceClient.UpdateOrderItemAsync(editResult.item); view.RefreshData(); } } } }

Create New Item

The DataServiceClient.CreateOrderItemAsync method creates a new OrderItem. The CreateOrderItemAsync method serializes the object, sends it to the service, and deserializes the response back into an OrderItem. This pattern supports server-generated values, such as auto-incremented keys.

C#
public static async Task<OrderItem?> CreateOrderItemAsync(OrderItem orderItem) { using var client = CreateClient(); var response = await client.PostAsync($"{baseUrl}/data/OrderItem", new StringContent(JsonSerializer.Serialize(orderItem), Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); return responseBody.AsOrderItem(); } static OrderItem? AsOrderItem(this string responseBody) { return JsonSerializer.Deserialize<OrderItem>(responseBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); }

To add a new data row, click the "Add Order Item" item within the toolbar.

C#
async void addItemButton_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { if (gridControl.FocusedView is ColumnView view) { var createResult = EditForm.CreateItem(); if (createResult.changesSaved) { await DataServiceClient.CreateOrderItemAsync(createResult.item!); view.RefreshData(); } } }

Delete Item

The DataServiceClient.DeleteOrderItemAsync method catches any exceptions and returns a Boolean value to indicate success or failure. The DeleteOrderItemAsync method logs errors using Debug.WriteLine() and returns false if an error occurs (allowing the UI or business logic to respond appropriately).

C#
public static async Task<bool> DeleteOrderItemAsync(int id) { try { using var client = CreateClient(); var response = await client.DeleteAsync($"{baseUrl}/data/OrderItem/{id}"); response.EnsureSuccessStatusCode(); return true; } catch (Exception ex) { Debug.WriteLine(ex); return false; } }

To delete the focused data row, click the "Delete Focused Item" item within the toolbar.

C#
async void deleteItemButton_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { if (gridControl.FocusedView is ColumnView view && view.GetFocusedRow() is OrderItem orderItem) { await DataServiceClient.DeleteOrderItemAsync(orderItem.Id); view.RefreshData(); } }

Files to Review

See Also

Does this example address your development requirements/objectives?

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

Example Code

DataService/Program.cs
C#
using DataService; using Microsoft.EntityFrameworkCore; using System.Linq.Dynamic.Core; using System.Reflection; using System.Xml.Serialization; var builder = WebApplication.CreateBuilder(args); string? connectionString = builder.Configuration.GetConnectionString("ConnectionString"); builder.Services.AddDbContext<DataServiceDbContext>(o => o.UseSqlServer(connectionString, options => { options.EnableRetryOnFailure(); })); var app = builder.Build(); // Make sure the database exists and is current using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<DataServiceDbContext>(); dbContext.Database.Migrate(); } app.MapGet("/api/populateTestData", async (DataServiceDbContext dbContext) => { var assembly = Assembly.GetExecutingAssembly(); Console.WriteLine(String.Join("\n", assembly.GetManifestResourceNames())); var resourceName = assembly.GetManifestResourceNames().Single(str => str.EndsWith("order_items.xml")); var serializer = new XmlSerializer(typeof(List<OrderItem>)); using Stream? stream = assembly.GetManifestResourceStream(resourceName); if (stream is not null) { var items = (List<OrderItem>?)serializer.Deserialize(stream); if (items is not null) { dbContext.OrderItems.AddRange(items); await dbContext.SaveChangesAsync(); return Results.Ok("Data populated successfully"); } } return Results.NotFound("Error populating data"); }); app.MapGet("/data/OrderItems", async (DataServiceDbContext dbContext, int skip = 0, int take = 20, string sortField = "Id", bool sortAscending = true) => { var source = dbContext.OrderItems.AsQueryable().OrderBy(sortField + (sortAscending ? " ascending" : " descending")); var items = await source.Skip(skip).Take(take).ToListAsync(); var totalCount = await dbContext.OrderItems.CountAsync(); return Results.Ok(new { Items = items, TotalCount = totalCount }); }); app.MapGet("/data/OrderItem/{id}", async (DataServiceDbContext dbContext, int id) => { var orderItem = await dbContext.OrderItems.FindAsync(id); if (orderItem is null) { return Results.NotFound(); } return Results.Ok(orderItem); }); app.MapPost("/data/OrderItem", async (DataServiceDbContext dbContext, OrderItem orderItem) => { dbContext.OrderItems.Add(orderItem); await dbContext.SaveChangesAsync(); return Results.Created($"/data/OrderItem/{orderItem.Id}", orderItem); }); app.MapPut("/data/OrderItem/{id}", async (DataServiceDbContext dbContext, int id, OrderItem orderItem) => { if (id != orderItem.Id) { return Results.BadRequest("Id mismatch"); } dbContext.Entry(orderItem).State = EntityState.Modified; await dbContext.SaveChangesAsync(); return Results.NoContent(); }); app.MapDelete("/data/OrderItem/{id}", async (DataServiceDbContext dbContext, int id) => { var orderItem = await dbContext.OrderItems.FindAsync(id); if (orderItem is null) { return Results.NotFound(); } dbContext.OrderItems.Remove(orderItem); await dbContext.SaveChangesAsync(); return Results.NoContent(); }); app.Run();
WinForms.Client/EditForm.cs
C#
namespace WinForms.Client { public partial class EditForm : DevExpress.XtraEditors.XtraForm { public EditForm() { InitializeComponent(); } public static (bool changesSaved, OrderItem item) EditItem(OrderItem orderItem) { using var form = new EditForm(); var clonedItem = new OrderItem { Id = orderItem.Id, ProductName = orderItem.ProductName, UnitPrice = orderItem.UnitPrice, Quantity = orderItem.Quantity, Discount = orderItem.Discount, OrderDate = orderItem.OrderDate }; form.orderItemBindingSource.DataSource = clonedItem; if (form.ShowDialog() == DialogResult.OK) { return (true, clonedItem); } return (false, orderItem); } public static (bool changesSaved, OrderItem? item) CreateItem() { using var form = new EditForm(); var newItem = new OrderItem() { OrderDate = DateTime.Now }; form.orderItemBindingSource.DataSource = newItem; if (form.ShowDialog() == DialogResult.OK) { return (true, newItem); } return (false, null); } } }
WinForms.Client/MainForm.cs
C#
using DevExpress.XtraEditors; using DevExpress.XtraGrid.Views.Base; using DevExpress.XtraGrid.Views.Grid; namespace WinForms.Client { public partial class MainForm : XtraForm { public MainForm() { InitializeComponent(); } private VirtualServerModeDataLoader? loader; private void VirtualServerModeSource_ConfigurationChanged(object? sender, DevExpress.Data.VirtualServerModeRowsEventArgs e) { loader = new VirtualServerModeDataLoader(e.ConfigurationInfo); e.RowsTask = loader.GetRowsAsync(e); } private void VirtualServerModeSource_MoreRows(object? sender, DevExpress.Data.VirtualServerModeRowsEventArgs e) { if (loader is not null) { e.RowsTask = loader.GetRowsAsync(e); } } private async void gridView1_DoubleClick(object sender, EventArgs e) { if (sender is GridView view) { if (view.FocusedRowObject is OrderItem oi) { var editResult = EditForm.EditItem(oi); if (editResult.changesSaved) { await DataServiceClient.UpdateOrderItemAsync(editResult.item); view.RefreshData(); } } } } private async void addItemButton_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { if (gridControl.FocusedView is ColumnView view) { var createResult = EditForm.CreateItem(); if (createResult.changesSaved) { await DataServiceClient.CreateOrderItemAsync(createResult.item!); view.RefreshData(); } } } private async void deleteItemButton_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { if (gridControl.FocusedView is ColumnView view && view.GetFocusedRow() is OrderItem orderItem) { await DataServiceClient.DeleteOrderItemAsync(orderItem.Id); view.RefreshData(); } } } }
WinForms.Client/DataServiceClient.cs
C#
using System.Diagnostics; using System.Net.Http; using System.Text; using System.Text.Json; namespace WinForms.Client { public static class DataServiceClient { static DataServiceClient() { string? url = System.Configuration.ConfigurationManager.AppSettings["baseUrl"]; if (string.IsNullOrEmpty(url)) throw new InvalidOperationException("The 'baseUrl' configuration setting is missing."); baseUrl = url; } static string baseUrl; static HttpClient CreateClient() { return new HttpClient(); } public static async Task<DataFetchResult?> GetOrderItemsAsync(int skip, int take, string sortField, bool sortAscending) { using var client = CreateClient(); var response = await client.GetAsync( $"{baseUrl}/data/OrderItems?skip={skip}&take={take}&sortField={sortField}&sortAscending={sortAscending}"); response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); var dataFetchResult = JsonSerializer.Deserialize<DataFetchResult>(responseBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return dataFetchResult; } static OrderItem? AsOrderItem(this string responseBody) { return JsonSerializer.Deserialize<OrderItem>(responseBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } public static async Task<OrderItem?> GetOrderItemAsync(int id) { using var client = CreateClient(); var response = await client.GetAsync($"{baseUrl}/data/OrderItem/{id}"); response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); return responseBody.AsOrderItem(); } public static async Task<OrderItem?> CreateOrderItemAsync(OrderItem orderItem) { using var client = CreateClient(); var response = await client.PostAsync($"{baseUrl}/data/OrderItem", new StringContent(JsonSerializer.Serialize(orderItem), Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); return responseBody.AsOrderItem(); } public static async Task UpdateOrderItemAsync(OrderItem orderItem) { using var client = CreateClient(); var response = await client.PutAsync($"{baseUrl}/data/OrderItem/{orderItem.Id}", new StringContent(JsonSerializer.Serialize(orderItem), Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); } public static async Task<bool> DeleteOrderItemAsync(int id) { try { using var client = CreateClient(); var response = await client.DeleteAsync($"{baseUrl}/data/OrderItem/{id}"); response.EnsureSuccessStatusCode(); return true; } catch (Exception ex) { Debug.WriteLine(ex); return false; } } } }

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.