I am using ASP.NET MVC Entity Framework and I created an API controller, now I want to add a method that is basically a copy of the put method, however I want to adjust this method so it updates a single column
[ResponseType(typeof(void))]
[Authorize]
public IHttpActionResult PutUser(int id, Users user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != user.id)
{
return BadRequest();
}
db.Entry(user).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!UsersExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
each user in my User class has an email and column called isOnline, for this method I want to update the isOnline to true based on the email.
The examples I have seen online are for non API controllers. Please Help!
Your subject is partial update in Entity Framework, following is an example:
var user= new User() {Id = id, TargetColumn = "test"};
context.Users.Attach(user);
var entry = context.Entry(user);
entry.Property(e => e.TargetColumn ).IsModified = true;;
Related
I can't figure out how to edit the row after seeing the changes in DB.
I have an API-project and an MVC-project. I use CRUD in my API and call them with my MVC with HttpClient
I have a public byte[] RowVersion { get; set; } property with the attribute [Timestamp].
I have a clientFactory where I do CreateClient() to perform PutAsync("api.example.com/{id}") action.
The HttpResponseMessage variable on my putasync action returns StatusCode(409) because my API successfully detected a concurrency conflict.
I managed to display error messages before updating the concurrency; showing the newly updated rows in the database(newsDb) with help of a new client, clientFactory.CreateClient(), and comparing them with the inputs(news).
Then I set the news.RowVersion = newsDb.RowVersion and re-display View(news).
And after clicking Save again, nothing happens - no redirects, no changes - the concurrency errors are still there:
[HttpPost("edit/{id}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditNewsArticle(int id, [Bind("NewsId,Author,Title,Content,CreatedDate,HashTags,RowVersion")] News news)
{
if (id != news.NewsId)
{
return NotFound();
}
if (ModelState.IsValid)
{
news.UpdatedDate = DateTime.Now;
string json = JsonConvert.SerializeObject(news);
HttpResponseMessage putTask = await clientFactory.CreateClient().PutAsync($"https://localhost:44331/api/News/{id}", new StringContent(json, Encoding.UTF8, "application/json"));
if (putTask.IsSuccessStatusCode)
{
return RedirectToAction(nameof(Index));
}
else if (putTask.StatusCode == HttpStatusCode.Conflict)
{
string jsonDb = await clientFactory.CreateClient().GetStringAsync($"https://localhost:44331/api/News/{id}");
News newsDb = JsonConvert.DeserializeObject<News>(jsonDb);
if (newsDb is null)
{
ModelState.AddModelError(string.Empty, $"Unfortunately, the news item you edited has already been deleted by another user.");
}
if (newsDb.Title != news.Title)
{
ModelState.AddModelError("Title", $"Title in database: {newsDb.Title}");
}
if (newsDb.Author != news.Author)
{
ModelState.AddModelError("Author", $"Author in database: {newsDb.Author}");
}
if (newsDb.Content != news.Content)
{
ModelState.AddModelError("Content", $"Content in database: {newsDb.Content}");
}
if (newsDb.HashTags != news.HashTags)
{
ModelState.AddModelError("HashTags", $"HashTags in database: {newsDb.HashTags}");
}
ModelState.AddModelError(string.Empty,
"Editing was canceled as the selected news item was changed by someone else in the meantime." +
"The values of the change are now shown below, which are derived from the database" +
"If you still want to edit the user, click Save again.");
news.RowVersion = newsDb.RowVersion;
}
else
{
ModelState.AddModelError(string.Empty, "Unknown error. Contact a support.");
return View(news);
}
}
return View(news);
}
API Put:
[HttpPut("{id}")]
public async Task<IActionResult> PutNews(int id, [FromBody] News news)
{
if (id != news.NewsId)
{
return BadRequest();
}
context.Entry(news).State = EntityState.Modified;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!NewsExists(id))
{
return NotFound();
}
else
{
return StatusCode(409);
}
}
return CreatedAtAction("GetNews", new { id = news.NewsId }, news);
}
I found my issue. I needed to call ModelState.Clear(); after de-serializing the 'jsonDb', and also remove RowVersion from Bind in the attribute.
I have an URL field in my table for each of the courses. And i am using it as route parameter. I did this to make Urls user-friendly and as per my understanding this might also help me in SEO ( Please correct me if i am wrong ). With such as setup, i am unable to figure-out how do i create Edit / Delete actions.
Course.cs : The model of the course
public partial class Course
{
public int Id { get; set; }
public string Title { get; set; }
// This is set as Unique Key in the table.
public string Url { get; set; }
public string InnerHtml { get; set; }
}
CourseController.cs : The controller and Edit action for our reference.
[HttpPost("Edit/{courseUrl}")]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrator")]
public async Task<IActionResult> Edit(string courseUrl, [Bind("Id,Title,Url,InnerHtml")] Course course)
{
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(course);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CourseExists(course.Url))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(course);
}
The problem : I am getting the following error on this action
InvalidOperationException: The instance of entity type 'Course' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
The WorkAround : Commenting the below code in the action, get's the application working. But, the below code is to check if the model being edited is contained in DB.
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id)
{
return NotFound();
}
What's the correct way to handle this scenario ?
As the error message explains, there is already a model loaded from the search which is being track by the ORM. You need to copy desired properties over to the tracked model if you intend to save it.
//...code removed for brevity
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id) {
return NotFound();
}
if (ModelState.IsValid) {
try {
Populate(OriginalCourse, course);
_context.Update(OriginalCourse);
await _context.SaveChangesAsync();
} catch (DbUpdateConcurrencyException) {
if (!CourseExists(course.Url)) {
return NotFound();
} else {
throw;
}
}
return RedirectToAction(nameof(Index));
}
//...code removed for brevity
Where Populate could look like this
void Populate(Course original, Cource source) {
original.Title = source.Title;
original.Url = source.Url;
original.InnerHtml = source.InnerHtml;
}
Another option would be to not load up an instance by not selecting/returning an item from the context
//...code removed for brevity
var exists = await _context.Courses.AnyAsync(m => m.Url == courseUrl);
if (!exists) {
return NotFound();
}
//...code removed for brevity
and then update the provided course
I have been following this tutorial on .NET Core CRUD operations https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/crud Below is my Edit method
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditUser(string id, ApplicationUser applicationUser)
{
if (id != null)
{
return NotFound();
}
var userToUpdate = await _context.ApplicationUser.SingleOrDefaultAsync(u => u.Id == id);
if(await TryUpdateModelAsync<ApplicationUser>(
userToUpdate,"",m => m.DateUpdated,m=> m.Name, m=>m.Email))
{
try
{
if (applicationUser.IsAdmin)
{
var x = await _userManager.AddToRoleAsync(applicationUser, "Admin");
if (!x.Succeeded)
{
}
}
else
{
await _userManager.AddToRoleAsync(applicationUser, "User");
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch(DbUpdateException ex)
{
//TODO: err
}
return View(applicationUser);
}
}
However, there is no clear documentation on how to then update the fields programmatically before calling the Update using the TryUpdateModelAsync method. For example, I want to set the DateUpdated. Or what if I wanted to make some other field change based on the form value?
The method TryUpdateModelAsync just updates properties of the userToUpdate and doesn't save to database anything. The properties listed in method params should be in the controller's current IValueProvider(e.g. in FormValueProvider)
I want to set the DateUpdated
If you want to change additional properties that are not exist in the form you can do it anywhere but before the entity will be saved by await _context.SaveChangesAsync();:
userToUpdate.DateUpdated = DateTime.Now;
Or what if I wanted to make some other field change based on the form value?
you can get form values from HttpContext.Request.Form:
userToUpdate.SomeProperty = HttpContext.Request.Form["SomeProperty"];
I have an ASP.net API 2 application with a register method, which basically does the following:
var user = new ApplicationUser() { UserName = user.email, Email = user.email };
UserManager.AddToRole(user.Id, "SomeRole");
In the same controller, I would like to assign a model class to the application user, for example:
var parent = db.Parents.Find(parentToCreate.id);
db.SaveChanges();
This gives me an error that the user name is already taken. I know that the issue is relating to there being two model trackers, one for the UserManager.CreateAsync and one for updating the db. Will it be alright to create users without using CreateAsync or is there another way to avoid this error?
Note that I think that this could be achieved by putting a Parent property on the account property, but not all accounts are parents, so I do not want to do this solution. A parent has an account, so there is an account property on the parent.
As requested, the full register method is as follows:
[AllowAnonymous]
[Route("RegisterParent")]
public async Task<IHttpActionResult>RegisterParent(ParentRegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.email, Email = model.email };
Parent parentToCreate = new Parent();
db.Parents.Add(parentToCreate);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.SaveChanges();
try
{
IdentityResult result = await UserManager.CreateAsync(user, model.password);
// The following two lines give an error
parentToCreate.account = user;
// the above two lines give an error
UserManager.AddToRole(user.Id, "ParentRole");
db.SaveChanges();
}
catch (Exception e)
{
Console.Write(e);
// e returns here with message
}
return Ok(200);
}
This is a simplified example based on minimal example provided in OP.
Based on conversation to clarify current design the Parent model would need to be updated to have a proper code first Foreign Key relationship
public class Parent {
//...other properties
//Foreign key
public string accountid { get; set; }
//navigation property
[ForeignKey("accountid")]
public ApplicationUser account { get; set; }
}
With that then you only need to assign the user id when creating the parent.
Refactoring/abstracting out specific responsibilities.
public interface IParentService {
Task AddParentAsync(ApplicationUser user);
}
public class ParentService : IParentService {
ApplicationDbContext db;
public ParentService(ApplicationDbContext db) {
this.db = db;
}
public async Task AddParentAsync(ApplicationUser user) {
Parent parentToCreate = new Parent() {
//...set other properties
accountid = user.Id
};
db.Parents.Add(parentToCreate);
await db.SaveChangesAsync();
}
}
Next separating the action into distinct processes to avoid concurrency issues.
public class AccountController : ApiController {
ApplicationUserManager userManager;
IParentService parentService;
public AccountController(ApplicationUserManager userManager, IParentService parentService) {
this.userManager = userManager;
this.parentService = parentService;
}
[AllowAnonymous]
[Route("RegisterParent")]
public async Task<IHttpActionResult> RegisterParent(ParentRegisterBindingModel model) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.email, Email = model.email };
var result = await userManager.CreateAsync(user, model.password);
if (result.Succeed) {
try {
await userManager.AddToRole(user.Id, "ParentRole");
await parentService.AddParentAsync(user);
return Ok();
} catch (Exception e) {
userManager.Delete(user.Id);
Console.Write(e);
// e returns here with message
return BadRequest(); //OR InternalServerError();
}
} else {
foreach (var error in result.Errors) {
ModelState.AddModelError("", error);
}
return BadRequest(ModelState);
}
}
}
You would obviously register dependencies with the DI framework to allow for proper injection.
I have a project I am using a database that is connected thru the entity data model and I am using the DBcontext to create and update entities.
My Create method (below) is working fine.
[HttpPost]
public IHttpActionResult PostCustomer([FromBody] Customer Customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var dbCtx = new CustomerDBEntities())
{
dbCtx.Customers.Add(Customer);
dbCtx.SaveChanges();
}
return CreatedAtRoute("DefaultApi", new { id = Customer.id }, Customer);
}
But my update method is doing nothing. I am not getting an error or anything just nothing seems to happen. and it is not updating the values.
The code is this
[HttpPut]
public IHttpActionResult UpdateCustomer([FromBody] Customer Customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Customer cust;
var ctx = new CustomerDBEntities();
cust = ctx.Customers.Where(s => s.id == Customer.id).FirstOrDefault<Customer>();
if (cust != null)
{
cust.Customer_Name = Customer.Customer_Name;
cust.email = Customer.email;
cust.Customer_address = Customer.Customer_address;
}
ctx.Entry(cust).State = System.Data.EntityState.Modified;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = Customer.id }, Customer);
}
When I was using an SQL command before to go directly to database it was working fine but ever since I changed it it stopped working.
Any help would be much appreciated.
Remove this line of code.
ctx.Entry(cust).State = System.Data.EntityState.Modified;
EF tracks the changes made to entities in the context, you do not need to tell it that it was modified.
Add changes at Property Level
context.Entry(Customer).Property(cust => cust.Customer_Name).IsModified = true;
More Details here https://msdn.microsoft.com/en-us/data/jj592677
Also similar question answered here https://stackoverflow.com/a/15339512/1481690