2015-08-02 2 views
3

Я хочу, чтобы иметь возможность возвращать общий ответ от вызовов функций в бизнес-слое моего приложения MVC. Большая часть времени я вижу объект создать функцию выглядеть следующим образомКак вернуть общий ответ и код ответа из всех функций приложения .NET 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); 
        } 
      } 
     } 
    } 

ответ

1

Переключатель заявление в ответе ValidateResetHash это немного кода вонючий. Это подсказывает мне, что вы можете воспользоваться использованием 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); 
} 

Следующих некоторые инфраструктуры (рамочные) классы (я использую StringEnum базового класс от AtomicStack проекта для 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); 
    } 

} 

Tweak код выше, чтобы соответствовать вашей ситуации.

Если вы хотите разрешить другим пользователям продлить перечисление ValidateHashResponses, вы можете сделать конструктор защищенным вместо частного. Затем они могут расширять ValidateHashResponses и добавлять свои собственные дополнительные перечисления значения.

Точка использования подклассифицированного перечисления, чтобы использовать метод TrySelect, который разрешает ответы на определенное значение перечисления. Затем мы вызываем метод GenerateView для значения enum для создания представления.

Еще одно преимущество перечисления состоит в том, что если вам нужно принять другие решения, принятые на основе значения перечисления, вы просто добавляете другой абстрактный метод в перечисление, и все значения определений будут вынуждены внедрять новый абстрактный метод, в отличие от традиционных комбинаций операторов enum/switch, где новые значения enum не требуются для добавления случаев, и где можно забыть пересмотреть все операторы switch, в которых использовалось перечисление.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я являюсь автором проекта AtomicStack. Не стесняйтесь использовать код класса подкласса enum из проекта, если вы считаете, что он подойдет вашим потребностям.

UPDATE:

Если вы хотите Inject перечисления ответ, вы должны создать интерфейс IResponseHandler адаптера с методом GenerateViewForResponse типа и обеспечивают конкретную реализацию, потребляющий на перечисление ValidateHashResponses.

+0

Tyree Я все еще перевариваю ваше решение. Я также нашел это СО, что имеет отношение к тому, что вы предложили, в отношении подклассовых перечислений. http://stackoverflow.com/questions/633656/programming-against-an-enum-in-a-switch-statement-is-this-your-way-to-do – codenesium

+0

@codenesium не стесняйтесь задавать любые вопросы. –

+0

@codenesium Это правильно. Я называю это как ограничение типа подкласса. TResponse должен быть подклассом Response . Таким образом, у нас есть доступ к его типу, чтобы мы могли извлечь его имя типа. –

Смежные вопросы