ModelState is coming up as invalid? - c#

I'm working on a MVC5 Code-First application.
On one Model's Edit() view I have included [Create] buttons to add new values to other models from within the Edit() view and then repopulate the new value within DropDownFors() on the Edit().
For this first attempt, I am passing a model_description via AJAX to my controller method createNewModel():
[HttpPost]
public JsonResult createNewModel(INV_Models model)
{
// model.model_description is passed in via AJAX -- Ex. 411
model.created_date = DateTime.Now;
model.created_by = System.Environment.UserName;
model.modified_date = DateTime.Now;
model.modified_by = System.Environment.UserName;
// Set ID
int lastModelId = db.INV_Models.Max(mdl => mdl.Id);
model.Id = lastModelId+1;
//if (ModelState.IsValid == false && model.Id > 0)
//{
// ModelState.Clear();
//}
// Attempt to RE-Validate [model], still comes back "Invalid"
TryValidateModel(model);
// Store all errors relating to the ModelState.
var allErrors = ModelState.Values.SelectMany(x => x.Errors);
// I set a watch on [allErrors] and by drilling down into
// [allErrors]=>[Results View]=>[0]=>[ErrorMessage] I get
// "The created_by filed is required", which I'm setting....?
try
{
if (ModelState.IsValid)
{
db.INV_Models.Add(model);
db.SaveChangesAsync();
}
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
return Json(
new { ID = model.Id, Text = model.model_description },
JsonRequestBehavior.AllowGet);
}
What I cannot figure out is why my ModelState is coming up as Invalid?
All properties are being specified before the ModelState check; the Model is defined as follows:
public class INV_Models
{
public int Id { get; set; }
[Required(ErrorMessage = "Please enter a Model Description.")]
public string model_description { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime created_date { get; set; }
[Required]
public string created_by { get; set; }
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime modified_date { get; set; }
public string modified_by { get; set; }
}
EDIT:
Added View code:
Input Form:
<span class="control-label col-md-2">Type:</span>
<div class="col-md-4">
#Html.DropDownListFor(model => model.Type_Id, (SelectList)ViewBag.Model_List, "<<< CREATE NEW >>>", htmlAttributes: new { #class = "form-control dropdown" })
#Html.ValidationMessageFor(model => model.Type_Id, "", new { #class = "text-danger" })
</div>
<div class="col-md-1">
<div class="btn-group">
<button type="button" class="btn btn-success" aria-expanded="false">CREATE NEW</button>
</div>
</div>
SCRIPT:
$('#submitNewModel').click(function () {
var form = $(this).closest('form');
var data = { model_description: document.getElementById('textNewModel').value };
$.ajax({
type: "POST",
dataType: "JSON",
url: '#Url.Action("createNewModel", "INV_Assets")',
data: data,
success: function (resp) {
alert("SUCCESS!");
$('#selectModel').append($('<option></option>').val(resp.ID).text(resp.Text));
alert("ID: " + resp.ID + " // New Model: " + resp.Text); // RETURNING 'undefined'...?
form[0].reset();
$('#createModelFormContainer').hide();
},
error: function () {
alert("ERROR!");
}
});
});

When you cannot quickly deduce why your ModelState validation fails, it's often helpful to quickly iterate over the errors.
foreach (ModelState state in ModelState.Values.Where(x => x.Errors.Count > 0)) { }
Alternatively you can pull out errors directly.
var allErrors = ModelState.Values.SelectMany(x => x.Errors);
Keep in mind that the ModelState is constructed BEFORE the body of your Action is executed. As a result, IsValid will already be set, regardless of how you set your model's properties once you are inside of the Controller Action.
If you want the flexibility to manually set properties and then re-evalute the validity of the object, you can manually rerun the validation inside of your Action after setting the properties. As noted in the comments, you should clear your ModelState before attempting to revalidate.
ModelState.Clear();
ValidateModel(model);
try
{
if (ModelState.IsValid)
{
db.INV_Models.Add(model);
db.SaveChangesAsync();
}
}
...
As an aside, if the model is still not valid ValidateModel(model) will throw an exception. If you'd like to prevent that, use TryValidateModel, which returns true/false instead:
protected internal bool TryValidateModel(Object model)

You should not be using a hack like ModelState.Clear() nor is TryValidateModel(model); required. Your issue stems from the fact you have a [Required] attribute on both your created_date and created_by properties but you don't post back a value, so they are null and validation fails. If you were to post back a more complex model, then you would use a view model which did not even have properties for created_date and created_by (its a Create method, so they should not be set until you post back).
In your case a view model is not necessary since your only posting back a single value ( for model-description) used to create a new INV_Models model.
Change the 2nd line in the script to
var data = { description: $('#textNewModel').val() };
Change your post method to
[HttpPost]
public JsonResult createNewModel(string description)
{
// Initialize a new model and set its properties
INV_Models model = new INV_Models()
{
model_description = description,
created_date = DateTime.Now,
created_by = System.Environment.UserName
};
// the model is valid (all 3 required properties have been set)
try
{
db.INV_Models.Add(model);
db.SaveChangesAsync();
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
return Json( new { ID = model.Id, Text = model.model_description }, JsonRequestBehavior.AllowGet);
}
Side notes:
I suggest modified_date be DateTime? (nullable in database
also). You are creating a new object, and are setting the
created_date and created_by properties, but setting
modified_date and modified_by properties does not seem
appropriate (it hasn't been modified yet).
I suspect you don't really want to set created_by to
System.Environment.UserName (it would be meaningless to have
every record set to administrator or whatever UserName of the
server returns. Instead you need to get users name from Identity
or Membership whatever authorization system you are using.

The model state is calculated when the binding from your post data to model is done.
The ModelState.IsValid property only tells you if there are some errors in ModelState.Errors.
When you set your created date you will need to remove the error related to it from ModelState.Errors

Related

MVC: DropdownListFor error "cannot convert lambda expression to type 'string' because it is not a delegate type"

I have the following problem.
I have a table which is called Users and I have another table called Department.
Now I want, that in the UI, when a user is going to create himself as user with the user form, that he get's on the department field a dropdown list all Department titles which are available from Department table. Reason for that: Department table will be used for other operations and should not be linked with the Userdata. So the Userdata should only contain the department name from the table, and that is enough.
My Controller looks like this:
public ActionResult UserCreate()
{
ViewBag.AppDataDepartment = new SelectList(database.department, "department_title", "department_title");
return View();
}
[HttpPost]
public ActionResult UserCreate(Users user)
{
if (user.UserID == "" || user.UserID == null)
{
ModelState.AddModelError(string.Empty, "UserID cannot be blank");
}
try
{
if (ModelState.IsValid)
{
List<string> results = database.Database.SqlQuery<String>(string.Format("SELECT UserID FROM USERS WHERE UserID = '{0}'", user.UID)).ToList();
bool _userExistsInTable = (results.Count > 0);
Users _user = null;
if (_userExistsInTable)
{
_user = database.Users.Where(p => p.UserID == user.UserID).FirstOrDefault();
if (_user != null)
{
if(_user.active == true)
{
ModelState.AddModelError(string.Empty, "USER already exists!");
}
else
{
database.Entry(_user).Entity.active = true;
database.Entry(_user).Entity.Last_Modified = System.DateTime.Now;
database.Entry(_user).State = EntityState.Modified;
database.SaveChanges();
return RedirectToAction("Index");
}
}
}
else
{
_user = new Users();
_user.UserID = user.UserID;
_user.lastname = user.lastname;
_user.firstname = user.firstname;
_user.mail = user.mail;
_user.department = user.department;
_user.user_image = user.user_image;
_user.image_path = user.image_path;
if (ModelState.IsValid)
{
_user.active = true;
_user.Last_Modified = System.DateTime.Now;
database.Users.Add(_user);
database.SaveChanges();
ViewBag.AppDataDepartment = new SelectList(database.department, "department_title", "department_title");
return RedirectToAction("Index");
}
}
}
}
catch (Exception ex)
{
//return base.ShowError(ex);
}
return View(user);
}
That is my HTML section:
<div class="row">
#Html.LabelFor(model => model.UserID, "UserID", new { #class = "col-sm-2 control-label" })
<div class="col-md-4">
#Html.TextBoxFor(model => model.UserID, new { #class = "col-md-4 control-label form-control", #id = "inputEmail3" })
</div>
#Html.LabelFor(model => model.department, "Department", new { #class = "col-sm-2 control-label" })
<div class="col-md-4">
#Html.DropDownList(model => model.department, (SelectList) ViewBag.AppDataDepartment, htmlAttributes: new { #class = "form-control" })
</div>
</div>
And Finally my department class from the table:
[Table("department")]
public partial class department
{
[Key]
public int departmentid { get; set; }
[Required]
public string department_title { get; set; }
public string subdepartment { get; set; }
}
But I can not compile, because I get the Error:
Error CS1660 Cannot convert lambda expression to type 'string' because
it is not a delegate type BTKPI
Why does this not work? How can I fix that?
I have already looked at this SolutionProposal, but this didn't helped, because the model, Linq and Data.Entity is already referenced.
It needs to be DropDownListFor(), not DropDownList()
#Html.DropDownListFor(m => m.department, (SelectList)ViewBag.AppDataDepartment, new { #class = "form-control" })
If you were to use DropDownList(), then it would be
#Html.DropDownList("department", (SelectList)ViewBag.AppDataDepartment, new { #class = "form-control" })
However there are numerous other issues with your code which you should address.
Your controller should be UserController and the method Create()
so that the url will be ../User/Create, not User/UserCreate
Your editing data, so you should be using a view model (do not use
data models when editing data) - refer What is ViewModel in
MVC?,
and the view model should contain a property
IEnumerable<SelectListItem> DepartmentList - a typical example is
shown in this
question/answer.
Your Users table should be storing the ID of the Department,
not its name, and there should be a FK relationship to the
Departments table.
Your view model property for UserID should have a [Required]
attribute and the view should include #Html.ValidationMesageFor(m
=> m.UserID) so that you get bothe client and server side validation (ditto for the DepartmentID property). You should also
consider a RemoteAttribute applied to the UserID property so
that you get client side validation - refer How to: Implement
Remote Validation in ASP.NET
MVC
And finally, almost nothing is you POST method is making much sense. You calling the database twice in order to check if the user already exists. You checking ModelState.IsValid multiple times. Your user_image and image_path properties suggest you need to upload an image, but nowhere do you save it. You assign ViewBag.AppDataDepartment and then immediately redirect to the Index() method. Your code should look something like (ignoring the file upload issue)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(UserVM model)
{
// Return early
if (!ModelState.IsValid)
{
// You will enter this automatically of UserID is null
model.DepartmentList = ... // assign the SelectList
return View(model);
}
// One database call to check if the user exists
User user = database.Users.Where(p => p.UserID == model.UserID).FirstOrDefault();
if (user != null && !user.active)
{
user.active = true;
// Save and redirect
}
else if (user != null)
{
ModelState.AddModelError(string.Empty, "USER already exists!");
model.DepartmentList = ... // assign the SelectList
return View(model);
}
user = new User
{
UserID = model.UserID,
lastname = model.lastname,
.... // set other properties of User
Last_Modified = System.DateTime.Now
}
// Save and redirect
}
Although its unclear why an existing user who has been archived would need to navigate to a Create() method (and be presented a form to fill out a whole lot of details they have already entered previously). You should have a separate method for activating previously archived users.

Change value of EditorFor on DropDownFor value change

I am trying to make an online bank website(for learning ASP.NET MVC).I have a class Account
class Account
{
int account_id;
String account_number;
decimal balance;
}
and I have a model for transaction.
public class MakeTransactionModel
{
[Required]
public String AccountFrom { get; set; }
[Required]
public String AccountTo { get; set; }
public Decimal OrignalBalance { get; set; }
[Required]
public Decimal Amount { get; set; }
[Required]
public String TransactionFor { get; set; }
}
Then in controller, i am putting accounts in ViewBag.
ViewBag.account_from = new SelectList(db.Accounts, "account_id", "account_number");
In View, I created a drop down for showing all accounts
#Html.DropDownListFor(u => u.AccountFrom, (SelectList)ViewBag.account_from, htmlAttributes: new { #class = "form-control", #id = "AccountFrom", onchange=#"
#Model.OrignalBalance = 1000; // I tried this but did not work
" })
Now , I am trying to show balance of selected account in an EditorFor
#Html.EditorFor(model => model.OrignalBalance, new { htmlAttributes = new { #id="OrignalBalance", #class = "form-control", disabled = "disabled", #readonly = "readonly" } })
I have all accountsin ViewBag and I am showing that accounts number in drop down (those accounts also have balance in it). I am trying to change value of EditorFor on DropDownFor value change but still unable to do that. I tried to do that using jquery, but i don't know can I use LINQ in jquery
My jquery code is
<script type="text/javascript">
$(document).ready(function () {
$(function () {
$('#AccountFrom').change(function () {
var selectedValue = $('#AccountFrom').text();
$('#OrignalBalance').val(#{new BankEntities().Accounts.SingleOrDefault(acc => acc.account_number == $('#AccountFrom').text())}); // I am trying to do this
});
});
}
)
</script>
It will be good if i find a good solution to do that, so I can update EditorFor on change event.
Thank you.
You should make an ajax call and pass the account number and get the amount from the server.
$(function()
{
$('#AccountFrom').change(function() {
var accountId= $('#AccountFrom').val();
var url="#Url.Action("Balance","Account")";
$.post(url+"?accountNumber="+accountId,function(response){
if(response.Status==="success")
{
$("#OrignalBalance").val(response.Balance);
}
else
{
alert("Invalid Account");
}
});
});
});
Assuming you have an action method to return the balance
[HttpPost]
public ActionResult Balance(string accountNumber)
{
//Of course you want to authorize the call
var db=new BankEntities();
var a= db.Accounts.FirstOrDefault(x=> x.account_number ==accountNumber);
if(a!=null)
{
return Json( new { Status="success", Balance=a.Balance });
}
else
{
return Json( new { Status="error"});
}
}
If you do not wish to make an action method and the ajax way, What you can do is, Create a dictionary of your account number and the balance and pass that as part of your view model and in your razor view, set that to a js object and in the change event you can query the js dictionary to get the value.
Also, I recommend to NOT use ViewBag to transfer data between your action method and your view for rendering the dropdown. You should add a strongly typed property to handle that.
So let's add some new properties to your view model.
public class MakeTransactionModel
{
// Your other existing properties here
public Dictionary<string,decimal> AccountBalances { set; get; }
// These 2 properties are for rendering the dropdown.
public int FromAccountId { set; get; }
public List<SelectListItem> FromAccounts { set; get; }
}
And in your GET action, fill this property with account number and corresponding balance value.
public ActionResult Transfer()
{
var vm = new MakeTransactionModel();
vm.AccountBalances = new Dictionary<string, decimal>();
// Hard coded for demo. You may read it from your db tables.
vm.AccountBalances.Add("CHECKING0001", 3450.50M);
vm.AccountBalances.Add("SAVINGS0001", 4450.50M);
//load the data for accounts.pls change to get from db
vm.FromAccounts = new List<SelectListItem>
{
new SelectListItem { Value="CHECKING0001", Text="Checking" },
new SelectListItem { Value="SAVINGS0001", Text="Saving" }
};
// to do : Load other properties also
return View(vm);
}
And in your razor view, serialize this property and set to a js object.
#model MakeTransactionModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(s=>s.FromAccountId,Model.FromAccounts,"Select")
#Html.EditorFor(model => model.OrignalBalance,
new { #id="OrignalBalance", #class = "form-control",
disabled = "disabled", #readonly = "readonly" } )
<input type="submit" />
}
#section Scripts
{
<script>
var balanceDict = #Html.Raw(Newtonsoft.Json.JsonConvert
.SerializeObject(Model.AccountBalances));
$(function () {
$('#FromAccountId').change(function() {
var accountId= $('#AccountFrom').val();
var v = balanceDict[accountId];
$("#OrignalBalance").val(v);
});
});
</script>
}
It may not seem like it, but this is pretty broad. Basic rundown, you'll either have to:
Serialize all accounts and balances into JSON and store them client-side:
This is more code than is appropriate here, but you could use JSON.net to get JSON for new BankEntities().Accounts.ToList() (something you should be getting from your controller code, by the way, not in your view), then set a window variable to that in your JavaScript, and call upon that whenever the value changes.
Untested, but something like:
var balances = #Html.Raw(JsonConvert.SerializeObject(new BankEntities()
.Accounts
// Filter by logged in user
.ToDictionary(c => c.account_number, c.balance));
$(document).ready(function () {
$(function () {
$('#AccountFrom').change(function () {
var selectedValue = $('#AccountFrom').text();
$('#OrignalBalance').val(balances[selectedValue]);
});
});
}
Introduce an API call performed through AJAX to get balances whenever the value changes.
Shyju beat me to this one, but it's probably a better way to do it, as long as you're comfortable introducing an API element. It's kind of advanced for first learning MVC, but it's not too complicated.
This is what I'd do in a production application (although I'd do it with Web API), but for just playing, the first option is a little quicker, and probably easier to understand and debug fully if you're just getting started.
The confusion here comes from where code is executed. Your script can't refer to the BankEntities because it's running client-side, as compared to server side.
JQuery knows nothing about LINQ, since it is client based. So, I suggest making an ajax request when the account from gets changed.
for example, in the view, make the ajax call
<script type="text/javascript">
$(document).ready(function() {
$('#AccountFrom').change(function() {
var selectedAccountNumber = $('#AccountFrom option:selected').text();
$.ajax({
url: "/Accounts/GetAccountBalance",
type: "POST",
dataType: "json",
data: { accountNumber: selectedAccountNumber },
success: function (
$('#OrignalBalance').val(data.Balance);
}
});
});
});
</script>
and have the following in the controller (let's say that you have a controller called Accounts)
public ActionResult GetAccountBalance(string accountNumber)
{
var account = db.Accounts.SingleOrDefault(a => a.account_number == accountNumber);
// add validation logic for account not exits
return Json(new { AccountNumber = accountNumber, Balance = account.balance });
}

Mvc3 and Jquery Multiselect, sending values to server not working?

I am trying to use Jquery Multiselect plugin from Beautiful site and MVC3 together to send values to server. As shown in example from Darin the key is to create MultiSelectModelBinder class that will, I guess, recognize values send from client, because the multiselect plugin uses the [] notation to send the selected values to the server. My aproach is a little diferent, i fill dropDownList from my controller and not the model, keeping the model clean, and also been able to fill the list from Database. I used Darins example to create MultiSelectModelBinder and register it,in the model binder in Application_Start(). My problem is that I always keep getting empty Model back to my controller, here is the code:
MODEL:
public class PersonsSearchModel
{
public string Person { get; set; }
public string Company { get; set; }
//here is my Cities collection
public IEnumerable<string> Cities { get; set; }
}
VIEW:
#model MyNamespace.Model.PersonsSearchModel
#using (Ajax.BeginForm("Search", "Persons", new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "results",
LoadingElementId = "progress"
},
new { #id = "searchFormPerson" }
))
{
<span>
#Html.TextBoxFor(x => Model.Person, new { #class = "halfWidth"})
</span>
<span>
#Html.TextBoxFor(x => Model.Company, new { #class = "halfWidth"})
</span>
<span>
#Html.ListBoxFor(x => x.Cities, Model.Items, new { #id="combobox1"})
</span>
<input name="Search" type="submit" class="searchSubmit" value="submit" />
}
CONTROLLER:
public ActionResult Index()
{
var listCities = new List<SelectListItem>();
listCities.Add(new SelectListItem() { Text = "Select one...", Value = "" });
listCities.Add(new SelectListItem() { Text = "New York", Value = "New York" });
listCities.Add(new SelectListItem() { Text = "Boston", Value = "Boston" });
listCities.Add(new SelectListItem() { Text = "Miami", Value = "Miami" });
listCities.Add(new SelectListItem() { Text = "London", Value = "London" });
ViewBag.Cities = listCities;
return View();
}
public class MultiSelectModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = (PersonsSearchModel)base.BindModel(controllerContext, bindingContext);
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[]");
if (value != null)
{
return value.RawValue;
}
return model;
}
}
Here the data from client sholud arive, butt is always null?
public PartialViewResult Search(PersonsSearchModel psm)
{
var person = psm.Person;
var company = psm.Company;
var city = psm.Cities.ElementAt(0);
return GetResultPartialView(city);
}
GLOBAL.asax.cs
protected void Application_Start()
{
//...
//model binder
ModelBinders.Binders.Add(typeof(IEnumerable<string>), new
FinessenceWeb.Controllers.PersonsController.MultiSelectModelBinder());
}
JQUERY
$("#combobox1").multiSelect();
I had the same issue, although your provided solution still works. There is another way of doing it with less effort.
Actually defaultModelbinder does bind to multiple selected values if you can change your input parameter to List<inputparameter> and change the line #Html.DropDownListFor to #Html.ListBoxFor.
The key difference between these 2 controls is, First one being a single selection box and second one being a multiple selector.
Hope this helps some one having the same issue.
Well... After looking into DOM, and Jquery plugin, turns out the plugin gives the select element, atribute name, current id, so they are the same, and the form, well.. look's at the name attr. So solution wolud be:
$("#Cities").multiSelect();
Cheers!

