Closed dhilgarth closed 12 years ago
There's already a constructor overload enabling you to provide a custom provider, and you can make that implementation as sophisticated as you'd like. One of the main scenarios for that extensibility mechanism is exactly to support multiple routes.
I've successfully used custom providers to address APIs with multiple routes. Is there any reason you can't do that?
I know about that constructor overload. The problem is that my controllers get their RouteLinker
instances injected.
Because of this, they
What would you do, if you would need to write the sample code from this issue?
I think it is a common requirement to use different routes in one controller.
Imagine that you can't change the API of RouteLinker, and that you'd need to write the sample code from the Stack Overflow question. Consider the signature of IRouteDispatcher:
Rouple Dispatch(MethodInfo method, IDictionary<string, object> routeValues);
This method is going to be invoked every time you call RouteLinker.GetUri. How could you select a specifically named route from within this method? What would your selection criterion be?
My apologies for not just giving you my answer, but first of all I believe it helps you more if I help you reach an answer by yourself, and secondly, you may come up with an answer which is better than mine :)
Actually, I already did implement this requirement without changing the API. I simply created an extension method:
public static Uri GetUri<T>(this RouteLinker linker, ... methodExpr, string routeName)
{
return new RouteLinker(linker.Request, new DefaultRouteDispatcher(routeName)).GetUri(methodExpr);
}
I actually created two extension methods. The other accepted an IRouteDispatcher instance and passed that to the constructor.
That works quite well. With one problem: because RouteLinker is disposable, I get code analysis warnings that I should dispose of the newly created instance. However, if I were to do that, I would also dispose the request... I will answer your comment on that issue tomorrow.
Given the "Posts Routes" route in the accepted Stack Overflow answer, and assuming that there's also an "API Default" route, wouldn't the following dispatcher do the trick?
public class MyDispatcher : IRouteDispatcher
{
public Rouple Dispatch(MethodInfo method, IDictionary<string, object> routeValues)
{
if (method.ReflectedType == typeof(PostsController) &&
method.Name == "CommentsForPosts")
return new Rouple("Posts Routes", routeValues);
return new Rouple("API Default", routeValues);
}
}
Yes, indeed it would. And it would be a much cleaner solution... Obviously I didn't think quite far enough when implementing my solution. That's why I like discussing things with you :-)
In my opinion it is quite a common scenario to have nested resources and thus multiple routes.
Wouldn't it be an idea to provide support for these scenarios in Hyprlinkr by means of an interface and maybe default implementation?
Something like this:
public interface ISpecialRouteDispatcher : IRouteDispatcher
{
bool CanDispatch(MethodInfo method);
}
Usage would be like this:
public class MyDispatcher : IRouteDispatcher
{
List<ISpecialRouteDispatcher> _specialDispatchers;
DefaultRouteDispatcher _defaultDispatcher;
public MyDispatcher(IEnumerable<ISpecialRouteDispatcher> specialDispatchers)
{
if(specialDispatchers == null)
throw new ArgumentNullException("specialDispatchers");
_specialDispatchers = specialDispatchers.ToList();
_defaultDispatcher = new DefaultRouteDispatcher();
}
public Rouple Dispatch(MethodInfo method, IDictionary<string, object> routeValues)
{
var specialDispatcher = _specialDispatchers.SingleOrDefault(x => x.CanDispatch(method));
if (specialDispatcher != null)
return specialDispatcher.Dispatch(method, routeValues);
return _defaultDispatcher.Dispatch(method, routeValues);
}
}
Like this you could cleanly encapsulate each new route either in its own class that directly implements ISpecialRouteDispatcher
or - better IMHO - by creating an instance of a default implementation. This could than be registered with the DI container to get them automatically injected into MyDispatcher
.
builder.RegisterInstance(SingleRouteDispatcher.Create<PostsController>("Posts Routes", x => x.CommentsForPosts(0, 0)))
.As<ISpecialRouteDispatcher>();
builder.RegisterType<MyDispatcher>.As<IRouteDispatcher>();
That wouldn't change the existing API but would still add out-of-the-box support for those special routes.
That's certainly a nice, object-oriented way to implement it.
Another would be to use a Chain of Responsibility, e.g.
public class PostsDispatcher : IRouteDispatcher
{
private readonly IRouteDispatcher nextDispatcher;
public PostsDispatcher(IRouteDispatcher nextDispatcher)
{
this.nextDispatcher = nextDispatcher;
}
public Rouple Dispatch(MethodInfo method, IDictionary<string, object> routeValues)
{
if (method.ReflectedType == typeof(PostsController) &&
method.Name == "CommentsForPosts")
return new Rouple("Posts Routes", routeValues);
return this.nextDispatcher.Dispatch(method, routeValues);
}
}
This would also allow you to compose fine-grained route dispatchers, if that's your thing.
My point here is that there are various good ways of doing this, and all of them can be easily implemented without adding anything to the public Hyprlinkr API. The ISpecialRouteDispatcher interface that you propose doesn't need to be defined by Hyprlinkr.
In my opinion, Hyprlinkr should make various approaches possible, but unless there's a good reason for it, it shouldn't mandate one approach over another.
How would you automatically wire up something like this with a DI/IoC container?
The "problem" I see with your argumentation is that I would need to re-implement this approach - or the one I proposed - in every new project...
I am actually a big fan of the 80/20 rule. And in this case, I think we can find several approaches which would simplify working with multiple routes and which would fit 80% of the use cases. IMHO it wouldn't harm Hyprlinkr if it even provided means for easy usage of both of our approaches.
How would I wire something like that with a DI Container? Well, that depends on the container... :)
If you wanted to use the same approach in many different projects, you could re-implement it - or you could package it in your own glue library. Obviously I understand it would be easier for you if Hyprlinkr just included the interface you suggest, but it's hardly an insurmountable obstacle if it doesn't.
As you can probably sense, I'm not too thrilled by this suggestion. I think my resistance can really be reduced to this: the MSDN Class Library design guidelines has this to say:
Do provide at least one type that is an implementation of an interface.
The problem that I really have is that I don't see any appropriate default implementation of the interface. Well you could argue that the default dispatcher should also implement the CanDispatch method and always return true, but apart from that, then what? The discoverability of such an interface strikes me as being very poor...
Yes, I already realized that you don't like that in Hyprlinkr. I suggest the following: I will create an implementation in a Hyprlinkr.Contrib repository over at my github account and than you can have a look at what I created and based on this, maybe re-think your decision.
Please tell me if Hyprlinkr.Contrib is OK with you as the project name.
Making DefaultRouteDispatcher
implement CanDispatch
isn't something I would do, it doesn't feel right.
The DI container is really not that important. IIRC you use Windsor, you could show it with that one.
Now I almost feel bad about it :/
IMO a library first and foremost exists to make something possible in as flexible manner as possible. I think Hyprlinkr already does that pretty well.
When it comes to addressing multiple routes, there are lots of (good) ways you can do it, and it's much a matter of personal taste whether one is better than the other. (I have even more options to show you if you don't believe me.) My main concern is that if we provide an API for one of these ways, we may be cutting off some other options. Granted, a developer could always drop down to the lower-level RouteLinker and IRouteDispatcher types to follow another path, but in my experience many people feel best if they think they are writing idiomatic code for a given library.
In the end it's a trade-off, so I'd still gladly choose to railroad people if I think the value to be gained is sufficient. However, in this case I don't think the value exceeds the disadvantage, but I may be wrong. However, from the code you posted here, I don't see how Hyprlinkr can provide much more than yet another interface and perhaps a single Composite.
In the end, it's a question of focus: the purpose of Hyprlinkr is to be a helper library for generating (and parsing) links. It shouldn't (IMO) dictate a particular coding style - it's not that kind of library (but AutoFixture is).
You are welcome to make your own contrib project, and I don't feel that I should have any say in how you'd like to name it (but thanks for asking).
No need to feel bad about it, it's your library, you say what goes in and what not.
Especially with multiple routes for nested resources it is necessary to use different route dispatchers.
The route dispatcher is only used in the call to GetUri, and thus an overload that accepts an
IRouteDispatcher
is possible.Use case:
Implementation is trivial and can be done by me, if you agree with this overload.