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")]
Related
I have a legacy WebApi running on ASP.NET 4.6.1 where I need to be able to enable OData fitler support to an endpoint.
However, when debugging the controller through Postman, the ODataQueryOptions<Job> searchOptions on the controller is always null.
My query string example is;
https://localhost:44310/api/job/find/odata?Filter=[["Title"%2C"contains"%2C"T"]]&Skip=1&Top=5
I have added System.Web.Http.OData - V5.7.0.0 reference to the controller.
With the following in my WebApiConfig Register method;
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.EnableQuerySupport();
//config.AddODataQueryFilter();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
To note I have tried both EnableQuerySupport() which intellisense is saying is obselete and AddODataQueryFilter() however neither seems to work.
In my controller I then have the following code;
[HttpGet]
[EnableQuery]
[Route("find/odata")]
public PageResult<Job> Find(ODataQueryOptions<Job> searchOptions)
{
...
return new PageResult<Job>(response.Result, null, null);
}
I have tried with and without the [EnableQuery] filter with no change.
for reference the link I have been using is;
Microsoft OData tutorial
Any resolution on this? I'm seeing the same behavior. If I globally enable filter via config.Filter(), it works, but I'd rather enable it on a class by class or property by property basic, which doesn't appear to work.
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.
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.
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)
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")
}
);