.NET Core 3 dependency injection in custom validation [duplicate] - asp.net-core

This question already has answers here:
Inject Dependencies into Validation Attribute using ASP.NET Core's WebAPI
(4 answers)
Closed 1 year ago.
I want to make a custom validation attribute in .NET Core called CheckIfEmailExists. I want to make sure the user is not in my database already. So this is my create user view model:
public class CreateUserViewModel
{
public readonly UserManager userManager;
public CreateUserViewModel()
{
}
public ExtendedProfile ExtendedProfile { get; set; }
public User User { get; set; }
public int SchemeId { get; set; }
public SelectList Schemes { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
[CheckIfEmailExists()]
[Display(Name = "Email Address")]
public string Email { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Confirm Email Address")]
public string ConfirmEmail { get; set; }
}
Here is my custom validation:
public class CheckIfEmailExists : ValidationAttribute
{
private readonly UserManager _userManager;
public CheckIfEmailExists(UserManager userManager)
{
var _userManager = userManager;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var user = (User)validationContext.ObjectInstance;
var result = _userManager.FindByEmailAsync(user.Email).Result;
//do something
return ValidationResult.Success;
}
}
I get an error when I add my custom validation on my email property, the error is that I must pass in the usermanager object to the custom class constructor.
Why doesn't my app just inject the object itself?
Is there a way I can create a user manager object in my custom class without coupling the classes?
Should I only access my database in my controller?

The comment above from Nikki9696 helped me and now I know how to get my user manager and dbcontext in my custom validation attribute.
public class IsEmailInDatabase : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var context = (DatabaseContext)validationContext.GetService(typeof(DatabaseContext));
if (value == null)
{
return null;
}
else
{
var results = context.Users.AnyAsync(x => x.Email == value.ToString());
if (results.Result)
{
return new ValidationResult("This email address already exists in our database");
}
return null;
}
}
}

Related

How do you centralise ASP.NET Core Web API attribute validation on multiple DTOs with similar properties?

Is there a way to centralise the model validation of the same property name across multiple DTOs?
For example, if I have the following classes to be used as the request body in a Web API action.
public class RegisterRequest
{
[Required]
[EmailAddress]
public string EmailAddress { get; set; } = null!;
[Required]
[MinLength(8)]
[RegularExpression(UserSettings.PasswordRegex)]
public string Password { get; set; } = null!;
[Required]
[MaxLength(100)]
public string DisplayName { get; set; } = null!;
}
public class UserProfileRequest
{
[Required]
public int UserId { get; set; }
[Required]
[MaxLength(100)]
public string DisplayName { get; set; } = null!;
[Range(3, 3)]
public string? CCN3 { get; set; }
}
Can I centralise the attribute validation on DisplayName, duplicating the attributes goes against single responsibility principle. I believe I could achieve the centralised validation using an IFilterFactory and dropping the usage of attributes.
I opted to use a custom ActionFilterAttribute to achieve centralisation of the validation. The example below is for validating the country code (CCN3).
CountryCodeValidationAttribute.cs - custom attribute to be applied to properties (contains no logic)
[AttributeUsage(AttributeTargets.Property)]
public class CountryCodeValidationAttribute : Attribute
{
}
CountryCodeValidationActionFilter.cs - custom action filter that supports dependency injection and looks for the custom attribute on the properties. In my case I'm returning the standard invalid model bad request response.
public class CountryCodeValidationActionFilter : ActionFilterAttribute
{
private readonly ICountryService countryService;
private readonly IOptions<ApiBehaviorOptions> apiBehaviorOptions;
public CountryCodeValidationActionFilter(
ICountryService countryService,
IOptions<ApiBehaviorOptions> apiBehaviorOptions)
{
this.countryService = countryService;
this.apiBehaviorOptions = apiBehaviorOptions;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var actionArguments = context.ActionArguments;
foreach (var actionArgument in actionArguments)
{
if (actionArgument.Value == null) continue;
var propertiesWithAttributes = actionArgument.Value
.GetType()
.GetProperties()
.Where(x => x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(CountryCodeValidationAttribute)))
.ToList();
foreach (var property in propertiesWithAttributes)
{
var value = property.GetValue(actionArgument.Value)?.ToString();
if (value != null && await countryService.GetCountryAsync(value) != null) await next();
else
{
context.ModelState.AddModelError(property.Name, "Must be a valid country code");
context.Result = apiBehaviorOptions.Value.InvalidModelStateResponseFactory(context);
}
}
}
await base.OnActionExecutionAsync(context, next);
}
}
Program.cs - register the custom action filter.
builder.Services.AddMvc(options =>
{
options.Filters.Add(typeof(CountryCodeValidationActionFilter));
});
UserProfile.cs - apply the [CountryCodeValidation] attribute to the CountryCode property.
public class UserProfile
{
[Required]
[MaxLength(100)]
public string DisplayName { get; set; } = null!;
[CountryCodeValidation]
public string? CountryCode { get; set; }
}
I can take this same approach and apply it to the DisplayName property to create a centralised validation for it 👍.

