How can I access FluentValidation errors from within an api post method? - .net-core

In the Fluent Validation docs
There is an example
public class PeopleController : Controller {
public ActionResult Create() {
return View();
}
[HttpPost]
public ActionResult Create(Person person) {
if(! ModelState.IsValid) { // re-render the view when validation failed.
// How do I get the Validator error messages here?
return View("Create", person);
}
TempData["notice"] = "Person successfully created";
return RedirectToAction("Index");
}
}
public class Person {
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
Where the validator has been set up as
public class PersonValidator : AbstractValidator<Person> {
public PersonValidator() {
RuleFor(x => x.Id).NotNull();
RuleFor(x => x.Name).Length(0, 10);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 60);
}
}
Suppose a validation fails. How can I access the Validation Error from within the Create method?
I ask because I am using FluentValidation with an API and need a way for the API to communicate validation errors.

Check ModelState for errors ( True / False )
<% YourModel.ModelState.IsValid %>
Check for a specific Property error
<% YourModel.ModelState["Property"].Errors %>
Check for all errors
<% YourModel.ModelState.Values.Any(x => x.Errors.Count >= 1) %>
There are a ton of good answers to this question on here.
Here is a link to one and here is another

Related

How to get class name in nested fluentvalidation validators

I am trying to implement a complex nested validator using FluentValidation in .Net Core. Suppose there's a failure in one of the nested validators and the error messages are available at validationResult.Errors. I was wondering if is there a way that I can access the name of the object that has the error.
Entities
Constituent
public class Constituent : BaseAuditedEntity
{
[Required]
[MaxLength(50)]
public string ClientProspectID { get; set; }
[InverseProperty("Client")]
public virtual ICollection<Solicit> Solicits { get; set; }
}
Solicit
public class Solicit: BaseAuditedEntity
{
[Required]
[ForeignKey("Client")]
public long ClientId { get; set; }
public StudyType StudyType { get; set; }
public virtual Constituent Client { get; set; }
}
Validators
ConstituentValidator and its nested SolicitValidator
public class ConstituentValidator : AbstractValidator<Constituent>
{
public ConstituentValidator()
{
RuleFor(x => x.ClientProspectID).NotEmpty().WithMessage("Invalid ClientProspectID");
RuleForEach(x => x.Solicits).SetValidator(new SolicitValidator());
}
class SolicitValidator : AbstractValidator<Solicit>
{
public SolicitValidator()
{
RuleFor(x => x.StudyType).NotEqual(StudyType.Invalid).WithMessage("Invalid StudyType");
}
}
}
Just to highlight the question, in the case of failing the validation rule, is there a way that I can find which main or nested validator has failed.
var validationResult = validator.Validate(item);
if (!validationResult.IsValid)
{
invalidData.AddRange(validationResult.Errors.Select(q => new DataImportValidationResult
{
DataImportId = dataImportData.Id,
ClientProspectID = item.ClientProspectID,
ValidationResult = q.ErrorMessage,
AttemptedValue = q.AttemptedValue.ToString(),
Field = q.PropertyName,
Category = "", //Here I need a way to find which object has failed.
}));
}
If it is not possible to access the failed object, is there a way that we can set a property in the validations rule that can be accessible in the validationResult errors?

Unable to validate Person<T> object using FluentValidation

I have code like this
public class Person<T>
{
public Person()
{
Result = default(T);
}
public bool IsValid { get; set; }
public T Result { get; set; }
public int StatusCode { get; set; }
}
public class PersonValidator<T> : AbstractValidator<Person<T>>
{
public PersonValidator()
{
RuleFor(r => r.StatusCode).GreaterThan(0);
}
}
in Startup.cs
mvcBuilder.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining(typeof(PersonValidator<>)));
There is no error and error count is coming zero even though rule is not satisfied.
When I remove this T from PersonValidator and update AbstractValidator<Person<T>> to AbstractValidator<Person<long>> then it works fine.
I have added validators as PersonValidator. Also registered validator as above. I have to validate generic class, please help understand what is going wrong here, and how I can fix it.

MVC 4 - Use a different model in partial view

Please bear with my noobness, I'm super new to the MVC pattern.
What I'm trying to do
I am building a profile information page for registered users on my site. This page would list data about the user, such as date of birth, telephone number, subscription status, etc.. You get the idea. I would also like to have a form to let users change their password, email address, personal information on the same page.
My problem
The user's data comes from my controller via a passed model variable:
public ActionResult Profil()
{
var model = db.Users.First(e => e.UserName == WebSecurity.CurrentUserName);
return View(model);
}
The output looks like this in my view:
<label>Phone number: </label>
#if (Model.PhoneNumber != null)
{
#Model.PhoneNumber
}
else
{
<span class="red">You haven't set up your phone number yet. </span>
}
The form in which the user could change his info would use another model, ProfileModel. So basiccaly I need to use two models in my view, one for outputting information and one for posting data. I thought that using a partial view I can achieve this, but I get this error:
The model item passed into the dictionary is of type
'Applicense.Models.User', but this dictionary requires a model item of
type 'Applicense.Models.ProfileModel'.
Here's what my call to the partial view looks like:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.Partial("_ModifyProfileInfo")
}
Here's the partial view:
#model Applicense.Models.ProfileModel
<ul>
<li>
#Html.LabelFor(m => m.Email)
#Html.EditorFor(m => m.Email)
</li>
<li>
#Html.LabelFor(m => m.ConfirmEmail)
#Html.EditorFor(m => m.ConfirmEmail)
</li>
<input type="submit" value="Update e-mail" />
</ul>
And finally here's my ProfileModel:
public class ProfileModel
{
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "New e-mail address")]
public string Email { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Confirm new e-mail address")]
[Compare("Email", ErrorMessage = "The e-mail and it's confirmation field do not match.")]
public string ConfirmEmail { get; set; }
}
Am I missing something? What's the proper way to do this?
Edit:
I remade my code reflecting Nikola Mitev's answer, but now I have another problem. Here's the error I get:
Object reference not set to an instance of an object. (#Model.UserObject.LastName)
This only occurs when I'm posting the changed e-mail address values. Here's my ViewModel (ProfileModel.cs):
public class ProfileModel
{
public User UserObject { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Új e-mail cím")]
public string Email { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Új e-mail cím megerősítése")]
[Compare("Email", ErrorMessage = "A két e-mail cím nem egyezik.")]
public string ConfirmEmail { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name= "E-mail cím")]
public string ReferEmail { get; set; }
}
Controller:
public ActionResult Profil()
{
var User = db.Users.First(e => e.UserName == WebSecurity.CurrentUserName);
var ProfileViewModel = new ProfileModel
{
UserObject = User
};
return View(ProfileViewModel);
}
And finally here's my user.cs model class:
[Table("UserProfile")]
public class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Column("UserName")]
public string UserName { get; set; }
[Column("Email")]
[Required]
public string Email { get; set; }
[Column("FirstName")]
public string FirstName { get; set; }
[Column("LastName")]
public string LastName { get; set; }
[Column("PhoneNumber")]
public string PhoneNumber { get; set; }
... You get the idea of the rest...
I'm thinking it's happening because the model is trying to put data in each required columns into the database.
Edit2:
The httppost method of my Profil action:
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult Profil(ProfileModel model)
{
if (ModelState.IsValid)
{
//insert into database
return Content("everything's good");
}
else
{
//outputs form errors
return View(model);
}
}
The best way to handle this situation is to use and pass viewModel to your Profile controller, viewModel is wrapper class for multiple objects that you want to pass to your view.
public class ProfileUserViewModel
{
public ProfileModel ProfileModelObject {get; set;}
public UserModel UserModelObject {get; set;}
}
Your controller should look like:
public ActionResult Profil()
{
var profileModel = db.Users.First(e => e.UserName == WebSecurity.CurrentUserName);
var userModel = //fetch from db.
var pmViewModel = new ProfileUserViewModel
{
ProfileModelObject = profileModel,
UserModelObject = userModel
};
return View(pmViewModel);
}
And finally your view :
#model Applicense.Models.ProfileUserViewModel
<label>Phone number: </label>
#if (Model.ProfileModelObject.PhoneNumber != null)
{
#Model.PhoneNumber
}
else
{
<span class="red">You haven't set up your phone number yet. </span>
}
There is an overload of #Html.Partial which allows you to send ViewData as defined in your controller - this is the method I generally use for partial views.
In your controller define ViewData["mypartialdata"] as ViewDataDictionary. Then in your view
#Html.Partial("_ModifyProfileInfo",ViewData["mypartialdata"])
In your [HttpPost] profil function, if modelstate.isvalid is false, you return your edit view, but you need to define your pmViewModel again , other wise your partial view will not have an object to display. Try using the following and let us know what happens
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult Profil(ProfileModel model)
{
if (ModelState.IsValid)
{
//insert into database
return Content("everything's good");
}
else
{
//outputs form errors
var pmViewModel = new ProfileUserViewModel
{
ProfileModelObject = profileModel,
UserModelObject = userModel
};
return View(model);
}
}
While I know this question has been asked longtime ago however some people might still face a similar problem. One easy solution I use to pass or have more than one view model on a page is to use a ViewBag to hold the second object and refer to it in the view. See example bellow.
In your controller do this:
Obj2 personalDets = new Obj2();
DbContext ctx = new DbContext();
var details = ctx.GetPersonalInformation;
foreach(var item in details) {
personalDets.Password = item.Password;
personalDets .EmailAddress = item.EmailAddress;
}
ViewBag.PersonalInformation = personalDets;
Then in your view those properties become readily available for you

How to validate MVC3 custom object properties

I have following code, but validation for the custom type(UserDetails) are not firing. Is there any way to overcome this problem? I know that if i define all the properties of UserDetails inside UserModel, it will work fine. but i need to reuse the UserDetails
Model,
public class UserModel
{
public string Something { get; set; }
public UserDetails User { get; set; }
}
Custom object,
public class UserDetails
{
[Required]
public string FirtstName { get; set; }
[Required]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "{0} can not be greater than {1} characters")]
public string Address { get; set; }
}
View,
#Html.ValidationSummary(true)
#Html.TextAreaFor(model => model.UserDetails.Address , new { rows = "5", cols = "20"})
#Html.ValidationMessageFor(model => model.UserDetails.Address )
....
This is just because of the ID and name of the element. for example in this case name of the FirtstName control is UserModel.FirtstName and ID is UserModel_FirtstName so client side validation will not fire in this case. if you want to add validation you have to add client validation manually. but you can validate it in the server side by using ModelState.IsValid
if (!ModelState.IsValid)
{
if( ModelState.IsValidField("UserDetails.FirstName"))
{
ModelState.AddModelError("UserDetails.FirstName", "Error in save");
}
......
}
client side validation
$("form").validate({
rules: {
"UserDetails.FirstName": { required: true }
}
});
why not create your own validation rules. you can use Ivalidatable Object. check for this link, it has nice explanationation
Asp.net Ivalidatable object implementation
Validation in asp.net mvc3
If you are talking about client side validation - make sure that your view code is placed within
#using(Html.BeginForm(...))
{
...
}
block and you have client side validation enabled with something like #{Html.EnableClientValidation(); }
I.e.
#using (Html.BeginForm())
{
#{ Html.EnableClientValidation(); }
#Html.ValidationSummary(true, "Password change was unsuccessful")
<fieldset>
<legend>Change Password Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.OldPassword)
#Html.PasswordFor(m => m.OldPassword)
#Html.ValidationMessageFor(m => m.OldPassword)
..............
As for triggering server-side validation - you should call Model.IsValid in your action
EDIT:
Just remembered something else:
Try putting [Required] attibute on the User property in UserModel

