Reputation: 956
I'm new to ASP.NET MVC Core Web API and EF Core, and I'm creating a simple MVC web API demo as follows.
[HttpPut("{id}")]
public async void Put(int id, [FromBody]Student student)
{
if (ModelState.IsValid)
{
var stu = _context.Students
.Where(s => s.StudentId == id)
.SingleOrDefault();
var updateResult = await TryUpdateModelAsync<Student>(stu, "", s => s.FirstName, s => s.LastName, s => s.EnrollmentDate);
_context.SaveChanges();
}
}
The problem is that TryUpdateModelAsync
doesn't work, the changes didn't get updated to the database.
I wonder:
If TryUpdateModelAsync
can be used in MVC Web API?
I really don't want to write boring code like the following, how do I avoid doing the property value settings from one object to another of the same type? (that's the very first reason why I used TryUpdateModelAsync
)
stu.FirstName = student.FirstName;
stu.LastName = student.LastName;
stu.SomeOtherProperties = student.SomeOtherProperties;
_context.SaveChanges();
.NET Core version: 1.1.0
ASP.Net Core version: 1.1.0
Entity Framework Core version: 1.1.0
Upvotes: 5
Views: 10271
Reputation: 1104
Here is a method that performs a basic update from one model to another, if any of the properties fail to update it will return false, otherwise true. I suspect that TryUpdateModelAsync
may perform additional work, but at least for my case this is good enough.
You will need to add using System;
and using System.Linq.Expressions;
. Aside from that just paste into a class where you want to use this from. I use it in my custom controller class so that I can use it in all of my derived controllers in a way that is similar to TryUpdateModelAsync
.
/// <summary>
/// Updates the destination model instance using values from the source model
/// </summary>
/// <typeparam name="TModel">The type of model to update</typeparam>
/// <param name="source">The model containing the values to use</param>
/// <param name="destination">The model to update</param>
/// <param name="properties">The properties of the model to update</param>
public static bool TryUpdateModel<TModel>(TModel source, TModel destination, params Expression<Func<TModel, object>>[] properties) where TModel : class
{
var ok = true;
foreach (var prop in properties)
{
try
{
string propertyName = string.Empty;
if (prop.Body is MemberExpression member)
propertyName = member.Member.Name;
else if (prop.Body is UnaryExpression unary)
propertyName =((MemberExpression)unary.Operand).Member.Name;
if (string.IsNullOrWhiteSpace(propertyName))
{
ok = false;
continue;
}
var input = source.GetType().GetProperty(propertyName).GetValue(source, null);
destination.GetType().GetProperty(propertyName).SetValue(destination, input);
}
catch
{
ok = false;
}
}
return ok;
}
In your case usage would be as follows. You likely will not need the <student>
portion, but I have included it for completeness.
var updateResult = TryUpdateModel<Student>(student, stu,
s => s.FirstName,
s => s.LastName,
s => s.EnrollmentDate
);
Upvotes: 0
Reputation: 22840
Web API generally use a DTO The official docs show the correct approach.
See GitHub issue TryUpdateModelAsync() doesn't work for ASP.NET Core MVC Web API for the definitive answer.
The [FromBody]
attribute uses formatters to deserialize the request body into an object (e.g. using JSON or XML).
On the other hand, model binding, which includes the TryUpdateModel
API, uses value providers to get data from the form body (if any), query string, route data, or some other places.
There are a few ways to go here:
Upvotes: 5
Reputation: 1112
FromBody uses formatters to deserialize the request body into a JSON object.
TryUpdateModelAsync relies on Model binding and uses value providers to get data.
You could change the posted data to be a form post which would use model binding.
Upvotes: 0
Reputation: 2882
Try passing in the student instance from your body instead of the one you looked up.
public async void Put(int id, [FromBody]Student student)
{
if (ModelState.IsValid)
{
student.StudentId = id; // If the ID isn't passed in or you could validate that it matches the id passed in
var updateResult = await TryUpdateModelAsync<Student>(student, "", s => s.FirstName, s => s.LastName, s => s.EnrollmentDate);
_context.SaveChanges();
}
}
Upvotes: 0