Back to Devexpress

Edit Model in Blazor Grid

blazor-404759-components-grid-editing-and-validation-edit-model.md

latest14.2 KB
Original Source

Edit Model in Blazor Grid

  • Sep 21, 2025
  • 8 minutes to read

Once a user starts editing a row, the Grid creates a clone of the edited data item – an edit model. The edit model has the same data type and property values as the edited data item. This topic describes how to access, customize, and save the edit model. The approaches listed below apply to all edit modes.

Save Changes

During edit operations, the Grid applies user changes only to the edit model and keeps the data item unchanged. Handle the events listed below to process changes:

EditModelSaving

This event fires if validation succeeds after a user saves changes or you call the SaveChangesAsync() method. Handle this event to check access permissions, assign changes from the edit model to the corresponding data item, and post changes to the underlying data source. The IsNew event argument identifies whether the edit model corresponds to a new or existing row.

Call the CopyChangesToDataItem() method to copy all changed fields including nested properties from the edit model to the data item. This method copies changes regardless of how you make changes: in the event handler, razor code, or business logic.

DataItemDeletingThis event fires when a user confirms the row delete operation in the confirmation dialog. Handle this event to check user access permissions and delete the corresponding data item from the data source.

The Grid component reloads its data after the corresponding event handler is executed or when you change an instance of a field/property bound to the Data parameter. In other cases, you may need to call the Reload() method to reload Grid data after you post updates to the data source. If you call this method in EditModelSaving and DataItemDeleting event handlers to refresh data manually, set the Reload event argument to false to prevent unnecessary repeated reloads.

The following code snippet changes an instance of the Data collection that is bound to the Data parameter:

View Example: Bind the Grid to data with Entity Framework Core

razor
@page "/"
@using Microsoft.EntityFrameworkCore
@using BindToData.Models
@inject IDbContextFactory<NorthwindContext> NorthwindContextFactory
@implements IDisposable

<DxGrid Data="Data"
        CustomizeEditModel="OnCustomizeEditModel"
        EditModelSaving="OnEditModelSaving"
        DataItemDeleting="OnDataItemDeleting"
        KeyFieldName="EmployeeId">
    <Columns>
        <DxGridCommandColumn />
        <DxGridDataColumn FieldName="FirstName" />
        <DxGridDataColumn FieldName="LastName" />
        <DxGridDataColumn FieldName="Title" />
        <DxGridDataColumn FieldName="HireDate" />
    </Columns>
    <EditFormTemplate Context="editFormContext">
        <DxFormLayout>
            <DxFormLayoutItem Caption="First Name:">
                @editFormContext.GetEditor("FirstName")
            </DxFormLayoutItem>
            <DxFormLayoutItem Caption="Last Name:">
                @editFormContext.GetEditor("LastName")
            </DxFormLayoutItem>
            <DxFormLayoutItem Caption="Title:">
                @editFormContext.GetEditor("Title")
            </DxFormLayoutItem>
            <DxFormLayoutItem Caption="Hire Date:">
                @editFormContext.GetEditor("HireDate")
            </DxFormLayoutItem>
        </DxFormLayout>
    </EditFormTemplate>
</DxGrid>

@code {
    IEnumerable<Employee> Data { get; set; }
    NorthwindContext Northwind { get; set; }

    protected override async Task OnInitializedAsync() {
        Northwind = NorthwindContextFactory.CreateDbContext();
        Data = await Northwind.Employees.ToListAsync();
    }

    void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e) {
        if(e.IsNew) {
            var editModel = (Employee)e.EditModel;
            editModel.EmployeeId = Data.Max(x => x.EmployeeId) + 1;
        }
    }

    async Task OnEditModelSaving(GridEditModelSavingEventArgs e) {
        var editModel = (Employee)e.EditModel;
        // Assign changes from the edit model to the data item.
        if (e.IsNew)
            await Northwind.AddAsync(editModel);
        else
            e.CopyChangesToDataItem();
        // Post changes to the database.
        await Northwind.SaveChangesAsync();
        // Reload the entire Grid.
        Data = await Northwind.Employees.ToListAsync();
    }

    async Task OnDataItemDeleting(GridDataItemDeletingEventArgs e) {
        // Remove the data item from the database.
        Northwind.Remove(e.DataItem);
        await Northwind.SaveChangesAsync();
        // Reload the entire Grid.
        Data = await Northwind.Employees.ToListAsync();
    }

    public void Dispose() {
        Northwind?.Dispose();
    }
}
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace BindToData.Models;

