Extremely bizarre Fluent Validation behaviour - asp.net

Current project:
DotNet 4.7
MVC 5
Database-first and generated from a very old Web Forms database (nchar fields and the like… don’t ask why)
So I am experiencing extremely bizarre behaviour from Fluent Validation.
To wit,
Non-Nulled Nullable fields validate only on the server-side. Take an int (and NOT an int?) field that is supposed to be populated by a drop-down menu's value, and while it will validate on the server-side, it will only de-validate on the client side. It will not re-validate on the client side if you choose a non-acceptable null value ("Select a choice") again.
This behaviour appears to be limited to int fields that are filled from drop-down lists.
All strings, dates and any other types of fields fail to validate on the client side until the form is submitted (once greedy validation kicks in), and will not validate at all on the server side. Plus, all .NotEmpty() and .NotNull() declarations appear to be ignored, even when they are the only one in the validation for a string field.
I have my Global.asax.cs properly configured:
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
FluentValidationModelValidatorProvider.Configure();
}
}
I have the correct JS files coming into the page:
<link href="/Content/bootstrap.css" rel="stylesheet"/>
<link href="/Content/bootstrap-datepicker3.css" rel="stylesheet"/>
<link href="/Content/fontawesome-all.css" rel="stylesheet"/>
<link href="/Content/style.css" rel="stylesheet"/>
<script src="/Scripts/modernizr-2.8.3.js"></script>
<script src="/Scripts/jquery-3.3.1.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="/Scripts/bootstrap.js"></script>
<script src="/Scripts/bootstrap-datepicker.js"></script>
<script src="/Scripts/popper.js"></script>
<script src="/Scripts/jquery.mask.js"></script>
<script src="/Scripts/script.js"></script>
My ViewModel is appropriately configured:
namespace Project.Models {
using Controllers;
using FluentValidation.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Validators;
[Validator(typeof(MoreInfoValidator))]
public class MoreInfoViewModel {
[DisplayName(#"First Name")]
public string FirstName { get; set; }
[DisplayName(#"Last Name")]
public string LastName { get; set; }
[DisplayName(#"Phone Number")]
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
[DisplayName(#"eMail Address")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DisplayName(#"Date of Birth")]
[DataType(DataType.DateTime)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Dob { get; set; } = DateTime.Now.AddYears(-16);
[DisplayName(#"Mailing Address")]
public string Address { get; set; }
[DisplayName(#"City")]
public string City { get; set; }
[DisplayName(#"Province or State")]
public string ProvState { get; set; }
[DisplayName(#"Postal Code")]
[DataType(DataType.PostalCode)]
public string Postal { get; set; }
[DisplayName(#"Country")]
public int CountryId { get; set; }
[DisplayName(#"How did you hear about us?")]
public int HowHeardId { get; set; }
[DisplayName(#"Training Site")]
public int TrainingSiteId { get; set; }
[DisplayName(#"Comments")]
public string Comments { get; set; }
public IEnumerable<SelectListItem> HowHeardList = ListController.HowHeardList();
public IEnumerable<SelectListItem> CountryList = ListController.CountryList();
public IEnumerable<SelectListItem> TrainingSiteList = ListController.TrainingSiteList();
}
}
I have my validators properly configured:
namespace Project.Validators {
using FluentValidation;
using Models;
public class MoreInfoValidator : AbstractValidator<MoreInfoViewModel> {
public MoreInfoValidator() {
RuleFor(x => x.FirstName)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("You must provide a first name of some kind.")
.MinimumLength(2).WithMessage(#"A first name must be at least two characters or longer.");
RuleFor(x => x.LastName)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage(#"You must provide a last name of some kind.")
.MinimumLength(2).WithMessage(#"A last name must be at least two characters or longer.");
RuleFor(x => x.Email.Trim())
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage(#"Please provide an eMail address to act as the login username.")
.EmailAddress().WithMessage(#"Please provide a valid eMail address to act as the login username.");
RuleFor(x => x.Phone)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please enter a valid 10-digit phone number.")
.Length(12, 12).WithMessage("Phone number must be in the form of “123-456-7890”")
.Matches(#"^\d{3}-\d{3}-\d{4}$").WithMessage("Phone number must be a valid 10-digit phone number with dashes, in the form of “123-456-7890”");
RuleFor(x => x.Address)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your street address.")
.MinimumLength(6).WithMessage("Addresses should be at least 6 characters long.");
RuleFor(x => x.City)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your city.")
.MinimumLength(2).WithMessage("City names should be at least 2 characters long.");
RuleFor(x => x.ProvState)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your province or state.")
.Length(2).WithMessage("Please provide the 2-character code for your province or state.");
RuleFor(x => x.CountryId)
.NotEmpty().WithMessage("Please choose your country.");
RuleFor(x => x.HowHeardId)
.NotEmpty().WithMessage("How did you hear of us?");
RuleFor(x => x.TrainingSiteId)
.NotEmpty().WithMessage("Please choose a desired training site.");
}
}
}
My forms are correctly built:
#model Project.Models.MoreInfoViewModel
#{
ViewBag.Title = "More Info";
}
<h1>#ViewBag.Title</h1>
<p><span class="requiredcolor">These fields</span> are required.</p>
#using(Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationMessage("", new { #class = "alert" })
<div class="row">
<div class="form-group col-md-6">
#Html.LabelFor(x => x.FirstName, new { #class = "control-label required" })#Html.EditorFor(x => x.FirstName, new { htmlAttributes = new { #class = "form-control required", maxlength = 100 } })
#Html.ValidationMessageFor(x => x.FirstName)
</div>
<div class="form-group col-md-6">
#Html.LabelFor(x => x.LastName, new { #class = "control-label required" })#Html.EditorFor(x => x.LastName, new { htmlAttributes = new { #class = "form-control required", maxlength = 100 } })
#Html.ValidationMessageFor(x => x.LastName)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
#Html.LabelFor(x => x.Phone, new { #class = "control-label required" })#Html.EditorFor(x => x.Phone, new { htmlAttributes = new { #class = "form-control required phone", maxlength = 12 } })
#Html.ValidationMessageFor(x => x.Phone)
</div>
<div class="form-group col-md-4">
#Html.LabelFor(x => x.Email, new { #class = "control-label required" })#Html.EditorFor(x => x.Email, new { htmlAttributes = new { #class = "form-control required", maxlength = 75 } })
#Html.ValidationMessageFor(x => x.Email)
</div>
<div class="form-group col-md-4">
#Html.LabelFor(x => x.Dob, new { #class = "control-label required" })#Html.EditorFor(x => x.Dob, new { htmlAttributes = new { #class = "form-control required datepicker" } })
#Html.ValidationMessageFor(x => x.Dob)
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
#Html.LabelFor(x => x.Address, new { #class = "control-label required" })#Html.EditorFor(x => x.Address, new { htmlAttributes = new { #class = "form-control required", maxlength = 150 } })
#Html.ValidationMessageFor(x => x.Address)
</div>
<div class="form-group col-md-6">
#Html.LabelFor(x => x.City, new { #class = "control-label required" })#Html.EditorFor(x => x.City, new { htmlAttributes = new { #class = "form-control required", maxlength = 50 } })
#Html.ValidationMessageFor(x => x.City)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
#Html.LabelFor(x => x.ProvState, new { #class = "control-label required" })#Html.EditorFor(x => x.ProvState, new { htmlAttributes = new { #class = "form-control required", maxlength = 2 } })
#Html.ValidationMessageFor(x => x.ProvState)
</div>
<div class="form-group col-md-4">
#Html.LabelFor(x => x.CountryId, new { #class = "control-label required" })#Html.DropDownListFor(x => x.CountryId, Model.CountryList, "« ‹ Select › »", new { #class = "form-control required" })
#Html.ValidationMessageFor(x => x.CountryId)
</div>
<div class="form-group col-md-4">
#Html.LabelFor(x => x.Postal, new { #class = "control-label" })#Html.EditorFor(x => x.Postal, new { htmlAttributes = new { #class = "form-control postalcode", maxlength = 7 } })
#Html.ValidationMessageFor(x => x.Postal)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
#Html.LabelFor(x => x.HowHeardId, new { #class = "control-label required" })#Html.DropDownListFor(x => x.HowHeardId, Model.HowHeardList, "« ‹ Select › »", new { #class = "form-control required" })
#Html.ValidationMessageFor(x => x.HowHeardId)
</div>
<div class="form-group col-md-4">
#Html.LabelFor(x => x.TrainingSiteId, new { #class = "control-label required" })#Html.DropDownListFor(x => x.TrainingSiteId, Model.TrainingSiteList, "« ‹ Select › »", new { #class = "form-control required" })
#Html.ValidationMessageFor(x => x.TrainingSiteId)
</div>
<div class="form-group col-md-4">
#Html.LabelFor(x => x.Comments, new { #class = "control-label" })#Html.TextAreaFor(x => x.Comments, new { #class = "form-control", rows = 3 })
#Html.ValidationMessageFor(x => x.Comments)
</div>
</div>
<div class="blank-divider clearfix" style="height:30px;"></div>
<div class="row">
<div class="form-group col-md-6"> </div>
<div class="form-group col-md-6"><label class="control-label"> </label><input type="submit" value="Submit Request" alt="Submit Request" title="Submit Request" class="btn btn-default btn-success" /></div>
</div>
}
I am utterly stumped as to why Fluent Validation is only firing under extremely limited circumstances.
Please understand that when it does fire -- post-submission greedy validation for all other fields, server-side validation for the drop-down menus -- all messages are perfectly as expected.
However, validation is not firing for both client-side and server-side for any field. If it fires server-side, it fails to fire client-side. If it fires client side, it does so only under limited conditions (string length not sufficient, etc.) and it then refuses to fire server-side.
What is not working is the usual validation and any attempt to catch .NotEmpty() and .NotNull().
EDIT:
I can’t see how this is possible (nothing is being loaded from the DB, only inserted into it), but could string-field validation be buggered up by the format of the database’s fields? Could it being an nchar be an issue here?
Plus, I am using a custom model to gather the data, before adding it to a Data Model for insertion into the DB, so we have even more distance from the DB structure. Theoretically I couldn’t see how such a relationship is possible prior to moving the data from the ViewModel to the DataModel.
EDIT 2:
The .Cascade(CascadeMode.StopOnFirstFailure) made no difference, the issues occur with or without these in the Validator class.
EDIT 3:
Let me be more precise about the greedy validation. In all cases, any .NotEmpty() and .NotNull() validations still fail to fire, so simply navigating from one field to the next (after hitting Submit) will fail to trigger greedy validation. It’s only when you put something in, and it’s not sufficient for other validation (too short, not a valid eMail, not a valid phone number etc.) that the greedy validation fires. That is why I came up with my first edit (above), because maybe the system is not seeing those string fields as either empty or null, even when they are.
EDIT 4:
More WTF bizarreness. When I put in a partial/incomplete string into something that does more than just length analysis -- such as only the first half of an eMail into the eMail field -- and then hitting Submit, THE SERVER-SIDE VALIDATION KICKS IN FOR ALL STRING FIELDS, EVEN THE NULL/EMPTY ONES.
Like, seriously Whisky. Tango. Foxtrot.
EDIT 5:
WTF x10: Edit 4 only happens if the three drop-downs have been selected. If any one of the three drop-downs are still un-selected, server-side validation fails to fire for any text field.
Plus, if all three drop-downs have been selected, full validation using .NotEmpty() and .NotNull() suddenly succeeds on all text fields, including both server-side and client-side greedy validation.
Holy tamale. This is getting bizarre.

This problem arises from a conceptual blind spot I have had since I started working with Fluent Validation.
Since before I started using Fluent Validation, I have used GUIDs as primary keys. And I have used GUIDs throughout the entire data life cycle - from the ViewModel out through the View and back into the Mapper where the data gets dumped back into the Data Model for adding to/updating the DB.
As such, when I used drop-down select menus using GUIDs as primary keys, and those select menus were meant to populate required foreign keys, I was able to set the ViewModel field to a non-nullable GUID and still have Fluent Validation fire correctly. And when I wanted to fill optional foreign keys, I used a nullable GUID for that field, and everything still worked.
This project used a database that pre-dated my involvement. As such, it made use of int as primary keys, and so certain foreign keys that were required were also int and not int?. Because of this, I made the assumption - however wrong - that I could continue using int to hold the value of the drop-down select menu for a required foreign key and be able to successfully validate everything else, including the text fields.
Boy, was I wrong.
Jeremy Skinner of Fluent Validation fame took his Sunday out to assist me and showed me what I needed to do.
Essentially, any integer-driven drop-down select menu, whether it is for a required foreign key or an optional one, needs an int? in the ViewModel to hold the selected value. Without a nullable int?, validation across the entire model will act in unexpected ways, to the point of not firing at all on anything else (as per my experience).
When I changed the value of that field to an int?, and added a null-coalescing operator to the Mapper (to cast the int? to an int by providing a default value for any null value that slipped past Fluent Validation), everything suddenly started working as expected.

Related

Does Having Hundreds of Readonly Static Variables Cause Performance Issues?

I am working on a new Asp.Net MVC project and this time to have better control over the static contents of the application, I am planning to store all content as reusable properties in readonly static variables. These values are not going to change throughout the application's lifecycle.
Example of usage:
I have created a class holding all messages in static variables so that if I want to change let's say, the default save message I can change it from here without having a need to change every occurrence in the project. This includes validation messages also. (numbers could grow rapidly, depending upon the size of the project)
Other usages: To store all application-related properties such as version, title, keys, and many more. I am also planning to store the button texts and other UI controls-related properties so that they can be easily customized, such as I don't know maybe CSS classes.
And not to mention, apart from the above I also have a number of static classes such as Data Access Helpers and Utility Functions with some static methods.
Sample Class:
public static class Messages
{
public static class Response
{
public readonly static string SUCCESS = "Process completed successfully";
public readonly static string FAILED = "Process failed";
public readonly static string ERROR = "Some error occured";
public readonly static string OPRNOTPERFORMED = "Operation aborted/failed for some unknown reason. Please contact your administrator";
//LOGIN
public readonly static string USERNOTFOUND = "Invalid username or password";
public readonly static string USERINACTIVE = "User is not active. Please contact your administrator";
public readonly static string NOROLEDEFINED = "User does not have any valid authority to access the application. Please contact your administrator";
public readonly static string FORCELOGOUT = "You have been logged out. Please login again";
//MASTERS
public readonly static string NOSTATESFOUND = "No states found in database. please contact your administrator";
public readonly static string APPCHOICENOTFOUND = "Option not found in the database. Please contact your system administrator.";
//COMPANY
public readonly static string INVALIDCOMPANY = "Please sign out from the application and login again before performing further opreations";
}
public static class Description
{
//COMPANY
public readonly static string INVALIDCOMPANY = "Company value changed between 2 consiquent requests. This happens when you left the page idle for so long. Please login again before performing further operations.";
}
public static class Instructions
{
public readonly static string MANDATORY_FIELDS = "Fields marked with <i class='text-warning-dark fw-bold fs-6'>*</i> cannot be blank.";
}
}
As of now, these are all on paper only. I am willing to take this approach so that I can easily customize the text contents according to different clients' needs. But before that, I have a few doubts to clear.
Does all static variables initialized and stored in memory as we run the web application?
Does all static variables remain in memory throughout the lifecycle of the web application, irrespective of its usage in the current View?
Does the compiler replace the occurrences of the static variable with its actual value at the time of converting the source code into bytecode? (I have seen this in java when I decompiled the class file, wherever I used the static variables, it is replaced with its actual value).
Another Example:
public class Select
{
private static readonly SelectListItem defaultChoice = new SelectListItem { Text = "Select", Value = "" };
public static readonly IEnumerable<SelectListItem> ISACTIVE = new List<SelectListItem>
{
new SelectListItem {Text = "Active", Value = "true"},
new SelectListItem {Text = "Inactive", Value = "false"}
};
private static IList<SelectListItem> STATES;
public static IEnumerable<SelectListItem> GetStates(string sessionHash)
{
if (sessionHash == null)
{
return null;
}
if (STATES == null)
{
StateService stateService = new StateService(sessionHash);
ProcessResponse response = stateService.GetStates();
IList<StateModel> statesModel = (IList<StateModel>)response.Data[0];
STATES = new List<SelectListItem>();
STATES.Add(defaultChoice);
foreach (StateModel state in statesModel)
{
STATES.Add(new SelectListItem { Text = state.Name, Value = state.StateId.ToString() });
}
}
return STATES;
}
public static IEnumerable<SelectListItem> AppChoices(IList<AppChoicesModel> choicesModel)
{
IList<SelectListItem> choices = new List<SelectListItem>();
choices.Add(defaultChoice);
if (choicesModel != null && choicesModel.Count > 0)
{
foreach(AppChoicesModel choice in choicesModel)
{
choices.Add(new SelectListItem { Text = choice.Text, Value = choice.Value });
}
}
return choices;
}
}
Usage in View
#model Cygnus.View.Models.Company.CompanyBranch
#using Cygnus.Data.Constants
#{
Layout = "~/Views/Shared/_LayoutDashboard.cshtml";
ViewBag.Title = Titles.Company.BRANCHT;
ViewBag.SubTitle = Titles.Company.BRANCHST;
var moduleName = Routes.Company.BRANCH;
var isActive = Select.ISACTIVE;
var states = Select.GetStates(Session[Codes.SessionParams.HASH].ToString());
Html.RenderPartial(Routes.Commons.PROCRESPONSE);
}
<div class="container-fluid">
<div class="row">
<!-- left column -->
<div id="#string.Format("{0}Form_Container", #moduleName)" class="col-md-7 mt-1 collapse show">
<!-- general form elements -->
<div class="card card-info">
<div class="card-header">
<h3 class="card-title">#ViewBag.SubTitle</h3>
</div>
<!-- /.card-header -->
<!-- form start -->
#using (Html.BeginForm(moduleName, Routes.Company.CONTROLLER, FormMethod.Post, new { #name = moduleName }))
{
#Html.HiddenFor(model => model.CompanyId)
#Html.HiddenFor(model => model.CompanyBranchId)
#Html.AntiForgeryToken()
<div class="card-body">
<div class="row gx-3">
<div class="col-10">
<i class="fas fa-info-circle text-info mr-2"></i>#Html.Raw(#Messages.Instructions.MANDATORY_FIELDS)
</div>
<div class="col-2">
<button class="btn btn-block btn-info btn-flat size-width-auto float-right" value="#Codes.ButtonValue.ADD" name="#Codes.ButtonGroupName.ACTION" ><i class="fas fa-plus-square fa-1xl align-middle mr-2"></i><span class="align-middle">#Codes.ButtonValue.NEW</span></button>
</div>
</div>
<div class="row gx-3 mt-3">
<div class="input-group-sm col">
<label for="Name" class="fw-semibold">#Html.DisplayNameFor(model => model.Name)</label>
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control input-required", #placeholder = "Branch Name" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger fs-6-5" })
</div>
<div class="input-group-sm col-4">
<label for="Code" class="fw-semibold">#Html.DisplayNameFor(model => model.Code)</label>
#Html.EditorFor(model => model.Code, new { htmlAttributes = new { #class = "form-control input-required", #placeholder = "Branch Code" } })
#Html.ValidationMessageFor(model => model.Code, "", new { #class = "text-danger fs-6-5" })
</div>
</div>
<div class="row gx-3 mt-5px">
<div class="input-group-sm col">
<label for="State" class="fw-semibold">#Html.DisplayNameFor(model => model.State)</label>
#Html.DropDownListFor(model => model.State, states, new { #class = "form-control input-required" })
#Html.ValidationMessageFor(model => model.State, "", new { #class = "text-danger fs-6-5" })
</div>
<div class="input-group-sm col-2">
<label for="Pincode" class="fw-semibold">#Html.DisplayNameFor(model => model.Pincode)</label>
#Html.EditorFor(model => model.Pincode, new { htmlAttributes = new { #class = "form-control input-required", #placeholder = "Pincode" } })
#Html.ValidationMessageFor(model => model.Pincode, "", new { #class = "text-danger fs-6-5" })
</div>
<div class="input-group-sm col">
<label for="BranchType" class="fw-semibold">#Html.DisplayNameFor(model => model.BranchType)</label>
#Html.DropDownListFor(model => model.BranchType, Model.BranchTypeList, new { #class = "form-control input-required" })
#Html.ValidationMessageFor(model => model.BranchType, "", new { #class = "text-danger fs-6-5" })
</div>
</div>
<div class="row gx-3 mt-5px">
<div class="input-group-sm col">
<label for="IsActive" class="fw-semibold">#Html.DisplayNameFor(model => model.IsActive)</label>
#Html.DropDownListFor(model => model.IsActive, isActive, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.IsActive, "", new { #class = "text-danger fs-6-5" })
</div>
</div>
</div>
<!-- /.card-body -->
<div class="card-footer">
<button type="submit" value="#Codes.ButtonValue.SAVE" name="#Codes.ButtonGroupName.ACTION" class="btn btn-success px-5">#Codes.ButtonValue.SAVE</button>
<div>#Html.ValidationSummary(true, "", new { #class = "text-danger" })</div>
</div>
}
</div>
<!-- /.card -->
</div>
<div class="col-md mt-1">
#{
Html.RenderAction(Routes.Company.BRANCHLIST, Routes.Company.CONTROLLER);
}
</div>
</div>
</div>
#section Scripts {
#Scripts.Render("~/Scripts/Cygnus/Company.js")
<script>
$(function () {
var tableId = "#listOfCompanyBranch";
initDataTableWithDefaultToolbar(tableId, '#moduleName');
attachRowDataEvenOnATag(tableId);
});
function onRowClicked(rowData, row) {
let formElement = $("##string.Format("{0}Form_Container",moduleName)");
if ($(formElement).is('.collapse:not(.show)'))
$(formElement).collapse("show");
document.getElementById("CompanyBranchId").value = $(row).attr("data-branch");
document.getElementById("CompanyId").value = $(row).attr("data-company");
FillFormControls(formArr["#moduleName"], rowData);
$("#IsActive").val((rowData[13]).toLocaleLowerCase());
}
</script>
}
Does Having Hundreds of Readonly Static Variables Cause Performance
Issues?
No. The variables in your example are a few bytes each. Let's say you have 500 variables, and they are 20 bytes each. That would be 10KB. Modern web servers generally have several gigabytes of memory, so it is unlikely that you face any performance issue because you are caching 10KB of data. See here for more info about static variables.
IMO, the main issue with having too many static types (class/variable) is that you don't know where to find them. In the examples that you have provided, it seems like you are hardcoding some constants into static variables. IMO that’s fine, provided:
You don’t define configuration variables as constants (things like connection strings, which are environment dependent)
You are using a logical approach to group these constants, so they are easy to find
I would try to limit the size of such constants (not because of performance but to keep the code clean)
Looking at your code, there might be more elegant ways to achieve this, for example you can define an interface called IResponse:
interface IResponce
{
short Code { get; }
string CssColor { get; }
string Message { get; }
}
and define different classes for response types, for example:
public class SuccessResponse : IResponse
{
public short Code => 0;
public string CssColor => "green";
public string Message => "Process completed successfully";
}
public class FailureResponse : IResponse
{
public short Code => 1;
string CssColor => "red";
public string Message => "Process failed";
}

ASP.NET MVC 5: How to avoid hidden form field manipulation during model binding?

I am working on a tutorial from the Microsoft website with the end result like this image:
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/mvc-music-store/mvc-music-store-part-5/_static/image4.png
The price textbox in the View is:
<div class="form-group">
#Html.LabelFor(model => model.AlbumToEdit.Price, "Price", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AlbumToEdit.Price)
#Html.ValidationMessageFor(model => model.AlbumToEdit.Price)
</div>
</div>
Which renders as:
<input class="text-box single-line" id="AlbumToEdit_Price" name="AlbumToEdit.Price" type="text" value="9.99">
If I inspect the HTML via DevTools and add the below:
<input id="AlbumToEdit_Price" name="AlbumToEdit.Price" type="hidden" value="33.33">
And click the Save button to save the album details, the binding process is taking the 33.33 in consideration, regardless what I insert in the Price textbox, probably because both <input> tags share the same id and name.
How can I avoid this?
Controller code follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AlbumToEditViewModel postedViewModel)
{
if (ModelState.IsValid)
{
db.Entry(postedViewModel.AlbumToEdit).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
AlbumToEditViewModel albumToEditViewModel = new AlbumToEditViewModel
{
AlbumToEdit = postedViewModel.AlbumToEdit,
Artists = new SelectList(db.Artists, "ArtistId", "Name", postedViewModel.AlbumToEdit.ArtistId),
Genres = new SelectList(db.Genres, "GenreId", "Name", postedViewModel.AlbumToEdit.GenreId)
};
return View(albumToEditViewModel);
}

dropdownlistfor yields error ,{"Object reference not set to an instance of an object."} , when I click submit button

I am trying to create sub category for categories. User first select the categories from dropdownlist and then type the subcategory name and clicks submit. Even though dropdownlist elements are properly fill the dropdown list. When I click submit button It creates error. How can I solve this?
My View:
#model CETAPPSUGG.Models.CategorySubCategoryModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.selectedId, new { id = "3" });
// #Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SubCatagories</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SubCategory.SubCategoryName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SubCategory.SubCategoryName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SubCategory.SubCategoryName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
Upper cat: <div class="col-md-10">
#Html.DropDownListFor(Model => Model.Categories, Model.categoryList)
</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>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
My Controller:
public ActionResult Create()
{
var categories = db.Categories.ToList();
CategorySubCategoryModel deneme = new CategorySubCategoryModel();
var list = new List<SelectListItem>();
deneme.Categories = categories;
foreach (Categories c in categories)
{
list.Add(new SelectListItem() { Text = c.CategoryName, Value = c.Id.ToString() });
}
deneme.categoryList = list;
return View(deneme);
}
// POST: SubCatagories/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 ActionResult Create( CategorySubCategoryModel model)
{
string strDDLValue = model.selectedId;
SubCatagories newSubCategory = new SubCatagories();
Categories cat = new Categories();
cat = db.Categories.Find(Convert.ToInt32(strDDLValue));
// cat = db.Categories.Find(Convert.ToInt32(strDDLValue));
newSubCategory.SubCategoryName = model.SubCategory.SubCategoryName;
newSubCategory.UpperCategory = Convert.ToInt32(strDDLValue);
newSubCategory.Categories = cat;
db.SubCatagories.Add(newSubCategory);
db.SaveChanges();
return View();
}
My Model
namespace CETAPPSUGG.Models
{
public class CategorySubCategoryModel
{
SubCatagories SubCatagories { get; set; }
public IEnumerable<Categories> Categories { get; set; }
public IEnumerable<SubCatagories> SubCategories { get; set; }
public IEnumerable<SelectListItem> categoryList { get; set; }
public SubCatagories SubCategory { get; set; }
public string selectedId;
}
}
It creates error in view
You have a bunch of problems here.
Your primary problem is that you are not passing a model back to the View on post, thus the model is null. So, when you attempt to dereference items from the model in the View, a null reference is generated.
First, you are using selectedId but do not set this anywhere. It doesn't get set by magic. What you probably want is #Html.DropDownListFor(model => model.selectedId, Model.categoryList) (note the lowercase m in model in the first parameter, and uppercase M in the second)
Second, don't use a Model in your lambda in the DropDownListFor, use the lowercase model, because uppercase Model is reserved for the actual Model instance. If you want to reference the Model instance, then do something like DropDownListFor(_ => Model.Foo, Model.Foos). Note that I replaced the Model before the lambda with an underscore or some other value that is not Model. Frankly i'm surprised this even works, but there's probably a scoping rule here that overrides the outer Model. Avoid this because it can cause you confusion down the road.
Third, you are passing an IEnumerable to the DropDownListFor as the selected item variable, this won't work on a number of levels. This needs to be a single string value in most cases (sometimes a numerical one, but always a single more basic type that can have ToString() called on it and get a sensible string since DropDownListFor can't display complex objects).
Fourth, You also need to re-populate your DropDownListFor in the Post action, because the contents of a dropdownlist are not posted back, and thus will be null in the model. This, along with the SubCategory derefences in your view are ultimately what is generating the Null Reference exception.
You also need to pass the model back to your view in the Post, but as stated above, it needs to be re-initialized with the Categories as well as SubCategories.
There are probably more problems here, but fix these and you should be on your way.

MVC5 SPA IdentityUser add State and display it as Dropdown with static values

I am trying to add a state field to the user registration on the default MVC SPA template. I want to use a dropdown that doesn't link to a database field, or anyother crazy stuff other than having them statically/manually created as they are not going to change.
Is there any easy to implement solution or a way that I can implement this easily without altering the defaults too much?
You sound lazy ;-). This should do the job:
Add this property to the RegisterViewModel in the AccountViewModel.cs:
[Display(Name = "State")]
public string State { get; set; }
This will take take of posting the state value. Then add this to the Register.cshtml, below the confirm password field:
#{ var states = new List<SelectListItem>
{
new SelectListItem { Text = "Example 1", Value="Example1" },
new SelectListItem { Text = "Example 2", Value="Example2" }
};
}
<div class="form-group">
#Html.LabelFor(m => m.State, new {#class = "col-md-2 control-label"})
<div class="col-md-10">
#Html.DropDownListFor(m => m.State, states, "-- Select Status --", new { #class = "form-control"})
</div>
</div>
This results in:

asp.net mvc 4.5.1 set field default based on another field

I have a form which includes 2 fields....Owner_ID and AQ_Manager. I want to make the AQ_Manager field default to a name, depending on what is entered into the Owner_ID field. For example, if user inputs "Company A" into the Owner_ID field, I want to default the AQ_Manager field to "John Smith". Is there a way for me to accomplish this?
Model Info:
[DisplayName("Owner ID")]
public string Owner_ID { get; set; }
[DisplayName("AQ Manager")]
public string AQ_Manager { get; set; }
View:
<div class="form-group">
#Html.LabelFor(model => model.Owner_ID, new { #class = "control-label col-md-2 required CreateEditFieldNamesSpan" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Owner_ID (SelectList)ViewBag.VBProjectOwnerList, "--Select Owner--")
#Html.ValidationMessageFor(model => model.Owner_ID)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AQ_Manager, new { #class = "control-label col-md-2 CreateEditFieldNamesSpan" })
<div class="col-md-10">
#Html.EditorFor(model => model.AQ_Manager)
#Html.ValidationMessageFor(model => model.AQ_Manager)
</div>
</div>
From your comments, you have indicated that there are only 3 options for Owner_ID, so you could add a property to your view model to store the associated defaults (say) public List<string> DefaultManagers { get; set; } and in the GET method, initialize the collection and add the 3 values (in the same order as the 3 options)
model.DefaultManagers = new List<string>{ "John", "Bob", "Jim" };
return View(model);
and in the view, include the following script
var defaults = #Html.Raw(Json.Encode(Model.DefaultManagers))
which will create a javascript array containing the 3 values. Then handle the .change() event of your dropdownlist an update the textbox
$('#Owner_ID').change(function() {
var index = $(this).children('option:selected').index();
$('#AQ_Manager').val(defaults[index]);
});
An alternative, if the view was more complex and you need to update multiple inputs, would be to use ajax to call a controller method that returns the result(s) you need
var url = '#Url.Action("GetDefaults")';
$('#Owner_ID').change(function() {
$.getJSON(url, { id: { $(this).val() }, function(data) {
$('#AQ_Manager').val(data.DefaultManager);
$(anotherElement).val(data.SomeOtherPropery);
});
});
and the controller method would be
public JsonResult GetDefaults(string id)
{
// get the values you need based on the selected option, for example
var data = new { DefaultManager = "Jim", SomeOtherPropery = someOtherValue };
return Json(data, JsonRequestBehavior.AllowGet);
}
as per your question you are entering a value in 'Owner_Id' field. we can easily set up a default value in 'AQ_Manager' field by using java script or JQuery. The syntax changes it depends up on what type of your fields

Resources