ASP.NET MVC3 edit action not updating records in database - asp.net-mvc-3

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.

Related

how do I build a composite UI in MVC3?

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.

MVC3 - list information used for dropdownlist is null after post

I wish to show a DropDownList in a view and therfore include in my model (ExampleAddSetupDto) sent to a view a list of entries to populate the dropdownlist. That works fine, but if I have a validation error and redisplay the view with in incoming model my list is now null.
My Action is given below (note: the problem occurs if ModelState.IsValid fails). Also the Action method second parameter may look odd as I am using Autofac to inject the right service into the method).
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add(ExampleAddSetupDto add, IServiceAddCommit<IExampleAddSetupDto, IExampleAddCommitDto> service)
{
if (ModelState.IsValid)
{
var response = service.Create(add);
if (response.IsValid)
{
TempData["message"] = "You successfully added a new Example Entry";
return View("AddSuccess", response);
}
//else errors, so copy the errors over to the ModelState
response.CopyErrorsToModelState(ModelState, add);
}
// Some validation error, so redisplay same view
return View(add);
}
My model looks like this:
public class ExampleAddSetupDto : IExampleAddSetupDto
{
[StringLength(50, MinimumLength = 2)]
public string Name { get; set; }
public int Option1Id { get; set; }
public int Option2Id { get; set; }
//-----------------------------
//now the properties for the drop down lists
public IList<Option1> PosibleEntriesForOption1 { get; set; }
public IList<Option2> PosibleEntriesForOption2 { get; set; }
}
My View is:
#model ServiceLayer.Example.DTOs.ExampleAddSetupDto
#{
ViewBag.Title = "Add";
}
<h2>Add</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Add an Example item</legend>
<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>
<div class="editor-field">
#Html.Label("Option1")
#Html.DropDownListFor(model => model.Option1Id, new SelectList(Model.PosibleEntriesForOption1, "Option1Id", "OptionText"))
#Html.ValidationMessageFor(model => model.Option1Id)
</div>
<div class="editor-field">
#Html.Label("Option2")
#Html.DropDownListFor(model => model.Option2Id, new SelectList(Model.PosibleEntriesForOption2, "Option2Id", "OptionText"))
#Html.ValidationMessageFor(model => model.Option2Id)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
I understand that I need to return the Model.PosibleEntriesForOption in with the form. I tried using the Html.HiddenFor helper in the view to return the list, i.e.
#Html.HiddenFor(model => model.PosibleEntriesForOption1)
but this throws the error
'The value 'System.Collections.Generic.List`1[DataClasses.ExampleClasses.Option1]' is invalid.'.
Clearly I am missing something here and I would appreciate some advice on how to return the lists so that redisplaying the model won't cause an error.
If you are forced to persist the entire list between the two requests, for whatever reason, the best way I see to do this would be to use:
TempData["EnterUniqueKeyHere"] = PossibleEntriesForOption1;
to store it, and then:
PossibleEntriesForOption1 = TempData["EnterUniqueKeyHere"] as IList<Option1>;
to retrieve it.
Note that anything stored in TempData will be removed automatically after a single request.
If the Validation is failing, you need to load up the the dropdownlist with the List of values. Other wise, it will fail.
I believe, when you load up your view initially, it executes HttpGet Method. In HttpGet method you must be binding the Dropdownlist
When you submit page, it executes httpPost method, if all is well, it will submit. If validation fails, it will execute, HTTPPost method, but it cannot find any binding for dropdown.
So try this : In your case
if (ModelState.IsValid)
{
var response = service.Create(add);
if (response.IsValid)
{
TempData["message"] = "You successfully added a new Example Entry";
return View("AddSuccess", response);
}
//else errors, so copy the errors over to the ModelState
response.CopyErrorsToModelState(ModelState, add);
}
else //if validation fails, you need to reload the dropdown and display your view.
{
// populate your dropdown again
// You can add errors list into ModelState.
ViewData.ModelState.AddModelError("What is the error", "Error Message, "What needs to be done by user, to get it work");
return view(add)
}
On the get action for add, you will be creating the model with appropriate values for these 2 properties - PosibleEntriesForOption1 & PosibleEntriesForOption2
Since these are set properly & available on the view, the dropdown gets rendered correctly on the get.
Now on a POST, when validation fails, you have to set those properties again.
if (ModelState.IsValid)
{
// Do something
}
// before you redisplay the same view
// set the properties PosibleEntriesForOption1 & PosibleEntriesForOption2
// Some validation error, so redisplay same view
return View(add);
The TempData technique from Dan Nixon works once but if the validation fails again, the TempData entry is null. I guess I'll have to reload my lists too.

How to keep the same data when return to the view?

How to keep the same data when return to the view?
I tried to put return the form to the view, but it did not work.
Is there any good and simple way to do this?
[HttpPost]
public ActionResult Register(FormCollection form)
{
string name = form["Name"].Trim();
if (string.IsNullOrEmpty(name))
{
TempData["TempData"] = "Please provide your name ";
return View(form);
}
string email = form["Email"].Trim();
var isEmail = Regex.IsMatch(email, #"(\w+)#(\w+)\.(\w+)");
if (!isEmail)
{
TempData["TempData"] = "Sorry, your email is not correct.";
return View(form);
}
//do some things
}
Not sure why you would be using FormCollection in the post but maybe you come from a WebForms background. In MVC you should use ViewModels for the transport of your data to and from the Views.
By default the Register method in an MVC 3 app uses a ViewModel in the Register View. You should simply post it back. In fact, the default app has that already created for you if you didn't know as part of the Internet template.
The standard pattern is to have a ViewModel that represents your data that you will use in your View. For example, in your case:
public class RegisterViewModel {
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
}
Your controller the should contain 2 actions, a Get and a Post. The Get renders the View and is ready for the user to enter data. upon submitting the View the Post action is then called. The View sends the ViewModel to the action and the method then takes action to validate and save the data.
If there is a validation error with the data, it's very simple to return the ViewModel back to the View and display the error messages.
Here is the Get action:
public ActionResult Register() {
var model = new RegisterViewModel();
return View(model);
}
And here is the Post action:
[HttpPost]
public ActionResult Register(RegisterViewModel model) {
if(ModelState.IsValid) { // this validates the data, if something was required, etc...
// save the data here
}
return View(model); // else, if the model had validation errors, this will re-render the same view with the original data
}
Your view would look something like this
#model RegisterViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Name) <br />
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Email) <br />
#Html.ValidationMessageFor(model => model.Email)
</div>
}
Using other strategies to capture and save data in an MVC app is absolutely possible, it's a very extensible framework. But there is a specific pattern that makes MVC what it is and working against that pattern can sometimes prove difficult. For a beginner it is best to understand the preferred patterns and strategies first and then once understood very well, you can then adopt some of your own custom strategies to meet your needs. By then you should understand the system well enough to know what you need to change and where.
Happy coding!!

Best practice to protect model after post in MVC3

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/

Performing create operations on two models via one action

I have two models : Category and Picture which refers to two tables, Categories and Pictures respectively. The Category model has a navigation property to Picture model.
Now, I created a controller using Scaffolding feature with CRUD operations for Category. Following is the code :-
public ActionResult Create()
{
ViewBag.ParentCategoryId = new SelectList(db.Categories, "Id", "Name");
ViewBag.PictureId = new SelectList(db.Pictures, "Id", "PictureUrl");
return View();
}
The automatically generated controller actions uses SelectList for listing the available Picture entries in the database and passes it down to dropdownlist for selection. This is not the ideal scenario since what I want is to unable the user to upload the Picture and then the reference is added to Category model. Later, the entries are saved to Categories and Pictures table.
Create model like this:
public class FullCategoryModel
{
public HttpPostedFileBase Picture { get; set; }
public Category CategoryModel {get; set;}
}
In view:
#using (Html.BeginForm("Create", "Category", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(model => model.Category.Name) // example, put there all category details
<input type="file" name="Picture" id="Picture" />
<input type="submit" value="Upload" />
}
Then create action:
[ActionName("Create")]
[HttpPost]
public ActionResult Create(FullCategoryModel model)
{
// here you can get image in bytes and save it in db,
// also all category detail are avalliable here
MemoryStream ms = new MemoryStream();
model.Picture.InputStream.CopyTo(ms);
Image picture = System.Drawing.Image.FromStream(ms);
// save in db as separate objects, than redirect
return RedirectToAction("Index", "Category");
}
First of all, I would like to thank #NickLarsen for making me believe that my understanding is good and i can achieve the task myself.
The problem was not that too tough but since i was new to Asp.net MVC, things were bit baffling. From the very start, I had the notion that i will be needing a ViewModel merging Category and Price classes and then a picture uploading API. But, somehow I wasn't able to fit the pieces in right place. Therefore after various regression and research over the internet, I achieved the task in following manner :-
First of all, I created a ViewModel
public class CatPicView
{
public Category Category { get; set; }
public Picture Picture { get; set; }
}
Second, I added the Uploadify javascript API
<script type="text/javascript">
$('#file_upload').uploadify({
'uploader': '#Url.Content("~/uploadify/uploadify.swf")',
'script': '#Url.Action("Upload", "Category")',
'cancelImg': '#Url.Content("~/uploadify/cancel.png")',
'buttonText': 'Upload',
'folder': '#Url.Content("~/content/images")',
'fileDesc': 'Image Files',
'fileExt': '*.jpg;*.jpeg;*.gif;*.png',
'auto': true,
'onComplete': function (event, ID, fileObj, response, data) {
var json = jQuery.parseJSON(response);
$("#pictureImage").html("<img src='"+json+"' alt='"+json+"' height='100px' width='100px'/>");
$("#Picture_PictureUrl").val(json);
$("#pictureRemove").show();
}
});
</script>
Hooked the API to following Server Function for renaming and saving to folder
[HttpPost]
public ActionResult Upload(HttpPostedFileBase fileData)
{
if (fileData != null && fileData.ContentLength > 0)
{
//var fileName = Server.MapPath("~/Content/Images/" + Path.GetFileName(fileData.FileName));
int pictureCount = 800000;
pictureCount += db.Pictures.Count();
string extension = Path.GetExtension(fileData.FileName);
string renamedImage = Server.MapPath("~/Content/Images/Categories/cat" + pictureCount + extension);
fileData.SaveAs(renamedImage);
return Json("/Content/Images/Categories/" + Path.GetFileName(renamedImage));
}
return Json(false);
}
And at last, rewrote the Category create Function as below for saving changes to DB
[HttpPost]
public ActionResult Create(CatPicView catPic)
{
if (ModelState.IsValid)
{
if (!String.IsNullOrEmpty(catPic.Picture.PictureUrl))
{
Picture picture = new Picture();
picture.PictureUrl = catPic.Picture.PictureUrl;
db.Pictures.Add(picture);
catPic.Category.PictureId = picture.Id;
}
db.Categories.Add(catPic.Category);
db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
I think MVC scaffolding feature see the relation of two models as "Many to Many". That's why it created two drop down list for you. According to your scenario, you could do "Category" create page without "Picture" model data because "Picture" is the main entity here. So In the picture create action.
[HttpPost]
public ActionResult Create(Picture picture)
{
if (ModelState.IsValid)
{
databaseContext.Pictures.Add(picture);
databaseContext.SaveChanges();
return RedirectToAction("Index");
}
return View(picture);
}
In the view page of create picture
#model YourProjectName.Models.Picture
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Picture</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Url)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Url)
#Html.ValidationMessageFor(model => model.Url)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Categories.CategoryID, "Category")
</div>
<div class="editor-field">
#Html.DropDownList("CategoryID", "Choose Category")
#Html.ValidationMessageFor(model => model.Categories.CategoryID)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

Resources