refit
refit copied to clipboard
[BUG?] Custom content serializer; ToHttpContent method generic type argument is always object?
First of all: I tried searching but haven't found a similar issue in this repository. If this doesn't belong here then my apologies; please point me in the correct direction.
Description
It appears that the generic type T is always object when a custom HttpContentSerializer is implemented:
public class MyContentSerializer : IHttpContentSerializer
{
public Task<T?> FromHttpContentAsync<T>(HttpContent content, CancellationToken cancellationToken = default)
{
// T is the expected type here 👍
}
public HttpContent ToHttpContent<T>(T item)
{
// T is always object here?? 🤔
}
}
I've looked at the source code and these are my findings:
- SystemTextJsonContentSerializer doesn't care about
T - SystemTextJsonContentSerializer ditto
- XmlContentSerializer uses
item.GetType()(notT) to get the type
Steps To Reproduce
- Create a new .Net 5.0 console application
- Add Refit Nuget package
- Paste the following in
program.csreplacing it's entire contents
using Refit;
using System;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace TestRefit
{
internal class Program
{
private static async Task Main(string[] args)
{
var client = RestService.For<ISomeApi>("https://httpbin.org", new RefitSettings
{
ContentSerializer = new MyContentSerializer()
});
await client.Test(new CustomerQuery("foo")).ConfigureAwait(false);
}
}
public class MyContentSerializer : IHttpContentSerializer
{
public Task<T?> FromHttpContentAsync<T>(HttpContent content, CancellationToken cancellationToken = default)
{
//T is Customer here 👍
throw new NotImplementedException();
}
public HttpContent ToHttpContent<T>(T item)
{
//T is Object here; expected CustomerQuery 🤔
throw new NotImplementedException();
}
public string GetFieldNameForProperty(PropertyInfo propertyInfo) => throw new NotImplementedException();
}
public interface ISomeApi
{
[Post("/post")]
Task<Customer> Test([Body] CustomerQuery customerQuery);
}
public record Customer(string Id, string Name);
public record CustomerQuery(string Find);
}
- Set a breakpoint at the
ToHttpContentandFromHttpContentAsyncmethods - Debug the application and inspect T when the methods are invoked to confirm my findings
Expected behavior
I may be mistaken, but I expect T to be the desired type?
Environment
- OS: Windows 10
- Device: PC
- Version: 6.1.15
- Working Version: Unknown
Additional context
I'm using a custom XML serializer that uses a generic Serialize<T> method where T is used to determine which serializer to use internally. I can put a shim in my MyContentSerializer where I use some ugly item.GetType() and reflection magic to get to the Serialize<T>(T value) method of the serializer but that's exactly that: a shim. I'm hoping not to have to use this ugly workaround.
Current:
private static readonly ConcurrentDictionary<Type, MethodInfo> _methodcache = new();
public HttpContent ToHttpContent<T>(T item)
{
var type = item.GetType();
var method = _methodcache.GetOrAdd(type, (t) => _serializer.GetType().GetMethods()
.Where(m => m.Name == nameof(IMyCustomXMLSerializer.Serialize) && m.ReturnType == typeof(string))
.First()
.MakeGenericMethod(t)
);
var xmlstring = (string)method.Invoke(_serializer, new object[] { item });
return new StringContent(xmlstring, _serializer.XmlWriterSettings.Encoding, "application/xml");
}
Desired:
public HttpContent ToHttpContent<T>(T item)
{
var xmlstring = _serializer.Serialize<T>(item);
return new StringContent(xmlstring, _serializer.XmlWriterSettings.Encoding, "application/xml");
}
Sorry for the bump; I hope to get a little attention for this issue. @clairernovotny ?