我应该如何从 .NET MVC 应用程序中的所有函数返回通用响应和响应代码?

cod*_*ium 5 .net c# architecture asp.net-mvc n-tier-architecture

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

 public int Create(ICNUser item)
 {
       return this._repository.Create(item);
 }
 public void Update(ICNUser item)
 {
      this._repository.Create(item);
 }
Run Code Online (Sandbox Code Playgroud)

在本例中,_repository 是包装实体框架的存储库。

这对于很多情况都很有效,但我希望返回更多信息,并且我希望有一个成功/失败变量以及一个响应代码来说明为什么此操作验证失败。我希望能够选择返回插入的对象或选定的对象。

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

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

这就是我现在所拥有的。

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

 public IGenericActionResponse Update(ICNUser item)
 {
       return this._repository.Update(item);
 }
Run Code Online (Sandbox Code Playgroud)

接口

 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; }
}
}
Run Code Online (Sandbox Code Playgroud)

实施

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;
        }
    }

}}
Run Code Online (Sandbox Code Playgroud)

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);
                    }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Tyr*_*son 1

ValidateResetHash 响应中的 switch 语句有点代码难闻。这对我来说表明您可能会从使用可子类化 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);
}
Run Code Online (Sandbox Code Playgroud)

接下来是一些基础设施(框架)类(我使用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);
}
Run Code Online (Sandbox Code Playgroud)

以下是一些回复示例:

public class HashValidated : Response<HashValidated>
{
    public string passwordResetHash;
}
public class InvalidHash : Response<InvalidHash> {}
public class PasswordResetHashExpired : Response<PasswordResetHashExpired> {}
public class Unexpected : Response<Unexpected> {}
Run Code Online (Sandbox Code Playgroud)

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

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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

实现示例控制器:

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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

使用可子类化枚举的目的是利用 TrySelect 方法来解析对特定枚举值的响应。然后我们对枚举值调用GenerateView方法来生成视图。

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

免责声明:我是 AtomicStack 项目的作者。如果您认为可以满足您的需求,请随意从项目中获取 Subclassable 枚举类代码。

更新

如果要注入响应枚举,则应IResponseHandler使用类型方法创建适配器接口GenerateViewForResponse,并提供使用 ValidateHashResponses 枚举的具体实现。