I use ASP.NET Core minimal APIs and I try to have my view-models (where incoming client data gets mapped to) use private setters for their properties.
But I see that the binding stops working once I make a property have a private setter. Is there any quick fix for this? I would expect this is to be a common requirement and have an easy fix, but I can't find anything online.
Plus, I don't think I am doing anything special :
public void AddRoutes(IEndpointRouteBuilder app)
{
app
.MapPost("api/document/getById", async ([FromBody] GetDocumentRequest request, [FromServices] ISender sender) =>
{
return await sender.Send(request.Query);
})
.WithName("GetDocumentById")
.Produces<DocumentViewModel>(StatusCodes.Status200OK);
}
Here, GetDocumentRequest
is my view-model.
Since binding here would just deserialize JSON you can mark corresponding properties with JsonIncludeAttribute
:
public class GetDocumentRequest
{
[JsonInclude]
public int Type { get; private set; }
}
See Non-public members and property accessors section of the docs.
I was hoping for a more global solution, since I plan on having all my properties, for all endpoint view-models, have private setters. And they are too many of them...
Alternatively you can look into customizing JSON contract. Something to get you started:
builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolver =
new DefaultJsonTypeInfoResolver
{
Modifiers = { SetAllSetters }
});
void SetAllSetters(JsonTypeInfo typeInfo)
{
// find types which need special management
if (typeInfo.Type.Assembly == typeof(GetDocumentRequest).Assembly)
{
var propertyInfos = typeInfo.Type.GetProperties()
.ToDictionary(pi => pi.Name, StringComparer.InvariantCultureIgnoreCase);
// todo: check if overriding set method is needed
foreach (var info in typeInfo.Properties)
{
var methodInfo = propertyInfos[info.Name].GetSetMethod(true);
info.Set = (s, v) => methodInfo.Invoke(s, [v]);
}
}
}
Note that this implementation highly likely is less effective compared to the default one. Also I recommend to go through the Use immutable types and properties and consider options provided there (like using constructors or init-only members or records)