2013-02-24 51 views
3

我正在尝试使用Hyprlinkr来生成HTTP Post操作的URL。我的控制器看起来是这样的:如何使用Hyprlinkr生成HTTP POST操作的链接?

public class MyController : ApiController { 
    [HttpPost] 
    public void DoSomething([FromBody]SomeDto someDto) { 
     ... 
    } 
} 

与这条路线:

routes.MapHttpRoute(
      name: "MyRoute", 
      routeTemplate: "dosomething", 
      defaults: new { controller = "My", action = "DoSomething" }); 

我希望得到一个简单的网址:http://example.com/dosomething,但它不工作。我尝试了两种方法:

1)routeLinker.GetUri(c => c.DoSomething(null)) - 抛出NullReferenceException

2)routeLinker.GetUri(c => c.DoSomething(new SomeDto())) - 产生无效的网址: http://example.com/dosomething?someDto=Namespace.SomeDto

更新: 期开于github上: https://github.com/ploeh/Hyprlinkr/issues/17

+0

这几乎是逐字我在hyprlinkr维基提出的一个问题:https://github.com/ploeh/Hyprlinkr/issues/28我现在,我没有提高之前检查所以首先我很尴尬。今后,我会先在这里发布我的问题! – 2014-01-27 11:43:24

回答

3

我发现了一个解决方法,松散地基于Mark's answer。这个想法是遍历每个路由参数,并删除那些应用了[FromBody]属性的属性。这种方式调度员不需要为每个新的控制器或操作进行修改。

public class BodyParametersRemover : IRouteDispatcher { 
    private readonly IRouteDispatcher _defaultDispatcher; 

    public BodyParametersRemover(String routeName) { 
     if (routeName == null) { 
      throw new ArgumentNullException("routeName"); 
     } 
     _defaultDispatcher = new DefaultRouteDispatcher(routeName); 
    } 

    public Rouple Dispatch(
     MethodCallExpression method, 
     IDictionary<string, object> routeValues) { 

     var routeKeysToRemove = new HashSet<string>(); 
     foreach (var paramName in routeValues.Keys) { 
      var parameter = method 
       .Method 
       .GetParameters() 
       .FirstOrDefault(p => p.Name == paramName); 
      if (parameter != null) { 
       if (IsFromBodyParameter(parameter)) { 
        routeKeysToRemove.Add(paramName); 
       } 
      } 
     } 
     foreach (var routeKeyToRemove in routeKeysToRemove) { 
      routeValues.Remove(routeKeyToRemove); 
     } 
     return _defaultDispatcher.Dispatch(method, routeValues); 
    } 

    private Boolean IsFromBodyParameter(ParameterInfo parameter) { 
     var attributes = parameter.CustomAttributes; 
     return attributes.Any(
      ct => ct.AttributeType == typeof (FromBodyAttribute)); 
    } 
} 
+1

+1基于惯例的方法是另一种选择。在这里,您不必在每次添加新控制器时都编辑自定义路由调度程序,但另一方面,您必须记住添加属性 - 这是一种折衷,但如果您更喜欢它,那就是精细。然而,我想指出的一件事是,您可能不希望从'routeValues'中删除路由参数,因为这会改变字典,并且会影响整个Web API的工作方式。 – 2013-04-03 07:38:32

+0

ctor参数“routeName”从哪里来?我试图用StructureMap来连接它,当然它是呕吐.... – 2014-01-27 12:16:21

1

的第二种选择是要走的路:

routeLinker.GetUri(c => c.DoSomething(new SomeDto())) 

但是,使用POST方法时,需要删除生成的URL的模型部分。你可以做一个自定义路由调度员:

public ModelFilterRouteDispatcher : IRouteDispatcher 
{ 
    private readonly IRouteDispatcher defaultDispatcher; 

    public ModelFilterRouteDispatcher() 
    { 
     this.defaultDispatcher = new DefaultRouteDispatcher("DefaultApi"); 
    } 

    public Rouple Dispatch(
     MethodCallExpression method, 
     IDictionary<string, object> routeValues) 
    { 
     if (method.Method.ReflectedType == typeof(MyController)) 
     { 
      var rv = new Dictionary<string, object>(routeValues); 
      rv.Remove("someDto"); 
      return new Rouple("MyRoute", rv); 
     } 

     return this.defaultDispatcher.Dispatch(method, routeValues); 
    } 
} 

