why is the dropdown value always null in database mvc razor - asp.net-mvc

I am creating a registration form, there is a field called gender in drop down list. when I select Male or Female from the gender drop down list the value will always be null and return a validation message "Male or Female is not valid for the field"
controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "ID, FirstName,DOB,Gender")] AgentMaster agentMaster)
{
if (string.IsNullOrEmpty(Convert.ToString(agentMaster.Gender)))
{
ModelState.AddModelError("Gender", "Gender is required");
}
if (ModelState.IsValid)
{
db.AgentMasters.Add(agentMaster);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(agentMaster);
}
view
#model A.Models.AgentMaster
<div class="form-group">
#Html.LabelFor(model => model.Gender, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Gender, new SelectList(new[] { "Male", "Female" }),"--select--")
#Html.ValidationMessageFor(model => model.Gender, "", new { #class = "text-danger" })
</div>
</div>
in model gender is given as
public Nullable<short> Gender { get; set; }
when I debug the code using break points the vale for gender in [httppost] is null, but by giving 1 or 0 it works. so I use validation in controller to convert to string, but it is not working. Iam not getting where I need to change. can anyone please help to find solution ??

Your generating a <select> which posts back a string (either "Male" or "Female" which cannot be bound to a short.
Change the property to string, or if you do want short then you need to generate an IEnumerable<SelectListItem> that has the corresponding Value, for example
new List<SelectListItem>()
{
new SelectListItem() { Value = "0", Text = "Male" },
new SelectListItem() { Value = "1", Text = "Female" }
};
however, storing a Gender as a numeric value makes no sense, and the property should be a string or an enum.

Related

Remove Comma when posting number to SQL Error:System.InvalidOperationException:

How can I remove a comma from the value when posting back to SQL? My SQL field is decimal but sometimes a value in the user will enter 10,000 and not 10000 so the post fails. So the strange this is I get an error message:
System.InvalidOperationException: 'The ViewData item that has the key 'TypeOf' is of type 'System.String' but must be of type 'IEnumerable'.'
Which happens with a comma is put in Amount - without a comma in amount all is fine?
Thanks,
EB
<div class="form-group">
#Html.LabelFor(model => model.TypeOf, htmlAttributes: new { #class = "required control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.TypeOf, (IEnumerable<SelectListItem>)ViewBag.TypesOfLoan, null, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TypeOf, "", new { #class = "text-danger" })
</div>
</div>
View:
#Html.(model => model.Amount, new { htmlAttributes = new { #class = "form-control", #style = "width:110px" } })
CS:
public Nullable<decimal> Amount { get; set; }
Controller:
public ActionResult CreateOrder(Order newOrder)
{
if (ModelState.IsValid)
{
db.Orders.Add(newOrder);
db.SaveChanges();
return RedirectToAction("Orders");
}
else
{
return View(newOrder);
}
}
If the user can add commas then it is no longer a decimal, which means your field is a text or varchar.
NOTE You can stop the user from entering the comma or any other funny character at all
#Html.TextBoxFor(model => model.Amount, new {#type="number"})
One of the things you can do is to use string replace and the parse to double
To replace the comma use
var amount = amount_input.Replace(',', '');
to parse to double use
var amount_as_double = double.Parse(amount, CultureInfo.InvariantCulture)
EDIT
looking at your question, I would not know if you are using #Html.TextBoxFor or and also no know how you are positing the form to the controller.
if your controller method looks like this
[HttpPost]
public ActionResult Index(Model model)
{
//let's say no1 is decimal, model.no1 is going to be 0 if the user enters a comma and string.Replace is not going to work
var amount = model.no1;
...
}
but of you are using something like
[HttpPost]
public ActionResult Index(FormCollection collection)
{
var amount = collection["Amount"].Replace(',', '')
...
}

MVC Dynamic View Binding to Dictionary of Properties for Known and Unknown properties

I have been trying to find a solution to a situation that I'm busy designing, however I have not managed to get to it.
Imagine having the following model
public enum InputType
{
TextInput,
LookupInput
}
public struct AdditionalProperty
{
public string Key {get;set;}
public string Value {get;set;}
public InputType Type {get;set;}
}
public class Person
{
public string FirstName {get;set;}
public List<AdditionalProperty> AdditionalProperties {get;set;}
}
Then, having the following controller
public class HomeController
{
public ActionResult Index()
{
var model = new Person { FirstName = "MyName" };
model.AdditionalProperties = new List<AdditionalProperty>();
var listItem = new AdditionalProperty
{
Key = "Surname",
Value = "MySurname"
};
model.AdditionalProperties.Add(listItem);
return View(model)
}
}
What I'm looking for is the Razor view code on how to "dynamically" create the properties with the correct input type, bound to something in order for me to be able to still use the model when the form gets posted back to the controller for a Save function.
So the property that is known, would be something like this:
<div class="form-group">
<div class="form-row">
<div class="col-md-6">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label" })
<div>
#Html.TextBoxFor(model => model.FirstName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
</div>
</div>
The Idea would then be to have the following. Obviously the below isn't sufficient, and this is where I need the help.
I would like to show the additional properties, one below the other, each on a separate line (using bootstrap row) based on the property.InputType
#foreach (var property in Model.Properties)
{
#Html.LabelFor(model => property.Key, new { #class = "control-label" })
<div>
#if (property.InputType == TextInput)
{
#Html.TextBoxFor(model => property.Value, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
}
#Html.ValidationMessageFor(model => property.Key, "", new { #class = "text-danger" })
</div>
}
Thus, I would like to see my view as:
| <label> | <input>
Known Property | FirstName | MyFirstName
Unknown Property | Surname | MySurname
In terms of completeness, I am posting the following answer.
I am going to post the Model, View (Index & EditorTemplates) & Controller to show the complete working solution that I used to test the answer that was given to me.
My Model class for this test
Person.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<AdditionalProperty> AdditionalProperties { get; set; }
}
AdditionalProperty.cs
public struct AdditionalProperty
{
public string Key { get; set; }
public object Value { get; set; }
public DateTime? DateValue
{
get
{
DateTime dateValue;
if (DateTime.TryParse(Value?.ToString(), out dateValue))
{
return dateValue;
}
return null;
}
set => Value = value;
}
public InputType InputType { get; set; }
public List<SelectListItem> ValueLookupItems { get; set; }
}
The reason I have a separate DateValue property here is to assist the browser when doing DateTime binding otherwise the DateTimePicker doesn't show.
I used an enum to determine what type of input type this specific property should make use of.
InputType.cs
public enum InputType
{
TextBox,
DropdownBox,
TextArea,
DateSelection,
}
In order to keep the views as simple as possible, Stephen provided me with a sample for the Index View as well as an EditorTemplate for the AdditionalProperty object. The EditorTemplate is used for separation of concerns and to ensure that all the logic behind what input type is being used is in one place.
I have found that the DateTime property doesn't work well, so an additional EditorTemplate was required. I got this from this post.
DateTime.cshtml
Note: Location of template -> /Views/Shared/EditorTemplates
#model DateTime
#{
IDictionary<string, object> htmlAttributes;
object objAttributes;
if (ViewData.TryGetValue("htmlAttributes", out objAttributes))
{
htmlAttributes = objAttributes as IDictionary<string, object> ?? HtmlHelper.AnonymousObjectToHtmlAttributes(objAttributes);
}
else
{
htmlAttributes = new RouteValueDictionary();
}
htmlAttributes.Add("type", "date");
String format = (Request.UserAgent != null && Request.UserAgent.Contains("Chrome")) ? "{0:yyyy-MM-dd}" : "{0:d}";
#Html.TextBox("", Model, format, htmlAttributes)
}
AdditionalProperty.cshtml
Note: Location of template -> /Views/Shared/EditorTemplates
Note: The location of my AdditionalProperty formed part of the DynamicViewExample.Models namespace
#model DynamicViewExample.Models.AdditionalProperty
<div>
#Html.HiddenFor(m => m.Key)
#Html.LabelFor(m => m.Key, Model.Key, new {#class = "control-label"})
#if (Model.InputType == DynamicViewExample.Models.InputType.TextBox)
{
#Html.TextBoxFor(m => m.Value, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.TextArea)
{
#Html.TextAreaFor(m => m.Value, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.DropdownBox)
{
#Html.DropDownListFor(m => m.Value, Model.ValueLookupItems, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.DateSelection)
{
#Html.EditorFor(m => m.DateValue, new {#class = "form-control"})
}
else
{
#Html.HiddenFor(m => m.Value) // we need this just in case
}
</div
This would be how the Index.cshtml file would look
#model DynamicViewExample.Models.Person
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm())
{
<div class="row">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label" })
#Html.TextBoxFor(model => model.FirstName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.LabelFor(model => model.LastName, new { #class = "control-label" })
#Html.TextBoxFor(model => model.LastName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.EditorFor(m => m.AdditionalProperties, new { htmlAttributes = new { #class = "form-control"}})
</div>
<input type="submit" class="btn btn-primary" />
}
And then finally, the HomeController.cs file contains a Get and Post that allows the ability to manipulate the data as you please. What is missing here is the "dynamic" way of populating the model, but that will naturally happen once a DB has been introduced into the mix.
[HttpGet]
public ActionResult Index()
{
var model = new Person
{
FirstName = "Gawie",
LastName = "Schneider",
AdditionalProperties = new List<AdditionalProperty>
{
new AdditionalProperty {Key = "Identification Number", Value = "1234567890123456", InputType = InputType.TextBox},
new AdditionalProperty {Key = "Date Of Birth", Value = DateTime.Today, InputType = InputType.DateSelection},
new AdditionalProperty {Key = "Age", Value = "31", InputType = InputType.TextBox},
new AdditionalProperty {Key = "Gender", Value = "Male", InputType = InputType.DropdownBox,
ValueLookupItems = new List<SelectListItem>
{
new SelectListItem{Text = "Male", Value = "Male"},
new SelectListItem{Text = "Female", Value = "Female"}
}},
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Person model)
{
//Do some stuff here with the model like writing it to a DB perhaps
return RedirectToAction("Index");
}
So if I would have to sum up what I was trying to do here.
The goal I wanted to achieve was to be able to make use of Strongly Typed / Known Properties in conjunction with Dynamic / Unknown Properties to create a system that would allow the user to create new inputs on the fly without the need for a developer to be involved.
I honestly hope that this might help someone else as well some day.
Enjoy the coding experience
Gawie

MVC create using liked table values in dropdown, entity framework

I have a patient model, which has a foreign key to a gender table. I would like to create a 'Create' view which allows user to select the value of male or female from a dropdown.
firstly I was not sure how to get the Gender model into the dropdown. I have managed to get it to work by putting into the viewbag, like so
// GET: Patient
public ActionResult Create()
{
using (var context = new WaysToWellnessDB())
{
// prepopulat roles for the view dropdown
var gender = context.Genders.Select(rr => new SelectListItem { Value = rr.GenderId.ToString(), Text = rr.GenderDesc }).ToList();
ViewBag.Gender = gender;
return View();
}
}
In my view I have the following, which gives me the dropdown I desire.
<div class="form-group">
#Html.LabelFor(model => model.Gender, "Gender", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Gender", (IEnumerable<SelectListItem>)ViewBag.Gender, "Select ...")
#Html.ValidationMessageFor(model => model.Gender.GenderId, "", new { #class = "text-danger" })
</div>
</div>
However when I do my post to add to the database, the gender column is null.
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(Patient patient)
{
try
{
using (var context = new WaysToWellnessDB())
{
context.Patients.Add(patient);
context.SaveChanges();
return RedirectToAction("Index");
}
}
catch
{
return View();
}
}
Please can someone advise how I get the gender Id to be stored in the database?
You should use #Html.DropDownListFor
#Html.DropDownListFor(model => model.GenderId, (IEnumerable<SelectListItem>)ViewBag.Gender, "Select ...")

Html Helper Drop Down List switches value to top option on submit / in database

I am filling out a form, however when selecting an option from the drop down list and click submit, no matter what option I select, it always parses the top one through. The displayed value never changes, so it you leave it as the default option 'please select...' and click submit, this stays as 'please select...' but the entry in the database is always the one that appears at the top of the drop down.
Here is the model:
public enum Medium
{
[Description("Teleconference & Report")]
Teleconference_Report,
[Description("Email & Telephone")]
Email_Telephone
}
[Required]
[Display(Name = "Medium")]
public Medium Medium { get; set; }
Here is the field in the form:
<div class="form-group">
#Html.LabelFor(model => model.Medium, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.DropDownList("MediumID", null, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Medium, "", new { #class = "text-danger" })
</div>
</div>
The "MediumID" DropDownList is populated using a viewbag which is set to whatever the following returns:
// Puts all of the mediums of communication into a user friendly dropdownlist.
public List<SelectListItem> GetMediumList()
{
List<SelectListItem> mediumList = new List<SelectListItem>();
foreach (Medium state in EnumToList<Medium>())
{
mediumList.Add(new SelectListItem
{
Text = GetEnumDescription(state),
Value = state.ToString(),
});
}
return mediumList;
}
Below shows the form section for another enum called 'Frequency', but these are not changed to user friendly strings (and is working fine).
<div class="form-group">
#Html.LabelFor(model => model.Frequency, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EnumDropDownListFor(model => model.Frequency, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Frequency, "", new { #class = "text-danger" })
</div>
</div>
Below here, shows the two methods which turn the enums into user friendly strings:
// Returns a 'user friendly', readable version of the enum.
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
// Puts all of the same enums into a list.
public static IEnumerable<T> EnumToList<T>()
{
Type enumType = typeof(T);
// Can't use generic type constraints on value types,
// so have to do check like this.
if (enumType.BaseType != typeof(Enum))
throw new ArgumentException("T must be of type System.Enum");
Array enumValArray = Enum.GetValues(enumType);
List<T> enumValList = new List<T>(enumValArray.Length);
foreach (int val in enumValArray)
{
enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
}
return enumValList;
}
Finally, here is the method signature where the fields are binded/bound:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Point,ApplicationID,MediumID,Frequency,StartDate,EndDate")] TouchPoint touchPoint)
Within this method, the dropdown is passed to the view using the following:
ViewBag.MediumID = GetMediumList();
Any help is greatly appreciated.
Your model has a property named Medium but your view does not bind to that property. The name of the <select> your generating is MediumID which does not exist in your model, so the default value for Medium when you submit will Teleconference_Report (the first enum value).
Change the view to
#Html.DropDownListFor(m => m.Medium, (IEnumerable<SelectListItem>)ViewBag.MediumID, "Please select...", new { #class = "form-control" })
although I would recommend changing the ViewBag property name to say MediumList to make it more obvious that its a collection. And even better, use a view model with a property public IEnumerable<SelectListItem> MediumList { get; set; } so that the viewcan be #Html.DropDownListFor(m => m.Medium, Model.MediumList, .... ).
You also need to change the [Bind] attribute to include "Medium" (and remove "MediumID") although using a view model means the [Bind] attribute is not required.
Side note: You do not need the [Required] attribute unless you want to add a specific error message using the ErrorMessage = "..." property (an enum is always required by default unless you make the property nullable).

Validation message won't appear - accepts 'please select' as an entry

I am submiting a blank form and all of the other fields display their correct validation error message stating that the field cannot be empty, except for one.
Here is the model:
public enum Medium
{
[Description("Teleconference & Report")]
Teleconference_Report,
[Description("Email & Telephone")]
Email_Telephone
}
[Required]
[Display(Name = "Medium")]
public Medium Medium { get; set; }
Here is the field in the form:
<div class="form-group">
#Html.LabelFor(model => model.Medium, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.DropDownList("MediumID", null, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Medium, "", new { #class = "text-danger" })
</div>
</div>
The "MediumID" DropDownList is populated using a viewbag which is set to whatever the following returns:
// Puts all of the mediums of communication into a user friendly dropdownlist.
public List<SelectListItem> GetMediumList()
{
List<SelectListItem> mediumList = new List<SelectListItem>();
foreach (Medium state in EnumToList<Medium>())
{
mediumList.Add(new SelectListItem
{
Text = GetEnumDescription(state),
Value = state.ToString(),
});
}
return mediumList;
}
The issue is, is that when the default display of the drop down list of 'please select...' is left, this is accepted and the form gets submitted, which doesn't happen for any of my other fields. When looking at the new entry in the database, the 'Medium' field gets set to the first thing in the DropDownList which is 'Teleconference & Report'.
Any help is greatly appreciated.
EDIT:
Below shows the form section for another enum called 'Frequency', but these are not changed to user friendly strings.
<div class="form-group">
#Html.LabelFor(model => model.Frequency, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EnumDropDownListFor(model => model.Frequency, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Frequency, "", new { #class = "text-danger" })
</div>
</div>
Below here, shows the two methods which turn the enums into user friendly strings:
// Returns a 'user friendly', readable version of the enum.
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
// Puts all of the same enums into a list.
public static IEnumerable<T> EnumToList<T>()
{
Type enumType = typeof(T);
// Can't use generic type constraints on value types,
// so have to do check like this.
if (enumType.BaseType != typeof(Enum))
throw new ArgumentException("T must be of type System.Enum");
Array enumValArray = Enum.GetValues(enumType);
List<T> enumValList = new List<T>(enumValArray.Length);
foreach (int val in enumValArray)
{
enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
}
return enumValList;
}
Add the EnumDataType attribute to the property on your view model:
[EnumDataType(typeof(Medium), ErrorMessage = "Submitted value is not valid for the enum")]
public Medium Medium { get; set; }
You should add this anyway, because an Enum can represent any numeric value and won't be validated automatically by the MVC framework.
I hope this helps!

Resources