ASP.NET Core: JSON object header parameter - c#

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!

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)

Related

Options Pattern in ASP.NET Core - how to return sub-options as a JSON string (not strongly typed)

The following documentation illustrates how to use the Options Pattern in ASP.NET Core to create a strongly-typed options class to access JSON configuration data.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options
This C# class
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
represents a portion of this JSON configuration file (the first two root-level properties)
{
"option1": "value1_from_json",
"option2": -1,
"subOptions": {
"subOption1": "subvalue1_from_json",
"subOption2": 200
}
}
I want to add another C# property named SubOptions to the MyOptions class that returns the raw data of the subOptions JSON sub-section, without creating a strongly-typed class for that sub-section of the JSON configuration file, but I don't know what data type to use (or if it's even possible to do that).
If I use string, I get a runtime error when service.Configure<MyOptions>(Configuration); is called, saying System.InvalidOperationException: 'Cannot create instance of type 'System.String' because it is missing a public parameterless constructor.
If I use object or dynamic, I get a different runtime error when service.AddSingleton(cfg => cfg.GetService<IOptions<MyOptions>>().Value); is called to register an instance of the MyOptions class, saying System.ArgumentNullException: 'Value cannot be null. Parameter name: type'
If I use JObject, I get {} back when I access the SubOptions property of the MyOptions object that's injected into my API Controller.
I know I can convert the sub-section to a JSON string property by escaping the sub-section data, but I want to avoid treating the sub-section as a string, and instead leave it as raw JSON.
Is it possible to do what I want to do? Is there a data type that works with the Options Pattern that will allow me to access the JSON sub-section without having to create a strongly-typed class?
*For background, I'm trying to create an API Controller method that returns the content of the JSON sub-section to the API client. I want to avoid using a strongly-typed class for the sub-section, so that the JSON configuration file can be edited on the server, adding new properties and values to the sub-section that will be returned to the API client, without having to update the C# code and redeploy the API service. In other words, I want the JSON sub-section to be 'dynamic', and just pull it and send it to the client. *
You can sorta do get raw configuration object by forcing your SubOptions property to be of IConfigurationSection:
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
public IConfigurationSection SubOptions { get; set; } // returns the "raw" section now
public string SubOptions_take2 { get; set; }
}
so you would still bind your strongly typed object in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration);
...
}
but this is where luck appears to run out, because even though it is a whole section - as far as options binder is concerned it's all been deserialised and parsed into hierarchy of values already. There appears to be no easy way to reassemble it back into one string. Injecting IOptionsMonitor allows you to get the values by opting for .GetChildren() but I could not find an obvious way to get the whole hierarchy without writing custom code to just recursively walk it (which I will leave out for you to play with should you feel this is worth the effort):
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions = _options.SubOptions as ConfigurationSection;
var children = subOptions.GetChildren(); // you see, the config has already been parsed into this hierarchy of items - it's too late to get the raw string value
var s = JsonConvert.SerializeObject(children);
// will produce something like this JSON:
//[{"Path":"SubOptions:subOption1","Key":"subOption1","Value":"subvalue1_from_json"},{"Path":"SubOptions:subOption2","Key":"subOption2","Value":"200"}]
}
one way around it will be to actually encode your json as string in the config file:
"subOptions_take2": "{\"subOption1\": \"subvalue1_from_json\",\"subOption2\": 200}"
then you can just grab it later:
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions_string = _options.SubOptions_take2;// this is valid json now: {"subOption1": "subvalue1_from_json","subOption2": 200}
}
I guess, you can use JObject from Newtonsoft.Json package - it's the default JSON parser & serializer in Asp.Net Core

C# Web API POST parameter FromBody is always null