public partial class Employee {
    public int EmployeeId { get; set; }
    [Required]
    public string LastName { get; set; } = null!;
    [Required]
    public string FirstName { get; set; } = null!;
    public string? Title { get; set; }
    public string? TitleOfCourtesy { get; set; }
    public DateTime? BirthDate { get; set; }
    public DateTime? HireDate { get; set; }
    public string? Address { get; set; }
    public string? City { get; set; }
    public string? Region { get; set; }
    public string? PostalCode { get; set; }
    public string? Country { get; set; }
    public string? HomePhone { get; set; }
    public string? Extension { get; set; }
    public byte[]? Photo { get; set; }
    public string? Notes { get; set; }
    public int? ReportsTo { get; set; }
    public string? PhotoPath { get; set; }
    public virtual ICollection<Employee> InverseReportsToNavigation { get; } = new List<Employee>();
    public virtual Employee? ReportsToNavigation { get; set; }
}
csharp
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace BindToData.Models;

public partial class NorthwindContext : DbContext {
    public NorthwindContext() {}
    public NorthwindContext(DbContextOptions<NorthwindContext> options) : base(options) {}
    public virtual DbSet<Employee> Employees { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.UseCollation("SQL_Latin1_General_CP1_CI_AS");
        modelBuilder.Entity<Employee>(entity => {
            entity.HasIndex(e => e.LastName, "LastName");
            entity.HasIndex(e => e.PostalCode, "PostalCode");
            entity.Property(e => e.EmployeeId).HasColumnName("EmployeeID");
            entity.Property(e => e.Address).HasMaxLength(60);
            entity.Property(e => e.BirthDate).HasColumnType("datetime");
            entity.Property(e => e.City).HasMaxLength(15);
            entity.Property(e => e.Country).HasMaxLength(15);
            entity.Property(e => e.Extension).HasMaxLength(4);
            entity.Property(e => e.FirstName).HasMaxLength(10);
            entity.Property(e => e.HireDate).HasColumnType("datetime");
            entity.Property(e => e.HomePhone).HasMaxLength(24);
            entity.Property(e => e.LastName).HasMaxLength(20);
            entity.Property(e => e.Notes).HasColumnType("ntext");
            entity.Property(e => e.Photo).HasColumnType("image");
            entity.Property(e => e.PhotoPath).HasMaxLength(255);
            entity.Property(e => e.PostalCode).HasMaxLength(10);
            entity.Property(e => e.Region).HasMaxLength(15);
            entity.Property(e => e.Title).HasMaxLength(30);
            entity.Property(e => e.TitleOfCourtesy).HasMaxLength(25);
            entity.HasOne(d => d.ReportsToNavigation).WithMany(p => p.InverseReportsToNavigation)
                .HasForeignKey(d => d.ReportsTo)
                .HasConstraintName("FK_Employees_Employees");
        });
        OnModelCreatingPartial(modelBuilder);
    }
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
csharp
using Microsoft.EntityFrameworkCore;
// ...
builder.Services.AddDbContextFactory<NorthwindContext>((sp, options) => {
    var env = sp.GetRequiredService<IWebHostEnvironment>();
    var dbPath = Path.Combine(env.ContentRootPath, "Northwind.db");
    options.UseSqlite("DataSource=" + dbPath);
});

Create a Custom Edit Model

The Grid cannot generate an edit model in the following cases:

