.net core api 對於FromBody的參數驗證

前言

在framework的mvc中,經常會使用 Model.StateModelState.IsValid 配合着特性進行參數驗證,通過這種方式可以降低controller的複雜度,使用方便。

常見的特性有: RequiredAttribute、RangeAttribute等...

而在.net core api中可以看到這些特性依然被保存了下來,接下來就通過使用這些特性來看看.net core api是如何進行校驗的。

首先,在控制器中添加一個測試方法,和一個測試action

[HttpPost]
public Info Tets([FromBody] Info data)
{
    return data;
}

public class Info
{
    [Required]
    public bool Data { get; set; }
}

然後不傳參數(空對象)來測試這個接口:

<response>

HTTPStatus : 400 Bad Request

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|8abecfa7-47340a71b12754df.",
    "errors": {
        "Data": [
            "The Data field is required."
        ]
    }
}

直接返回400,Action不用做多餘的處理,這樣的話對於簡單的驗證,我們可以直接在模型裏面打標記,action只關注業務,錯誤碼也不用特意去定義,真是一舉多得。

如何擴展?

雖然官方已經提供了一些驗證特性,但是對於一些其他簡單校驗,我們可能還是要自己寫驗證,那麼如何去實現自定義的特性驗證呢?

查看RequiredAttribute定義:

public class RequiredAttribute : ValidationAttribute

1.繼承ValidationAttribute,通過此特性標記當前特性是一個驗證特性,在請求時會觸發此類的實現

public override bool IsValid(object value)
{
	if (value == null)
	{
		return false;
	}
	if (!AllowEmptyStrings)
	{
		string text = value as string;
		if (text != null)
		{
			return text.Trim().Length != 0;
		}
	}
	return true;
}

2.可以看到這裏實現了IsValid方法,然後通過返回值的true or false 來表示是否驗證成功

接下來,讓我們來實現一個簡單的例子:傳入的集合不能爲空且必須有子項

public class CollectionRequiredAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null)
            return false;
        if (value is ICollection collection)
            if (collection.Count == 0) return false;
        return true;
    }
}

測試成功. 不符合時返回結果:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|c04340c0-41f44630c5a43e25.",
    "errors": {
        "Arr": [
            "The field Arr is invalid."
        ]
    }
}

有的時候,我們可能會想要把錯誤信息顯示得更明顯一些,可以提供實現父類方法實現:

public override string FormatErrorMessage(string name)
{
    return "你想要返回的消息格式";
}

以上例子都只是一個列的驗證,如果你想要類型於CompareAttribute那種多個列進行驗證的,該如何實現?

查看CompareAttribute源碼:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
	PropertyInfo runtimeProperty = validationContext.ObjectType.GetRuntimeProperty(OtherProperty);
	if (runtimeProperty == null)
	{
		return new ValidationResult(System.SR.Format(System.SR.CompareAttribute_UnknownProperty, OtherProperty));
	}
	if (runtimeProperty.GetIndexParameters().Any())
	{
		throw new ArgumentException(System.SR.Format(System.SR.Common_PropertyNotFound, validationContext.ObjectType.FullName, OtherProperty));
	}
	object value2 = runtimeProperty.GetValue(validationContext.ObjectInstance, null);
	if (!object.Equals(value, value2))
	{
		if (OtherPropertyDisplayName == null)
		{
			OtherPropertyDisplayName = GetDisplayNameForProperty(runtimeProperty);
		}
		return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
	}
	return null;
}

可以看到它實現了另外一個IsValid,然後通過ValidationContext獲取信息進行校驗。

返回值說明 : null - 驗證通過

實現都非常簡單,直接來看例子: 當另外一個字段的值爲true時,當前字段不能爲null

public class MustDefinedWithPrevAttribute : ValidationAttribute
{

    public string PrevCol { get; set; } // 另外一個字段的字段名,用於反射獲取值

    public MustDefinedWithPrevAttribute(string prevCol, string errorMessage)
    {
        PrevCol = prevCol;
        ErrorMessage = errorMessage; // 指定錯誤信息
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {

        var type = validationContext.ObjectInstance.GetType();

        if (type.GetProperty(PrevCol).GetValue(validationContext.ObjectInstance) is bool flag && flag)// 另外一個字段值爲true
        {
            if(value == null)
                return new ValidationResult(ErrorMessage); // 爲空返回驗證失敗。
        }
        return null;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章