Possibility to have Range data annotation validation only on form submit? - c#

I have been searching through the stackoverflow website for an answer to my question, but I couldn't find a proper solution. So I hope I can get the solution this way.
Situation:
I have a form with several fields; 2 of them are required: one of them is an EnumDropDownListFor which has an extra 'empty option'.
Problem:
The validation of the dropdownlist triggers on the change event of the dropdown/when the empty option has been selected.
Wanted behavior:
Only validate the dropdownlist when the form is submitted and NOT on the change event.
Here is my code:
public class SearchOrderViewModel
{
[Required]
[Display(Name = "Supplier", ResourceType = typeof(SearchOrderStrings))]
public string Supplier { get; set; }
[Required]
[Range(1, int.MaxValue, ErrorMessageResourceType = typeof(SearchOrderStrings), ErrorMessageResourceName = "OrderTypeRequired")]
[Display(Name = "OrderType", ResourceType = typeof(SearchOrderStrings))]
public OrderTypeEnum OrderType { get; set; }
[Display(Name = "PurchasingReason", ResourceType = typeof(SearchOrderStrings))]
public int PurchasingReason { get; set; }
}
public enum OrderTypeEnum
{
OpenOrder = 1,
ClosedOrder = 2
}
Index.cshtm
<div id="divExternalSupplierSearch" class="form-group">
#Html.LabelFor(model => model.Supplier)
#Html.ValidationMessageFor(model => model.Supplier)
<div id="divDropdown">
#Html.DropDownListFor(model => model.Supplier,
Model.SupplierCodes, new { #class = "form-control" })
</div>
</div>
<div id="divOrderTypes" class="form-group">
#Html.LabelFor(model => model.OrderType)
#Html.ValidationMessageFor(model => model.OrderType)
<div id="divDropdown">
#Html.EnumDropDownListFor(model => model.OrderType, new { #class = "form-control" })
</div>
</div>
<div id="divPurchasingReasons" class="form-group">
#Html.LabelFor(model => model.PurchasingReason)
<div id="divDropdown">
#Html.DropDownListFor(model => model.PurchasingReason, Model.PurchasingReasons, new { #class = "form-control" })
</div>
</div>
If more information is needed, let me know.
Thanks
EDIT (Solution):
By combining #Elmer Dantas answer and trying some things, I found that I could become the wanted behaviour by removing the [Range] data-annotation and making the OrderType property in my SearchOrderViewModel nullable.
So this code is only validating the OrderType value on submit of the form:
[Required]
[Display(Name = "OrderType", ResourceType = typeof(SearchOrderStrings))]
public OrderTypeEnum? OrderType { get; set; }
Apparently the
[Range(1, int.MaxValue, ErrorMessageResourceType = typeof(SearchOrderStrings), ErrorMessageResourceName = "OrderTypeRequired")]
data-annotation was checking if the value was valid on the change event of the dropdownlist. By making the property nullable and just keeping the [Required] data-annotation, I could keep the validation to only be triggered when submitting the form.

Related

Filter validation-type in Html.ValidationMessageFor

I got a model like this:
public class SignUpModel
{
[Required, DisplayName(#"Particulars/Salutation")]
public short Salutation { get; set; }
public IEnumerable<SalutationOption> SalutationOptions { get; set; }
[Required, DisplayName("Particulars/FirstName")]
public string FirstName { get; set; }
[Required, Display(Name = "Particulars/LastName")]
public string LastName { get; set; }
[Required, DataType(DataType.EmailAddress), Display(Name = "Particulars/Email")]
public string Email { get; set; }
[Required, DataType(DataType.EmailAddress), Compare("Email"), Display(Name = "Particulars/EmailConfirmation")]
public string EmailConfirmation { get; set; }
}
Now in my view, I want to post validation-errors (also client-side) using #Html.ValidationMessageFor.
<div class="section-field">
<div class="form-group">
#Html.LabelFor(model => model.Email, new { #class = "control-label required" })
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { autocomplete = "off" } })
#Html.ValidationMessageFor(x=>x.Email);
</div>
</div>
<div class="section-field">
<div class="form-group">
#Html.LabelFor(model => model.EmailConfirmation, new { #class = "control-label required" })
#Html.EditorFor(model => model.EmailConfirmation, new { htmlAttributes = new { autocomplete = "off" } })
#Html.ValidationMessageFor(x=>x.EmailConfirmation);
</div>
</div>
But in Html.ValidationMessageFor I only want to show things like "invalid email-adress" or "both emails must match".
I don't want to show the message saying, that the fields are required, since all are required - and they get bordered red.
Is it possible somehow? Tried with implementing a custom HtmlHelper, but that doesn't filter anything client-side.
So I fixed it simply with css now.
I add a class required-validation to invalid Required-validations:
<p class="validation-error required-validation">The field 'FirstName' is required</p>
and added into my custom.css
p.validation-error.required-validation {
display: none;
}
Other ideas on how to handle this with ASP are welcome :)

