简体   繁体   中英

Default value data annotation for empty array

I'm creating a .NET Core Web API and want to call an endpoint submitting a customer order. The customer id comes as a route parameter. In the request body it's possible to send an array of objects. Each object contains the product id and its amount. But this field is optional, empty orders are possible too (products can be added later on).

So I started with this DTO

public class CreateCustomerOrderByIdDto
{
    [FromRoute]
    public uint Id { get; set; }

    [FromBody]
    public OrderPosition[] OrderPositions { get; set; }
}

public class OrderPosition
{
    [Range(1, uint.MaxValue)]
    public uint ProductId { get; set; }

    [Range(1, uint.MaxValue)]
    public uint Amount { get; set; }
}

This request DTO should make the OrderPositions field optional but when adding an item both properties are required for that item. I want to set a default value for OrderPositions if missing so I thought this data annotation would do it

[DefaultValue(new OrderPosition[0])]

Unfortunately I get this error message

An attribute argument must be a constant expression, 'typeof()' expression or array creation expression of an attribute parameter type

So how do you mark that field as optional and set a default value?

When passing no order positions the array will be transformed to an empty one so I can avoid null checks and work with loops that just never run

Maybe you can use List<OrderPosition> instead of array. Then initialize it to empty list at constructor

public class CreateCustomerOrderByIdDto
{
    public CreateCustomerOrderByIdDto()
    {
        this.OrderPositions = new List<OrderPosition>();
    }
    [FromRoute]
    public uint Id { get; set; }

    [FromBody]
    public List<OrderPosition> OrderPositions { get; set; }
}

public class OrderPosition
{
    [Range(1, uint.MaxValue)]
    public uint ProductId { get; set; }

    [Range(1, uint.MaxValue)]
    public uint Amount { get; set; }
}

Similar to hphp's answer, you can set a default value like you would with any c# class:

public class CreateCustomerOrderByIdDto
{
    [FromRoute]
    public uint Id { get; set; }

    [FromBody]
    public OrderPosition[] OrderPositions { get; set; } = new OrderPosition[0]; // auto-initialize it here. 

    // alternatively you can use a constructor, but I prefer setting the property like above
    public CreateCustomerOrderByIdDto
    {
        OrderPositions = new OrderPosition[0];
    }
}

public class OrderPosition
{
    [Range(1, uint.MaxValue)]
    public uint ProductId { get; set; }

    [Range(1, uint.MaxValue)]
    public uint Amount { get; set; }
}

If you don't like those ideas and you are using Newtonsoft.Json, you can also take advantage serialization events, and perform the default value setters on those as well: https://www.newtonsoft.com/json/help/html/SerializationCallbacks.htm

The problem with arrays is that it has a fixed length. You can resize the array using Array.Resize() but that too needs a fixed length.

A better way is to use List instead of an array:

public class CreateCustomerOrderByIdDto
{
    [FromRoute]
    public uint Id { get; set; }

    [FromBody]
    public List<OrderPosition> OrderPositions { get; set; }
}

By default, the list object OrderPositions will have null value; which is a good way to represent no items present in the list.

If you still need the list to be empty by default, instead of null, you can set the default value as below:

public List<OrderPosition> OrderPositions { get; set; } = new List<OrderPosition>();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM