Pass variable data to ValidationAttribute - entity-framework

I have a requirement to apply different min length values in a change password function to a new password being created based on a user's roles. If a user has no administrative roles min length is 12, if they have admin roles min length is 16.
The current code has no such variable requirement logic. The implementation for the new password property was like so in model class called ChangePasswordData:
///summary>
/// Gets and sets the new Password.
/// </summary>
[Display(Order = 3, Name = "NewPasswordLabel", ResourceType = typeof(UserResources))]
[Required]
[PasswordSpecialChar]
[PasswordMinLower]
[PasswordMinUpper]
[PasswordMaxLength]
[PasswordMinLength]
[PasswordMinNumber]
public string NewPassword { get; set; }
The validation attribute is set like so:
/// <summary>
/// Validates Password meets minimum length.
/// </summary>
public class PasswordMinLength : ValidationAttribute
{
public int MinLength { get; set; }
public bool IsAdmin { get; set; }
public PasswordMinLength()
{
// Set this here so we override the default from the Framework
this.ErrorMessage = ValidationErrorResources.ValidationErrorBadPasswordLength;
//Set the default Min Length
this.MinLength = 12;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null || !(value is string) || !UserRules.PasswordMinLengthRule(value.ToString(), this.MinLength))
{
return new ValidationResult(this.ErrorMessageString, new string[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
I want to be able to set the value of MinLength to 12 or 16 based on the value of IsAdmin however i cannot figure out how to decorate the attribute [PasswordMinLength(IsAdmin=myvarable)]. Only constants are allowed. How can I inject a property value into the ValidationAttribute that I can evaluate to determine the correct minimum length?
Thanks!

Thanks to Steve Greene for providing this link to an example (http://odetocode.com/blogs/scott/archive/2011/02/21/custom-data-annotation-validator-part-i-server-code.aspx), I have the answer to my validation question. Here is the updated code:
/// <summary>
/// Validates Password meets minimum length.
/// </summary>
public class PasswordMinLength : ValidationAttribute
{
public int MinLength { get; set; }
public PasswordMinLength(string IsAdminName)
{
// Set this here so we override the default from the Framework
this.ErrorMessage = ValidationErrorResources.ValidationErrorBadPasswordLength;
IsAdminPropertyName = IsAdminName;
//Set the default Min Length
this.MinLength = 12;
}
public string IsAdminPropertyName{ get; set; }
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, IsAdminPropertyName);
}
protected bool? GetIsAdmin(ValidationContext validationContext)
{
var retVal = false;
var propertyInfo = validationContext
.ObjectType
.GetProperty(IsAdminPropertyName);
if (propertyInfo != null)
{
var adminValue = propertyInfo.GetValue(
validationContext.ObjectInstance, null);
return adminValue as bool?;
}
return retVal;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (GetIsAdmin(validationContext) != null)
{
if (GetIsAdmin(validationContext) == true)
this.MinLength = 16;
else
this.MinLength = 12;
}
else
this.MinLength = 12;
if (value == null || !(value is string) || !UserRules.PasswordMinLengthRule(value.ToString(), this.MinLength))
{
return new ValidationResult(this.ErrorMessageString, new string[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
I simply added an IsAdmin property to my Model class and decorated the PasswordMinLength attribute like so:
[Display(Order = 3, Name = "NewPasswordLabel", ResourceType = typeof(UserResources))]
[Required]
[PasswordSpecialChar]
[PasswordMinLower]
[PasswordMinUpper]
[PasswordMaxLength]
[PasswordMinLength("IsAdmin")]
[PasswordMinNumber]
public string NewPassword { get; set; }
public bool IsAdmin { get; set; }
Works like a charm. Thanks Steve!

Related

How to write an audit log entry per changed property with Audit.NET EntityFramework.Core

I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event

EF6 - Get entity for DbUpdateCommandTree in DbCommandTreeInterceptor

I am trying to get the value of a "NotMapped" property for a Entity/class when intercepting a DbUpdateCommandTree.
I have looked through the various metadata, but I cannot find the "link" to the Entity from the CommandTree, so unfortunately I am stuck.
Is it even possible ?
public class SomeEntity
{
public int ID { get; set; }
[NotMapped]
public int SomeUnmappedProperty { get; set; }
}
public class CommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext ctx)
{
if (ctx.OriginalResult.DataSpace == DataSpace.SSpace)
{
var updateCommand = ctx.OriginalResult as DbUpdateCommandTree;
if (updateCommand != null)
{
// I would like to get a value of a specific property here.
// Pseudo code
var val = updateCommand.Entity.GetPropertyValue("SomeUnmappedProperty") as int;
}
}
}
}

Entity Framework: Entity with composite key as PK/FK throws exception

On escalado, throws the exception. It throws with or wihtout Include.
static void Main(string[] args)
{
try
{
using (var context = new CKContext())
{
var servReprosWithIncludes = context.ServicioRepro
.Include(p => p.Categoria)
.ToList();
var escalado = context.EscaladoPrecio
//.Include(p => p.Servicio)
.ToList();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
InvalidOperationException: The value of a property that is part of an object's key does not match the corresponding property value stored in the ObjectContext. This can occur if properties that are part of the key return inconsistent or incorrect values or if DetectChanges is not called after changes are made to a property that is part of the key.
The mapping of EscaladoPrecio:
public class EscaladoPrecioMapping : EntityTypeConfiguration<EscaladoPrecio>
{
public EscaladoPrecioMapping()
{
base.HasKey(p => new { p.Desde, p.Hasta, p.ServicioReproId });
base.HasRequired(p => p.Servicio)
.WithMany()
.HasForeignKey(p => p.ServicioReproId);
base.ToTable("PreciosServicioReprografia");
}
}
The entity ServicioRepro is a part from TPT hierarchy. Looks like:
public class ServicioRepro : Producto
{
public bool IncluirPrecioClick { get; set; }
public bool IncluirPrecioPapel { get; set; }
public bool HayPapel { get; set; }
public bool HayImpresion { get; set; }
public bool PrecioPorVolumen { get; set; }
//public virtual ICollection<EscaladoPrecio> EscaladoPrecio { get; set; }
public virtual CategoriaServicioRepro Categoria { get; set; }
public virtual ServicioReproFacturacionType ServicioReproFacturacionType { get; set; }
}
On this entity you can't see the key, because the base entity Producto have it.
The entity EscaladoPrecio have 3 PK: desde, hasta and Servicio. Servicio is PK and FK.
The entity looks like (methods, overrides and members have been removed to reduce the code):
public class EscaladoPrecio : IComparable<EscaladoPrecio>, IComparable<int>, IComparable, IEntity
{
#region Declarations
private int _desde;
private int _hasta;
private double _precio;
private int _cada;
#endregion Declarations
#region Constructor
public EscaladoPrecio()
: this(1, 1, 0, 0)
{ }
public EscaladoPrecio(int desde, int hasta, double precio)
: this(desde, hasta, precio, 0)
{ }
public EscaladoPrecio(int desde, int hasta, double precio, int cada)
{
_desde = desde;
_hasta = hasta;
_precio = precio;
_cada = cada;
}
#endregion Constructor
#region Properties
public int Desde
{
get
{
return _desde;
}
set
{
_desde = value;
}
}
public int Hasta
{
get
{
return _hasta;
}
set
{
_hasta = value;
}
}
public double Precio
{
get
{
return _precio;
}
set
{
_precio = value;
}
}
public int Cada
{
get
{
return _cada;
}
set
{
_cada = value;
}
}
#endregion Properties
private int _ServicioReproId;
public int ServicioReproId
{
get
{
if (Servicio != null)
{
_ServicioReproId = Servicio.Id;
return Servicio.Id;
}
else
return 0;
}
set
{
_ServicioReproId = value;
}
}
public virtual ServicioRepro Servicio { get; set; }
}
Why throws the exception?
Why are you doing this:
public int ServicioReproId
{
get
{
if (Servicio != null)
{
_ServicioReproId = Servicio.Id;
return Servicio.Id;
}
else
return 0;
}
set
{
_ServicioReproId = value;
}
}
Your part of the key property ServicioReproId is returning 0 here potentially although it has been loaded (and stored in the context) with a value != 0 (probably). I think this part of the exception is refering to this problem: "This can occur if properties that are part of the key return inconsistent or incorrect values."
Better leave it an automatic property:
public int ServicioReproId { get; set; }
try to initialice his virtual property in the constructor of the class EscaladoPrecio()

ASP.NET MVC 2 - Validation attribute error messages not showing up in my view when using Simon Ince's conditional validation attributes

I'm trying to use Simon Ince's conditional validation attributes for one of my view models. The logic seems to be working spot on, but the attribute's error message isn't appearing in my view's ValidationFor() methods.
The attribute:
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
The validator:
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value.Equals(Attribute.TargetValue)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
How they're hooked up (Global.asax.cs):
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));
}
}
And how I'm trying to use it:
public class AdminGameViewModel
{
public bool IsCreated { get; set; }
[Required]
public int GameID { get; set; }
[Required(ErrorMessage = "A game must have a title")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string GameTitle { get; set; }
[Required(ErrorMessage = "A short URL must be supplied")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string Slug { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A box art image must be supplied")]
public HttpPostedFileBase BoxArt { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A large image for the index page is required")]
public HttpPostedFileBase IndexImage { get; set; }
// other props of the class....
}
I don't know enough about the inner workings of MVC's validation mechanism in order to troubleshoot my problem. Any ideas?
Have you tried updating to my MVC3 implementation? It is cleaner than the hack needed with the validator in MVC2.
One thing that is missing from even the MVC3 code is the need to override FormatErrorMessage on the attribute, which will likely be similar to what you're seeing here. For the MVC 3 code I use;
public override string FormatErrorMessage(string name)
{
if (!String.IsNullOrEmpty(this.ErrorMessage))
innerAttribute.ErrorMessage = this.ErrorMessage;
return innerAttribute.FormatErrorMessage(name);
}
HTH
Simon

conditional either or validation in asp.net mvc2

In my registration page I have land line phone number and mobile number fields.
I need to ensure that the user needs to add at least one phone number either the land line or mobile.
How do I do this?
Thanks
Arnab
You could write a custom validation attribute and decorate your model with it:
[AttributeUsage(AttributeTargets.Class)]
public class AtLeastOnePhoneAttribute: ValidationAttribute
{
public override bool IsValid(object value)
{
var model = value as SomeViewModel;
if (model != null)
{
return !string.IsNullOrEmpty(model.Phone1) ||
!string.IsNullOrEmpty(model.Phone2);
}
return false;
}
}
and then:
[AtLeastOnePhone(ErrorMessage = "Please enter at least one of the two phones")]
public class SomeViewModel
{
public string Phone1 { get; set; }
public string Phone2 { get; set; }
}
For more advanced validation scenarios you may take a look at FluentValidation.NET or Foolproof.
Adding a solution that can be applied to individual properties, rather than overriding the validation method at the class level...
Create the following custom attribute. Note the "otherPropertyName" parameter in the constructor. This will allow you to pass in the other property to use in validation.
public class OneOrOtherRequiredAttribute: ValidationAttribute
{
public string OtherPropertyName { get; set; }
public OneOrOtherRequiredAttribute(string otherPropertyName)
{
OtherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherPropertyName);
var otherValue = (string)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(otherValue) && string.IsNullOrEmpty((string)value))
{
return new ValidationResult(this.ErrorMessage); //The error message passed into the Attribute's constructor
}
return null;
}
}
You can then decorate your properties like so: (be sure to pass in the name of the other property to compare with)
[OneOrOtherRequired("GroupNumber", ErrorMessage = "Either Group Number or Customer Number is required")]
public string CustomerNumber { get; set; }
[OneOrOtherRequired("CustomerNumber", ErrorMessage="Either Group Number or Customer Number is required")]
public string GroupNumber { get; set; }