entity framework telling the context an entity has been updated - asp.net-mvc-3

I have a scenario where I have an image property that is part of a product entity.
When allowing a user to Edit this product via MVC3 screen, the image property is displayed as follows:
<div class="editor-label">Image</div>
<div class="editor-field">
#if (Model.ProductItem.ImageData == null)
{
#:None
}
else
{
<img alt="Product Image" width="125" height="125"
src="#Url.Action("GetImage", "Product", new { Model.ProductItem.ProductId })" />
}
</div>
<div>Upload new image: <input type="file" name="Image" /></div>
To edit the current image the user essentially selects a new one via the upload. This means that the current ImageData property is null and the model state is invalid. The new image is past back in the post so I set this to the ImageData property and clear the model validation error.
I then save the 'changes' via the context.savechanges() method, however the context doesn't think there are any changes to this particular entity. To get round this I have done the following when on Edit:
if (context.Products.Local.Count() == 0)
{
Product procurr = context.Products
.Where(p => p.ProductId == product.ProductId)
.FirstOrDefault();
context.Entry(procurr).CurrentValues.SetValues(product);
}
Essentially I am forcing an update on the item in the list of products that I want to updqate (procurr is the item in the list and product is the new edited values I want to save)
My questions would be (A) Is this the best way to do this in terms of using the context, and (B) Is there a better way to do this in the UI ie someway of tying together the old and new image so as the model automatically picks up the changes?
Thanks

What you have done looks correct.
The basic idea is that when you get data from the server and return it to the browser, the EF context that you are using gets closed.
When the reply comes back from the browser you need to connect the data with the row that you are trying to update.
Reading the row based on the id, updating the relevant fields and then running save changes, is a good approach.

Related

How to get checked checkbox value from html page to spring mvc controller

im using spring mvc framework with thymeleaf template engine
the problem is , i have 1 page with multiple check box iterated sing thymeleaf th:each iterator.When i clicked multiple check boxes i want to pass check box values to the controller method..
html content
<table>
<tr th:each="q : ${questions}">
<h3 th:text="${q.questionPattern.questionPattern}"></h3>
<div>
<p >
<input type="checkbox" class="ads_Checkbox" th:text="${q.questionName}" th:value="${q.id}" name="id"/>
</p>
</div>
</tr>
</table>
*Controller*
#RequestMapping(value = Array("/saveAssessment"), params = Array({ "save" }))
def save(#RequestParam set: String, id:Long): String = {
var userAccount: UserAccount = secService.getLoggedUserAccount
println(userAccount)
var questionSetQuestion:QuestionSetQuestion=new QuestionSetQuestion
var questionSet: QuestionSet = new QuestionSet
questionSet.setUser(userAccount)
questionSet.setSetName(set)
questionSet.setCreatedDate(new java.sql.Date(new java.util.Date().getTime))
questionSetService.addQuestionSet(questionSet)
var list2: List[Question] = questionService.findAllQuestion
var limit=list2.size
var qustn:Question=null
var a = 1;
for( a <- 1 to limit ){
println( a );
qustn= questionService.findQuestionById(a)
questionSetQuestion.setQuestion(qustn)
questionSetQuestion.setQuestionSet(questionSet)
questionSetQuestion.setCreatedDate(new java.sql.Date(new java.util.Date().getTime))
questionSetQuestionService.addQuestionSetQuestion(questionSetQuestion) } "redirect:/teacher/Assessment.html" }
I think you pretty much have it. With a checkbox, you can only send one piece of information back with the form...that being the value. So if you are trying to determine which checkboxes are checked when the user clicks the submit button, then I would have the checkboxes all use one name...like "id" (exactly like you have). Value is the actual id of the question (again like you have). Once submitted, "id" will be a String array which includes all the values of the checkboxes that were checked.
So your controller method needs to take param called "ids" mapped to parameter "id" which is a string[]. Now for each id, you can call questionService.findQuestionById.
(I'm not a Groovy guru so no code example sry :)
I have used JSTL with JSP and thymeleaf was something new. I read the THYMELEAF documentation.
There is a section which explains multi valued check boxes.
<input type="checkbox"
class="ads_Checkbox"
th:text="${q.questionName}"
th:value="${q.id}" name="id"/>
In the above code we are not binding the value to the field of the command object. Instead try doing this
<input type="checkbox"
class="ads_Checkbox"
th:text="${q.questionName}"
th:field="*{selectedQuestions}"
th:value="${q.id}" />
here the selectedQuestions is an array object present in the spring command object.

load data into file upload MVC3

Html code:
#using (Html.BeginForm("Edit", "RoomInfo", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<input type="file" class="multi" />
<p>
<input type="submit" value="Update" />
</p>
}
Controller code;
public ActionResult Search(string id)
{
var arrayid = id.Split(',');
int roomid = int.Parse(arrayid[0]);
ViewBag.ImageID = new SelectList(db.RoomImgs.Where(p => p.RoomID == roomid), "ImageID", "FilePath");
return View(roominformation);
}
I'm trying to upload the image into server and insert the file path into database and I successful to insert in to server and database, but now I need to select out the file path and put into the file upload tag in Edit mode. I trying some method but still fail to set in. How can I do it?
If what you're trying to do is display the previously uploaded image and then allow users to modify the image.
You can't populate the file input like you would a text box. What most websites do is display the image above the input and then have the input box below if you want to change the image. If you want to remove the image then you could also have a checkbox for removing the image. Then on the server side you have to check if the checkbox is checked and clear the image or, if the file input is populated store the new image and update the database.

