【问题标题】:How should I return a generic response and response code from all functions in a .NET MVC app?我应该如何从 .NET MVC 应用程序中的所有函数返回通用响应和响应代码?
【发布时间】:2015-10-24 11:42:51
【问题描述】:

我希望能够从我的 MVC 应用程序的业务层中的函数调用返回通用响应。大多数时候我看到一个对象创建函数看起来像这样

 public int Create(ICNUser item)
 {
       return this._repository.Create(item);
 }
 public void Update(ICNUser item)
 {
      this._repository.Create(item);
 }

在这种情况下,_repository 是一个包装实体框架的存储库。

这适用于很多情况,但我希望返回更多信息,并且我希望有一个成功/失败变量和一个响应代码,说明此操作验证失败的原因。我希望能够选择返回插入的对象或选定的对象。

一个示例是创建用户函数,它返回电子邮件不能为空白错误和/或用户已存在错误,并根据该错误向用户显示不同的消息。

我遇到的问题是我想让单元测试覆盖一个函数的所有可能的响应代码,而我不必去查看代码并尝试找出可能的返回值是什么。我正在做的事情感觉像是一种反模式。有没有更好的方法来完成这一切?

这就是我现在所拥有的。

 public IGenericActionResponse<ICNUser> Create(ICNUser item)
 {
        return this._repository.Create(item);
 }

 public IGenericActionResponse Update(ICNUser item)
 {
       return this._repository.Update(item);
 }

接口

 namespace Web.ActionResponses
 {



public enum ActionResponseCode
{
    Success,
    RecordNotFound,
    InvalidCreateHash,
    ExpiredCreateHash,
    ExpiredModifyHash,
    UnableToCreateRecord,
    UnableToUpdateRecord,
    UnableToSoftDeleteRecord,
    UnableToHardDeleteRecord,
    UserAlreadyExists,
    EmailCannotBeBlank,
    PasswordCannotBeBlank,
    PasswordResetHashExpired,
    AccountNotActivated,
    InvalidEmail,
    InvalidPassword,
    InvalidPageAction
}

public interface IGenericActionResponse
{
    bool RequestSuccessful { get; }
    ActionResponseCode ResponseCode { get; }
}

public interface IGenericActionResponse<T>
{
    bool RequestSuccessful { get; }
    bool RecordIsNull{get;}
    ActionResponseCode ResponseCode { get; }
}
}

实现

namespace Web.ActionResponses
{

public class GenericActionResponse<T> : IGenericActionResponse<T>
{
    private bool _requestSuccessful;
    private ActionResponseCode _actionResponseCode;
    public T Item { get; set; }
    public GenericActionResponse(bool success, ActionResponseCode actionResponseCode, T item)
    {
        this._requestSuccessful = success;
        this._actionResponseCode = actionResponseCode;
        this.Item = item;
    }

    public GenericActionResponse(bool success, ActionResponseCode actionResponseCode)
    {
        this._requestSuccessful = success;
        this._actionResponseCode = actionResponseCode;
        this.Item = default(T);
    }

    public bool RecordIsNull
    {
        get
        {
            return this.Item == null;
        }
    }

    public bool RequestSuccessful
    {
        get
        {
            return this._requestSuccessful;
        }
    }

    public ActionResponseCode ResponseCode
    {
        get
        {
            return this._actionResponseCode;
        }
    }

}




public class GenericActionResponse : IGenericActionResponse
{
    private bool _requestSuccessful;
    private ActionResponseCode _actionResponseCode;

    public GenericActionResponse(bool success, ActionResponseCode actionResponseCode)
    {
        this._requestSuccessful = success;
        this._actionResponseCode = actionResponseCode;

    }

    public bool RequestSuccessful
    {
        get
        {
            return this._requestSuccessful;
        }
    }

    public ActionResponseCode ResponseCode
    {
        get
        {
            return this._actionResponseCode;
        }
    }

}}

MVC 应用