How to define a System.Security.Claims.ClaimsPrincipal as a request argument in gRPC?

I would like to build a authorization service using gRPC under .Net Code. In order to do that, I need to pass a System.Security.Claims.ClaimsPrincipal object as a request argument from caller to the server so the server can use it to authorize the caller. But I don't know how to that - how can I define a .proto for a class that is a standard library. What am I supposed to do?
I'm doing the same, using protobuf-net grpc libraries.
As many of the Identity/Security classes (if you are using them) are from Microsoft, you'll need to expose their members for serialization; you can use:
RuntimeTypeModel.Default.Add(typeof(SignInResult), false).Add(
nameof(SignInResult.Succeeded),
nameof(SignInResult.IsLockedOut),
nameof(SignInResult.IsNotAllowed),
nameof(SignInResult.RequiresTwoFactor)
);
and list the members that need to be exposed over gRpc.
As for ClaimsPrincipal, specifically, that is what I'm currently trying to implement. For Claims, i'm using a surrogate class:
RuntimeTypeModel.Default.Add(typeof(Claim), true).SetSurrogate(typeof(ClaimSurrogate));
public class ClaimSurrogate
{
[DataMember, ProtoMember(1)]
public string Type { get; set; }
[DataMember, ProtoMember(2)]
public ClaimsIdentity Subject { get; set; }
[DataMember, ProtoMember(3)]
public IDictionary<string, string> Properties { get; set; }
[DataMember, ProtoMember(4)]
public string OriginalIssuer { get; set; }
[DataMember, ProtoMember(5)]
public string Issuer { get; set; }
[DataMember, ProtoMember(6)]
public string ValueType { get; set; }
[DataMember, ProtoMember(7)]
public string Value { get; set; }
public static implicit operator ClaimSurrogate(Claim claim)
{
if (claim == null)
return null;
return new ClaimSurrogate()
{
Type = claim.Type,
Subject = claim.Subject,
Properties = claim.Properties,
OriginalIssuer = claim.OriginalIssuer,
Issuer = claim.Issuer,
ValueType = claim.ValueType,
Value = claim.Value
};
}
public static implicit operator Claim(ClaimSurrogate surrogate)
{
if (surrogate == null)
return null;
return new Claim(surrogate.Type, surrogate.Value, surrogate.ValueType, surrogate.Issuer, surrogate.OriginalIssuer, surrogate.Subject);
}
}
And I'm assuming that ClaimsPrincipal can be done the same way, but, I'm having trouble with it. That is how I came across your question...
Actually, by, trying to provide an answer...Literally, I just realized what I overlooked, I need to also set up a surrogate for the ClaimsIdentity
So far, I've needed surrogates for 'third' party classes that have get; only properties. ClaimsPrincipal has these types of properties, and so does ClaimsIdentity (as does Claim). I'll update/comment if ClaimsIdentitySurrogate does the trick
Updates:
Yes, it can be done. Surrogates, like the example above, will be needed for ClaimsIdentity and IIdentity. These classes are used as members/properties within ClaimsPrincipal.
ClaimsIdentity: you can mix up the SetSurrogate and the Add(nameof(...)) as it has get onlies and get/sets (get/sets go in the Add portion). Do not include the Actor in the ClaimsIdentity surrogate as it will create a never ending loop in your service's startup. If you do include it, make sure it is not a DataMember/Protomember. And (private) set it in the surrogate operator. Same same with Claims.
Essentially, any surrogates with members that reference the parent class, or that of a another type with a surrogate that references this parent type, will create a circular reference and error out your service on startup.
IIdentity: This is a simple one, just
RuntimeTypeModel.Default.Add(typeof(IIdentity), false).
Lastly (I posted this update when I thought I had it, but, amidst all the UT tests and changes, etc, I posted a bit early; after making a breaking change on the ClaimPrincipal surrogate class)....
You'll want an IIdentity dummy class that will be used in your ClaimPrincipal surrogate, instead of the IIdentity Identity {get;set;}. This dummy class should inherit from IIdentity, e.g.
[DataContract]
public class IIdentityFraud : System.Security.Principal.IIdentity
And within your surrogate's implicit operator:
IIdentityFraud identityfraud = null;
if (claimPrincipal.Identity != null)
{
identityfraud = new IIdentityFraud(claimPrincipal.Identity.AuthenticationType, claimPrincipal.Identity.Name, claimPrincipal.Identity.IsAuthenticated);
}
Updates (11/05/2021):
[DataContract]
public class ClaimsPrincipalSurrogate
{
[DataMember, ProtoMember(1)]
public IIdentityFraud Identity { get; set; }
[DataMember, ProtoMember(2)]
public IEnumerable<ClaimsIdentity> Identities { get; set; }
[DataMember, ProtoMember(3)]
public IEnumerable<Claim> Claims { get; set; }
public static implicit operator ClaimsPrincipalSurrogate(ClaimsPrincipal claimPrincipal)
{
if (claimPrincipal == null)
{
return null;
}
else
{
IIdentityFraud identityfraud = null;
if (claimPrincipal.Identity != null)
{
identityfraud = new IIdentityFraud(claimPrincipal.Identity.AuthenticationType, claimPrincipal.Identity.Name, claimPrincipal.Identity.IsAuthenticated);
}
return new ClaimsPrincipalSurrogate()
{
Identity = identityfraud, // (System.Security.Principal.IIdentity)identityfraud,
Identities = claimPrincipal.Identities,
Claims = claimPrincipal.Claims
};
}
}
public static implicit operator ClaimsPrincipal(ClaimsPrincipalSurrogate surrogate)
{
if (surrogate == null)
return null;
if (surrogate.Identities != null && surrogate.Identities.Any() == true)
{
return new ClaimsPrincipal(surrogate.Identities);
}
else if (surrogate.Identity != null)
{
return new ClaimsPrincipal(surrogate.Identity);
}
return new ClaimsPrincipal();
}
}
[DataContract]
public class ClaimsIdentitySurrogate
{
[DataMember, ProtoMember(1)]
public string AuthenticationType { get; set; }
[DataMember, ProtoMember(2)]
public string Name { get; set; }
//[DataMember, ProtoMember(3)]
//public string Label { get; set; }
[DataMember, ProtoMember(4)]
public bool IsAuthenticated { get; set; }
[DataMember, ProtoMember(5)]
public IEnumerable<Claim> Claims { get; private set; }
//[DataMember, ProtoMember(6)]
//public object BootstrapContext { get; set; }
//[DataMember, ProtoMember(7)]
public ClaimsIdentity Actor { get; private set; }
[DataMember, ProtoMember(8)]
public string RoleClaimType { get; set; }
[DataMember, ProtoMember(9)]
public string NameClaimType { get; set; }
public static implicit operator ClaimsIdentitySurrogate(ClaimsIdentity claimIdentity)
{
if (claimIdentity == null)
return null;
return new ClaimsIdentitySurrogate()
{
AuthenticationType = claimIdentity.AuthenticationType,
Name = claimIdentity.Name,
//Label = claimIdentity.Label,
IsAuthenticated = claimIdentity.IsAuthenticated,
Claims = claimIdentity.Claims,
//BootstrapContext = claimIdentity.AuthenticationType,
Actor = claimIdentity.Actor,
RoleClaimType = claimIdentity.RoleClaimType,
NameClaimType = claimIdentity.NameClaimType
};
}
public static implicit operator ClaimsIdentity(ClaimsIdentitySurrogate surrogate)
{
if (surrogate == null)
{
return null;
}
if (surrogate.Claims?.Any() == true)
{
return new ClaimsIdentity(surrogate.Claims, surrogate.AuthenticationType);
}
else
{
return new ClaimsIdentity(surrogate.AuthenticationType, surrogate.NameClaimType, surrogate.RoleClaimType);
}
}
}
[DataContract]
public class IIdentityFraud : System.Security.Principal.IIdentity
{
[DataMember, ProtoMember(1)]
public string AuthenticationType { get; private set; }
[DataMember, ProtoMember(2)]
public string Name { get; private set; }
[DataMember, ProtoMember(3)]
public bool IsAuthenticated { get; private set; }
public IIdentityFraud() { }
public IIdentityFraud(string authenticationType, string name, bool isAuthenticated)
{
this.AuthenticationType = authenticationType;
this.Name = name;
this.IsAuthenticated = isAuthenticated;
}
}
[DataContract] //don't know if this is really needed. Too involved in testing out the rest of it and have yet to come back to this.
public class IIdentitySurrogate : System.Security.Principal.IIdentity
{
[DataMember, ProtoMember(1)]
public string AuthenticationType { get; set; }
[DataMember, ProtoMember(2)]
public string Name { get; set; }
[DataMember, ProtoMember(3)]
public bool IsAuthenticated { get; set; }
public static implicit operator IIdentitySurrogate(IIdentityFraud iidentity)
{
if (iidentity == null)
return null;
return new IIdentitySurrogate()
{
AuthenticationType = iidentity.AuthenticationType,
Name = iidentity.Name,
IsAuthenticated = iidentity.IsAuthenticated
};
}
public static implicit operator IIdentityFraud(IIdentitySurrogate surrogate)
{
if (surrogate == null)
return null;
return new IIdentityFraud(surrogate.AuthenticationType, surrogate.Name, surrogate.IsAuthenticated);
}
}
More of what is executed on startups:
#region ClaimsIdentity
RuntimeTypeModel.Default.Add(typeof(ClaimsIdentity), true).Add(
nameof(ClaimsIdentity.Label),
nameof(ClaimsIdentity.BootstrapContext),
nameof(ClaimsIdentity.Actor)
).SetSurrogate(typeof(ClaimsIdentitySurrogate));
#endregion ClaimsIdentity
#region ClaimsPrincipal
RuntimeTypeModel.Default.Add(typeof(ClaimsPrincipal), true).SetSurrogate(typeof(ClaimsPrincipalSurrogate));
#endregion ClaimsPrincipal
#region IIdentity
RuntimeTypeModel.Default.Add(typeof(IIdentity), true);
#endregion IIdentity

