ViewModel not binding when using in a new view - asp.net-mvc

I am able to pass a view model initially on a form on my index.cshtml page to an editor template page. On the index page I have a submit button that post the form results (radio button groups in the editor template) back to the controller and within the HttpPost method its passing this model to a partial view which is displayed in a modal popup. All this does is show the form elements that were selected but it disables the radio buttons to the user. From here the user can either go back (close the window) or confirm the form results. When the user clicks the confirm button it should pass the viewmodel back to the controller to another HttpPost method which will then process the form results and return the final confirmation view. But when I try to pass the viewmodel back to the controller from the modal popup it does not keep the binding. I tried making sure all were binded through Hidden inputs but I must be missing something somewhere. Maybe I am going about this the wrong way. I just need to basically keep the viewmodel binding from the initial post and be able to process that after the user confirms the selection from the modal popup. What would be the best way to accomplish this without having to put a session hack in there?
Index
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "ballotForm" }))
{
#Html.AntiForgeryToken()
#(Html.EditorFor(m => m.BallotViewModel, new ViewDataDictionary(ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = "BallotViewModel"
}
}))
<table class="col-sm-12">
<tr>
<td class="pull-right">
<button type="submit" class="btn btn-primary" data-target="#modal-container" data-toggle="modal">Vote Management Ballot</button>
</td>
</tr>
</table>
}
Controller - Initial Post to Modal Popup
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(HomeViewModel bModel)
{
if (ModelState.IsValid)
{
//set property to identity view
bModel.BallotViewModel[0].IsVoteConfirmationView = true;
return PartialView("ViewVoteConfirmation", bModel);
}
}
Controller - Post after Confirm submit from modal popup
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmVote(HomeViewModel cModel)
{
//Process form results here but model is null
//Go to Thank You View
return View();
}
ViewVoteConfirmation:
#model Ballot.WebUI.Models.HomeViewModel
<div class="row">
#(Html.EditorFor(m => m.BallotViewModel, new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "BallotViewModel" } }))
</div>
#using (Html.BeginForm("ConfirmVote", "Home", FormMethod.Post, new { id = "ballotConfirmVoteForm" }))
{
#Html.AntiForgeryToken()
<div class="row">
#Html.EditorFor(m => m.BallotViewModel[0].Proposals, "Proposals", new ViewDataDictionary(ViewData)
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = "Proposals"
}
})
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<button type="button" class="btn btn-default"
data-dismiss="modal">
Cancel
</button>
<button type="submit" id="approve-btn"
class="btn btn-danger">
Confirm
</button>
</div>
</div>
}
ProposalViewModel:
public class ProposalViewModel
{
public int ProposalItemID { get; set; }
public string ProposalItemTitle { get; set; }
public string Option0_Name { get; set; }
public string Option1_Name { get; set; }
public string Option2_Name { get; set; }
public string Option3_Name { get; set; }
public string PercOfShare { get { return "% of Share"; }}
public bool IsHeader { get; set; }
public int TagOrder { get; set; }
public int SelectedVoteOption { get; set; }
public bool IsVoteConfirmationView { get; set; }
public bool IsCumulative { get; set; }
public int SharePercentage { get; set; }
public List<VoteOptionViewModel> lVoteOptions { get; set; }
}
Proposals:
#model List<Ballot.WebUI.Models.ProposalViewModel>
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m[i].ProposalItemID)
#Html.HiddenFor(m => m[i].ProposalItemTitle)
#Html.HiddenFor(m => m[i].Option0_Name)
#Html.HiddenFor(m => m[i].Option1_Name)
#Html.HiddenFor(m => m[i].Option2_Name)
#Html.HiddenFor(m => m[i].Option3_Name)
#Html.HiddenFor(m => m[i].PercOfShare)
#Html.HiddenFor(m => m[i].IsHeader)
#Html.HiddenFor(m => m[i].TagOrder)
#Html.HiddenFor(m => m[i].SelectedVoteOption)
#Html.HiddenFor(m => m[i].IsVoteConfirmationView)
#Html.HiddenFor(m => m[i].IsCumulative)
#Html.HiddenFor(m => m[i].lVoteOptions)
#Html.HiddenFor(m => m[i].SharePercentage)
}
jquery script to change the value of the SharePercentage label
$(function () {
//When 'For' is Selected
$('[class$=PercOfShareFor]').on('click', function (e) {
if ($(this).is(':checked')) {
var forMatches1 = 0;
$('[class$=PercOfShareFor]').each(function (i, val) {
if ($(this).is(':checked')) {
//check how many 'For' Vote Options are selected
forMatches1++;
//select the Share Percentage value label in the same row, and change the class to ForSelected (used as selector)
$(this).closest('td').next('td').next('td').find('.SharePercentage')
.removeClass("SharePercentage")
.addClass("SharePercentageForSelected");
//if the Share Percentage class (used as selector) was previously WithholdSelected then change to ForSelected
$(this).closest('td').next('td').next('td').find('.SharePercentageWithholdSelected')
.removeClass("SharePercentageWithholdSelected")
.addClass("SharePercentageForSelected");
}
});
//divide total 'For' Selections by number of Director Proposals
var forPercent1 = 100 / forMatches1;
//format the percentage to display 2 decimal places if not a whole number
var forPercent2 = Math.round(forPercent1 * 100) / 100;
//Update 'For' Percentages
$('[class$=SharePercentageForSelected]').text(forPercent2);
}
});
//When 'Withhold' is Selected after initially selecting 'For'
$('[class$=PercOfShareWithhold]').on('click', function (e) {
if ($(this).is(':checked')) {
var forMatches = 0;
$('[class$=PercOfShareFor]').each(function (i, val) {
if ($(this).is(':checked')) {
//check how many 'For' Vote Options are still selected
forMatches++;
}
});
var withholdMatches = 0;
$('[class$=PercOfShareWithhold]').each(function (i, val) {
if ($(this).is(':checked')) {
//check how many 'Withhold' Vote Options are still selected
withholdMatches++;
//set the class to WithholdSelected
$(this).closest('td').next('td').find('.SharePercentageForSelected')
.removeClass("SharePercentageForSelected")
.addClass("SharePercentageWithholdSelected")
.text("0"); //Set 'Withhold' Percentage back to 0
}
});
//divide total 'For' Selections by number of Director Proposals
var forPercent1 = 100 / forMatches;
//format the percentage to display 2 decimal places if not a whole number
var forPercent2 = Math.round(forPercent1 * 100) / 100;
//Update 'For' Percentages
$('[class$=SharePercentageForSelected]').text(forPercent2);
}
});
});

