I have a little problem with passing Dictionary as part of the parent object as a query parameter.
So, I have a method that gets a FormulationParameters variable.
public async ValueTask<IActionResult> Get([FromQuery] FormulationParameters formulationParameters)
{
return Ok(await _service.Get(formulationParameters));
}
The FormulationParameters class looks like this:
public class FormulationParameters
{
//other simple properties, e.g. string, int
public Dictionary<string, string> Substitute { get; set; }
}
When I was using Postman to pass these parameters I filled its form "Query Params" like this and everything was okay:
- I passed a Key as Substitute[FirstValue] and its Value as 10
And in the Substitute dictionary I got a record where the Key is equal to "FirstValue" and its Value is 10.
But when I tried to write some tests for my API via Refit I found out that this didn't work as I expected.
So, I have a method in the Refit interface:
[Get("/api/path")]
Task<HttpResponseMessage> GetFormulations([Query] FormulationParameters parameters]
And I fill all properties of FormulationParameters in code. When I Debug my tests I see that in the API method (which is defined above) all properties of the formulationParameters variable are filled except this Substitute dictionary.
I was looking for this problem on the Internet and found some solutions. I tried them all but it didn't work for me.
First of all, I tried to use [JsonDataExtension] attribute for this property. As I read it means that if you put this attribute for some property all parameters from Request with no matching class member will be written into the specified collection. So, I changed Dictionary<string, string> to Dictionary<string, object>. Also, I changed how data is sent via the Refit method: I moved Substitute from the main class and sent it as a separate parameter, so the query string looked like this:
?Codes=Code1,Code2&Type=Type&Language=en&FirstValue=1&SecondValue=10
But, no effect, Substitute still was not filled.
Also, I tried to write a custom converter for this property, but no effect, it even didn't invoke when the request came.
The one thing that worked was an action filter. I had found this solution in the StackOverflow post where the same problem was discussed. A method from this filter looks like this:
public override void OnActionExecuting(ActionExecutingContext context)
{
var notMatchedProperties = new Dictionary<string, string>();
// because in ActionArguments there is only one variable - formulationParameters
var mainVariableKey = context.ActionArguments.First().Key;
// the content of FormulationParameters class
var innerParts = context.ActionArguments.First().Value as FormulationParameters;
// conversion from class to dictionary
// the names of the properties are the keys, and their values are values in the dictionary
var classAsDictionary = ClassToDictionaryConverter.Convert(innerParts);
var keys = classAsDictionary.Keys;
foreach (var kvp in context.HttpContext.Request.Query)
{
if (!keys.Contains(kvp.Key, StringComparer.InvariantCultureIgnoreCase))
{
notMatchedProperties.Add(kvp.Key, kvp.Value);
}
}
// fill this dictionary with no matched properties from input
innerParts!.Substitute = notMatchedProperties;
// rewrite the existing value with the new value
context.ActionArguments[mainVariableKey] = innerParts;
base.OnActionExecuting(context);
}
It is not good either, because it does not work properly when I try to pass some parameters via Postman. For example: Codes[0]=1&Codes[1]=2, but it works fine when I write like this Codes=1&Codes=2 - it's mapped into Codes : {1, 2} and it is considered like one key Codes. Also, it is not universal, and it looks like a sort of a workaround.
I don't know if it's okay to use this action filter, or there is another solution that I haven't tried yet (but I don't know about it). And if it's okay if I will use this filter, and my users and I always need to remember how to pass these parameters properly. So, I'm looking for a more universal way to parse these parameters when requests come from different sources (like Refit, Swagger, Postman).
P.S. I am using .NET 5, and my application is Web API type