MVC post action ViewModel is returned as NULL

I am trying to post the form values back to the controller. But in the action, I get the ViewModel as null.
Here is ViewModel
public class CommunicationDetailsViewModel
{
public string OrganizationName { get; set; }
public List<Country> Country { get; set; }
public List<State> State { get; set; }
public List<City> City { get; set; }
[Display(Name = "Id")]
public int CountryId { get; set; }
[Display(Name = "Id")]
public int StateId { get; set; }
[Display(Name = "Id")]
public int CityId { get; set; }
[StringLength(32), Required(ErrorMessage ="Address is required")]
public string Address { get; set; }
[StringLength(32), Required(ErrorMessage = "Building name is required")]
public string BuildingName { get; set; }
}
Below is the controller action:
[HttpPost]
public ActionResult Save(CommunicationDetailsViewModel communicationDetailsViewModel)
{
return View();
}
Does it have to do anything with the Kendo UI for MVC? Because this is the very first time I am using Kendo UI. Below is the view:
#model WebAPI.ViewModels.CommunicationDetailsViewModel
#{
ViewBag.Title = "Supplier Information";
}
<h4>Supplier Details</h4>
#using (Html.BeginForm("Save", "SupplierInformation", FormMethod.Post ))
{
<div class="demo-section k-content">
<div class="form-group">
#Html.Label("Organization name")
#Html.Kendo().TextBoxFor(model => model.OrganizationName).Name("txtOrganization").HtmlAttributes(new { #class = "k-textbox required", placeholder = "Organization Name" })
</div>
<div class="form-group">
#Html.Label("Country")
#(Html.Kendo().DropDownList().Name("ddlCountry").DataTextField("CountryName").DataValueField("Id").BindTo(Model.Country))
</div>
<div class="form-group">
#Html.Label("State")
#(Html.Kendo().DropDownList().Name("ddlState").DataTextField("StateName").DataValueField("Id").BindTo(Model.State))
</div>
<div class="form-group">
#Html.Label("City")
#(Html.Kendo().DropDownList().Name("ddlCity").DataTextField("CityName").DataValueField("Id").BindTo(Model.City))
</div>
<div class="form-group">
#Html.Label("Address")
#Html.Kendo().TextBoxFor(model => model.Address).Name("txtAddress").HtmlAttributes(new { #class="k-textbox required", placeholder="Address", #maxlength = "32" })
</div>
<div class="form-group">
#Html.Label("Building name")
#Html.Kendo().TextBoxFor(model => Model.BuildingName).Name("txtBuildingName").HtmlAttributes(new { #class = "k-textbox required", placeholder = "Address", #maxlength = "32" })
</div>
</div>
#Html.Kendo().Button().Name("btnSave").Content("Save").HtmlAttributes(new { type = "submit", #class = "k-button k-primary" })
}
And interestingly, if I use FormCollection instead of my ViewModel, I am able to get the values in the action.
What am I missing here? Must be something stupid. Any help appreciated.
I think problem here is caused by you change name by Name function. Note that MVC binding properties by name attribute of input tag so don't change it
For example you use
#Html.Kendo().TextBoxFor(model => model.OrganizationName).Name("txtOrganization").HtmlAttributes(new { #class = "k-textbox required", placeholder = "Organization Name" })
You change name of input from OrganizationName to txtOrganization that may cause MVC cann't binding properties exactly. You should keep its original name or ignore change its name like this
#Html.Kendo().TextBoxFor(model => model.OrganizationName).Name("OrganizationName").HtmlAttributes(new { #class = "k-textbox required", placeholder = "Organization Name" })

