1

I'm using DryIOC.WebAPI to resolve my APIControllers.

WebAPI Config is thus:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));


        config.EnableCors();
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        Startup.IocContainer.WithWebApi(config);
        Startup.IocContainer.RegisterWebApiControllers(config);
    }
}

Its working great, until I forget to create a mapping in the IOC Container. In which case the Dependency resolver fails to make an instance of the APIController using DryIOC.

When this happens it falls back to the default WebAPI resolver which then barfs with the "No default constructor" error.

This is undesirable behavior for me. If the DryIOC resolution fails, I want things to stop there - no fallback to the default implementation, and no inaccurate message about no default constructor. I want to know what's types DryIOC couldn't resolve so I can fix it.

How can I achieve this?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
reach4thelasers
  • 26,181
  • 22
  • 92
  • 123
  • you could override the default resolver yourself before passing it to DryIOC and stop it there – Nkosi Mar 23 '16 at 14:30
  • I made a DryIOC Pull Request Here: https://bitbucket.org/dadhi/dryioc/pull-requests/15/changed-the-resolution-behavior-to-throw/diff – reach4thelasers Mar 23 '16 at 16:25
  • making that change in the DryIOC framework is rather extreme. You should take advantage of the extendability that web api allows. After looking at how DryIOC source replaces the default resolver rather than wrap it I'll edit my answer to show how you could wrap the DryIOCResover and throw the error you want – Nkosi Mar 23 '16 at 18:08
  • 1
    I commented on proposed PR https://bitbucket.org/dadhi/dryioc/pull-requests/15/changed-the-resolution-behavior-to-throw/diff – dadhi Mar 23 '16 at 18:18
  • @reach4thelasers found some documentation re: `IDependencyResolver` that may conflict what your requirement. – Nkosi Mar 23 '16 at 22:44

2 Answers2

1

According to Microsoft documentation:

Dependency Injection in ASP.NET Web API 2

If the GetService method cannot resolve a type, it should return null. If the GetServices method cannot resolve a type, it should return an empty collection object.

Don't throw exceptions for unknown types.

Implementations of this interface should delegate to the underlying dependency injection container to provide the registered service for the requested type. When there are no registered services of the requested type, the ASP.NET MVC framework expects implementations of this interface to return null from GetService and to return an empty collection from GetServices.

When Web API creates a controller instance, it first calls IDependencyResolver.GetService, passing in the controller type. You can use this extensibility hook to create the controller, resolving any dependencies. If GetService returns null, Web API looks for a parameterless constructor on the controller class.

You could override the default resolver yourself

public static class ResolverHelper {
    public static void OverrideResolver(this HttpConfiguration httpConfiguration) {
        var innerResolver = httpConfiguration.DependencyResolver;
        var resolver = new MyDryIocDependencyResolver(innerResolver);
        httpConfiguration.DependencyResolver = resolver;
    }
}

and in your custom resolver fail how ever you want.

Snippet from MyDryIocDependencyResolver

public class MyDryIocDependencyResolver : IDependencyResolver { 
    IDependencyResolver innerResolver;

    public MyCustomDependencyResolver(IDependencyResolver innerResolver) {
        this.innerResolver = innerResolver;
    }

    public IDependencyScope BeginScope() {
        return this;
    }

    public object GetService(Type serviceType) {
        try {
            return innerResolver.Getservice(serviceType);
        } catch (Exception ex) {
            //TODO: Log resolution error
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType) {
        try {
            return innerResolver.GetServices(serviceType);
        } catch (Exception ex) {
           //TODO: Log resolution error
           return new List<object>();
        }
    }

    public void Dispose() {
      // NO-OP 
    }
}

In your registration you can then override it

//...other code
Startup.IocContainer.WithWebApi(config);
Startup.IocContainer.RegisterWebApiControllers(config);
config.OverrideResolver();
//...other code

This way if/when DryIocDependencyResolver could not resolve the service type you will still know what was not resolved, catch it, and return the default values as indicated by suggested guidelines. Best of both worlds.

Hope that helps

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    This is indeed MS recommendation. The problem with it though, that we are losing detailed exception from integrated IoC library. DryIoc and other IoCs provide a lot of info on what, why is not resolved, and what the state of container. So you can pinpoint the problem much faster. – dadhi Mar 24 '16 at 04:26
1

Another way to check problems with Controller resolutions with all the possible info, is to use IContainer.VerifyResolutions method.

For example:

var container = new Container().WithWebApi(config);
var errors = container.VerifyResolutions();
Debug.Assert(errors.Length == 0);

More details in a wiki.

Note: The only catch with controllers, that current DryIoc.WebApi registers controller implementation with concrete controller type and all its implemented types as the service types. So for one unresolved class MyController: ApiController {} method VerifyResolutions will return 3 identical errors:

  • one for MyController
  • one for ApiController
  • and one for IHttpController

You may group result verification errors by Factory.FactoryID to have a more compact output.

dadhi
  • 4,807
  • 19
  • 25