Migrating WCF to grpc

Time:2021-6-9

Using protobuf-net.grpc to migrate WCF Services to grpc is very simple. In this post, we’ll see how simple it is. Microsoft’s official guide on migrating WCF Services to grpc only mentions the Google. Protobuf method. If you have many data contracts that need to be migrated to. Proto format, it may be time-consuming. However, by using protobuf-net.grpc, we can reuse old WCF data contracts and service contracts with minimal code changes.

Migrating data contracts and service contracts

In this section, we’ll use a simple request response portfolio service that allows you to download individual or all portfolios for a given trader. The definition of service and data contract is as follows:

[ServiceContract]
public interface IPortfolioService
{
    [OperationContract]
    Task<Portfolio> Get(Guid traderId, int portfolioId);

    [OperationContract]
    Task<List<Portfolio>> GetAll(Guid traderId);
}
[DataContract]
public class Portfolio
{
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public Guid TraderId { get; set; }

    [DataMember]
    public List<PortfolioItem> Items { get; set; }
}

[DataContract]
public class PortfolioItem
{
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public int ShareId { get; set; }

    [DataMember]
    public int Holding { get; set; }

    [DataMember]
    public decimal Cost { get; set; }
}

Before migrating the data contract and service contract to grpc, I suggest creating a new class library for the contract. Depending on the structure of your WCF solution, these contracts can be easily shared between the server and the client through project or package references. Once we created the class library, we copied the source files and started migrating to grpc.

Unlike using Google. Protobuf to migrate to grpc, data contracts require minimal changes. The only thing we need to do is define the order property in the DataMember property. This is equivalent to defining the field number when creating a message in. Proto format. These field numbers are used to identify the fields in the message binary format and should not be changed after using the message.

[DataContract]
public class Portfolio
{
    [DataMember(Order = 1)]
    public int Id { get; set; }

    [DataMember(Order = 2)]
    public Guid TraderId { get; set; }

    [DataMember(Order = 3)]
    public List<PortfolioItem> Items { get; set; }
}

[DataContract]
public class PortfolioItem
{
    [DataMember(Order = 1)]
    public int Id { get; set; }

    [DataMember(Order = 2)]
    public int ShareId { get; set; }

    [DataMember(Order = 3)]
    public int Holding { get; set; }

    [DataMember(Order = 4)]
    public decimal Cost { get; set; }
}

Due to the difference between grpc and WCF, the service contract will need more modification. RPC method in grpc service must define only one message type as request parameter and return only one message. We cannot accept a scalar type (that is, a base type) as a request parameter, nor can we return a scalar type. We need to combine all the original parameters into a single message (i.e. data contract). This also explains the type of the guid parameter, because it can be serialized as a string, depending on how you configure protobuf net. We also cannot accept message lists (or scalars) or return message lists (or scalars). With these rules in mind, we need to modify our service contract to look like this:

[ServiceContract]
public interface IPortfolioService
{
    [OperationContract]
    Task<Portfolio> Get(GetPortfolioRequest request);

    [OperationContract]
    Task<PortfolioCollection> GetAll(GetAllPortfoliosRequest request);
}

The above changes in the service contract force us to create some additional data contracts. Therefore, we create the following:

[DataContract]
public class GetPortfolioRequest
{
    [DataMember(Order = 1)]
    public Guid TraderId { get; set; }

    [DataMember(Order = 2)]
    public int PortfolioId { get; set; }
}

[DataContract]
public class GetAllPortfoliosRequest
{
    [DataMember(Order = 1)]
    public Guid TraderId { get; set; }
}

[DataContract]
public class PortfolioCollection
{
    [DataMember(Order = 1)]
    public List<Portfolio> Items { get; set; }
}

Basically. Now we have migrated our WCF service contract and data contract to grpc. The next step is to migrate the data layer to the. Net core.

Migrating portfoliodata library to. Net core

Next, we will migrate the portfolio data library to the. Net core, as described in the Microsoft guide. However, we do not need to copy the models (portfolio. CS and portfolio item. CS) because they are already defined in the class library we created in the previous section. Instead, we will add a project reference to the shared library. The next step is to migrate WCF Services to ASP. Net core applications.

Migrating WCF Services to ASP. Net core applications

The first thing we need to do is create an ASP. Net core application. So either start your favorite IDE, create a basic asp.net core application, or run dotnet new web from the command line. Next, we need to add a package for protobuf-net.grpc. Install it with your favorite package manager, or simply run dotnet add package protobuf net. Grpc. Aspnetcore. We also need to add a project reference to the portfolio data library created in the previous section.

Now that we have the project ready and added all the dependencies, we can continue and create the portfolio service. Create a new class with the following.

public class PortfolioService : IPortfolioService
{
    private readonly IPortfolioRepository _repository;

    public PortfolioService(IPortfolioRepository repository)
    {
        _repository = repository;
    }

    public async Task<Portfolio> Get(GetPortfolioRequest request)
    {
        var portfolio = await _repository.GetAsync(request.TraderId, request.PortfolioId);

        return portfolio;
    }

