I am creating an action/view in ASP.net MVC that I would like to POST using AJAX / jQuery.
I am using Fluent Validation for the validation in my view models.
Is it possible to have client side validation when I do this? What would the script look like in order to trigger this client side validation using fluent validation?
Do I create a regular form and create a submit event using jquery and call something or would I just Ajax.BeginForm() instead?
I use the jQuery unobtrusive validation with data annotations but it looks like you need the same settings as me (the first two options below) plus another step:
Enable client validation in your view or web.config
Enable unobtrusive validation in your view or in web.config
Add the FluentValidationModelValidatorProvider to the ModelValidatorProviders collection
For the first two, see Enabling Client-Side Validation. For the last one see Fluent Validation: Integration with ASP.NET MVC.
If you want to submit the form via AJAX, you can trigger the validation on the whole form with $('#form_selector').valid() or on an individual input with $('#input_selector').valid(). The calls to valid() return true if the validation is successful (and false if not).
Fluent Validation is a server-side validation library. Because of this, Fluent Validation supports some basic client-validations (like required, maxlength etc.) You can't use all server-side rules on client-side.
If you want to add fully client-side support to Fluent Validation, you need to add one more library to your project. Form Helper can be a solution for your problem.
https://github.com/sinanbozkus/FormHelper
You need to create your forms like this:
var formConfig = new FormConfig(ViewContext)
{
FormId = "ProductForm",
FormTitle = "New Product",
BeforeSubmit = "ProductFormBeforeSubmit", // optional
Callback = "ProductFormCallback" // optional,
};
// <form id="#formConfig.FormId" asp-controller="Home" asp-action="Save"
// ...
#await Html.RenderFormScript(formConfig)
After that you need to add [FormValidator] attribute to your action.
[HttpPost, FormValidator]
public IActionResult Save(FormViewModel viewModel)
Now all Fluent Validation rules work on client-side.
Related
.Net Core 3.0 MVC view. Needs to apply - Client Side validation for below model.
Tried as follow:
Model:Person
public class Person {
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
Validation Rules:
public class PersonValidator : AbstractValidator<Person> {
public PersonValidator() {
RuleFor(x => x.Id).NotNull().NotEmpty();
RuleFor(x => x.Name).Length(0, 10);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 60);
}
}
Followed documentation, it shows, "validator" attribute but I could not find in namespace.
https://docs.fluentvalidation.net/en/latest/mvc5.html
Solution 1: Use AddFluentValidationClientsideAdapters
You may use the FluentValidation.AspNetCore and register the client-side validation by adding:
services.AddFluentValidationClientsideAdapters();
Solution 2: Use FormHelper
You may also use the FormHelper and, instead of using client-side validation you could instead execute your full server-side rules via AJAX.
Details
The FluentValidation GitHub readme says:
Clientside Validation
FluentValidation is a server-library and does not provide any
client-side validation directly. However, it can provide metadata
which can be applied to the generated HTML elements for use with a
client-side framework such as jQuery Validate in the same way that
ASP.NET's default validation attributes work.
Note that not all rules defined in FluentValidation will work with
ASP.NET's client-side validation. For example, any rules defined using
a condition (with When/Unless), custom validators, or calls to Must
will not run on the client side. Nor will any rules in a RuleSet
(although this can be changed - see below). The following validators
are supported on the client:
NotNull/NotEmpty
Matches (regex)
InclusiveBetween (range)
CreditCard
Email
EqualTo (cross-property equality comparison)
MaxLength
MinLength
Length
To enable clientside integration you need to install the
FluentValidation.AspNetCore package and call the
AddFluentValidationClientsideAdapters in your application startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddFluentValidationClientsideAdapters();
services.AddScoped<IValidator<Person>, PersonValidator>();
// etc
}
Note that the AddFluentValidationClientsideAdapters method is only available in FluentValidation 11.1 and newer. In older versions,
you should use the AddFluentValidation method which enables both
auto-validation and clientside adapters. If you only want clientside
adapters and don't want auto validation in 11.0 and older, you can
configure this by calling services.AddFluentValidation(config => config.AutomaticValidationEnabled = false)
Alternatively, instead of using client-side validation you could
instead execute your full server-side rules via AJAX using a library
such as FormHelper. This allows you to use the full power of
FluentValidation, while still having a responsive user experience.
Specifying a RuleSet for client-side messages
If you're using rulesets alongside ASP.NET MVC, then you'll notice
that by default FluentValidation will only generate client-side error
messages for rules not part of any ruleset. You can instead specify
that FluentValidation should generate clientside rules from a
particular ruleset by attributing your controller action with a
RuleSetForClientSideMessagesAttribute:
[RuleSetForClientSideMessages("MyRuleset")]
public ActionResult Index(){ return View(new Person()); }
You can also use the SetRulesetForClientsideMessages extension method
within your controller action, which has the same affect:
public ActionResult Index()
{
ControllerContext.SetRulesetForClientsideMessages("MyRuleset");
return View(new Person());
}
You can force all rules to be used to generate client-side error
message by specifying a ruleset of "*".
Read more
Read more at:
GitHub
Documentation
Was able to figure it out.
this needs to be added under, Startup file, .AddMvc().AddFluentValidation()
So, it automatically able to pick the validation at client side as well as server side.
Thanks.
You need to add .AddFluentValidation() after .AddMvc() (or .AddControllersWithViews()) to enable Fluent Validation.
Fluent Validation supports some basic client-side validations like required, maxlength etc. If you want to use all server-side validations on client-side, you need to use third party libraries like FormHelper.
Form Helper helps you to create ajax forms and validations without writing any javascript code. It transforms server-side validations to client-side. It's very to use just add .AddFormHelper() and .UseFormHelper() on Startup.cs.
FormHelper: https://nuget.org/packages/FormHelper
Document: https://github.com/sinanbozkus/formhelper
Say I have a simple web form that does not utilize razor html helpers/a model.
<form id="survey-form" action="#Url.Action("Submit", "Questionnaire")" method="post">
<textarea name="1" form="survey-form">
</form>
The above form gets submitted using
$("#survey-form").submit();
Which posts to the following test method on a controller
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(true)]
public ActionResult Submit()
{
NameValueCollection nvc = Request.Form;
return View();
}
<script>alert("xss!")</script> into my text input does not get validated by the controller. Where I would normally expect the "A potentially dangerous Request.Form was detected from the client ..." exception to be thrown.
The Method does not return any exceptions or messages and is allowed to run.
The MSDN documentation says
The HttpRequest class uses input validation flags to track whether to perform validation on the request collections accessed through the Cookies, Form, and QueryString properties.
Since I am posting and processing a posted form with potentially dangerous values I would expect my input to be validated.
Is it possible for me to still utilize a simple MVC helper like the ValidateInput attribute, to validate user input without using the ASP.NET MVC conventions? (eg. creating a form using razor html helpers and a model etc.)
Edit:
As there seems to be some confusion of the values actually reaching my Action on the Controller, here is a screenshot showcasing that the values do reach the function.
I think you should include FormCollection as an argument for the Submit ActionResult.
I am pretty sure I am doing something wrong here. I have been developing a web app using MVC and Razor and I never thought of using the form element. Now so much has already been done with master pages and sub pages that it means restructuring most of our code in order to use form element and the would result in multiple form elements on a page.
That aside, in Asp.Net if I wanted to access any control in the C# code behind I could just give it an ID="SomeID" and a RUNAT="SERVER". Then in my code behind I could set its value and properties.
When I do this in Razor, I use lines like:
<input id="hiddenPostBack" runat="server" type="hidden" />
Why can't I access this in the controller? I want to detect a postback and set the value to false if it is the first time the page loads, and if not, then set the value to true. Then based on this, I will read it either server side or client side and do something.
My real question is, how do I "do something" both server side and client side given that I don't have a form element. I was under the impression that if I wanted to pass values from client to server and back, the easiest way to do this is with a hidden input. But I am just not getting how to accomplish this with MVC3 and razor.
A move from WebForms to MVC requires a complete sea-change in logic and brain processes. You're no longer interacting with the 'form' both server-side and client-side (and in fact even with WebForms you weren't interacting client-side). You've probably just mixed up a bit of thinking there, in that with WebForms and RUNAT="SERVER" you were merely interacting with the building of the Web page.
MVC is somewhat similar in that you have server-side code in constructing the model (the data you need to build what your user will see), but once you have built the HTML you need to appreciate that the link between the server and the user no longer exists. They have a page of HTML, that's it.
So the HTML you are building is read-only. You pass the model through to the Razor page, which will build HTML appropriate to that model.
If you want to have a hidden element which sets true or false depending on whether this is the first view or not you need a bool in your model, and set it to True in the Action if it's in response to a follow up. This could be done by having different actions depending on whether the request is [HttpGet] or [HttpPost] (if that's appropriate for how you set up your form: a GET request for the first visit and a POST request if submitting a form).
Alternatively the model could be set to True when it's created (which will be the first time you visit the page), but after you check the value as being True or False (since a bool defaults to False when it's instantiated). Then using:
#Html.HiddenFor(x => x.HiddenPostBack)
in your form, which will put a hidden True. When the form is posted back to your server the model will now have that value set to True.
It's hard to give much more advice than that as your question isn't specific as to why you want to do this. It's perhaps vital that you read a good book on moving to MVC from WebForms, such as Steve Sanderson's Pro ASP.NET MVC.
If you are using Razor, you cannot access the field directly, but you can manage its value.
The idea is that the first Microsoft approach drive the developers away from Web Development and make it easy for Desktop programmers (for example) to make web applications.
Meanwhile, the web developers, did not understand this tricky strange way of ASP.NET.
Actually this hidden input is rendered on client-side, and the ASP has no access to it (it never had). However, in time you will see its a piratical way and you may rely on it, when you get use with it. The web development differs from the Desktop or Mobile.
The model is your logical unit, and the hidden field (and the whole view page) is just a representative view of the data. So you can dedicate your work on the application or domain logic and the view simply just serves it to the consumer - which means you need no detailed access and "brainstorming" functionality in the view.
The controller actually does work you need for manage the hidden or general setup. The model serves specific logical unit properties and functionality and the view just renders it to the end user, simply said. Read more about MVC.
Model
public class MyClassModel
{
public int Id { get; set; }
public string Name { get; set; }
public string MyPropertyForHidden { get; set; }
}
This is the controller aciton
public ActionResult MyPageView()
{
MyClassModel model = new MyClassModel(); // Single entity, strongly-typed
// IList model = new List<MyClassModel>(); // or List, strongly-typed
// ViewBag.MyHiddenInputValue = "Something to pass"; // ...or using ViewBag
return View(model);
}
The view is below
//This will make a Model property of the View to be of MyClassModel
#model MyNamespace.Models.MyClassModel // strongly-typed view
// #model IList<MyNamespace.Models.MyClassModel> // list, strongly-typed view
// ... Some Other Code ...
#using(Html.BeginForm()) // Creates <form>
{
// Renders hidden field for your model property (strongly-typed)
// The field rendered to server your model property (Address, Phone, etc.)
Html.HiddenFor(model => Model.MyPropertyForHidden);
// For list you may use foreach on Model
// foreach(var item in Model) or foreach(MyClassModel item in Model)
}
// ... Some Other Code ...
The view with ViewBag:
// ... Some Other Code ...
#using(Html.BeginForm()) // Creates <form>
{
Html.Hidden(
"HiddenName",
ViewBag.MyHiddenInputValue,
new { #class = "hiddencss", maxlength = 255 /*, etc... */ }
);
}
// ... Some Other Code ...
We are using Html Helper to render the Hidden field or we could write it by hand - <input name=".." id=".." value="ViewBag.MyHiddenInputValue"> also.
The ViewBag is some sort of data carrier to the view. It does not restrict you with model - you can place whatever you like.
As you may have already figured, Asp.Net MVC is a different paradigm than Asp.Net (webforms). Accessing form elements between the server and client take a different approach in Asp.Net MVC.
You can google more reading material on this on the web. For now, I would suggest using Ajax to get or post data to the server. You can still employ input type="hidden", but initialize it with a value from the ViewData or for Razor, ViewBag.
For example, your controller may look like this:
public ActionResult Index()
{
ViewBag.MyInitialValue = true;
return View();
}
In your view, you can have an input elemet that is initialized by the value in your ViewBag:
<input type="hidden" name="myHiddenInput" id="myHiddenInput" value="#ViewBag.MyInitialValue" />
Then you can pass data between the client and server via ajax. For example, using jQuery:
$.get('GetMyNewValue?oldValue=' + $('#myHiddenInput').val(), function (e) {
// blah
});
You can alternatively use $.ajax, $.getJSON, $.post depending on your requirement.
First of all ASP.NET MVC does not work the same way WebForms does. You don't have the whole runat="server" thing. MVC does not offer the abstraction layer that WebForms offered. Probabaly you should try to understand what controllers and actions are and then you should look at model binding. Any beginner level tutorial about MVC shows how you can pass data between the client and the server.
You are doing it wrong since you try to map WebForms in the MVC application.
There are no server side controlls in MVC. Only the View and the
Controller on the back-end. You send the data from server to the client by
means of initialization of the View with your model.
This is happening on the HTTP GET request to your resource.
[HttpGet]
public ActionResult Home()
{
var model = new HomeModel { Greeatings = "Hi" };
return View(model);
}
You send data from client to server by means of posting data to
server. To make that happen, you create a form inside your view and
[HttpPost] handler in your controller.
// View
#using (Html.BeginForm()) {
#Html.TextBoxFor(m => m.Name)
#Html.TextBoxFor(m => m.Password)
}
// Controller
[HttpPost]
public ActionResult Home(LoginModel model)
{
// do auth.. and stuff
return Redirect();
}
What's the recommended way to do client-side validation using the built-in MVC2 code with MvcContrib's FluentHtml builders? We're using the jQuery client-side validation code, not the default Microsoft AJAX stuff, if that matters (though I don't think it should).
It seems the client-side validation only gets registered with jQuery Validate when you place a validation message (Html.ValidationMessageFor(x => x.FirstName)) on the page. MvcContrib's FluentHtml this.ValidationMessage(x => x.FirstName) only works with ModelState on the server side, doesn't write out any HTML if there's no error, and doesn't register the given property with jQuery Validate on the client-side.
So my question: is there a way to make the current trunk build of MvContrib work with MVC2's built-in client-side validation somewhat painlessly right now? If so, how? If not, is there another client-side validation that's recommended (other than xVal, which we're currently using and has been depreciated)? Should this be patched in MvcContrib so it works properly? A last resort would be to move to using ASP.NET MVC's built-in input builders, but we already invested a lot in MvcContrib's and would rather not.
Thanks!
Im in the exact same situation...i came across this post with in interesting comment further down although I couldn't quite get it to work.
http://lunaverse.wordpress.com/2008/11/24/mvcfluenthtml-fluent-html-interface-for-ms-mvc/
If you can make any sense of it would be good to post it back up here.
Paul
I got the comment from that blog article working Paul, and modified it to use all the known MVC validation adapters instead of just the Required one (basically mimicking much of what's in the framework itself). It gets kind of hairy with how it displays the error message and working with what we already have, and I implemented a patch for MVC Contrib to work with it, but in the end I'm giving up for now until MVC3 is finialized and MVC Contrib builds against it. No point in going through all this when there's an updated release coming soon.
Here's what I ended up with (FluentViewPage<T> is where we add behaviors):
public class ClientsideValidationBehavior<T> : IBehavior<IMemberElement> where T : class
{
private readonly FluentViewPage<T> _viewPage;
public ClientsideValidationBehavior(FluentViewPage<T> viewPage)
{
_viewPage = viewPage;
}
public void Execute(IMemberElement element)
{
var attribute = element.GetAttribute<ValidationAttribute>();
if (attribute == null)
{
return;
}
var formContext = _viewPage.ViewContext.FormContext;
var fieldMetadata = formContext.GetValidationMetadataForField(UiNameHelper.BuildNameFrom(element.ForMember), true);
var modelMetadata = ModelMetadata.FromStringExpression(element.ForMember.Member.Name, _viewPage.ViewData);
var validators = ModelValidatorProviders.Providers.GetValidators(modelMetadata, _viewPage.ViewContext);
validators.SelectMany(v => v.GetClientValidationRules()).ForEach(fieldMetadata.ValidationRules.Add);
fieldMetadata.ValidationMessageId = element.ForMember.Member.Name + "_Label";
}
}
Hope that helps some.
I have 2 actions, one for GET and the other handles POST requests.
Although I have validation on the client side, there are some things I check for on the server-side also (in my action method).
In the POST action, when I find errors etc. that I want to report back to the UI, what options do I have to send back messages/errors to the client side?
Note:
I am using the popular jQuery validation plugin als.
I you are using Model binding from your View to your Post Action, it has built in validation that can be used for this.
Some people however prefer to do validation outside of the Model. To return these errors back to the View, you need to update the ModelState, then do the normal check if it is valid (contains errors):
public Dictionary<string, string> ValidateEntry(Object obj)
{
var errors = new Dictionary<string, string>();
if (string.IsNullOrEmpty(obj.Owner))
{
errors.Add("Owner", "You must select Owned By.");
}
//... whatever else
return errors;
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Object cap)
{
var errors = ValidateEntry(cap);
if (errors.Count > 0)
{
foreach (var err in errors)
{
ModelState.AddModelError(err.Key, err.Value);
}
}
if (ModelState.IsValid)
{
//... do if valid
}
return View(ViewModel);
}
Some rules I go by when implementing validation in web applications/sites:
The validation should occur client side and server side
Server side validation should mirror the client side validation for users with scripting turned off
Even though the validation rules are enforced in two places the logic should not be duplicated
Going by those rules I use the following solution:
xVal (found from ScottGu's blog)
IDataError solution from Adam
Schroder
Business rules are implemented in the model with DataAnnotations
The service may throw custom exceptions for unique business constraints
Its amazing how easy this is to use and how well it works. It uses the JQuery validation plugin transparently and just does what it is supposed to with minimal coding.
Client side validation in your view consists of:
<%= Html.ClientSideValidation<Foo>() %>
Server side validation in your action consists of
ModelState.IsValid