[英]Inject concrete class into Generic c# .net core
我有一個我創建的通用類:
public class GenericCreate<T> : IRequest<Attempt<T>> where T: class
{
public T Model { get; }
public GenericCreate(T model) => Model = model;
}
public class GenericCreateHandler<T> : IRequestHandler<GenericCreate<T>, Attempt<T>> where T : class
{
private readonly NotNullValidator<T> _validator;
private readonly DatabaseContext _databaseContext;
public GenericCreateHandler(NotNullValidator<T> validator, DatabaseContext databaseContext)
{
_validator = validator;
_databaseContext = databaseContext;
}
public async Task<Attempt<T>> Handle(GenericCreate<T> request, CancellationToken cancellationToken)
{
var generic = request.Model;
var validationAttempt = _validator.Validate(generic).ToAttempt();
if (validationAttempt.Failure) return validationAttempt.Error;
_databaseContext.Add(generic);
await _databaseContext.SaveChangesAsync(cancellationToken);
return generic;
}
}
就類的功能而言,它是有效的。 但是,我正在嘗試根據T
的類型注入不同的驗證器。 您可以看到我正在嘗試注入NotNullValidator<T>
本身就是一個類:
public class NotNullValidator<T> : AbstractValidator<T>
{
protected override bool PreValidate(ValidationContext<T> context, ValidationResult result)
{
if (context.InstanceToValidate != null) return true;
result.Errors.Add(new ValidationFailure("", "Please ensure a model was supplied."));
return false;
}
}
但我想注入的是:
public class CategoryValidator: NotNullValidator<Category>
{
public CategoryValidator()
{
RuleFor(m => m.Id).NotEmpty();
RuleFor(m => m.Name).NotEmpty();
}
}
可以想象,我為項目中的每個實體都有一個驗證器類,因此能夠獲得正確的驗證器很重要。 有誰知道我如何使用 .net core 做到這一點?
正如所承諾的,第一個基於約定的示例:
// List all classes containing validators in their name, you might want to do this at startup to avoid the overhead each time you need to create a class
// You might also want to change which assembly you are searching in.
var validators = Assembly.GetExecutingAssembly().DefinedTypes.Where(t => t.Name.Contains("Validator")).ToList();
// You'll be able to get that from your type parameter T (i.e typeof(T))
var type = typeof(MyClass);
//This is your convention => <ClassName>Validator
var validatorToUse = validators.Single(v => v.Name == $"{type.Name}Validator");
//Instantiate your validator (you should cast to your abstract type to use it more easily)
var obj = Activator.CreateInstance(validatorToUse);
如果需要,其他實例化驗證器的方法: https : //stackoverflow.com/a/981344/2245256
只是為了澄清,以防萬一其他人有問題。 我使用 Sidewinders 回答並創建了這個擴展方法:
public static class ValidatorExtensions
{
public static NotNullValidator<T> GetValidator<T>()
{
// List all classes containing validators in their name, you might want to do this at startup to avoid the overhead each time you need to create a class
// You might also want to change which assembly you are searching in.
var validators = Assembly.GetExecutingAssembly().DefinedTypes.Where(t => t.Name.Contains("Validator")).ToList();
// You'll be able to get that from your type parameter T (i.e typeof(T))
var type = typeof(T);
//This is your convention => <ClassName>Validator
var validatorToUse = validators.Single(v => v.Name == $"{type.Name}Validator");
//Instantiate your validator (you should cast to your abstract type to use it more easily)
return (NotNullValidator<T>) Activator.CreateInstance(validatorToUse);
}
}
然后我就可以像這樣更新我的通用類:
public class GenericCreate<T> : IRequest<Attempt<T>> where T: class
{
public T Model { get; }
public GenericCreate(T model) => Model = model;
}
public class GenericCreateHandler<T> : IRequestHandler<GenericCreate<T>, Attempt<T>> where T : class
{
private readonly NotNullValidator<T> _validator;
private readonly DatabaseContext _databaseContext;
public GenericCreateHandler(DatabaseContext databaseContext)
{
_validator = ValidatorExtensions.GetValidator<T>();
_databaseContext = databaseContext;
}
public async Task<Attempt<T>> Handle(GenericCreate<T> request, CancellationToken cancellationToken)
{
var generic = request.Model;
var validationAttempt = _validator.Validate(generic).ToAttempt();
if (validationAttempt.Failure) return validationAttempt.Error;
_databaseContext.Add(generic);
await _databaseContext.SaveChangesAsync(cancellationToken);
return generic;
}
}
注意構造函數:
_validator = ValidatorExtensions.GetValidator<T>();
這使我的測試可以毫無問題地通過:
[TestFixture]
public class GenericCreateShould
{
[Test]
public async Task ThrowValidationErrorWhenGenericIsInvalid()
{
// Assemble
var services = GenericCreateContext<Venue>.GivenServices();
var handler = services.WhenCreateHandler();
// Act
var response = await handler.Handle(new GenericCreate<Venue>(new Venue()), CancellationToken.None);
// Assert
response.Success.Should().BeFalse();
response.Result.Should().BeNull();
response.Error.Should().BeOfType<ValidationError>();
response.Error.Message.Should().Be("'Name' must not be empty.");
}
[Test]
public async Task ReturnGeneric()
{
// Assemble
var services = GenericCreateContext<Venue>.GivenServices();
var handler = services.WhenCreateHandler();
var model = new GenericCreate<Venue>(new Venue
{
Name = "Main branch"
});
// Act
var response = await handler.Handle(model, CancellationToken.None);
// Assert
response.Success.Should().BeTrue();
response.Error.Should().BeNull();
response.Result.Should().BeOfType<Venue>();
}
}
public class GenericCreateContext<T, TKey> : DatabaseContextContext where T: TClass<TKey>
{
public static GenericCreateContext<T, TKey> GivenServices() => new GenericCreateContext<T, TKey>();
public GenericCreateHandler<T> WhenCreateHandler() => new GenericCreateHandler<T>(DatabaseContext);
}
public class GenericCreateContext<T> : GenericCreateContext<T, int> where T: TClass<int>
{
}
第一個測試通過了,這就是問題所在,因為NotNullValidator
不包含名稱等規則,但VenueValidator
; 這意味着擴展方法正在起作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.