    public async Task<PortfolioCollection> GetAll(GetAllPortfoliosRequest request)
    {
        var portfolios = await _repository.GetAllAsync(request.TraderId);

        var response = new PortfolioCollection
        {
            Items = portfolios
        };

        return response;
    }
}

The above service looks very similar to the WCF service implementation, except for input parameter types and return parameter types.

Last but not least, we need to connect protobuf-net.grpc to the ASP. Net core pipeline and register it in the di container. On startup.cs, we will add the following:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IPortfolioRepository, PortfolioRepository>();
        services.AddCodeFirstGrpc();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGrpcService<PortfolioService>();
        });
    }
}

Now we have grpc service. The next thing on our list is to create a client application.

Create grpc client application

For our client application, we will continue to create a console application. Either create a console using your favorite ide or run dotnet new console directly from the command line. Next, we need to add nuget packages for protobuf net. Grpc and grpc. Net. Client. Install them with your favorite package manager, or simply run dotnet add package protobuf-net.grpc and dotnet add package grpc.net.client. We also need to add a project reference to the shared library we created in the first section.

In our program.cs, we will add the following code to create a grpc client and communicate with the grpc service.

class Program
{
    private const string ServerAddress = "https://localhost:5001";

    static async Task Main()
    {
        var channel = GrpcChannel.ForAddress(ServerAddress);
        var portfolios = channel.CreateGrpcService<IPortfolioService>();

        try
        {
            var request = new GetPortfolioRequest
            {
                TraderId = Guid.Parse("68CB16F7-42BD-4330-A191-FA5904D2E5A0"),
                PortfolioId = 42
            };
            var response = await portfolios.Get(request);

            Console.WriteLine($"Portfolio contains {response.Items.Count} items.");
        }
        catch (RpcException e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

Now we can test our implementation by starting ASP. Net first   Core application, and then start the console application.

Migrating WCF duplex service to grpc

Now we’ve talked about using protobuf-net.grpc   For the basics of migrating WCF Services to grpc, let’s take a look at some more complex examples.

In this section, we’ll look at simplestockpriceticker, a duplex service where the client initiates the connection and the server uses the callback interface to send updates when they are available. The WCF service has a method without a return type because it uses the callback interface isimplestocktickercallback to send data to the client in real time.

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ISimpleStockTickerCallback))]
public interface ISimpleStockTickerService
{
    [OperationContract(IsOneWay = true)]
    void Subscribe(string[] symbols);
}

[ServiceContract]
public interface ISimpleStockTickerCallback
{
    [OperationContract(IsOneWay = true)]
    void Update(string symbol, decimal price);
}

When migrating this service to grpc, we can use grpc flow. The grpc server stream works like the WCF service above. For example, the client sends a request and the server responds with a message flow. The usual way to implement server stream in protobuf net.grpc is to return iasyncenumerable < T > from RPC method. In this way, we can use the same interface for service contracts on both the client and server sides. Please note that protobuf-net.grpc also supports google.protobuf mode (use iserverstreamwriter < T > on the server and asyncserverstreamingcall < T > on the client). We need to use separate interface methods for the client and server. Using iasyncenumerable < T > as streaming media will make our service contract look like the following code.

[ServiceContract]
public interface IStockTickerService
{
    [OperationContract]
    IAsyncEnumerable<StockTickerUpdate> Subscribe(SubscribeRequest request, CallContext context = default);
}

Note the CallContext parameter, which is the grpc call context on both the client and server sides. This allows us to access the call context on both the client and server sides without the need for a separate interface. The code generated by gogoggle. Protobuf will use the call on the client side and the servercallcontext on the server side.

Because WCF Services only use basic types as parameters, we need to create a set of data contracts that can be used as parameters. The data contract attached to the service above looks like this. Note that we have added a timestamp field to the response message, which does not exist in the original WCF service.

[DataContract]
public class SubscribeRequest
{
    [DataMember(Order = 1)]
    public List<string> Symbols { get; set; } = new List<string>();
}

[DataContract]
public class StockTickerUpdate
{
    [DataMember(Order = 1)]
    public string Symbol { get; set; }

    [DataMember(Order = 2)]
    public decimal Price { get; set; }

    [DataMember(Order = 3)]
    public DateTime Time { get; set; }
}

By reusing istockpricesubscriberfactory in Microsoft migration guide, we can implement the following services. By using system. Threading. Channels, you can easily stream events to an asynchronous enumerable object.

public class StockTickerService : IStockTickerService, IDisposable
{
    private readonly IStockPriceSubscriberFactory _subscriberFactory;
    private readonly ILogger<StockTickerService> _logger;
    private IStockPriceSubscriber _subscriber;

    public StockTickerService(IStockPriceSubscriberFactory subscriberFactory, ILogger<StockTickerService> logger)
    {
        _subscriberFactory = subscriberFactory;
        _logger = logger;
    }

