ASP.NET Core 6 - Use URI's extension to get Accept header value - rest

I'm migrating an application from NancyFx to Kestrel in ASP.NET Core 6.
In Nancy, you could specify the Accept value in the URI. For example, these Uris:
http://localhost:5000/my/resource.json
http://localhost:5000/my/resource.protobuf
http://localhost:5000/my/resource.xml
Would be the equivalent of setting the Accepts header to application/json, application/protobuf or application/xml respectively.
Does this exist in Kestrel? I remember finding one example, long ago, of regex-ing the route and doing it somewhat manually. But
I can't find that post again, and
If I have to do that, I'm not sure I want to :)
Is there a way to configure this behavior in ASP.NET Core 6?
The object returned from my handler in the controller is already capable of being serialized to json/xml/whatever. I just need to check the URI to set the content-type of the response so the correct formatter will be invoked.
At the moment, I have a client that will speak to both Nancy and Kestrel and it was written to use the URI to get the type. I'm fine to rewrite/update the client so it will use the Accept header. But getting the URI method to work will make the initial integration easier and a refactor to use the headers can come next.

I created a very simple middleware that reads the accept value from the query string and sets the Accept header to the request:
public class AcceptHeaderFromQueryString
{
private readonly RequestDelegate _next;
public AcceptHeaderFromQueryString(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var accept = context.Request.Query["accept"];
if (!string.IsNullOrEmpty(accept))
{
context.Request.Headers.Accept = accept;
}
await _next(context);
}
}
Register the middleware:
app.UseMiddleware<AcceptHeaderFromQueryString>();
I added [Produces(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)] attribute to my api controller action (this step is not required):
[HttpGet]
[Produces(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
Finally I added support for xml serialization in Program.cs:
builder.Services.AddControllers()
.AddXmlDataContractSerializerFormatters();
Then I tried these urls and they both gave appropriate response:
https://localhost:7258/weatherforecast?accept=application/json
https://localhost:7258/weatherforecast?accept=application/xml

You possibly want the [Consumes] attribute. This allows you to specify a controller action that only gets called from a route of the specified content type.
Obviously this is not using the Accepts header but the content type of the request.

Related

Using AcceptedAtActionResult with Azure Function to direct to specific method

I have a API that accepts a submission request (that when accepted to return http code 202 Accepted), for some workflow to approve, and then the caller to retrieve the result later.
The URL should be returned on successful submission and I am trying to use AcceptedAtActionResult with Azure Functions, however I do not understand how to use it as they don't have Controllers per my understanding, but this is one of the parameters.
Regardless and trying to use string.Empty, or the class name EnrollmentFunctions, or path enroll, the library is generating strange paths (in this case pointing to a different function in a different file i.e. location: http://localhost:7071/api/attest/c588484e-8e57-47f6-bf6c-973cfa5b9214?action=GetStatus&controller=enroll).
I am looking to return of the form location: http://localhost:7071/api/enroll/c588484e-8e57-47f6-bf6c-973cfa5b9214 (but avoid hardcoding URLs, both for maintenance and also running in dev mode on local machine).
I believe this would get more complicated if we used a cloud WAF (like cloudflare) on the front-end, in that case I presume would create my own class to build the URL (i.e. https://api-protected-by-waf.mydomain.com/enroll/bf920104-a630-4b58-97cb-cc3ae45c31d3)? I can see this then becoming a configuration issue was looking to avoid on the previous para.
Research points to creating my own IUrlHelper and possibly specifying key:values for retrival via Environment.GetEnvironmentVariable("your_key_here") (for use by my IUrlHelper implementation).
Thanks in advance on how to solve this issue or if i am using the libraries incorrectly and alternate best practice. Code of the Azure functions are below:
public class EnrollmentFunctions
{
[FunctionName("Enroll")]
public async Task<IActionResult> Enroll(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "enroll")] HttpRequest req)
{
var id = Guid.NewGuid();
return new AcceptedAtActionResult("GetStatus", "enroll", new { id = id }, id);
// return new AcceptedAtRouteResult("GetStatus", (new { id }, new { Result = id.ToString() }));
}
[FunctionName("GetStatus")]
public async Task<IActionResult> GetStatus(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "enroll/{id}")] HttpRequest req)
{
return new OkObjectResult("OK");
}
}

Spring Boot REST API Endpoint Mapping best practice