I've been scouring the web for hours and tried many different solutions also described here on StackOverflow. I know similar questions have been asked before, but none of the answers or comments have worked for me.
The problem: I have a .NET Web API that has a Post-method with some parameters.
One of the parameters is a complex object that is supposed to be read from the body (that is JSON). However, this object is always null.
This is my code:
// POST api/worksheets/post_event/true/false
[Route("post_event/{newWorksheet}/{eindEvent}")]
[HttpPost]
public Event Post(bool newWorksheet, bool eindEvent, [FromBody] Event eventData)
{
return eventData;
}
To be clear: eventData is the object that's always null. The boolean values are read correctly.
The full request body is:
POST http://localhost:5000/api/worksheets/post_event/true/false
Content-Type: application/json
{"Persnr":1011875, "WorksheetId":null, "Projectnr":81445, "Uursoort":8678, "Tijd":{"09-08-2016 9:25"}}
And for reference, this is the Event-class:
public class Event
{
public long Persnr { get; set; }
public int WorksheetId { get; set; }
public int Projectnr { get; set; }
public int Uursoort { get; set; }
public DateTime Tijd { get; set; }
}
Some of the things I've already tried:
Change JSON to different formats (only values, "Event": {} surrounding the actual object, an = in front of the JSON).
Test with just the Event parameter (removing the others as well as in the route)
Add a default ctor to Event.
Remove the [FromBody] tag. If I do this, the Event-object is not null, but all the properties are. Properties can be filled through the URI, but that is not the desired behavior.
According to all solutions and documentation I have read, it should simply work the way I have it displayed above.
What am I missing?
Your json object is invalid. My suggestion is to always run json object written manually through a json parser like this: http://json.parser.online.fr/
"Tijd":{"09-08-2016 9:25"}
should instead be
"Tijd":["09-08-2016 9:25"]
This usually happens when your object can't be deserialized from JSON request.
The best practice would be to make sure that all the request properties can accept null values (make value types properties are nullable). And then you can validate that all needed request properties are provided, or return 400 error if not. This way you at least will be able to understand what request property causes the problem.
The right JSON for this type should be just
{"Persnr":1011875, "WorksheetId":null, "Projectnr":81445, "Uursoort":8678, "Tijd":"09-08-2016 9:25"}
No curly braces for Tijd, because Tijd is plain DateTime property that can be inferred from string, representing DateTime

Is it possible to use a dash character in variable name .net Web API?

The get request is from an SMS API delivery report to get the information about the SMS.
One of variable that will be posted to my api is this: ?err-code=0. Is it possible to do it in a .Net Web API solution or should I use another language?
Web API Get Method:
public HttpResponseMessage Get([FromUri]TestModel testingDetials)
{
return Request.CreateResponse(System.Net.HttpStatusCode.OK);
}
Model
public class TestModel
{
public string foo { get; set; }
public string err_code { get;set; }
}
I tried various solution found on this website none of them work like adding [JsonProperty] and [DataMember] to the err_code property.
You can use [JsonProperty(PropertyName = "err-code")] provided the request is being received as JSON. This is because JsonProperty is part of the Newtonsoft JSON serializer library which is what Web API uses to deserialize JSON. If the request is not JSON, the library is not used in the pipeline.
As you mentioned you can use HttpContext. If I remember correctly the model binding in MVC converts '-' to '_' but I could be wrong. Regardless to continue using strongly typed models, which I recommend, is to use model binding. This is basically writing a custom mapping between the http context and the model. You could even expand the usual one and map something like "err-code" to a property called ErrCode automatically by writing a convention based one. Here is an example, scroll a bit: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Happy Coding!
(Through I would provide a complete answer for the sake of...well... having a complete answer)
For my case I created a model binder to convert the var "_" to "-" and setting the value by using reflection. This answer is just for a reference.
Here is the code: (This solution is used for Web API not MVC)
public class SmsReceiptModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(SmsReceiptModel))
{
return false;
}
Type t = typeof(SmsReceiptModel);
var smsDetails = new SmsReceiptModel();
foreach (var prop in t.GetProperties())
{
string propName = prop.Name.Replace('_', '-');
var currVal = bindingContext.ValueProvider.GetValue(
propName);
if (currVal != null)
prop.SetValue(smsDetails, Convert.ChangeType(currVal.RawValue, prop.PropertyType), null);
}
bindingContext.Model = smsDetails;
return true;
}
}

.NET MVC API - dots (period) in get request

