MVC model conflict using partial class and Validate - asp.net-mvc

I have a page containing a form and a partial view (containing a form too).
both model have 1 (or more) properties with the same name. when I validate the first form, the value and validation message is duplicate on the second form.
I create a little sample with dummy entities.
person.cs
public partial class Person : IValidatableObject
{
[Required(ErrorMessage = "name required")]
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (Name == "admin") //just example
{
results.Add(new ValidationResult("You cant be admin.", new[] { "Title", "Name" }));
}
return results;
}
}
Person/Index.cshtml
#model Person
#{
ViewBag.Title = "Person";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("Index", "Person", FormMethod.Post, new { id = "CreatePersonForm" }))
{
#Html.AntiForgeryToken()
#Html.DisplayNameFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
<input type="submit" value="Save" class="btn btn-default" />
}
#Html.Partial("~/Views/Dog/Index.cshtml", new Dog())
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
PersonController.cs
public class PersonController : Controller
{
// GET: Person
public ActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index([Bind(Include = "Name")] Person person)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
return View(person);
}
}
I made a partial view practically the same.
Dog.cs
public partial class Dog : IValidatableObject
{
[Required(ErrorMessage = "name required")]
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (Name == "admin") //just example
{
results.Add(new ValidationResult("You cant be admin.", new[] { "Title", "Name" }));
}
return results;
}
}
Dog/Index.cshtml
#model Dog
#{
ViewBag.Title = "Dog Page";
}
#using (Html.BeginForm("Index", "Dog", FormMethod.Post, new { id = "CreateDogForm" }))
{
#Html.AntiForgeryToken()
#Html.DisplayNameFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
<input type="submit" value="Save" class="btn btn-default" />
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
DogController.cs
public class DogController : Controller
{
// GET: Dog
public ActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index([Bind(Include = "Name")] Dog dog)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
return View(dog);
}
}
if you start /Person/Index, if you write admin in the first textbox (person form), after posting (save) the second form (dog form) have the same text and validation than the first form.

The #Html.EditorFor by default uses the property name as the id and name of the generated HTML, and the validation uses these values to set the error messages! You can pass a value to overwrite that default behavior in your partial view as following:
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger", #data_valmsg_for="partial_name" })
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control", #id="partial_name" } })

Related

how i can send multivalue to create action

i have a doctor i want add doctor subspecialty to the doctor from sub specialties table many to many relationship
i need to add subspecialties from multiselect list but my controller only add first selection , i want my create controller take all passed subspecialties and create it
my model
public partial class DoctorSubSpecialty
{
public int Id { get; set; }
public Nullable<int> DoctorId { get; set; }
public Nullable<int> SubSpecialtyId { get; set; }
public virtual DoctorProfile DoctorProfile { get; set; }
public virtual SubSpecialty SubSpecialty { get; set; }
}
}
create get action
public ActionResult Create()
{
ViewBag.DoctorId = new SelectList(db.DoctorProfiles, "Id", "FullName");
ViewBag.SubSpecialtyId = new MultiSelectList(db.SubSpecialties, "id", "Name");
return View();
}
create post action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Id,DoctorId,SubSpecialtyId")] DoctorSubSpecialty doctorSubSpecialty)
{
DoctorSubSpecialty doctorSub = db.DoctorSubSpecialties.Where(d => d.DoctorId == doctorSubSpecialty.DoctorId & d.SubSpecialtyId == doctorSubSpecialty.SubSpecialtyId).FirstOrDefault();
if (doctorSub == null) {
db.DoctorSubSpecialties.Add(doctorSubSpecialty);
await db.SaveChangesAsync();
}
my view
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DoctorSubSpecialty</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.DoctorId, "DoctorId", htmlAttributes: new { #class = "control-label col-md-2", #id = "DoctorID" })
<div class="col-md-10">
#Html.DropDownList("DoctorId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.DoctorId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SubSpecialtyId, "SubSpecialtyId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SubSpecialtyId",(MultiSelectList)ViewBag.SubSpecialtyId, htmlAttributes: new { #multiple = "multiple", #class = "form-control" })
#Html.ValidationMessageFor(model => model.SubSpecialtyId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Create a ViewModel specific to your usecase that can actually transport more than one Id.
I.e. you will need an int[] to bind the selection to.
A ViewModel also helps you to get rid of all this ViewBag and [Bind] nonsense.
public class CreateDoctorSubSpecialtyViewModel {
// These are the selected values to be posted back
public int DoctorId { get; set; }
public int[] SubSpecialtyIds { get; set; }
// These are the possible values for the dropdowns
public IEnumerable<SelectListItem> DoctorProfiles { get; set; }
public IEnumerable<SelectListItem> SubSpecialties { get; set; }
}
GET action - initialize the ViewModel and pass it to the View:
[HttpGet]
public ActionResult Create() {
var doctorProfiles = db.DoctorProfiles.Select(d =>
new SelectListItem {
Text = d.FullName,
Value = d.Id
}
).ToArray();
var subSpecialties = db.SubSpecialties.Select(s =>
new SelectListItem {
Text = s.Name,
Value = s.id
}
).ToArray();
var viewModel = new CreateDoctorSubSpecialtyViewModel {
DoctorProfiles = doctorProfiles,
SubSpecialties = subSpecialties
};
return View("Create", viewModel);
}
View "Create.cshtml" (styling removed for clarity) - tell MVC which ViewModel we want to use with #model:
#model CreateDoctorSubSpecialtyViewModel
#using (Html.BeginForm("Create", "YourControllerName", FormMethod.Post)) {
#Html.DropDownListFor(m => m.DoctorId, Model.DoctorProfiles)
#Html.DropDownListFor(m => m.SubSpecialtyIds, Model.SubSpecialties, new { multiple = "multiple" })
<input type="submit" />
}
POST action - use Linq Contains to test against multiple submitted SubSpecialtyIds:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CreateDoctorSubSpecialtyViewModel postData) {
DoctorSubSpecialty[] allSelectedSubSpecialities = db.DoctorSubSpecialties
.Where(d => d.DoctorId == postData.DoctorId
&& postData.SubSpecialtyIds.Contains(d.SubSpecialtyId))
.ToArray();
// ...
}
EDIT #Html.DropDownListFor requires an IEnumerable<SelectListItem> as second parameter.