You can't do what you are trying to through form Posts. The browser sees the return from the Post as a full HTML page (even if you are saying it is a Partial server side). You will need to use some form of javascript to accomplish it or you will need to make your confirmation page an actual page instead of a modal popup.
The basic premise is that you need to trap the submit (or the button click) via javascript then display the modal. You can fill the modal with the results of the first action but you will need to submit the form via ajax instead of a standard form post. Then based on their selection in the modal you can submit the form or not.
There are a variety of resources available already that might help you. Here is one that show how to display a confirmation for a delete action. You could then alter that javascript to load the result of your first action via ajax as in this (admittedly older) article about loading MVC partial views using AJAX or maybe this one about using jQuery dialog for CRUD operations.

The model binding fix was to add a hidden field to the EditorTemplate for BallotViewModel so that when a radio button is selected, not only will the label change values but the hidden field would change values as well.
Editor Template
#Html.HiddenFor(m => Model.Proposals[i].SharePercentage, new { #class = "hdnSharePercentage" })
#Html.LabelFor(m => Model.Proposals[i].lVoteOptions[j].SharePercentage, Model.Proposals[i].lVoteOptions[j].SharePercentage, new { #class = "SharePercentage" })
jQuery
$(this).closest('td').next('td').next('td').find('.hdnSharePercentageWithholdSelected')
.removeClass("hdnSharePercentageWithholdSelected")
.addClass("hdnSharePercentageForSelected");

Related

BindingNested List Property to Send to MVC Controller

