To simplify the problem, in C# I have a model like this
public class Person
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[MaxLength(128)]
public string? Id { get; set; }
[MaxLength(256)]
public string PersonName { get; set; }
public short PersonAge { get; set; }
// ommitted other properties
now, I have a form like this in ASP Core in the frontend
@model Person
<form action="/api/new-person">
<input form="PersonName"/>
<button type="submit"></button>
</form>
As you can see, I only have PersonName
in my form, in my backend I only want to be able to update the Person
object in my database for the value of PersonName
because it is property that exists in the form, again, this is a simplified example because my object is massive and it changes just about all the time not to mention a lot of foreign keys, now, I don't want to painstakingly set each properties in the backend such as
var existingPersonFromDatabase = await _appDbContext.User.FirstOrDefaultAsync(x=>x.Id == formPerson.Id);
existingPersonFromDatabase.PersonName = formPerson.PersonName;
existingPersonFromDatabase.PersonAge = formPerson.PersonAge ;
// + settings 20 more properties one by one
so the question here is, how do i make it so that i only have to update the fields existing in the form?
I found the answer, for some reason this is not well documented by Microsoft and its hidden below the docs, I used TryUpdateModelAsync
in the Controller
,
inside the controller you can have
var existingObject = await _appDbContext.Objects.FirstAsync(x => x.Id == 1);
// then we can populate this existing object with the one in the form
await TryUpdateModelAsync(existingObject);
below is my complete code
[HttpPost]
public async Task<IActionResult> PostNewEmployee(IFormCollection formCollection)
{
if (!ModelState.IsValid) // check model validity
{
var errors = ModelState
.Select(x => x.Value)
.Where(y => y is not null && y.Errors.Count > 0)
.ToList();
return BadRequest(JsonConvert.SerializeObject(errors));
}
// if the form has an Id then this is an existing person
if (formCollection.TryGetValue("Id", out var existingId))
{
var realId = existingId.ToString();
var existingUser = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Id == realId); // get the current existing user
if (existingUser is not null) // make sure the user exists
{
await TryUpdateModelAsync(existingUser); // this will populate the current ``existingUser`` magically with the current model, it will only overwrite fields in the form, it will not touch properties that are NOT in the model
// update a foreign key
_applicationDbContext.Entry(existingUser.EmployeeAdditionalData).State = EntityState.Modified;
await _applicationDbContext.SaveChangesAsync();
return Ok();
}
}
What TryUpdateModelAsync
does is it will populate an object with the current properties from the model, overwriting only properties from the model and ignoring properties not in both model or object. It also supports foreign or class properties in the model.