MVC-5 Razor Validation Errors showing on page load

I have an insert page when I go to the insert page all the validation field is being shown,
[Required(ErrorMessage ="Please Enter Name")]
public string ccname { get; set; }
This is my class where I declared string ccname with required validation message
ENTER NAME
and it supposed to appear when user clicks on insert without entering data in ccname
but validatation message is being shown on the page load
#Html.TextBoxFor(model => model.ccname, new { #class = "textboxstyle" })
#Html.ValidationMessageFor(model => model.ccname)
I tried few things but nothing works,
here is an example
in my controller I added ModelState.clear();
public ActionResult insert()
{
ModelState.Clear();
return View();
}
and in my view I changed the code from
#Html.TextBoxFor(model => model.ccname, new { #class = "textboxstyle" })
#Html.ValidationMessageFor(model => model.ccname)
to
#Html.TextBoxFor(model => model.ccname, new { #class = "textboxstyle" })
#Html.ValidationMessageFor(model => model.ccname,"",new {#style= ".validation-summary-valid { display:none; }" })
but neither of these works
what should I do now?
Example:
Model:
public class MyModel
{
[Required(ErrorMessage ="Please Enter Name")]
public string ccname { get; set; }
}
Controller:
public class HomeController:Controller
{
[HttpGet]
ActionResult Insert()
{
var model =new MyModel();
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
ActionResult Insert(MyModel model)
{
if(ModelState.IsValid)
{
//Do something
return View();
}
return View(model);
}
}
View
Insert.cshtml
#model MyModel
#using (Html.BeginForm("Insert", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.TextBoxFor(model => model.ccname, new { #class = "textboxstyle" })
#Html.ValidationMessageFor(model => model.ccname)
<input type="submit" value="Insert" class="btn btn-primary" />
}
#Scripts.Render("~/bundles/jqueryval")

MVC MultipleModel DropdownlistFor SaveChanges()

