I have an Asp.Net MVC5 project, where when using the requires attribute on the ViewModel, I cannot reach my controller in the event of an invalid ViewModel. But, it only happens on a specific screen.
I need that, even with my wrong VM, this request reaches my controller, so that another action takes place in my View (in this case, a spinner is hidden).
An example of my codes:
ViewModel:
public class ParameterizationViewModel
{
/// <summary>
/// Name of Parameterization.
/// </summary>
[Required(ErrorMessageResourceName = "LabelErrorFieldRequired", ErrorMessageResourceType = typeof(ResourcesGSC.Language))]
[Display(Name = "LabelName", ResourceType = typeof(ResourcesGSC.Language))]
public string Name { get; set; }
}
Controller:
public class ParameterizationController : BaseController
{
[HttpGet]
public ActionResult Index(string id)
{
var model = new ParameterizationViewModel();
if (String.IsNullOrEmpty(id))
{
//Code omitted
//Here, I structure my model to be a clean view
}
else
{
//Code omitted
//Here, I structure my model to be a screen filled with recovered data
}
return View(model);
}
[HttpPost]
public ActionResult Index(ParameterizationViewModel model)
{
if (!ModelState.IsValid)
{
//Here, I validate my ViewModel. I need you to get here, but it doesn't.
return View(model);
}
//Code omitted
//Here, follow the flow of persistence with WebService
}
}
View:
#model Project.Models.Parameterization.ParameterizationViewModel
#{
ViewBag.Title = ResourcesGSC.Language.LabelParameterizationMenu;
}
#using (Html.BeginForm("", "Parameterization", FormMethod.Post, new { }))
{
<div class="form-group">
<div class="row mb-3">
<div class="col-lg-6 col-md-12">
#Html.LabelFor(m => m.Name, new { })
#Html.TextBoxFor(m => m.Name, new { #class = "form-control", placeholder = "" })
#Html.ValidationMessageFor(m => m.Name, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="row mb-3">
<div class="col">
<button type="submit" class="btn btn-primary float-right">
#ResourcesGSC.Language.LabelBtnSave
</button>
</div>
</div>
}
I can't understand what happens. I have the same code in several other parts, which work perfectly.
I've searched through everything I got and I can't solve it...
Someone can help on this issue?
In addition, the validation message is displayed on the screen. But I can't get to my controller, as it happens on other screens
It appears that the client-side validation is getting in your way when you don't want it to. By default, if you use the MVC boilerplate code it will automatically setup and turn on client-side validation for your forms. This will validate things like required fields on the client-side in javascript and prevent the form from posting to the server if it doesn't pass this client-side validation.
You can read how it works here: https://www.blinkingcaret.com/2016/03/23/manually-use-mvc-client-side-validation/ (this article tell how to manually use it, but does a good job explaining how it works)
But if you want to handle all validation server-side in your controller, you can disable the client-side validation a few ways:
Remove the script references to jquery.validate.js and jquery.validate.unobtrusive.js in your bundle config
add <add key="ClientValidationEnabled" value="false"/> and <add key="UnobtrusiveJavaScriptEnabled" value="false"/> to your weeb.config under the <appSettings> node
Add HtmlHelper.ClientValidationEnabled = false; and HtmlHelper.UnobtrusiveJavaScriptEnabled = false; to individual views or actions
Don't use jQuery validator. It will check for validation error(s) and won't let the request reach your controller in case validation fails. Since you said that client side validation is happening, I can only guess that this is the case. Disable jQuery validator and even if your view model is not valid the request will reach the controller.
If you have used default template of mvc 5 project then look for it in bundle.config file. There you can comment it out.
The current project:
ASP.NET 4.5.2
MVC 5
Entity Framework 6
FluentValidation
So I have a bunch of “notes” that are structurally identical tables that are meant to be paired with individual elements across at least two pages and summarized in a third page. All elements requiring notes are a part of a single “cycle”, so elements are all fragments of the same table that the notes tables hang off of. For example, a “presentation” consists of a done (yes/no) boolean and a date in the cycle table. The presentation notes are a separate table, just for those two cycle columns, that hang off the cycle table (the notes table has a foreign key that is the cycle's primary key). Since these notes are just for the presentation, the entire notes table is called PresentationNotes. There are many other elements within the cycle that have their own Notes table, and all Notes tables throughout the entire project are structurally identical.
From this identical structure, I was able to abstract out the Model and the View such that I didn’t have to replicate distinct CRUD models and CRUD views for every single notes table. All I had to in the controller was take the model for each notes table and associate specific entries with the generic entries in the generic Notes model.
For example, here is the aforementioned Presentation model:
namespace CCS.Models {
public class CycleNotesPresentation {
[Key]
public Guid NotesId { get; set; }
[DisplayName("Cycle")]
public Guid CycleId { get; set; }
[DisplayName("Comm. Type")]
public Guid NotesStatusId { get; set; }
[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime NotesDate { get; set; }
[DisplayName("Notes")]
[DataType(DataType.MultilineText)]
public string Notes { get; set; }
#region Essentials
//Essential DB components for each and every table. Place at end.
[HiddenInput, DefaultValue(true)]
public bool Active { get; set; }
[HiddenInput, Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; }
[HiddenInput]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Recorded { get; set; }
[HiddenInput]
public DateTime Modified { get; set; }
[HiddenInput]
public string TouchedBy { get; set; }
#endregion
[ForeignKey("CycleId")]
public virtual Cycle Cycle { get; set; }
[ForeignKey("NotesStatusId")]
public virtual NotesStatus NotesStatus { get; set; }
}
}
As you can see, there is a lot here that doesn’t necessarily need to be in the abstracted model and view.
The abstracted Notes model, for the Create at least, is as such:
[Validator(typeof(CreateNotesValidator))]
public class CreateNotes {
public string NotesCategory { get; set; }
[DisplayName("Comm. Type")]
public string NotesStatusId { get; set; }
[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime NotesDate { get; set; }
[DisplayName("Notes")]
public string Notes { get; set; }
}
Of course, I have three other models: View, Edit and Delete, but let’s concentrate on just this one for now. If I can fix the Create, I can fix the Edit, which is the only other one with the drop-down menu that needs client-side validation.
Note the difference above -- the NotesStatusId field is actually a string here instead of a Guid. Well, it turns out that I am extremely limited in my client-side validation options if I use a Guid all the way through. Plus, client-side validation still wasn’t working with a Guid, so I decided to simplify the Model (and therefore the validation) by using a string instead.
So when I pull the original Presentation model, I will convert from a Guid to a string, and when I process the Notes model and dump it back into the Presentation model, I will convert the string back to a Guid. This allows me to have more client-side validation options.
My Controller for the whole process is as such:
// GET: Onboarding/CreateCycleNotesPresentation
[HttpGet]
public ActionResult CreateCycleNotesPresentation() {
var model = new CreateNotes() {
NotesCategory = "Presentation",
NotesDate = DateTime.Now
};
ViewBag.NotesStatusId = new SelectList(db.NotesStatus.Where(x => x.Active == true), "NotesStatusId", "NotesStatusName");
return PartialView("_CreateNotesPartial", model);
}
// POST: Onboarding/CreateCycleNotesPresentation
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CreateCycleNotesPresentation(CreateNotes model) {
if(ModelState.IsValid) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
CycleNotesPresentation cycleNotes = new CycleNotesPresentation();
cycleNotes.NotesId = new Guid();
cycleNotes.CycleId = id;
cycleNotes.NotesStatusId = new Guid(model.NotesStatusId);
cycleNotes.NotesDate = model.NotesDate;
cycleNotes.Notes = model.Notes;
cycleNotes.Active = true;
cycleNotes.Recorded = DateTime.UtcNow;
cycleNotes.Modified = DateTime.UtcNow;
cycleNotes.TouchedBy = User.Identity.GetFullNameLF();
db.CycleNotesPresentation.Add(cycleNotes);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
model.NotesCategory = "Presentation";
ViewBag.NotesStatusId = new SelectList(db.NotesStatus.Where(x => x.Active == true), "NotesStatusId", "NotesStatusName", model.NotesStatusId);
return PartialView("_CreateNotesPartial", model);
}
Here we get to see some juicy bits -- I have added a NotesCategory entry so that the View can be populated with the title of the element that a note is being added to. This will not be processed in the end.
I am also ending the POST with a refresh of the entire page. I have found this to be the easiest solution, as I was unable to make the JSON submission work correctly (the actual POST method never received the data, and so the submission would hang). Besides, the overall page works out better with a whole-page refresh. So let’s leave that alone, k?
Now for the most important thing: the validators for the abstracted Notes Model and View:
namespace CCS.Validators {
class NotesValidator {
}
public class CreateNotesValidator : AbstractValidator<CreateNotes> {
public CreateNotesValidator() {
RuleFor(x => x.NotesDate)
.NotEmpty().WithMessage("Please select a date that this communication occurred on.");
RuleFor(x => x.NotesStatusId)
.NotEmpty().NotNull().WithMessage("Please indicate what type of communication occurred.");
RuleFor(x => x.Notes)
.NotEmpty().WithMessage("Please submit notes of some kind.")
.Length(2, 4000).WithMessage("Please provide notes of some substantial length.");
}
}
public class EditNotesValidator : AbstractValidator<EditNotes> {
public EditNotesValidator() {
RuleFor(x => x.NotesDate)
.NotEmpty().WithMessage("Please select a date that this communication occurred on.");
RuleFor(x => x.NotesStatusId)
.NotNull().NotEmpty().NotEqual("00000000-0000-0000-0000-000000000000").Matches("^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$").WithMessage("Please indicate what type of communication occurred.");
RuleFor(x => x.Notes)
.NotEmpty().WithMessage("Please submit notes of some kind.")
.Length(2, 4000).WithMessage("Please provide notes of some substantial length.");
}
}
}
We can largely ignore the EditNotesValidator for now, as this is not what we are working on.
The view is a simple Partial for the abstracted Notes, and the form itself is about as vanilla as you can get:
#model CCS.Models.CreateNotes
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Create Note for “#Model.NotesCategory”</h3>
</div>
#using(Html.BeginForm()) {
#Html.AntiForgeryToken()
<div class="modal-body">
<fieldset>
#Html.LabelFor(m => Model.NotesDate, new { #class = "control-label" })<div class="input-group date">#Html.TextBoxFor(m => m.NotesDate, "{0:yyyy-MM-dd}", new { #class = "form-control date" })<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span></div>
#Html.ValidationMessageFor(m => m.NotesDate)
#Html.LabelFor(m => Model.NotesStatusId, new { #class = "control-label" })#Html.DropDownList("NotesStatusId", null, "« ‹ Select › »", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.NotesStatusId)
#Html.LabelFor(m => Model.Notes, new { #class = "control-label" })#Html.TextAreaFor(m => m.Notes, new { #class = "form-control required" })
#Html.ValidationMessageFor(m => m.Notes)
</fieldset>
</div>
<div class="modal-footer">
<span id="progress" class="text-center" style="display: none;">
<img src="/images/wait.gif" alt="wait" />
Wait..
</span>
<button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
<button class="btn btn-warning" data-dismiss="modal">Close</button>
</div>
}
<script>
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
$(function () {
$.fn.datepicker.defaults.format = "yyyy-mm-dd";
$(".date").datepicker();
});
</script>
So, yeah. The Date validator works exactly as expected. The Notes textarea gets validated beautifully. However the drop-down menu is completely out to lunch -- no matter what I try, be it .NotEmpty() or .NotNull() or anything else that is clearly flagged by FluentValidation as being functional on the client side, nothing works with the drop-down menu. A check of the raw HTML shows that I am getting the SelectList properly constructed:
<select id="NotesStatusId" class="form-control" name="NotesStatusId">
<option value="">« ‹ Select › »</option>
<option value="98e9f033-20df-e511-8265-14feb5fbeae8">Phone Call</option>
<option value="4899dd4d-20df-e511-8265-14feb5fbeae8">eMail</option>
<option value="8c073863-20df-e511-8265-14feb5fbeae8">Voice Mail</option>
<option value="8a13ec76-20df-e511-8265-14feb5fbeae8">Meeting</option>
</select>
And that empty value for the default « ‹ Select › » first option should mean that .NotEmpty() and .NotNull() should work perfectly. But they are not. If I erase the date (which is auto-filled with the form load, see the controller above) and leave the drop-down and textarea untouched, only the date field and the textarea get flagged -- the drop-down is not being flagged at all.
Suggestions?
Edit 1: Eheh, oops… added the wrong controller… fixed now.
Edit 2: …Bueller? …Bueller?
Edit 3: I am finding it difficult to believe that no-one else has ever had an issue doing client-side validation on drop-down menus via FluentValidation.
This is for any poor souls that come after me. To be specific, my situation involved:
Forms in a modal dialog.
The modal dialog was launched with a generic Partial that was used by many different sections of the site to populate multiple tables with identical structures. As such, a single generic Partial/Form was able to be used in many places for many different identically-structured tables
The modal dialog closed out to refresh the entire page, no JSON whatsoever.
Because this was a form in a modal dialog, and because the modal itself could not be refreshed (I do not know how), all validation had to be CLIENT SIDE. This is the most important issue I had.
Because of how the select menus were created, client side validation did not function as needed.
The way I fixed this was part serendipity, part abandoning inadvisable methods. Specifically, ViewBags. Here I learned that ViewBags make client-side validation of drop-down selects impossible when they do the job of populating the drop-down selects that need validating.
So part of the serendipitous luck was with how I was working the controller and model. Because there are multiple identically-structured Notes tables for various different parts of the site, I was able to “abstract out” an entire model, view and validation for the notes so that this abstracted collection could handle the full CRUD needs of multiple Notes tables. Code reuse, FTW!. I will also be looking to abstract out part of the controller as well, if that is possible, but that is something for another day.
So looking at the content of my original post, my model for the Create and Edit parts of the abstracted Notes had a very simple addition:
public IList<SelectListItem> NotesStatus { get; set; }
You see, the NotesStatusId is the foreign key for the NotesStatus table, which has basic comm details -- phone, eMail, meeting, voice mail and so forth. So I need to tell the model that I am going to make a list from this table.
Next is my Controller. Because I was already taking the specific Notes models and stuffing them into the abstracted Notes model, I was able to extend this to include the content of the drop-down menu instead of having it stuffed in a ViewBag. Compare what I had above for my controller with what is below:
[HttpGet]
public async Task<ActionResult> CreateProspectingNotes() {
var model = new CreateNotes() { // We just need to set up the abstracted Notes model with a create -- no populating from the db needed.
NotesCategory = "Prospecting",
NotesDate = DateTime.Now,
NotesStatus = await db.NotesStatus.Where(x => x.Active).Select(x => new SelectListItem { Text = x.NotesStatusName, Value = x.NotesStatusId.ToString() }).ToListAsync()
};
return PartialView("_CreateNotesPartial", model);
}
See how we are filling the NotesStatus part of the model with the SelectList that will end up in the view?
The edit is a little more complicated, because we not only have to bring up the abstracted Notes, but also fill it with content from the Notes table you wish to edit:
[HttpGet]
public async Task<ActionResult> EditProspectingNotes(Guid? id) {
ProspectingNotes prospectingNotes = await db.ProspectingNotes.FindAsync(id); // getting the specific ProspectingNotes table that is to be edited.
if(prospectingNotes == null) { return HttpNotFound(); }
EditNotes model = new EditNotes() { // Populating the abstracted Notes model with the specific ProspectingNotes model.
NotesCategory = "Prospecting",
NotesId = prospectingNotes.NotesId,
NotesStatusId = Convert.ToString(prospectingNotes.NotesStatusId),
NotesDate = prospectingNotes.NotesDate,
Notes = prospectingNotes.Notes,
NotesStatus = await db.NotesStatus.Where(x => x.Active).Select(x => new SelectListItem { Text = x.NotesStatusName, Value = x.NotesStatusId.ToString() }).ToListAsync()
};
return PartialView("_EditNotesPartial", model);
}
Now on to the View:
#Html.LabelFor(m => Model.NotesStatusId, new { #class = "control-label" })#Html.DropDownListFor(x => x.NotesStatusId, new SelectList(Model.NotesStatus, "Value", "text"), "« ‹ Select › »", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.NotesStatusId)
In particular, the .DropDownList() is replaced by a .DropDownListFor() because we are now solidly binding it to the x => x.NotesStatusId rather than the loosely-coupled "NotesStatusId" that calls the ViewBag, which I believe is the key to the whole client-side stuff working. With a ViewBag, you are just populating the drop-down with a list that already has the default value selected, here you are binding the default value to the list and then populating it directly from the ViewModel/Controller. Because it is strongly coupled, there is now something for client-side validation to validate.
Once I got all this done, all it took was to ensure that my Validation had only a single .NotEmpty() and not a double chaining like .NotEmpty().NotNull() which did throw an exception (double REQUIRED, apparently).
I hope this helps. If you are having issues yourself, create a post that references this one and send me a PM. I will see what I can do to help.
I am working on a multi-step form which must commit the first step to database before the other steps add up and commit finally after last step. Looking at the options I decided to use sessions(however I will be happy to use any better alternative). I managed to get this to work and later decided to use ajax for the form submission hence the requirement of partialviews. The problem is the dropdowns are returning null viewdata values - ie they are not binding. Can anyone suggest the best way to compose in the controller to make this work. It works fine if I revert to return views instead of return partialviews.
Controller
[AllowAnonymous]
public ActionResult ContactViewDetails()
{
ViewBag.CountryList = new SelectList(db.Countries, "CountryId", "CountryName");
return PartialView("ContactViewDetails");
}
model
public int CountryId{get;set;}
public virtual Country Country { get; set; }
.......and others
Default Page View
<script src="~/Scripts/jquery-2.1.3.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<div id="divContainer">
#Html.Partial("RegViewDetails")
</div>
PartialView: ContactViewDetails.cs
#{
AjaxOptions options = new AjaxOptions();
options.HttpMethod = "POST";
options.InsertionMode = InsertionMode.Replace;
options.UpdateTargetId = "divContainer";
}
#using (Ajax.BeginForm("ContactViewDetails", "OnlineApplication", options, new { #class = "form-horizontal" }))
{
#Html.DropDownListFor(x => x.CountryId, (IEnumerable<SelectListItem>)ViewBag.CountryList, new { #class = "chooseOption" })
#Html.......others
}
The ContactViewDetails page is the second step in the form succession The first step is RegViewDetails page as you can see in the Default page view. After validation RegViewDetails returns ContactViewDetails Partial
......
return PartialView("ContactViewDetails");
I have an issue using ViewBag with Html.EditorFor. I am trying to pass data from a form field named "ID" from view 'Create.chtml' back to controller. I would like to use ViewBag for this. When I tried to do as I did in the View below, it's throwing an error:
Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper' has no applicable method named 'EditorFor' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
Please let me know where I am doing it wrong.
Controller:
[HttpPost]
public ActionResult Create(TABLE_CODES dc)
{
try
{
using (var db = new InpEntities())
{
TABLE_CODES codes = new TABLE_CODES(); //TABLE_CODES has data with various columns - ID, NAME, DATE, SOURCE etc.
ViewBag.keys = codes;
db.AddToTABLE_CODES(dc);
db.SaveChanges();
}
return RedirectToAction("Index");
catch
{
return View();
}
}
View: Create.chtml
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-label">ID</div>
<div class="editor-field">
#Html.EditorFor(ViewBag.keys.ID) #****** THIS IS NOT WORKING ********#
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#Html.EditorFor requires a strongly-typed model. It cannot be used with data from the ViewBag.
If you must use ViewBag to pass data to the view, use #Html.TextFor(...) instead to manually setup the input field.
However, I recommend you use a strongly-typed model.
Edit:
Model:
public class MyModel
{
public TABLE_CODES Keys { get; set; }
}
Controller:
var model = new MyModel();
model.Keys = new TABLE_CODES();
return View(model);
View:
#model MvcApplication1.MyModel
....
#Html.EditorFor(m => m.Keys)
I would like to know how can I pass data from controller to view in mvc3 razor.
The view is in .cshtml file.
I have a variable in the controller where I am storing the data I need to push to the listbox control in the view.
var results = data.Select(item => new { id = item, label = item.Value, value = item.Key });
Doing this:
return Json(results, JsonRequestBehavior.AllowGet);
Gives me only a popup with the data which needs to be pushed to the listbox:
In the view the listbox resides in accordion control:
<div id="accordion">
#{int i=0;}
#foreach (var item in Model.Parameters)
{
<h3>#Html.LabelFor(m => item.Name, item.Prompt)</h3>
<div>
<div class="editor-field">
<select multiple id="#("Select" +item.Name)" name="#("Select" +item.Name)"></select>
</div>
</div>
i++;
}
</div>
So, I would like to know what should I do to push the items to the listbox instead of showing a popup for the control
Beginner in MVC, Thanks for your understanding.
Thanks in advance, Laziale
EDIT: Json format output
{System.Linq.Enumerable.WhereSelectListIterator<System.Collections.Generic.KeyValuePair<string,string>,<>f__AnonymousType1<System.Collections.Generic.KeyValuePair<string,string>,string,string>>}
returning JSON to your razor view is probably not the best method. I would suggest use a viewModel which is a c# class by itself.
namespace Test
{
public class ViewModel
{
public bool GivingAPresentation { get; set; }
}
}
public class MyController: Controller
{
public virtual ActionResult Index()
{
var model=new ViewModel(){GivingAPresentation =true;}
return View(model);
}
}
Your view code:
#model Test.ViewModel <!-- Note the full namespace -->
<br>GivingAPresentation: #Model.GivingAPresentation </br>
If you are forced to work with a JSON object that is returned from your action then you need to deserialize it first and then work with that object. you can read this post http://www.drowningintechnicaldebt.com/ShawnWeisfeld/archive/2010/08/22/using-c-4.0-and-dynamic-to-parse-json.aspx on how to parse JSON to a c# dynamic object.
Let me know if that helps.