Database not updating model in MVC - asp.net

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);
}

Related

With same parameters and methods name, how can the controller finds which one to be invoked in ASP.NET core MVC

I am following the tutorial posted on the Microsoft website https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/details?view=aspnetcore-2.2
I just wonder once I click the delete button, how does it know which method or action should be invoked first? get or post? with the same parameters and action name
The code below might show you more details.
Thank you
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.FindAsync(id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
As stated in the comments above, a GET request will usually return a View to perform an action, but it won't actually perform that action unless it is a POST request, e.g. a GET request to an action named Edit will grab the data to edit and display it. That's it. The changes are not saved to the database until a POST to the Edit action is submitted.
Overloaded methods are required to have different parameter signatures. Since the other scaffolded pairs of CRUD actions (except Delete) have different signatures, they can have the same name. But since both the GET and POST methods for the Delete action have the same parameter signature, one of them needs to be renamed, which is why the POST action is named DeleteConfirmed. However, having GET and POST methods named differently will break the routing built into MVC. Adding the ActionName("Delete") attribute fixes that.
Routing depend on the HTTP Method + The name + The Parameters
so, when you issue a GET request to /Movies/Delete/5 it will use the first one.
When you issue a POST request to /Movies/Delete/5, it will use the second one.
If you have more than one POST method with different parameters, it will use the most specific. ex:
Delete(int id, bool confirm)
Delete(int id)
If you issue a POST request to /Movies/Delete/5, it will go for the second action, but if you change it to /Movies/Delete/5?confirm=true, it will go for the first one unless the parameter confirm was nullable, in this case it will throw an exception as it will not be able to determine which action to invoke

Entity Framework 6 update record SaveChanges does nothing

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.

MVC 5 refresh partial view with information from post

Hello I have this puzzle and i would like to share and see if my solution is plausible and possibly get some help.
Basically I have a view called "Create".
Now i click "Escolher Ficheiro" (means "Choose File"), I choose the wanted file and click fill.
So far so good, my file reaches the POST method and its ok at this point.
You can see in the image below that I extract that number from the file and the button "Create" its shown.
However as you can see my "Choose File" input gets null "Nenhum fic..."(means no file selected)
This happens because I return the View Create and it refreshes my whole page.
The thing is that I need to go trhough a POST action on my Controller, so i can read that Prop1.
For obvious security reasons I cannot set a file by default, so I have tried to have some partial views and returning only that partial, but there is something im missing completly. Is there a way of refreshing the "bottom part of the view trhough ajax but going trhough the action on the controller at the same time?
Here is my controller Action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "XL2XMLProcID,SourceXLFiles,Prop1,Prop2,TargetXMLFile,State")] XL2XMLProc xL2XMLProc, HttpPostedFileBase postedFile, string submitButton)
{
if (ModelState.IsValid)
{
if (postedFile != null)
{
// fetch the date from the file
var h = new XLHelper();
var v = h.Fetch("nrContaACreditarRC", postedFile.FileName, #"C:\somefolder\somefolder\somefolder\ExcelSamples\");
if (submitButton == "Create")
{
TestZipAndUpload(v);
}
else
{
// extract postedFile data to show
xL2XMLProc.Prop1 = v;
ViewBag.comingFromFill = true;
return View(ActionName.Create.ToString(), xL2XMLProc);
}
}
else
{
// posted file is null, throw error.
return View();
}
return RedirectToAction(ActionName.Index.ToString());
}
return View(xL2XMLProc);
}
Thank you in advance.

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.

RedirectToAction usage in asp.net mvc

I want to post some questions about ASP.Net MVC. I am not familiar with web developing, But I was assigned to the web part of a project. We are doing the following: first, we create get & set properties for the person data:
public class Person
{
public int personID {get;set;}
public string personName {get;set;}
public string nric {get;set;}
}
and after login, we put the data in a class Person object and we use RedirectToAction like this:
return RedirectToAction("profile","person",new { personID = Person.personID});
It's working normally, but the parameter are shown in the URL. How can I hide them and also
can I hide the action name? Guide me the right way with some examples, please.
The parameter are shown in the URL because that is what the third parameter to RedirectToAction is - the route values.
The default route is {controller}/{action}/{id}
So this code:
return RedirectToAction("profile","person",new { personID = Person.personID});
Will produce the following URL/route:
/Person/Profile/123
If you want a cleaner route, like this (for example):
/people/123
Create a new route:
routes.MapRoute("PersonCleanRoute",
"people/{id}",
new {controller = "Person", action = "Profile"});
And your URL should be clean, like the above.
Alternatively, you may not like to use ID at all, you can use some other unique identifier - like a nickname.
So the URL could be like this:
people/rpm1984
To do that, just change your route:
routes.MapRoute("PersonCleanRoute",
"people/{nickname}",
new {controller = "Person", action = "Profile"});
And your action method:
public ActionResult Profile(string nickname)
{
}
And your RedirectToAction code:
return RedirectToAction("profile","person",new { nickname = Person.nickname});
Is that what your after?
If you don't want the parameter to be shown in the address bar you will need to persist it somewhere on the server between the redirects. A good place to achieve this is TempData. Here's an example:
public ActionResult Index()
{
TempData["nickname"] = Person.nickname;
return RedirectToAction("profile", "person");
}
And now on the Profile action you are redirecting to fetch it from TempData:
public ActionResult Profile()
{
var nickname = TempData["nickname"] as string;
if (nickname == null)
{
// nickname was not found in TempData.
// this usually means that the user directly
// navigated to /person/profile without passing
// through the other action which would store
// the nickname in TempData
throw new HttpException(404);
}
return View();
}
Under the covers TempData uses Session for storage but it will be automatically evicted after the redirect, so the value could be used only once which is what you need: store, redirect, fetch.
this may be solution of problem when TempData gone after refresh the page :-
when first time you get TempData in action method set it in a ViewData & write check as below:
public ActionResult Index()
{
TempData["nickname"] = Person.nickname;
return RedirectToAction("profile", "person");
}
now on the Profile action :
public ActionResult Profile()
{
var nickname = TempData["nickname"] as string;
if(nickname !=null)
ViewData["nickname"]=nickname;
if (nickname == null && ViewData["nickname"]==null)
{
throw new HttpException(404);
}
else
{
if(nickname == null)
nickname=ViewData["nickname"];
}
return View();
}
Temp data is capable of handling single subsequent request. Hence, value gone after refresh the page. To mitigate this issue, we can use Session variable also in this case. Try below:
public ActionResult Index(Person _person)
{
Session["personNickName"] = _person.nickName;
return RedirectToAction("profile", "person");
}
And in "profile" actionmethod:
public ActionResult profile()
{
Person nickName=(Person)Session["personNickName"];
if(nickName !=null)
{
//Do the logic with the nickName
}
}

Resources