  • A data item class does not have a parameterless constructor.
  • Data item fields bound to Grid columns are read-only.

Handle the CustomizeEditModel event to create a custom edit model or customize an automatically generated edit model. Use the event argument’s DataItem property to access this data item. Assign your custom edit model to the event argument’s EditModel property.

When you obtain an EditModel object in event handlers or edit templates, cast this object to your custom edit model class as shown below:

razor
<DxGrid Data="GridDataSource"
        EditModelSaving="OnEditModelSaving"
        DataItemDeleting="OnDataItemDeleting"
        KeyFieldName="EmployeeId"
        CustomizeEditModel="Grid_CustomizeEditModel">
    <Columns>
        <DxGridCommandColumn />
        <DxGridDataColumn FieldName="FirstName" />
        <DxGridDataColumn FieldName="LastName" />
        <DxGridDataColumn FieldName="Title" />
        <DxGridDataColumn FieldName="HireDate" />
    </Columns>
    <EditFormTemplate Context="editFormContext">
        @{ 
            var editModel = (EmployeeEditModel)editFormContext.EditModel; 
        }
        @* ... *@
    </EditFormTemplate>
</DxGrid>

@code {
    IEnumerable<object> GridDataSource { get; set; }

    class EmployeeEditModel {
        public int? EmployeeId { get; set; }
        [Required, MaxLength(64)]
        public string LastName { get; set; }
        [Required, MaxLength(64)]
        public string FirstName { get; set; }
        public string Title { get; set; }
        public DateTime? HireDate { get; set; }
    }
    async Task OnEditModelSaving(GridEditModelSavingEventArgs e) {
        var editModel = (EmployeeEditModel)e.EditModel;
        // ...
    }
    async Task Grid_CustomizeEditModel(GridCustomizeEditModelEventArgs e) {
        var dataItem = (Employee)e.DataItem;
        if (dataItem == null)
            e.EditModel = new EmployeeEditModel { };
        else {
            e.EditModel = new EmployeeEditModel {
                EmployeeId = dataItem.EmployeeId,
                FirstName = dataItem.FirstName,
                LastName = dataItem.LastName,
                Title = dataItem.Title,
                HireDate = dataItem.HireDate
            };
        }
    }
    // ...
}

View Example: Display an error message from the Web API Service View Example: Create and edit unbound columns

Initialize New Rows

Follow the steps below to display predefined values in data editors when a user adds a new row:

  1. Handle the CustomizeEditModel event.
  2. Check the event argument’s IsNew property to identify new rows.
  3. Use the EditModel property to access the edit model and initialize model field values.

The following snippet specifies the predefined value for the HireDate field:

razor
<DxGrid Data="GridDataSource"
        EditModelSaving="OnEditModelSaving"
        DataItemDeleting="OnDataItemDeleting"
        KeyFieldName="EmployeeId"
        CustomizeEditModel="Grid_CustomizeEditModel">
        @* ... *@
</DxGrid>

@code {
    IEnumerable<object> GridDataSource { get; set; }
    @* ... *@
    async Task Grid_CustomizeEditModel(GridCustomizeEditModelEventArgs e) {
        if (e.IsNew) {
            var editModel = (Employee)e.EditModel;
            editModel.HireDate = DateTime.Today;
        }
    }
    @* ... *@
}

Edit Data with Nested Properties

When you edit data, the Grid clones a bound data item – namely creates an object of the same type and copies all property values into it. If a property is of a reference type, the reference is copied but the referred object is not copied. As the result, the original object and its clone refer to the same object.

To edit nested objects, create a copy of these objects manually (make a deep copy of the original data item object). You can use the following approaches to implement a deep copy operation:

  • Handle the CustomizeEditModel event, clone the nested object, and assign it to field whose value is the reference type.

  • Create constructors for the data item type and the nested object’s type. In the data item type constructor, the nested object type constructor clones the nested object with its property values. Handle the CustomizeEditModel event and call a data item’s class constructor to initialize the edit model.