I'm trying to make changes to an object that my controller is getting back from the view. When I debug and step through the program and look at the values of job.JobTypesId and job.JobStatusID the correct values are displaying but they are not being saved while everything else does. I have read a couple of articles that explain that the framework needs to know that the model has changed and that's what the entity state is supposed to do (http://www.mattburkedev.com/saving-changes-with-entity-framework-6-in-asp-dot-net-mvc-5/). I have also tried to use UpdateModel but that dosent seem to work either (http://forums.asp.net/t/1807184.aspx?MVC+4+Not+saving+data+after+edit). I get a message "The model of type 'StaffServiceTracker.Models.Job' could not be updated.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Job job) {
var task = from t in db.JobTypes where t.JobTypeID == job.JobTypesID.JobTypeID select t;
var status = from s in db.Statuses
where s.StatusID == job.JobStatusID.StatusID
select s;
job.JobTypesID = task.FirstOrDefault();
job.JobStatusID = status.FirstOrDefault();
db.Entry(job).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
Any guidance would be appreciated.
First of all, you should use ViewModels (in 90% of the cases) instead of business objects in the views. This is because you would send only the necessary data to the client, you can combine business objects, etc.
Second, once you receive the ViewModel in the controller you should first validate it (always validate at client and server side because client side validations can be bypassed).
Once you have validated everything, get the Job object from the database using EF and set the properties. Call SaveChanges and that's it. It would be better if you move this code from the controller to the service layer.
One more thing: you can use SQL Server Profiler to know exactly what queries EF is executing. That way you'll understand what's happening.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(JobVM jobVM) {
// validate VM
// ...
// the following code should be in the Service Layer but I'll write it below to simplify
// get job from DB
var job = db.Jobs.SingleOrDefault(p => p.Id == jobVM.Id);
job.JobTypesID = jobVM.JobTypesID;
job.JobStatusID = jobVM.StatusId;
db.SaveChanges();
return RedirectToAction("Index");
}
Related
I've been following an EF core tutorial on basic CRUD operations, and am having trouble with getting data from my ViewModel.
I'm following their recommended practice of using TryUpdateModel (or in my specific case, TryUpdateModelAsync) in the following action.
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int id)
{
var versionToUpdate = await _context.Versions.FirstOrDefaultAsync(a => a.Id == id);
var editModel = new VersionEditModel() { Version = versionToUpdate };
if (await TryUpdateModelAsync(
editModel.Version,
"Version",
a => a.Name,
...further code removed for brevity
In the past, I would have passed the model itself in action signature, i.e.
public async Task<IActionResult> EditPost(VersionEditModel editModel)
But the recommendation in the tutorial was to use just the ID, fetch the item from the database and run TryUpdateModel against it.
This all works, but I want to access a value that has been POSTed to the action as part of the ViewModel, but I can't seem to access it.
If I add the model to the signature as an additional parameter, like this
public async Task<IActionResult> EditPost(int id, VersionEditModel editModel)
I can access it using editModel, but the TryUpdateAsync no longer works properly.
I assume there must be a way to get to this data, but I can't figure out the right way to get to it.
My main problem has been trying to find the right words to explain my issue and what I want, so please ask for clarification if something doesn't make sense.
I want to execute 2 Actions and make them atomic.
This is what I want to achieve:
public IActionResult DataUserForm()
{
return View();
}
[HttpPost]
public IActionResult Handler(data)
{
/*
Do something with data
*/
return RedirectToAction("Index");
}
DataUserForm View sends a Form to User where he can write some data. Then he is redirected to Handler, that does some business logic and redirects to Index homePage. I want to make all this Actions a transaction
There is already BeginTransaction() method in EF. But it can only be used with specific block of code and not in the methods. I also can not pass transaction as a parameter to action, because there are some problems with submit buttons and c# object
Your DbContext is a scoped service, so you have a single instance per web request.
"The ModelState represents a collection of name and value pairs that were submitted to the server during a POST." That is the best definition I have found for the ModelState property. So I have some code using a web api that Creates a customer and Updates a customer, and my question is, since I have accessed ModelState in CreateCustomer, wont the ModelState always be not valid if I hadnt already submitted a Post request with CreateCustomer? Or is the ModelState updated with both PUT and Post requests? Because according to the definition above, the ModelState property wont be initialized until I create a customer, and in my UpdateCustomer method, I need to check if the customer object given in the parameters is the one that is valid, not any Model that was submitted to the server during a different POST request.
[HttpPost]
//If we name it PostCustomer, we dont have to put the httppost tag
public Customer CreateCustomer(Customer customer)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
_context.Customers.Add(customer);
_context.SaveChanges();
return customer;
}
// Put /api/customers/1
[HttpPut]
public void UpdateCustomer(int id, Customer customer)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
Customer customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
customerInDb.Name = customer.Name;
customerInDb.Birthdate = customer.Birthdate;
customerInDb.IsSubscribedToNewsletter = customer.IsSubscribedToNewsletter;
customerInDb.MembershipTypeId = customer.MembershipTypeId;
_context.SaveChanges();
}
Code is from Mosh Hamedani's ASP.Net MVC course.
Link to the ModelState definition. https://exceptionnotfound.net/asp-net-mvc-demystified-modelstate/
That's an incomplete and somewhat confusing definition. A model is simply how you send data, nothing more. It could be via a GET, POST, PUT whatever. It just models whatever data each endpoint requires, nothing more.
I guess POST was mentioned because that's the most common scenario, but don't let it limit your understanding of it.
You might have the same model for a PUT and a POST or they might be a bit different.
For example your entity model may or may not include an ID. POST, which creates data does not need ID, while PUT does.
If the model you use for updating already includes the ID then you might decide to not have the id separate since it's redundant data. So you see, there are a number of decisions you need to make with an API.
So I've always used the loose PRG pattern where you return a view on ModelState validation failure from a POST action. However, it's always bothered me that I have to build the model not only in the GET action, but also rebuild it again in the POST action on failure. I've used different methods of doing the rebuilding using "view model builders" or just a private function within the controller that builds the view model for both actions, but these still bother me as well.
After reading this article by Ben Foster (http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc), it makes a lot more sense to just rely on the GET action to build your view model - keeping it one area of the code - and then use the necessary action filters to save the ModelState for rendering when you are redirected back to GET on a failed POST.
So I've implemented that using the filters Ben mentions in his article like below. However, I am curious what happens if the user refreshes after they have been redirected back to the GET on a ModelState failure? How would I differentiate between someone accessing the GET directly versus a ModelState failure? Currently the ModelState would be gone if a user refreshes at that point. Is that the correct action, or should the user continue to see the errors until they POST with valid data? Essentially, should they be seeing the data that is in the database, or should they continue to see the changes they made when POSTing?
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
// in a real application this would be retrieved from the db
var editView = new EditView()
{
UserId = id,
Name = "John Doe",
Age = 20,
Message = "Hello world"
};
return View(editView);
}
[HttpPost]
[ValidateModelState]
public ActionResult Edit(EditCommand editCommand)
{
// save to db here in real application
return RedirectToAction("Success");
}
I use the same [ImportModelStateFromTempData] filter in a couple projects, and it works great.
In my opinion, if the user refreshes, you shouldn't preserve any model state errors. The user is asking for a fresh view of that page, and it'd be frustrating to never be able to get a clean view. In same vain that refreshing after a POST shouldn't resubmit a form, refreshing after a GET shouldn't preserve a POST.
I've got two controller actions at the moment: one that displays a strongly typed view, based on an unique identifier and another one that changes the model. Here's some code to visualize what I mean:
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
// Request the device based on the code.
var device = GetDeviceModel(code);
// Present the device in a view.
return View(device);
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
}
The code identifies the device, but it is also possible to change it. And that's my problem: Inside the post method I can access the new code using model.Code, but I also need to know the old code, to be able to change it.
I tried several alternatives, but none of them did satisfy my requirements:
ViewData does not get persisted until the post.
TempData is based on Sessions or Cookies – none of them I want to use at the moment.
Hidden fields and model bindings are not an option, because they can be manipulated on client side.
Finally I tried requesting data from the query string like this:
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldCode = Request.QueryString["code"];
}
And this works! But I haven't found any resources on this around the web. So my question is: Is using the query string inside a post action safe against modifications? And what (if there are any) are the conditions for this to work?
If this is not a valid way to "remember" the code, are there any alternatives?
Based on the requirements you have mentioned in your question, it seems that you are looking for safety in the case. So I would say that QueryString is not safe. In my opinion using Session variables is the appropriate method that keeps your critical data in a safe location. You can use the method like this
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
Session["code"] = code;
....
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
if (Session["code"] == null)
throw new Exception("Error Message.");
var code = Session["code"].ToString();
....
Session["code"] = null;
}
I think you can make another property inside DeviceModel name OldCode.
and in your view you can save this value in hiddenfield like this
#Html.HiddenFor(m=>m.OldCode)
Now in controller post method you can get both the values like this.
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldcode=model.OldCode;
var newcode=model.Code;
}
Hope this helps...
Nothing submitted via a GET or a POST request is safe from modifications. While a GET query string is obviously easy to modify, it doesn't take much effort to spoof POST variables either. It sounds to me like you need to re-think your approach.
Optimally, you would do permission checking server-side to determine if the user is allowed to update the device with the passed (old) code. If they do not have permission, return an error such as an HTTP 550 code.
If you truly can't support this approach, I would suggest adding an "OldCode" field to the DeviceModel class as suggested by others, but encrypting it before sending it to the client. Then you can safely write it to a hidden field, and decrypt back on the server without fear of the user changing the value.