Entity Framework 6 update record SaveChanges does nothing - asp.net

I am using the following code in an ASP.NET MVC project to update a record (using a JSON call from AngularJS code, but that shouldn't make a difference, right?):
public JsonResult UpdatePerson(Person person)
{
Person pers = db.People.Single(p => p.BusinessEntityID == person.BusinessEntityID);
pers.FirstName = person.FirstName;
pers.MiddleName = person.MiddleName;
pers.LastName = person.LastName;
pers.Suffix = person.Suffix;
pers.Title = person.Title;
pers.ModifiedDate = DateTime.Now;
db.Entry(pers).State = EntityState.Modified;
db.SaveChanges();
return Json(new { status = "Person successfully saved." });
}
When I step through the code, everything looks great for the properties for both the "pers" object and the "person" object. But when I get to the "db.SaveChange()" line, the code just does nothing. I get no error, but I also never get beyond it. It's as if the code just ends.
I've also tried this code that I found, but again, the SaveChanges line does nothing.
public JsonResult UpdatePerson(Person person)
{
db.People.Attach(person);
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return Json(new { status = "Person successfully saved." });
}
Any idea what's happening here? It seems from all the articles I've found all over the place that this should work, yet it doesn't. Again: no errors. The code just stops. I never get to the "return Json" line at all.

Related

How can I use a default value/model on WebAPI EmptyBody?

I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]

WebApi and Swagger

