0

I want to have a modular EmbedIO setup with a dynamic list of unknown web API controller types. I thought it'd be easy :( But at the moment I'm stuck at registering the web APIs:

// Some APIs to setup at the EmbedIO webserver in "Server"
Dictionary<string, WebApiController> apis = ...;

// Register the APIs at the webserver
foreach(KeyValuePair<string, WebApiController> kvp in apis)
{
    // Exception: "Controller type must be a subclass of WebApiController."
    Server.WithWebApi(kvp.Key, m => m.WithController(() => kvp.Value));
}

The problem is: The factory method needs to return the final type of the controller object. Everything else seems to fail.

I tried with dynamic instead of WebApiController or returning object and giving the type as first parameter for WithController - whatever I tried, it resulted in an exception; Or when I use a class WebApiControllerWrapper : WebApiController and a Dictionary<string, WebApiControllerWrapper>, the exported controller methods of the final type are missing, because they're not defined in WebApiControllerWrapper.

It seems the only way is to use reflection for the generic call of WithController - or does anyone know another working solution (I'm in .NET Standard 2.1)?

nd_
  • 93
  • 1
  • 9

1 Answers1

0

I was able to solve it with an expression tree that calls a generic method to create the factory function:

public class ModularWebApiController : WebApiController
{
    public Func<T> CreateFactoryMethod<T>() where T : WebApiController => () => (T)this;
}

public static class Extensions
{
    public static WebApiModule WithController(this WebApiModule webApiModule, ModularWebApiController api)
    {
        Delegate factoryFunc = Expression
            .Lambda(Expression.Call(
                Expression.Constant(api), 
                typeof(ModularWebApiController).GetMethod("CreateFactoryMethod").MakeGenericMethod(api.GetType())
            ))
            .Compile();
        return (WebApiModule)typeof(WebApiModuleExtensions)
                    .GetMethods(BindingFlags.Static | BindingFlags.Public)
                    .Single(mi => mi.IsGenericMethod & mi.Name == "WithController" && mi.GetParameters().Length == 2)
                    .MakeGenericMethod(api.GetType())
            .Invoke(null, new object[] { webApiModule, factoryFunc.DynamicInvoke(Array.Empty<object>()) });
    }
}

I only had to ensure that all web API controller types extend the ModularWebApiController type, and I had to change the modular web API setup for EmbedIO:

Dictionary<string, ModularWebApiController> apis = ...;
foreach(KeyValuePair<string, ModularWebApiController> kvp in apis)
{
    Server.WithWebApi(kvp.Key, m => m.WithController(kvp.Value));
}

After browsing the EmbedIO source I think this seems to be the only way to have a modular web API setup, where the code doesn't know which web API controller types are going to be used.

Now I'm able to load and instance any web API controller type configured in a JSON configuration file like this:

[
    {
        "Type": "name.space.WebApiControllerTypeName",
        "Path": "/webapipath/"
    },
    {
        "Type": "name.space.AnotherWebApiControllerTypeName",
        "Path": "/anotherwebapipath/"
    }
]

Just for example. I wonder why it seems that nobody else want to do this ;)

nd_
  • 93
  • 1
  • 9