C# 如何删除操作中的重复代码?
我在mvc应用程序中多次重复以下代码C# 如何删除操作中的重复代码?,c#,asp.net-mvc,refactoring,C#,Asp.net Mvc,Refactoring,我在mvc应用程序中多次重复以下代码 public ActionResult AnAction(int Id) { var claim = GetClaim(Id); if (claim == null) { return View("ClaimNotFound"); } // do stuff here .... ret
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>());
}
public ActionResult索赔(int-Id)
{
var索赔=GetClaim(Id);
如果(索赔==null)
{
返回视图(“ClaimNotFound”);
}
返回视图(“索赔”,索赔);
}
公共行动结果MedicalPV(int Id)
{
var索赔=GetClaim(Id);
如果(索赔==null)
{
返回视图(“ClaimNotFound”);
}
返回PartialView(claim.MedCerts.AsQueryable());
}
通常,我需要访问视图中的对象。此特定代码仅在一个控制器中使用,但在具有不同对象和视图的其他控制器中可能需要类似的代码。如果所有操作都需要声明,则可以尝试在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" );
}
}
或
如果要重构的是声明检查逻辑,那么最好的选择是编写一个自定义授权过滤器。您可以通过创建一个继承自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" }; }
}
}
public ActionResult AnAction(Claim claim)
{
if (claim == null) throw new ArgumentNullException("cliam");
// do stuff here
....
return ....;
}
一旦你有了这个过滤器,你就可以简单地把这个属性放在你想要进行声明检查的每个动作方法上。如果您希望对控制器中的每个操作进行索赔检查,您可以将其放在控制器上。更新2:我不知道这个问题是关于MVC的。很抱歉。从更一般的角度来看,下面的答案仍然有效
- 我要做的第一个重构是取消方法的
调用。看起来,GetClaim
无法(因此不应该)依赖于a操作
。它应该直接接收GetClaim
索赔
- 其次,我将对
调用应用相同的规则。相反,视图
应该不允许空的a操作
声明
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" }; }
}
}
public ActionResult AnAction(Claim claim)
{
if (claim == null) throw new ArgumentNullException("cliam");
// do stuff here
....
return ....;
}
您可能会说,我只是将处理null索赔的责任转移到了另一个地方。这正是我建议你做的。最初的问题是你所有的几种行动方法都有太多的责任。我只是在申请签证
更新1:在进行了我建议的重构之后,您可以通过调用以下方法来替换对action方法的所有调用。这可能不是您正在寻找的解决方案,但它看起来是一个良好的开端(至少对我来说)
publicstaticactionresult-InvokeAction(Func-action,int-id)
{
var索赔=GetClaim(Id);
if(claim==null)返回视图(“ClaimNotFound”);
退货诉讼(索赔);
}
您可以使用自定义模型活页夹
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());
为什么它比action filter更好:
- 活页夹自动拾取,不需要属性
- 您可以有几个索赔参数
- 适用于某些其他类的声明属性,甚至IList,以防您要传递多个声明对象
但是您不能处理特定的操作,例如,返回视图(“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映射到类,然后您可以自由地手动检查模型状态或依赖于一些自动处理。对于简单的应用程序来说,这看起来有些过分,但是如果你开始到处重复,并且你的模型变得复杂,那么模型绑定可以极大地简化你的生活,因为你的控制器操作将准备好使用对象,你可以专注于真正的业务代码,而不是基础设施/管道
这就是(我相信)ASP.NET MVC的目的——您可以围绕它自由构建自己的一套约定。在同一个控制器或不同的控制器中重复?也只是为了索赔?也许还会发布一个重复代码的示例。GetClaimNotNull(Id)?或者(伪)doStuffWithClaim(委托doSomethingWith(Claim c){//do stuff})?我会添加“TryGetClaim”,并让GetClaim负责抛出异常。毕竟,如果你告诉某人“给我X”,你是在给他们下命令,而不是请求,否则会是“请你给我X好吗?”。这将使每个操作至少丢失if+throw块。您从何处获取类名ActionExecutingContext
和ActionFilterAttribute
?您是使用SeanX还是希望这些类的实现是隐式的?没关系!它们来自MVC框架。当我看这个问题时,我完全没有注意到这一点。我还不熟悉MVC。需要一些小的代码修改来编译,但解决了我的问题。从MVC的角度看,这毫无意义。在MVC中,该操作由框架根据路由和路由数据按名称调用。如果所有数据都可用的话,他可能能够从一个请求中构成一个声明,但通常情况下,您只会得到作为RESTful URL一部分的id,并且需要从数据库中检索正确的id