Validate Partial View in mvc 4 - c#

I have been trying using Data annonations to validate form on my partial view which i am showing on bootstrap modal dialog. Following is code
Partial View
#model JoyRydeStoreWebPortal.Models.tbl_user
<div class="modal fade" id="createLoginModal" aria-hidden="false" role="dialog" tabindex="-1">
<div class="modal-dialog">
<form class="modal-content" method="post" action="#Url.Action("Index","Index", new{ Area = "Admin" })">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="createLoginTitle">Create Login</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-lg-4 form-group">
#Html.TextBoxFor(modal => modal.TXT_USER_ID, new { placeholder = "UserID", id="userID" , #class="form-control"})
</div>
<div class="col-lg-4 form-group">
#Html.TextBoxFor(modal => modal.TXT_USER_PWD, new {type="Password", placeholder = "Password", id = "userPwd", #class = "form-control" })
</div>
<div class="col-sm-12 pull-right">
<button class="btn btn-primary btn-outline" type="submit">Create</button>
</div>
</div>
</div>
</form>
</div>
Index View where i am rendering partial view
#model List<JoyRydeStoreWebPortal.Models.tbl_store>
#using GridMvc.Html;
#{
ViewBag.Title = "Index";
Layout = "~/Areas/Admin/Views/Shared/_layoutAdmin.cshtml";
}
<div class="page">
<div class="page-content padding-30 container-fluid">
#Html.Grid(Model).Columns(columns =>
{
columns.Add(foo => foo.LNG_STORE_ID,true).Sortable(true);
columns.Add(foo => foo.TXT_STORE_NAME).Titled("Store Name").SetWidth(110).Sortable(true).Filterable(true);
columns.Add(foo => foo.TXT_STORE_CONTACT).Titled("Phone Number").SetWidth(110).Sortable(true).Filterable(true);
columns.Add() .Encoded(false).Sanitized(false).SetWidth(30).RenderValueAs(o => #Create Login);
columns.Add().Encoded(false).Sanitized(false).SetWidth(30).RenderValueAs(o => #Detail);
}).WithPaging(20)
</div>
<div>
#{Html.RenderPartial("Partial_CreateLogin", new JoyRydeStoreWebPortal.Models.tbl_user());}
</div>
Following is my Modal Class for TBL_USER create by Entity Framework
namespace JoyRydeStoreWebPortal.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(tbl_userMetaData))]
public partial class tbl_user
{
public long LNG_USER_ID { get; set; }
public string TXT_USER_ID { get; set; }
public string TXT_USER_PWD { get; set; }
public long LNG_STORE_ID { get; set; }
public virtual tbl_store tbl_store { get; set; }
}
}
tbl_userMetaData class
public class tbl_userMetaData
{
[Required(ErrorMessage = "User ID is required")]
public string TXT_USER_ID { get; set; }
[Required(ErrorMessage = "User Password is required")]
public string TXT_USER_PWD { get; set; }
}
Index Controller
[HttpGet]
public ActionResult Index()
{
using (joyryde_storeEntities context = new joyryde_storeEntities())
{
var items = context.tbl_store.ToList();
return View(items);
}
}
[HttpPost]
public ActionResult Index(FormCollection frm)
{
if (ModelState.IsValid)
{
RedirectToAction("Index", new { Area = "Admin" });
}
return View();
}
Here ModalState.IsValid always return true. and modal dialog does not validate.
What causing the behaviour ?

Do not use FormCollection on your post
try this:
[HttpPost]
public ActionResult Index([Bind(Include="ITXT_USER_ID ,TXT_USER_PWD ")]tbl_userMetaData userMetaData)
{
if (ModelState.IsValid)
{
RedirectToAction("Index", new { Area = "Admin" });
}
return View();
}
It is good practice to specify the properties to be binded.

Related

Unable to send Model Data from Controller's GET Method to Controller's POST Method via View

