Reputation: 398
My API sends requests to another system to get data. I am trying to give more control to the consumer of my API over the requests sent to the uderlying system, in the sense of filtering and specifying characteristics of the request.
I am hoping to achieve this by allowing the user to supply a specified JSON object in a header parameter.
Not all actions require this however, so I am using an attribute and an operationfilter to add the parameter where necessary.
The attribute is defined as below:
[AttributeUsage(AttributeTargets.Method)]
public class DmsFilter : Attribute
{
// Nothing here
}
The subsequent OperationFilter applied is as below:
public class DmsFilterOperationFilter : IOperationFilter
{
/// <inheritdoc />
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if(context.MethodInfo.GetCustomAttribute(typeof(DmsFilter), true) is null) return;
operation.Parameters ??= new List<OpenApiParameter>();
OpenApiSchema dmsFilterScheme =
context.SchemaGenerator.GenerateSchema(typeof(DmsRequestModel), context.SchemaRepository);
operation.Parameters.Add(new OpenApiParameter
{
Name = "dmsFilter",
Description = "Configure requests to Indicium here.",
In = ParameterLocation.Header,
Required = false,
Schema = dmsFilterScheme
});
}
private OpenApiSchema CreateDmsFilterScheme()
{
PropertyInfo[] propertyList = typeof(DmsRequestModel).GetProperties();
OpenApiSchema schema = new()
{
Type = nameof(DmsRequestModel),
Default = new OpenApiObject()
};
foreach (PropertyInfo propertyInfo in propertyList)
{
bool isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
if (schema.Properties.ContainsKey(propertyInfo.Name)) continue;
schema.Properties.Add(propertyInfo.Name, new OpenApiSchema
{
Type = propertyInfo.PropertyType.Name,
Default = isNullable ? null : new OpenApiString(string.Empty)
});
if (!isNullable)
schema.Required.Add(propertyInfo.Name);
}
return schema;
}
}
The object which I want to pass as JSON in a header, DmsRequestModel
, is as below:
public class DmsRequestModel
{
public string? Filter { get; set; }
public string? Select { get; set; }
public string? Prefilter { get; set; }
public string? OrderBy { get; set; }
public bool? IncludeFiles { get; set; }
public bool? IncludeHashCodes { get; set; }
}
I have tried using both fthe built-in SchemaGenerator to generate the object, as well as the self-defined CreateDmsFilterScheme
method in the OperationFilter. The resulting parameter from the SchemeGenerator appears to come closest to my needs so, to avoid confusion, the remainder of this question will be about this method.
In SwaggerUI, the resulting parameter looks as such:
On the surface, this appears to fit my needs exactly, as it looks like a valid JSON object.
However, when making an actual request, in the cURL output the entire object is seemingly supplied as a comma-separated string:
curl -X GET "http://localhost:5001/modules/dms/WorkOrders/all?api-version=1.0" -H "accept: text/plain" -H "basicAuthHeader: Basic <basic auth token>" -H "dmsFilter: prefilter,authorized,my_workshop,work_order_status,hide_work_order_status_cancelled,orderBy,planned_starting_date_time asc,includeFiles,false,includeHashCodes,true" -H "Authorization: Bearer <bearer token>"
I am not sure why this is happening, but I cannot seem to get the object into the header as a JSON string. I am not very experienced or familiar with HTTP technicalities, however I imagine supplying a JSON string in a header would not or should not be impossible. I have built APIs before but never with this level of complexity.
Am I doing this wrong, or is there some extra step somewhere which I am missing? I am not sure if this is the right way to go about this, but it felt like the most straight-forward and obvious way. Any suggestions or possible alternative ways of achieving this are welcome!
Please feel free to comment should anything be unclear.
Thank you in advance!
Upvotes: 0
Views: 1318
Reputation: 692
I don't think your current approach is the right one. As far as I'm aware, the header is not meant for changing what you return, but rather providing context to the request and the form it is returned in. E.g. JSON/XML and the way caching behaves. But no matter what you provide in the headers, the data itself should remain the same (that's the way I use headers, but if this is wrong please correct me).
The comma seperation is also quite logical: headers aren't exactly meant to have complex objects in them such as json.
A more logical approach would be to use the query string if you can. From a usage point this is also more logical as querystrings are generally used to change what a specific request returns. It also makes working with your API much easier. If you're having this much difficulty making what should be a simple get request, someone else likely will as well.
Alternatively, if the objects are really complex I would use a POST request instead, as this does allow you to send a much more expansive body.
On a sidenote: it is possible to send a body with a GET request, but especially in your usecase this would not be recommended as it could severely impact caching among other things. (referencee)
Upvotes: 1