I have net6.0
project with minimal api and I would like to use NetwtonsoftJson
instead of built in System.Text.Json
library for serialization and deserialization.
At the moment I have this configurations for JsonOptions
and that works as expected
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});
If I try to change to something equivalent that uses Newtonsoft.Json.JsonSerializerSettings
like below I am not getting same behavior. Instead it looks like it uses default System.Text.Json
configuration.
builder.Services.Configure<JsonSerializerSettings>(options =>
{
options.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.Converters.Add(
new StringEnumConverter
{
NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy()
});
});
In net5.0
I know I could use this
services.AddControllers().AddNewtonsoftJson((options) => //options); // OR
services.AddMvc().AddNewtonsoftJson((options) => //options);
However, if I use it like above in my net6.0
project then I am not using anymore MinimalApi ?
CodePudding user response:
From my understanding Minimal APIs rely on some conventions regarding type binding. From what I can see they search for method with next signature - ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter)
on the type otherwise will try to use httpContext.Request.ReadFromJsonAsync
which internally uses System.Text.Json
and that can't be changed, so services.Add...().AddNewtonsoftJson((options) => //options);
approach will not work.
To use Newtonsoft.Json
you can try next (other than directly handling request via app.MapPost("/pst", (HttpContext c) => c.Request...)
):
If you have control over all your classes which needs to be deserialized using it you can inherit them all from some generic base class which will have the method with needed signature (also you can use interface with implemented static method):
public class BaseModel<TModel>
{
public static async ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasJsonContentType())
{
throw new BadHttpRequestException(
"Request content type was not a recognized JSON content type.",
StatusCodes.Status415UnsupportedMediaType);
}
using var sr = new StreamReader(context.Request.Body);
var str = await sr.ReadToEndAsync();
return JsonConvert.DeserializeObject<TModel>(str);
}
}
And usage:
class PostParams : BaseModel<PostParams>
{
[JsonProperty("prop")]
public int MyProperty { get; set; }
}
// accepts json body {"prop": 2}
app.MapPost("/pst", (PostParams po) => po.MyProperty);
Note that BaseModel<TModel>
implemenation in this example is quite naive and possibly can be improved (check out HttpRequestJsonExtensions.ReadFromJsonAsync
at least).
If you don't have control over the models or don't want to inherit them from some base you can look into creating wrappers:
public class Wrapper<TModel>
{
public Wrapper(TModel? value)
{
Value = value;
}
public TModel? Value { get; }
public static async ValueTask<Wrapper<TModel>?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasJsonContentType())
{
throw new BadHttpRequestException(
"Request content type was not a recognized JSON content type.",
StatusCodes.Status415UnsupportedMediaType);
}
using var sr = new StreamReader(context.Request.Body);
var str = await sr.ReadToEndAsync();
return new Wrapper<TModel>(JsonConvert.DeserializeObject<TModel>(str));
}
}
And usage changes to:
class PostParams
{
[JsonProperty("prop")]
public int MyProperty { get; set; }
}
// accepts json body {"prop": 2}
app.MapPost("/pst", (Wrapper<PostParams> po) => po.Value.MyProperty);
Some extra useful links:
- MVC model binders - by David Fowler. Though I was not able to make it work for
services.AddControllers().AddNewtonsoftJson((options) => //options);
- ParameterBinder - similar approach by Damian Edwards