Route all Web API requests to one controller method - asp.net

Is it possible to customize ASP.NET Web API's routing mechanism to route all requests to the API to one controller method?
If a request comes in to
www.mysite.com/api/products/
or
www.mysite.com/api/otherResource/7
All would be routed to my SuperDuperController's Get() method?

I ran into a case where I needed to do this. (Web API 2)
I first looked into creating custom IHttpControllerSelector and IHttpActionSelectors. However, that was a bit of a murky way around. So I finally settled on this dead simple implementation. All you have to do is setup a wildcard route. Example:
public class SuperDuperController : ApiController
{
[Route("api/{*url}")]
public HttpResponseMessage Get()
{
// url information
Request.RequestUri
// route values, including "url"
Request.GetRouteData().Values
}
}
Any GET request that starts with "api/" will get routed to the above method. That includes the above mentioned URLs in your question. You will have to dig out information from the Request or context objects yourself since this circumvents automatic route value and model parsing.
The good thing about this is you can still use other controllers as well (as long as their routes don't start with "api/").

I don't konw why you would want to do this and I certainly wouldn't recommend routing everything through one controller, however you could achieve this as follows. Assuming you are only ever going to have a resource with an optional id in your calls, add this to your WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{resource}/{id}",
defaults: new { controller = "SuperDuper", id = RouteParameter.Optional }
);
}
}
Then define your controller method as follows:
public class SuperDuperController : ApiController
{
public IHttpActionResult Get(string resource, int? id = null)
{
return Ok();
}
}
You would need to decide on an appropriate IHttpActionResult to return for each different type of resource.
Alternatively using Attribute Routing, ensure that config.MapHttpAttributeRoutes() is present in your WebApiConfig and add the following attributes to your controller method:
[RoutePrefix("api")]
public class SuperDuperController : ApiController
{
[Route("{resource}/{id?}")]
public IHttpActionResult Get(string resource, int? id = null)
{
return Ok();
}
}

Related

How to make a method forbidden to direct request but allowed for server requests on Spring MVC?

What I basically want to know is this:
Suppose I have a method annotated with #RequestMapping and the value "/test/ajax". Can I make that specific method accessible only to internal calls but not to the client? If I run an ajax request on that url from within the server it should work normally, but if I run it directly from the browser it should return a 403.
Is that in any way possible?
add the spring annotation #CrossOrigin on controller layer for example
also, follow the given link https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
#CrossOrigin
#RestController
#RequestMapping("/account")
public class AccountController {
#GetMapping("/{id}")
public Account retrieve(#PathVariable Long id) {
// ...
}
#DeleteMapping("/{id}")
public void remove(#PathVariable Long id) {
// ...
}
}
If you allow only a method pass like this
#RestController
#RequestMapping("/account")
public class AccountController {
#CrossOrigin
#GetMapping("/{id}")
public Account retrieve(#PathVariable Long id) {
// ...
}
#DeleteMapping("/{id}")
public void remove(#PathVariable Long id) {
// ...
}
}

Default route when using ASP.NET MVC attribute routing

I'm using attribute routing with Web API, and everything works as expected if I request the URL /myapi/list with the following controller:
[RoutePrefix("myapi")]
public class MyController : ApiController
{
[HttpGet]
[Route("list")]
public async Task<string> Get()
{
// Return result
}
}
However, I would like my Get() method to be the default, i.e. when requesting the URL /myapi (without the /list part).
But if I remove the "list" part of the Route attribute like so...
[RoutePrefix("myapi")]
public class MyController : ApiController
{
[HttpGet]
[Route] // Default route
public async Task<string> Get()
{
// Return result
}
}
...I get a 403.14 error saying
"The Web server is configured to not list the contents of this
directory."
Any ideas of what might be causing this?
Thanks!
Edit: If I request the API controller using the default route pattern like /api/myapi, it maps to the Get() method as expected.
Default route is registered after the attribute routes:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Use
[Route("")]
for the default route
[RoutePrefix("myapi")]
public class MyController : ApiController
{
//GET myapi
[HttpGet]
[Route("")] // Default route
public async Task<string> Get() { ... }
}
Reference: Attribute Routing in ASP.NET Web API 2 : Route Prefixes
As pointed out by haim770 in the comments: the problem was that I had a physical folder with the same name as the route prefix.
Renaming either the folder or the route prefix solved the problem.
I guess an alternative would have been to tweak the route/handler order to ensure attribute routes take precedence over physical paths.

Authorization has been denied for this request error when running webapi in MVC project

I have created an ASP.Net MVC project with WebApi option. Then modified the values controller with the code below:
public class ValuesController : ApiController
{
static List<string> data = initList();
private static List<string> initList()
{
var ret = new List<string>();
ret.Add("value1");
ret.Add( "value2" );
return ret;
}
// GET api/values
public IEnumerable<string> Get()
{
return data ;
}
// GET api/values/5
public string Get(int id)
{
return data[id];
}
// POST api/values
public void Post([FromBody]string value)
{
data.Add(value);
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
data[id] = value;
}
// DELETE api/values/5
public void Delete(int id)
{
data.RemoveAt(id);
}
}
When I am running the project and navigating to API/values URL, the following image is showing error.
.
The error description in text is:
<Error>
Authorization has been denied for this request.
</Error>
Have a look at the following article about
Authentication and Authorization in ASP.NET Web API
It will explain the different ways of how to use the [Authorize] and [AllowAnonymous] attribute on your controller/actions and any configurations you would need to do.
The following was taken from the linked article above:
Using the [Authorize] Attribute
Web API provides a built-in authorization filter,
AuthorizeAttribute. This filter checks whether the user is
authenticated. If not, it returns HTTP status code 401 (Unauthorized),
without invoking the action.
You can apply the filter globally, at the controller level, or at the
level of inidivual actions.
Globally: To restrict access for every Web API controller, add the
AuthorizeAttribute filter to the global filter list:
public static void Register(HttpConfiguration config){
config.Filters.Add(new AuthorizeAttribute());
}
Controller: To restrict access for a specific controller, add the
filter as an attribute to the controller:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post() { ... }
}
Action: To restrict access for specific actions, add the attribute to
the action method:
public class ValuesController : ApiController
{
public HttpResponseMessage Get() { ... }
// Require authorization for a specific action.
[Authorize]
public HttpResponseMessage Post() { ... }
}
Alternatively, you can restrict the controller and then allow
anonymous access to specific actions, by using the [AllowAnonymous]
attribute. In the following example, the Post method is restricted,
but the Get method allows anonymous access.
[Authorize]
public class ValuesController : ApiController {
[AllowAnonymous]
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
}
In the previous examples, the filter allows any authenticated user to
access the restricted methods; only anonymous users are kept out. You
can also limit access to specific users or to users in specific roles:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
So, I've been dealing with this error for awhile.
I didn't understand it at first, so I just removed and lived with it.
I finally got sick of it, because it's rather stupid. Microsoft wants a user to be authorized before they have signed in.
My error was looking for GET method which asks for HomeTown. In my case, I had changed it to CityCode.
Since the user is not logged in, there is no CityCode to GET. So, you get either a 402 or a 500 Resource Not Found.
I still don't understand it so, I gave CityCode some default data. So, from MeController I put the following code:
Public Function [Get]() As GetViewModel
Dim userInfo As ApplicationUser = UserManager.FindById(User.Identity.GetUserId())
Return New GetViewModel() With {.CityCode = "94110"}
End Function
App loads completely error free now.
This is a quick fix, not a certified solution.

Abstract Generic ODataController Class Leads To 'No HTTP resource was found'

I am trying to abstract the auto-generated ODataController class in VS 2013 because the code looks identical across different controllers except the name of the POCO, so, I did the following:
public abstract class ODataControllerBase<T,DB> : ODataController
where T : class, IIdentifiable, new()
where DB : DbContext, new()
{
protected DB _DataContext;
public ODataControllerBase() : base()
{
_DataContext = new DB();
}
// only one function shown for brevity
[Queryable]
public SingleResult<T> GetEntity([FromODataUri] int key)
{
return SingleResult.Create(_DataContext.Set<T>().Where(Entity => Entity.Id.Equals(key)));
}
}
IIdentifiable is an interface that forces the T parameter to have a readable/writable Id integer property.
The implementation looks like this (POCOs and DataContexts should've already been created)
public class MyObjectsController : ODataControllerBase<MyObject,MyDbContext>
{
public MyObjectsController() : base()
{
}
// That's it - done because all the repetitive code has been abstracted.
}
Now, my WebApiConfig's Register function contains the following only:
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyObject>("MyObjects");
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
}
I run the project, http://localhost:10000/odata/MyObjects and I get the response:
<m:error>
<m:code/>
<m:message xml:lang="en-US">No HTTP resource was found that
matches the request URI `http://localhost:10000/odata/MyObjects.`
</m:message>
<m:innererror>
<m:message>No routing convention was found to select an action
for the OData path with template '~/entityset'.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
</m:error>
What is missing? What should I remove? Is this something we can't do, i.e. are we really required to inherit ODataController directly with no intermediate parent class?
In one of our projects We also use a generic ODataController base class where we actually use GetEntity for retrieving single entities and GetEntitySet for retrieving a list of entities.
According to your supplied URL and the resulting error message, the ODATA framework cannot find an ODataAction for ~/entityset. As you have given http://localhost:10000/odata/MyObjects as the example, the action in question cannot be public SingleResult<T> GetEntity([FromODataUri] int key) as this only corresponds to a query like this http://localhost:10000/odata/MyObjects(42).
Our code for a generic controller looks like this:
public abstract class OdataControllerBase<T> : ODataController
where T : class, IIdentifiable, new()
{
protected OdataControllerBase(/* ... */)
: base()
{
// ...
}
public virtual IHttpActionResult GetEntity([FromODataUri] long key, ODataQueryOptions<T> queryOptions)
{
// ...
return Ok(default(T));
}
public virtual async Task<IHttpActionResult> GetEntitySet(ODataQueryOptions<T> queryOptions)
{
// ...
return Ok<IEnumerable<T>>(default(List<T>));
}
public virtual IHttpActionResult Put([FromODataUri] long key, T modifiedEntity)
{
// ...
return Updated(default(T));
}
public virtual IHttpActionResult Post(T entityToBeCreated)
{
// ...
return Created(default(T));
}
[AcceptVerbs(HTTP_METHOD_PATCH, HTTP_METHOD_MERGE)]
public virtual IHttpActionResult Patch([FromODataUri] long key, Delta<T> delta)
{
// ...
return Updated(default(T));
}
public virtual IHttpActionResult Delete([FromODataUri] long key)
{
// ...
return Updated(default(T));
}
}
The code for a specific controller then is as short as this:
public partial class KeyNameValuesController : OdataControllerBase<T>
{
public KeyNameValuesController(/* ... */)
: base()
{
// there is nothing to be done here
}
}
However we found out that both Get methods (for single result and enumerable result) actually have to start with Get. First we tried List instead of GetEntitySet and this did not work, as the framework then expects a POST for the List action).
You can actually verify and diagnose the resolving process by supplying a custom IHttpActionSelector as described in Routing and Action Selection in ASP.NET Web API (ahving a look at ASP.NET WEB API 2: HTTP Message Lifecycle might also be worth it).
So actually it is possible to use GetEntity as your method name as you originally tried in your example and there is no need to rename it to simple Get. In addition, there is no need for any modification in your ODATA configuration.
To determine which action to invoke, the framework uses a routing table. The Visual Studio project template for Web API creates a default route:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Routing by Action Name
With the default routing template, Web API uses the HTTP method to select the action. However, you can also create a route where the action name is included in the URI:
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I configured config as follows:
config.Routes.MapHttpRoute(
name: "GetMessage",
routeTemplate: "api/{controller}/{action}/{quoteName}",
defaults: new { quoteName = RouterParameters.Optional }
);
Access your URI like this:
http://localhost:42201/api/Extract/GetMessage/Q3
OR
http://localhost:42201/api/Extract/GetMessage/?quotename=Q3

ASP.NET Web API Routing with PUT

I'm trying to setup Web API routing for what I thought would be a very simple thing. However, it seems that routing in Web API is not consistent with different HTTP verbs. Suppose I have this controller with these actions...
public class AvalancheController : ApiControllerBase
{
// GET api/avalanche
public IEnumerable<Avalanche> Get() {}
// GET api/avalanche/5
public Avalanche Get(int id) {}
// GET api/avalanche/ActionTest/5
[ActionName("ActionTest")]
public Avalanche GetActionTest(int id) {}
// GET api/avalanche/ActionTest/2
[ActionName("ActionTest2")]
public Avalanche GetActionTest2(int id) {}
// POST api/avalanche
public void Post([FromBody]Avalanche value) {}
// PUT api/avalanche/5
public void Put(int id, [FromBody]Avalanche value) {}
// PUT api/avalanche/test/5
[ActionName("Test")]
public void PutTest(int id, [FromBody]Avalanche value) {}
// DELETE api/avalanche/5
public void Delete(int id) {}
}
and I have the following routes defined...
config.Routes.MapHttpRoute(
name: "ActionRoutes",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new
{
controller = "Avalanche",
action = "(ActionTest|ActionTest2|Test)"
}
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then I end up with the following routes being defined...
GET api/Avalanche/ActionTest/{id}
GET api/Avalanche/ActionTest2/{id}
PUT api/Avalanche/Test/{id}
GET api/Avalanche
POST api/Avalanche
DELETE api/Avalanche/{id}
Why doesn't the default PUT route get picked up? What's different between the routing of the default GET and the default PUT? I've tried decorating the functions in every imaginable way but I get the same results.
Mainly I want to know how to get the default PUT route to be picked up. If you have any suggestions on how to modify these routes so that I don't have to have a route for each controller to specify action names that would be fantastic also.
Thanks!
Ian
EDIT: I noticed this morning that the following route is also not being defined..
GET api/Avalanche/{id}
Glad you've found solution for your problem. But I would provide my feedback based on my learning with REST services. Idea for REST webservice, is to resolve each url to a resource (or maybe entity) and depending upon HttpVerb, operation is decided. In this case, you've three GET operations, which works fine with your modification.
But I think controllers can also be re-arranged to have single GET operation and have single responsibility thus better maintainability. For ex:
AvalancheController
public class AvalancheController : ApiControllerBase
{
public IEnumerable<Avalanche> GET()
{
}
public void POST(Avalanche avalanche)
{
}
}
It can be assumed to deal with all avalanche (s) on top level, below are the operations to be defined.
GET : returns all avalanche
POST: inserts new avalanche
PUT: not used
DELETE: not used
AvalancheDetailsController
public class AvalancheDetailsController : ApiControllerBase
{
public Avalanche GET(int id)
{
}
public int PUT(int id)
{
}
public int DELETE(int id)
{
}
}
It can be assumed to deal with single avalanche, below are the operations to be defined.
GET : returns single avalanche
POST: not used
PUT: updates single avalanche
DELETE: deletes single avalanche
Now I assume we have clear distinction of between controllers. In the OP you've mentioned, there can be different GET operations, but it returns only single Avalanche. So, I would change GET method to take object as input and check for values i.e,
public class AvalanceRequest
{
public int? Id {get;set;}
public string Name {get;set;}
}
public class AvalancheDetailsController : ApiControllerBase
{
public Avalanche GET(AvalanceRequest request)
{
//write business logic based on parameters
if(request.Id.HasValue)
//return avalanche;
if(request.Name.IsNullOrEmpty())
//return avalanche
}
//other methods
}
Dealing with URL, I didn't really work with WebAPI but was trying ServiceStack to develop REST services. It allows to attach url's independent of controller names.
Url
api/Avalanche --> AvalancheController (Operations are called based on HttpVerb)
api/Avalanche/Id --> AvalancheDetailsController (Operations are called based on HttpVerb)
I don't know whether url's can be attached likewise in WebAPI, otherwise you end up having default config and call via. api/Avalanche and api/AvalancheDetails/id.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I am sorry for long post, hope it makes sense.

Resources