使用带有自定义验证器的数据库



我希望能够创建一个自定义验证器,它将允许我连接到数据库并告诉我(例如(名称是否唯一。我曾经在EF中使用[Remote]属性,但我读到您不能将其用于Blazor。

到目前为止,我的验证代码是:

public class LandlordNameIsUniqueValidator : ValidationAttribute 
{  
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{  
//This is always null
var context = (ApplicationDbContext)validationContext.GetService(typeof(ApplicationDbContext));          
var checkName = new LandlordData(context);
var name = value.ToString();
var nameExists = checkName.CheckNameIsUnique(name);
if (!exists)
{
return null;
}
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}

我使用的代码(在应用程序的其他部分成功使用(如下,这将返回一个bool:

public class LandlordData : ILandlordData
{
private readonly ApplicationDbContext _context; 
public LandlordData(ApplicationDbContext context)
{
_context = context;
}

public bool CheckNameIsUnique(string name)
{
var exists = _context.Landlords
.AsNoTracking()
.Any(x => x.LandlordName == name);
return exists;
}
}

StartUp.cs中如下:

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
_config.GetConnectionString("DefaultConnection")),
ServiceLifetime.Transient);

我还成功注册了这项服务,并在我的Blazor页面中使用。

services.AddTransient<ILandlordData, LandlordData>();

尽管有很多尝试和不同的方法,但我无法(更有可能我不知道如何(注入DbContext,所以我可以使用LandlordData Class来检查记录。

但是我的ApplicationDbContext总是空的!

有人能建议访问我的数据库以执行自定义验证的正确方法吗。

TIA-

但是我的ApplicationDbContext总是空的!

您可以在此处参阅官方文件。已经说过ValidationContext.GetService为空。不支持在IsValid方法中注入用于验证的服务。

对于您的场景,您需要首先阅读答案,学习如何将IServiceProvider传递给ValidationContext

详细演示:

  1. 自定义DataAnnotationsValidator

    public class DIDataAnnotationsValidator: DataAnnotationsValidator
    {
    [CascadingParameter] EditContext DICurrentEditContext { get; set; }
    [Inject]
    protected IServiceProvider ServiceProvider { get; set; }
    protected override void OnInitialized()
    {
    if (DICurrentEditContext == null)
    {
    throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " +
    $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " +
    $"inside an EditForm.");
    }
    DICurrentEditContext.AddDataAnnotationsValidationWithDI(ServiceProvider);
    }
    }
    
  2. 自定义EditContextDataAnnotationsExtensions

    public static class EditContextDataAnnotationsExtensions
    {
    private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> _propertyInfoCache
    = new ConcurrentDictionary<(Type, string), PropertyInfo>();
    public static EditContext AddDataAnnotationsValidationWithDI(this EditContext editContext, IServiceProvider serviceProvider)
    {
    if (editContext == null)
    {
    throw new ArgumentNullException(nameof(editContext));
    }
    var messages = new ValidationMessageStore(editContext);
    // Perform object-level validation on request
    editContext.OnValidationRequested +=
    (sender, eventArgs) => ValidateModel((EditContext)sender, serviceProvider, messages);
    // Perform per-field validation on each field edit
    editContext.OnFieldChanged +=
    (sender, eventArgs) => ValidateField(editContext, serviceProvider, messages, eventArgs.FieldIdentifier);
    return editContext;
    }
    private static void ValidateModel(EditContext editContext, IServiceProvider serviceProvider,ValidationMessageStore messages)
    {
    var validationContext = new ValidationContext(editContext.Model, serviceProvider, null);
    var validationResults = new List<ValidationResult>();
    Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);
    // Transfer results to the ValidationMessageStore
    messages.Clear();
    foreach (var validationResult in validationResults)
    {
    foreach (var memberName in validationResult.MemberNames)
    {
    messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
    }
    }
    editContext.NotifyValidationStateChanged();
    }
    private static void ValidateField(EditContext editContext, IServiceProvider serviceProvider, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
    {
    if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
    {
    var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
    var validationContext = new ValidationContext(fieldIdentifier.Model, serviceProvider, null)
    {
    MemberName = propertyInfo.Name
    };
    var results = new List<ValidationResult>();
    Validator.TryValidateProperty(propertyValue, validationContext, results);
    messages.Clear(fieldIdentifier);
    messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));
    // We have to notify even if there were no messages before and are still no messages now,
    // because the "state" that changed might be the completion of some async validation task
    editContext.NotifyValidationStateChanged();
    }
    }
    private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, out PropertyInfo propertyInfo)
    {
    var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);
    if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo))
    {
    // DataAnnotations only validates public properties, so that's all we'll look for
    // If we can't find it, cache 'null' so we don't have to try again next time
    propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);
    // No need to lock, because it doesn't matter if we write the same value twice
    _propertyInfoCache[cacheKey] = propertyInfo;
    }
    return propertyInfo != null;
    }
    }
    
  3. DIDataAnnotationsValidator替换DataAnnotationsValidator

    <EditForm Model="@book" >
    <DIDataAnnotationsValidator />   //change here
    <ValidationSummary />
    <div class="row content">
    <div class="col-md-2"><label for="Name">Name</label></div>
    <div class="col-md-3"><InputText id="name" @bind-Value="book.UserName" /></div>
    <ValidationMessage For=" (() => book.UserName)" />
    </div>  
    <div class="row content">
    <button type="submit">Submit</button>
    </div>
    </EditForm>
    @code {
    Booking book= new Booking();
    }
    
  4. 然后您可以使用自定义的验证属性:

    public class LandlordNameIsUniqueValidator : ValidationAttribute
    {
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
    //This is always null
    var context = (LykosqlContext)validationContext.GetService(typeof(LykosqlContext));
    var checkName = new LandlordData(context);
    var name = value.ToString();
    var nameExists = checkName.CheckNameIsUnique(name);
    
    return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
    }
    }
    
  5. 型号设计:

    public class Booking
    {
    public int Id { get; set; }
    [LandlordNameIsUniqueValidator]
    public string UserName { get; set; }
    }
    
当验证上下文服务提供程序没有注册服务(DbContext(时,GetService返回null。

这里有一个自定义验证器,它使用验证器中使用的stringHelper服务。

调用验证器

using Microsoft.Extensions.DependencyInjection;

var serviceProvider = new ServiceCollection()
.AddSingleton<IStringHelper, StringHelper>()
.BuildServiceProvider();
var context = new ValidationContext(yourObjectRequiringValidation,serviceProvider,null);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(yourObjectRequiringValidation, context, results, true);

以及使用字符串助手服务的自定义验证器:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var stringValue = value?.ToString();

var stringHelper = (IStringHelper)validationContext.GetService(typeof(IStringHelper));

if (stringHelper == null)
throw new InvalidOperationException("The string helper service has not been registered in the validation context service provider and so GetService cannot find the service string helper. ");

return stringHelper.IsValidString(stringValue) ? ValidationResult.Success : new ValidationResult(this.ErrorMessageString);

}

相关内容

  • 没有找到相关文章

最新更新