How do I return a generic response and response code from all the features of a .NET MVC application?

I want to be able to return a generic response from function calls in the business layer of my MVC application. Most of the time when I see the object creation function looks like this:

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

      

In this case, _repository is a repository that wraps the entity structure.

This works great for many cases, but I want more information to be returned and I want to have a success / failure variable and a response code for why this action could not be verified. I want, perhaps, to be able to return the inserted object or the selected object.

An example would be a custom creator function that returns an email, cannot be an empty error, or if the user already exists and based on the error, I show the user a different message.

The problem I'm having is that I want the unit tests to cover all possible response codes from a function, without having to look at the code and try to figure out what possible return values ​​might be. What I am doing looks like an anti-pattern. Is there a better way to accomplish all of this?

This is what I have now.

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

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

      

Interfaces

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

      

implementation

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 application

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

      

+3


source to share


1 answer


The switch statement in your ValidateResetHash answer is smelly code. This tells me that you can use the subclassable enum . The subclassed enumeration will map action response codes or types to return views using models. Here is a compiled example of how to use it.

First, I used some fillable classes to get a compilation example:

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

      

Here are some framework classes (I'm using the base StringEnum class from the AtomicStack project for the base ResponseEnum class):

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

      

Here are some sample responses:

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

      

An example of a subclassed enumeration displaying the sample responses would look something like this:

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 implementation:

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

}

      

Modify the code above to suit your situation.

If you want to allow others to extend the ValidateHashResponses enumeration, you can make the constructor protected instead of private. They can then extend ValidateHashResponses and add their own additional enumeration values.

The point of using a subclassed enumeration, it is to use the TrySelect method, which allows responses to a specific enumeration value. Then we call the GenerateView method on the enum value to create the view.

Another advantage of enumeration is that if you need to make other decisions based on the enum value, you simply add another abstract method to the enum, and all value definitions will be forced to implement a new abstract method, unlike traditional enum / switch. where new enum values ​​are not required to add cases, and where one might forget to revise all switch statements that used enumeration.

DISCLAIMER : I am the author of the AtomicStack project. Feel free to use the enum subclass class code from the project if you think it will suit your needs.

UPDATE

If you want to introduce a responsive enumeration, you must create an adapter interface IResponseHandler

using a type method GenerateViewForResponse

and provide a concrete implementation that consumes the ValidateHashResponses enumeration.

+1


source







All Articles