Somehow I can't find someone having the same problem.
We have a plugin based project, on the main folder we have the Plugin Starter, the Bootstrapper, and some dependencies.
The plugins are in the "Plugins" folder and within, are some other folders.
My Startup.cs file is as following:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.Use(async (env, next) =>
{
new object().Info(string.Concat("Http method: ", env.Request.Method, ", path: ", env.Request.Path));
await next();
new object().Info(string.Concat("Response code: ", env.Response.StatusCode));
});
RunWebApiConfiguration(appBuilder);
}
private static void RunWebApiConfiguration(IAppBuilder appBuilder)
{
var httpConfiguration = new HttpConfiguration();
httpConfiguration.Routes.MapHttpRoute(
name: "WebApi"
, routeTemplate: "{controller}/{id}"
, defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(httpConfiguration);
}
}
The call is made as following:
WebApp.Start<Startup>("http://localhost/MyRestApi");
if I load the Assembly on the same folder, no problem, but if I load it where it "belongs", I can't get Owin to find it.
Anyone had ever ran into this or have any idea? I might think of something like a configuration line in the App.config, but I don't find this as a solution.
UPDATE 1:
I get the System working again when I copy the Rest Service assembly on the main directory but then it is loaded two times, which is a big problem.
When I send a Rest request, I get the following message:
{"Message":"An error has occurred.","ExceptionMessage":"Multiple types were found that match the controller named 'ExternalOrder'. This can happen if the route that services this request ('{controller}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported.\r\n\r\nThe request for 'ExternalOrder' has found the following matching controllers:\r\nInternalOrderValidationPlugin.Controllers.ExternalOrderController\r\nInternalOrderValidationPlugin.Controllers.ExternalOrderController","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.Dispatcher.DefaultHttpControllerSelector.SelectController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"}
Ok, after wandering around on the internet, I found a solution, and namely Dependency Injection.
Here in this post "How to use DI container when OwinStartup" you can find a lot of possibilities, but the one that I implemented and solved my problem, was this blog post:
https://damienbod.wordpress.com/2013/10/01/self-host-webapi-with-owin-and-unity/
I hope this can help anyone else.
Related
I am trying to refactor an old setup, the end goal will be that I will have a functional REST API. I am getting rid of an old NuGet service and I sort of have to rebuild everything now. I am trying to set it up using ASP.NET Web API. This will include 60+ routes (like "www.website.com/cars/{id}/engine" "www.website.com/inventory/{id}" etc..)
I have tried attribute routing and conventional routing, and nothing seems to work. I get 404's no matter what I seem to try. I am probably just not doing either of them correctly.
Here is how I am setting up configuration (I am using a self hosting package btw but this is the main configuration point):
public class SomeHostClass
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
//If I am trying attribute based routing, uncomment this
//config.MapHttpAttributeRoutes();
Register(config.Routes);
// I think I need this at all times?
app.UseWebApi(config);
}
public void Register(HttpRouteCollection routes)
{
// One of many other routes.MapHttpRoute() calls
routes.MapHttpRoute(
name: "Cars",
routeTemplate: "cars/{id}/engine",
defaults: new
{
controller = "CarController",
action = "Get"
},
constraints: null);
}
}
Then here is my Car Controller:
public class CarController : ApiController
{
[HttpGet]
public object Get([FromUri] CarDTO carObject)
{
// Some code calling a private worker method
return WorkerMethod();
}
private object WorkerMethod()
{
// Worker method do stuff, return
}
}
This Get() method is never called and a 404 is returned.
Another note: I have tried using reflection to register all the routes, which slims down the code. I'll debug it and it looks like all the routes have been configured correctly within the HttpConfiguration.routes but I will get 404's still. Even without reflection doing it the way shown above- if I debug and look at the values they all seem correct, but don't work.
Another Note: I should also mention that this is the error I usually get along with the 404.
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:8000/cars/1/engine'.","MessageDetail":"No type was found that matches the controller named 'CarController'."}
How do I get these routes recognized and stop returning 404's and start returning my data?
If this is a bad way of going about this- what's the best way?
I am trying to solve the following problem and i am not exactly sure how to do it:
I am building a web server that has differenty APIs/Controllers that are loaded from .dll-Files on Startup. It will run in a linux docker container and is implemented as an ASP-NET Webapplication & .net Core 2.1 .
The loading of assemblies that contain controllers works fine by doing something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddApplicationPart(AssemblyLoadContext.Default.LoadFromAssemblyPath("/PATH/APIPlugin.dll"));
}
This application must have versioned REST-APIs that means: I need to load the same assembly multiple times in different versions. then i need to have some kind of routing between the versions.
For example:
/api/data/latest/
/api/data/v1/
I cannot use AssemblyLoadContext.Default.LoadFromAssemblyPath to load multiple versions of the same assembly. I also tried to grab the controller from the assembly and creating an instance of it like this:
var controllerAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("/PATH/APIPlugin.dll");
var pluginType = controllerAssembly ExportedTypes.First<Type>();
var pluginInstance = (ControllerBase)Activator.CreateInstance(pluginType);
services.Add(new ServiceDescriptor(pluginType, pluginInstance));
This throws no exception but ultimately does not work. But i am pretty new to ASP.Net so this might very well be nonsense and i would have to find a solutuion to route between the different versions, even if it would work like this.
My Question:
How would one approach this requirement ?
Is it a god idea/possible to load multiple Controllers from the "same" assembly ? If yes, how would one achieve this?
Or would it be a better solution to have one controller that does all the routuing and load some self-defined implementation from the assemblies. So that the controller would route between the versions, and api-methods?
I was able to find a sultion while tinkering around:
public class ControllerPluginProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var basePath = AppContext.BaseDirectory;
var pluginPath = Path.Combine(basePath, "plugins");
foreach (var file in Directory.GetFiles(pluginPath, "*.dll")){
var assembly = Assembly.LoadFile(file);
var controllers = assembly.GetExportedTypes().Where(t => typeof(ControllerBase).IsAssignableFrom(t));
foreach (var candidate in controllers)
{
feature.Controllers.Add(candidate.GetTypeInfo());
}
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc().ConfigureApplicationPartManager(m =>
m.FeatureProviders.Add(new ControllerPluginProvider()));
}
This lead to the following error when the same assembly, and therefore a Controller with the same name was loaded: Attribute routes with the same name 'Get' must have the same template
I was able to fix it, and also add versioning with the versioning library:
https://github.com/microsoft/aspnet-api-versioning
[ApiVersion("2.0")]
[ApiController]
[Route("api/v{version:apiVersion}/MyController")]
public class MyControllerController : ControllerBase
{
}
Now the only step that is missing, is routing the /latest/ path to the most recent controller of the given type. I have not found a solution to this yet, but this should be doable.
Summary: I want to have an wehook url with a specific name for the webhook, rather than .../custom/ or .../genericjson/ if using those nuget packages.
i.e.
https://localhost:44300/api/webhooks/incoming/MyWebhook?code=1234567890
All the guides and the likes that I've found are either for existing Webhook nuget packages (Github, bitbucket, slack etc.) or the Generic and Custom one (that I don't really understand the difference of, besides the names, but thats another matter).
I got the genericjson one up and running and it's doing what it is supposed to, but I would much rather have a more correct url and/or name for the Webhook.
So how do I get (which I assume is what I need to get it working as I want)
config.InitializeReceiveGenericJsonWebHooks();
to instead work as if it were
config.InitializeReceiveMyWebhookWebHooks();
and thus my server listening for webhooks on the above URL (provided I add the key in appSettings and so on).
I tried looking at the definition of InitializeReceiveGenericJsonWebHooks() and InitializeReceiveGitHubWebHooks(), but there is barely any code there nor do I know how/what/where to create my own version of it.
I'm quite new to Asp.net (have previously mostly coded sites in html/css/php/javascript on Apache, rather than Asp.net/C# on IIS) so I recon there ought to be some easy way to solve it, but after an entire day searching and trying I'm at an end as to what to try next.
Edit:
Currently this is what my code (related to Webhook) looks like. Nothing special, just bare minimum to get the webhook working.
Web.config
<appSettings>
<add key="MS_WebHookReceiverSecret_GenericJson" value="1234567890123456789012345678901234567890"/>
</appSettings>
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//The revelant part to the question
config.InitializeReceiveGenericJsonWebHooks();
}
}
GenericJsonWebHookHandler.cs
namespace Project.API.Webhooks
{
public class GenericJsonWebHookHandler : WebHookHandler
{
public GenericJsonWebHookHandler()
{
this.Receiver = "genericjson";
}
public override Task ExecuteAsync(string receiver, WebHookHandlerContext context)
{
// Get JSON from WebHook
JObject data = context.GetDataOrDefault<JObject>();
//Do something with webhook
return Task.FromResult(true);
}
}
}
I finally managed to solve it.
By looking at the GitHub of AspNetWebHooks and further in to the GenericJSON WebHook that I'm using, I finally found out what was missing.
The only thing needed was create a file called HttpConfigurationExtensions.cs and copy+paste the code from the HttpConfigurationExtensions.cs source into the newely created file and change
public static void InitializeReceiveGenericJsonWebHooks(this HttpConfiguration config)
to what I wanted it to listen to
public static void InitializeReceiveMyWebhookWebHooks(this HttpConfiguration config)
like so.
Then I also needed to create a file called MyWebhookReceiver.cs and copy+paste the code from GenericJsonWebHookReceiver.cs and change the contents slightly
public class GenericJsonWebHookReceiver : WebHookReceiver
{
internal const string RecName = "genericjson";
into
public class MyWebhookWebHookReceiver : WebHookReceiver
{
internal const string RecName = "MyWebhook";
It still acts like GenericJSON webhook (which is what I wanted) but now I can use /api/incoming/MyWebhook/whateverIWant?code=123456790 as webhook URL instead of having it look like /genericjson/MyWebhook?code=1234567890.
You still need the handler file, but in the same way the two files were slightly changed here you can do the same with the handler. Create MyWebHookHandler.cs and just copy+paste the code, changing the GenericJson to MyWebHook and it should be good to go.
Nice work! If anyone else is following this, the only couple of thingf missing is the Web.config entry
<add key="MS_WebHookReceiverSecret_GenericJson" value="i=xxx,z=xxx" />
becomes:
<add key="MS_WebHookReceiverSecret_MyWebhook" value="i=xxx,z=xxx" />
As the last section of the key name needs to match the name of the new naming convention in the last section.
And the handler itself - on the constructor:
Should be:
public MyWebhookHandler()
{
this.Receiver = GenericJsonWebHookReceiver.ReceiverName;
}
not:
public MyWebhookHandler()
{
this.Receiver = "genericjson";
}
Also - there is no benefit doing the HttpConfigurationExtensions.cs step.
You do however need to to the GenericJsonWebHookReceiver.cs step for the process to work.
I recently added Microsoft.AspNet.WebApi.WebHost to a MVC WebAPI project which would allow me to use the [Route("api/some-action")] attribute on my action. I solved some errors using this article but can't solve the third error below. Added solved errors below to get feedback if I did anything wrong.
First Error: No action was found on the controller 'X' that matches the name 'some-action'
Solution: Added config.MapHttpAttributeRoutes(); to WebApiConfig.cs Register method.
Second Error: System.InvalidOperationException The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.
Solution: Added GlobalConfiguration.Configure(WebApiConfig.Register); to Global.asax.cs Application_Start
Third Error: System.ArgumentException: A route named 'MS_attributerouteWebApi' is already in the route collection. Route names must be unique.
Solution = ?
I've already tried cleaning and deleting all DLLs from bin folder according to this post.
I had a similar problem and it was related to a copy paste error on my part where I added a copy of this line in my WebApiConfig.cs file:
config.MapHttpAttributeRoutes();
make sure you only have one of these.
In the Global.asax, check how many times WebApiConfig.Register function has been called.
Solved! Removed the line WebApiConfig.Register(GlobalConfiguration.Configuration); from Global.asax.cs Application_Start.
I have solved by cleaning the deployment directory before copy the new files. Probably there was some old file that try to register the same root multiple times.
I had the same issue and I discovered that in WebApiConfig.cs
and after I added configuration for API version
I have those two lines
config.MapHttpAttributeRoutes(constraintResolver);
config.MapHttpAttributeRoutes();
I removed the second line
the final code is
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
config.AddApiVersioning();
// Web API configuration and services
// Web API routes
// config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
For anybody stumbling across this as I did, this error will happen if you rename the Assembly name (project properties). In my case I was renaming a project, and went into the properties to change the assembly name (which VS2013 won't do for you).
Because the assembly name is different, a Clean or Rebuild will not remove the "old" assembly if it is in the \bin folder. You have to delete the assembly from the \bin folder, then rebuild & run.
Probably you have same register more than one.
Try to delete below codes from Global.asax:
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
and write this ones instead of them :
GlobalConfiguration.Configuration.EnsureInitialized();
BundleConfig.RegisterBundles(BundleTable.Bundles);
I am not sure about reason; but it worked for me in my same case.
I was also experiencing a similar problem (not the MS_attributerouteWebApi route in particular, but a different named route). After verifying only one config.MapHttpAttributeRoutes() existed, began realizing that MapHttpAttributeRoutes will register all project assemblies including externally referenced ones. After finding out that I had a referenced assembly that was registering its own routes, I found a way to exclude or "skip over" routes by overriding the DefaultDirectRouteProvider:
/// <summary>
/// Allows for exclusion from attribute routing of controllers based on name
/// </summary>
public class ExcludeByControllerNameRouteProvider : DefaultDirectRouteProvider {
private string _exclude;
/// <summary>
/// Pass in the string value that you want to exclude, matches on "ControllerType.FullName" and "ControllerType.BaseType.FullName"
/// </summary>
/// <param name="exclude"></param>
public ExcludeByControllerNameRouteProvider(string exclude) {
_exclude = exclude;
}
protected override IReadOnlyList<RouteEntry> GetActionDirectRoutes(
HttpActionDescriptor actionDescriptor,
IReadOnlyList<IDirectRouteFactory> factories,
IInlineConstraintResolver constraintResolver)
{
var actionRoutes = new List<RouteEntry>();
var currentController = actionDescriptor.ControllerDescriptor.ControllerType;
if (!currentController.FullName.Contains(_exclude) && !currentController.BaseType.FullName.Contains(_exclude))
{
var result = base.GetActionDirectRoutes(actionDescriptor, factories, constraintResolver);
actionRoutes = new List<RouteEntry>(result);
}
return actionRoutes.AsReadOnly();
}
}
This allows for you to pass a Controller name or Base Type name in to exclude in your WebApiConfig.cs like:
config.MapHttpAttributeRoutes(new ExcludeByControllerNameRouteProvider("Controller.Name"));
Whether or not directly related, hoping this snippet can help!
I was experiencing the same problem. The solution I found was that I needed visual basic support for mono. When I executed "yum install mono-basic" and restarted my computer the error went away.
In my case error was : "A route named 'MS_attributerouteWebApi' is already in the route collection. Route names must be unique.
Parameter name: name"
Code was:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.MapHttpAttributeRoutes(); // this line had issue
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Solution :
I just removed - config.MapHttpAttributeRoutes();
line from above method and it got resolved.
I have a solution with two projects. One Web Api bootstap project and the other is a class library.
The class library contains a ApiController with attribute routing.
I add a reference from web api project to the class library and expect this to just work.
The routing in the web api is configured:
config.MapHttpAttributeRoutes();
The controller is simple and looks like:
public class AlertApiController:ApiController
{
[Route("alert")]
[HttpGet]
public HttpResponseMessage GetAlert()
{
return Request.CreateResponse<string>(HttpStatusCode.OK, "alert");
}
}
But I get a 404 when going to the url "/alert".
What am I missing here? Why can't I use this controller? The assembly is definitely loaded so I don't think http://www.strathweb.com/2012/06/using-controllers-from-an-external-assembly-in-asp-net-web-api/ is the answer here.
Any ideas?
Try this. Create a class in your class library project that looks like this,
public static class MyApiConfig {
public static void Register(HttpConfiguration config) {
config.MapHttpAttributeRoutes();
}
}
And wherever you are currently calling the config.MapHttpAttributeRoutes(), instead call MyApiConfig.Register(config).
One possibility is you have 2 routes on different controllers with the same name.
I had 2 controllers both named "UploadController", each in a different namespace and each decorated with a different [RoutePrefix()]. When I tried to access either route I got a 404.
It started working when I changed the name of one of the controllers. It seems the Route Attribute is only keyed on the class name and ignores the namespace.
We were trying to solve a similar problem.
The routes within the external assembly were not registering correctly.
We found one additional detail when trying the solution shown on this page.
The call to the external assembly "MyApiConfig.Register" needed to come before the call to MapHttpRoute
HttpConfiguration config = new HttpConfiguration();
MyExternalNamespace.MyApiConfig.Register(config); //This needs to be before the call to "config.Routes.MapHttpRoute(..."
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);