List of CC for email purposes not working

I have an EmailFormModel class.
public class EmailFormModel
{
[Required, Display(Name = "Your Name:")]
public string FromName { get; set; }
[Required, Display(Name = "Your Email:")]
public string FromEmail { get; set; }
[Required, Display(Name = "To Email:")]
public string ToEmail { get; set; }
public List<SelectListItem> CCEmail { get; set; }
[Required]
[AllowHtml]
public string Message { get; set; }
public EmailFormModel()
{
CCEmail = new List<SelectListItem>();
}
}
Now I need this email to have multiple CC recipients, hence why I made the property CCEmail a type of List. In my HttpGet method I am populating the list which is correctly working. In my HttpPost I am doing this:
foreach(var item in model.CCEmail)
{
message.CC.Add(new MailAddress(item.Text));
}
Now, in my View... what can I do to display these email addresses.. so that when I hit Submit they will be submitted as email addresses?
Currently in my View I have this:
<div class="form-group">
#Html.LabelFor(m => m.CCEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.ListBoxFor(m => m.CCEmail,null, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.CCEmail)
</div>
</div>
Is there another/better way to display the email addresses rather than ListBoxFor?
But when I select the email addresses.. and then hit Submit, I get an error message:
The value 'John.Doe#test.com,Test.User1#test.com' is invalid.
Those aren't the real email addresses.. the ones that I am using are valid.
Any help is appreciated.
Even though I have found an alternative solution, I am still looking for a cleaner solution. Here is what I have done.
I changed the CCEmail property to a List<string>.
So, in the HttpPost method I changed the foreach loop to this syntax:
foreach(var item in model.CCEmail)
{
message.CC.Add(new MailAddress(item));
}
Then in my view, I did this:
<div class="form-group">
#foreach(var item in Model.CCEmail)
{
#Html.LabelFor(m => m.CCEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBox("CCEmail", item, htmlAttributes: new { #class = "form-control", #readonly = true } )
</div>
}
</div>
Even though this creates 2 separate textboxes, it still submits as 2 separate email addresses instead of both of them combined as what I think the error in my OP was.
Again, if you know of a simpler/cleaner solution, please post!

Showing and hiding controls from both controller and view

I am trying to create a best possible solution for this but as this is the first time I am encountering this scenario so I am not sure how to best implement this.
I have a very simple model,
public class Feedback
{
[Required]
[Display(Name = "Current ID")]
public int? PreviousID { get; set; }
[Required]
[Display(Name = "Next ID")]
public int? NextID { get; set; }
[Required]
public int ScenarioID { get; set; }
[Display(Name = "Select you scenario")]
public IEnumerable<SelectListItem> YourScenario { get; set; }
}
When User first loads the view then only dropdownlist for YourScenario and TextBox for PreviousID is displayed. When User select dropdownlist then based on its value the TextBox for NextID is displayed to user. Here is my view,
<div class="form-group">
#Html.LabelFor(m => m.YourScenario, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.DropDownListFor(m => m.ScenarioID, m.YourScenario, "Choose Scenario", new { #class = "form-control chosen-select" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.PreviousID, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.PreviousID)
</div>
</div>
<div class="form-group" style="display:none">
#Html.LabelFor(m => m.NextID, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.NextID)
</div>
</div>
To show/hide the NextID I use the Jquery on the view,
$('#YourScenario').change(function () {
var selectedValue = $(this).val();
var nextID = $('#NextID');
if (selectedValue = "3") {
nextID .show();
}
else {
nextID .hide();
}
});
All this works great. Now to use the same View as Edit Mode I pass the model to the view from controller. What I want is that the TextBox for NextID should be displayed or hidden automatically based on the model values. If I use if condition on the View then the control is not available through Javascript so how can I achieve this?
Here is a simple example: https://jsfiddle.net/jma71jf7/
When you run the code, it will hide the input, because there is no selected value, and this line will trigger the change event function:
$('#YourScenario').trigger('change');
But when editing, the select will have some value, and it will hide/show the input according to the value.