What's the proper way to add a form to the Index page that posts to the Create action, including validation?

I have an Index view in my application that shows a list of vendors. I also want to add a small form to add new items right on that page. The create form will post to the Create action. My model class contains a list of vendors, plus one property for a single vendor named NewVendor.
public IEnumerable<SoftwareVendor> Vendors { get; set; }
public SoftwareVendor NewVendor { get; set; }
The SoftwareVendor class has validation attributes. It's an Entity Framework class.
Making a form that posts to the Create action is easy:
#using (Html.BeginForm( "Create", "Vendor" )) {
#Html.ValidationSummary(true)
<fieldset>
<legend>New Software Vendor</legend>
<div class="editor-label">
#Html.LabelFor(model => model.NewVendor.Name)
</div>
<div class="editor-field">
#Html.EditorFor( model => model.NewVendor.Name )
#Html.ValidationMessageFor( model => model.NewVendor.Name )
</div>
<br />
<input type="submit" value="Create" />
</fieldset>
}
This posts just fine, and client-side validation also works. However, the default Create action takes an instance of SoftwareVendor and is looking for a key in the form collection called "Name". Instead, the above form posts "NewVendor.Name".
I can remedy this by specifying a template and field name in #Html.EditorFor.
#Html.EditorFor( model => model.NewVendor.Name, "string", "Name" )
Now the Create action is happy because the "Name" value is being received. However, the validation message is broken because it is still looking for a field named "NewVendor.Name", and there seems to be no way to override this.
<span class="field-validation-valid" data-valmsg-for="NewVendor.Name" data-valmsg-replace="true"></span>
Is there something simple I'm missing to make this work?
Here is a list of things I can do to solve this:
Have my Create action take an instance of my Index model instead of a SoftwareVendor. I still have a traditional Create view, though, and I don't want to do this.
Don't have my Create action take any parameters. Instead, manually look at the form keys and pull the name from either "Name" or "NewVendor.Name", whichever is there.
Have the Create action take both model classes and detect which one got populated properly. This is a lot like #2 but I'm checking properties for non-null values instead of checking the form collection.
Figure out how to make a model binder that will perform what #2 is doing. This seems overly complicated, and I'm going to have this problem in a number of pages, so I'm hoping for an easier way.
Use javascript to make the post instead of a form submit, so I can control the exact field names I'm posting. This works, but I'd prefer to leverage an HTML form, since that's what it's for.
Use the overload of EditorFor to specify the field name, and create the validation message manually.
Write my own extension method on HtmlHelper for a new ValidationMessageFor that can override the field name.
Of these options, #2 or #5 are the ones I think I'd choose from unless there's a better way.
Well, this worked:
#Html.EditorFor( model => model.NewVendor.Name, "string", "Name" )
#Html.ValidationMessage( "Name" )
Since my only real problem with my above code was a broken validation message, this seems to solve my problem. I'm still curious if there is a better solution overall.

jQuery unobtrusive validation to validate part of a form

