What is the best practice to protect model against unwanted parse/update after post in MVC3
Controller action called at HttpGet-> Product/Edit:
public ActionResult Edit()
{
Product p = new Product();
p.Id = 1;
p.Name = "PC";
Category cat = new Category();
cat.Id = 1;
cat.Name = "Non food";
p.Category = cat;
return View(p);
}
This is the Edit View:
#model MvcApplication3.Models.Product
#using (Html.BeginForm("Edit", "Product", FormMethod.Post))
{
#Html.HiddenFor(model => model.Id)
#Html.EditorFor(model => model.Name)
<input type="submit" value="Submit" name="go" />
}
After the browser gets the response, the user inserts the following html segment into the page:
<input type="text" value="5" name="Category.Id" id="Category_Id"/>
He posts the form, and the following controller action gets the "Product" parameter.
//
// POST: /Class1/Edit/5
[HttpPost]
public ActionResult Edit(Product p)
{
//Here: p.Company.Id is 5 !!!
db.Save(p);
return null;
}
The problem is that the user should not be allowed to post/update the c.Company.Id.
I would not like to check the whole parameter structure hunting for unwanted values.
Im seeking for the best practice to solve the problem.
Any help is appreciated!
Bests,
Boolish
You could separate the received entity type (i.e. the ViewModel) from the entity type persisted to the database, as described in this recent blog post by Josh Bush. Well worth a read - topical too as it stems from the recent similar problem experienced by GitHub.
e.g.
public ActionResult Edit(ProductModel p)
{
// Map ProductModel -> a Product instance
// Then save
}
That's why you should use view models and not db entities in your views
http://blog.gauffin.org/2011/07/three-reasons-to-why-you-should-use-view-models/
Related
I am very new to MVC, let me try to explain my scenario in plain simple English:
I have an strongly typed mvc form/page (Product.cshtml) with a model, say ProductViewModel.
This page has got two search buttons, one to search and bring the items to be added to the Product and other to bring in the location, most probably partial views.
Now, what I want is that these search results work in ajax form without complete post back, and then the results of these searches (items and location) should be posted back using model binding to the form when user clicks on the submit button.
What could be the best way of achieving this functionality?
Immediate responses will be well appreciated.
I thought, its good to share the complete code for clarity:
I have one form(Service1.chtml) that has a partial view to display users(_TestUser a partial view:read only), then another partial view(_PlotServiceRequestData) that should have a field to search the plot and bring back the details lke its owner name and landuser etc.
Then when I click on submit button of the main form, I should be able to read all data(main form) + new data from _PlotServiceRequestData partial view and save all data to database.
I was trying one more option, that is, to use #Ajax.ActionLink on Service1.cshtml to call the _GetPlotDetails method and then store partial view data in TempData, so that it is available to the form when users clicks on "Submit" button of Service1.cshtml, is this a right approach?, if I use ajax.BeginForm inside partial view then the data is posted to the
Service1 controller method which is actually to save the form data and not to update the partialview and in this method even I am not getting model data of the partial view.
Sevice1.cshtml:
#model ViewModels.TestViewModel
#{
ViewBag.Title =
"Service1";
}
#
using (Html.BeginForm())
{
#Html.LabelFor(m => m.Title)
#Html.EditorFor(m => m.Title)
#Html.Partial(
"_TestUser", Model)
<div id="RequestPlotData">
#Html.Partial(
"_PlotServiceRequestData", Model.requestData)
</div>
<button type="submit">Save Form</button>
}
#section Scripts {
}
_PlotServiceRequestData.cshtml:
===============================
#model ViewModels.PlotServicesRequestDataViewModel
<
div id="RequestPlotData">
#
using (Ajax.BeginForm("_GetPlotDetails", "Test", new AjaxOptions { UpdateTargetId = "RequestPlotData", Url = Url.Action("_GetPlotDetails","Test") }))
{
<h1>Request Details</h1>
<div>
#Html.LabelFor(m => m.plotAddress)
#Html.EditorFor(m => m.plotAddress)
<input type="submit" name="submit" value="Ajax Post" />
</div>
<div>
#Html.LabelFor(m => m.LandUser)
#Html.EditorFor(m => m.LandUser)
</div>
<div>
#Html.LabelFor(m => m.OwnerName)
#Html.EditorFor(m => m.OwnerName)
</div>
}
</
div>
CONTROLLER:
==========
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Web;
using
System.Web.Mvc;
namespace
TestNameSpace
{
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Service1()
{
Injazat.AM.mServices.
LocalDBEntities context = new Injazat.AM.mServices.LocalDBEntities();
TestViewModel model =
new TestViewModel() { user = context.Users.First(), Title = "Land Setting Out",
requestData =
new PlotServicesRequestDataViewModel() { ServiceNumber ="122345", TransactionDate="10/10/2033" } };
return View(model);
}
[
HttpPost()]
public ActionResult Service1(TestViewModel model)
{
PlotServicesRequestDataViewModel s = (PlotServicesRequestDataViewModel)TempData[
"Data"];
TestViewModel vm =
new TestViewModel() { user = model.user, requestData = s, Title = model.Title };
return View(vm);
}
[
HttpGet()]
//public PartialViewResult _GetPlotDetails(string add)
public PartialViewResult _GetPlotDetails(PlotServicesRequestDataViewModel requestData)
{
//PlotServicesRequestDataViewModel requestData = new PlotServicesRequestDataViewModel() { plotAddress = add};
requestData.OwnerName =
"owner";
requestData.LandUser =
"landuser";
TempData[
"Data"] = requestData;
return PartialView("_PlotServiceRequestData", requestData);
}
}
}
You can probably use the jQuery Form plugin for this. This makes the process of posting the data from your form back to the server very easy. The form would post to an action that would return a partial view that you can then push into your UI.
To make this easier, jQuery form actually has a "target" option where it will automatically update with the server response (ie. the partial view returned from your search action).
View
<form id="searchForm" action="#(Url.Action("Search"))" method="POST">
<input name="query" type="text" /> <!-- order use Html.TextBoxFor() here -->
<input type="submit" />
</form>
<div id="result"><!--result here--></div>
Javascript
$('#searchForm').ajaxForm({
target: '#result'
});
Controller
public ActionResult Search(string query)
{
// Do something with query
var model = GetSearchResults(query);
return Partial("SearchResults", model)
}
This should hopefully help you to get on the right track. jQuery Form is a good plugin and is the main thing you should look into for ajaxifying your form posts back to the server. You might also want to look into using jQuery's $.post and $.ajax functions, but these require slightly more work.
I understand how to use Partial Views, and I understand Ajax.ActionLink and Ajax.BeginForm when it comes to how to set those up in the view. I'm assuming each partial view has it's own controller. I'm thinking bounded context here, as in each partial view could talk to it's own bounded context via its own controller
I guess the piece I'm missing is:
how to have partial views included in a "master view" (or holding view) and have each of these partial views independently post to a separate controller action, and then return to refresh the partial view WITHOUT loading the "master view" or holding view.
the "master" view or holding view still needs to have its own controller, I want to keep the master controller from reloading its view, and let the view that is produced by an action method of the master controller hold a reference to these two partial views.
There are two approaches it seems I can take, one is to use the "Ajax." functionality of MVC3, the other is to use straight-up jQuery and handle all this interaction by hand from the client side.
Is what I'm trying to do possible both ways, or is one way "better suited" to this type of composite ui construction?
So far, the only things I have seen are trivial examples of composite ui construction like a link via an Ajax.ActionLink that refreshes a single on the page, or a form written as an Ajax.BeginForm that repopulates a div with some content from a partial view.
Okay, so I finally have some working code that I think is the right way to do it. Here is what I went with. I have a two simple "entities"; Customer and BillingCustomer. They're really meant to be in separate "bounded contexts", and the classes are super-simple for demostration purposes.
public class Customer
{
public Guid CustomerId { get; set; }
public string Name { get; set; }
}
public class BillingCustomer
{
public Guid CustomerId { get; set; }
public bool IsOverdueForPayment { get; set; }
}
Note that both classes reference CustomerId, which for the sake of this demo, is a GUID.
I started with a simple HomeController that builds a ViewModel that will be utilized by the Index.cshtml file:
public ActionResult Index()
{
var customer = new Customer {
CustomerId = Guid.Empty,
Name = "Mike McCarthy" };
var billingCustomer = new BillingCustomer {
CustomerId = Guid.Empty,
IsOverdueForPayment = true };
var compositeViewModel = new CompositeViewModel {
Customer = customer,
BillingCustomer = billingCustomer };
return View(compositeViewModel);
}
The CompositeViewModel class is just a dumb DTO with a property for each domain entity, since the partial views I'll be calling into in my Index.cshtml file each need to pass their respective domain model into the partial view:
public class CompositeViewModel
{
public BillingCustomer BillingCustomer { get; set; }
public Customer Customer { get; set; }
}
Here is my resulting Index.cshtml file that uses the Index method on the HomeController
#model CompositeViews.ViewModels.CompositeViewModel
<h2>Index - #DateTime.Now.ToString()</h2>
<div id="customerDiv">
#{Html.RenderPartial("_Customer", Model.Customer);}
</div>
<p></p>
<div id="billingCustomerDiv">
#Html.Partial("_BillingCustomer", Model.BillingCustomer)
</div>
A couple things to note here:
the View is using the CompositeViews.ViewModels.CompositeViewModel ViewModel
Html.RenderPartial is used to render the partial view for each
entity, and passes in the appropriate entity. Careful with the
syntax here for the Html.Partial call!
So, here is the _Customer partial view:
#model CompositeViews.Models.Customer
#using (Ajax.BeginForm("Edit", "Customer", new AjaxOptions {
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "customerDiv" }))
{
<fieldset>
<legend>Customer</legend>
#Html.HiddenFor(model => model.CustomerId)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
the important part here is the Ajax.BeginForm call. Note that it's explicitly calling the Edit ActionMethod of the CustomerController. Also note that the UpdateTargetId is set to "customerDiv". This div is NOT in the partial view, but rather in the "parent" view, Index.cshtml.
Below is the _BillingCustomer view
#model CompositeViews.Models.BillingCustomer
#using (Ajax.BeginForm("Edit", "BillingCustomer", new AjaxOptions {
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "billingCustomerDiv" }))
{
<fieldset>
<legend>BillingCustomer</legend>
#Html.HiddenFor(model => model.CustomerId)
<div class="editor-label">
#Html.LabelFor(model => model.IsOverdueForPayment)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.IsOverdueForPayment)
#Html.ValidationMessageFor(model => model.IsOverdueForPayment)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Again, note that UpdateTargetId is set to billingCustomerDiv. This div is located in the Index.cshtml file, not this partial view file.
So, the only thing we haven't looked at yet is the Edit ActionResult on the CustomerController and the BillingCustomerController. Here is the CustomerController
public class CustomerController : Controller
{
[HttpGet]
public PartialViewResult Edit(Guid customerId)
{
var model = new Customer {
CustomerId = Guid.Empty,
Name = "Mike McCarthy"};
return PartialView("_Customer", model);
}
[HttpPost]
public ActionResult Edit(Customer customer)
{
return PartialView("_Customer", customer);
}
}
There is nothing really "happening" in this controller, as the post deals directly with building a composite UI. Notice how we're returning via "PartialView" and specifying the name of the partial view to use, and the required model the view needs to render.
Here is BillingCustomerController
public class BillingCustomerController : Controller
{
[HttpGet]
public PartialViewResult Edit(Guid customerId)
{
var model = new BillingCustomer {
CustomerId = Guid.Empty,
IsOverdueForPayment = true };
return PartialView("_BillingCustomer", model);
}
[HttpPost]
public PartialViewResult Edit(BillingCustomer billingCustomer)
{
return PartialView("_BillingCustomer", billingCustomer);
}
}
Again, the same as CustomerController, except for the fact that it's this controller is dealing with the BillingCustomer entity.
Now when I load up my HomeController's Index ActionResult, I get a screen that looks like this:
Each Save button will do an async postback to the controller the partial view needs to update and talk to in order to get data, all without causing a regular postback for the whole page. You can see the DateTime stamp does NOT change when hitting either save button.
So, that's how I went about building my first composite view using partial views. Since I'm still very new to MVC3, I could still be screwing something up, or doing something in a way that is harder than it needs to be, but this is how I got it working.
It has been intriguing that my MVC3 razor form renders duplicated values inside a foreach code block in spite of correctly receiving the data from the server. Here is my simple form in MVC3 Razor...
-- sample of my .cshtml page
#model List<Category>
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
foreach (Category cat in Model)
{
<span>Test: #cat.CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => cat.CategoryName)</span>
#Html.HiddenFor(model => cat.ID)
<p>---</p>
}
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
My controller action looks something like this -
[HttpPost]
public ActionResult Save(ViewModel.CategoryForm cat)
{
... save the data based on posted "cat" values (I correctly receive them here)
List<Category> cL = ... populate category list here
return View(cL);
}
The save action above returns the model with correct data.
After submitting the form above, I expect to see values for categories similar to the following upon completing the action...
Test: Category1, Actual:Category1
Test: Category2, Actual:Category2
Test: Category3, Actual:Category3
Test: Category4, Actual:Category4
However #Html.TextBoxFor duplicates the first value from the list. After posting the form, I see the response something like below. The "Actual" values are repeated even though I get the correct data from the server.
Test: Category1, Actual:Category1
Test: Category2, Actual:Category1
Test: Category3, Actual:Category1
Test: Category4, Actual:Category1
What am I doing wrong? Any help will be appreciated.
The helper methods like TextBoxFor are meant to be used with a ViewModel that represent the single object, not a collection of objects.
A normal use would be:
#Html.TextBoxFor(c => c.Name)
Where c gets mapped, inside the method, to ViewData.Model.
You are doing something different:
#Html.TextBoxFor(c => iterationItem.Name)
The method internall will still try to use the ViewData.Model as base object for the rendering, but you intend to use it on the iteration item. That syntax, while valid for the compiler, nets you this problem.
A workaround is to make a partial view that operates on a single item: inside that view you can use html helpers with correct syntax (first sample), and then call it inside the foreach, passing the iteration item as parameter. That should work correctly.
A better way to do this would be to use EditorTemplates.
In your form you would do this:
#model List<Category>
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
#Html.EditorForModel()
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
Then, you would create a folder called EditorTemplates, either in the ~/Views/Shared folder or in your Controllers View folder (depending on whether you want to share the template with the whole app or just this controller), and in the EditorTemplates folder, create a Category.cshtml file which looks like this:
#model Category
<span>Test: #Model.CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => model.CategoryName)</span>
#Html.HiddenFor(model => model.ID)
<p>---</p>
MVC will automatically iterate over the collection and call your template for each item in it.
I've noticed that using foreach loops within Views causes the name attributes of text boxes to be rendered the same for every item in the collection. For your example, every text box will be rendered with the following ID and Name attributes:
<input id="cat_CategoryName" name="cat.CategoryName" value="Category1" type="text">
When your controller receives the form data collection, it won't be able reconstruct the collection as different values.
The solution
A good pattern I've adopted is to bind your View to the same class you want to post back. In the example, model is being bound to List<Category> but the controller Save method receives a model ViewModel.CategoryForm. I would make them both the same.
Use a for loop instead of a foreach. The name/id attributes will be unique and the model binder will be able to distinguish the values.
My final code:
View
#model CategoryForm
#using TestMvc3.Models
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
for (int i = 0; i < Model.Categories.Count; i++)
{
<span>Test: #Model.Categories[i].CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => Model.Categories[i].CategoryName)</span>
#Html.HiddenFor(model => Model.Categories[i].ID)
<p>---</p>
}
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
Controller
public ActionResult Index()
{
// create the view model with some test data
CategoryForm form = new CategoryForm()
{
Categories = new List<Category>()
};
form.Categories.Add(new Category() { ID = 1, CategoryName = "Category1" });
form.Categories.Add(new Category() { ID = 2, CategoryName = "Category2" });
form.Categories.Add(new Category() { ID = 3, CategoryName = "Category3" });
form.Categories.Add(new Category() { ID = 4, CategoryName = "Category4" });
// pass the CategoryForm view model
return View(form);
}
[HttpPost]
public ActionResult Save(CategoryForm cat)
{
// the view model will now have the correct categories
List<Category> cl = new List<Category>(cat.Categories);
return View("Index", cat);
}
I have an Index.cshtml view:
#model AttendenceModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("VisOppsummering", "Attendences", new { AttendenceModel = Model }, FormMethod.Post))
{
#Html.DisplayFor(m => m.ClassName)
#Html.EditorFor(m => m.Attendences)
<button type="submit">Next</button>
}
and an Editor Template Attendence.cshtml:
#model Attendence
#Html.DisplayFor(m => m.Student.Name)
#Html.RadioButtonFor(m => m.Attended, true, new { id = "attendence" })
Teachers can check off all students that attended school and than pass on the changed model to "Review" action where they can review all the attendended and not attended students and Submit. I want to use MVC best practice for this. AttendenceModel has several properties and a generic list Attendences which is List.
I've tried following without success. Model is empty.:
[HttpPost]
public ActionResult Review(AttendenceModel model)
{
if (TryUpdateModel(model))
{
return View(model);
}
}
The following argument to your BeginForm helper is meaningless:
new { AttendenceModel = Model }
you cannot pass complex objects like this. Only simple scalar values. You could use hidden fields in your form for all properties that cannot be edited and visible input fields for the other. Or even better: use a view model which will contain only the properties that can be edited on the form and an additional id which will allow you to fetch the original model from the database and using the TryUpdateModel method update only the properties that were part of the POST request:
[HttpPost]
public ActionResult Review(int id)
{
var model = Repository.GetModel(id);
if (TryUpdateModel(model))
{
return View(model);
}
...
}
as far as the view is concerned it would become:
#model AttendenceViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("Review", "SomeControllerName"))
{
#Html.HiddenForm(x => x.Id)
#Html.DisplayFor(m => m.ClassName)
#Html.EditorFor(m => m.Attendences)
<button type="submit">Next</button>
}
I have an edit action configured that is not updating records but not throwing any exceptions. The update appears to have worked but the changes are not reflected in the database.
I am using EF and MVC3, i have an interface that defines save customer
public interface ICustomerRepository
{
//allows a sequence of customers to be displayed
IQueryable<Customer> Customers { get; }
//saves edits to customer records
void SaveCustomer(Customer customer);
}
Then my implementation of this
public void SaveCustomer(Customer customer)
{
if (customer.CustomerId == 0)
{
context.Customers.Add(customer);
}
context.SaveChanges();
}
Then in the controller my get and post actions
public ViewResult Edit(int customerId)
{
Customer customer = repository.Customers.FirstOrDefault(c => c.CustomerId == customerId);
return View(customer);
}
[HttpPost]
public ActionResult Edit(Customer customer)
{
if (ModelState.IsValid)
{
repository.SaveCustomer(customer);
TempData["message"] = string.Format("{0} has been saved", customer.CustomerName);
return RedirectToAction("Index");
}
else
{
//there is something wrong with the data values
return View(customer);
}
}
Then in my view i have
#model CustomerOrders.Domain.Entities.Customer
#{
ViewBag.Title = "Admin: Edit" + #Model.CustomerName;
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h1>Edit #Model.CustomerName</h1>
using (Html.BeginForm("Edit", "Admin"))
{
<div class="left-column">
<div class="editor">#Html.EditorFor(model => model.CustomerId)</div>
<div class="label-for">#Html.LabelFor(model => model.CustomerName)</div>
<div class="editor">#Html.EditorFor(model => model.CustomerName)</div>
#Html.ValidationMessageFor(model => model.CustomerName)
</div>
<div class="middle-column">
<div class="label-for">#Html.LabelFor(model => model.PrimaryContactName)</div>
<div class="editor">#Html.EditorFor(model => model.PrimaryContactName)</div>
<div class="label-for">#Html.LabelFor(model => model.PrimaryContactNo)</div>
<div class="editor">#Html.EditorFor(model => model.PrimaryContactNo)</div>
</div>
<div class="right-column">
<input type="submit" value="Save" />
#Html.ActionLink("Cancel and return to list", "Index")
</div>
}
I also have a create method wired up to the same edit action and view which works fine. Not sure where i am going wrong, im new to MVC3 and not sure if my edit implementation is correct?
The simple answer is because your working in a stateless environment, unless your using self tracking entities, you need to Attach the object to EF's graph.
I've had problems with edit in the past though. So i ended up going to the DB to fetch the object first, then merging in the changes i need, then doing Save.
You need to seperate your Create/Edit actions out. Simply checking if the ID > 0 to deem an edit is not enough, be more explicit.
So to sum up:
Have one action method for new objects. Use context.AddObject in this scenario.
Have another action method for modifying objects. Go get the object from the repository, merge in your changes (left to right, or auto mapper, or TryUpdateModel), and do context.SaveChanges.
Overall, it's a pain. Many developers (myself included) have gone through what you have.