HI i Have a MultipleModel View with CompanyName and EmployeeRange. CompanyName contains names of companies with a Relationship link to EmployeeRange (int) Field.
The Employee Range is basically
0-9
10-19
20-49
I can create and SaveChanges for a new Company Name Field.
Please help With Saving Selected value from the EmployeeRange DropDownListFor to DB.
Here is the Code
//MultipleModel.cs
public partial class MultipleModel
{
public MultipleModel()
{
CompanyEntities = new company();
EmployeeEntities = new Employee();
}
public company CompanyEntities { get; set; }
public Employee EmployeeEntities { get; set; }
}
//CompanyController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RegisterCompany(MultipleModel model)
{
if (ModelState.IsValid)
{
//GET VARIBALE FROM DB
var addComanyName = db.companies.Add(model.CompanyEntities);
//GET VARIABLLE
addComanyName.COMPANY_NAME = model.CompanyEntities.COMPANY_NAME;
//ADD VARIBALE TO DB
db.companies.Add(addComanyName);
db.SaveChanges();
return RedirectToAction("index");
}
return View();
}
//RegisterCompany.cshtml
#model EISystem.Models.MultipleModel
#Html.DropDownListFor(m => m.CompanyEntities, new SelectList(ViewBag.products, "Employees_Range_ID", "Employees_Range"), "Select Number of Employees")
?? How Do i view the DropDownListFor so that selected Value can be POST to Controller and later be saved to DB?
You should use another class which is ViewModel for your MultipleModel class.
Like:
public class MultipleModelViewModel
{
public int SelectedProductId { get; set; }
public List<Products> ProductList{ get; set; }
public string Name { get; set; }
}
In a View
#model MultipleModelViewModel
#using (Html.BeginForm("RegisterCompany", "Company", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.SelectedProductId , htmlAttributes: new { #class = "form-control" })
#Html.DropDownListFor(model => model.SelectedProductId, new SelectList(Model.ProductList, "Id", "Name"), "Select Product", new { #class = "form-control " })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "form-control " })
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
</div>
<button type="Submit" class="btn btn-success " id="Save-btn">
Save
</button>
}
Controller:
//Get
public ActionResult RegisterCompany()
{
var model = new MultipleModelViewModel (){
Name = model.Name,
Products = db.Products.Select(x=>new {Id=x.Id, Name=x.Name).ToList()
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RegisterCompany(MultipleModelViewModel model)
{
if (ModelState.IsValid)
{
var company = new Company(){
Name = model.Name,
Product = db.Products.Find(model.SelectedProductId)
};
db.companies.Add(company);
db.SaveChanges();
return RedirectToAction("index");
}
return View();
}

Losing form values after validation

Using a ViewModel for validation:
public class CCvm
{
[Required(ErrorMessage = "Please enter your Name")]
public string cardHolderName { get; set; }
}
My controller calls a task on post:
public async Task<ActionResult> Pay(FormCollection form, CCvm model)
{
if (!ModelState.IsValid)
{
return View(model);
}
}
And the View:
#model GCwholesale.Models.CCvm
#{
Layout = "~/Views/Shared/_HomeSubPageLayout.cshtml";
ViewBag.Title = "Secure Checkout";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="Payment">
<label>Name on Card: </label>
#Html.EditorFor(model => model.cardHolderName, new { htmlAttributes = new { #placeholder = "Cardholder Name Please", #Value = ViewBag.Name } })<br />
#Html.ValidationMessageFor(model => model.cardHolderName)
<button class="submitCheckout">SUBMIT NOW</button>
</div>
}
But when validation fails the data in the form goes away.
Thanks for taking a look.
You do not need to set #Value = ViewBag.Name inside EditorFor.
#Html.EditorFor(model => model.cardHolderName,
new { htmlAttributes = new { #placeholder = "Cardholder Name Please" } })
Besides, you do not need FormCollection as a parameter because you already have CCvm Model.
public async Task<ActionResult> Pay(CCvm model){
{
//...
}
#Value = ViewBag.Name
You're not setting the ViewBag.Name, so it wouldn't have a value and would result in a blank input. Remove that and let the HtmlHelper set it based off the value in the posted model.

Add to list mvc

I'm trying to add string data to list, but have null reference exception and don't have any idea how to fix this.
Here home controller:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ChatUser model)
{
if (ModelState.IsValid)
{
**model.chatGroup.Add(model.inputGroups);** - here excepton
}
return View(model);
}
And Index.cshtml:
#model test.Models.ChatUser
#{
ViewBag.Title = "Test";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
#Html.LabelFor(m => m.inputGroups, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.inputGroups, new { #class = "form-control" })
<input type="submit" value="GO" class="btn btn-default" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Your form does not include any controls for properties of chatGroup so its null when you post back. You need to either initialize the property in a parameterless constructor
public class ChatUser
{
public ChatUser()
{
chatGroup = new List<string>(); // initialize collection
}
....
public List<string> chatGroup { get; set; }
}
or initialize it in the POST method
[HttpPost]
public ActionResult Index(ChatUser model)
{
if (ModelState.IsValid)
{
model.chatGroup = new List<string>(); // initialize collection
model.chatGroup.Add(model.inputGroups);
}
return View(model);
}

Resources