ASP.NET MVC how to achieve to use the same model with different error message

I am having this issue at the moment, I had address model (use required attribute to decorate) which can be used more than once on the same page, one is billing address and the other one is shipping address. when validation failed, I'd like to have suffix in front of my generic error message indicate which address is required e.g. "{0} - address line 1 required", either billing or shipping
Here is my model
public class AddressBaseModel
{
[Display(Name="Address line 1")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine1Required")]
public string AddressLine1 { get; set; }
[Display(Name="Address line 2")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine2Required")]
public string AddressLine2 { get; set; }
[Display(Name="Address line 3")]
public string AddressLine3 { get; set; }
[Display(Name="Address line 4")]
public string AddressLine4 { get; set; }
}
}
Here is the code segment I used in my page
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
Thanks
Personally I use the FluentValidation.NET library instead of Data Annotations as it makes things so much easier and provides a lot more power. Here's an example of how to achieve your goal using this ilbrary.
Create a new ASP.NET MVC 3 project using the default Visual Studio template
Install the FluentValidation.MVC3 NuGet package.
Add the following line to Application_Start:
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(
new AttributedValidatorFactory()
)
);
Define the following models:
public class AddressBaseModel
{
public string AddressLine1 { get; set; }
}
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public AddressBaseModel HomeAddress { get; set; }
public AddressBaseModel DeliveryAddress { get; set; }
}
And the following Validators:
public class AddressBaseModelValidator : AbstractValidator<AddressBaseModel>
{
private readonly string _addressType;
public AddressBaseModelValidator(string addressType)
{
_addressType = addressType;
RuleFor(x => x.AddressLine1)
.NotEmpty()
.WithMessage(string.Format("{0} - address line 1 required", addressType));
}
}
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.HomeAddress)
.SetValidator(new AddressBaseModelValidator("billing"));
RuleFor(x => x.DeliveryAddress)
.SetValidator(new AddressBaseModelValidator("shipping"));
}
}
Modify the HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
HomeAddress = new AddressBaseModel(),
DeliveryAddress = new AddressBaseModel()
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
And the corresponding Index.cshtml view:
#model MyViewModel
#using (Html.BeginForm())
{
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
<input type="submit" value="Register" />
}
You could create a custom attribute that does the dynamic formatting for you. You would just tag your address fields with the Address attribute like this:
[Address]
public string AddressLine1 { get; set; }
You would need to add a property in the AddressBaseModel where you tell the system what type of address this is (you would set this to "Billing" or "Shipping" when you instantiate the view model right before you pass the view model to the View in the controller get action):
public string AddressType { get; set; }
A custom attribute like this should work (I haven't tested it, I wrote it just now). This automatically gets the address type you specified when you create the model instance and formats it with the display name of the address field).
public class AddressAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "{0} - {1} required";
public AddressAttribute()
: base(DefaultErrorMessage) { }
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
if (!base.IsValid(value))
{
// get the property called "AddressType" from the model so we know if it's Billing or Shipping
var addressType = validationContext.ObjectInstance.GetType()
.GetProperty("AddressType")
.GetValue(validationContext.ObjectInstance, null);
// use the display name of the address field in the error message
return new ValidationResult(
string.Format(DefaultErrorMessage, addressType, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
This should work:
[Required(ErrorMessage = "The Address 2 is required.")]

Resources