Как я могу удалить повторяющийся код в моих действиях?

2

У меня есть следующий код, повторяемый несколько раз в приложении mvc.

    public ActionResult AnAction(int Id)
    {           
        var claim = GetClaim(Id);
        if (claim == null)
        {
            return View("ClaimNotFound");
        }

        // do stuff here
        ....
        return ....;
    }

Пока этот шаблон используется 4 раза, и он становится уродливым. Каков наилучший способ реорганизации?

Edit:
Несколько примеров использования

    public ActionResult Claim(int Id)
    {           
        var claim = GetClaim(Id);
        if (claim == null)
        {
            return View("ClaimNotFound");
        }

        return View("Claim", claim);
    }

    public ActionResult MedicalPV(int Id)
    {
        var claim = GetClaim(Id);
        if (claim == null)
        {
            return View("ClaimNotFound");
        }

        return PartialView(claim.MedCerts.AsQueryable<MedCert>());
    }

Обычно мне требуется доступ к объекту в представлении. Этот конкретный код используется только в одном контроллере, но мне может понадобиться нечто подобное в других контроллерах с разными объектами и представлениями.

  • 1
    Повторяется в одном контроллере или на разных контроллерах? И это только для претензий? Возможно, опубликуйте пример дублированного кода тоже.
  • 0
    GetClaimNotNull (Id)? Или (псевдо) doStuffWithClaim (делегат doSomethingWith (Claim c) {// do stuff})?
Показать ещё 1 комментарий
Теги:
asp.net-mvc
refactoring

4 ответа

7
Лучший ответ

Если для всех действий требуется требование, вы можете попытаться извлечь его в OnActionExecuting и установить Результат в ViewResult при ошибке. Если только некоторые действия требуют, возможно, ActionFilter, который проверяет, чтобы запрос был доступен до запуска метода, а если нет, задает правильный вид.

private Claim Claim { get; set; }

public override void OnActionExecuting( ActionExecutingContext context )
{
    this.Claim = GetClaim( int.Parse( context.RouteData["id"] ) );
    if (this.Claim == null)
    {
        context.Result = View( "ClaimNotFound" );
    }
}

ИЛИ

public class RequiresClaimIdAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting( ActionExecutingContext context )
    {
         var claim = GetClaim( int.Parse( context.RouteData["id"] ) );
         if (claim == null)
         {
              context.Result = new ViewResult
              {
                   ViewName = "ClaimNotFound",
                   ViewData = context.Controller.ViewData
              };
         }
         else
         {

              var property = context.Controller
                                    .GetType()
                                    .GetProperty( "Claim",
                                                   BindingFlags.Public
                                                   | BindingFlags.NonPublic
                                                   | BindingFlags.Instance);
              if (property != null)
              {
                   property.SetValue(context.Controller,claim);
              }
         }
    }
}

[RequiresClaimId]
public ActionResult AnAction( int id )
{
    this.Claim.Updated = DateTime.Now;
    ...
}
  • 0
    Откуда вы взяли имена классов ActionExecutingContext и ActionFilterAttribute ? Работаете ли вы с SeanX или вы хотели, чтобы реализация этих классов была неявной?
  • 0
    Неважно! Они из структуры MVC. Я полностью пропустил это, когда я посмотрел на вопрос. Я еще не знаком с MVC.
Показать ещё 1 комментарий
0

Обновление 2. Я не понимал, что этот вопрос касается MVC. Извини за это. Следующий ответ по-прежнему действителен с более общей точки зрения.

  • Первый рефакторинг, который я бы сделал, - это вызов GetClaim из метода. Похоже, что AnAction не мог (и, следовательно, не должен) зависеть от GetClaim. Он должен получать вместо этого Claim;

  • Во-вторых, я применил бы то же правило к вызову View. Вместо этого AnAction должен просто не допускать null Claim.

Код будет выглядеть так:

public ActionResult AnAction(Claim claim)
{           
    if (claim == null) throw new ArgumentNullException("cliam");

    // do stuff here
    ....
    return ....;
}

Вы могли бы сказать, что я просто отказался от ответственности за обработку null Claim в другое место. И это именно то, что я предлагаю вам сделать. Первоначальная проблема заключалась в том, что все ваши действия были слишком ответственны. Я применяю принцип единой ответственности.


Обновление 1. После создания рефакторинга, который я предложил, вы можете заменить все вызовы методам действий вызовом следующего метода. Возможно, это не решение, которое вы ищете, но это похоже на хорошее начало (по крайней мере для меня).

public static ActionResult InvokeAction(Func<Claim,ActionResult> action, int id)
{
    var claim = GetClaim(Id);
    if (claim == null) return View("ClaimNotFound");
    return action(claim);
}
  • 0
    Это не имеет никакого смысла с точки зрения MVC. В MVC действие вызывается платформой по имени на основе маршрута и данных маршрута. Возможно, он сможет составить претензию из запроса - если все данные доступны - но обычно вы получаете только идентификатор как часть URL-адреса RESTful, и вам нужно получить правильный идентификатор из базы данных.
  • 0
    Действительно, я пропустил тег asp.net-mvc по этому вопросу. извиняюсь
Показать ещё 1 комментарий
0

Вы можете использовать настраиваемое связующее устройство.

    public ActionResult AnAction(Claim claim)
    {
      if (!ModelState.IsValid)
        return View("Invalid claim"); 
    }

    public class ClaimModelBinder: IModelBinder
    {
      public object BindModel(BindingContext context) // don't remember exactly
      {
          var id = context.ValueProvider.GetRawValue(context.ModelName);
          if (string.IsNullOrEmpty(id))
          {
               context.ModelState.AddModelError(context.ModelName, "Empty id");
               return null;
          }
          var claim = GetClaim(id);
          if (claim == null)
          {
               context.ModelState.AddModelError(context.ModelName, "Invalid claim");
               return null;
          }
          return claim;
      }
    }

    // in global.asax.cs
    ModelBinders.Binders.Add(typeof(Claim), new ClaimCustomerBinder());

Почему это лучше, чем фильтр действий:

  • привязка автоматически подбирается, вам не нужен атрибут
  • у вас может быть несколько параметров заявки
  • работает для свойств претензии другого класса и даже IList в случае, если вы хотите передать несколько объектов претензии

    Но вы не можете обрабатывать определенные действия, т.е. вернуть View ( "NotFound" ) для этой ошибки. Однако это легко сделать, если вы разработаете собственное соглашение: например, ваш ModelBinder может выполнять

       context.ModelState.AddModelError(context.ModelName, new NotFoundException("Invalid claim", "ClaimNotFound"));
    
       public override void OnActionExecuting(ActionExecutingContext context)
       {
           var notfound = from o in ModelState 
                          from e in o.Value.Errors where 
                          where e.Exception is NotFoundException
                          select e.Exception;
           if (notfound.Count() == 1)
               context.Result = View { Name = notfound.First().ViewName };
       }
    

Альтернатива просто

  if (!ModelState.IsValid)
       // this view will just show all ModelState errors
       return View("IncorrectInput"); 

Это может показаться сложнее, но это хорошее разделение проблем. Модельное связующее отображает данные из HTTP на ваши классы, тогда вы можете либо проверить ModelState вручную, либо полагаться на некоторую автоматическую обработку. Это может показаться излишним для простых приложений, но если вы начнете получать дубликаты здесь и там, а ваши модели станут сложными, модельные вяжущие могут значительно упростить вашу жизнь, так как ваши действия с контроллером получат готовые к использованию объекты, и вы можете сконцентрироваться на реальном бизнес-коде вместо инфраструктуры/сантехники.

Это (я считаю), для чего предназначен ASP.NET MVC - вы можете создавать собственный набор соглашений вокруг него.

0

Если это логика проверки требований, которую вы хотите реорганизовать, лучшим вариантом будет написать настраиваемый фильтр авторизации. Вы сделали бы это, создав класс, который наследует от FilterAttribute и реализует IAuthorizationFilter.

У IAuthorizationFilter есть один метод: OnAuthorization, в котором вы должны поставить логику проверки требований. Вместо того, чтобы возвращать ViewResult (как вы это делаете выше), вы должны установить свойство Result объекта AuthorizationContext, которое будет передано в метод OnAuthorization. Он будет выглядеть примерно так:

public class RequiresClaim : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var claim = GetClaim(filterContext.RouteData["id"]);

        if (claim == null) { filterContext.Result = new ViewResult { ViewName = "ClaimNotFound" }; }
    }
}

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

  • 0
    Вы имели в виду ActionFilter, а не IAuthorizationFilter? Почему вы используете IAuthorizationFilter для задач без авторизации (но связанных с данными)?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню