How to access property of parent in validation attribute - validation

In my code below I want to check with AttributeValidation if a field is given dependent on a property of its parent element. The comment in the class
RequiredIfParentState1
describes my question best.
public class ChildModel()
{
[RequiredIfParentState1]
public string ImRequired { get; set; }
}
public class ParentViewModel()
{
public int state { get; set; }
public ChildModel child = new ChildModel();
}
public class RequiredIfParentState1: ValidationAttribute, IClientModelValidator
{
RequiredIfParentState1()
{
}
void AddValidation(ClientModelValidationContext context)
{
}
protected override ValidationResult IsValid(object i_value, ValidationContext i_context)
{
var element = i_context.ObjectInstance;
if(i_value == null && //what do i have to put here to check if the state is 1?)
{
return new ValidationResult($"Field is Required in state 1.");
}
return ValidationResult.Success;
}
}

I feel this is the wrong approach.
An object being in a valid state is one thing (required fields and type checking), but handling business logic is a separate concern.
You could write a validation service, that examines the model in detail, checking business logic concerns, and build up a list of errors.
Where errors are found you can return these in your response.

Related

Custom remote validations for complex models in blazor?

I am currently using <ObjectGraphDataAnnotationsValidator/> to validate complex models.
So far so good, except that there is also a requirement to check against the database to see if a record with the same value already exists.
I have tried implementing the <CustomValidator/> as per advised in https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-5.0#validator-components
However, it seems to only work for the top level properties.
And the <ObjectGraphDataAnnotationsValidator/> does not work with remote validations (or does it!?)
So say that I have:
*Parent.cs*
public int ID {get;set;}
public List<Child> Children {get;set;}
*Child.cs*
public int ID {get;set;}
public int ParentID {get;set}
public string Code {get;set;}
<EditForm Model="#Parent">
.
.
.
Child.Code has a unique constraint in the database.
I want to warn users "This 'Code' already exists! Please try entering a different value.", so that no exceptions will be thrown.
For now, I am a bit lost as to where my next step is.
In the past with asp.net core mvc, I could achieve this using remote validations.
Is there an equivalent to remote validations in blazor?
If not, what should I do to achieve the same result, to remotely validate the sub properties for complex models?
Any advises would be appreciated. Thanks!
[Updated after #rdmptn's suggestion 2021/01/24]
ValidationMessageStore.Add() accepts the struct FieldIdentifier, meaning that I can simply add a overload of the CustomValidator.DisplayErrors to make it work:
public void DisplayErrors(Dictionary<FieldIdentifier, List<string>> errors)
{
foreach (var err in errors)
{
messageStore.Add(err.Key, err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
Full example below:
#using Microsoft.AspNetCore.Components.Forms
#using System.ComponentModel.DataAnnotations
#using System.Collections.Generic
<EditForm Model="parent" OnSubmit="Submit">
<ObjectGraphDataAnnotationsValidator></ObjectGraphDataAnnotationsValidator>
<CustomValidator #ref="customValidator"></CustomValidator>
<ValidationSummary></ValidationSummary>
#if (parent.Children != null)
{
#foreach (var item in parent.Children)
{
<div class="form-group">
<label>Summary</label>
<InputText #bind-Value="item.Code" class="form-control"></InputText>
</div>
}
}
<input type="submit" value="Submit" class="form-control"/>
</EditForm>
#code{
private CustomValidator customValidator;
private Parent parent;
public class Parent
{
public int Id { get; set; }
[ValidateComplexType]
public List<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Code { get; set; }
}
protected override void OnInitialized()
{
parent = new Parent()
{
Id = 1,
Children = new List<Child>()
{
new Child()
{
Id = 1,
ParentId = 1,
Code = "A"
},
new Child()
{
Id = 1,
ParentId = 1,
Code = "B"
}
}
};
}
public void Submit()
{
customValidator.ClearErrors();
var errors = new Dictionary<FieldIdentifier, List<string>>();
//In real operations, set this when you get data from your db
List<string> existingCodes = new List<string>()
{
"A"
};
foreach (var child in parent.Children)
{
if (existingCodes.Contains(child.Code))
{
FieldIdentifier fid = new FieldIdentifier(model: child, fieldName: nameof(Child.Code));
List<string> msgs = new List<string>() { "This code already exists." };
errors.Add(fid, msgs);
}
}
if (errors.Count() > 0)
{
customValidator.DisplayErrors(errors);
}
}
}
The [Remote] validation attribute is tied to MVC and is not usable for Blazor.
ObjectGraphDataAnnotationsValidator is not enough. In addition, each property, that represents an object with possible validation needs to be decorated with a [ValidateComplexType] attribute.
In your CustomValidatior, you can see DI to get your API service to call your API and validate your constraint.
public class Parent
{
...other properties...
[ValidateComplexType]
public List<Child> Children {get; set; }
}
public class Child
{
...other properties...
[Required]
[IsUnique(ErrorMessage = "This 'Code' already exists! Please try entering a different value.")]
public String Code {get; set;}
}
public class IsUniqueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var service = (IYourApiService)validationContext.GetService(typeof(IYourApiService));
//unfortunately, no await is possible inside the validation
Boolean exists = service.IsUnique((String)value);
if(exists == false)
{
return ValidationResult.Success;
}
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
You might want to check out FluentValidation as this library provide features for asynchronous validation. I'm not sure if this validator can be used inside Blazor WASM.

Retrieve a complex object from ActionParameters

I am working on an MVC project where controller actions deal with Assets. Different controllers take in the assetId parameter in different way: Some controllers simply get int assetId, other int id, and other using a complex object AssetDTO dto (which contains a property that holds the assetId)
I am writing an ActionFilter that is added to the action method and is provided with the actionParameter name where I can get the asset value.
Action Method:
[AssetIdFilter("assetId")]
public ActionResult Index(int assetId)
{
...
}
The attribute is defined as:
public class AssetIdFilterAttribute : ActionFilterAttribute
{
public string _assetIdParameterKey { get; set; }
public AssetIdFilterAttribute (string assetIdParameterKey)
{
_assetIdParameterKey = assetIdParameterKey;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
int assetId;
if (Int32.TryParse(filterContext.ActionParameters[_assetIdParameterKey].ToString(), out assetId))
{
......
}
}
This works as expected, but will only work when the assetId is provided as a primitive. I am not sure what to do when the assetId is provided within a complex object into the action method.
Will I need to parse each object differently depending on the type? I am hoping I can specify some kind of dot-notation in the AssetIdFilter to tell it where the assetId is located: dto.assetId
Any way I can use dynamics? or reflection?? ect.???
and here dynamic comes to the rescue.you can change the actionFilterAttribute to be :
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
dynamic assetIdHolder = filterContext.ActionParameters[_assetIdParameterKey];
if (assetIdHolder.GetType().IsPrimitive)
{
//do whatever with assetIdHolder
}
else
{
//do whatever with assetIdHolder.assetId
}
}
cheers!
Well, yes, you answered your question. One way would be to use dot notation:
//simple case:
[AssetId("id")]
public ActionResult Index(string id) {
//code here
}
//complex case:
[AssetId("idObj", AssetIdProperty = "SubObj.id")]
public ActionResult index(IdObject idObj) {
//code here
}
And AssetIdAttribute is as follows:
public class AssetIdAttribute : ActionFilterAttribute
{
public string _assetIdParameterKey { get; set; }
public string AssetIdProperty { get; set; }
public AssetIdFilterAttribute(string assetIdParameterKey)
{
_assetIdParameterKey = assetIdParameterKey;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
int assetId;
var param = filterContext.ActionParameters[_assetIdParameterKey];
int.TryParse(GetPropertyValue(param, this.AssetIdProperty).ToString(), out assetId);
//you code continues here.
}
private static string GetPropertyValue(object souce, string property)
{
var propNames = string.IsNullOrWhiteSpace(property) || !property.Contains('.') ? new string[] { } : property.Split('.');
var result = souce;
foreach (var prop in propNames)
{
result = result.GetType().GetProperty(prop).GetValue(result);
}
return result.ToString();
}
}
The code does not have null checks when calling ToString and when calling GetProperty though. Also, it does not check the success of TryParse. Please apply these corrections when used.
Maybe this code could be written using dynamic, but at the end dynamic usage is compiled into object using reflection (something like what I have done here), thus no big difference to me.
Also, maybe it would be more clear to have a parameter like "idObj.SubObj.id", but that again depends on the preference, and the code will become a little bit more complex.

ASP.NET MVC Require positive number as input, save as negative in model - validation fails

I want to model an 'Expense' object that has a 'Sum' (decimal) field.
In the view, I want to validate that the user enters a positive value.
OTOH I want to make sure I save the object with a negative value in the DB.
Right now, the model looks like this:
//------The model-------
public class Operation {
[Range(typeof(decimal), "0.0001", "79228162514264337593543950335")]
public virtual decimal Sum { get; set; }
[...]
}
public class Expense : Operation
{
public override decimal Sum
{
get
{
return base.Sum;
}
set
{
base.Sum = - Math.Abs(value);
}
}
}
//------In the controller-------
[HttpPost]
public ActionResult CreateExpense(Expense operation, int[] SelectedTags)
{
return CreatePost(operation, SelectedTags);
}
private ActionResult CreatePost(Operation operation, int[] SelectedTags)
{
if (ModelState.IsValid) // <-- this fails
[...]
}
The problem is, the MVC validation works with the object's properties (not the POST'ed form values), sees the negative value and fails to validate.
What should I do to fix this?
It looks to me like I'm not separating concerns (validate user input vs maintain database integrity).
Should I use a view model to hold the user input and then populate the actual model from the view model? Doesn't sound like KISS...
I found out that specifying a separate validation attribute on the property of the inherited class works a treat.
Can't think of something more straight-forward.
Here's how the model looks like now:
public class Operation {
public virtual decimal Sum { get; set; }
}
public class Income : Operation
{
[Range(typeof(decimal), "0.0001", "79228162514264337593543950335")]
public override decimal Sum
{
get { return base.Sum; }
set { base.Sum = Math.Abs(value); }
}
}
public class Expense : Operation
{
[Range(typeof(decimal), "-79228162514264337593543950335", "-0.0001")]
public override decimal Sum
{
get { return base.Sum; }
set { base.Sum = - Math.Abs(value); }
}
}
A validation attribute to check if the value is less than zero is another simple solution.
public class PositiveNumberAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object number, ValidationContext validationContext)
{
return int.Parse(number.ToString()) >= 0
? ValidationResult.Success : new ValidationResult("Positive value required.");
}
}
Then apply to property
[PositiveNumber]
public virtual decimal Sum { get; set; }

Entity framework: ObjectContext and inheritance

I need to have a CRUd operations on my class (CompetenceSpecific).
Competence has three derived classes - CompetenceFunction, CompetenceArea and CompetenceSpecifc
The error I recieved:
There are no EntitySets defined for the specified entity type 'CompetencyManagement.Domain.Entities.CompetenceFunction'. If 'CompetencyManagement.Domain.Entities.CompetenceFunction' is a derived type, use the base type instead. Parameter name: TEntity
How should I correct this? Please suggest a solution that would solve my problem. Thanks
Please check the code below, I removed some parts of the code for simplicity.
--MODEL
public class Competence
{
public int CompetenceID { get; set; }
public int CourseID { get; set; }
...
}
public class CompetenceFunction : Competence
{
}
--REPOSITORY and interfaces
public interface IRepository<T> where T : class
{
T GetById(object id);
IEnumerable<T> GetAll();
IEnumerable<T> Query(Expression<Func<T, bool>> filter);
void Add(T entity);
void Remove(T entity);
}
public abstract class Repository<T> : IRepository<T>
where T : class
{
protected IObjectSet<T> _objectSet;
public Repository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
...
}
public class CompetenceFunctionRepository : Repository<CompetenceFunction>
{
public CompetenceFunctionRepository(ObjectContext context)
: base(context)
{
}
public override CompetenceFunction GetById(object id)
{
return _objectSet.SingleOrDefault(s => s.CompetenceID == (int)id);
}
}
--UNIT oF WORK
public interface IUnitOfWork
{
IRepository<CompetenceFunction> CompetenceFunctions { get; }
IRepository<CompetenceArea> CompetenceAreas { get; }
IRepository<CompetenceSpecific> CompetenceSpecifics { get; }
void Commit();
}
public class UnitOfWork : IUnitOfWork, IDisposable
{
private CompetenceFunctionRepository _competencefunction;
private CompetenceAreaRepository _competencearea;
private CompetenceSpecificRepository _competencespecifc;
public UnitOfWork(ObjectContext context)
{
if (context == null)
{
throw new ArgumentNullException("Context was not supplied");
}
_context = context;
}
#region IUnitOfWork Members
public IRepository<CompetenceFunction> CompetenceFunctions
{
get
{
if (_competencefunction == null)
{
_competencefunction = new CompetenceFunctionRepository(_context);
}
return _competencefunction;
}
}
public IRepository<CompetenceArea> CompetenceAreas
{
get
{
if (_competencearea == null)
{
_competencearea = new CompetenceAreaRepository(_context);
}
return _competencearea;
}
}
public IRepository<CompetenceSpecific> CompetenceSpecifics
{
get
{
if (_competencespecifc == null)
{
_competencespecifc = new CompetenceSpecificRepository(_context);
}
return _competencespecifc;
}
}
--Im getting an error in this part of Repository
public Repository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
There are no EntitySets defined for the specified entity type 'CompetencyManagement.Domain.Entities.CompetenceFunction'. If 'CompetencyManagement.Domain.Entities.CompetenceFunction' is a derived type, use the base type instead. Parameter name: TEntity
Here's how I implement in the controller
private IUnitOfWork _unitOfWork;
var a = _unitOfWork.CompetenceFunctions.GetAll();
return View(a);
You have to get derived type by the OfType function, e.g.
context.CreateObjectSet<Competence>().OfType<CompetenceFunction>()
In your case that would mean that there is only a CompetenceRepository that serves all derivatives of Competence.
Edit
(After your comment)
First, UoW is meant for temporarily storing changes that should be dealt with in one batch (like changes to be committed to the database). GetAll and similar functions are repository stuff.
But do you need repositories? I like this post. When beginning to know EF, I would focus on the ins and outs of EF without getting distracted too much by surrounding architecture. E.g. start with services that at the inside communicate directly with the context and expose methods like GetCompetenceFunctions, GetCompetenceAreas (using OfType), and SaveCompetenceFunction, ....
You can address these service methods directly from action methods in the MVC controllers.

Implementing Unique Contraint with ValidateEntity gives "The given key was not present in the dictionary" error

While in search of trying to implement unique key validations for my db using EF CodeFirst/Mvc3 I came upon this post http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx which gave an example on how to do it by using IValidateObject for my object model:
public class Category : IValidatableObject
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var testContext = (TestContext)validationContext.Items["Context"];
if (testContext.Categories.Any(
c => c.CategoryName == CategoryName && c.CategoryID != CategoryID))
{
yield return new ValidationResult("A category with the same name already exists!", new[] { "CategoryName" });
}
yield break;
}
}
and overriding DbEntityValidationResult ValidateEntity:
public class TestContext : DbContext
{
public DbSet<Test.Models.Category> Categories { get; set; }
protected override DbEntityValidationResult ValidateEntity( DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var myItems = new Dictionary<object, object>();
myItems.Add("Context", this);
return base.ValidateEntity(entityEntry, myItems);
}
}
And the action on the controller
[HttpPost]
public ActionResult Create(Category category)
{
if (ModelState.IsValid) {
categoryRepository.InsertOrUpdate(category);
categoryRepository.Save();
return RedirectToAction("Index");
} else {
return View();
}
}
But I get the error: "The given key was not present in the dictionary." for the line
var testContext = (TestContext)validationContext.Items["Context"];
It seems like Validate on the object is getting called which accesses "Context" before its set in the override ValidateEntity code.
At first I thought it could have been ModelState.Isvalid triggering validate too early but it wasn't.
Anyone know what I'm missing here or what I'm doing wrong? Thanks in advance.
Model.IsValid definitely triggers it too early and perhaps something else. IValidatableObject is global interface used by both MVC and EF but your method in DbContext is called only when you call SaveChanges on the context so any usage of IValidatableObject prior to calling SaveChanges will result in the exception. You must use another approach if you want to validate your entity this way. For example store context in HttpContext.Items - you can create custom action filter and instantiate and store the context before the operation call and dispose it after operation call - hopefully it will cover all problems.
I was facing the same problem... Then after a lot of Googling I finally found this:
Exercise 3: Using IValidatableObject Custom Validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
MusicStoreEntities storeDB = new MusicStoreEntities();
if (storeDB.Albums.Any(
a => a.Title.Trim().ToUpper() == this.Title.Trim().ToUpper() &&
a.ArtistId == (int)this.ArtistId))
{
yield return new ValidationResult("Existing Album", new string[] { "Title" });
}
}
As you see in their example, they instantiate a new Context and as such there's no need for validationContext.Items["Context"];. Doing so we won't get this error anymore.

Resources