I have a view where I need to be able to select an item from a dropdown list and add it cumulatively to a list to display. The only way I've been able to think to do this so far is to have a Property which binds to the Dropdownlist, send this back with the cumulative list and have the server add to the List and send it back for display. This works well for the first item because the list to be added to has a Count of 0. Any subsequent additions will cause the controller binding to wipe the List.
Here are the important parts of my code:
Model:
public class CaseAppealViewModel
{
//needs to accumulate records as I add them and retain between server calls
public List<CaseFile> SelectedCases
{
get { return _selectedCases; }
set { _selectedCases = value; }
}
private List<CaseFile> _selectedCases = new List<CaseFile>();
public Nullable<int> ChosenCase { get; set; }
}
View:
#model MySystem.Models.CaseAppealViewModel
#using (Html.BeginForm("AddCase", "AppealLogs", Model))
{
<div class="col-md-6">
#Html.DisplayName("Cases:")
#Html.DropDownListFor(model => model.ChosenCase, CaseAppealViewModel.CaseDropDownList, "", new {#class = "form-control"})
#Html.HiddenFor(model => model.SelectedCases)
#Html.HiddenFor(model => model.Appeals)
</div>
<div class="col-md-6">
<input type="submit" id="AddCase" value="Add"/>
</div>
}
Controller:
public ActionResult AddCase(CaseAppealViewModel cavm)
{
var tempCase = db.CaseFiles.Find(cavm.ChosenCase);
if (tempCase != null)
{
cavm.SelectedCases.Add(db.CaseFiles.Find(cavm.ChosenCase));
}
return View("Details", cavm);
}
Is there a way I can send the SelectedCases List back and forth from the view to the controller without the Count resetting to 0?

Updated partial view model values in contoller after form submit