现在传递定制调度到您的RouteLinker实例。注意:我写这个的时候已经很晚了,我还没有试图编译上面的代码,但是我想我宁愿在这里抛出一个尝试的答案,而不是等待几天。

+0

每次添加或删除POST操作时,我都需要修改此调度程序。或者它是否被建议作为临时解决方法,直到Hyprlinkr开箱即可处理[HttpPost]? – Dmitry 2013-03-16 15:43:17

+0

这就是你必须要做的。这不是Hyprlinkr中的一个错误,除非你可以定义一种算法来使这种自定义调度程序变得冗余。 – 2013-03-16 15:51:04

1

德米特里的解决方案让我最的方式,我想在那里,但是routeName构造函数PARAM是一个问题,因为StructureMap不知道要放什么东西在那里。内部hyprlink使用UrlHelper来生成URI,并且想要知道要使用的路由名称

在那一点上,我明白了为什么URI生成非常棘手,因为它与路由配置中的路由名称绑定在一起,并且为了支持POST,我们需要将该方法与正确的routename相关联,并且这在调度器时间是未知的。默认的hyprlinkr假设只有一个名为“DefaultRoute”的路由配置

我改变了Dimitry的代码,并采用了基于约定的方法,其中以“Get”开头的控制器方法映射到名为“Get”的路由,以“Add”开头的控制器方法被映射到名为“Add”的路由。

我不知道是否有更好的方法与正确的命名routeConfig关联的方法?

 public class RemoveFromBodyParamsRouteDispatcher : IRouteDispatcher 
{ 
    private static readonly ILog _log = LogManager.GetLogger(typeof (RemoveFromBodyParamsRouteDispatcher)); 

    public Rouple Dispatch(MethodCallExpression method, 
          IDictionary<string, object> routeValues) 
    { 
     var methodName = method.Method.Name;  
     DefaultRouteDispatcher defaultDispatcher; 

     if (methodName.StartsWith("Get")) 
      defaultDispatcher = new DefaultRouteDispatcher("Get"); 
     else if (methodName.StartsWith("Add")) 
      defaultDispatcher = new DefaultRouteDispatcher("Add"); 
     else 
      throw new Exception("Unable to determine correct route name for method with name " + methodName); 

     _log.Debug("Dispatch methodName=" + methodName); 

     //make a copy of routeValues as contract says we should not modify 
     var routeValuesWithoutFromBody = new Dictionary<string, object>(routeValues); 

     var routeKeysToRemove = new HashSet<string>(); 
     foreach (var paramName in routeValuesWithoutFromBody.Keys) 
     { 
      var parameter = method.Method 
            .GetParameters() 
            .FirstOrDefault(p => p.Name == paramName); 
      if (parameter != null) 
       if (IsFromBodyParameter(parameter)) 
       { 
        _log.Debug("Dispatch: Removing paramName=" + paramName); 

        routeKeysToRemove.Add(paramName); 
       } 
     } 

     foreach (var routeKeyToRemove in routeKeysToRemove) 
      routeValuesWithoutFromBody.Remove(routeKeyToRemove); 

     return defaultDispatcher.Dispatch(method, routeValuesWithoutFromBody); 
    } 

    private static bool IsFromBodyParameter(ParameterInfo parameter) 
    { 
     //Apparently the "inherit" argument is ignored: http://msdn.microsoft.com/en-us/library/cwtf69s6(v=vs.100).aspx 
     const bool msdnSaysThisArgumentIsIgnored = true; 
     var attributes = parameter.GetCustomAttributes(msdnSaysThisArgumentIsIgnored); 

     return attributes.Any(ct => ct is FromBodyAttribute); 
    } 
}