We have a ASP.NET MVC 3 application that uses unobtrusive jQuery validation. The page allows to add children objects to the model in the same go. The <form> contains a grid for the children, and some input fields for adding new children.
Simplified example with Issue as the Model and Subtasks as the children:
Issue.cshtml -> Defines the form and includes fields for the issue as well as its subtasks.
#model Issue
#using (Html.BeginForm("Create", "Issues", FormMethod.Post, new { id = "mainForm" })
{
#Html.TextBoxFor(model => model.Summary)
#Html.Partial("SubtaskFields", new Subtask())
#Html.Partial("SubtasksGrid", model.Subtasks)
}
SubtaskFields.cshtml:
#model Subtask
#Html.TextBoxFor(model => model.Summary)
<button id="add">Add</button>
SubtasksGrid.cshtml:
#model IEnumerable<Subtask>
<table>
#foreach (var subtask in Model)
{
<tr>
<td>
#subtask.Name
<input type="hidden" name="Subtasks[#subtask.Index].Name" value="#subtask.Name"/>
</td>
</tr>
}
</table>
The point is, when submitting the form, only the properties of the issue (Issue.Name, e.g.), plus the hidden fields for the children (Subtask.Name, e.g.) should be validated and submitted.
We have some javascript code that hooks on the "add" button, and adds a new subtask based on the values in the SubtaskFields.cshtml partial view. That script validates the input fields first. In order for this to work, we use the TextBoxFor etc. html helpers for the SubtaskFields.cshtml, too, rendering a dummy/default Subtask object (new Subtask()). Our javascript the uses $("#mainForm").validate().element(...) to validate the SubtaskFields before adding a new subtask.
The big problem with this approach is that the jQuery unobtrusive validation framework automatically hooks on the submit button and validates all fields within the form before submitting the form. I.e., even the subtask fields are validated. This does not make any sense. Say that the subtask name is mandatory (which means the user can only click on "add" if he has filled in a subtask name). But if the user does not click on "add", the values in the Subtask Fields don't have any meaning and can in particular be left blank. In this case, in our current setting, jQuery validation fails because a mandatory field was left blank.
How can this be solved?
This is what we've come up with:
Add an attribute to all subtask fields (which should not be validated when submitting the form), e.g. "data-val-ignore".
Set the ignore setting on the form's validator to "[data-val-ignore]"
For the add button, in order to validate the subtask fields (which are normally ignored), iterate over them, and for each field, remove the attribute, re-parse to genereate the rules, execute validation, add the attribute, parse one more time.
Ad 2:
$(document).ready(function () {
$.data($('form')[0], 'validator').settings.ignore = "[data-val-ignore]";
});
Ad 3:
$(allSubtaskFields).each(function() {
$(this).removeAttr("data-val-ignore");
$.validator.unobtrusive.parseElement(this, false);
if (!$("mainForm").validate().element($(this))) { result = false; }
$(this).attr("data-val-ignore", "true");
$.validator.unobtrusive.parseElement(this, false);
});
I would suggest moving #Html.Partial("SubtasksGrid", model.Subtasks) outside of your form, and either having it in a single separate form, or have the partial generate a form for each grid row.
This will address your validation problems with your main form, and should also permit you to simplify validation of each row in SubTasksGrid.
To validate part of the form, wrap the section or the controls you want to validate into a div with an #id or .class and do the following:
var validator = $("#myForm").validate();
var isValid = true;
$("myDivToBeValidated").find("*[data-val]").each(function (indx, elem) {
if (!validator.element(elem)) {
isValid = false;
}
});
//this part of form is valid however there might be some other invalid parts
if (isValid)
//do your action, like go to next step in a wizard or any other action
goToNextStep();
I hope it is clear, if not please leave a comment. For more info about jQuery validation plugin and element() function, check this
Looks like you are working against the MVC egine here.
I would use Editor templates and Display templates, EditorFor template for the stuff you wanna validate and post, and Display template for the stuff you dont wanna post and validate.. If you have a TextBoxFor in the display template make sure its binding property has no Required attribute, and if its a value type make it nullable.

ASP.NET MVC 3 - Validation Question