In my sample MVC application I have a model
class SampleModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<Certification> Certifications { get; set; }
}
class Certification
{
public int Id { get; set; }
public string CertificationName { get; set; }
public int DurationInMonths { get; set; }
}
My View (I need the certification details to be shown in a partial view)
#model SampleApplication.Model.SampleModel
<!-- other code... -->
#using (Html.BeginForm("SaveValues","Sample", FormMethod.Post, new { id= "saveForm" }))
{
#Html.HiddenFor(m => m.Id, new { id = "hdnID" })
#Html.TextBoxFor(m => m.Name, new { id = "txtName" })
#{Html.RenderPartial("_CertDetails.cshtml", Model.Certifications);}
<input type="submit" id="btnSubmit" name="btnSubmit" value="Update" />
}
Partial View
#model List<SampleApplication.Model.Certification>
<!-- other code... -->
#if (#Model != null)
{
for (int i = 0; i < #Model.Count; i++)
{
#Html.HiddenFor(m => m[i].Id , new { id = "CId" + i.ToString() })
#Html.TextBoxFor(m => m[i].CertificationName,new{ id ="CName" + i.ToString() })
#Html.TextBoxFor(m => m[i].DurationInMonths,new{ id ="CDur" + i.ToString() })
}
}
Controller
[HttpPost]
public ActionResult SaveValues(SampleModel sm)
{
//Here i am not getting the updated Certification details (in sm)
}
How I get the updated values of partial view in my controller after the form post? I am able to get the updated certification values when I am not using partialview.
Is this the right way or should I follow some other methods?
If sm.Certifications is coming back null, that means that either nothing was posted for that, or the modelbinder was unable to attach the posted data properly.
In your partial, you're defining the fields properly with an indexer, but initially, Certifications is a null list, so this code is never actually be run. That means, elsewhere you have some JavaScript logic that is adding new Certification fields to the page, dynamically, and my guess is that the field names that JavaScript is generating do not follow the indexing convention that the modelbinder expects. All your fields should be in the format of:
ListProperty[index].PropertyName
So in your case, your JS should be generating names like:
Certifications[0].CertificationName
In order for the data to be bound properly.
Oh Nooo... It was my mistake :( . I gave Certification List as my partialview model
#model List<SampleApplication.Model.Certification>
But I should use the same model(Main page model) in the partial view also.
#model SampleApp.Models.SampleModel
In the partial view the coding will be like
#for (int i = 0; i < #Model.Certifications.Count; i++)
{
#Html.HiddenFor(m => m.Certifications[i].Id, new { id = "CId" + i.ToString() })
#Html.TextBoxFor(m => m.Certifications[i].CertificationName, new { id = "CName" + i.ToString() })
#Html.TextBoxFor(m => m.Certifications[i].DurationInMonths, new { id = "CDur" + i.ToString() })<br /><br />
}
Now i am getting the updated values in my controller.
Thanks #Chris Pratt for the hint.

edit form not working, data is being passed to server but not the httpPost method

I had asked question yesterday, which can be found here and
based upon the answer I got from asp.net/mvc forum which can be found here, I was told to clear my modelstate, as by default my form tends to hold its default value, and not the value I just tried to update. So, I added Modelstate.Clear(), which still doesn't work. Can anyone tell me if i'm using the ModelState.Clear() in a wrong place or if I have to change something?
So, here is the problem, I have a edit form which shows its current values in textboxes, when user clicks edit button. If a user wants to edit some current value which is shown in textbox he edits the value in text box and clicks the save changes button. What currently is happening is in my HttpPost method when i check the values that are being passed, I don't get the new value user just provided, rather I get the value that was shown as current value in form.
But when I check in the developer tools in chrome, it shows the new value user just provided as the value that is being passed to server.
Here is my view
#using BootstrapSupport
#model AdminPortal.Areas.Hardware.Models.EditModule
#{
ViewBag.Title = "Edit";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
<fieldset>
<legend>Module <small>Edit</small></legend>
#using (Html.BeginForm("Edit", "Module"))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(m=>m.Id)
for(var i = 0; i < Model.Properties.Count(); i++)
{
#Html.HiddenFor(model=>model.HiddenProperties[i].Name)
#Html.HiddenFor(model=>model.HiddenProperties[i].Value)
<label class="label">#Model.Properties[i].Name</label>
<div class="input-block-level">#Html.TextBoxFor(model => model.Properties[i].Value)</div>
}
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<p>
#Html.ActionLink("Back to List", "ModuleList")
</p>
Here is the get and post method in controller
[HttpGet]
public ActionResult Edit(long id)
{
var module = _repository.GetModuleProperties(id);
ModelState.Clear();
return View(module);
}
[HttpPost]
public ActionResult Edit(EditModule module)
{
ModelState.Clear();
if (ModelState.IsValid)
{
_repository.SaveModuleEdits(module);
Information("Module was successfully edited!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Edit was unsuccessful, if the problem persists please contact Merijn!");
return RedirectToAction("ModuleList", "Module", new { area = "Hardware" });
}
The problem is with your model:
public class EditModule
{
public long Id { get; set; }
public List<PropertyViewModel> Properties { get; set; }
public List<PropertyViewModel> HiddenProperties
{
get { return Properties; }
set { Properties = value; }
}
}
You're posting back both Properties and HiddenProperties, but only changing Properties. The modelbinder sets the new values in Properties and then sets the values for HiddenProperties which in turn sets Properties and you've just overwritten your changes.
I'm not sure what exactly you're trying to do with HiddenProperties, but it's completely broken as it's currently set up.
UPDATE: Suggested changes
Model
public class EditModule
{
public long Id { get; set; }
public List<PropertyViewModel> Properties { get; set; }
}
Removed HiddenProperties property
Controller Action
[HttpPost]
public ActionResult Edit(long id, EditModule module)
{
var originalModule = _repository.GetModuleProperties(id);
// do whatever comparisons you want here with originalModule.Properties / module.Properties
if (ModelState.IsValid)
{
_repository.SaveModuleEdits(module);
Information("Module was successfully edited!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Edit was unsuccessful, if the problem persists please contact Merijn!");
return RedirectToAction("ModuleList", "Module", new { area = "Hardware" });
}
Edit POST version takes the id just like the GET version. You use this id to lookup the original version of the module from the database and then you can compare original and posted versions of Properties.
View
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(m=>m.Id)
for(var i = 0; i < Model.Properties.Count(); i++)
{
<label class="label">#Model.Properties[i].Name</label>
<div class="input-block-level">#Html.TextBoxFor(model => model.Properties[i].Value)</div>
}
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
The Html.BeginForm() syntax tells Razor to just use the current page's URL as the action for the form. HiddenProperties form fields have been removed.

Retrieving values from partial view during post method

I have a view which contains a dropdown list and on dropdownlist item being selected I load a partial view. And when the form is submitted I want to be able to get both the values from main view and partial view during form submit.
Here is the main view
#model AdminPortal.Areas.Hardware.Models.CreateModule
#{
ViewBag.Title = "Create Module";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
#Html.ValidationSummary(true)
<fieldset class="form-horizontal">
<legend>Add a Module <small>Create</small></legend>
#using (Html.BeginForm("CreateModule", "Module", new{id="AddModuleForm"}))
{
#Html.ValidationSummary(true)
<div class ="controls">
<div class="input-block-level">#Html.TextBoxFor(model => model.ModuleId, new {#placeholder = "ModuleID"})</div>
<br/>
<div class ="input-block-level" id="selectedModuleTypeName">#Html.DropDownListFor(model => model.SelectedModuleTypeName, Model.TypeNames,"Select Moduletype", new{id = "ModuleList"})</div>
<br/>
<div id="partialDiv"></div>
</div>
<div class="form-actions" id="buttons">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<div>
#Html.ActionLink("Back to List", "ModuleList")
</div>
<script>
$("#buttons").hide();
$("#ModuleList").on("change", function() {
var modId = $(this).val();
$.get('#Url.Action("GetModulePropertyName", "Module")', { moduleTypeValue: modId }, function(result) {
$("#partialDiv").html(result);
});
//uncomment following section to check if the partial view is working properly
/*.done(function() { alert("done"); })
.fail(function() { alert("fail"); })
.always(function() { alert("completed"); });*/
});
$("#buttons").show();
</script>
and here is the partial view
#model IEnumerable<string>
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})</div>
<br/>
}
Here is my model
public class CreateModule
{
//Empty form to handle form serialization
public CreateModule()
{
}
[Required]
public string ModuleId { get; set; }
[DataType(DataType.DateTime)]
public DateTime DateEntered { get; set; }
[Required]
public string SelectedModuleTypeName { get; set; }
public IEnumerable<SelectListItem> TypeNames { get; set; }
public List<Property> Properties { get; set; }
}
public class Property
{
public string Name { get; set; }
public string Value { get; set; }
}
Here is the method that script in main view forwards to
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
var modulePropertyNames = _repository.GetModuleKindPropertyNames(moduleTypeValue);
return PartialView("GetModulePropertyName",modulePropertyNames);
}
and finally here is httppost method for the main view
[HttpPost]
public ActionResult CreateModule(CreateModule moduleV)
{
var module = new Module
{
ModuleTypeId = Convert.ToInt64(moduleV.SelectedModuleTypeName),
ModuleId = moduleV.ModuleId,
DateEntered = moduleV.DateEntered,
};
if (ModelState.IsValid)
{
_repository.AddModule(module);
Success("Module added successfully!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Something went wrong!");
return RedirectToAction("CreateModule", "Module", new { area = "Hardware" });
}
Current situation:
When the form is posted, the properties value of the model that is being passed via partial view is null. I get other values, like typename, Module ID.
What I'd want:
I also want to get the value of properties that is being passed via partial view.
You don't have any input field for the Properties property anywhere in your form. So it will always be null. That's normal.
Here's how you could proceed. Start by setting the correct navigational property so that the helper generates correct names of the corresponding input fields.
Also make sure that you are passing an IEnumerable<Property> model to the partial if you want to be able to get them back correctly:
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
IList<Property> model = ...
return PartialView("GetModulePropertyName", model.ToList());
}
and in your partial view use an editor template:
#model IList<Property>
#{
// This indicates the current navigational context to the helpers
ViewData.TemplateInfo.HtmlFieldPrefix = "Properties";
}
#Html.EditorForModel()
and the last step is to define a custom editor template for the Property class: ~/Views/Shared/EditorTemplates/Property.cshtml (note that the name and location of the template is important)
#model Property
<div class="input-block-level">
#Html.HiddenFor(m => m.Name)
#Html.TextBoxFor(m => m.Value, new { placeholder = Model.Name })
</div>
<br />
Try using the
List<Property>
as a model in your partial view and pass the CreateModule.Properties as model from your View
The problem is model binder can not figure out there
#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})
belongs to as the "names" is not a property on your model class. If you need to bind to the CreateModule.Properties you need to change the partial view to emit textboxes with aproprate names, like this one:
#model IEnumerable<string>
#
{
int i=0;
}
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBox("Properties[" + i + "].Value")</div>
<br/>
}

