I see everywhere (in each tutorial) something like that:
public ActionResult Edit(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
[HttpPost]
public ActionResult Edit(Employee employee)
{
if (ModelState.IsValid)
{
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
So in GET action edit we check if id exists but in POST action edit not. Shouldn't we check also id in POST action edit? I mean something like that:
[HttpPost]
public ActionResult Edit(Employee employee)
{
// Check if employe exists in database:
Employee employeeFromDB = db.Employees.Find(employee.id);
if (employeeFromDB == null)
{
return HttpNotFound();
}
if (ModelState.IsValid)
{
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
Or maybe this isn't necessary because database doesn't allow to save employee with bad id? What do you think?
It's actually not necessary, for the reason you note; Your code is going to result in an UPDATE command, but if the id of the entity is not valid, you'll get an error. There's no need to explicitly load it again, for purposes of checking the existence of an entity matching the id...
That said, there are possibly other reasons you want to do that. For instance, to make sure the logged-on user is allowed to edit that particular entity.
One thing I would address earlier rather than later is decoupling your entity model from your view, this is not good practice. What if your entities contain sensitive data? or complex models? This data is viewable in the browser. You potentially end up passing additional data to your view and end up with 'fat' models, keep them small and necessary.
With a View Model you can apply validations to them which will handle your problem for you. These errors will then populate when you call ModelState.IsValid and pass them back to the view. I use Fluent Validation instead of the default MVC annotations out of preference. But check out both / similar and see which suits you.
By separating out your validations into your view model you keep your controllers nice and tidy. It's more effort of course as you need to map them back and forth from your database, but it gives you a great deal of flexibility whereby you can extend them, add validations etc.
There is nothing wrong with doing a sanity check imho. If nothing to give you some error during the development phase.
Related
I am new to asp.net mvc and recently I came across a method called AsNoTracking(). I did some research and found out that AsNoTracking() helps to speed up performance. It should be used with queries in which we do not want to save the data back to the database. So I know that AsNoTracking() helps to boost performance if you are returning hundreds to thousands of records. However, What I was wondering is that What if you are only returning one record?. For example the record of a specific employee. Is there a need to use AsNoTracking()?
Its will be useful to know that where AsNoTracking() could be needful.
In certain scenario where you post an Edit call and before actually updating the model to database you are trying to check if particular record already exists or not and then you want to place Update call it will throw exception. Below is the demonstration of such scenario.
public ActionResult Edit(Model model)
{
var modelFromDb = db.Model.Where(x => x.ID == model.ID);
if (modelFromDb.Count() > 0)
{
db.Entry(model).State = EntityState.Modified; //THIS IS WHERE THE ERROR WILL BE THROWN AS RECORD WITH ID IS ALREADY ATTACHED WITH modelFromDb OBJECT.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You will find many questions asking about error like "Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value" which can be caused by above snippet.
AsNoTracking() can prevent such exceptions. Suggested code will be like below.
public ActionResult Edit(Model model)
{
var modelFromDb = db.Model.AsNoTracking().Where(x => x.ID == model.ID);
if (modelFromDb.Count() > 0)
{
db.Entry(model).State = EntityState.Modified; //WORKS PERFECTLY.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You can refer one such question asked here ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value
Afternoon Folks,
I have a question in reference to how to redirect to another model within my mvc project.
I have a controller with CRUD operations, and i have a [HttpPost] action on Create. The system saves the data to the database as required but instead of me sending the user back to the "Index" page i want to sent them to another model.
I know how to use the redirect option...
// POST: CarerDetailsNew/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CarerViewModel carerViewModel)
{
if (ModelState.IsValid)
{
db.CarerDetails.Add(carerViewModel.carer);
db.SaveChanges();
return RedirectToAction("Edit", carerViewModel.carer);
//return RedirectToRoute("ClientRecordsController");
}
return View(carerViewModel);
}
However i have read that i may be able to use the "RedirectToRoute" option to get back to another model.
The model that i am in is my "Carer" model but i want to get back to my "Client" model and specifically the "Edit".
I have tried to replace my RedirectToAction to one of the following but this fails to see the "Client" model.
return RedirectToRoute("Edit", clientViewRecord.client);
Or
return RedirectToRoute(ClientRecordsController, "Edit", clientViewRecord.client);
Any suggestions are more than welcome as i am struggling to get this to work.
Regards
Betty
I've been looking around and can't quite find the answer. I'm using a ViewModel in my Edit View so that I can have values for some dropdownlist. Now when I go to update my DB I'm not understanding how I can update my database record. I'm guessing I could create a new entity object, do a Find, and then update each property based on the ViewModel passed in from the Form but that sure seems like a lot of manual work.
Here I'm using the VeiwModel in the Edit View.
#model CPPCustomerCall.ViewModels.CustomerCallVM
Here is my controller's ActionResult. I changed the object type of the ActionResult to take in CustomerCallVM instead of the CustomerCall which was auto-generated. I assume since the Edit View's model is the ViewModel that's the type of object the ActionResult will receive. However, my ViewModel has more properties that aren't needed for the Entity Model to update the record. How do I go about updating my DB record in this ActionResult?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
db.Entry(customerCall).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
First, Bind and view models are mutually exclusive. If you don't want something to be eligible to be modified, then it shouldn't be on your view model, in the first place. Barring that, view models diverge from entities in the sense that they can't be saved directly. As a result, there's always some intervention present on your part to map the posted values back onto the entity, which means you can then selectively not map over certain properties that shouldn't be, regardless of whether they were posted or not. Long and short, get rid of the Bind stuff. It's just something else to maintain and a huge source of potential bugs.
That said, the code you have is workable; you're just missing the crucial part where you map the data from your view model back onto your entity. First, you need to fetch the entity from the database so you have a base to work from:
var customerCall = db.CustomerCalls.Find(id);
if (customerCall == null)
{
return new HttpNotFoundResult();
}
FWIW, your edit route should include the id in the route, according to REST conventions. Following REST isn't strictly required, but it's certainly recommended. While a web application adhering to REST doesn't mean it's a good application, not adhering to rest is generally a sure sign of a badly designed and coded application.
Then, you map over your properties. You can either do this manually:
customerCall.CustomerName = model.CustomerName;
// etc.
Or you can use a library like AutoMapper.
mapper.Map(model, customerCall);
AutoMapper requires a bit of initial setup to make this magic work, of course, so review the docs, if you're going that route. Manual mapping is easier, but far more tedious and repetitive.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
// Find The record you need
var dbObj = CustomerCalls.FirstOrDefault(x=> x.id = customerCall.id);
//Check if null
if(dbObj == null) dbObj = new CustomerCall();
/// Map your properties
// Add object to the stack
if(dbObj.id == 0){
CustomerCalls.Add(dbObj);
}else{
CustomerCalls.Update(dbObj);
}
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
In my action I need to delete some rows from database. But if exception is thrown, I need to return View with current model:
[HttpGet]
public ActionResult Delete(int id)
{
try
{
mDataMgr.DeleteUnit(id);
}
catch (DataManagerException ex)
{
if (ex.Error == DataManagerError.UnitHasMaps)
{
ModelState.AddModelError(String.Empty, "Unit has maps");
UnitRegionsViewModel regionsVM = new UnitRegionsViewModel()
{
Regions = mDataMgr.UnitRegions(id),
UnitId = id
};
return View("View", regionsVM);
}
}
return RedirectToAction("List");
}
I have to reload my current Model from database. Is there any ways to get current Model in my action?
There is no such thing as a "current model". Web pages are "stateless". Once a page is rendered, all information about that page is gone, including its model. One would normally post the model back to the controller if they want to get that information.
You could certainly serialize your model to hidden fields in the page, then accept that model as a parameter to your Delete method. However, if any of that information is sensitive, this is not something you should do.
Alternatively, you could fetch that model from the database and send it back? Since you have the Id that should be something you might do. Or a better way is let the delete Action accept the complete model. That way you can just return it if delete fails without fetching from the DB? Or, your delete method in your repository should send back a complete model if delete fails? All can be done.
In my Controller before a Model is modified (updated or deleted) I am trying to verify that the User performing the action actually owns the object they are trying to modify.
I am currently doing this at the method level and it seems a bit redundant.
[HttpPost]
public ActionResult Edit(Notebook notebook)
{
if (notebook.UserProfileId != WebSecurity.CurrentUserId) { return HttpNotFound(); }
if (ModelState.IsValid)
{
db.Entry(notebook).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(notebook);
}
Is there a generic way of doing this that could be reusable across various models?
Is it possible to do this with an ActionFilter?
A filter approach might look like:
public class VerifyOwnership : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
foreach(var parameter in filterContext.ActionParameters)
{
var owned = paramter.Value as IHaveAnOwner;
if(owned != null)
{
if(owned.OwnerId != WebSecurity.CurrentUserId)
{
// ... not found or access denied
}
}
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
That assumes models like Notebook implement a specific interface.
public interface IHaveAnOwner
{
int OwnerId { get; set; }
}
Blowdart has a good point that a user could tamper with the OwnerId in a post. I'm sure they could tamper with their auth ticket, too, but they'd have to know the other user's ticket and tamper with both to get the IDs to match for another user, I believe.
I can see one problem with what you have - you are relying on user input to perform the security check.
Consider your code
if (notebook.UserProfileId != WebSecurity.CurrentUserId)
Notebook has come from model binding. So UserProfileId has come from model binding. And you can quite happily fake that - for example I use Firefox's TamperData to change the value of the hidden UserProfileId to match my login and away I go.
What I end up doing (in a service, rather than the controller) is on a post pulling back the record from the database based on the unique id passed (Edit/2 for example would use 2), and then checking User.Identity.Name (well, the passed identity parameter) against the current owner field I have in my returned database record.
Because I pull back from the database (repository, whatever) an attribute isn't going to work for this, and I'm not sure you could be generic enough in an attribute's approach anyway.
The filter sounds like an ok approach, but it's somewhat limited. It would be nice if you could have a filter like this:
[RequireOwnership<Notebook>(n => n.UserProfileId)]
...but Attributes are limited in which data types are allowed, and I don't think that generics are allowed either. So you could have a [RequireOwnership] attribute that works by inspecting model properties using reflection, or you could create a custom validator instead, where your model looks like this:
public class Notebook
{
[MatchesCurrentUserId]
public int UserProfileId { get; set; }
}
Then your ModelState.IsValid check should suffice.
Edit:
Another option occurred to me. You could use a filter in conjunction with an attribute on your model (doesn't have to be a ValidationAttribute). The filter could inspect your request models and check for properties with [MatchesCurrentUserId], comparing with the current user ID.
When I have done things like this in the past it really hasn't been much better. For our projects we would have a method that would accept a Notebook object and check it against the currently logged in user.
You could overload this method with all of your different object types and use a consistent method for checking access. Sorry, that's the best way I know of.
[HttpPost]
public ActionResult Edit(Notebook notebook)
{
if(!SessionUser.LoggedInUser.CheckAccess(notebook))
return HttpNotFound();
//Other code...
}
P.S. SessionUser is a custom class we had basically just to manage whoever is logged in at the time. You could write something similar but don't expect it to be in .NET by default.