To build an API for a thick-client .NET Framework application, you should implement the System.Runtime.Remoting infrastructure using custom channels and well-known objects. This approach allows legacy desktop applications to communicate over TCP or HTTP protocols without migrating to modern web standards, ensuring backward compatibility while exposing specific business logic through a structured interface.
Understanding Remoting in Legacy .NET
System.Runtime.Remoting was the primary distributed object technology for .NET Framework versions prior to .NET Core. While Microsoft has deprecated it in favor of gRPC and REST APIs, many enterprise thick clients still rely on this pattern. The core concept involves separating the client-side proxy from the server-side implementation. When a client calls a method on a remote object, the remoting infrastructure serializes the parameters, sends them over a channel, deserializes them on the server, executes the method, and returns the result.
This architecture is particularly useful when your thick client requires high-performance binary serialization and low-latency communication that standard HTTP JSON endpoints might not provide efficiently in older frameworks. By leveraging MarshalByRefObject, you can create classes that live on the server but appear as local objects to the client code.
Configuring the Server-Side Channel
The first step is to configure the server to listen for incoming requests. You must register a channel, typically TCP or HTTP, and publish your service. For a robust internal API, the TCP channel is preferred due to its performance and security features compared to the HTTP channel.
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class RemotingServer
{
public static void Start()
{
// Configure the TCP channel with a specific port
var properties = new System.Collections.Hashtable();
properties["port"] = 8085;
// Create and register the channel
TcpChannel channel = new TcpChannel(properties, null, null);
ChannelServices.RegisterChannel(channel, false);
// Publish the service as a single-call or singleton object
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(MyBusinessApi),
"MyApi",
WellKnownObjectMode.Singleton);
Console.WriteLine("Remoting server started on port 8085");
}
}
In this configuration, MyBusinessApi is the class implementing your API logic. It must inherit from MarshalByRefObject. The string "MyApi" serves as the URI endpoint that clients will use to locate the service. Using Singleton mode ensures that all clients share the same instance of the service, which is often desirable for stateful business logic. If your API is stateless, consider using SingleCall mode to create a new instance per client request.
Implementing the Service Interface
Your API methods should be defined in a separate interface assembly if possible, allowing both the server and client to reference the same contract. This decouples the implementation from the client code. The service class must inherit from MarshalByRefObject to allow the runtime to manage its lifetime across application domains.
using System;
public interface IMyBusinessApi
{
string GetData(int id);
bool ProcessTransaction(string payload);
}
public class MyBusinessApi : MarshalByRefObject, IMyBusinessApi
{
public string GetData(int id)
{
// Simulate database access or business logic
return $"Data for ID {id}";
}
public bool ProcessTransaction(string payload)
{
// Execute transaction logic
return true;
}
}
Notice that GetData returns a primitive type, which is easily serialized. However, if you need to return complex objects, ensure they are marked with [Serializable] or implement ISerializable. Avoid returning non-serializable types like IDisposable instances unless you handle disposal carefully on the client side.
Connecting the Thick Client
The client application needs to obtain a proxy to the remote object. This is done by creating a TcpChannel (if not already registered) and calling RemotingConfiguration.GetWellKnownServiceType or using Activator.GetObject.
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class ClientProxy
{
private IMyBusinessApi _api;
public ClientProxy()
{
// Register the client channel
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel, false);
// Get the remote object proxy
string url = "tcp://localhost:8085/MyApi";
_api = (IMyBusinessApi)Activator.GetObject(typeof(IMyBusinessApi), url);
}
public string FetchData(int id)
{
return _api.GetData(id);
}
}
Once the proxy is obtained, the client calls methods on _api as if they were local. The remoting framework handles the network communication transparently. Errors such as RemotingException may occur if the server is unavailable or if serialization fails. It is crucial to wrap these calls in try-catch blocks to handle network interruptions gracefully.
Security Considerations
By default, TCP remoting does not encrypt traffic. In production environments, you should enable authentication and encryption. You can configure the channel properties to require secure connections or integrate with Windows Authentication. Additionally, always validate input parameters on the server side to prevent injection attacks, just as you would with any web API.
Migration Path to Modern Standards
While remoting works for legacy systems, maintaining it long-term is challenging. As you refactor your thick client, consider gradually moving specific endpoints to a Web API. You can run both systems in parallel during the transition period. For new development within the .NET Framework ecosystem, look into lightweight alternatives or prepare for migration to .NET 6+ where remoting is completely absent. Reading about Mastering Nested Asynchronous Tasks in .NET for Data Processing can help you modernize the underlying data processing logic that your API exposes.
Frequently Asked Questions
Is System.Runtime.Remoting supported in .NET Core?
No, remoting is not supported in .NET Core or .NET 5+. It is exclusive to the full .NET Framework. You must migrate to gRPC, WCF, or REST APIs if you upgrade the runtime.
Can I use remoting over the internet?
Technically yes, but it is highly discouraged due to security risks. Remoting lacks built-in HTTPS support and encryption. Use a VPN or migrate to a secure web API for external exposure.
How do I handle exceptions thrown by the remote method?
Exceptions thrown on the server are wrapped in a RemotingException on the client. The original exception details are available in the InnerException property. Ensure your custom exceptions are serializable to preserve error context.
What is the difference between Singleton and SingleCall modes?
Singleton creates one shared instance of the service for all clients, preserving state. SingleCall creates a new instance for every method invocation, ensuring statelessness and better scalability for simple operations.
Why am I getting a 'Type not found' error?
This usually occurs when the client cannot find the assembly containing the interface or the service class. Ensure the assembly is deployed correctly and referenced by both the server and client projects.