我有一个使用CommandService的ASP.NET MVC站点。此命令服务负责执行命令,但在执行命令之前,每个命令都需要进行验证,因此有一个返回ValidationResult的Validate操作,看起来像(简化):
public class ValidationResult
{
public List<string> ErrorCodes { get; set; }
}
我想改进这一点,因为目前返回一个字符串列表,如'UserDoesNotExist'或'TitleIsMandatory',当然这不是最好的方法。
返回强类型的东西会更好。但是我该怎么做呢?
选项1:使用一个大的枚举,如:
public enum ErrorCode { UserDoesNotExist, TitleIsMandatory}
public class ValidationResult
{
public List<ErrorCode> ErrorCodes { get; set; }
}
我不知道创建如此大的枚举并将所有域错误代码放入其中是否是一个好主意?
选项2:使用类
public class ErrorCode {}
public class UserDoesNotExist : ErrorCode {}
public class TitleIsMandatory : ErrorCode {}
public class ValidationResult
{
public List<ErrorCode> ErrorCodes { get; set; }
}
更清洁,但更难使用?
你会做什么,或者我错过了其他选择?
答案 0 :(得分:2)
所以这就是我解决它的方式。首先,主要的ValidationResult类看起来像:
public class ValidationResult
{
public List<ValidationResultItem> ValidationResultItems { get; set; }
public bool IsAcceptable
{
get { return (ValidationResultItems == null || !ValidationResultItems.Any(vri => !vri.IsAcceptable)); }
}
public void Add(ValidationResultItem propertyValidationResultItem)
{
ValidationResultItems.Add(propertyValidationResultItem);
}
public void Add(IEnumerable<ValidationResultItem> validationResultItems)
{
ValidationResultItems.AddRange(validationResultItems);
}
}
ValidationResultItem是一个抽象类:
public abstract class ValidationResultItem
{
private ResultType _resultType;
protected ValidationResultItem(ResultType resultType, string message)
{
ResultType = resultType;
Message = message;
}
public bool IsAcceptable { get; private set; }
public string Message { get; private set; }
public ResultType ResultType
{
get { return _resultType; }
set { _resultType = value; IsAcceptable = (_resultType != ResultType.Error); }
}
}
并且有两种实现方式:
public class PropertyValidationResultItem : ValidationResultItem
{
public PropertyValidationResultItem(ResultType resultType, string message, string propertyName, object attemptedValue) : base(resultType, message)
{
PropertyName = propertyName;
AttemptedValue = attemptedValue;
}
public string PropertyName { get; private set; }
public object AttemptedValue { get; private set; }
}
和
public abstract class BusinessValidationResultItem : ValidationResultItem
{
protected BusinessValidationResultItem(ResultType resultType, string message) : base(resultType, message)
{
}
}
每个命令处理程序都有自己的BusinessValidationResultItem实现,例如:
public class AddArticleBusinessValidationResultItem : BusinessValidationResultItem
{
public enum AddArticleValidationResultCode { UserDoesNotExist, UrlTitleAlreadyExists, LanguageDoesNotExist }
public AddArticleBusinessValidationResultItem(ResultType resultType, string message, AddArticleValidationResultCode code)
: base(resultType, message)
{
Code = code;
}
public AddArticleValidationResultCode Code { get; set; }
}
这意味着如果客户端获得ValidationResult,他可以将BusinessValidationResultItem强制转换为具体的AddArticleBusinessValidationResultItem,因此在switch语句中使用特定的枚举 - 避免使用魔术字符串。
答案 1 :(得分:0)
我的REST api非常相似,如果存在验证错误,则会将字符串消息与对象一起返回给用户。
例如:
[DataContract]
public class ApiErrorResult : ApiResult<IList<ErrorCodes>>
{
public ApiErrorResult(String message)
{
Code = ApiCode.ValidationError;
Error = message;
}
}
然后ApiResult是:
public class ApiResult<T>
{
[DataMember]
public ApiCode Code
{
get;
set;
}
[DataMember]
public String Error
{
get;
set;
}
[DataMember]
public T Context
{
get;
set;
}
}
因此,如果它是验证错误,则上下文将包含等效的验证错误代码。如果不是我将实际结果存储在上下文中并返回ApiResult而不是ApiErrorResult
希望这会有所帮助。
答案 2 :(得分:0)
当然,这不是最好的方法。
为什么不呢?字符串怎么了?如果您担心一致性可能使用资源文件或常量。字符串在层之间将更加便携,但这取决于您的设计。无论如何,您可能需要ErrorCode(字符串或枚举)和ErrorMessage(字符串)。我假设您需要将此错误转换为更易读的用户?
您应该查看其中一条评论中提到的内置验证。验证来自System.ComponentModel.DataAnnotations,与MVC无关。您可以在MVC范围之外进行验证,这是一个示例:
var validationContext = new ValidationContext(yourObject, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(yourObject, validationContext, validationResults);
... HTH
答案 3 :(得分:0)
也许是一个愚蠢的观察,但为什么不做验证客户端?表现良好的客户端应在提交命令之前验证命令。不良行为的客户不会验证和提交,但也不会回到它失败的原因(这一点是什么 - 甚至不建议从安全POV)。当然,服务器端你应该重新验证并以某种方式记录无效命令,以便Ops知道他们需要解决/查看失败。如果您的客户端使用不同的技术,那么在语言中立的DSL中定义验证并在您喜欢的DSL中生成代码(这里有其他选项)可能会很有用。