簡體   English   中英

將具體類注入通用 c# .net 核心

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM