I'm creating a selfhost REST server that dynamically loads controllers.
When my controller is defined in my server project, then attribute routing works fine.
When same controller is dynamically loaded then it seems attribute routing is not working.
Here is what i do: 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:
publicclassSampleController:ApiController{publicSampleController(){}[HttpGet][Route("api/Sample/Test")][Route("api/Sample/Test/{data}")][ActionName("Test")]publicstringGetTest(string data){returnTest(data);}[HttpPost][Route("api/Sample/Test")]publicstringTest([FromBody]string data){returnstring.Concat("Test - Received ", data);}...
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:
publicDynControllerSelector(HttpConfiguration configuration):base(configuration){_Configuration= configuration;_ControlleDescriptorDict=newConcurrentDictionary<string,HttpControllerDescriptor>();}publicoverrideHttpControllerDescriptorSelectController(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 nowstring 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)thrownewException(string.Concat("Failed to find controller ", controllerName,"Controller"));
httpControllerDesc =newHttpControllerDescriptor(_Configuration, controllerName, matchedController);_ControlleDescriptorDict.TryAdd(controllerName, httpControllerDesc);}else{thrownewException(string.Concat("Failed to load assembly ", assemblyName));}}}}}else
httpControllerDesc =base.SelectController(request);..
Attribute routing runs at startup. It uses reflection on all loaded modules to build the route table. As you are loading controllers after this step, they are not “seen”. You need to go thru the attribute routing source code for the version you are using
and see if there is a way to trigger a reload of the route table.
To make dynamically loaded controller work with attribute routing, you can try to implement custom IAssembliesResolver like below.
public class MyAssembliesResolver : DefaultAssembliesResolver
{
public override ICollection<Assembly> GetAssemblies()
{
ICollection<Assembly> baseAssemblies = base.GetAssemblies();
List<Assembly> assemblies = new List<Assembly>(baseAssemblies);
string thirdPartySource = @"D:\DynamicAPIsController\APIs\";
if (!string.IsNullOrWhiteSpace(thirdPartySource))
{
if (Directory.Exists(thirdPartySource))
{
foreach (var file in Directory.GetFiles(thirdPartySource, "*.*", SearchOption.AllDirectories))
{
if (Path.GetExtension(file) == ".dll")
{
var externalAssembly = Assembly.LoadFrom(file);
baseAssemblies.Add(externalAssembly);
}
}
}
}
return baseAssemblies;
}
}
In Startup.cs
//...
config.Services.Replace(typeof(IAssembliesResolver), new MyAssembliesResolver());
config.MapHttpAttributeRoutes();
//...
Test Result
With Regards,
Fei Han
.NET forums are moving to a new home on Microsoft Q&A, we encourage you to go to Microsoft Q&A for .NET for posting new questions and get involved today.
Thanks a lot for your response. I really appreciate it.
Indeed it does load the assembly after the service has been started and configuration/routing is applied properly when using a custom assembly resolver.
That works fine.
One thing i should have mentioned is that the overall goal of this is to be able to update on the fly the assemblies and load them at runtime.
Meaning the assembly loaded may be updated several times at a later time. It means i need to find a way to reload it somehow to reflect the changes.
This should be done without interruption to the service.
That is what my DynamicControllerSelector class is achieving. It manages updates of the assembly.
Basically the controller is first created using assembly say A. Server is loading that assembly when client is calling the controller apis.
Now later controller is updated, a new assembly is generated (assembly called A.v2).
The DynamicControllerSelector will load that assembly and will process the request using the new version of the controller.
This is done with no interruption to the service. This works well. Only down side is that it does not seem to define the routine properly, thus causing
the attribute routing to not be functional.
Of course after some time, it may be needed to recreate the current AppDomain to clean/remove "obsolete" assemblies
but this can be done once a day only if needed as it is not expected to get thousands of updates for these dynamic assemblies.
So question is how to achieve this by using the custom assembly resolver. Is there a way to remove the controller somehow and force the service to call
again the assemblies resolver?
.net does not support unloading a module. you can only unload an appdomain. you probably need create you own route loader, that updates the route table based on the attributes of the dynamic loaded modules. re-running the attribute route reader will still
see the "old" assemblies.
note: asp.net core does not support appdomains and currently can not unload an assembly.
Thanks for your valuable responses.
I believe one option would be to overwrite ApiControllerActionSelector:
_Config.Services.Replace(typeof(IHttpActionSelector), new MyHttpActionSelector());
Indeed when assembly is dynamically loaded, the routing definition is not updated.
You can clearly see that when this method is called.
One way will then be to do some custom logic to update the routing table there when new assembly is loaded.
Thanks again for the help
Member
2 Points
4 Posts
Attribute routing not working when controller is loaded dynamically (using custom controller sele...
Oct 17, 2019 04:41 PM|dtf017|LINK
I'm creating a selfhost REST server that dynamically loads controllers.
When my controller is defined in my server project, then attribute routing works fine.
When same controller is dynamically loaded then it seems attribute routing is not working.
Here is what i do:
I'm creating a self hosted REST server (using Owin):
I create a static controller just to make sure it works ok with my settings:
My startup class looks like this:
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:
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
All-Star
58214 Points
15668 Posts
Re: Attribute routing not working when controller is loaded dynamically (using custom controller...
Oct 17, 2019 06:45 PM|bruce (sqlwork.com)|LINK
Attribute routing runs at startup. It uses reflection on all loaded modules to build the route table. As you are loading controllers after this step, they are not “seen”. You need to go thru the attribute routing source code for the version you are using and see if there is a way to trigger a reload of the route table.
Member
2 Points
4 Posts
Re: Attribute routing not working when controller is loaded dynamically (using custom controller...
Oct 21, 2019 07:19 AM|dtf017|LINK
Hi Bruce,
Thanks for your response. I figured that out but question is how to set the routing after the controller has been loaded.
Also this should not impact other controller already dynamically loaded.
That is the part i'm missing. Still thanks for your response.
All-Star
40565 Points
6233 Posts
Microsoft
Re: Attribute routing not working when controller is loaded dynamically (using custom controller...
Oct 24, 2019 03:19 AM|Fei Han - MSFT|LINK
Hi dtf017,
To make dynamically loaded controller work with attribute routing, you can try to implement custom IAssembliesResolver like below.
In Startup.cs
Test Result
With Regards,
Fei Han
Member
2 Points
4 Posts
Re: Attribute routing not working when controller is loaded dynamically (using custom controller...
Dec 10, 2019 01:51 PM|dtf017|LINK
Hi Fei Han,
Thanks a lot for your response. I really appreciate it.
Indeed it does load the assembly after the service has been started and configuration/routing is applied properly when using a custom assembly resolver.
That works fine.
One thing i should have mentioned is that the overall goal of this is to be able to update on the fly the assemblies and load them at runtime.
Meaning the assembly loaded may be updated several times at a later time. It means i need to find a way to reload it somehow to reflect the changes.
This should be done without interruption to the service.
That is what my DynamicControllerSelector class is achieving. It manages updates of the assembly.
Basically the controller is first created using assembly say A. Server is loading that assembly when client is calling the controller apis.
Now later controller is updated, a new assembly is generated (assembly called A.v2).
The DynamicControllerSelector will load that assembly and will process the request using the new version of the controller.
This is done with no interruption to the service. This works well. Only down side is that it does not seem to define the routine properly, thus causing
the attribute routing to not be functional.
Of course after some time, it may be needed to recreate the current AppDomain to clean/remove "obsolete" assemblies
but this can be done once a day only if needed as it is not expected to get thousands of updates for these dynamic assemblies.
So question is how to achieve this by using the custom assembly resolver. Is there a way to remove the controller somehow and force the service to call
again the assemblies resolver?
Thanks again for your feedback
All-Star
58214 Points
15668 Posts
Re: Attribute routing not working when controller is loaded dynamically (using custom controller...
Dec 10, 2019 04:49 PM|bruce (sqlwork.com)|LINK
.net does not support unloading a module. you can only unload an appdomain. you probably need create you own route loader, that updates the route table based on the attributes of the dynamic loaded modules. re-running the attribute route reader will still see the "old" assemblies.
note: asp.net core does not support appdomains and currently can not unload an assembly.
Member
2 Points
4 Posts
Re: Attribute routing not working when controller is loaded dynamically (using custom controller...
Jan 16, 2020 11:11 AM|dtf017|LINK
Thanks for your valuable responses.
I believe one option would be to overwrite ApiControllerActionSelector:
_Config.Services.Replace(typeof(IHttpActionSelector), new MyHttpActionSelector());
Indeed when assembly is dynamically loaded, the routing definition is not updated.
You can clearly see that when this method is called.
One way will then be to do some custom logic to update the routing table there when new assembly is loaded.
Thanks again for the help