API URL requires "/api/api/" after being published - c#

I've got an API app up and running on my localhost. It works great at en endpoint such as:
http://localhost:26307/api/ModelName/12345
However, after deploying to the Test server, the same url needs to be:
http://v10test.mydomain.com/api/api/ModelName/12345
I'm trying to get rid of the double /api/.
I adjusted my routes FROM THIS (for example):
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "api/{controller}/{organizationSys}"
);
TO THIS.....
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "{controller}/{organizationSys}"
);
NOTE: I REMOVED "api/" FROM THE ROUTES.
But that seemed to have no effect. In IIS, the api's are set up as an Application within the domain:
I'm probbaly overlooking something obvious, but I'm just not sure what. How do I get rid of that double /api/?

There are several ways to specify routes to a controllers actions, and the order of precedence matters.
Firstly, it can be done from a general configuration, e.g. as you've done it...
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "{controller}/{organizationSys}"
);
However this can be overridden by specifying a Route attribute on the controller or by specifying the route in the verb attribute. For example in the code below...
[Route("api/[controller]/[action]")]
public class TestController : Controller
{
[HttpGet]
[Route("/api")] // url... /api
[Route("/api/test")] // url... /api/test
[Route("testalso")] // url... /api/test/get/testalso
public string Get()
{
return "Alive";
}
[HttpGet("/api/echo/{id}")] // url... /api/echo/{id}
public string Echo(string id)
{
return $"Get Echo: {id}";
}
[HttpPost("{id}")] // url... /api/test/postit/{id}
public string PostIt(string id)
{
return $"Thanks for {id}";
}
}
The declaration on the controller specifies a default for all methods within the controller and any methods that specify attributes can either override the controller by starting the route with a '/' or append to the controller's route. See the comments next to each attribute above for examples.
Beyond that the routes will be relative to the base application hosted within iis in your case which starts at...
http://v10test.mydomain.com/api/
Hope that's enough information for you.

It is because of the way it is deployed in IIS. For IIS your application is at address
http://v10test.mydomain.com/api/
And after that comes all the controllers and etc. If you deploy it to (for example)
http://v10test.mydomain.com/myapp/
the calls will work like
http://v10test.mydomain.com/myapp/api/ModelName/12345
EDIT
Also - check the routes of your controllers or methods inside the controllers. This is the other place where the route may be modified/extended.

Related

Not able to access two different action under two different controllers in web api

I have two controllers which contain different action name, I am trying to access them, I am able to access PrepareTransmittalLetterPath under Transmittal controller but not able to access PrepareConcreteReport under Concrete controller.
May be my web api route is not working properly. Thanks in advance.
Transmittal Controller--where TransmittalApi is a class
public string PrepareTransmittalLetterPath(TransmittalApi transmittalApi)
{
}
Concrete Controller- where ConcreteReportApi is a class
public ConcreteReportApi PrepareConcreteReport(ConcreteReportApi concreteApi)
{
}
WebApiConfig-
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Api",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Using convention-based routes means your action method names need to start with Get.... or Post.....
To find the action, Web API looks at the HTTP method, and then looks for an action whose name begins with that HTTP method name. For example, with a GET request, Web API looks for an action that starts with "Get...", such as "GetContact" or "GetAllContacts". This convention applies only to GET, POST, PUT, and DELETE methods. You can enable other HTTP methods by using attributes on your controller.
See Routing in Asp.Net Web API for more information.

WebAPI ActionName routing half working

