URLs ignore arguments in GET request (ASP.NET) - c#

I need to write a Web API for this request:
{apiUrl}/api/sessions/byhour?startTime=2021-06-30T01:00:00&endTime=2021-06-30T03:00:00
So I have this controller and method:
[ApiController]
[Route("/api/sessions/byhour")]
public class LoginStatsByHourController : ControllerBase
{
[HttpGet, Route("{startTime=0001-01-01T12:00:00}/{endTime=9999-12-31T11:59:59}")]
public List<SessionEntry> GetSessionEntryByDate(string startTime, string endTime)
{...}
}
I tested this request:
https://localhost:5001/api/sessions/byhour/2021-07-01T14%3A00%3A00/2021-07-01T16%3A00%3A00
which essentially equals to:
https://localhost:5001/api/sessions/byhour/2021-07-01T14:00:00/2021-07-01T16:00:00
and everything works fine. But when I try this request:
https://localhost:5001/api/sessions/byhour?startTime=2021-07-01T14:00:00&endTime=2021-07-01T16:00:00
(notice ? and &). And I discovered that these arguments are ignored and the default ones (0001-01-01T12:00:00 and 9999-12-31T11:59:59) are used instead. Why is that so?

Try this :
[HttpGet]
public List<SessionEntry> GetSessionEntryByDate(string startTime, string endTime)
{...}
and call it like this : https://localhost:5001/api/sessions/byhour/GetSessionEntryByDate?startTime=2021-07-01T14:00:00&endTime=2021-07-01T16:00:00
for further reading : checkout this link

It ignores your arguments, because if you want to use a query string you have to remove "{startTime}/{endTime}" and sometimes ( depends on the version) have to add [FromQuery] attribute to each input parameter
[HttpGet]
public List<SessionEntry> GetSessionEntryByDate(string startTime="0001-01-01T12:00:00",
string endTime="9999-12-31T11:59:59")
{...}
}
If you still want to use MVC routing , try this
[HttpGet("{startTime?}/{endTime?}")]
public List<SessionEntry> GetSessionEntryByDate(string startTime="0001-01-01T12:00:00",
string endTime="9999-12-31T11:59:59")
{...}
}

Related

ASP.NET Core Web API : route by query parameter

I am coming from a heavy Java/Spring background and trying to transition some knowledge over to ASP.NET Core 6.
In Spring, on a RestController, I am able to route the request based on the presence of a query parameter.
So a HttpRequest with the uri: /students?firstName=Kevin can be routed to a different controller method than a HttpRequest with the uri: /students.
In ASP.NET Core 6, I am unable to determine if the equivalent is possible after working through some examples and reading the documentation for Web API.
Here is what I am trying to achieve, is this possible using two methods and routing configuration that will discern which controller method to invoke based on the query parameter?
[ApiController]
[Route("Students")]
public class StudentHomeProfileController : ControllerBase
{
[HttpGet] //Route here when no parameters provided
public async Task<ActionResult<IEnumerable<Student>>> GetStudentAsync()
{
/* Code omitted */
}
[HttpGet] //Route here when firstName query param provided
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
}
While filtering by query parameters does not come with ASP.NET Core out of the box, it's not too hard to supply this functionality on your own.
When it comes to extensibility, ASP.NET has some superpowers, one of them is IActionConstraint, which
Supports conditional logic to determine whether or not an associated action is valid to be selected for the given request. (Source)
Creating an annotation to filter for query parameters is as easy as
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryParameterConstraintAttribute : Attribute, IActionConstraint
{
private readonly string _parameterName;
public QueryParameterConstraintAttribute(string parameterName)
{
this._parameterName = parameterName;
}
public bool Accept(ActionConstraintContext context)
{
return context.RouteContext.HttpContext.Request.Query.Keys.Contains(this._parameterName);
}
public int Order { get; }
}
All that's left is annotating your controller method with that constraint
[HttpGet] //Route here when firstName query param provided
[QueryParameterConstraint("firstName")]
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
In a quick test I was able to confirm that it seems to work as intended, even if you add multiple of those attributes for different query parameters (if all conditions match, the route is called).
(Please note, this was tested with .NET Core 2.1. Anyway, it shuold be pretty much the same with .NET 6)
I think you are looking for something like this, you need to specify the parameter in the "HttpGet" attribute
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing-with-http-verb-attributes
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
You are trying to differentiate API calls using query params. this is not the way to do this. if you want to separate the calls you should probably use path params instead.
Read more about Routing in ASP.NET Core - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0

Why does Post work, but not Get, with the same route and args?

