How to set up Ninject DI to create Hyprlinkr RouteLinker instances
Asked Answered
L

3

5

I have an MVC4 Web API project and I making use of Mark Seemann's Hyprlinkr component to generate Uris to linked resources. (Customer -> Addresses for example).

I have already followed Mark's guide on Dependency injection with Web API (changing appropriately for Ninject) bit I can't quite work out what I should do to inject a IResourceLinker into my controllers.

Following Mark's guide my IHttpControllerActivator.Create create method looks like this:

IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
    var controller = (IHttpController) _kernel.GetService(controllerType);

    request.RegisterForDispose(new Release(() => _kernel.Release(controller)));

    return controller;
}

It is in this method that the Hyprlinkr readme suggests to create the RouteLinker. Unfortunately I'm not sure how to register this with Ninject.

I can't just bind like below, as this results in multiple bindings:

_kernel.Bind<IResourceLinker>()
    .ToMethod(context => new RouteLinker(request))
    .InRequestScope();

I've got rebind working like this:

_kernel.Rebind<IResourceLinker>()
    .ToMethod(context => new RouteLinker(request))
    .InRequestScope();

But I'm concerned that changing the ninject binding graph is potentially a bad thing to do on every request.

What is the best way to achieve this?


Update following the request from Paige Cook

I'm using rebind here:

IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
    _kernel.Rebind<IResourceLinker>()
        .ToMethod(context => new RouteLinker(request))
        .InRequestScope();

    var controller = (IHttpController) _kernel.GetService(controllerType);

    request.RegisterForDispose(new Release(() => _kernel.Release(controller)));

    return controller;
}

IHttpControllerActivator.Create is called on every request. The rest of the bindings are made in the standard way, by standard I mean in the class generated by using the Ninject.MVC3 nuget package.

My controller looks like this:

public class CustomerController : ApiController
{
    private readonly ICustomerService _customerService;
    private readonly IResourceLinker _linker;

    public CustomerController(ICustomerService customerService, IResourceLinker linker)
    {
        _customerService = customerService;
        _linker = linker;
    }

    public CustomerModel GetCustomer(string id)
    {
        Customer customer = _customerService.GetCustomer(id);
        if (customer == null)
        {
            throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
        }

        return
            new CustomerModel
                {
                    UserName       = customer.UserName,
                    Firstname      = customer.Firstname,
                    DefaultAddress = _linker.GetUri<AddressController>(c => c.Get(customer.DefaultAddressId)),
                };
    }
}
Lialiabilities answered 15/10, 2012 at 12:5 Comment(3)
Can you show us where in context you are executing the _kernel.Rebind call and also what a typical controller would look like?Joannejoannes
Thanks Page, I've added more information.Lialiabilities
For a way to make this work with Castle Windsor, see here: https://mcmap.net/q/494796/-resolving-httpcontrollercontext-with-castle-windsor/126014Warp
W
6

Register a delegate Function to give you the linker

_kernel.Bind<Func<HttpRequestMessage, IResourceLinker>>()
    .ToMethod(context => (request) => new RouteLinker(request));

Inject the delegate

readonly Func<HttpRequestMessage, IResourceLinker> _getResourceLinker;

public controller(Func<HttpRequestMessage, IResourceLinker> getResourceLinker) {

    _getResourceLinker = getResourceLinker;
}

Use in your actions

public async Task<Thingy> Get() {

    var linker = _getResourceLinker(Request);
    linker.GetUri( ... )

}
Waterfront answered 15/10, 2012 at 12:22 Comment(3)
Hi Anthony, thanks for the reply. That solution works though I can't use it from the constructor as the Request object is null at that point. I can use it in the Actions.Lialiabilities
@JamesSkimming: +1 good tidy answer. You might also find Ninject.Extensions.Factory useful as it can do some of this stuff (the ToMethod lambda implicitly). Obviously that would be overkill for this question and hide the point though!Dissyllable
What if you need a RouteLinker instance further down in the object graph (i.e. not from inside an ApiController instance)? In that case, you don't have any HttpRequestMessage with which you can invoke the delegate... Also, see my answer for the case when you are inside an ApiController.Warp
W
4

If you only need to use RouteLinker from ApiController derivates, you don't really need to go through all the DI hoops.

You can just create it within the Controller like this:

var linker = new RouteLinker(this.Request);

IMO, using DI with RouteLinker first becomes valuable when you need a RouteLinker further down the stack - but then again, I also only use RouteLinker as a Concrete Dependency...

Warp answered 15/10, 2012 at 20:59 Comment(0)
J
2

Thanks for adding the code sample. Based on what you have posted, you are running into your Bind/Rebind issue because you are issuing the _kernel.Bind<IResourceLinker> in the IHttpControllerActivtor.Create method every time.

You need to move the _kernel.Bind<IResourceLinker> to be registered the same way your are registering the rest of your bindings in the

...standard way, by standard I mean in the class generated by using the Ninject.MVC3 nuget package.

There should not be any need for the IResourceLinker to be binded multiple times, and this is why you are getting multiple instances, because the binding is firing every time a controller is created by the IHttpControllerActivator.

Update: Sorry that I missed the need for an HttpRequestMessage as a constructor argument, I would go with Anthony Johnson's answer on this one.

Joannejoannes answered 15/10, 2012 at 13:39 Comment(2)
Hi Page, thanks for replying. Putting the binding in with the rest of the bindings doesn't work as the concrete class RouteLinker requires a HttpRequestMessage on its constructor.Lialiabilities
+1 Coz you identified the main problem and got the whole thing clarified to the point of being answerable.Dissyllable

© 2022 - 2024 — McMap. All rights reserved.