asp mvc 4. Get current model in controller - asp.net-mvc

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.

Related

return RedirectToAction("Edit", Model)

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

Should we check id in POST action edit or not?

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.

Returning data with viewmodel in POST request

I have a view model like such:
public class MyViewModel
{
public string Name { get; set; }
public List<Purchases> Purchases { get; set; }
}
This viewmodel is sent to a view that allows the user to edit the name property. The Purchases property is used only to create a dropdown box for it:
<%: Html.DropDownListFor(t => t.Name, new SelectList(Model.Purchases, "Value", "Text")) %></p>
This works fine.
However, when I perform server-side validation and then return to the View, I'm getting an object null reference error because the Purchases property is now set to null. I'm guessing this is because when the form is submitted because the Purchases property isn't bound to any editable control, it isn't being passed back with the viewmodel.
How can I prevent this happening? I want to send back the List to be send back with the Post request always.
You don't need to send back the list. If validation fails then simply rebuild the view model from scratch. One of the main selling points of MVC is how well it works in a stateless environment. Web Forms used ViewState to do this kind of thing, I don't think you want to replicate this kind of functionality though.
I like to have two overloaded Action methods for this (both with the same name but different method signatures). One with an [HttpGet()] attribute and the other with an [HttpPost()]. If your model is found to be invalid on the POST then simply return the "GET" method (NOTE: you'll need to to pass in any parameters required to rebuild the view).
When I say return, I mean:
return MyGetAction();
and not a Redirect to the GET action.
If the model is valid then you could/should perform a RedirectToAction() to a GET Action (this means if the user hits the refresh button it won't submit the form again, this is called the Post/Redirect/Get (PRG) pattern)
You'd have to create a hidden input for each of the elements in the list in addition to the select list. Having said, that I think caching the results of the query on the server is a better way to handle repopulating the list if you don't want to perform the query again. There's no sense in sending the data back across the wire if the server can just hang on to it. Personally, I wouldn't even bother with the caching unless it proved to be a performance bottleneck. Just populate the model selection list from the DB.
<% for (int i = 0; i < Model.Purchases.Length; ++i) { %>
<%: Html.Hidden( string.Format( "Purchases[{0}]", i ), Model.Purchases[i] ) %>
<% } %>
Lee Gunn is spot on. To make the answer a little more concrete, it is very common to re-build non-scalar values in the event ModelState is not valid. This can be done by simply returning the [HttpGet] version in your Controller. However, you could simply re-build the purchases collection manually. It's up to you.
[HttpGet]
public ActionResult MyView(string name)
{
//get entity and build up a view model
var entity = _myDb.GetEntity(name);
MyViewModel vm = AutoMapper.Map<Entity, MyViewModel>(entity);
return vm;
}
[HttpPost]
public ActionResult MyView(MyViewModel vm)
{
If(!ModelState.IsValid)
{
//here is one way of doing it
return MyView("");
//OR
vm.Purchases = GetSomePurchasesBro();
return View(vm);
}
//continue on persisting and doing the Post Redirect Get
}
P.S.
Calling
return MyView("");
can be replaced with
return MyView(vm.Name);
Both will do the same thing (provided you're using the Html Helper Extensions, i.e. Html.TextBoxFor(x=>x.Name))
Defaut model binding looks in the ModelState for attemptedValues when rendering Html. This is described here by Steve Sanderson.

RESTful Controllers with Different Http Methods, But the Same Parameters

Let's say I have a Controller that handles a CRUD scenario for a 'Home'. The Get would look something like this:
[HttpGet]
public ActionResult Index(int? homeId)
{
Home home = homeRepo.GetHome(homeId.Value);
return Json(home, JsonRequestBehavior.AllowGet);
}
So far so good. Then I add a post action for adding new ones.
[HttpPost]
public ActionResult Index(Home home)
{
//add the new home to the db
return Json(new { success = true });
}
Awesome. But when I use the same scheme to handle puts (updating an existing home)...
[HttpPut]
public ActionResult Index(Home home)
{
//update existing home in the db
return Json(new { success = true });
}
We run into a problem. The method signatures for Post and Put are identical, which of course C# doesn't like. I could try a few things, like adding bogus parameters to the signature, or changing the method names to directly reflect CRUD. Those are hacky or undesirable, though.
What is the best practice for going about preserving RESTful, CRUD style controllers here?
This is the best solution that I know of:
[HttpPut]
[ActionName("Index")]
public ActionResult IndexPut(Home home)
{
...
}
Basically the ActionNameAttribute was created to deal with these scenarios.
HttpPut and HttpDeletes are restricted by some firewalls so at times simply HttpPost and HttpGet are used. If a record ID is passed in (or some other criteria) you know its an update. Granted - this is for you to determine, httpput may work just fine for you, this is just a warning on it, it usually isn't a big deal.
Either method used - beware of users trying to inject false IDs into the page in order to forcing updates of records they don't have access to. I get around this issue by hashing in this case home.HomeId on the view when we render it
ViewData["IdCheck"] = Encryption.ComputeHash(home.HomeId.ToString());
in your view:
<%: Html.Hidden("IdCheck", ViewData["IdCheck"]) %>
in your HttpPost or HttpPut method (whichever is doing the update)
if (Encryption.ComputeHash(home.HomeId.ToString()) != (string)Request.Form["IdCheck"])
{
throw new Exception("Hashes do not match");
}
Again - this same security issue exists no matter which method you use to do your update if you are trusting form data.

Validate unique keys with asp.net mvc and linq to sql?

I have a sql server table with 2 fields, ID (primary key) and Name (unique key).
I'm using linq to sql to produce model objects for asp.net MVC from this table.
To perform the model validation I've implemented IDateErrorInfo in a partial class
public partial class Company : IDataErrorInfo
{
private Dictionary<string, string> _errors = new Dictionary<string,string>();
partial void OnNameChanging(string value)
{
if (value.Trim().Length == 0)
{
_errors.Add("Name", "Name is required");
return;
}
}
}
This performs as expected with the Model.IsValid property and Html.ValidationSummary helper.
However, this code is just checking that the newly created Company has a Name that is not blank. I also need to check if the Name has been used by another Company in the table.
I could just call the AddCompany method on my repository and catch the SQLException, but this feels dirty.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="ID")] Company companyToCreate)
{
if (!ModelState.IsValid)
{
return View();
}
//Add to the Database
try
{
_companyRepos.AddCompany(companyToCreate);
return RedirectToAction("Index");
}
catch(SQLException ex)
{
return View("do something to show the error inserting");
}
}
Ideally I want the OnNameChanging method in the partial class to perform the unique key check before I try to add the Company.
Any ideas on how I should be doing this? The only thought I've has so far is to create a fresh database connection in the partial class and query the table.
Thanks
One possibility is for the AddCompany method to return a boolean indicating success or failure of the operation.
However, it is customary to catch this type of error before you attempt to add the record. Put a
bool Exists(string companyName)
method in your Company Repository, and use this to catch the error before attempting to add the record.
The reason this is preferable is you know exactly why the failure occurred. Otherwise, you would either have to catch a custom exception or examine a returned error code.
However you slice it, you're going to obviously have to hit the database to get a list of names already in use. Therefore, I would suggest adding a method in your repository that basically just returns an IList<string> or IEnumerable<string> that simply contains all the distinct names in that table in the DB. Then, in your validating method, simply use that method on the repository to get all the unique names, and implement your check there.

Resources