I'm trying to setup Facebook Notification API.
I have an APi Controller with RealtimeUpdate() - Get, will be used just for verification of endpoint.
As is written in Fb Docs:
Firstly, Facebook servers will make a single HTTP GET to your callback
URL when you try to add or modify a subscription. A query string will
be appended to your callback URL with the following parameters:
hub.mode - The string "subscribe" is passed in this parameter
hub.challenge - A random string
hub.verify_token - The verify_token value you specified when you created the subscription
From here I have a problem - I have no idea how to handle this dots in query params names. I google a lot, and did not find the solution.
Can somebody please say to me how to get data from this hub.* values?
Thank you!
Update your method signature using the FromUri attributes, like this:
public string Get(
[FromUri(Name="hub.mode")]string mode,
[FromUri(Name="hub.challenge")]string challenge,
[FromUri(Name="hub.verify_token")]string verifyToken
)
{
/* method body */
}
The parameters will be bound from the query string using the specified names.
Slightly different form Steve's answer.
In case you need to have a normal controller instead of an Api one (if you are inheriting from Controller rather tha ApiController), the follow worked for me:
namespace Name
{
public class Hub
{
public string Mode { get; set; }
public string Challenge { get; set; }
// ReSharper disable once InconsistentNaming
public string Verify_Token { get; set; }
}
public class FacebookWebHooksController : Controller
{
[System.Web.Http.HttpGet, ActionName("Callback")]
[AllowAnonymous]
public ContentResult CallbackGet(Hub hub)
{
if (hub.Mode == "subscribe" && hub.Verify_Token == "YOUR_TOKEN")
return Content(hub.Challenge, "text/plain", Encoding.UTF8);
return Content(string.Empty, "text/plain", Encoding.UTF8);
}
}
[HttpPost]
[AllowAnonymous]
public ActionResult Callback()
{
Request.InputStream.Seek(0, SeekOrigin.Begin);
var jsonData = new StreamReader(Request.InputStream).ReadToEnd();
}
}
The Model Binder has some illegal characters, of which I believe '.' is a special character, used primarily to bind complex objects. When all else fails, you can look at Request.QueryString and Request.Form directly, just like in ASP.NET WebForms.
You can also try using a complex object that has a Property named hub with subproperties mode, challenge, and verify_token. This might just do the trick.

pass array of an object to webapi

I have a .net mvc 4 webapi project that I'm trying to pass an array of an object to a method on my controller.
I've found some examples here on SO that talk about needing to set my object's properties with: param1=whatever&param2=bling&param3=blah.
But I don't see how I can pass in a collection using that.
Here is my method signature. Notice I've decorated the argument with the [FromUri] attribute.
public List<PhoneResult> GetPhoneNumbersByNumbers([FromUri] PhoneRequest[] id)
{
List<PhoneResult> prs = new List<PhoneResult>();
foreach (PhoneRequest pr in id)
{
prs.Add(PhoneNumberBL.GetSinglePhoneResult(pr.PhoneNumber, pr.RfiDate, pr.FinDate, pr.State));
}
return prs;
}
here is my simple PhoneRequest object:
public class PhoneRequest
{
public string PhoneNumber { get; set; }
public string RfiDate { get; set; }
public string FinDate { get; set; }
public string State { get; set; }
}
and here's a sample of what I'm using to pass in:
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers/
[{"PhoneNumber":"8016667777","RfiDate":"","FinDate":"2012-02-11","State":"UT"},
{"PhoneNumber":"8018889999","RfiDate":"2012-12-01","FinDate":"","State":"UT"}]
using this comes back with "bad request"
I also tried this
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers?
id=[{"PhoneNumber":"8016667777","RfiDate":"","FinDate":"2012-02-11","State":"UT"},
{"PhoneNumber":"8018889999","RfiDate":"2012-12-01","FinDate":"","State":"UT"}]
which does reach the method, but the array is null.
how can I pass in an array of my PhoneRequest object to my Web API method?
Try passing the PhoneRequest[] from the uri in this format:
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers?
id[0][PhoneNumber]=8016667777&id[0][FinDate]=2012-02-11&id[0][State]=UT&
id[1][PhoneNumber]=8018889999&id[1][RfiDate]=2012-12-01&id[1][State]=UT
I suggest you use POST for this.
As you query string grows, you will run into problems with the maximum length of the URL, which is browser dependent.
If you have a lot of parameters to pass, a POST is perfectly acceptable even if you are really only GETting data. What you will lose, however, is the ability for the user to bookmark a particular page with the query string.
I created a custom model binder, the FieldValueModelBinder class, which can effectively pass any object containing nested array or generic list types of data with query strings having field-name pairs without imbedding any JSON and XML structures. The model binder can resolve all issues discussed above. Since this question was extended by the question ID 19302078, you can see details of my answer in that thread.

Categories