Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does not seem to be working with web api 2 properly ... #30

Open
RahmanM opened this issue Feb 23, 2014 · 9 comments
Open

Does not seem to be working with web api 2 properly ... #30

RahmanM opened this issue Feb 23, 2014 · 9 comments

Comments

@RahmanM
Copy link

RahmanM commented Feb 23, 2014

Hi,

I am using it in a web api 2 project along with the attribute routing. I have a function as below:

// GET /api/authors/1/books
[Route("~/api/authors/{authorId:int}/books")]
public IEnumerable GetByAuthor(int authorId) { ... }

When calling this function as:

var link=linker.GetUri(a=>a.GetByAuther(id)).ToString();

It returns as http://localhost/api/auther/1

regards,

rahman

@ploeh
Copy link
Owner

ploeh commented Feb 23, 2014

There's no active code in Hyprlinkr that takes attribute routing into account.

It looks as though this would need to be explicitly added. However, care must be taken to ensure that the addition of such a feature doesn't break Hyprlinkr's compatibility with ASP.NET Web API 1, so either the implementation would need to be sufficiently abstract, or it would be necessary to explicitly add a Hyprlinkr.WebApi2 extension library.

I'm going to tentatively add the jump in tag on this issue, because I think it sounds like a great task for a new contributor to take on. On the other hand, I've not performed an in-depth analysis of what this would entail, so there's no guarantee that this is an 'easy' task.

@jakejscott
Copy link

I agree this would be a great feature to add, I jumped straight into using Hyprlinkr, and only after playing around with it for a couple hours and I couldn't get it working with Attribute routing I decided to check if it was supported and found this issue.

Perhaps you'd be kind enough to mention that it doesn't support Attribute Routing just yet :)

Cheers
Jake

@ploeh
Copy link
Owner

ploeh commented Jul 17, 2014

Indeed; it say so right here :)

@stanshillis
Copy link

There is a fairly simple solution to attribute based routing and Hyprlinkr as long as Name property on the Route attribute is set. Then it's just a matter of implementing a dispatcher that looks up that route name from the attribute at run time. Seems to work on sample routes that i tried.

@MatthewRichards
Copy link
Contributor

I came across this limitation and did some digging. As @stanshillis says it's easy enough to write an IRouteDispatcher that grabs the Name of the Route attribute and uses that. However if you have an unnamed Route attribute it seems rather harder.

I wonder whether the WebAPI designers actually want us to generate URIs in this scenario. As far as I can tell you can't create links using UrlHelper.Link without a named route either - most attribute routes are stored in the route table within a single RouteCollectionRoute, and only the named routes are given a LinkGenerationRoute which then allows link generation code (either UrlHelper.Link or Hyprlinkr) to work.

However, giving names to all my routes felt a bit unnecessary so I wanted to find a workaround anyway. I've come up with one that dynamically adds named routes as necessary at runtime... I suspect this is as bad an idea as it sounds, but it works for me in simple cases so far 😄 Here it is in case it's of use to anyone else (it's just an implementation of Hyprlinkr's IRouteDispatcher plug-in interface):

https://gist.github.com/MatthewRichards/3177e0c5a5181de8f1e8

The only other solution I could see is to parse the Route attribute's route template in the Hyprlinkr code. This involves rather less unpleasant messing around with the routing table, but is duplicating the work of parsing the template which is likely to be fragile. Hopefully there's an even better solution I haven't spotted.

I have to say though even with my hacking about of the Route table I prefer the result to the lack of type safety you'd get by not using Hyprlinkr at all!

@ploeh
Copy link
Owner

ploeh commented Feb 18, 2015

@MatthewRichards Thanks for sharing your solution.

As you hint, I'm also concerned that the workaround of assigning named routes dynamically may not be a good idea. I'd really like to know what the proper way to get a URL for an unnamed route is. If anyone figures that out, I'd be interested to hear it.

The first part of your shared code, where the code uses the name from the attribute if it exists, seems to be safe to add to Hyprlinkr, if anyone fancy sending a pull request 😄

@jkodroff
Copy link

Seems like this issue should be closed due to the above PR?

@ploeh
Copy link
Owner

ploeh commented Jun 14, 2015

@jkodroff The original issue is still unresolved, as #36 only enables named attribute routes.

It'd be nice to also get support for unnamed attribute routes, but I haven't looked into how (if at all) that would be possible to do.

@dhilgarth
Copy link
Contributor

dhilgarth commented Feb 2, 2017

I have implemented this in a rather hacky way in my project. So far, it is working for my scenarios. Maybe this can be a starting point for someone who wants to implement it in Hyprlinkr:

public static class UrlHelperEx
{
    public static Uri GetLink<T, TResult>(HttpRequestMessage request, Expression<Func<T, TResult>> method)
    {
        return GetUri(request, method.GetMethodCallExpression()) ?? new RouteLinker(request).GetUri(method);
    }

    public static Uri GetLink<T>(HttpRequestMessage request, Expression<Action<T>> method)
    {
        return GetUri(request, method.GetMethodCallExpression()) ?? new RouteLinker(request).GetUri(method);
    }

    private static Uri GetBaseUri(HttpRequestMessage request)
    {
        var authority = request.RequestUri.GetLeftPart(UriPartial.Authority);
        return new Uri(authority);
    }

    private static Uri GetUri(HttpRequestMessage request, MethodCallExpression method)
    {
        var routeAttribute = method.Method.GetCustomAttribute<RouteAttribute>(false);
        if (routeAttribute == null)
            return null;
        var routePrefixAttribute = method.Object?.Type.GetCustomAttribute<RoutePrefixAttribute>(false);
        var routeTemplate = routeAttribute.Template;
        if (routePrefixAttribute != null)
            routeTemplate = $"{routePrefixAttribute.Prefix}/{routeTemplate}";

        var subRoutes = request.GetConfiguration().Routes.Where(x => x is IEnumerable<IHttpRoute>).Cast<IEnumerable<IHttpRoute>>().SelectMany(x => x);
        var matchingSubRoutes =
            subRoutes.Where(
                x =>
                    x.RouteTemplate == routeTemplate
                    && ((HttpActionDescriptor[])x.DataTokens["actions"])[0].SupportedHttpMethods.Contains(
                        request.Method));
        var routeValues = new ScalarRouteValuesQuery().GetRouteValues(method);
        routeValues.Add("httproute", null);
        var uris =
            matchingSubRoutes.Select(
                                 x =>
                                     x.GetVirtualPath(request, routeValues))
                             .ToArray();

        var relativeUri = uris.SingleOrDefault()?.VirtualPath;
        if (relativeUri == null)
            return null;

        var baseUri = GetBaseUri(request);
        return new Uri(baseUri, relativeUri);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants