I created a testing Web Api 2 in .Net 4.5.2.
The controller code is
using System.Web.Http;
using MyService.Models;
namespace MyService.Controllers
{
public class DefaultController : ApiController
{
[HttpPost]
[Route("Members/")]
public IHttpActionResult CreateRequest(MyRequestDto request)
{
return this.Ok();
}
[HttpGet]
[Route("Members/{id : int}")]
public IHttpActionResult GetMember(int id)
{
var response = new MyRequestDto()
{
FirstName = "Test F " + id,
LastName = "Test L " + id
};
return this.Ok(response);
}
}
}
The web api config code is
using System.Web.Http;
using MyService.Infrastructure;
namespace MyService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
formatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
}
But when I request the services from soapUI I got
404 http code for posting some JSON to http://localhost:15945/Members/
405 http code for getting from http://localhost:15945/Members/123
It looks like the routing is not working properly. But why?
I'm not sure but what about declare routing attribute on the class-
something like this:
[RoutePrefix("api")]
public class DefaultController : ApiController
You got error of 404 or 405 (for post), so that mean that your request didn't reach the web api.
404
if the request URI did not match a route for the Web API application, the server would return an HTTP 404 Not Found error.
405
A request method is not supported for the requested resource; for example, a GET request on a form which requires data to be presented via POST, or a PUT request on a read-only resource.
should be call
For JSON to http://localhost:15945/api/Members/
For http code for getting from http://localhost:15945/api/Members/123
please check this link http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
You have some syntax errors in your route templates. You should read up some more on Attribute Routing.
Given that here is your fixed controller.
public class DefaultController : ApiController
{
//POST Members
[HttpPost]
[Route("~/Members")]
public IHttpActionResult CreateRequest(MyRequestDto request)
{
return this.Ok();
}
//GET Members/123
[HttpGet]
[Route("~/Members/{id:int}")]
public IHttpActionResult GetMember(int id)
{
var response = new MyRequestDto()
{
FirstName = "Test F " + id,
LastName = "Test L " + id
};
return this.Ok(response);
}
}
Explanation:
Your original prefix for the POST method included a slash with out a parameter so it treated the slash as a literal part of the template.
Your GET method's template had bad syntax [Route("Members/{id : int}")] for the id parameter. {id : int} should not have spaces in the constraint is {id:int}.
A couple updates to your Route attributes and you should be fine:
Remove the trailing slash, change [Route("Members/")] to [Route("Members")]
Remove the spaces, change [Route("Members/{id : int}")] to [Route("Members/{id:int}")]
You don't need route prefixes, you don't need the tilde in front as the other answers suggest. We have hundreds of routes defined just like this across many different API projects and they work just fine.
The 404 error you are getting is because the trailing slash is part of your route definition, but the actual URI is without the slash even if you include the slash in your request because somewhere in the default asp.net pipeline or even IIS is set to remove the trailing slash. So the actual route never matches your attribute resulting in a 404.
The 405 error you are getting is because the spaces in the route attribute actually messed up the id variable so the route that was getting matched was your POST route and a GET is not allowed resulting in a 405.
Related
I created an asp.net core web api project, using the .net5 version, and I have a route like this.
[Route("api/detail")]
public IEnumerable<User> Get()
{
//TODO
return users;
}
[Route("api/detail")]
public IEnumerable<User> Get(string name)
{
//TODO
return users;
}
Although my request method is the same and the request parameters are different, the 500 error will be reported in swagger. Is there any way to solve it? Any help is greatly appreciated.
There could be multiple reasons why you're getting a 500 error. When I pasted your code into a new controller the first is error I received was:
Ambiguous HTTP method for action... Actions require an explicit HttpMethod binding for Swagger
It's telling you that you need to decorate each action in the controller with an HttpMethod binding, like [HttpGet]. More on that in a second...
The next issue is that you're using [Route] to bind two different action methods to the exact same route with the same HttpMethod. That's not possible in an API controller.
Conflicting method/path combination... Actions require a unique
method/path combination for Swagger
My preferred method for routing is to use Attribute routing with Http verb attributes.
The first step would be to move the route attribute to the controller. I'm going to assume you've created a DetailsController:
[Route("api/[controller]")]
[ApiController]
public class DetailsController : ControllerBase { }
Now, update your actions. Remove the [Route] attribute, replace with the HttpGet attribute, and add the name parameter to your second endpoint. I also prefer to return an IActionResult:
[HttpGet]
public IActionResult Get()
{
//TODO
return Ok(users);
}
[HttpGet("{name}")]
public IActionResult Get(string name)
{
//TODO
return Ok(users);
}
Note that parameters are identified by using curly braces around the variable {name} in the Http method attribute. Both endpoints work and are accessible through swagger. I urge you to read the linked page above for a better understanding of the possible routing options (linked again).
How do I pass optional (nullable) [FromBody] parameter in ASP.NET Core (5.0)? If I don't send body in my request I get 415 Unsupported Media Type error. Can this be configured and if so, how to do it on a controller or action, rather than an app level? I presume it has to do something with model validation, but not sure. Thanks.
[HttpGet("[action]")]
public async Task<IActionResult> GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
var result = await dataService.GetData(elements);
return Ok(result);
}
EDIT: To clarify:
This is typical scenario and it works normally:
But passing empty body is returning 415 right away without even reaching action:
You can find a solution here:
https://github.com/pranavkm/OptionalBodyBinding
From this issue on github:
https://github.com/dotnet/aspnetcore/issues/6878
And from .net Core 5 you can use this one:
public async Task<IActionResult> GetElementsAsync([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] IEnumerable<int> elements = default)
...
Also needed (from Pawel experience):
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
Just add content-type in your request header. Without the content-type:application/json will appear 415 when body is empty.
No changes to your controller. Test in my side is ok.
I created a new asp.net core 5 api project and this is my controller:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace WebApi_net5.Controllers
{
public class HomeController : ControllerBase
{
[HttpGet("[action]")]
public string GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
return "value";
}
}
}
With ASP.NET Core 3.1, I could allow nullable optional parameters by implementing Nicola's suggestion:
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
I will address some points that were not mentioned here.
To get rid of 415 without sending Content-Type you need to create your custom consumer of the Content-Type
https://stackoverflow.com/a/65813534/2531209
But I would say this is an overkill
If you pass Content-Type: application/json in your request header you will get "Body cannot be empty" (Tested on .NET 6) and only then #Nicola answer comes in handy.
From my tests it looks like modifications to the controller are not needed and only FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow) is enough with nullable type as parameter.
Or you can change nothing in your current code and send a header Content-Type: application/json with a body of {}. This will bypasses all of those errors, but this is not the most optimal solutions for public API's
I have a ASP.NET Core 3.1 API where I have not used CORS. As I understand, CORS is a browser thing. And as my ajax calls from another site on another origin is blocked to the API endpoints (which is great), I can still reach the same endpoints by using Postman or a HttpClient and GetAsync() calls.
My question is of it's possible to also block server-to-server calls (or Postman calls) to my API? Or like CORS, only allow certain origins?
Most of my endpoints are protected by a bearer JWT token, but I have an anonymous endpoint that I would like to let only origins I control (or can configure) to have access to that anonymous API.
I solved it after i bumped in to this post on stackoverflow:
How do you create a custom AuthorizeAttribute in ASP.NET Core?
I simply made a custom Authorize attribute [ApiAuthorize()], that I call this way:
[ApiController]
[ApiAuthorize(new string[] { "https://localhost:44351", "https://mysite.onthe.net" })]
public class MyInternalApiController : ControllerBase
{
...
}
It may also be implemented on the Action instead of the Controller. The implementation was done like this:
public class ApiAuthorizeAttribute : TypeFilterAttribute
{
public ApiAuthorizeAttribute(string[] origins) : base(typeof(ApiAuthorizeFilter))
{
Arguments = new object[] { origins };
}
}
public class ApiAuthorizeFilter : IAuthorizationFilter
{
readonly string[] _origins;
public ApiAuthorizeFilter(string[] origins)
{
_origins = origins;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_origins == null)
return;
string referer = context.HttpContext.Request.Headers["Referer"].ToString();
if (string.IsNullOrWhiteSpace(referer) || !_origins.Any(origin => referer.StartsWith(origin, StringComparison.OrdinalIgnoreCase)))
context.Result = new ForbidResult();
}
}
Things to consider:
The implementation and check of the referer could be exact match instead of StartsWith
The handling could use RegEx or any good alternative to handle subdomains, wildcards etc
The referer could be translated to a Uri objects to get better results and variations
A jQuery ajax call gets a "403 - Forbidden" as expected, but Postman gets a "404 - Not Found". To me that does not matter, but that's something to look into if it matters.
But it covers what I need, so I'm happy with this.
I am creating web api using asp.net core. The api end point is logically mapped to resource's relations based on guidelines here
So my API looks like
http://tax.mydomain.com/api/v1/clients/1/batches/12/start
Where Client is parent of Batch, 1 is clientid and 12 is batchid, and Start is POST action method.
Here is the corresponding controller
public class TaxController : Controller
{
[HttpPost]
[Route("clients/{clientid}/batches/{batchid}/start")]
public void Start([FromRoute]string clientId, [FromRoute]string batchId,
[FromBody]IEnumerable<string> urls)
{
// do something
}
}
since api/v1 is common to all controllers i configured that in startup's Configure method. Also i want Home as default controller.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapRoute("default","api/v1/{controller=Home}/{action=Index}/{id?}");
});
}
However client is getting not found error for api http://tax.mydomain.com/api/v1/clients/1/batches/12/start
Any controller methods that do not have a route attribute use convention-based routing.
When you use [Route] attribute, you define attribute routing and so conventional routing is not used for that action/controller. Therefore, your controller is accessible by
http://tax.mydomain.com/clients/1/batches/12/start
As an option, you can use the fact, that attribute routes can be combined with inheritance. Set a Route attribute on the entire controller and this will work as route prefix (the same behavior as [RoutePrefix] attribute in WebApi):
[Route("api/v1")]
public class TaxController : Controller
{
}
More general example from routing documentation:
[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }
public class ProductsController : MyBaseController
{
[HttpGet] // Matches '/api/Products'
public IActionResult List() { ... }
[HttpPost("{id}")] // Matches '/api/Products/{id}'
public IActionResult Edit(int id) { ... }
}
There are two things wrong with your setup
You call http://tax.mydomain.com/clients/1/batches/12/start but you don't have specified the controller name within it. This route looks for a controller named ClientsController. So the correct url would have to be http://tax.mydomain.com/tax/clients/1/batches/12/start instead
You seem to be using default MVC/Viewbased route, but your url suggest you use WebAPI.
When you use WebAPI to create a Rest service, you don't have any actions. Instead, actions map to the Http Verbs (GET (Read), PUT (update/replace), POST (insert), DELETE).
So for REST Services your default route should look like this instead: api/v1/{controller=Home}/{id?}
I have a .net core MVC Web API project (or whatever it's called in .net core now). I have a Message Controller:
[Route("api/[controller]")
public class MessageController
{
Public MessageController()
{}
// This works
[HttpGet]
public IMessage Get()
{
// Do stuff to get a Message
return Message;
}
}
When I call the Get endpoint like so:
GET => http://MyService/api/Message/
I get back the expected message object as JSON and an HTTP 200.
However, when I call a nonexistent endpoint:
GET => http://MyService/api/Message/Foo
I get back empty json [] and an HTTP 200.
Obviously something in the routing is matching on every route and returning 200 rather than matching only on actual routes and returning 404s for anything that doesn't match. So where is the default route which is catching everything defined and how do I kill it?