My question may look simple but I'm stuck in this problem for a while.
Code Controller:
[Route("api/ReceiptOrders/{receiptOrderNo}/[controller]")]
[ApiController]
public class ReceiptPositionsController : ControllerBase
{
[HttpPost("{orderNo}")]
public async Task<IActionResult> PostReceiptOrderPositions(
[FromBody] IEnumerable<ReceiptPositionForCreationDto> receiptPositions)
{
var receiptOrder = await _repositoryManager.ReceiptOrder.GetReceiptOrderByOrderNoAsync(receiptOrderNo, false);
//More code here...
}
}
URL: https://localhost:7164/api/ReceiptOrders/RO-20220731-4/ReceiptPositions
my question is: how to get the ReceiptOrderNo(RO-20220731-4 in this case)?
I need it to fill "GetReceiptOrderByOrderNoAsync" method.
Bind to the template entry by adding another parameter in your method. In addition, remove the template from the method's attribute. A method's template is appended to the class's template, so what you currently have won't match.
[Route("api/ReceiptOrders/{receiptOrderNo}/[controller]")]
[ApiController]
public class ReceiptPositionsController : ControllerBase
{
[HttpPost] // <--- no template
public async Task<IActionResult> PostReceiptOrderPositions(
[FromRoute] string receiptOrderNo, // <---- new parameter
[FromBody] IEnumerable<ReceiptPositionForCreationDto> receiptPositions)
{
// ... use "receiptOrderNo"
}
You don't need [FromRoute], but I think it's a good idea as it makes it explicit where the value should be read from, instead of accidently read from a Query String or another source.
Related
I have a question about .NET Core controller routing. Recently I discovered that the controller route attribute (which you place just above the controller) only works for the root method, or at least it seems that way.
My code:
using KrabbelMicroservice.Models;
using KrabbelMicroservice.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace KrabbelMicroservice.Controllers;
[ApiController]
[Route("/profile")] // <-- This is the controller routing attribute I am talking about
public class ProfileKrabbelController : Controller
{
private readonly IProfileKrabbelService _krabbelService;
public ProfileKrabbelController(IProfileKrabbelService krabbelService)
{
_krabbelService = krabbelService;
}
[HttpGet]
public IActionResult Index()
{
// not relevant
}
[HttpGet]
[Route("/id/{krabbelId}")]
public IActionResult GetKrabbelById(long krabbelId)
{
// not relevant
}
[HttpGet]
[Route("/pid/to/{profileId}")]
public IActionResult GetKrabbelsToProfileId(long profileId)
{
// not relevant
}
[HttpGet]
[Route("/pid/from/{profileId}")]
public IActionResult GetKrabbelsFromProfileId(long profileId)
{
// not relevant
}
[HttpGet]
[Route("/pid/with/{profileId}")]
public IActionResult GetKrabbelsWithProfileId(long profileId)
{
// not relevant
}
[HttpPost]
[Route("/new")]
public IActionResult AddKrabbel(ProfileKrabbel krabbel)
{
// not relevant
}
[HttpPut]
[Route("/update")]
public IActionResult UpdateKrabbel(ProfileKrabbel krabbel)
{
// not relevant
}
[HttpDelete]
[Route("/delete")]
public IActionResult DeleteKrabbel(ProfileKrabbel krabbel)
{
// not relevant
}
}
In my swagger launch the requests look like this:
I expected that all paths would be prefixed by /profile/ but it seems like only the root function (which did not have its own route attribute) implemented the prefix.
I am not only trying to get a fix for this, but also looking for an explanation as to why my controller route attribute is ignored for the other requests. The only possibility I can think of is the specific route attributes for each endpoint overriding the controller route attribute but I would like to hear it from an expert.
Secondly I would of course also like to find a solution to this problem, preferrably not adding /profile before every seperate route but if that is the only solution so be it.
Thanks in advance!
you should be remove "/" if you have root route
ex:
[Route("test")]
[ApiController]
public class TestController3 : Controller
{
[HttpGet]
[Route("testobj")]
public TestObj Test()
{
return "test";
}
}
the even shorter in httpget
[HttpGet("testobj")]
the both output:
test/testobj
I have the following action within an asp.net core 3.1 controller
[ApiController]
[Route("example")]
public class MyExampleController : ControllerBase
{
[HttpPost("{id}/value")]
public async Task<IActionResult> Post(string id, [FromBody] int? value)
=> Task.FromResult(Ok());
}
This works fine if I post a body value of int (for example: 1, 2, etc...)
However, I can't find a way to get a null value passed in.
If I pass in an empty body or null body I get a status code of 400 returned with a validation message of A non-empty request body is required. returned.
I've also tried to change the value parameter to be an optional argument with a default value of null:
public async Task<IActionResult> Post(string id, [FromBody] int? value = null)
How do I pass in null to this action?
Finally figured this out, big thanks to #Nkosi and #KirkLarkin helping fault find this.
Within the Startup.cs when configuring the controllers into the container we just need to alter the default mvc options to AllowEmptyInputInBodyModelBinding
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(x => x.AllowEmptyInputInBodyModelBinding = true);
}
This way we can pass in null into the body of the post and it works perfectly fine. It also still applies the normal model validation via the attributes without having to check the ModelState manually:
public async Task<IActionResult> Post(string id,
[FromBody][Range(1, int.MaxValue, ErrorMessage = "Please enter a value bigger than 1")]
int? value = null)
Reference Automatic HTTP 400 responses
The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response
This would explain the returned response.
Remove the [ApiController] to allow the invalid request to still make it to the controller action and also if the additional features of having that attribute is not critical to the current controller.
It would however require that the desired featured be applied manually
[Route("example")]
public class MyExampleController : ControllerBase {
[HttpPost("{id}/value")]
public async Task<IActionResult> Post(string id, [FromBody] int? value) {
if (!ModelState.IsValid) {
//...
return BadRequest(ModelState);
}
//...
return Ok();
}
}
It seems it has something to do with the way a single value is conducted through JSON. It expects a value and the null just simply creates an empty request body. You should consider defining a class like this
public class MyInt{
public int Value { get; set; }
public bool IsNull { get; set; }
}
[ApiController]
[Route("example")]
public class MyExampleController : ControllerBase
{
[HttpPost("{id}/value")]
public IActionResult Post(string id, [FromBody]MyInt value)
{
if(value.IsNull){
}
else{
}
return Ok();
}
}
In other words when you POST you post not just use a default value. You could do that the other way like this
[HttpPost("{id}/value")]
public IActionResult Post(string id, [FromBody]int value)...
[HttpGet("{id}/value")]
public IActionResult Get(string id)...//use the default value here
I have Asp.Net Core web application. With following controller
[Produces("application/json")]
[Route("api/[controller]")]
public class TestController
{
[HttpGet]
[Route("firstroute")]
public async Task<IActionResult> FirstMethod()
{
...Some code...
}
[HttpGet]
[Route("secondroute")]
public async Task<IActionResult> SecondMethod()
{
...
SomeMethod(redirectLink)
...
}
}
What I need is to get fully assembled redirectLink to FirstMethod (it will probably be similar to this: "http://localhost/api/test/firstroute").
I need not RedirectToAction, but exact Url as string.
Didn't manage to find any suitable methods in this.Url or Microsoft.AspNetCore.Http.Extensions.
this.Request.GetDisplayUrl() returns result in appropriate format, but only for the called method.
you can use data from HttpContext.Request like bellow
var Url = string.Format("{0}://{1}{2}", HttpContext.Request.Scheme,HttpContext.Request.Host,"/api/firstroute");
and rediret by
return Redirect(Url);
In the documentation of ODATA's WebAPI there is a page about Attribute Routing.
In this page, there is an example about using ODataRoutePrefixAttribute when all requests to a particular controller have the same prefix, and this prefix can include a parameter. In the sample, all action methods declare the same parameter. From their sample:
[ODataRoutePrefix("Customers({id})")]
public class MyController : ODataController
{
[ODataRoute("Address")]
public IHttpActionResult GetAddress(int id)
{
......
}
[ODataRoute("Address/City")]
public IHttpActionResult GetCity(int id)
{
......
}
[ODataRoute("/Order")]
public IHttpActionResult GetOrder(int id)
{
......
}
}
I would like to avoid repeating the parameter in each and every method and just have it be a property of the class, like this:
[ODataRoutePrefix("Customers({id})")]
public class MyController : ODataController
{
public int Id
{
get { ... }
}
[ODataRoute("Address")]
public IHttpActionResult GetAddress()
{
......
}
}
How to get the value of the id parameter from the URL when it is not passed as parameter to the action method?
I found out that I could implement the property getter by reading the value from the RequestContext.RouteData.Values:
public string Id => (string)this.RequestContext.RouteData.Values["id"];
One drawback of this solution is that route data values do not seem to be available during the controller's Initialize method, so one needs to be cautious not to depend on such properties in there.
I'm trying to implement both Attribute Routing and the VersionedRoute from RoutingConstaints Sample but when I use both on a controller, the versioned attribute no longer works.
What would I need to modify on the attribute to get it to play nice with Attribute Routing?
For code example download the sample project (or just look at the few files from the above link) and then modify the routes as such:
// When I use the RoutePrefix, VersionedRoute no longer works (Sending "Api-Version" through http header doesn't route correctly
// If I remove the RoutePrefix I can use VersionedRoute again
// What do I need to change in its code to be able to use both?
[VersionedRoute("api/Customers", 1)] // This route would be used as http://url/api/customers with a header of "api-version: 1"
[RoutePrefix("api/v1/Customers")] // This route would be used purely through url versioning of http://url/api/v1/Customers
public class CustomersV1Controller : ApiController {
/* Other stuff removed */
[VersionedRoute("api/Customer", 1)] // I'd rather not have to use this here at all and just use a single one on the class, but having both nor just one on either works right now.
[Route("")]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
VersionedRoute Code
VersionConstraint Code
Edit: Please let me know if you need more information or even post ideas or things to try :)
Edit2: Here is an example of what I'm trying to do from Troy Hunt's Blog: http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html
Edit3: Here is what I'd like to code to be as close to since it would reduce a lot of the overhead and magic strings.
[VersionedRoute("api/Customers", 1)] // This route would be used as http://url/api/customers with a header of "api-version: 1"
[RoutePrefix("api/v1/Customers")] // This route would be used purely through url versioning of http://url/api/v1/Customers
public class CustomersV1Controller : ApiController {
/* Other stuff removed */
[Route("")]
public IHttpActionResult Get()
{
// Removed
return Ok(customers);
}
[Route("{id:int}")]
public IHttpActionResult GetById(int id)
{
// Removed
return Ok(customer);
}
}
[VersionedRoute("api/Customers", 2)] // This route would be used as http://url/api/customers with a header of "api-version: 2"
[RoutePrefix("api/v2/Customers")] // This route would be used purely through url versioning of http://url/api/v2/Customers
public class CustomersV2Controller : ApiController {
/* Other stuff removed */
[Route("")]
public IHttpActionResult Get()
{
// Removed
return Ok(customersThatAreDifferentThanV1);
}
[Route("{id:int}")]
public IHttpActionResult GetById(int id)
{
// Removed
return Ok(customerThatIsDifferent);
}
}
Edit: Last bump, trying to only have to write the route version information once per route, at the controller attribute level and not per-action.
The Route and VersionedRoute attributes are working fine together, but your RoutePrefix attribute is also applied to your VersionedRoute (try accessing /api/v1/Customers/api/Customer - you'll get a response when the api-version header is set)
The following code would produce the desired behaviour with regards to the two URLs in your example returning the correct responses, but obviously this does not solve your problem of wanting one VersionedRoute and one RoutePrefix at the top of the class. Another approach would be needed for this. You can, however, have separate controllers for different api versions.
[RoutePrefix("api")]
public class CustomersV1Controller : ApiController
{
/* Other stuff removed */
[VersionedRoute("Customers", 1)]
[Route("v1/Customers")]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
An improvement would be to create your own attribute instead of Route so you wouldn't need to prefix the version every time:
public class CustomVersionedRoute : Attribute, IHttpRouteInfoProvider
{
private readonly string _template;
public CustomVersionedRoute(string route, int version)
{
_template = string.Format("v{0}/{1}", version, route);
}
public string Name { get { return _template; } }
public string Template { get { return _template ; } }
public int Order { get; set; }
}
[RoutePrefix("api")]
public class CustomersV2Controller : ApiController
{
/* Other stuff removed */
[VersionedRoute("Customers", 2)]
[CustomVersionedRoute("Customers", 2)]
public IHttpActionResult Get()
{
return Json(_customers);
}
}