Client-side custom data annotation validation

I've create a custom data annotation to do some validation on my view model. The problem is that it doesn't validate on the client-side. Here's my model:
public class MemberViewModel
{
[ScaffoldColumn(false)]
public int MemberId { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
//My custom data annotation
[EnforceTrue(ErrorMessage = "You must agree to the Terms and Conditions")]
public bool AgreeTerms { get; set; }
}
My data annotation validation code:
public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
public EnforceTrueAttribute() { }
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule() { ValidationType = "enforcetrue", ErrorMessage = this.ErrorMessageString };
}
}
My controller method:
[HttpPost]
public ActionResult Index(MemberViewModel viewModel)
{
Member member = new Member();
TryUpdateModel(member);
if (ModelState.IsValid)
{
_membersRepository.SaveMember(member);
return RedirectToAction("Index", "Home");
}
return View(viewModel); // validation error, so redisplay same view
}
And my view:
#using (Html.BeginForm("Index", "Members", FormMethod.Post)) {
#Html.HiddenFor(m => m.MemberId)
<div class="editor-label">#Html.LabelFor(model => model.Name)</div>
<div class="editor-field">#Html.TextBoxFor(model => model.Name)</div>
<div class="editor-field">#Html.CheckBoxFor(model => model.AgreeTerms) <label for="AgreeTerms">I agree to the Terms and Conditions</label></div>
<p>
<input type="submit" value="Submit" />
</p>
#Html.ValidationSummary()
}
So all my other error messages get displayed in the validation summary with client-side validation. But for my custom data annotation, the error message doesn't show until the rest of the model is valid, and after you submit the form and page reloads, that's when the error is displayed in the summary.
Is there something else I need to do here to get it to show up in the summary with the other errors?
I'm using C# and ASP.NET MVC 3
Had same issue recently. You can write:
$.validator.addMethod('enforcetrue', function (value, element) {
return $(element).is(":checked");
});
$.validator.unobtrusive.adapters.add('enforcetrue', [], function (options) {
options.messages['enforcetrue'] = options.message;
options.rules['enforcetrue'] = options.params;
});
Similar question here ASP.NET MVC 3 client-side validation
Implementing Iclientvalidatable only adds unobtrusive attributes to generated html inputs. To enable validation on client side you must write validators that use these unobtrusive attributes to validate the inputs. Here you can find very good explanation of client and server validation in asp.net mvc 3
A Remote Validator is what you need here is the link
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1

Returning the View Model to a Controller from a Form.Submit client action

I have a controller with 2 Index methods:
public ActionResult Index()
{
viewModel.PipelineIndex pivm = new viewModel.PipelineIndex(null, User.Identity.Name);
return View(pivm);
}
[HttpPost]
public ActionResult Index(viewModel.PipelineIndex model, FormCollection collection)
{
viewModel.PipelineIndex pivm = null;
if (ModelState.IsValid)
{
string key = collection.AllKeys[0];
string ID = collection.Get(key).ToString();
pivm = new viewModel.PipelineIndex(ID, User.Identity.Name);
}
else
pivm = new viewModel.PipelineIndex(null, User.Identity.Name);
return View(pivm);
}
The ViewModel I am using is a well defined class:
public class PipelineIndex
{
private Models.Context _db = new Models.Context();
public List<SelectListItem> GroupList { get; set; }
public List<string> ButtonCaptions { get; set; }
public List<ContactDetail> ContactList { get; set; }
public string PageTitle { get; set; }
...
The View consumes the ViewModel setting up a Grid and a Drop Down control:
#model BlueSkies.Pipeline.ViewModels.PipelineIndex
#{ ViewBag.Title = "Index"; }
#using (Html.BeginForm())
{
<h2>#Model.PageTitle</h2>
<div style="clear:both">
#if (Model != null)
{
var grid = new WebGrid(canPage: true, rowsPerPage: 15, canSort: true, ajaxUpdateContainerId: "grid");
grid.Bind(Model.ContactList, rowCount: Model.ContactList.Count, autoSortAndPage: true);
grid.Pager(WebGridPagerModes.All);
#grid.GetHtml(htmlAttributes: new { id = "grid" },
columns: grid.Columns(
grid.Column(format: (item) => Html.ActionLink("View", "Details", "Contacts", new { ID = item.Name }, null)),
grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", "Contacts", new { ID = item.Name }, null)),
grid.Column("Name"),
grid.Column(columnName: "Phone1", header: "Phone")
));
}
</div>
<hr />
<div>
#*foreach (string caption in ViewBag.ButtonCaptions)
{
#Html.ActionLink(caption, "Index", "Pipeline", new { ID = caption }, new { #class = "menuSubButton" })
}*#
#Html.DropDownList("GroupDropDown", Model.GroupList, new { #onchange = "this.form.submit()" }) Select a pipe section...
</div>
}
Where I am having challenges is when the Drop Down fires the Form.Submit (on the onChange event). No model is being returned to my Controller. I do have the FormCollection but I would rather have the updated model including the new selectedItem in the drop down. What am I missing? And yes, I am looking for a non-JS based solution at this point - or as close as I can. I don't want to AJAX this page.
TIA
NOTE: There is a similar question here. It is AJAX based but getting the same null model on call into the controller. Why is it so hard to find the right answer? :)
I think the rendered HTML form will have a select with the name "GroupDropDown", is that right? If so, the selected value will be posted back on submit with that name and would be bound to either a parameter called groupDropDown or to a string property GroupDropDown on your model class. Do you have such a property on your model?
I am mentally exhausted, so I add this disclaimer. Thought Answering a question would get me away from my day.
The business problem appears to be "select one item from drop down and have contents rendered accordingly". If that is correct, there is no need to pass back the entire contents of the View Model; you only need to pass back the id, which is demoed numerous times on many sites.
Am I missing something?

Categories

Resources