I am sending the list of affiliates from the get method of controller to View via ViewBag(ViewBag.AffiliateData) and expecting to get the same list in controller's HTTP Post Method(myAffiliateList) but receiving value as null. Could you please help me to understand what am I doing wrong here?
NOTE: I am sending the data as hidden because it is not needed to be shown. So I am trying to get 2 values in HTTP-Post method, one
which is being selected and other the whole list that is being used to
populate the select box dynamically.
Model:
public class ModelOld
{
public string Aname { get; set; }
public string Acode { get; set; }
}
public class ModelNew
{
public string Acode { get; set; }
public IEnumerable<ModelOld> myAffiliateList { get; set; }
}
Controller: UserInvitation.cs
// GET Method
public ActionResult Import()
{
List<ModelOld> affiliateList = new List<ModelOld>();
ModelNew affiliateList1 = new ModelNew();
var affiliateMappingList = Configuration.GetSection("AffiliateMapping").GetChildren();
foreach (var KeyValuePair in affiliateMappingList)
{
affiliateList.Add(new ModelOld()
{
Aname = KeyValuePair.Key,
Acode = KeyValuePair.Value
});
}
affiliateList.Insert(0, new ModelOld { Acode = "", Aname = "--Select Your Affiliate--" });
affiliateList1.myAffiliateList = affiliateList;
ViewBag.AffiliateData = affiliateList1.myAffiliateList;
return View();
}
[HttpPost]
public async Task<ActionResult> ImportAsync(string Acode, IEnumerable<ModelOld> myAffiliateList)
{
//Some Code
}
View: Import.cshtml
#model ModelNew
<!DOCTYPE html>
<html>
<body>
#using (Html.BeginForm("Import", "UserInvitation", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="row">
<div class="form-group">
<div class="col-md-offset-3 col-md-10">
<label class="col-md-offset-3 col-md-2" title="Select Your Affiliate" style="font-size:large;"><b>Affiliate:</b></label>
<select id="affiliate" class="form-control" style="-webkit-appearance:listbox" asp-for="Acode" asp-items="#(new SelectList(ViewBag.AffiliateData,"Acode","Aname"))">
</select>
#Html.HiddenFor(m => m.myAffiliateList, htmlAttributes: new { #Value = ViewBag.AffiliateData })
</div>
</div>
</div>
<br />
<div class="row">
<div class="form-group">
<div class="col-md-offset-3 col-md-10">
<br />
<button type="submit" id="btnSubmitData" title="Click to Invite the Users" class="btn btn-info">
<i class="glyphicon glyphicon-upload"></i> Invite Users
</button>
</div>
</div>
</div>
}
</body>
</html>
Here is a whole working demo:
Model
public class ModelOld
{
public string Aname { get; set; }
public string Acode { get; set; }
}
public class AffiliateModel
{
public string Aname { get; set; }
public string Acode { get; set; }
}
public class ModelNew
{
public string Acode { get; set; }
public IEnumerable<ModelOld> myAffiliateList { get; set; }
}
View
#model ModelNew
#{
var data = TempData["AffiliateData"] as string;
TempData.Keep("AffiliateData"); //be sure add this to persist the data....
var list = System.Text.Json.JsonSerializer.Deserialize<IEnumerable<ModelOld>>(data);
}
<!DOCTYPE html>
<html>
<body>
#using (Html.BeginForm("Import", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="row">
<div class="form-group">
<div class="col-md-offset-3 col-md-10">
<label class="col-md-offset-3 col-md-2" title="Select Your Affiliate" style="font-size:large;"><b>Affiliate:</b></label>
<select id="affiliate" class="form-control" style="-webkit-appearance:listbox" asp-for="Acode"
asp-items="#(new SelectList(list,"Acode","Aname"))"> #*change here....*#
</select>
</div>
</div>
</div>
<br />
<div class="row">
<div class="form-group">
<div class="col-md-offset-3 col-md-10">
<br />
<button type="submit" id="btnSubmitData" title="Click to Invite the Users" class="btn btn-info">
<i class="glyphicon glyphicon-upload"></i> Invite Users
</button>
</div>
</div>
</div>
}
</body>
</html>
Controller
[HttpGet]
public async Task<IActionResult> Index()
{
List<ModelOld> affiliateList = new List<ModelOld>();
ModelNew affiliateList1 = new ModelNew();
var affiliateMappingList = Configuration.GetSection("AffiliateMapping").GetChildren();
foreach (var KeyValuePair in affiliateMappingList)
{
affiliateList.Add(new ModelOld()
{
Aname = KeyValuePair.Key,
Acode = KeyValuePair.Value
});
}
affiliateList.Insert(0, new ModelOld { Acode = "", Aname = "--Select Your Affiliate--" });
affiliateList1.myAffiliateList = affiliateList;
//change here....
//or asp.net core 2.x,you could use NewtonSoft.Json -----JsonConvert.SerializeObject(affiliateList1.myAffiliateList);
TempData["AffiliateData"] = System.Text.Json.JsonSerializer.Serialize(affiliateList1.myAffiliateList);
return View();
}
[HttpPost]
public async Task<ActionResult> ImportAsync(string Acode)
{
var data = TempData["AffiliateData"] as string;
IEnumerable<AffiliateModel> myAffiliateList = System.Text.Json.JsonSerializer.Deserialize<IEnumerable<AffiliateModel>>(data);
return View();
}