public ActionResult ValidateResetHash(string passwordResetHash)
    {
        IGenericActionResponse result = (IGenericActionResponse)this._userManager.IsValidPasswordResetHash(passwordResetHash);

        if (result.RequestSuccessful)
        {
            Models.PasswordChangeModel model = new Models.PasswordChangeModel();
            model.PasswordResetHash = passwordResetHash;
            return View("~/Areas/Public/Views/ResetPassword/PasswordChangeForm.cshtml", model);
        }
        else
        {
            switch (result.ResponseCode)
            {
                case ActionResponseCode.RecordNotFound:
                    {
                        FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/Login", "Login", "You have submitted an invalid password reset link.", false);
                        return View("~/Views/Shared/GenericAction.cshtml", responseModel);
                    }
                case ActionResponseCode.PasswordResetHashExpired:
                    {
                        FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/ResetPassword", "Reset Password", "You have submitted an expired password reset link. You must reset your password again to change it.", false);
                        return View("~/Views/Shared/GenericAction.cshtml", responseModel);
                    }
                default:
                    {
                        FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/", "Home", "An unknown error has occured. The system administrator has been notified. Error code:" + Enum.GetName(typeof(ActionResponseCode), result.ResponseCode), false);
                        return View("~/Views/Shared/GenericAction.cshtml", responseModel);
                    }
            }
        }
    }

