【问题标题】:How to return validation results?如何返回验证结果?
【发布时间】:2011-10-27 11:45:07
【问题描述】:

我有一个使用 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; }
}

更干净,但更难使用?

你会怎么做,还是我错过了其他选择?

【问题讨论】:

  • 这是针对 api 还是针对普通的基于视图的项目?我的意思是对于基于视图的项目,您可以使用城堡验证套件,您可以使用 xval 或其他。还是您正在寻找这些示例代码?
  • 为我的问题添加了更多上下文。这是一个带有执行验证的命令处理程序的命令服务。我不是在寻找示例代码,而是关于如何返回强类型验证结果的想法。
  • 有什么理由不使用内置验证(假设是 mvc3)?已经有一个 ValidationResult 类 - msdn.microsoft.com/en-us/library/dd411789%28v=VS.100%29.aspx
  • 我在 MVC 中进行验证,但我说的是与 MVC 无关的 CommandService 中的验证。
  • Lud,ValidationResult 被退回后你在做什么?是否有任何东西返回给客户,或者只是记录下来?我正试图弄清楚您需要返回哪些数据 - 这会影响我的回答(我认为)。

标签: c# asp.net-mvc validation domain-driven-design cqrs


【解决方案1】:

所以这就是我解决它的方法。首先,主要的 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 语句中使用特定的枚举 - 避免使用魔法字符串。

【讨论】:

    【解决方案2】:

    我的 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

    希望这会有所帮助。

    【讨论】:

    • 谢谢,确实一样,但我想知道我们是否不能做得更好而不是返回这些字符串 - 我的意思是,强类型的东西......
    • 但是这是基于 T 的强类型。字符串只是用来描述错误信息,如果需要在 UI 中显示。
    • ErrorCode - 如您的示例所示。这可以是任何东西,只要你知道它包含什么并且实例化是相应的,Context 仍然是强类型的
    • 在我的情况下,我只需要显示验证结果,而不需要以编程方式实际通知收到的错误。用户将在其屏幕上看到验证结果列表。所以我只是将验证结果字符串连接在一起并用逗号分隔它们并将其作为上下文返回。但是,如果在您的情况下,您需要实际通知接收方验证结果的类型,那么我将返回一个枚举列表或一组预先商定的错误代码。
    【解决方案3】:

    这当然不是最好的方法。

    为什么不呢?字符串有什么问题?如果您担心一致性,可以使用资源文件或常量。字符串在层之间将更便携,但这取决于您的设计。无论如何,您可能需要一个 ErrorCode(字符串或枚举)和一个 ErrorMessage(字符串)。我假设您需要将此错误转换为对用户更具可读性的内容?

    您应该查看其中一个 cmets 中提到的内置验证。验证来自 System.ComponentModel.DataAnnotations,与 MVC 无关。您可以在 MVC 范围之外进行验证,这是一个示例:

    var validationContext = new ValidationContext(yourObject, null, null);
    var validationResults = new List<ValidationResult>();
    
    Validator.TryValidateObject(yourObject, validationContext, validationResults);
    

    HTH...

    【讨论】:

    • 谢谢,数据注释很有趣,但上下文验证的很大一部分不是在实体上完成,而是在命令处理程序中完成。不知道用数据注解能不能解决这个问题?
    【解决方案4】:

    也许是一个愚蠢的观察,但为什么不在客户端验证呢?表现良好的客户端应在提交命令之前对其进行验证。行为不端的客户不会验证和提交,也不会找回失败的原因(重点是什么——即使从安全 POV 来看也不可取)。当然,在服务器端,您应该重新验证并以某种方式记录无效命令,以便 Ops 知道他们需要解决/查看故障。如果您有不同技术的客户,那么在语言中立的 DSL 中定义验证并从该 DSL 以您喜欢的任何技术/语言生成代码可能会很有用(不过这里还有其他选项)。

    【讨论】:

    • 我正在做客户端和服务器端验证。
    猜你喜欢
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    • 2019-03-30
    • 2019-06-25
    • 2013-03-31
    • 2020-07-01
    • 1970-01-01
    • 2015-08-26
    相关资源
    最近更新 更多