I am using below endPoint URL Mapping with HTTP Methods like ( POST, DELETE, GET, PUT)
POST for Create a new Trade -
#PostMapping("/trade")
DELETE for Delete a Trade with specific id -
#DeleteMapping("/trade/{id}")
GET for Get details of specific Trade -
#GetMapping("/trade/{id}")
PUT for Update Trade details -
#PutMapping(“/trade/{id}”)
GET for Retrieve all Trade list of the collection -
#GetMapping("/trades")
Spring currently supports five types of inbuilt annotations for handling different types of incoming HTTP request methods which are GET, POST, PUT, DELETE and PATCH. These annotations are:
#GetMapping
#PostMapping
#PutMapping
#DeleteMapping
#PatchMapping
From the naming convention, we can see that each annotation is meant to handle the respective incoming request method types, i.e. #GetMapping is used to handle GET type of request method, #PostMapping is used to handle POST type of request method, etc.
if I am missing anything here Please suggest
Add API version like
#RestController
#RequestMapping("/API/V1")
public class TestController {
#RequestMapping("/greeting")
public String greeting( {
return "welcome";
}
}
For versioning there are several approaches you can use:
URI path:
Include the version number in the URL path of the endpoint.
For example v1 in /api/v1/trade:
public class TradeController {
#GetMapping("v1/trade")
public Trade tradeVersionOne() {
return new Trade("123","Trade Result");
}
#GetMapping("v2/trade")
public Trade tradeVersionTwo() {
return new Trade(new RealTimeTrade("123", "Real Time Trade Result"));
}
}
Query parameters:
Pass the version number as a query parameter with a specified name.
For example: ?version=1 in /api/trade?version=1:
public class TradeController {
#GetMapping(name = "v1/trade", params = "version=1")
public Trade tradeVersionOne() {
return new Trade("123","Trade Result");
}
#GetMapping(name = "v2/trade", params = "version=2")
public Trade tradeVersionTwo() {
return new Trade(new RealTimeTrade("123", "Real Time Trade Result"));
}
}
Custom HTTP headers:
Define a new header that contains the version number in the request.
Content negotiation:
Include the version number in the Accept header along with the accepted content type.

Jaxrs QueryParam as a sub or a root item

Hi the design document I'm working off of wants me to have a URL in the pattern of
<root>/v1/installs/XYZ123/actions/next?app=1234ABCD HTTP/1.1
However the only examples I can find are extremely simple, and only show URLs that would end at the /next.
http://www.mkyong.com/webservices/jax-rs/jax-rs-queryparam-example/
I'm thinking it's something like... ? I'm missing a key step
#GET
#Produces({ "application/json" })
#Path("v1/installs/{id}/actions<SOMETHINGHERE?>/next HTTP/1.1")
public Response getSetupCommands(#PathParam("id")
String id,#QueryParam("next") String next) {
I'm using jboss and jaxrs
Why do I get the feeling that HTTP/1.1 should not be a part of the URL. You may have read/understood the design document incorrectly. If it was saying that the request should look like
GET /v1/installs/XYZ123/actions/next?app=1234ABCD HTTP/1.1
Then you only need to be worried about /v1/installs/XYZ123/actions/next?app=1234ABCD. HTTP/1.1 is simply the HTTP version that will be used implicitly with every request and response.
Your original example was fine, exception you should have replaced #QueryParam("next") with #QueryParam("app"). next is actually part of the path.
This /v1/installs/{id}/actions/next should be what's included in #Path.
The complete semantics of this request URL seems to read something like:
Get the next (resource) controller, and we will use the app query parameter as an argument to pass to this controller.
UPDATE: with example
#Path("/v1")
public class QueryResource {
#GET
#Path("/installs/{id}/actions/next")
public Response getResponse(#PathParam("id") String id,
#QueryParam("app") String app) {
StringBuilder sb = new StringBuilder();
sb.append("ID: ").append(id).append("<br/>");
sb.append("app param: ").append(app);
return Response.ok(sb.toString()).build();
}
}
Browser Test
Fire bug
v1/installs/{id}/actions/{next : .+}
public Response getSetupCommands(#PathParam("id") String id,#PathParam("next") String next) {
Way too hackish for my tastes, but I don't have any control in this situation..

Play Framework redirect not working in Safari

I'm attempting to query Solr from Angular and routing the request through a Play Controller for security and using Play redirect to forward the request to Solr.
This seems to be working on Chrome but not on Safari/Firefox.
Angular ajax request
var solrUrl = '/solr';
storesFactory.getAdvancedMessages = function (searchCriteria, searchType) {
var filterQuery = solrQueryComposer(searchCriteria);
$log.warn(filterQuery);
return $http({
method: 'GET',
url: solrUrl,
params: { 'q': '*',
'fq': filterQuery,
'rows': 30,
'wt': 'json'}
}).
then(function(data, status, headers, config) {
$log.debug(data.data.response.docs);
return data.data.response.docs;
},
function(error){
$log.error(error.message);
});
Play Controller
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Security;
#Security.Authenticated(Secured.class)
public class SolrController extends Controller {
private static String solrUrl = "http://whatever.com:5185/solr/select/";
private static String queryPart = "";
public static Result forward(){
queryPart = request().uri().substring(5);
System.out.println(queryPart);
return seeOther(solrUrl+queryPart);
}
}
Play Route
GET /solr controllers.SolrController.forward()
First of all, I'd like to clarify what you're doing.
Play is not forwarding anything here, it's sending a redirect to the client, asking to fetch another URL. The client will send a request, receive a redirect, and send another request.
Which means:
this controller is not "forwarding" anything. It's just tells the client to go somewhere else. ("seeOther", the name speaks for itself).
It's not secure at all. Anyone knowing solr's URL could just query it directly.
since the query is performed by the client, it may be stopped by the cross-domain security policy.
Moreover, There's a HUGE race condition waiting to happen in your code. solrUrl and queryPart are static, therefore shared by all threads, therefore shared by all clients!!
There's absolutely no reason for queryPart to be static, and actually, there's absolutely no reason for it to be in this scope. This variable should be defined in the method body.
I'd also like to point out that request().uri().substring(5) is very brittle and is going to break if you change the URL in the route file.
In return seeOther(solrUrl+queryPart), queryPart arguments keys and values should also be URLencoded.

ASP.NET Web API REST Querystring - How does a client know available parameters and options?

When exposing querystring parameters using GET I have the following base URL:
https://school.service.com/api/students
This will return the first 25 students.
What if I want to return a list of students based on ONE of the following criteria:
* have accepted a job
* have received a job offer
* have no job offers
The three above choices are essentially an enum.
Therefore, the query request for students who have no job offers I assume would look like:
https://school.service.com/api/students?jobOfferStatus=3
However, I'm wondering if jobOfferStatus=3 is the proper way to handle this. If so, how would I publish/provide to the clients a list of available options for that jobOfferStatus query parameter? What about other possible query parameters and their valid options? We'll have many possible query parameters like this.
I'd love to see an example of how this should be done properly. What are the best practices?
There are two main options: documenting it, or making it discoverable. A lot of APIs have documentation where they list all of the resources and parameters for reference. Otherwise, the client won't know.
You could also make it discoverable in some way by including the options in a response. For conventions on this, search for HATEOAS if you haven't already. (I'm not really knowledgeable enough about HATEOAS myself to make a suggestion.)
I will mention that "3" is not a very meaningful value for jobOfferStatus, and there's no need for the client to know that number. You can make it anything you want -- jobOfferStatus=none or even jobOffer=none. Your controller can do the work of matching that value to your enumeration. Try to design your interface to be intuitive for developers (and, of course, write good documentation).
To handle multiple query parameters, you can use optional parameters in your function:
public HttpResponseMessage GetStudents(string jobOffer = "",
string other1 = "",
string other2 = "")
{
if (jobOffer == "accepted" && other2 == "whatever") {
// return a response
}
else {
// return a different response
}
}
When the client uses parameters by those names, you can tailor your response appropriately.
You have some options to do this, let's try to help:
1) Configure a generic route to asp.net web api knows how to solve another action's name different from Get to a get method, on the App_Start\WebConfigApi.cs class, try to add this:
config.Routes.MapHttpRoute("DefaultApiWithActionAndId",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Using it, you can have diferent methods on the api controller:
// request: get
// url: api/Students/GetStudents
public HttpResponseMessage GetStudents()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsWithJobOffer
public HttpResponseMessage GetStudentsWithJobOffer()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsAcceptedJob
public HttpResponseMessage GetStudentsAcceptedJob()
{
return Request.CreateResponse(...);
}
2) Use a simple parameter on the Get method:
// request: get
// url: api/Students?jobOfferStatus=1
public HttpResponseMessage GetStudents(int jobOfferStatus)
{
// use jobOfferStatus parameter to fill some list
return Request.CreateResponse(...);
}
3) Use a simple method with a parameter named id, to get a default friendly url by asp.net mvc web api.
// request: get
// url: api/Students/1
public HttpResponseMessage GetStudents(int id)
{
// use the id parameter to fill some list
return Request.CreateResponse(...);
}