Validate JQuery UI modal form within another form in MVC 4

I have a form in MVC 4 which contains several fields and, depending on the value of a combo, I need to open a modal dialog form and load into that one 3 additional fields that will impact against the same entity that I'm creating/editing in the main form.
For this modal dialog I'm using the one from jQuery UI.
Now, what I need to do is to validate (Required) the fields within the modal dialog in order to allow the user to retain the entered values which will be submited later by the main form.
My problem is how to perform the validation of those 3 fields from within the modal form (because they wouldn't be able to submit the main form until dialog is closed).
Any hints or ideas?
Regards,
Cesar.
You could use AJAX to submit the form modal to the server. The modal form will have of course a separate view model associated with it. Let's exemplify:
Main view model:
public class MyViewModel
{
[DisplayName("select a value")]
public string SelectedValue { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
public string SomeOtherProperty { get; set; }
}
Modal dialog view model:
public class DialogViewModel
{
[Required]
public string Prop1 { get; set; }
[Required]
public string Prop2 { get; set; }
[Required]
public string Prop3 { get; set; }
}
Then you could have a controller containing 4 actions:
public class HomeController : Controller
{
// Renders the main form
public ActionResult Index()
{
var model = new MyViewModel
{
Values = new[]
{
new SelectListItem { Value = "1", Text = "item 1" },
new SelectListItem { Value = "2", Text = "item 2" },
new SelectListItem { Value = "3", Text = "item 3" },
}
};
return View(model);
}
// Processes the submission of the main form
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return Content(
string.Format(
"Thanks for filling out the form. You selected value: \"{0}\" and other property: \"{1}\"",
model.SelectedValue,
model.SomeOtherProperty
)
);
}
// Renders the partial view which will be shown in a modal
public ActionResult Modal(string selectedValue)
{
var model = new DialogViewModel
{
Prop1 = selectedValue
};
return PartialView(model);
}
// Processes the submission of the modal
[HttpPost]
public ActionResult Modal(DialogViewModel model)
{
if (ModelState.IsValid)
{
// validation of the modal view model succeeded =>
// we return a JSON result containing some precalculated value
return Json(new
{
value = string.Format("{0} - {1} - {2}", model.Prop1, model.Prop2, model.Prop3)
});
}
// Validation failed => we need to redisplay the modal form
// and give the user the possibility to fix his errors
return PartialView(model);
}
}
Next you could have a main view (~/Views/Home/Index.cshtml):
#model MyViewModel
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.SelectedValue)
#Html.DropDownListFor(x => x.SelectedValue, Model.Values, new { id = "ddl" })
</div>
<div>
#Html.LabelFor(x => x.SomeOtherProperty)
#Html.TextBoxFor(x => x.SomeOtherProperty, new { id = "otherProperty" })
#Html.ActionLink(
"click here to open a modal and help you fill the value",
"Modal",
"Home",
null,
new { id = "showModal" }
)
</div>
<button type="submit">OK</button>
}
<div id="modal"></div>
and a partial view to contain the modal form (~/Views/Home/Modal.cshtml):
#model DialogViewModel
#using (Ajax.BeginForm(new AjaxOptions { OnSuccess = "handleModalSubmit" }))
{
<div>
#Html.LabelFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop1)
#Html.ValidationMessageFor(x => x.Prop1)
</div>
<div>
#Html.LabelFor(x => x.Prop2)
#Html.EditorFor(x => x.Prop2)
#Html.ValidationMessageFor(x => x.Prop2)
</div>
<div>
#Html.LabelFor(x => x.Prop3)
#Html.EditorFor(x => x.Prop3)
#Html.ValidationMessageFor(x => x.Prop3)
</div>
<button type="submit">OK</button>
}
OK, now all that's left is write some javascript to make the whole thing alive. We start by making sure that we have included all the required scripts first:
<script src="#Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery-ui-1.8.20.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
and then write our own:
$(function () {
$('#showModal').click(function () {
$.ajax({
url: this.href,
type: 'GET',
cache: false,
data: { selectedValue: $('#ddl').val() },
success: function (result) {
$('#modal').html(result).dialog('open');
}
});
return false;
});
$('#modal').dialog({
autoOpen: false,
modal: true
});
});
function handleModalSubmit(result) {
if (result.value) {
// JSON returned => validation succeeded =>
// close the modal and update some property on the main form
$('#modal').dialog('close');
$('#otherProperty').val(result.value);
} else {
// validation failed => refresh the modal to display the errors
$('#modal').html(result);
}
}

Resources