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.
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
Related Examples
- Connect a DevExpress WinForms Data Grid to a .NET Core Service
- Connect a WinForms Data Grid to an ASP.NET Core WebAPI Service Powered by EF Core — Authenticate Users and Protect Data
- Connect a DevExpress WinForms Data Grid to a Backend using a Middle Tier Server (EF Core without OData)
- Connect a WinForms Data Grid to an ASP.NET Core WebAPI Service Using EF Core — Authorization Code Flow
See Also
- Connect a WinForms Data Grid to an Arbitrary ASP.NET Core WebAPI Service Powered by EF Core — Architecture and Data Binding (Part 1)
- Connect a WinForms Data Grid to an Arbitrary ASP.NET Core WebAPI Service Powered by EF Core — Add Editing Features (Part 2)
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
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();
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);
}
}
}
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();
}
}
}
}
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;
}
}
}
}