如何在 EF Core 3.1 的模型驗證方法中注入 ServiceCollection 中的服務 | The Will Will Web

The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 EF Core 3.1 的模型驗證方法中注入 ServiceCollection 中的服務

由於 .NET Core 大量的使用 DI 技術,所有註冊到 DI 容器(ServiceCollection)的服務,幾乎都可以用「注入」的方式取得物件。只不過有些類別不容易用建構式注入的方式取得服務,這篇文章將分享如何在 EF Core 3.1 的模型驗證方法中注入 ServiceCollection 裡的任何服務。

簡介模型驗證

假設我們有個 LoginUser 實體模型如下:

using System.ComponentModel.DataAnnotations;

namespace api1.Controllers
{
    public class LoginUser
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

在 ASP․NET Core 與 Entity Framework Core 中,提供了兩種驗證方式:

  1. 輸入驗證 (Input Validation)

    這種類型的驗證主要針對「單一欄位」進行驗證。

    以上述範例來說,我們套用了 [Required] 輸入驗證屬性,用以驗證每個屬性在做 模型繫結(Model Bindings) 時,必須填寫內容。

  2. 模型驗證 (Model Validation)

    當所有欄位都已經驗證完畢,整個實體模型在 模型繫結(Model Bindings) 的過程中會將該物件所有屬性填滿,此時就會進行 模型驗證 驗證物件資料的完整性與正確性。

    有時候一筆資料的正確性並無法透過「單一欄位」來判斷,如果你要判斷多重欄位的相關性才能確認該物件是否有效,就需要利用 模型驗證 來進行檢查。

以下是模型驗證的使用範例:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace api1.Controllers
{
    public class LoginUser : IValidatableObject
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            throw new System.NotImplementedException();
        }
    }
}

模型驗證 不單單只能驗證同一個 模型類別 (Model Class) 下的多重欄位而已,有時候還可以透過 Entity Framework Core 的 DbContext 對資料庫做出其他資料查詢,並藉此做出資料正確性比對。有時候也可以呼叫外部的 Web API 取得驗證結果。這個時候,就很有可能需要透過 DI 注入其他服務!不過,模型類別並無法使用「建構式注入」來取得「服務」,所以肯定要換個方式取得服務。

透過 ValidationContext 注入服務

還好 ValidationContext 類別有個成員 ValidationContext.GetService(Type) Method 可以用來取得任何註冊在 ServiceCollection 裡面服務,所以我們透過這個 API 就可以順利注入所需的服務物件。

以下是實際的使用範例:

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using api1.Models;
using Microsoft.EntityFrameworkCore;

namespace api1.Controllers
{
    public class LoginUser : IValidatableObject
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var db = (ContosouniversityContext)validationContext.GetService(typeof(ContosouniversityContext));

            if (!db.User.Any())
            {
                yield break;
            }

            if (!db.User.FromSqlInterpolated($"SELECT 1 FROM dbo.User WHERE Username={Username} AND Password={Password}").Any())
            {
                yield return new ValidationResult($"您登入的帳號或密碼錯誤", new string[] { "Username", "Password" });
            }
        }
    }
}

如果你自訂 ValidationAttribute 的話,也可以透過實作 IsValid(Object, ValidationContext) 來取得 ValidationContext 物件,因此你一樣可以取得任何你想獲得的服務物件!👍

相關連結