refit icon indicating copy to clipboard operation
refit copied to clipboard

[BUG?] Custom content serializer; ToHttpContent method generic type argument is always object?

Open RobThree opened this issue 4 years ago • 2 comments

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:

Steps To Reproduce

  1. Create a new .Net 5.0 console application
  2. Add Refit Nuget package
  3. Paste the following in program.cs replacing 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);
}
  1. Set a breakpoint at the ToHttpContent and FromHttpContentAsync methods
  2. 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");
}

RobThree avatar Oct 20 '21 09:10 RobThree

Sorry for the bump; I hope to get a little attention for this issue. @clairernovotny ?

RobThree avatar Jul 13 '22 08:07 RobThree