NullReferenceException: Object reference not set to an instance of an object when trying to create a cascading dropdown list in ASP.NET Core MVC [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 3 years ago.
I am trying to create a view model that allows the user to create a new product by typing in the products new name and then select which parent category it belongs to, which then filters the sub category and the user selects the appropriate sub-category. I am currently just trying to have both a drop down list of all parent categories and a drop down list of all subcategories, but I am getting a null refernce for the parent category in the view.
Models
public class Product
{
[Key]
public int ProductID { get; set; }
[Required]
[Display(Name = "Item Name")]
public string ProductName { get; set; }
public int ProductSubcategoryID { get; set; }
[Required]
public ProductSubcategory ProductSubcategory { get; set; }
}
public class ProductSubcategory
{
[Key]
public int ProductSubcategoryID { get; set; }
[Required]
public int ParentCategoryID { get; set; }
public ParentCategory ParentCategory { get; set; }
[Required]
[Display(Name = "Type")]
public string ProductSubcategoryDescription { get; set; }
}
public class ParentCategory
{
[Key]
public int ParentCategoryID { get; set; }
[Required]
public string ParentCategoryDescription { get; set; }
public IEnumerable<ProductSubcategory> ProductSubcategories { get; set; }
}
View Model
public class CreateProductViewModel
{
public IEnumerable<ParentCategory> ParentCategories{ get; set; }
public IEnumerable<ProductSubcategory> ProductSubcategories { get; set; }
public Product Product { get; set; }
}
Controllers
// GET: Products/Create
public IActionResult Create()
{
var parentCategories = _context.ParentCategories.ToList();
var productSubcategories = _context.ProductSubcategories.ToList();
var viewModel = new CreateProductViewModel
{
ParentCategories = parentCategories,
ProductSubcategories = productSubcategories
};
return View(viewModel);
}
// POST: Products/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ProductID,ProductName,VendorID,ProductSubcategoryID,ParentCategoryID,LocationID,QuantityPerUnit,UnitsInStock,UnitsInOrder,ReorderLevel,ProductComment,ProductMedia")]Product product)
{
_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
view
#model PrototypeWithAuth.ViewModels.CreateProductViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/View.cshtml";
}
<h1>Create</h1>
<h4>Product</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.ProductName" class="control-label"></label>
<input asp-for="Product.ProductName" class="form-control" />
<span asp-validation-for="Product.ProductName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubcategoryID" class="control-label"></label>
<!--<input asp-for="Product.ProductSubcategoryID" class="form-control" />-->
#Html.DropDownListFor(s => s.Product.ProductSubcategoryID,
new SelectList(Model.ProductSubcategories, "ProductSubcategoryID", "ProductSubcategoryDescription"),
"Select Subcategory", new { #class = "form-control" })
<span asp-validation-for="Product.ProductSubcategoryID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubcategory.ParentCategoryID" class="control-label"></label>
<!--<input asp-for="ProductSubcategoryID" class="form-control" />-->
#Html.DropDownListFor(c => c.Product.ProductSubcategory.ParentCategoryID,
new SelectList(Model.ParentCategories, "ParentCategoryID", "ProductSubcategoryDescription"),
"Select Category", new { #class = "form-control" })
<span asp-validation-for="Product.ProductSubcategory.ParentCategoryID" class="text-danger"></span>
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
When trying to add a new product I get a null reference for ParentCategory, but if I do not us a html helper tag for a dropdown than the form works. Thank you in advance
The SelectList is used like below
public SelectList(IEnumerable items, string dataValueField, string dataTextField)
In you case,ProductSubcategoryDescription is not the field of ParentCategory leading to the error.So change it to
#Html.DropDownListFor(c =>
c.Product.ProductSubcategory.ParentCategoryID,
new SelectList(Model.ParentCategories, "ParentCategoryID", "ParentCategoryDescription"),
"Select Category", new { #class = "form-control" })
create a new product by typing in the products new name and then select which parent category it belongs to, which then filters the sub category and the user selects the appropriate sub-category.
If you want to get the subcategories when you choose the parent category, you could use some JavaScript, for example:
View:
#model PrototypeWithAuth.ViewModels.CreateProductViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/View.cshtml";
}
<h1>Create</h1>
<h4>Product</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.ProductName" class="control-label"></label>
<input asp-for="Product.ProductName" class="form-control" />
<span asp-validation-for="Product.ProductName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubcategory.ParentCategoryID" class="control-label"></label>
<!--<input asp-for="ProductSubcategoryID" class="form-control" />-->
#Html.DropDownListFor(c => c.Product.ProductSubcategory.ParentCategoryID,
new SelectList(Model.ParentCategories, "ParentCategoryID", "ParentCategoryDescription"),
"Select Category", new { #class = "form-control",#id="parentlist" })
<span asp-validation-for="Product.ProductSubcategory.ParentCategoryID" class="text-danger"></span>
</div>
<div>
<select name="Product.ProductSubcategoryID" id="sublist" class="form-control"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
//Insert default item "Select" in dropdownlist on load
$(document).ready(function () {
var items = "<option value='0'>Select</option>";
$("#sublist").html(items);
});
$("#parentlist").change(function () {
var parentCategoryId = $("#parentlist").val();
var url = "/Products/GetSubCategoryList";
$.getJSON(url, { ParentCategoryId: parentCategoryId }, function (data) {
var item = "";
$("#sublist").empty();
$.each(data, function (i, subCategory) {
item += '<option value="' + subCategory.productSubcategoryID + '">' + subCategory.productSubcategoryDescription + '</option>'
});
$("#sublist").html(item);
});
});
</script>
}
ProductsController:
[HttpGet]
public JsonResult GetSubCategoryList(int ParentCategoryId)
{
var subCategoryList = _context.ProductSubcategories.Where(c => c.ParentCategoryID == ParentCategoryId).ToList();
return Json(subCategoryList);
}

Partial Views don't have any style when the action has a parameter? ASP.NET MVC 5

whenever i try to load a partial view, it doesn't have any style and doesn't apply java script within it.
Here is an example:
a partial view where the user is able to comment on a project.
this is the model i'm using:
public class Comment
{
[Key]
public int Id { get; set; }
public string CommentDesc { get; set; }
public string ProjectManagerId { get; set; }
[ForeignKey("ProjectManagerId")]
public ApplicationUser ProjectManager { get; set; }
public int ProjectId { get; set; }
[ForeignKey("ProjectId")]
public Project Project { get; set; }
}
this is the action:
[HttpGet]
[MyAuthorize(Roles = "Project Manager")]
public ActionResult Comment()
{
return PartialView("Comment");
}
[HttpPost]
[MyAuthorize(Roles = "Project Manager")]
public ActionResult Comment(int id,Comment Comment)
{
var userId = User.Identity.GetUserId();
Comment Com = new Comment
{
ProjectManagerId = userId,
ProjectId= id,
CommentDesc = Comment.CommentDesc
};
context.Comments.Add(Com);
context.SaveChanges();
return RedirectToAction("Main", "Home");
}
this is where the action gets called:
<a title="Comment" class="CommentProj" href='#Url.Action("Comment", "Project",new { id = item.Project.Id })'>
<img src='~/Bikes/Comment.png' width="32px" height="32px" />
</a>
and this is the Partial View:
#model Trial.Models.Comment
#using (Html.BeginForm("Comment", "Project"))
{
<div class="modal hide fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">Comment</h4>
</div>
<div class="modal-body">
<form>
#Html.TextAreaFor(m => m.CommentDesc, new { #class = "input-style form-control", #style = "background-color: transparent;", #placeholder = "Comment on the post", #rows = "6" })<br />
</form>
</div>
<div class="modal-footer">
<input type="submit" class="put-in btn btn-succes" value="Post" />
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div> }
and here is how it looks like when loaded:Here
Example:
Your Action :
[HttpGet]
[MyAuthorize(Roles = "Project Manager")]
public ActionResult Comment(int id)
{
var model= new Comment{
ProjectId=id
}
return PartialView("Comment",model);
}
[HttpPost]
[MyAuthorize(Roles = "Project Manager")]
public ActionResult Comment(Comment comment)
{
var com = new Comment
{
ProjectManagerId = User.Identity.GetUserId(),
ProjectId= id,
CommentDesc =comment.CommentDesc
};
context.Comments.Add(com);
context.SaveChanges();
return RedirectToAction("Main", "Home");
}
Your PartialView:
#model Trial.Models.Comment
#using (Html.BeginForm("Comment", "Project"))
{
<div class="modal hide fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">Comment</h4>
</div>
<form>
//Please use Html.BeginForm OR Ajax.BeginForm OR jQuery Ajax for submit form
<div class="modal-body">
#Html.HiddenFor(model=>model.ProjectId)
#Html.HiddenFor(model=>model.ProjectManagerId)
#Html.TextAreaFor(m => m.CommentDesc, new { #class = "input-style form-control", #style = "background-color: transparent;", #placeholder = "Comment on the post", #rows = "6" })<br />
</div>
<div class="modal-footer">
<input type="submit" class="put-in btn btn-succes" value="Post" />
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>
}
In you question i can't understand how you form submit. so please
use Html.BeginForm OR Ajax.BeginForm OR jQuery Ajax for submit
form.

Display list of objects and set a value field for each input MVC

I have the following class defined as my ViewModel
public class CreateApplicationViewModel
{
public Step1ViewModel Step1 { get; set; }
public Step2StandAloneViewModel Step2StandAlone { get; set; }
public Step2ChildViewModel Step2Child { get; set; }
public Step3ViewModel Step3 { get; set; }
public Step4ViewModel Step4 { get; set; }
}
I'm trying to display items in the Step4ViewModel which consists of the following:
public class Step4ViewModel
{
public List<DataDetails> DataDetails = new List<DataDetails>();
}
public class DataDetails
{
public string GroupCode { get; set; }
public string GroupDesc { get; set; }
public decimal DetailSequence { get; set; }
public string DetailCode { get; set; }
public string DetailDesc { get; set; }
public string YesNoFlag { get; set; }
public string NumberFlag { get; set; }
public string ValueFlag { get; set; }
public string DateFlag { get; set; }
public string ListValuesFlag { get; set; }
public string CommentFlag { get; set; }
public string CalcRateFlag { get; set; }
public string ColumnSequence { get; set; }
public string TextFlag { get; set; }
public string CheckboxFlag { get; set; }
public string YesNoValue { get; set; }
public int NumberValue { get; set; }
public DateTime DateValue { get; set; }
public string ListValue { get; set; }
public string CommentValue { get; set; }
public string TextValue { get; set; }
public bool CheckboxValue { get; set; }
}
In my controller I populate the Step4ViewModel.DataDetails like so:
private Step4ViewModel GetCaseDataDetails(string caseType)
{
Step4ViewModel model = new Step4ViewModel();
List<DataDetails> data = new List<DataDetails>();
List<DataDetailsValues> values = new List<DataDetailsValues>();
var dataDetails = (from tb1 in db.DEFAULT_CASE_DATA_VW
join tb2 in db.CASE_DATA_DETAIL on tb1.CASE_DATA_GROUP_ID equals tb2.CASE_DATA_GROUP_ID
where tb1.BUS_CASE_CODE == caseType
orderby tb2.DETAIL_SEQUENCE
select new { tb1, tb2 });
foreach (var detail in dataDetails.ToList())
{
DataDetails i = new DataDetails();
DataDetailsValues j = new DataDetailsValues();
i.CalcRateFlag = detail.tb2.CALC_RATE_FLAG;
i.CheckboxFlag = detail.tb2.CHECKBOX_FLAG;
i.ColumnSequence = detail.tb2.COLUMN_SEQUENCE;
i.CommentFlag = detail.tb2.COMMENT_FLAG;
i.DateFlag = detail.tb2.DATE_FLAG;
i.DetailCode = detail.tb2.DETAIL_CODE;
i.DetailDesc = detail.tb2.DETAIL_DESC;
i.DetailSequence = detail.tb2.DETAIL_SEQUENCE;
i.GroupCode = detail.tb1.GROUP_CODE;
i.GroupDesc = detail.tb1.GROUP_DESC;
i.ListValuesFlag = detail.tb2.LIST_VALUES_FLAG;
i.NumberFlag = detail.tb2.NUMBER_FLAG;
i.TextFlag = detail.tb2.TEXT_FLAG;
i.ValueFlag = detail.tb2.VALUE_FLAG;
i.YesNoFlag = detail.tb2.YES_NO_FLAG;
data.Add(i);
}
model.DataDetails = data;
return model;
}
My thought process with the Step4ViewModel is that for every DataDetail I will display the DetailDesc as a label and then beside of it I will have an input for the NumberValue, YesOrNoValue, NumberValue, DateValue, ListValue, CommentValue, TextValue, or CheckboxValue depending on the control type and then post that data to server. I am able to successfully display each DataDetail.DetailDesc, but for each input, which also renders, the values I enter into the inputs never post back to the server. Here is what my view looks like:
#model Portal.Models.ViewModel.CreateApplicationViewModel
#{
ViewBag.Title = "Step 4/5";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using System.Linq
<h4>Case Data Details</h4>
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#if (Model.Step4.DataDetails[i].TextFlag == "Y")
{
#Html.TextBoxFor(val => Model.Step4.DataDetails[i].TextValue, new { #class = "form-control" })
}
else if (Model.Step4.DataDetails[i].CheckboxFlag == "Y")
{
#Html.CheckBoxFor(val => Model.Step4.DataDetails[i].CheckboxValue, new { #class = "checkbox" })
}
</div>
</div>
</div>
}
</div>
</div>
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
Controller to which post data
[HttpPost]
public ActionResult Step4(Step4ViewModel step4)
{
if (ModelState.IsValid)
{
CreateApplicationViewModel model = (CreateApplicationViewModel)Session["case"];
// model.Step4 = step4;
Session["case"] = model;
return View();
}
return View();
}
I was thinking this could be due the grouping, which I do to separate each group into a separate HTML panel element, but my inputs are rendering with the index number in the name. Any help or suggestions on a better way to accomplish this would be greatly appreciated. Cheers!
UPDATE
Here is my updated post controller and view:
#model Portal.Models.ViewModel.CreateApplicationViewModel
#{
ViewBag.Title = "Step 4/5";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using System.Linq
<h4>Case Data Details</h4>
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
int index = 0;
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
<input type="hidden" name="Step4.DataDetails.Index" value="#index" />
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#if (Model.Step4.DataDetails[i].TextFlag == "Y")
{
#Html.TextBoxFor(val => val.Step4.DataDetails[i].TextValue, new { #class = "form-control" })
}
else if (Model.Step4.DataDetails[i].CheckboxFlag == "Y")
{
#Html.CheckBoxFor(val => val.Step4.DataDetails[i].CheckboxValue, new { #class = "checkbox" })
}
</div>
</div>
</div>
}
</div>
</div>
index++;
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
}
[HttpPost]
public ActionResult Step4(CreateApplicationViewModel step4)
{
if (ModelState.IsValid)
{
CreateApplicationViewModel model = (CreateApplicationViewModel)Session["case"];
// model.Step4 = step4;
Session["case"] = model;
return View();
}
return View();
}
UPDATE 2
I am able to get the form input if I pass a FormCollection to the HttpPost controller. Any ideas as to why I can get these values as a FormCollection but not as the model?
You are posting list of complex objects. But MVC DefaultModelBinder can’t able to bind to your DataDetails object because Index must be in sequence when posting the form with list of complex objects. In your case due to nested for loop, this sequence is broken. So what you can do is take one separate variable and initialize with default 0 value like this - I have tried to modify your code.
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
int index = 0;
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
<input type="hidden" name="Step4.DataDetails.Index" value="#index" />
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#if (Model.Step4.DataDetails[i].TextFlag == "Y")
{
#Html.TextBoxFor(val => val.Step4.DataDetails[i].TextValue, new { #class = "form-control" })
}
else if (Model.Step4.DataDetails[i].CheckboxFlag == "Y")
{
#Html.CheckBoxFor(val => val.Step4.DataDetails[i].CheckboxValue, new { #class = "checkbox" })
}
</div>
</div>
</div>
}
</div>
</div>
index++;
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
}
Look at the hidden field i have added in the view. That do the trick to post your data even with the broken sequences. Hope this help you.
I was able to get the model back to the controller by taking the idea of using an index integer and incrementing it from the answer above and implementing the idea in a different way in my view:
#model Portal.Models.ViewModel.CreateApplicationViewModel
#{
ViewBag.Title = "Step 4/5";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using System.Linq
<h4>Case Data Details</h4>
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
int index = 0;
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#Html.TextBoxFor(val => val.Step4.DataDetails[index].TextValue)
#Html.HiddenFor(val => val.Step4.DataDetails[index].GroupCode)
</div>
</div>
</div>
index++;
}
</div>
</div>
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
}
The above code in the view gives me the proper index of every element and allows me to post

