Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add support for custom formating when flattening complex object to query parameters #1258

Open
yohny opened this issue Oct 23, 2021 · 6 comments · May be fixed by #1712
Open

feature: Add support for custom formating when flattening complex object to query parameters #1258

yohny opened this issue Oct 23, 2021 · 6 comments · May be fixed by #1712

Comments

@yohny
Copy link

yohny commented Oct 23, 2021

It would be nice if one could specify how objects are flattened when used as parameters to GET method. For example I have a refit API definition as follows:

public interface IApi
{
    [Get("/info")]
    Task<string> GetInfo(InfoDto dto);
}

where the InfoDto looks like this:

public class InfoDto
{
    public string Name { get; set; }

    [Query(Format = "yyyy-M-d")]
    public DateTime Date { get; set; }

    public Size CarSize { get; set; }
}

the important thing to notice is that Size is not a regular enum, but instead its a custom class implementation of enumeration pattern (simplified version of https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types) and it looks like this:

public class Size
{
    public static Size Small = new Size("small");
    public static Size Medium = new Size("medium");
    public static Size Large = new Size("large");

    public string Key { get;  }

    private Size(string key)
    {
        Key = key;
    }
}

When calling the APi using refit code below

var api = RestService.For<IApi>("http://localhost:54321");
            
var infoDto = new InfoDto { Name = "John Doe", Date = DateTime.Now, CarSize = Size.Medium };
var response = await api.GetInfo(infoDto);

the resulting URL looks like this /info?Name=John%20Doe&Date=2021-10-23&CarSize.Key=medium. Notice that CarSize property is flattened as CarSize.Key in query string.

It would be nice if one could specify how the individual properties are flattened, so it would be possible to tell the CarSize property what its name should be in URL query parameter. The aim in this case it so achieve that CarSize is flattened under name CarSize in URL, so the result looks like /info?Name=John%20Doe&Date=2021-10-23&CarSize=medium

@yohny
Copy link
Author

yohny commented Oct 25, 2021

I found out that what I want is achievable by specifying empty format on CarSize property:

public class InfoDto
{
    public string Name { get; set; }

    [Query(Format = "yyyy-M-d")]
    public DateTime Date { get; set; }

    [Query(Format = "")] // added empty format specification
    public Size CarSize { get; set; }
}

and by adding ToString() implementation to the enumeration:

public class Size
{
    public static Size Small = new Size("small");
    public static Size Medium = new Size("medium");
    public static Size Large = new Size("large");

    public string Key { get;  }

    private Size(string key)
    {
        Key = key;
    }

    // added ToString() implementation
    public override string ToString()
    {
        return Key;
    }
}

(without overriding ToString() the value in URL would be the type name aka. the result of inherited ToString() implementation)

Now the URL produced by refit looks like I want - /info?Name=John%20Doe&Date=2021-10-23&CarSize=medium, but I didn't find this behavior documented anywhere - so is this behavior intentional or just a side effect that can go away at any time?

@yohny
Copy link
Author

yohny commented Dec 7, 2021

I just noticed that the above trick does not work if instead of DTO class the method consumes the values as individual arguments aka. if I change the method signature to:

public interface IApi
{
    [Get("/info")]
    Task<string> GetInfo(string name, [Query(Format = "yyyy-M-d")] DateTime date, [Query(Format = "")] Size carSize);
}

and call the api

var response = await api.GetInfo("John Doe", DateTime.Now, Size.Medium);

the requested URL is /info?name=John%20Doe&date=2021-12-7&Key=medium - the carSize argument is named Key instead of using its parameter name

@kamranayub
Copy link

kamranayub commented Jan 14, 2022

I am running into this except specifically I want to apply my System.Text.Json snake_case naming policy on the query object property parameter names automatically instead of [AliasAs]. When you have tons of objects that all follow the same convention, it's much nicer to define it in one place.

For now I can manually specify each overridden alias (or use snake case property names, which feels weird in "native" .NET userland). After all, it's an implementation detail of the API.

What I was hoping for was something like UrlParameterFormatter that handled the names of parameters, but this only handles parameter values (as far as I can tell). I'll keep digging to see if I can find a different workaround...

edit: Looks like there's no way to override this behavior in a centralized location 😞

var key = propertyInfo.Name;
var aliasAttribute = propertyInfo.GetCustomAttribute<AliasAsAttribute>();
if (aliasAttribute != null)
key = aliasAttribute.Name;

@sguryev
Copy link
Contributor

sguryev commented Nov 22, 2023

@kamranayub have tried create a custom implementation of DelegatingHandler and register it via AddHttpMessageHandler<>? Then you can try to override SendAsync method and do whatever you want with the request.RequestUri

@kamranayub
Copy link

@kamranayub have tried create a custom implementation of DelegatingHandler and register it via AddHttpMessageHandler<>? Then you can try to override SendAsync method and do whatever you want with the request.RequestUri

Thanks, it's been awhile but I have an API I'd like to convert to Refit where I may need to do that.

@tcortega
Copy link
Contributor

tcortega commented Jun 4, 2024

@ChrisPulman this should be closed

@ChrisPulman ChrisPulman linked a pull request Jun 9, 2024 that will close this issue
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants