3

我正在尝试使用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" });

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

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

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

更新: 在 github 上打开的问题: https ://github.com/ploeh/Hyprlinkr/issues/17

4

3 回答 3

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));
    }
}
于 2013-04-02T20:21:15.967 回答
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 实例。

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

于 2013-03-12T22:59:52.033 回答
1

Dimitry 的解决方案使我大部分时间都到达了我想要的位置,但是 routeName ctor 参数是一个问题,因为 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);
    }
}
于 2014-01-27T12:50:08.087 回答