I am using asp.net webapi and using swagger to create a RestApi within a WPF app via AutoRest.
I am having problem figuring out how to consume the returned data if there is an error.
My controller is as follows;
// POST: api/Personnel
//[SwaggerResponse(HttpStatusCode.InternalServerError ,Type = typeof(HttpError))]
[SwaggerOperation("AddEditContract")]
[SwaggerResponse(HttpStatusCode.OK, Description = "Add/Edit a Contract", Type =typeof(int))]
public IHttpActionResult Post(ContractDto value)
{
try
{
var _contractsService = new Business.ContractsService();
var contractToSave = _contractsService.GetContractsById(value.CC_Id);
if (contractToSave == null)
{
return NotFound();
}
var ret = _contractsService.SaveContract(value);
if (ret > 0)
{
return Ok(ret);
}
else
{
return BadRequest();
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
I happened to have an error appear within the WebApi based on an error with AutoMapper but it was getting swallowed up. It is returning an error message in the response, which is great.
Here is the current AutoRest code for this call.
public static int? AddEditContract(this IBuxtedConAPI operations, ContractDto value)
{
return Task.Factory.StartNew(s => ((IBuxtedConAPI)s).AddEditContractAsync(value), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult();
}
As you can see its expecting an int. If I uncomment the
//[SwaggerResponse(HttpStatusCode.InternalServerError ,Type = typeof(HttpError))]
The int return type turns to object.
So the real question.
Here is my service call from WPF to the WebApi
public async Task<int> SaveContract(ContractDto entity)
{
using (var db = new BuxtedConAPI())
{
var ret = await db.AddEditContractAsync(entity);
return (int)ret;
}
}
If an object is returned how do I pick up if an error has occurred or if the simple int (with a success) is just returned.
Thanks in advance.
Scott
Can you post the swagger file that you're generating and passing to AutoRest?
The reason return type turns to object (or whatever common base class is shared between all the possible responses), is because AutoRest treats explicitly defined responses as return values. Exceptions are used only for the default response.
We're investigating ways to specify multiple error responses that will generate the appropriate exceptions.

Angular JS - send data to .NET Controller API

I have this angular js code here:
$http.post('/reports/', JSON.stringify($scope.user));
and its hitting my Reports Controller Post method:
[HttpPost]
public dynamic Post(Array data){
//do something
}
but when I check the data in my Post method when it hits in my breakpoint it appears as null :( how do I pass the data from $scope.user to my Controller. I did a console.log of $scope.user and the data is there, it is an object but trying to pass it in as JSON.
I found this:
public HttpResponseMessage Post([FromBody]Customer cust)
{
var newCust = _Repository.InsertCustomer(cust);
if (newCust != null)
{
var msg = new HttpResponseMessage(HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + newCust.ID.ToString());
return msg;
}
throw new HttpResponseException(HttpStatusCode.Conflict);
}
would I have to put [FromBody] Reports report instead of Array data
Just do this simple as possible, you are missing the parameter name:
$http.post('/reports/', {data: $scope.user});
Make sure that $scope.user is an Array, else change the type.

MVC Music Store Saving concurrency. What is causing this?

I am doing the MVC Music Store Tutorials and I have finished, every works fine but for some reason when I edit an album on an Admin account it comes up with this error when I try to save changes. It highlights db.SaveChanges(); what is causing this problem?
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
I am the only person accessing the site I have made as I am doing it locally only on my machine, I have been told that somebody else may changed something but this is not the case. What's going on?
This issue is explained on this page on the musicstore codeplex site.
Here is an excerpt:
In the Album class, you've defined [Bind(Exclude = "AlbumId")] on the class, which means that the code won't try and bind the AlbumId from the form. Which is fine, except that on the edit page, you're passing in a populated Album, which is presumably being populated using binding, which is of course ignoring the Album Id, so the AlbumId of the object passed into the edit method, is always 0, which throws a misleading concurrency error, because no rows are affected, because there's no album with ID of 0.

Database not updating model in MVC

So i just started using ASP.NET MVC and i'm really liking it, except i seem to have an odd knack to encounter the most bizarre of errors. I'm making a simple blogging application for myself. I have two simple models: post and comment. I have a partial view for creating a comment that is embedded in the details view for each post. When i submit the form to update the comment, it goes to my CommentsController's create action, which looks like...
[HttpPost]
public ActionResult Create(comment comment)
{
comment.date = DateTime.Now;
if (ModelState.IsValid)
{
post p = db.posts.Find(comment.post); //I've verified that comment.post is coming in
if (p.comments == null) p.comments = new List<comment>();
p.comments.Add(comment);
db.Entry(p).State = EntityState.Modified; //I'm using this line since that's how its done in the edit actionmethod of the BlogController. I was just updating db.posts.Find(... manually, but that wasn't workign either.
db.comments.Add(comment);
db.SaveChanges();
return RedirectToAction("Details", "Blog", new { id = comment.post });
}
return PartialView(comment);
}
The problem is that while the comment gets added to the database just fine, the post doesn't update. When i examine p just before the changes are saved, it's updated, but apparently it never actually commits to database since when i redirect to the Details, those comments aren't there. Is there anything obviously wrong with my code? Am i missing some basic fundamental of .NET or MVC? Let me know if i need to provide more code or context.
Interesting Note: No matter what, post.comments always seems to be null. I set it to an empty list when the post is created, but it still seems to come back null. Not sure if this is just a result of trying to store an empty list or if it has to do with my problem, though. Again, lemme know and i'll stick anything else needed up here.
Thanks!
Perhaps saving the changes is working fine but you don't see the saved comments to a post because you don't load them when you display the post. You can eager load the comments of a post in your action which displays a post like so:
post p = db.posts
.Include(p1 => p1.comments)
.Where(p1 => p1.Id == id)
.SingleOrDefault();
I also think that you can simplify your Create action:
[HttpPost]
public ActionResult Create(comment comment)
{
comment.date = DateTime.Now;
if (ModelState.IsValid)
{
db.comments.Add(comment);
db.SaveChanges();
return RedirectToAction("Details", "Blog", new { id = comment.post });
}
return PartialView(comment);
}
This should work if comment.post is the foreign key of a comment to the related post. (Your code looks like this is the case, because of Find(comment.post))
While #Slauma led me to my solution, I'm just posting my final code i used for future reference (thanks #George Stocker)
public ActionResult Create(comment comment)
{
comment.date = DateTime.Now;
if (ModelState.IsValid)
{
db.comments.Add(comment);
db.SaveChanges();
return RedirectToAction("Details", "Blog", new { id = comment.post });
}
return PartialView(comment);
}
and to retrieve comments...
public ActionResult Details(int id)
{
var post = (from p in db.posts
where p.id == id
select new { p.id, p.title, p.content, p.date, p.tag, comments = (from c in db.comments where c.post == id select c) }).SingleOrDefault();
post p2 = new post(post.id, post.title, post.content, post.date,post.tag, post.comments.ToList());
return View(p2);
}

Resources