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

由於 .NET Core 大量的使用 DI 技術，所有註冊到 DI 容器([ServiceCollection](https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection))的服務，幾乎都可以用「注入」的方式取得物件。只不過有些類別不容易用**建構式注入**的方式取得服務，這篇文章將分享如何在 EF Core 3.1 的模型驗證方法中注入 ServiceCollection 裡的任何服務。

### 簡介模型驗證

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

```csharp
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) 的過程中會將該物件所有屬性填滿，此時就會進行 **模型驗證** 驗證物件資料的完整性與正確性。
    
    有時候一筆資料的正確性並無法透過「單一欄位」來判斷，如果你要判斷多重欄位的相關性才能確認該物件是否有效，就需要利用 **模型驗證** 來進行檢查。
    

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

```csharp
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](https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations.validationcontext) 類別有個成員 [ValidationContext.GetService(Type) Method](https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations.validationcontext.getservice) 可以用來取得任何註冊在 [ServiceCollection](https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection) 裡面服務，所以我們透過這個 API 就可以順利注入所需的服務物件。

以下是實際的使用範例：

```csharp
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](https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations.validationattribute?view=netcore-3.1) 的話，也可以透過實作 [IsValid(Object, ValidationContext)](https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations.validationattribute.isvalid?view=netcore-3.1#System_ComponentModel_DataAnnotations_ValidationAttribute_IsValid_System_Object_System_ComponentModel_DataAnnotations_ValidationContext_) 來取得 [ValidationContext](https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations.validationcontext) 物件，因此你一樣可以取得任何你想獲得的服務物件！👍

### 相關連結

-   [Model validation in ASP.NET Core MVC and Razor Pages](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1)
-   [Part 9, add validation to an ASP.NET Core MVC app](https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/validation?view=aspnetcore-3.1)
-   [Injecting services into ValidationAttributes in ASP.NET Core](https://andrewlock.net/injecting-services-into-validationattributes-in-asp-net-core/)
-   [Validation in ASP .NET Core 3.1 | Wake Up And Code!](https://wakeupandcode.com/validation-in-asp-net-core-3-1/)
-   [Executing Raw SQL Queries using Entity Framework Core | Learn Entity Framework Core](https://www.learnentityframeworkcore.com/raw-sql)
-   [Inject Dependencies into Validation Attribute Web Api Asp.Net Core - Stack Overflow](https://stackoverflow.com/a/47192006/910074)
