Am i implementing a radio button list in MVC correctly? - asp.net-mvc-3

I have an IList in my model. Which i am displaying as radio buttons.
But when i submit the form the value is not correct and the model state is not valid and where the value for the selected radio button should be there is 'Count =0'
This the option in model:
[Display(Name = "My enquiry is regarding: *")]
public IList<Industry> A1_EnquiryRegarding { get; set; }
controller:
populate list:
Industry blank = new Industry();
blank.Id = 0;
blank.Name = "Other";
IList<Industry> industryList = manager.GetIndustries();
industryList.Insert(industryList.Count, blank);
EnquiryModel.A1_EnquiryRegarding = industryList;
html:
<td>
<div class="editor-label">
<b> #Html.LabelFor(m => m.A1_EnquiryRegarding)</b>
</div>
<div class="editor-field">
#foreach (var radiobutton in Model.A1_EnquiryRegarding) {
#Html.RadioButtonFor(m => m.A1_EnquiryRegarding, radiobutton.Name)
<label>#radiobutton.Name</label>
<br></br>
}
#Html.ValidationMessageFor(m => m.A1_EnquiryRegarding)
</div>
</td>
where am i goign wrong? why am i not getting the correct selected value back?
Edit:
[HttpPost]
public ActionResult EnquiryForm(Enquiry Enquiry)
{

When you post back, your collection of complex object is not recreated. Instead, there is only one string value passed with the selected value of the radio. Your model for the update action should only include one name.
Implement your radiolist as follows:
#foreach (var radiobutton in Model.A1_EnquiryRegarding) {
#Html.RadioButton("selectedIndustry", radioButton.Name);
}
All your radio buttons should have the same name, but different values. That way, when you call your Post action, you just search for parameter "selectedIndustry".
[HttpPost]
public ActionResult MyPostAction(string selectedIndustry) {
}

Related

MVC 5 Conditional Validation Option?

I'm developing an MVC 5 web application. Within a particular View I need to validate a ViewModel, however, I need some of the validation only to occur depending on the users inpupt.
For example, I have a ViewModel
public class TimeEntryViewModel
{
public int proposalID { get; set; }
public int proposalCode { get; set; }
public int nonchargeCode { get; set; }
public SelectList UserProposals { get; set; }
public SelectList TimeEntryClientCodes { get; set; }
public SelectList TimeEntryNonChargeCodes { get; set; }
}
This ViewModel is passed to a View which looks like this
<div class="form-group">
#Html.LabelFor(m => m.proposalID, "Proposal")
#Html.DropDownListFor(m => m.proposalID, Model.UserProposals, "No Proposal", new { #class = "form-control"})
#Html.ValidationMessageFor(m => m.proposalID)
</div>
<div id="ClientCodes" class="form-group" style="display:none">
#Html.LabelFor(m => m.proposalCode, "Client")
#Html.DropDownListFor(m => m.proposalCode, Model.TimeEntryClientCodes, "Select", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.proposalCode)
</div>
<div id="NonChargeCodes" class="form-group">
#Html.LabelFor(m => m.nonchargeCode, "Non Charge")
#Html.DropDownListFor(m => m.nonchargeCode, Model.TimeEntryNonChargeCodes, "Select", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.nonchargeCode)
</div>
If the user selects 'No Proposal' from the first drop down list, then the drop down list 'nonchargeCode' appears and I need to validate so that the user selects an option from it.
However, if the user selects another option from the first down drop list, then the drop down list 'nonchargeCode' will disappear and another drop down called 'proposalCode' will appear. I then want to validate to ensure the user selects an option from this drop down, but not the 'nonchargeCode' (which will be hidden).
In an MVC 4 application I previously coded, I used http://fluentvalidation.codeplex.com/ to help with this scenario.
I'm just wondering if anyone else had used anything else to overcome this problem of conditional validation? If so, I'd be keen to hear.
Thanks again.
You can use conditional validation in jQuery and in fluentvalidation.
You can use a jQuery selector on the validation, something like this.
I'm not sure about the HTML element names.
$( "#myform" ).validate({ rules: {
proposalCode: {
required: "#proposalCode:visible"
} }
Check out jQuery Dependency expression for more information.
In FluentValidation validation (Server side only) you can use the 'When' expression.
RuleFor(r => r.proposalCode).NotNull().When(e => // Check selected value);
Check out the documentation here
I think this should get you started.

Displaying null IEnumerable with EditorTemplate

I have a complex object which has a IEnumerable property and that I want to display in my view.
For that purpose I've created an EditorTemplate for that list.
So, in my view I have the following code:
<div id="tabMed" class="tab-pane">
<fieldset>
<div>
#Html.EditorFor(m => m.MyList)
</div>
</fieldset>
</div>
m.List is an IEnumerable type and the Editor template has inside some fields for displaying the object.
This works fine when the list has 1 or more objects; it will display and repeat the template for every object in that list.
Now the problem:
Let's supose that I want to edit the object that contains that list and the IEnumerable property is null. The Editor template won't appear at all, leaving no possibility to create the first object in that list.
Is there any approach to show an empty template if the list is null without having to something like this?:
<div id="tabMed" class="tab-pane">
<fieldset>
<div>
#if (Model.MyList.Count() > 0)
{
#Html.EditorFor(m => m.MyList)
}
else
{
// empty object form template here...
}
</div>
</fieldset>
</div>
Edit: The EditorTemplate look like this:
#model Jazz.Models.MyList.MyObject
<div class="control-group">
#Html.LabelFor(model => Model.Name, new { #class="control-label"})
<div class="controls">
#Html.TextBoxFor(m => m.Name, new { #class="collection-item", autocomplete = "off", maxlength = 64 })
</div>
</div>
The MyList is the list that I want to display
The MyObject class is declared inside MyList
So, the model would be like this:
public class MyList
{
public MyList()
{
this.list = new List<MyObject>();
}
public int id{ get; set; }
public List<MyObject> list{ get; set; }
public class MyObject
{
public MyObject(){}
public string Name{ get; set; }
}
}
I've put the example in a generic way so it's simplier to show the problem.

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.

Problem with Edit View using ViewModel

I have a complex object that I want to use in an edit view. To simplify things I have created a ViewModel and have successfully created the edit view page, and everything renders correctly. When I hit save, everything falls apart.
The ViewModel is as follows:
public class ClosureEditViewModel
{
public Model.Closure Closure { get; set; }
public Model.School School { get; set; }
public Model.ClosureDetail CurrentDetails { get; set; }
}
Some of the View is as follows:
<div class="display-label">School</div>
<div class="display-field">
#Html.DisplayFor(model => model.Closure.School.Name)
</div>
<div class="display-label">Closed</div>
<div class="display-field">
#Html.DisplayFor(model => model.Closure.Logged)
</div>
....
<div class="editor-label">
#Html.LabelFor(model => model.CurrentDetails.DateOpening, "Date Opening (dd/mm/yyyy)")
</div>
<div class="editor-field">
#Html.TextBox("DateOpening", Model.CurrentDetails.DateOpening.ToString("dd/MM/yyyy"))
#Html.ValidationMessageFor(model => model.CurrentDetails.DateOpening)
</div>
....
<tr>
<td>
#Html.CheckBoxFor(model => model.CurrentDetails.Nursery, (Model.School.Nursery ? null : new { #disabled = "disabled" }))
</td>
The important parts of the controller are as follows:
public ActionResult Edit(int id)
{
Data.IClosureReasonRepository reasonRepository = new Data.SqlServer.Repositories.ClosureReasonRepository(UnitOfWork);
IEnumerable<Model.ClosureReason> reasons = reasonRepository.GetAll();
Model.Closure closure = ClosureRepository.GetClosure(id);
Model.ClosureDetail currentDetail = closure.ClosureDetails.Last();
ViewModels.ClosureEditViewModel editClosure = new ViewModels.ClosureEditViewModel() { Closure = closure, School = closure.School, CurrentDetails = closure.ClosureDetails.Last() };
ViewBag.ReasonId = new SelectList(reasons, "Id", "Name", currentDetail.ReasonId);
return View(editClosure);
}
[HttpPost]
public ActionResult Edit(ViewModels.ClosureEditViewModel newDetail)
{
//if (ModelState.IsValid)
//{
//}
Data.IClosureReasonRepository reasonRepository = new Data.SqlServer.Repositories.ClosureReasonRepository(UnitOfWork);
IEnumerable<Model.ClosureReason> reasons = reasonRepository.GetAll();
ViewBag.ReasonId = new SelectList(reasons, "Id", "Name", newDetail.CurrentDetails.ReasonId);
return View(newDetail);
}
When I hit save the following message appears:
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 94: </td>
Line 95: <td>
Line 96: #Html.CheckBoxFor(model => model.CurrentDetails.P1, (Model.School.P1 ? null : new { #disabled = "disabled" }))
Line 97: </td>
Line 98: <td>
I just can't figure out why it is having problems with the School property but neither of the other two.
James :-)
It seems that Model.School is null when you render the view once again in the POST action. Make sure that it isn't null because in your view you don't have a single input field bound to the School property => this property will be null inside your POST controller action.
[HttpPost]
public ActionResult Edit(ClosureEditViewModel viewModel)
{
... some operations
// Make sure that viewModel.School is not null
// Remember that the checkbox is bound to CurrentDetails.P1 so
// when you post to this action there is nothing that will initialize
// the School property => you should do whatever you did in your GET
// action in order to initialize this property before returning the view
return View(viewModel);
}

UpdateModel not updating the model via ViewModel and property from DropDownListFor

I am trying to set up an Edit view on which I have a text box and DropDownListFor. I have figured out a way to populate the DDLF, and the rendered and posted values are correct, but i cant seem to get the model to update properly.
The object i am trying to update is generated from LINQtoSQL, and in database it has foreign key column. In LINQtoSQL class that resulted in "Contains" relationship. I can get to ID property that represents the column in DB, and also the object that it represents.
zupanija = new Zupanija(); //object that needs to be updated
zupanija.Drzava; //object that i want to change to make the update
zupanija.DrzavaID; //Property linked to object that should change
Only way i have figured out to do the update is to get the value from DDLF and use it to get the object that i want to change like this:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var zupanija = repo.ZupanijaById(id);
var drzava = new repoDrzava().DrzavaById(Convert.ToInt32(collection["Zupanija.DrzavaID"]));
zupanija.Drzava = drzava;
}
Also when i try to update the ID field like this, then i get the folowing error:
zupanija.DrzavaID = Convert.ToInt32(collection["Zupanija.DrzavaID"]);
Error: throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
This seems to me that it is very lousy way to do this, and i am trying to get UpdateModel to work.
I have found the solution while looking for something else, in blog by Joe Stevens:
Using Controller UpdateModel when using ViewModel
The catch is in following: When view model is used then to correctly bind the properties it is necessary to "instruct" the UpdateModel helper how to find the actual class we wish to update.
My solution required to modify
UpdateModel(zupanija); to UpdateModel(zupanija,"Zupanija");
Because i was using a ViewModel class that contained couple properties along with the main data class i wanted to update.
Here is the code, i hope it helps to understand:
public class ZupanijaFVM
{
public IEnumerable<SelectListItem> Drzave { get; private set; }
public Zupanija Zupanija { get; private set; }
...
}
// From Controller
//
// GET: /Admin/Zupanije/Edit/5
public ActionResult Edit(int id)
{
var zupanija = repo.ZupanijaById(id);
return zupanija == null ? View("Error") : View(new ZupanijaFVM(repo.ZupanijaById(id)));
}
//
// POST: /Admin/Zupanije/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var zupanija = repo.ZupanijaById(id);
if (TryUpdateModel(zupanija, "Zupanija"))
{
repo.Save();
return RedirectToAction("Details", new { id = zupanija.ZupanijaID });
}
return View(new ZupanijaFVM(zupanija));
}
//From View:
#model VozniRed.Areas.Admin.Models.ZupanijeFVM
<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.ValidationSummary(true)
<fieldset>
<legend>Zupanija</legend>
#Html.HiddenFor(model => model.Zupanija.ZupanijaID)
<div class="editor-label">
#Html.LabelFor(model => model.Zupanija.Naziv)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Zupanija.Naziv)
#Html.ValidationMessageFor(model => model.Zupanija.Naziv)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Zupanija.Drzava)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Zupanija.DrzavaID, Model.Drzave)
#Html.ValidationMessageFor(model => model.Zupanija.DrzavaID)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
A dropdown list is represented by a <select> tag in an HTML form. A <select> contains a list of <option> tags each containing an ID and a text. When the user selects an option and submits the form the corresponding ID of this options is POSTed to the server. And only the ID. So all you can expect to get in your Edit POST action is the ID of the selected option. And all that UpdateModel does is use the request parameters that are sent and convert them to a strongly typed object. But because all that is a POSTed is a simple ID that's all you can get. From there on you have to query the datastore using this ID if you want to obtain the corresponding model. So you cannot get something that is not existing.

Resources