Extending EF Core Identity UserStore with integer user ID

Using Blazor server, dotnet 5 and Entity Framework Core, I have successfully customised Identity using integer IDs
e.g
public class ApplicationUser : IdentityUser<int>
{
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
}
I now want to extend UserStore for custom login to store password history to prevent duplication. The documented way of doing this seems to be:
public class ApplicationUserStore : UserStore<ApplicationUser>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
public override async Task<IdentityResult> CreateAsync(ApplicationUser appUser)
{
await base.CreateAsync(appUser);
await AddToUsedPasswordAsync(appUser, appUser.PasswordHash);
}
public Task AddToUsedPasswordAsync(ApplicationUser appuser, string userpassword)
{
appuser.UserUsedPassword.Add(new UsedPassword() { UserID = appuser.Id, HashPassword = userpassword });
return UpdateAsync(appuser);
}
}
This works for the default GUID Id but when using an integer it throws the error:
The type ApplicationUser cannot be used as type parameter 'TUser' in the generic type or method 'UserStore'. There is no implicit reference conversion from ApplicationUser' to 'Microsoft.AspNetCore.Identity.IdentityUser'.
What is the correct way to do this?
EDIT:
Per #Yinqiu, modified for custom ApplicationRole to
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
Builds successfully and creates users but gives run time error when trying to access ApplicationUserManager.IsInRoleAsync:
{"Cannot create a DbSet for 'IdentityUserRole' because this type is not included in the model for the context."}
However I have a custom User Role:
public class ApplicationUserRole : IdentityUserRole<int>
{
public ApplicationUserRole() : base() { }
public bool IsSystemEssential { get; set; }
}
With a definition in ApplicationDbContext :
public DbSet ApplicationUserRoles { get; set; }
SOLVED (provisionally)
After looking at the overloaded inheritance options you have to extend as follows:
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationUserToken, ApplicationRoleClaim>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
}
This seems to work. Still need to check all role and claim updates.
You can change your ApplicationUserStore as following.
public class ApplicationUserStore : UserStore<ApplicationUser,IdentityRole<int>, ApplicationDbContext,int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
//...
}

