none
Attribute routing not working when controller is loaded dynamically (using custom controller selector) RRS feed

  • Question

  • I'm creating a self hosted REST server (using Owin):

    string baseUri = string.Concat("http://+:12345/MyServer");
    myserver = WebApp.Start<Startup>(baseUri);
    Console.WriteLine("MyServer is listening at " + baseUri);
    ...

    I create a static controller just to make sure it works ok with my settings:

    public class SampleController : ApiController
    {
        public SampleController() { }
    
        [HttpGet]
        [Route("api/Sample/Test")]
        [Route("api/Sample/Test/{data}")]
        [ActionName("Test")]
        public string GetTest(string data)
        {
            return Test(data);
        }
    
        [HttpPost]
        [Route("api/Sample/Test")]
        public string Test([FromBody] string data)
        {
            return string.Concat("Test - Received ", data);
        }
    ...

    My startup class looks like this:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var webApiConfiguration = ConfigureWebApi();
            app.UseWebApi(webApiConfiguration);
        }
    
        private HttpConfiguration ConfigureWebApi()
        {
            HttpConfiguration config = new HttpConfiguration();
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                        "DefaultApi",
                        "api/{controller}/{action}/{id}",
                        new { id = RouteParameter.Optional});
    
            DynControllerSelector custom = new DynControllerSelector(config);
    
            config.Services.Replace(typeof(IHttpControllerSelector), custom);
    
            return config;
        }
    }

    Then i dynamically load another controller which has the same methods and definition exposed than the static one. To do that i use a custom controller selector:

    public DynControllerSelector(HttpConfiguration configuration) : base(configuration)
     {
        _Configuration = configuration;
        _ControlleDescriptorDict = new ConcurrentDictionary<string, HttpControllerDescriptor>();
     }
     public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
     {
        HttpControllerDescriptor httpControllerDesc = null;
        try
        {
          IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
          //string controllerName = base.GetControllerName(request); // is null for some reason
         // Just get the controler in a hack way for now
        string controllerName = request.RequestUri.LocalPath.Replace("/MyServer/api/", "");
        int idx = controllerName.IndexOf("/");
        controllerName = controllerName.Substring(0, idx);
    
        if (!controllers.ContainsKey(controllerName))
        {
           if (_ControlleDescriptorDict.TryGetValue(controllerName, out httpControllerDesc) == false)
           {
            lock (_mlock)
            {
                if (_ControlleDescriptorDict.TryGetValue(controllerName, out httpControllerDesc) == false) // Check that controller has not been created while we were waiting for the lock
                {
                  string assemblyName = string.Concat(controllerName, ".dll");
                  if (System.IO.File.Exists(assemblyName))
                  {
                     Assembly assembly = Assembly.LoadFrom(assemblyName);
                     var types = assembly.GetTypes();
                     var matchedTypes = types.Where(i => typeof(IHttpController).IsAssignableFrom(i)).ToList();
                     var matchedController = matchedTypes.FirstOrDefault(i => i.Name.ToLower() == controllerName.ToLower() + "controller");
                     if (matchedController == null) throw new Exception(string.Concat("Failed to find controller ", controllerName, "Controller"));
                     httpControllerDesc = new HttpControllerDescriptor(_Configuration, controllerName, matchedController);
    
                    _ControlleDescriptorDict.TryAdd(controllerName, httpControllerDesc);
                   }
                   else
                   {
                    throw new Exception(string.Concat("Failed to load assembly ", assemblyName));
                    }
                 }
              }
             }
          }
             else
                httpControllerDesc = base.SelectController(request);
    . . 

    They should both use the same routing settings.

    I can call (HTTP GET) my static controller using attribute routing: http://localhost:12345/MyServer/api/Sample/Test/hello2 
    That works fine.

    Problem is when i try the same thing on my dynamic controller: http://localhost:12345/MyServer/api/MyDynamic/Test/hello2 

    I get following error: { "Message": "The requested resource does not support http method 'GET'." }

    It seems somehow the attribute routing is not enabled for my dynamic controller. If i use query parameters, it works just fine: http://localhost:12345/MyServer/api/MyDynamic/Test?data=hello

    How can i make it work?

    Thanks in advance for your help

    Nick

    • Moved by CoolDadTx Wednesday, October 16, 2019 5:22 PM ASP.NET related
    Wednesday, October 16, 2019 8:38 AM

All replies