Search code examples
c#xmlrefit

Send !DOCTYPE inside XML content using Refit


How could I be sure that every time Refit sends an API call to an endpoint the !DOCTYPE declaration is defined.

<?xml version="1.0"?>
<!DOCTYPE greeting SYSTEM "hello.dtd">
<greeting>Hello, world!</greeting> 

I've found that when it's missing I got a 400 error response with this content:

No message type defined for xml root node 'greeting, line 45:16
Technical ID 14f2a71af84#2fef8552034a12

Inside the Program file I've defined this code:

services
    .AddRefitClient<IEndpointApi>(new()
    {
        ContentSerializer = new XmlContentSerializer()
    });

The models look like these:

[XmlSchemaProvider("hello.dtd")]
[XmlType("greeting")]
[XmlRoot("greeting")]
public class Greeting 
{
    [XmlText]
    public string Text { get; set; }
}

And the Refit client looks like this:

public interface IEndpointApi
{
    [Post("/endpoint")]
    Task<Response> SendData([Body] Greeting greeting);
}

Solution

  • Found a solution IHttpContentSerializer, maybe not the best one but it's working.

    public class XmlDocTypeContentSerializer : IHttpContentSerializer
    {
        /// <summary>Original XmlContentSerializer from Refit but without a possibility to add a !DOCTYPE.</summary>
        private readonly XmlContentSerializer _xmlContentSerializer;
    
        public XmlContentSerializerSettings ContentSerializerSettings { get; init; }
    
        public XmlDocTypeContentSerializer() : this(new())
        { }
    
        public XmlDocTypeContentSerializer(XmlContentSerializerSettings settings)
        {
            // No settings to process DTD or !DOCTYPE on writing... :-(
            settings.XmlReaderWriterSettings.ReaderSettings.DtdProcessing = DtdProcessing.Parse;
    
            ContentSerializerSettings = settings;
            _xmlContentSerializer = new(settings);
        }
    
        public async Task<T?> FromHttpContentAsync<T>(HttpContent content, CancellationToken cancellationToken = default) =>
            await _xmlContentSerializer.FromHttpContentAsync<T>(content, cancellationToken);
    
        public string? GetFieldNameForProperty(PropertyInfo propertyInfo) =>
            _xmlContentSerializer.GetFieldNameForProperty(propertyInfo);
    
        public HttpContent ToHttpContent<T>(T item)
        {
            ArgumentNullException.ThrowIfNull(item);
    
            Type type = item.GetType();
            StringWriter stringWriter = new();
            XmlSerializer xmlSerializer = new(type);
            XmlWriter xmlWriter = XmlWriter.Create(stringWriter, ContentSerializerSettings.XmlReaderWriterSettings.WriterSettings);
    
            xmlWriter.WriteDocType(name: "greeting", pubid: null, sysid: "hello.dtd", subset: null);
            xmlSerializer.Serialize(xmlWriter, item);
    
            return new StringContent(stringWriter.ToString(), new MediaTypeHeaderValue("application/xml"));
        }
    }