I have the following Controller code:
namespace PlatypusReports.Controllers
{
[RoutePrefix("api/platypus")]
public class PlatypusController : ApiController
{
[Route("{unit}/{begindate}")]
[HttpPost]
public void Post(string unit, string begindate)
{
. . .
}
[Route("{unit}/{begindate}")]
[HttpGet]
public string Get(string unit, string begindate)
{
. . .
}
. . .
Calling the POST method works, but calling the GET method does not; in the latter case, I get, "405 Method Not Allowed - The requested resource does not support http method 'GET'."
I call them with the same exact URL from Postman:
http://localhost:52194/api/platypus/poisontoe/201509
...the only difference being I select "POST" it works, and when I select "GET" it doesn't.
Why would POST work but not GET? What do I have to change in my GET code to get it to be supported/allowed?
If you are using an apicontroller you don't need the decorators HttpPost and HttpGet. If you remove them it should then work.

Optional DateTime Web API

I have a class like this:
public class FooController : ApiController
{
[System.Web.Http.Route("live/topperformers")]
[System.Web.Http.AcceptVerbs("GET", "POST")]
[System.Web.Http.HttpGet]
public List<string> GetTopPerformers()
{
return new List<string>();
}
}
When I access it by going to "http://foo.com/live/topperformers", it works great. So now I want to add an optional DateTime param to this method, so I modify the method to take a DAteTime param, and make it nullable.
public class FooController : ApiController
{
[System.Web.Http.Route("live/topperformers/{dateTime:DateTime}")]
[System.Web.Http.AcceptVerbs("GET", "POST")]
[System.Web.Http.HttpGet]
public List<string> GetTopPerformers(DateTime? dateTime)
{
return new List<string>();
}
}
When I try to access the URL without a parameter, the same as I access before - it gives a 404. Pasing in the date value like " like "http://foo.com/live/topperformers/2010-01-01" works fine. But without the date, it gives 404.
I thought Web API supported optional params in this fashion? I can simply overload and have both versions, but is this possible with just one method?
Set the optional parameter = null. Try this:
public class FooController : ApiController
{
[System.Web.Http.Route("live/topperformers/{dateTime:DateTime?}")]
[System.Web.Http.AcceptVerbs("GET", "POST")]
[System.Web.Http.HttpGet]
public List<string> GetTopPerformers(DateTime? dateTime = null)
{
return new List<string>();
}
}
You missed to make your route paramter optional. Change your code to the following
public class FooController : ApiController
{
[System.Web.Http.Route("live/topperformers/{dateTime:datetime?}")]
[System.Web.Http.AcceptVerbs("GET", "POST")]
[System.Web.Http.HttpGet]
public List<string> GetTopPerformers(DateTime? dateTime)
{
return new List<string>();
}
}
The question mark in the route is important. If you miss it it will be handled like a required paramter (that's why you get a 404). For more information have a look at Optional URI Parameters and Default Values

How to support both 'regular' and 'readable' urls in Web API attribute rouring?

I have ASP.NET Web API 2.1 project with attribute routing enabled, and a controller action decorated as following:
[Route("api/product/barcode/{barcodeType}/{barcode}")]
public async Task<IHttpActionResult> GetProduct([FromUri] BarcodeSearchCriteria searchCriteria)
where BarcodeSearchCriteria is a complex type:
public class BarcodeSearchCriteria
{
public string Barcode { get; set; }
public string BarcodeType { get; set; }
}
It works well for a 'regular' url like this:
/api/product/barcode/EAN/0747599330971
but how in the same time support an url like this:
/api/product/barcode/?barcodeType=EAN&barcode=0747599330971
I used to use it in my *.webtest before switched to 'readable` mode.
you could have 2 routes in this case:
[Route("api/product/barcode")] //expects values from query string
[Route("api/product/barcode/{barcodeType}/{barcode}")] //expects value from route
public async Task<IHttpActionResult> GetProduct([FromUri] BarcodeSearchCriteria searchCriteria)
It looks like there is no route defined for the regular Url with query string parameters.
Try making the route parameters optional like this.
[Route("api/product/barcode/{barcodeType=""}/{barcode=""}")]
public async Task<IHttpActionResult> GetProduct([FromUri] BarcodeSearchCriteria searchCriteria)
So it should also match the route template api/product/barcode route.
Haven't tested, though hope you got my point.

asp.net webapi: how to pass optional parameters?

I am using the new asp.net web api and would like to pass optional parameters. Is it correct that one needs to populate an attribute so it allows one to pass params using the ? symbol?
Before this was done with with uri templates, I believe.
Does anyone have an example?
I am currently passing the id in the url which arrives in my controller as int. But I need to pass some dates.
You can make a parameter optional by using a nullable type:
public class OptionalParamsController : ApiController
{
// GET /api/optionalparams?id=5&optionalDateTime=2012-05-31
public string Get(int id, DateTime? optionalDateTime)
{
return optionalDateTime.HasValue ? optionalDateTime.Value.ToLongDateString() : "No dateTime provided";
}
}
In addition to the previous answer provided by Ian, which is correct, you can also provide default values which I feel is a cleaner option which avoids having to check whether something was passed or not. Just another option.
public class OptionalParamsController : ApiController
{
// GET /api/optionalparams?id=5&optionalDateTime=2012-05-31
public string Get(int id, DateTime optionalDateTime = DateTime.UtcNow.Date)
{...}
}

Categories