如何使用Hyprlinkr生成HTTP POST操作的链接?

时间:2013-02-24 01:27:54

标签: asp.net-web-api hyprlinkr

我正在尝试使用Hyprlinkr生成HTTP Post操作的URL。我的控制器看起来像这样:

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

3 个答案:

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

第二种选择是要走的路:

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实例。

警告:我写这篇文章的时间已经很晚了,而且我没有尝试编译上面的代码,但我想我宁愿在这里尝试一下你的答案,而不是再等几天。

答案 2 :(得分:1)

Dimitry的解决方案让我大部分都能到达我想要的地方,但是routeName ctor param是个问题,因为StructureMap不知道该放什么。内部hyprlink使用UrlHelper生成URI,并且想要知道要使用的路由名称

此时,我知道为什么URI生成是如此棘手,因为它与路由配置中的路由名称相关联,并且为了支持POST,我们需要将该方法与正确的路由名相关联,而不是在调度员时间知道。默认的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);
    }
}