在新增的章節有提到過官方有內建一些常用的驗證標籤供我們使用,但有時候還是會碰上想在自己做一個更適合自己使用的驗證標籤,這時候就可以使用繼承ValidationAttribute來打造專屬自己的驗證標籤。
首先先創一個ValidationAttributes資料夾來放自訂的驗證標籤,然後我們取名為NewsTitleAttribute

接著寫上我們的基本外框,繼承ValidationAttribute
public class NewsTitleAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
}
}
繼承ValidationAttribute,並且裡面有一個override ValidationResult的方法,這是我們今天要寫判斷邏輯的地方。
接著要來示範,如果我今天想從資料庫驗證,新聞標題有沒有存在,那該如何實作呢,因為大部分的判斷可以用內建的正則表達式解決
所以示範一個沒辦法用規則解決的範例,就是讀取資料庫,那我們可以透過ValidationContext來取得相關service,這邊我們是取得資料庫物件
KcgContext _kcgContext = (KcgContext)validationContext.GetService(typeof(KcgContext));
接著我們就來寫相關判斷邏輯,以下為完整程式碼。
public class NewsTitleAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
KcgContext _kcgContext = (KcgContext)validationContext.GetService(typeof(KcgContext));
var title = (string)value;
var findTitle = from a in _kcgContext.News
where a.Title == title
select a;
if (findTitle.FirstOrDefault() != null)
{
return new ValidationResult("已存在相同的新聞標題");
}
return ValidationResult.Success;
}
}
一開始我們從object value
取到的值會是你驗證標籤所放置的欄位的值,因為我們預定會放在title上,所以會是個string,接著我們再去資料庫撈有沒有相同的title,最後如果findTitle != null
就代表資料庫有相同的title,那我們就回傳一個錯誤訊息,如果找不到則會執行ValidationResult.Success
代表我們通過驗證。
接著我們把寫好的標籤放在NewsCreateDto.cs的Title上方
public class NewsCreateDto
{
[NewsTitle]
[Required(ErrorMessage = "標題不可為空")]
public string Title { get; set; }
}
然後我們馬上來測試效果如何,新增一筆資料庫已存在的新聞標題的資料

這邊就可以看到出現提示"已存在相同的新聞標題",代表驗證程式有如期的運作。
但這時候如果我們要放在同樣要驗證標題是否重複的NewsEditDto.cs的Title上可能就會有一些小問題,什麼樣的問題?就是我們今天可能在做修改時,Name並不改動而是改其他欄位,那我們上方的判斷邏輯,就會抓到自己這筆而導致驗證不過,所以我們要修改一下。

如上圖,我們這樣的邏輯會變成自己跟自己重複,所以今天我們要排除掉自己
public class NewsTitleAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
KcgContext _kcgContext = (KcgContext)validationContext.GetService(typeof(KcgContext));
var title = (string)value;
var findTitle = from a in _kcgContext.News
where a.Title == title
select a;
var dto = validationContext.ObjectInstance;
if (dto.GetType() == typeof(NewsEditDto))
{
var updateDto = (NewsEditDto)dto;
findTitle = findTitle.Where(a => a.NewsId != updateDto.NewsId);
}
if (findTitle.FirstOrDefault() != null)
{
return new ValidationResult("已存在相同的新聞標題");
}
return ValidationResult.Success;
}
}
一開始我們先從validationContext.ObjectInstance
可取得整個類別,接著我們再判斷我們是進到哪個類別,這邊的話就是如果是dto.GetType() == typeof(NewsEditDto)
,代表我們當下是做編輯,這裡也可以表現出為什麼我們必須將新增和修改拆成兩個Dto的原因之一,因為我們可能會有很多判斷是不一樣的。
確定是更新的時候,我們就再排除自己的NewsId,findTitle = findTitle.Where(a => a.NewsId != updateDto.NewsId);
,這樣就大功告成了。
這樣我們就可以正常編輯了
這個文章學到了兩個特殊用法,第一個就是取得資料庫物件(service)的方法validationContext.GetService
第二個就是判斷進入時類別的方法,var dto = validationContext.ObjectInstance;dto.GetType() == typeof(NewsEditDto)
範例檔:下載