I have been building a WebAPI, trying to route to the right methods with ActionName. It works with one of my methods I try to call, but the other one gets a 404 error.
My WebAPI Config file:
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));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
My WebAPI Controller methods are formatted as such:
This first one is the working one:
[ActionName("postdb")]
public IEnumerable<string[]> postDB(string id)
{ ...
This second one does not:
[ActionName("getquery")]
public IEnumerable<string[]> getQuery(string tables)
{ ...
I'm calling both of them the same way from angular (Temp is a string that is being passed as the argument):
$http.post('api/Test/postdb/' + temp).then(function (response) { ...
and
$http.get('api/Test/getquery/' + temp).then(function (response) { ...
I have tried changing names of both actions, the first one works no matter the name, the second one doesn't work no matter the name. I have also tried reordering them, changing between GET and POST, and changing arguments.
Any suggestions?
Not sure why you are using ActionName to setup routing?
You should probably be looking at Route attribute. eg.
[HttpPost]
[Route("postdb")]
// Action doesn't have to be called 'postdb'
public IEnumerable<string[]> postDB(string id)
ActionName is usually used for a different purpose (Purpose of ActionName)
Nevertheless, I think something odd is going on in your example - I'd think setting ActionName shouldn't have affected routing there. To debug I'd suggest to set up Failed Request Tracing to see at which point the request fails to reach the action.
These are the basic rules for Action selection in WebAPI (http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection)
You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
If none of the above, the method supports POST.
So, in your example postdb method may map to the POST method. But may be because it's in lower case ASP.NET didn't like that and applied Rule 3 - try with ActionName("PostDB") and [ActionName("GetQuery")] if you really want to use ActionName (for whatever reason) instead of Route.
The name of the parameter tables in the second action
[ActionName("getquery")]
public IEnumerable<string[]> getQuery(string tables)
{ ...
does not match the name of the parameter id in the route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }

How can I disable some APIs of my ASP.NET application

Let's say that I have a ASP.NET application that have some APIs.
For example,
{HostName}/api/a/*
{HostName}/api/b/*
Now I want to disable all {HostName}/api/a/* APIs(But remain /api/b/*). When client call /api/a/* APIs, they get a 404 error. When client call /api/b/* APIs, response normally.
Is there any way to do this in c# ASP.NET application?
There are several approaches a can take to disable certain actions or routes as mentioned in the comments.
1.) [NonAction] attribute
The [NonAction] attribute from System.Web.Http can be applied for ApiController actions. If such a method is called then the server returns the HTTP Code 404 (Method not found). The attribute can only be applied on method level and not on classes. So every single method has to be decorated with this attribute.
2.) Writing a custom action filter
This approach gives you more control. Your filter can be applied on class level and you can implement some more advanced logic in which conditions your controller is accessible or not (depending on dates, licences, feature toggles and so forth)
public class MyNoActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (IfDisabledLogic(actionContext))
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
else
base.OnActionExecuting(actionContext);
}
}
[MyNoActionFilter]
public class ValuesController : ApiController
{
// web api controller logic...
}
3.) Route Configuration in WebApiConfig.cs
You can add a web api route for the inaccessible controllers in the WebApiConfig and map this route to a non existant controller. Then the framework takes this route, does not find the controller and sends a 404 return code to the client. It is important to place these routes at the beginning in order to avoid undesired execution.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Routes.MapHttpRoute(
name: "DisabledApi",
routeTemplate: "api/b/{id}",
defaults: new { controller = "DoesNotExist", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Because you stated not to use attributes because of the amount of work I recommend the third option, because the route configuration defines a single place for this. And if you want to enable the route in the future again you have to remove only one route definition.
Might be a hack, but works fine for me:
Changing scope of the Controller from public to internal hides all actions from that Controller class. So:
internal class AController : ApiController
{
[...]
}
Requests to http://host/api/a/* then will fail with
"No type was found that matches the controller named 'a'."
Feature flags were built for this purpose
How to enable/disable controllers/routes using feature flags: Tutorial for using feature flags in a .NET Core app | Microsoft Docs
To enable this in your app:
Add the Microsoft.FeatureManagement.AspNetCore nuget package to your project.
Add the feature management service to your builder in Program.cs: builder.Services.AddFeatureManagement();
Add settings for the features you want to turn on and off in appsettings.json:
{ "FeatureManagement": {    
"AdminAccess": true,
"AnonymousAccess": false
}
Add the FeatureGate decorator to the controllers/actions you want to control access to: [FeatureGate("AnonymousAccess")]

ASP.NET WebAPI returning Http 405

I have a WebApi controller
[RoutePrefix("api/invitations")]
public class InvitationsApiController : ApiController
And an action method:
[Route]
[HttpPost]
public IHttpActionResult Create([FromBody] CreateCommand command)
When i try to POST to http://host/api/invitations i get a "“405 Method Not Allowed”
But when I use another route like:
[Route("test")]
[HttpPost]
public IHttpActionResult Create([FromBody] CreateCommand command)
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
SetupFormatters(config);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
And make a POST to http://host/api/invitations/test its working. I have controllers that works just perfect when requesting to "root".
More details on Http error:
HTTP Error 405.0 - Method Not Allowed The page you are looking for
cannot be displayed because an invalid method (HTTP verb) is being
used.
Detailed Error Information:
Module DefaultDocumentModule
Notification ExecuteRequestHandler
Handler StaticFile Error
Code 0x80070001
Requested URL http://localhost:8008/api/invitations
Physical Path <removed>
Logon Method Anonymous
Logon User Anonymous
Any suggestions what might be wrong?
The problem is that you're having a physical directory that is matching your route (/api/invitations). Because such physical directory exists, IIS native DirectoryListingModule and DefaultDocumentModule modules (that are not allowing the POST verb) are taking over the request before the TransferRequestHandler (that is used by MVC and WebAPI) is able to handle it.
There is no reason to put your InvitationsController.cs in the same directory stucture that is going to match the requested route. That's why we have routing in the first place, to be able to register routes dynamically regardless of the physical location of the controller.
The common convention (that is also the default WebAPI template structure) is to put your controllers in the ~/Controllers directory (or ~/Controllers/Api). But you can actually put them in any directory as long as they're getting compiled as part of the project.
Change your controller RoutePrefix declaration to Route at the controller level:
[Route("api/invitations")]
public class InvitationsApiController : ApiController
RoutePrefix doesn't add the route to the route table. This means, that if you want to use RoutePrefix, you'll have to prefix each action by itself, for example:
[RoutePrefix("api/invitations")]
public class InvitationsApiController : ApiController
[Route("")]
[HttpPost]
public IHttpActionResult Create([FromBody] CreateCommand command)

Web API route is ignored and processed by MVC

I can't figure out why a route is being processed by MVC instead of Web API.
The whole route is configured as follows:
configuration.Routes.MapHttpRoute
(
name: "AdminControllers.TicketingController",
routeTemplate: "api/company/tickets",
defaults: new
{
controller = "Ticketing",
id = RouteParameter.Optional,
action = "GetTickets"
}
);
And the API controller looks like this:
public sealed class TicketingController : ApiController
{
[HttpGet]
public HttpResponseMessage GetTickets()
{
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK);
}
}
I perform a HTTP/GET request to /api/company/tickets and I get the following error:
The controller for path "/api/company/tickets" was not
found or does not implement IController.
Exception's stack trace points to ASP.NET MVC (not Web API): System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType).
AFAIK, this happens when you map a route using the wrong framework (MVC) instead of Web API HttpConfiguration.Routes.MapHttpRoute extension method. In my sample found above you'll find I'm using the right resource to register a controller in Web API.
I can confirm that the route gets registered during application startup.
The problem is ASP.NET MVC pipeline handles the route and, obviously, it does find no controller for the whole route.
What am I doing wrong here?
NOTE: It's ASP.NET Web API 1.x and I can't use Web API 2.0 (I would love to use attribute routing, yeah).
Update: A pic of registered routes after invoking .MapHttpRoute(...)
Update 2
Believe or not, but it started to work when I changed route configuration to:
configuration.Routes.MapHttpRoute(
name: "AdminControllers.TicketingController",
routeTemplate: "api/company/tickets/{id}",
defaults: new
{
controller = "Ticketing",
id = RouteParameter.Optional,
action = "GetTickets"
}
);
And Web API action changed to:
[HttpGet]
public HttpResponseMessage GetTickets(int? id)
{
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK);
}
It seems like a route parameter makes the route different enough to be ignored by MVC and then processed by Web API pipeline. But I can confirm that there's no other route starting with "api/company/".
Anyway, it works if I give the id (f.e. /api/company/tickets/11). Otherwise, MVC pipeline processes the route...
After looking for solutions, I've got a working one.
First of all, I'm deploying a Web API controller to Windows Azure Pack administrator's site (AdminSite). It's made with ASP.NET MVC 4.0. Anyway, I believe my answer should make sense if you find the same problem I'm going to describe in any ASP.NET MVC and Web API mixed application.
Windows Azure Pack registers this ASP.NET MVC route: {controller}/{action}/{id}: PROBLEM!
*Yes, because it's an URL route that can fit in Web API controller URI scheme...**
So... what?
At the end of the day, this is solved putting Web API routes before MVC ones. This way, ASP.NET will route requests to the first coincidence of some URI route pattern:
// #1 we add the whole routes. One for listing, other for getting
// a single entity (ticket)
configuration.Routes.MapHttpRoute(
name: "Company.AzurePack.Ticketing.List",
routeTemplate: "api/company/tickets",
defaults: new
{
controller = "Ticketing",
action = "GetTickets"
}
);
configuration.Routes.MapHttpRoute(
name: "Company.AzurePack.Ticketing.GetOne",
routeTemplate: "api/company/tickets/{id}",
defaults: new
{
controller = "Ticketing",
action = "GetTicket"
}
);
// #2 we get both added routes from the route table and we create two
// references to them. Later, we remove and re-insert them at the top
// of route table. BINGO!
RouteBase apiRoute1 = RouteTable.Routes[RouteTable.Routes.Count - 1];
RouteBase apiRoute2 = RouteTable.Routes[RouteTable.Routes.Count - 2];
RouteTable.Routes.Remove(apiRoute1);
RouteTable.Routes.Remove(apiRoute2);
RouteTable.Routes.Insert(0, apiRoute1);
RouteTable.Routes.Insert(0, apiRoute2);
Wait, Web API routes are registered into GlobalConfiguration.Configuration.Routes route table...
Yes, but HttpConfiguration.Routes registers its routes in RouteTable.Routes too, and ASP.NET pipeline works with RouteTable.Routes. That is, both MVC and WebAPI routes are in the same route table.
In the following screenshot, you'll find that Web API routes are of type HttpWebRoute:
Now these URIs are served by the fancy Web API controller:
/api/company/tickets
/api/company/tickets/11
An extension method to make our life even easier!
After I checked that this solution was working, I refactored above sample code to an extension method:
public static class HttpRouteCollectionExtensions
{
public static IHttpRoute MapHttpRouteAt(this HttpRouteCollection routes, int index, string name, string routeTemplate, object defaults = null, object constraints = null, HttpMessageHandler handler = null)
{
Contract.Requires(routes != null);
IHttpRoute route = routes.MapHttpRoute(name, routeTemplate, defaults, constraints, handler);
RouteBase apiRoute = RouteTable.Routes[RouteTable.Routes.Count - 1];
RouteTable.Routes.Remove(apiRoute);
RouteTable.Routes.Insert(index, apiRoute);
return route;
}
}
...and now I'm able to add routes at the top of routing table as follows:
configuration.Routes.MapHttpRouteAt(
index: 0,
name: "sampleRoute",
routeTemplate: "api/some/path/{name}",
defaults: new
{
controller = "Some",
action = "SomeAction",
httpMethod = new HttpMethodConstraint("GET")
}
);

Categories