【问题讨论】:

    标签: c# .net asp.net-mvc architecture n-tier-architecture


    【解决方案1】:

    您的 ValidateResetHash 响应中的 switch 语句是一个有点臭的代码。这向我表明,您可能会从使用subclassable enum 中受益。可子类枚举将映射动作响应代码或类型以返回带有模型的视图。这是一个如何使用它的编译示例。

    首先我用来获取编译示例的一些类填充:

    public class GenericActionModel
    {
        private bool v1;
        private string v2;
        private string v3;
        private string v4;
        private bool v5;
    
        protected GenericActionModel() {}
        public GenericActionModel(bool v1, string v2, string v3, string v4, bool v5)
        {
            this.v1 = v1;
            this.v2 = v2;
            this.v3 = v3;
            this.v4 = v4;
            this.v5 = v5;
        }
    }
    
    public class ActionResult
    {
        private GenericActionModel responseModel;
        private string v;
    
        public ActionResult(string v, GenericActionModel responseModel)
        {
            this.v = v;
            this.responseModel = responseModel;
        }
    }
    
    public class PasswordChangeModel : GenericActionModel
    {
        public object PasswordResetHash
        {
            get;
            set;
        }
    }
    
    public interface IUserManager
    {
        Response IsValidPasswordResetHash(string passwordResetHash);
    }
    

    接下来是一些基础设施(框架)类(我使用来自 AtomicStack 项目的 StringEnum 基类作为 ResponseEnum 基类):

    public abstract class Response
    {
        public abstract string name { get; }
    }
    
    public class Response<TResponse> : Response where TResponse : Response<TResponse>
    {
        private static string _name = typeof(TResponse).Name;
        public override string name => _name;
    }
    
    // Base ResponseEnum class to be used by more specific enum sets
    public abstract class ResponseEnum<TResponseEnum> : StringEnum<TResponseEnum>
        where TResponseEnum : ResponseEnum<TResponseEnum>
    {
        protected ResponseEnum(string responseName) : base(responseName) {}
        public abstract ActionResult GenerateView(Response response);
    }
    

    以下是一些示例响应:

    public class HashValidated : Response<HashValidated>
    {
        public string passwordResetHash;
    }
    public class InvalidHash : Response<InvalidHash> {}
    public class PasswordResetHashExpired : Response<PasswordResetHashExpired> {}
    public class Unexpected : Response<Unexpected> {}
    

    映射示例响应的示例可子类枚举如下所示:

    public abstract class ValidateHashResponses : ResponseEnum<ValidateHashResponses>
    {
    
        public static readonly ValidateHashResponses HashOk                     = HashValidatedResponse.instance;
        public static readonly ValidateHashResponses InvalidHash                = InvalidHashResponse.instance;
        public static readonly ValidateHashResponses PasswordResetHashExpired   = PasswordResetHashExpiredResponse.instance;
        public static readonly ValidateHashResponses Default                    = DefaultResponse.instance;
    
        private ValidateHashResponses(string responseName) : base(responseName) {}
    
        protected abstract class ValidateHashResponse<TValidateHashResponse, TResponse> : ValidateHashResponses
            where TValidateHashResponse : ValidateHashResponse<TValidateHashResponse, TResponse>, new()
            where TResponse : Response<TResponse>
        {
            public static TValidateHashResponse instance = new TValidateHashResponse();
            private static string name = Response<TResponse>.Name;
            protected ValidateHashResponse() : base(name) {}
        }
    
        protected class HashValidatedResponse : ValidateHashResponse<HashValidatedResponse, HashValidated>
        {
            public override ActionResult GenerateView(Response response)
            {
                PasswordChangeModel model = new PasswordChangeModel();
                model.PasswordResetHash = ((HashValidated) response).passwordResetHash;
                return new ActionResult("~/Areas/Public/Views/ResetPassword/PasswordChangeForm.cshtml", model);
            }
        }
    
        protected class InvalidHashResponse : ValidateHashResponse<InvalidHashResponse, InvalidHash>
        {
            public override ActionResult GenerateView(Response response)
            {
                GenericActionModel responseModel = new GenericActionModel(true, "/Login", "Login", "You have submitted an invalid password reset link.", false);
                return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
            }
        }
    
        protected class PasswordResetHashExpiredResponse : ValidateHashResponse<PasswordResetHashExpiredResponse, PasswordResetHashExpired>
        {
            public override ActionResult GenerateView(Response response)
            {
                GenericActionModel responseModel = new GenericActionModel(true, "/ResetPassword", "Reset Password", "You have submitted an expired password reset link. You must reset your password again to change it.", false);
                return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
            }
        }
    
        protected class DefaultResponse : ValidateHashResponses
        {
            public static DefaultResponse instance = new DefaultResponse();
            private DefaultResponse() : base("Default") {}
            public override ActionResult GenerateView(Response response)
            {
                GenericActionModel responseModel = new GenericActionModel(true, "/", "Home", "An unknown error has occured. The system administrator has been notified. Error code:" + response.name, false);
                return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
            }
        }
    
    }
    

    实现 SampleController:

    public class SampleController
    {
        private IUserManager _userManager;
        public ActionResult ValidateResetHash(string passwordResetHash)
        {
            Response    result      = this._userManager.IsValidPasswordResetHash(passwordResetHash);
            var         resultType  = ValidateHashResponses.TrySelect(result.name,ValidateHashResponses.Default);
            return resultType.GenerateView(result);
        }
    
    }
    

    调整上面的代码以适应您的情况。

    如果您想允许其他人扩展 ValidateHashResponses 枚举,您可以使构造函数受保护而不是私有。然后他们可以扩展 ValidateHashResponses 并添加自己的额外枚举

    使用可子类枚举的要点是,它利用了将响应解析为特定枚举值的 TrySelect 方法。然后我们在枚举值上调用 GenerateView 方法来生成视图。

    枚举的另一个好处是,如果您需要根据枚举值做出其他决定,您只需向枚举添加另一个抽象方法,所有 value 定义将被强制实现新的抽象方法,与传统的枚举/switch 语句组合不同,传统的枚举值不需要添加案例,而且人们可能会忘记重新访问使用枚举的所有 switch 语句。

    免责声明: 我是 AtomicStack 项目的作者。如果您认为它适合您的需要,请随意从项目中获取 Subclassable 枚举类代码。

    更新

    如果你想注入响应枚举,你应该创建一个带有GenerateViewForResponse类型方法的IResponseHandler适配器接口,并提供一个使用ValidateHashResponses枚举的具体实现。

    【讨论】:

    • Tyree 我仍在消化您的解决方案。我还发现这个 SO 与您所建议的子类枚举相关。 stackoverflow.com/questions/633656/…
    • @codenesium 没错。我将此称为子类类型约束。 TResponse 必须是 Response 的子类。这样我们就可以访问它的类型,以便我们可以提取它的类型名称。
    • @codenesium 子类类型约束也有其他用途,尤其是用于编写支持编写流畅接口的泛型类。
    • 如果我不希望业务层知道视图,我仍然需要将业务层方法调用的可能响应转换为适当视图的部分,并且需要在 MVC 中应用程序某处。如果没有在枚举或我为每个响应创建的类的类型上使用 switch case,我不知道如何完成我想要做的事情。忍受我。我不是高级开发人员,我有很多复数视频可以观看。感谢您之前的详细回复。
    • 很抱歉,cmets 都被抬高了。我正在编辑一篇很长的文章,超过了 5 分钟。我了解在您的示例中,我们如何尝试使用策略模式来摆脱易碎的开关盒。它解决了我几乎遇到的确切问题,我想知道何时向我的代码和测试中涵盖的函数添加可能的响应。在这里大声思考。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    • 2019-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多