如何在我的操作中删除重复的代码?

时间:2009-11-04 19:39:33

标签: c# asp.net-mvc refactoring

我在mvc app中重复了几次以下代码。

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

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

到目前为止,这种模式使用了4次,而且变得越来越难看。重构它的最佳方法是什么?

修改
几个示例用法

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

通常我需要访问视图中的对象。这个特殊的代码只在一个控制器中使用,但我可能需要在其他控制器中使用不同的对象和视图。

4 个答案:

答案 0 :(得分:7)

如果所有操作都需要声明,那么您可以尝试在OnActionExecuting中检索它,并在失败时将Result设置为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" );
    }
}

OR

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

答案 1 :(得分:0)

如果是要重构的声明检查逻辑,最好的选择是编写自定义授权过滤器。您可以通过创建一个继承自FilterAttribute并实现IAuthorizationFilter的类来完成此任务。

IAuthorizationFilter有一个方法:OnAuthorization,这是你提出声明检查逻辑的地方。您不必返回ViewResult(如上所述),而是设置传递给OnAuthorization方法的AuthorizationContext对象的Result属性。它看起来像这样:

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

一旦有了这个过滤器,你就可以简单地将该属性放在你希望进行索赔检查的每个动作方法上。如果您希望对控制器中的每个操作进行声明检查,则可以将其放在控制器上。

答案 2 :(得分:0)

更新2 :我没有意识到这个问题是关于MVC的。对于那个很抱歉。无论如何,从更通用的观点来看,下面的答案仍然有效。

  • 我要做的第一次重构是从方法中调出GetClaim。看起来AnAction无法(因此不应该)依赖于GetClaim。它应该直接收到Claim而不是

  • 其次,我会将相同的规则应用于View来电。相反,AnAction应该不允许空Claim

代码如下所示:

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

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

您可能会说我只是将处理null Claim的责任移到另一个地方。而这正是我建议你做的。最初的问题是你所有的几个动作方法都承担了太多的责任。我只是应用Single responsibility principle


更新1 :完成我建议的重构后,您可以通过调用以下方法替换所有对操作方法的调用。它可能不是您正在寻找的解决方案,但它看起来是一个好的开始(至少对我而言)。

public static ActionResult InvokeAction(Func<Claim,ActionResult> action, int id)
{
    var claim = GetClaim(Id);
    if (claim == null) return View("ClaimNotFound");
    return action(claim);
}

答案 3 :(得分: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());

为什么它比动作过滤器更好:

  • 自动拾取活页夹,您不需要属性
  • 您可以拥有多个声明参数
  • 适用于某些其他类的Claim属性,甚至IList,以防您想要传递多个Claim对象

    但您无法处理特定操作,即返回此错误的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的用途 - 您可以自由地围绕它构建自己的约定。