Conditionally change the [Display(Name = "")] attribute

I have a simple requirement but unable to understand how to achieve this. I have these properties in my View Model.
public class Feedback
{
[Required]
[Display(Name = "Current ID")]
public int? PreviousID { get; set; }
[Required]
[Display(Name = "Next ID")]
public int? NextID { get; set; }
[Required]
public int ScenarioID { get; set; }
[Display(Name = "Select you scenario")]
public IEnumerable<SelectListItem> YourScenario { get; set; }
}
Now on the view I have,
<div class="form-group">
#Html.LabelFor(m => m.YourScenario, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.DropDownListFor(m => m.ScenarioID, m.YourScenario, "Choose Scenario", new { #class = "form-control chosen-select" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.PreviousID, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.PreviousID)
</div>
</div>
<div class="form-group" style="display:none">
#Html.LabelFor(m => m.NextID, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.NextID)
</div>
</div>
On the view as it can be seen that only PreviousID and a DropDownList for Scenario is displayed. Using JQuery I am showing and hiding the div for NextID based on some specific value of DropDownList. So suppose if user select "Scenario 3" from the dropdown then the Input for NextID is displayed.
Having explained all of this what I want is to change the Display name for PreviousID based on the selected value of dropdownlist. So if user selects "Scenario 1" and "Scenario 2" then the Display Name for PreviousID should be "Current ID" and if user selects "Scenario 3" then the Display Name should be "Previous ID". This change should also be reflected in the Validation Summary. So kindly tell me how to achieve this? Many Thanks
I have two ideas:
1)
You could remove the line [Display(Name = "Current ID")]. Then you need to use Label("SomeName") instead of LabelFor.
Then you create an event like DropDownList_SelectionChanged that then changes the Name of the Label.
2)
Or you add a member DisplayName and bind to it:
public class Feedback
{
[Required]
public int? PreviousID { get; set; }
[Required]
[Display(Name = "Next ID")]
public int? NextID { get; set; }
[Required]
public int ScenarioID { get; set; }
[Display(Name = "Select you scenario")]
public IEnumerable<SelectListItem> YourScenario { get; set; }
public String DisplayName {
get
{
String name = "";
if (ScenarioID == 1) {
name = "SomeName";
} else {
name = "otherName";
}
return name;
}
}
}
In your view:
#Html.Label({Binding Content=m,Path=DisplayName}
It's probably not completely correct since I can not test your project but I hope you get the idea. Please tell me what you tried.
I suggest you remove the [DisplayName] attribute, and instead use a <label> element manually in the view. Give the label an id e.g.
<label for="whatever" id="lbl-whatever">Hello World</label>
Create a JavaScript file that the view references, via a <script> tag, which uses jQuery to capture the change event of the dropdown
$(function(){
$("#ScenarioID").on("change", function() {
console.log(this.id);
console.log(this.value);
// I think this.id and this.value should display the id and value of the selected // item in the list.
$("#lbl-whatever").text("Foo");
});
});
Hopefully the above should be enough to get you started.

Categories