Use constructor to set properties in swagger

Viewed 1909

I am building an API and using swagger to test the endpoints. I have a ProductDTO:

public string ProductName { get; set; }
.
.
.
public Price Price { get; set; }

in this DTO I want to use Price class which is used throughout my code. Price class looks like this:

public class Price
{
    public Price(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public decimal Amount { get; private set; }
    public string Currency { get; private set; }
}

But since private setters are used in Price class I cannot set these values using swagger (it has readonly attribute on those). I really like this approach of having private setters and setting values with constructor, which by the way is public. Is there any way I could set values for Price class using swagger and still use private setters on properties?

2 Answers

Update: My original answer (see further below) was that this is not possible, however, this really depends on the serialization library used in your project.

For example Newtonsoft's Json.NET allows you to set some of your class' properties via constructor (note: in case your class comes with more than one constructor, apply the JsonConstructorAttribute):

public class Price
{
    [JsonConstructor]
    public Price(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public decimal Amount { get; private set; }
    public string Currency { get; private set; }
}

Original answer

No, this is not possible. Because if you keep your properties private and just initialize them via constructor your deserializer would not know how these properties should be mapped to your backend (DTO) model.

Therefore, when working with DTOs, you usually don't see people initializing via constructor.

Also, since you are using your Price class throughout your code, you are mixing your "domain model" with your "view model" (= the model you use to communicate with the client) – which is a pragmatic approach but not advocated in styles like DDD. In such a case, if you want different properties or different access modifiers on your properties, you should create a dedicated PriceDTO that maps to your Price entity but has public setters and getters.

Another option would be to separate the model used by the endpoint that sends data to the client (via GET) from the model that is used by the endpoint that receives data as payload to create or update things (via POST/PATCH/PUT). However, this often comes at the cost of redundancy as the models in both cases are often highly similar.

Adding this Schema filter worked for me.

public class AddReadOnlyPropertiesFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (schema?.Properties?.Count > 0)
            schema.Properties.Values.SingleOrDefault(v => v.ReadOnly = false);
    }
}
Related