ploeh / Hyprlinkr

A URI building helper library for ASP.NET Web API
MIT License
197 stars 34 forks source link

Support for complex type as parameter to GetUri #16

Closed jfloodnet closed 11 years ago

jfloodnet commented 11 years ago

Is it possible to have Hyprlinkr support complex type parameters inside the MethodCallExpression passed to linker.GetUri?

The scenario I'm trying to accomplish is in trying to get the Uri of an action that accepts a complex type as the key.

I.e.

public Contact Get(ContactQuery query)
{
    var contact = this.repository.GetContact(query.Id, query.UserName);
    contact.Link = new Link()
    {
        Href = _linker.GetUri<ContactsController>(x => x.Get(query)).ToString(),
        Rel = "self",
        Title = contact.ToString()
    };
    return contact;
}

Where ContactQuery is defined as:

[FromUri]
public class ContactQuery
{
    public int Id { get; set; }
    public string UserName { get; set; }
}

When stepping into my CustomRouteDispatcher, I can see that the route linker has passed the routeValues with the type name as the value for the "query" key.

routeValues["query"]
    "WebAPI.Controllers.Models.ContactQuery"

I "think" this is because the GetValue implementation in RouteLinker only calls ToString()

private static object GetValue(MethodCallExpression methodCallExp,
        ParameterInfo p)
    {
        var arg = methodCallExp.Arguments[p.Position];
        var lambda = Expression.Lambda(arg);
        return lambda.Compile().DynamicInvoke().ToString();
    }

Ideally, the values of the ContactQuery object, (Id and UserName) need to be pulled out and added to the route values.

Is this possible with the current implementation or is there a way that the RouteLinker can be extended so that I can customise this behaviour?

Appreciate your help.

Joe.

ploeh commented 11 years ago

Thank you for asking. I'm pretty sure that this is currently impossible. It seems like a reasonable feature request, however :)

You could argue that the internal implementation could (or should) be expanded to make this possible. In the current example, the code could use Reflection to pull the complex object apart and get the value of each property. However, what about more complex (nested) models?

Based on my experience, the next feature request then would be the ability to read protected and/or internal properties or fields, and I don't really wish to go in that direction.

What I can do, on the other hand, is to open up the API by moving this part of the code to an injected Strategy:

var routeValues = methodCallExp.Method.GetParameters()
    .ToDictionary(p => p.Name, p => GetValue(methodCallExp, p));

This would give you the option to provide a custom implementation of the Strategy where you can serialize your ContactQuery instance to route values.

Would that be an acceptable solution?

Or can you suggest a better approach?

jfloodnet commented 11 years ago

Thanks for your response. I think opening up another point for extension is the way to go, otherwise you'd have to build an entirely new tool just for building routeValues :)

Something with this signature would suffice:

IDictionary<string, object> GetRouteValues(MethodCallExpression methodCallExp)

Which makes me realise, I could do this in the Dispatch method by replacing the routeValues entirely as the signature of the Dispatch method should give me all I need:

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

But I think your suggestion will make this an explicit concept within Hyrprlinkr.

jfloodnet commented 11 years ago

Yep, I've got it working inside the call to Dispatch, but it doesn't feel like its the right place to do it. What do you think?

floodg commented 11 years ago

Just customise it yourself and upload your customs

ploeh commented 11 years ago

I agree that while it may be technically possible to do it in a custom dispatcher, it's not the right place.

jfloodnet commented 11 years ago

OK, I've teased out the Strategy in the way you were thinking. I'll try to make a pull request over the weekend when I get the chance.

ploeh commented 11 years ago

Oh, that would have been nice, but I've just now uploaded version 0.7.0 to the nuget gallery, with this feature... :/

You can supply a custom IRouteValuesQuery to RouteLinker. In your case, I think you should be able to derive from the default ScalarRouteValuesQuery and override the GetParameterValues method.

Please let me know if this addresses your issue.

jfloodnet commented 11 years ago

Ah, nice work! Yours is that little bit nicer than mine; I like your naming especially :) I'll give it a try, looks like it'll do the trick. Thanks for your help Mark, its good to know I've contributed in some way!

jfloodnet commented 11 years ago

Ok, back at work, updated the Hyprlinkr package and overrided GetParameterValues of the ScalarRouteValuesQuery . Works a charm. :)

Thanks for your effort Mark, Hyprlinkr is an awesome tool!