MVC Model Custom Control Attribute

I am using custom uniqueemail confirmation attribute like
public class UniqueEmailAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
WMContext db = new WMContext();
var userWithTheSameEmail = db.Users.SingleOrDefault(
u => u.email == (string)value);
return userWithTheSameEmail == null;
}
}
It is working fine while inserting user. But, when I try to update user it gives an error.
public class User
{
public int userID { get; set; }
[Required]
[Display(Name = "Name")]
public string name { get; set; }
[Required]
[Display(Name = "Email")]
[UniqueEmail(ErrorMessage = "This email is used by another user")]
public string email { get; set; }
}
Solution : I have changed the IsValid method like this :
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (User)validationContext.ObjectInstance;
WMContext db = new WMContext();
var userWithTheSameEmail = db.Users.SingleOrDefault(
u => u.email == (string)value && u.userID!=model.userID);
if (userWithTheSameEmail!=null)
{
return new ValidationResult("Bu eposta adresi kullanılıyor.");
}
return ValidationResult.Success;
}

Custom attribute to validate if mail is unique in asp.net mvc

I have the following model
public partial class OwnerData
{
public string firstname { get; set; }
public string lastname { get; set; }
[Required]
[StringLength(60)]
[EmailAddress]
[MailNotExists] //this is a custom function
public string email { get; set; }
}
the custom function MailNotExists is the following
namespace MyApp.CustomDataAnnotations
{
public class MailNotExists : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
DbContext db = new DbContext ();
var user = db.Users.FirstOrDefault(u => u.email == (string)value);
if (user == null)
return ValidationResult.Success;
else
return new ValidationResult("Mail already exists");
}
}
}
The model is used into the view to give the user the possibility to change his mail, trough
#Html.EditorForModel();
The html output returns into the editor also the already existing mail, if you want to change it the system first check if the mail is already present in the db trough the validator [MailNotExists] and everything is ok. The only problem is that if the user don't want to change the mail but only the firstname or lastname and then submit the form the validator returns an error because the mail already exists.
Any solution on how to by-pass the validator only in that particular case?
Well its a bit specific since i dont see primary key, but i would do it next way add primary key to owner data
public partial class OwnerData
{
public int Id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
[Required]
[StringLength(60)]
[EmailAddress]
[MailNotExists] //this is a custom function
public string email { get; set; }
}
And modify your attribute
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var owner = validationContext.ObjectInstance as OwnerData;
if(owner == null) return new ValidationResult("Model is empty");
DbContext db = new DbContext();
var user = db.Users.FirstOrDefault(u => u.email == (string) value && u.id != owner.Id);
if (user == null)
return ValidationResult.Success;
else
return new ValidationResult("Mail already exists");
}