0

I wrote an endpoint which accepts MediaParams object which extends another param class and together they act like a wrapper for parameters and look like this:

public class MediaParams : PaginationParams
    {
        public string OrderBy { get; set; } = "title";
        public string SearchTerm { get; set; } = string.Empty;
        public string Genres { get; set; } = string.Empty;
    }
    public class PaginationParams
    {
        private const int MaxPageSize = 50;
        public int PageNumber { get; set; } = 1;
        private int _pageSize = 6;

        public int PageSize
        {
            get => _pageSize;
            set => _pageSize = value > MaxPageSize ? MaxPageSize : value;
        }
    }

As you may notice I also assigned a default values for each property as I want to make them optional, my endpoint

app.MapGet("/media/getby", async (
    [AsParameters] MediaParams pa,
    IMediator mediator,
    HttpContext context) =>
{
    var mediaListDto = await mediator.Send(new GetMediaByParamsQuery(pa));

    if (mediaListDto is not { Count: > 0 })
    {
        return Results.NotFound("No media was found by these params.");
    }
    context.Response.Headers.Add("Pagination", JsonSerializer.Serialize(mediaListDto.MetaData));
    return Results.Ok(mediaListDto);
});

Now it works ok, but only if I specify every single property in my uri like this:

serveraddress/media/getby?OrderBy=rating&SearchTerm=pal&Genres=Drama&PageNumber=1&PageSize=10

Could someone help me to rewrite the logic so I could use these params individually to override default values, or not use at all to keep default values? Thanks.

Ruby Rain
  • 105
  • 6
  • Have you tried using `[FromQuery]` attribute instead of `[AsParameters]`? – Rafal Kozlowski Aug 24 '23 at 13:17
  • @RafalKozlowski ofc, It throws "An unhandled exception was thrown by the application. System.InvalidOperationException: No public static bool MediaParams.TryParse(string, out MediaParams) method found for pa." – Ruby Rain Aug 24 '23 at 13:23

2 Answers2

1

It seems that AsParameter by default requires all properties and when it deserialises it skips the default values because it uses FormatterServices.GetUninitializedObject that instantiates an object but without invocation of its constructor and field initializers (because of performance purposes as this class & method should be used for serialisation/deserialisation).

When I changed your models to the following code, it started doing its job:

public class MediaParams : PaginationParams
    {
        [DefaultValue("title")]
        public string? OrderBy { get; set; }

        [DefaultValue(" ")]
        public string? SearchTerm { get; set; }

        [DefaultValue(" ")]
        public string? Genres { get; set; }
    }

    public class PaginationParams
    {
        private const int MaxPageSize = 50;

        [DefaultValue(1)]
        public int? PageNumber { get; set; }
        
        [DefaultValue(6)]
        public int? PageSize { get; set; }
    }

Sadly, when DefaultValue has a value of "" (string.Empty) passed, it converts it to null. If you play around with setters and logic there to prevent assigning nulls, this should do the work for you.

Rafal Kozlowski
  • 224
  • 2
  • 2
  • Thank you for response, unfortunately it didn't work for me as it did set all values to null for some reason, so I ended up removing all params from the endpoint and added a class to read params from the the request just like "if (context.Request.Query.ContainsKey("OrderBy"))" and so on. – Ruby Rain Aug 25 '23 at 11:01
1

I've created a class that reads the request and "assembles" a parameters object or keeps default values, I also removed default values from Parameter classes like MediaParams and PaginationParams, and also removed the nullability from properties of these classes

public static class QueryHelper
{
    private const string OrderByDefaultValue = "title";
    private const string SearchTermDefaultValue = "";
    private const int PageNumberDefaultValue = 1;
    private const int PageSizeDefaultValue = 6;


    public static MediaParams GetMediaParams(HttpContext context)
    {
        var mediaParams = new MediaParams
        {
            OrderBy = OrderByDefaultValue,
            SearchTerm = SearchTermDefaultValue,
            Genres = null,
            PageNumber = PageNumberDefaultValue,
            PageSize = PageSizeDefaultValue
        };
        //other parameters
        return mediaParams;
    }
}

In endpoint I am calling this class like this

var pa = QueryHelper.GetMediaParams(context); 
Ruby Rain
  • 105
  • 6