XmlRpcCore is a small, modernized XML-RPC client and serializer library compatible with .NET Standard 2.0 and later. The library favors modern .NET patterns:
- Generic collections (
List<object>,Dictionary<string, object>) instead of legacy non-generic collections. - DI-friendly serializers/deserializers (
IXmlRpcSerializer). - Async streaming request/response APIs to avoid intermediate allocations.
- Extensible mapping from XML-RPC values to POCOs via
IObjectMapper.
This README shows current usage examples and a migration guide from legacy code (3.x and XmlRpcCs).
Public types you will commonly use are:
XmlRpcCore.XmlRpcRequest/XmlRpcCore.XmlRpcResponseXmlRpcCore.IXmlRpcSerializer(serializer abstraction)XmlRpcCore.XmlRpcNetSerializer(default serializer)XmlRpcCore.XmlRpcClient(modern, DI-friendly client)XmlRpcCore.ObjectMapper(default POCO mapper)
Breaking / migration notes:
- There is an
[Obsolete]constructor onXmlRpcRequestthat acceptsArrayListkept only for transition. PreferList<object>/Dictionary<string, object>.
A minimal example showing the typical call flow (includes required usings):
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using XmlRpcCore;
class Program
{
static async Task Main()
{
var httpClient = new HttpClient();
var request = new XmlRpcRequest("demo.sum", new List<object> { 4, 5 });
var response = await httpClient.PostAsXmlRpcAsync("https://example.com/rpc", request);
Console.WriteLine(response.Value);
}
}Register the serializer, mapper and typed client using IServiceCollection so you can consume a typed XmlRpcClient with IHttpClientFactory integration:
using Microsoft.Extensions.DependencyInjection;
using XmlRpcCore;
var services = new ServiceCollection();
// register defaults
services.AddSingleton<IXmlRpcSerializer, XmlRpcNetSerializer>();
services.AddSingleton<IObjectMapper, ObjectMapper>();
// register typed client; AddHttpClient will supply HttpClient in constructor
services.AddHttpClient<XmlRpcClient>();
var provider = services.BuildServiceProvider();
var xmlClient = provider.GetRequiredService<XmlRpcClient>();
// use xmlClient.InvokeAsync<T>(url, request)Use the async stream APIs to avoid intermediate string allocations and to support cancellation:
using System.IO;
using System.Threading;
using XmlRpcCore;
var serializer = new XmlRpcNetSerializer();
var request = new XmlRpcRequest("demo.heavy", new List<object> { /* large payload */ });
using var ms = new MemoryStream();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await serializer.SerializeRequestAsync(request, ms, cts.Token);
ms.Position = 0;
var response = await serializer.DeserializeResponseAsync(ms, cts.Token);The default ObjectMapper uses System.Text.Json internally to map dictionary-shaped XML-RPC results into POCOs and back. A few important notes:
- Nested objects and arrays are handled: the mapper converts
JsonElementtrees into plain CLR objects (integers, longs, doubles, strings, DateTime,List<object>,Dictionary<string, object>). - Numeric types may be
intorlongdepending on value magnitude; code deserializing dictionary values should be resilient tolongvsint(tests include helpers to convert when necessary). - Enums are mapped from strings when possible (default
JsonStringEnumConverterbehavior). - If you need tighter control or better performance for hot paths, consider implementing a reflection-based
IObjectMapperand registering it in DI.
Below is a minimal example that demonstrates mapping a dictionary-shaped XML-RPC value to a POCO and back using the default ObjectMapper:
using System;
using System.Collections.Generic;
using XmlRpcCore;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Joined { get; set; }
}
// Mapping a dictionary to a POCO
var dict = new Dictionary<string, object>
{
["Name"] = "Alice",
["Age"] = 30,
["Joined"] = "2020-12-31T23:59:59Z" // JSON date string will be converted to DateTime
};
var mapper = new ObjectMapper();
Person p = mapper.MapTo<Person>(dict);
Console.WriteLine($"Name={p.Name}, Age={p.Age}, Joined={p.Joined:o}");
// Mapping a POCO back to a dictionary (useful for sending structs)
var payload = (Dictionary<string, object>)mapper.MapFrom(p);
Console.WriteLine(payload["Name"]);Notes:
- The mapper will attempt to convert JSON strings to
DateTimewhere possible. - Numeric values may come back as
intorlongdepending on magnitude; code consuming raw dictionaries should handle both. - Enums are supported when the JSON contains the enum name (e.g.
"GreenBlue").
XmlRpcCore uses safe defaults to mitigate XXE and XML-based DoS attacks:
- DTD processing is disabled by default and
XmlResolveris not used. - A maximum element depth (
MaxDepth, default 128) is enforced during parsing. XmlRpcOptionsexposes additional limits:MaxCharactersInDocumentandMaxCharactersFromEntities. These are applied when supported by the runtime.
To tune settings for your application, set XmlRpcSettingsManager.Options at startup (example below). Only enable DTDs or an XmlResolver if you fully understand the security implications.
XmlRpcSettingsManager.Options = new XmlRpcOptions
{
MaxDepth = 256,
MaxCharactersInDocument = 50_000_000,
MaxCharactersFromEntities = 2_000_000,
AllowDtd = false,
AllowXmlResolver = false
};Notes
- Changing
Optionsaffects all deserializers that use the shared settings. Set before handling untrusted input. - Only change
AllowDtd/AllowXmlResolverif you fully understand the security implications.
If you previously used ArrayList/Hashtable or singleton serializers, follow these steps to migrate safely:
- Replace
ArrayListinputs withList<object>andHashtablewithDictionary<string, object>.
Legacy code example:
// legacy
ArrayList args = new ArrayList();
args.Add("hello");
Hashtable map = new Hashtable();
map["a"] = 1;Modern replacement:
var args = new List<object> { "hello" };
var map = new Dictionary<string, object> { ["a"] = 1 };-
Use the DI-friendly
IXmlRpcSerializerinstead of singletons. Pass a serializer intoXmlRpcClientor toXmlRpcRequestconstructors when you need custom behavior. -
Replace
XmlRpcRequestSerializer.SingletonorXmlRpcResponseSerializer.Singletoncalls by constructing an instance or registering one in DI.
- Legacy non-generic collections support has been removed from the core serialization pipeline. The codebase still ships a marked
[Obsolete]constructor onXmlRpcRequestthat acceptsArrayListas a transition, but you should migrate to generic collections. - The library now prefers modern APIs and async streaming patterns; some convenience synchronous APIs may still exist for compatibility.
There are typed exceptions to help diagnosing issues:
XmlRpcProtocolException� parsing/format errors in XML.XmlRpcTransportException� transport or unexpected deserialization errors.XmlRpcException� represents an XML-RPC fault (faultCode/faultString).
The repository includes unit tests covering serialization, deserialization, streaming, mapping, and client behaviors. Run them with:
dotnet test XmlRpcCore.Tests/XmlRpcCore.Tests.csproj- Use the stream-based async APIs to avoid intermediate string allocations in high-throughput scenarios.
- Swap the
ObjectMapperfor a reflection-based mapper if JSON round-trips are too costly for your workload. - Configure
JsonSerializerOptionsor provide a customIObjectMapperto control mapping behavior.
Contributions are welcome. Please follow the existing project style and add unit tests for new behaviors. Open issues or PRs on the project GitHub repository.
XmlRpcCore is under the BSD license. See: LICENSE.
- XML-RPC spec: http://xmlrpc.org
- Original project: http://xmlrpccs.sourceforge.net/