Good evening everyone I have a question regarding validation of drop-down list values. I have a view that is bound to a view model type called ReservationData.
This object contains a property CustomerVehicles of type List<VehicleData>. VehicleData has two int properties VehicleMakeId and VehicleModelId.
On my view I am trying to loop over the number of items in the CustomerVehicles collection and displaying two dropdowns for each, a vehicle make dropdown and a vehicle model dropdown using DropDownListFor.
When I try to submit and validate I do not see any validation errors displayed on the screen.
Just in case you are wondering I have added a ValidationMessageFor for each dropdown as well. I am not sure if this is an issue with the structure of my view model and its complexity and how the controls need to be named or how the ids need to be set. Any help would be greatly appreciated.
Here is the code for the looping over the collection:
#for (var i = 0; i < Model.CustomerVehicles.Count(); i++)
{
var vehicleNumber = i + 1;
<div class="vehicle-selection-wrapper">
<div class="content-container">
<h3>
Vehicle #vehicleNumber</h3>
<img class="vehicle-image" alt="manufacturer image" src="#Url.Content("~/Content/images/default-vehicle.gif")" /><br />
#Html.LabelFor(m => m.CustomerVehicles[i].VehicleMakeId)
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleMakeId
, new SelectList(Model.VehicleMakes, "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption, new { #class = "long-field" })<br />
#Html.ValidationMessageFor(m => m.CustomerVehicles[i].VehicleMakeId)<br />
#Html.LabelFor(m => m.CustomerVehicles[i].VehicleModelId)
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleModelId
, new SelectList(new List<CWR.Domain.VehicleModel>(), "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption, new { #class = "long-field" })
#Html.ValidationMessageFor(m => m.CustomerVehicles[i].VehicleModelId)
</div>
</div>
}
Ok so I also noticed that in the generated HTML the selects that are generated are missing the HTML5 data-val attributes that are associated to elements to handle validation. Here is the generated HTML
<select class="long-field" id="CustomerVehicles_0__VehicleMakeId" name="CustomerVehicles[0].VehicleMakeId"><option value="">-- Select --</option>
</select><br />
<span class="field-validation-valid" data-valmsg- for="CustomerVehicles[0].VehicleMakeId" data-valmsg-replace="true"></span><br />
<label for="CustomerVehicles_0__VehicleModelId">Model</label>
<select class="long-field" id="CustomerVehicles_0__VehicleModelId" name="CustomerVehicles[0].VehicleModelId"><option value="">-- Select --</option>
</select>
<span class="field-validation-valid" data-valmsg-for="CustomerVehicles[0].VehicleModelId" data-valmsg-replace="true"></span>
Additionally in my VehicleData class the VehicleMakeId and VehicleModelId properties are decorated with a Required attribute.
UPDATE:
Ok so I was testing and noticed that if I keep my code identical except I swap the Html.DropdownListFor calls with Html.TextboxFor calls then the validation works. What could be causing this? Could it be a framework bug with the unobtrusive validation?
UPDATE: Contains Fix
So after posting this same question on the ASP.NET Forums, I was able to get a solution. In the post you will be able to see that there is a bug in the unobtrusive validation framework and how it handles validation of dropdownlists. The user counsellorben does a good job in explaining the problem as well as a solution (including sample code) that will assist others in avoiding this issue in the future, or at least until Microsoft builds in a fix in to the framework.
Thank you everyone for your assistance.
I too have come across this obviously massive oversight regarding client side validation with dropdownlists in MVC 3 and the best solution I can offer is to put the missing HMTL attributes in yourself.
In your view model create a property like this.
public Dictionary<string, object> CustomerVechicleAttributes
{
get
{
Dictionary<string, object> d = new Dictionary<string, object>();
d.Add("data-val", "true");
d.Add("data-val-required", "Please select a Vechicle.");
return d;
}
}
Then in your code, enter
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleMakeId
, new SelectList(Model.VehicleMakes, "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption,
**Model.CustomerVechicleAttributes** })
Just add the Model.CustomerVechicleAttributes as htmlAttributes to your dropdownlist.
This will inject the necessary attributes that are missing. You will of course need to add any other attributes you may need like your class attribute.
Hope this helps.
This is the simpliest way I found to do it, just adding data-val-*-* attributes in HtmlAttributes of DropDownListFor, inside the view. The following method works with RemoteValidation too, if you do not need remote validation, simply remove the elements containing data-val-remote-*:
#Html.DropDownListFor(m => m.yourlistID, (IEnumerable<SelectListItem>)ViewBag.YourListID, String.Empty,
new Dictionary<string, object>() { { "data-val", "true" },
{ "data-val-remote-url", "/Validation/yourremoteval" },
{ "data-val-remote-type", "POST" }, { "data-val-remote-additionalfield", "youradditionalfieldtovalidate" } })
I hope it may help. Best Regards!
you should try to add data annotations on your view model properties first so you could see the validation messages.
you might find what you need here
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx
or create custom ones if needed.
what exactly do you need to validate?
I had exactly the same problem with the field getting correctly validated in TextBoxFor but not in DropDownListFor.
#Html.DropDownListFor(m => m.PaymentTO.CreditCardType, Model.CreditCardTypeList, "Select Card Type", new { style = "width:150px;" })
Since I had another DropDownListFor working on the same page, I knew that it wasn’t a generic DropDownListFor problem. I also have a complex model and parent object PaymentTO wasn’t initialized. When I set viewTO.PaymentTO = new PaymentTO(); in the Controller, the validation for the DropDownListFor started to work. So there is probably a problem with DropDownListFor, but the fix can be as simple as initializing the object in the controller.

Resources