Ajax.BeginForm with Dropdown + Server Side Validation

I am trying to build an Ajax.BeginForm (that has a dropdown) which uses server side validation that checks the model's annotations. I am having trouble getting the dropdown to work properly. Originally I populated the dropdown using the viewbag as such:
view:
#Html.DropDownList("CheckIn_Location", ViewBag.CageLocationList as IEnumerable<SelectListItem>, "(Select one)", new { #class = "form-control" })
controller:
public void GetCageLocations()
{
IEnumerable<SelectListItem> selectList =
from c in db.Locations
select new SelectListItem
{
Text = c.LocationName,
Value = c.Id.ToString()
};
ViewBag.CageLocationList = selectList;
}
But that didn't seem to play friendly with server side validation so I am tried reworking my model/view/controllers as such:
Here is my Model:
public class CheckInViewModel
{
public int CheckIn_Id { get; set; }
[Required(ErrorMessage = "Location Required.")]
public IEnumerable<SelectListItem> CheckIn_Location { get; set; }
[Required(ErrorMessage = "Quantity Required.")]
[Range(1, 100, ErrorMessage = "Quantity must be between 1 and 100000")]
public int CheckIn_Quantity { get; set; }
public string CheckIn_Comment { get; set; }
}
Here is my Controller:
[HttpPost]
public ActionResult CheckIn(CheckInViewModel model)
{
if (ModelState.IsValid)
{
var New_Transaction = new Transaction
{
Id = model.CheckIn_Id,
Quantity = model.CheckIn_Quantity,
LocationId = Convert.ToInt32(model.CheckIn_Location),
TransactionDate = DateTime.Now,
TransactionComments = model.CheckIn_Comment.Replace("\r\n", " ")
};
unitOfWork.TransactionRepository.Insert(New_Transaction);
unitOfWork.Save();
return PartialView("CheckIn", model);
}
return PartialView("CheckIn", model);
}
Here is my PartialView called CheckIn.cshtml
#model ViewModels.CheckInViewModel
<!-- Modal -->
<div class="modal fade" id="CheckInModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h1 class="modal-title" id="CheckInModalLabel">Check In</h1>
</div>
<div class="modal-body">
#using (Ajax.BeginForm("CheckIn", "Cage", null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "success", OnFailure = "failure"}, new { #id = "CheckInForm", #class = "form-horizontal" }))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.CheckIn_Id, new { #class = "form-control" })
<div class="form-group">
<label for="CheckIn_Location" class="col-sm-4 control-label">Location</label>
<div class="col-sm-8">
#Html.DropDownListFor(x => x.CheckIn_Location, Model.CheckIn_Location, "Select One")
#Html.ValidationMessageFor(model => model.CheckIn_Location)
</div>
</div>
<div class="form-group">
<label for="CheckIn_Quantity" class="col-sm-4 control-label">Quantity</label>
<div class="col-sm-8">
#Html.TextBoxFor(model => model.CheckIn_Quantity, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CheckIn_Quantity)
</div>
</div>
<div class="form-group">
<label for="CheckIn_Comment" class="col-sm-4 control-label">Comment</label>
<div class="col-sm-8">
#Html.TextAreaFor(model => model.CheckIn_Comment, new { #class = "form-control" })
</div>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="SubmitCheckInForm()">Check In</button>
</div>
</div>
</div>
</div>
here is the JS function that fire the submit:
function SubmitCheckInForm() {
$('#CheckInForm').submit();
}
Can someone show me how to:
populate the dropdown with (value/text) using the model as the binding agent (not the viewbag as I previously did it)
return the selected option to the controller and insert it into the transaction table's location element (which is an int)
properly hook this all up so the server side annotations work and return messages when something is incorrect in the form.
Thanks in Advance!
If I have understood you correctly, there is more than one way to do this. For instance, you could have two properties in your model to manage the Dropdown control.
1- CheckIn_Location_Selected. To store the value selected by the user
2- CheckIn_Location_List. To fill the DropdownList.
This could be your model.
public class CheckInViewModel
{
[Required(ErrorMessage = "Location Required.")]
public int CheckIn_Location_Selected { get; set; }
public IEnumerable<SelectListItem> CheckIn_Location_List { get; set; }
//Rest of the properties...
}
So now in your GET action you could have something like this:
[HttpGet]
public ActionResult CheckIn()
{
var model = new CheckInViewModel
{
CheckIn_Location_List = repository.GetCageLocations().Select(
location => new SelectListItem
{
Value = location.Id.ToString(),
Text = location.LocationName
})
};
return View(model);
}
And in your view:
#Html.DropDownListFor(x => x.CheckIn_Location_Selected, Model.CheckIn_Location_List, "Select One")
We need to change a bit your POST action.
[HttpPost]
public ActionResult CheckIn(CheckInViewModel model)
{
if (!ModelState.IsValid)
{
// This is necessary because we are sending the model back to the view.
// You could cache this info and do not take it from the DB again.
model.CheckIn_Location_List = repository.GetCageLocations().Select(
location => new SelectListItem
{
Value = location.Id.ToString(),
Text = location.LocationName
});
return PartialView("CheckIn", model);
}
var New_Transaction = new Transaction
{
Id = model.CheckIn_Id,
Quantity = model.CheckIn_Quantity,
LocationId = Convert.ToInt32(model.CheckIn_Location_Selected),
TransactionDate = DateTime.Now,
TransactionComments = model.CheckIn_Comment.Replace("\r\n", " ")
};
unitOfWork.TransactionRepository.Insert(New_Transaction);
unitOfWork.Save();
return PartialView("CheckIn", model);
}

Categories