GithubHelp home page GithubHelp logo

Comments (12)

ploeh avatar ploeh commented on September 15, 2024

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?

from hyprlinkr.

dhilgarth avatar dhilgarth commented on September 15, 2024

I know about that constructor overload. The problem is that my controllers get their RouteLinker instances injected.
Because of this, they

  1. have only one RouteDispatcher
  2. have no say in creation of the RouteLinker

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.

from hyprlinkr.

ploeh avatar ploeh commented on September 15, 2024

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 :)

from hyprlinkr.

dhilgarth avatar dhilgarth commented on September 15, 2024

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.

from hyprlinkr.

ploeh avatar ploeh commented on September 15, 2024

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);
    }
}

from hyprlinkr.

dhilgarth avatar dhilgarth commented on September 15, 2024

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.

from hyprlinkr.

ploeh avatar ploeh commented on September 15, 2024

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.

from hyprlinkr.

dhilgarth avatar dhilgarth commented on September 15, 2024

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.

from hyprlinkr.

ploeh avatar ploeh commented on September 15, 2024

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...

from hyprlinkr.

dhilgarth avatar dhilgarth commented on September 15, 2024

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.

from hyprlinkr.

ploeh avatar ploeh commented on September 15, 2024

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).

from hyprlinkr.

dhilgarth avatar dhilgarth commented on September 15, 2024

No need to feel bad about it, it's your library, you say what goes in and what not.

from hyprlinkr.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.