    public IAsyncEnumerable<StockTickerUpdate> Subscribe(SubscribeRequest request, CallContext context = default)
    {
        var buffer = Channel.CreateUnbounded<StockTickerUpdate>();

        _subscriber = _subscriberFactory.GetSubscriber(request.Symbols.ToArray());
        _subscriber.Update += async (sender, args) =>
        {
            try
            {
                await buffer.Writer.WriteAsync(new StockTickerUpdate
                {
                    Symbol = args.Symbol,
                    Price = args.Price,
                    Time = DateTime.UtcNow
                });
            }
            catch (Exception e)
            {
                _logger.LogError($"Failed to write message: {e.Message}");
            }
        };

        return buffer.AsAsyncEnumerable(context.CancellationToken);
    }

    public void Dispose()
    {
        _subscriber?.Dispose();
    }
}

WCF full duplex service allows two-way asynchronous, real-time messaging. In the previous example, the client started a request and received an update flow. In this release, clients stream request messages to add and remove subscriptions from the list without having to create a new subscription. The WCF service contract is defined as follows. The client uses the subscribe method to start the subscription, and uses the addsymbol and removesymbol methods to add or delete it. Updates are received through the callback interface, as in the previous server flow example.

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IFullStockTickerCallback))]
public interface IFullStockTickerService
{
    [OperationContract(IsOneWay = true)]
    void Subscribe();

    [OperationContract(IsOneWay = true)]
    void AddSymbol(string symbol);

    [OperationContract(IsOneWay = true)]
    void RemoveSymbol(string symbol);
}

[ServiceContract]
public interface IFullStockTickerCallback
{
    [OperationContract(IsOneWay = true)]
    void Update(string symbol, decimal price);
}

The equivalent service contract implemented with protobuf-net.gprc is as follows. The service accepts the request message flow and returns the response message flow.

[ServiceContract]
public interface IFullStockTicker
{
    [OperationContract]
    IAsyncEnumerable<StockTickerUpdate> Subscribe(IAsyncEnumerable<SymbolRequest> request, CallContext context = default);
}

The attached data contract is defined below. The request includes an action property that specifies whether the symbol should be added or removed from the subscription. The response message is the same as the previous example.

public enum SymbolRequestAction
{
    Add = 0,
    Remove = 1
}

[DataContract]
public class SymbolRequest
{
    [DataMember(Order = 1)]
    public SymbolRequestAction Action { get; set; }

    [DataMember(Order = 2)]
    public string Symbol { get; set; }
}

[DataContract]
public class StockTickerUpdate
{
    [DataMember(Order = 1)]
    public string Symbol { get; set; }

    [DataMember(Order = 2)]
    public decimal Price { get; set; }

    [DataMember(Order = 3)]
    public DateTime Time { get; set; }
}

The implementation of the service is shown below. Using the same technique as the previous example, we flow events through iasyncenumerable < T > and create a background task that enumerates the request flow and responds to a single request.

public class FullStockTickerService : IFullStockTicker, IDisposable
{
    private readonly IFullStockPriceSubscriberFactory _subscriberFactory;
    private readonly ILogger<FullStockTickerService> _logger;
    private IFullStockPriceSubscriber _subscriber;
    private Task _processRequestTask;
    private CancellationTokenSource _cts;

    public FullStockTickerService(IFullStockPriceSubscriberFactory subscriberFactory, ILogger<FullStockTickerService> logger)
    {
        _subscriberFactory = subscriberFactory;
        _logger = logger;
        _cts = new CancellationTokenSource();
    }

    public IAsyncEnumerable<StockTickerUpdate> Subscribe(IAsyncEnumerable<SymbolRequest> request, CallContext context)
    {
        var cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, context.CancellationToken).Token;
        var buffer = Channel.CreateUnbounded<StockTickerUpdate>();

        _subscriber = _subscriberFactory.GetSubscriber();
        _subscriber.Update += async (sender, args) =>
        {
            try
            {
                await buffer.Writer.WriteAsync(new StockTickerUpdate
                {
                    Symbol = args.Symbol,
                    Price = args.Price,
                    Time = DateTime.UtcNow
                });
            }
            catch (Exception e)
            {
                _logger.LogError($"Failed to write message: {e.Message}");
            }
        };

        _processRequestTask = ProcessRequests(request, buffer.Writer, cancellationToken);

        return buffer.AsAsyncEnumerable(cancellationToken);
    }

    private async Task ProcessRequests(IAsyncEnumerable<SymbolRequest> requests, ChannelWriter<StockTickerUpdate> writer, CancellationToken cancellationToken)
    {
        await foreach (var request in requests.WithCancellation(cancellationToken))
        {
            switch (request.Action)
            {
                case SymbolRequestAction.Add:
                    _subscriber.Add(request.Symbol);
                    break;
                case SymbolRequestAction.Remove:
                    _subscriber.Remove(request.Symbol);
                    break;
                default:
                    _logger.LogWarning($"Unknown Action '{request.Action}'.");
                    break;
            }
        }

        writer.Complete();
    }

    public void Dispose()
    {
        _cts.Cancel();
        _subscriber?.Dispose();
    }
}

summary

congratulations! You’ve come this far. Now you know another way to migrate WCF Services to grpc. Hopefully, this technique is much faster than rewriting existing data contracts in. Proto format.

 Welcome to official account official account. If you love foreign technical articles, you can recommend it to me through public comments.

Migrating WCF to grpc