簡體   English   中英

FluentValidation 在錯誤的視圖模型上使用驗證器

[英]FluentValidation using validator on wrong viewmodel

我是第一次使用 FluentValidation。 我有一些基本的驗證工作,但后來我意識到我需要為一些更復雜的驗證做一些數據庫檢索。 這需要進行依賴注入,以便我可以使用數據庫服務,這使我進入了當前狀態:卡住了。 我不能讓它工作。

為簡化起見,我假設我的應用程序正在處理體育聯盟和球隊,因為我認為這比合同、發票、資金來源、供應商和分包商更容易理解。 :-)

所以,假設我有一個體育聯盟的視圖模型。 在該視圖模型中,有該聯盟中球隊的一組視圖模型。

我有一個屏幕可以編輯聯賽。 同一個屏幕允許更改有關該聯盟中球隊的一些信息。

聯賽視圖模型

聯賽的視圖模型包含球隊的視圖模型列表。

[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
public class LeagueViewModel
{
    public string LeagueName { get; set; }
    public DateTime SeasonBeginDate { get; set; }
    public DateTime SeasonEndDate { get; set; }

    public List<TeamViewModel> TeamViewModels { get; set; }
}

我為 LeagueViewModel 創建了一個驗證器。 不幸的是,當我編輯聯賽並單擊提交按鈕時,我收到以下錯誤消息:

InvalidCastException:無法將“TeamViewModel”類型的對象轉換為“LeagueViewModel”類型。 在 FluentValidation.ValidationContext.ToGenericT

顯然它正在嘗試使用 LeagueValidator 驗證 TeamViewModel。

我經歷了許多變化,試圖弄清楚如何讓它發揮作用。 這是我目前所擁有的。

驗證器

public class LeagueValidator : AbstractValidator<LeagueViewModel>
{
    private readonly ILeagueService _leagueService;

    public LeagueValidator(ILeagueService leagueService)
    {
        _leagueService = leagueService;

        RuleFor(x => x.SeasonEndDate)
            .NotNull()
            .GreaterThan(x => x.SeasonBeginDate)
            .WithMessage("Season End Date must be later than Season Begin Date.");
    }
}

(LeagueService 位在那里,因為在實際代碼中它需要檢查一些數據庫值,它使用服務來檢索。)

請注意,LeagueValidator 沒有針對 TeamViewModel 列表中的任何字段的任何驗證規則。

聯盟驗證器工廠

public class LeagueValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public LeagueValidatorFactory(Container container)
    {
        _container = container;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        return _container.GetInstance<LeagueValidator>();
    }
}

依賴注入器

我們使用 SimpleInjector 進行 DI。 作為現有設置的一部分,它正在調用注冊服務的方法。 在該方法中,我添加了對此的調用:

private static void RegisterValidators(Container container)
{
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

    var leagueValidatorProvider =
        new FluentValidationModelValidatorProvider(new LeagueValidatorFactory(container));
    leagueValidatorProvider.AddImplicitRequiredValidator = false;
    ModelValidatorProviders.Providers.Add(leagueValidatorProvider);

    container.Register<LeagueValidator>();
}

問題

  1. 我如何讓它正常工作?
  2. 為什么要嘗試使用 LeagueValidator 來驗證 TeamViewModel?
  3. 我是否需要為每個視圖模型設置單獨的驗證器和驗證器工廠?
  4. 即使那些沒有任何驗證規則?
  5. 我如何告訴它哪個驗證器用於哪個視圖模型?

我想我一定是誤解了一些基本的東西。

編輯

下面史蒂文的回答讓我指出了正確的方向! 在我進行了他建議的更改后,我遇到了另一個錯誤。 一旦我解決了這個問題,它就起作用了! 以下是我為使上述代碼正常工作所做的更改。

聯賽視圖模型

我刪除了這一行,因為它不是必需的。

[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]

聯盟驗證器工廠

我將其重命名為“ValidatorFactory”,因為無論我創建了多少個驗證器,結果證明只有一個驗證器工廠。 然后我將 CreateInstance 方法更改為:

    public override IValidator CreateInstance(Type validatorType)
    {
        if (_container.GetRegistration(validatorType) == null)
        {
            return null;
        }

        return (IValidator)_container.GetInstance(validatorType);
    }

這不再明確指定要獲取的驗證器類型(這就是為什么只需要一個工廠的原因)。 為了確定給定類型的驗證器是否可用,它調用 GetRegistration,如果沒有找到則返回 null。

這很重要! 對於每個視圖模型,它都會嘗試找到一個驗證器。 如果沒有此 null 檢查,則會拋出 InvalidCastException。

依賴注入器

按照史蒂文的建議,我用這個替換了 container.Register 行:

container.Register(typeof(IValidator<>), new[] { typeof(SimpleInjectorInitializer).Assembly });

這避免了每次添加新驗證器時都需要明確列出每個驗證器。

現在一切正常! 非常感謝您的幫助,史蒂文!

我不熟悉 FluentValidation,但考慮到它提供了要驗證的類型,因此您的LeagueValidatorFactory似乎從容器中請求了錯誤的類型。

因此,我希望您的驗證工廠看起來像這樣:

public class LeagueValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public LeagueValidatorFactory(Container container) =>
        _container = container;

    public override IValidator CreateInstance(Type validatorType) =>
        (IValidator)_container.GetInstance(validatorType);
}

我從 FluentValidator 源代碼中可以看到validatorTypeIValidator<T>類型的封閉泛型版本, T是被驗證的實際類型。 這意味着,您必須通過它們的IValidator<T>接口注冊驗證器。 例如:

container.Register<IValidator<LeagueViewModel>, LeagueValidator>();

這種配置即代碼(或顯式注冊)模型,您使用一行代碼顯式注冊每個驗證器,如果您只有幾個驗證器,可能會正常工作,但這通常會導致必須經常更新的組合根.

因此,更好的模型是使用Auto-Registration ,您可以在其中使用反射注冊所有IValidator<T>實現。 幸運的是,您不必自己實現; Simple Injector 支持您:

var validatorAssemblies = new[] { typeof(LeagueValidator).Assembly };
container.Register(typeof(IValidator<>), validatorAssemblies);

這可確保您在添加新驗證器(在該特定程序集中)時永遠不必更改組合根。

有了這個設置,我看不出為什么你應該用FluentValidation.Attributes.ValidatorAttribute標記你的視圖模型。 如果可以,請刪除它,因為它只會導致視圖模型和驗證器之間的不緊密耦合。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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