Microsoft recently released .NET Core 3.0 and set the stage for WCF’s exit. .NET Core no longer supports “full framework” mode which effectively ends support for WCF as we know it. Our options for migration vary. Today let’s discuss one such approach I feel is a “low-impact” approach. Let’s look at migrating WCF to gRPC using .NET Core while still maintaining backwards compatibility.
Let’s branch the code from Using WCF With .NET Core for today’s discussion. All code for today’s post is located on the gRPC branch. Several of the concepts today were shared to me by my brother Jason Seeley. I’ve been trying to convince him to write this post for several months. Kudos to him on the approach I’ll highlight for migrating from WCF to gRPC.
What is gRPC?
Rather than make the assumption you know what gRPC is perhaps a small introduction is in order? gRPC is an open source remote procedure call (RPC) system. gRPC was developed by Google and runs on the HTTP/2 transport protocol. At the time of writing this post, gRPC is officially supported by 12 languages (“web” is loosely included in that list).
Ultimately the goal of gRPC is to allow an RPC framework that can run anywhere. It allows true cross-platform communication in a really rich format. I invite you to head to the official site to learn more.
Why do I want to migrate WCF to gRPC?
Aside from the (semi?) obvious fact that WCF is no longer a first-class citizen in the .NET framework, there are plenty of reasons you should might want to migrate. How about we let the official page start the talking:
The main usage scenarios:
- Low latency, highly scalable, distributed systems.
- Developing mobile clients which are communicating to a cloud server.
- Designing a new protocol that needs to be accurate, efficient and language independent.
- Layered design to enable extension eg. authentication, load balancing, logging and monitoring etc.
Living in our connected modern world, it seems like you stand to gain a lot by a truly cross-platform system. Yes, it can be argued that a lot of the same can be accomplished via REST but we’re comparing apples and oranges there.
Another reason you may want to migrate is to take advantage of newer framework versions. Entity Framework Core v3, for example, is only supported on netstandard2.1
or netcoreapp3.0
. That flat out disqualifies it’s use with WCF. *update* EF Core 3.1, however, does have support for netstandard2.0
. That buys you some time.
Performance
Performance is yet one more reason why you might want to switch. Mark Rendle wrote two posts benchmarking the two. The second post he attempts to make a truly apples to apples comparison which puts them close, but still a win by gRPC. I want to stress that you *also* gain the cross-platform compatibility by going gRPC where WCF does not offer that with the NetTCP binding (used in the second test). One reader still suggests that WCF will outperform gRPC if you strip authentication from WCF. I’ll leave it to you to decide on that.
Let’s get started!
As I began looking into .NET Core 3.0 and how I might configure a gRPC application I naturally started with MSDN’s own introduction. The tutorial and sample found there is pretty simple but I want to highlight a couple red flags.
- Examples on MSDN are almost always extremely simplistic in form. This means that they aren’t necessarily going to be “best” for production-ready code.
- The example on the aforementioned link assumes you are moving forward with .NET Core 3.0 fully and completely.
If you want to take a graceful migration approach to gRPC, however, you cannot commit 100% to netcoreapp3.0
(or, by extension, netstandard2.1
). Luckily we don’t need to create a dependency on these latest framework constructs to accomplish our task.
To get started, however, you’ll want to make sure you either have Visual Studio 2019.3 (Version 16.3) or are familiar with VSCode and have the netcore3.0 SDK installed. Using the templates provided by the SDK will help get you there faster even if you ultimately end up changing things around to support earlier framework versions.
Next I highly recommend you download the code for this post. It is going to get really tricky really fast. While I will highlight the heavy points as possible, this post would be astronomically huge if I talked everything. Instead I’ll try to keep it shorter but reference the completed code periodically.
Creating a gRPC host (the easy way)
I started by following the tutorial on MSDN. I quickly decided I was unhappy with it. In their example they have you copy/paste the proto file from your server to your client. They will quickly get out of sync doing this if you control both. There are ways to keep them in sync, of course, but I hypothesized there had to be a way to share them.
Discussing the idea with my brother led to his suggestion that I make a separate assembly and put the proto files there but service agnostic. Now, both the client and server can consume the same generated DTOs, etc. I recommend you still go through their tutorial to see how it all lines up and then you can better appreciate the approach I took. We have a few steps to get there though.
1) Create the shared “proto” library
Let’s start by creating a netstandard2.0
library. I called mine DotNetCoreWCF.Proto
to keep with the naming convention of the previous blog post. **IMPORTANT** For this to work in our WCF host it cannot be netstandard2.1
. Next, add references to Google.Protobuf
, Grpc
, and Grpc.Tools
. Your csproj should end up looking something like this:
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.10.0" />
<PackageReference Include="Grpc" Version="2.24.0" />
<PackageReference Include="Grpc.Tools" Version="2.24.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
Next, add a folder called Protos
. If your repository shares with applications/libraries in other languages, you might consider adding a folder and then linking your protos from another folder in your repo. (Add Existing Item -> Add As Link) In our case, let’s just create the proto
files here directly. I copied the greet.proto
from the MSDN example and then created my own called employee.proto
. There is not a template option for proto in this library so just add as a text file. Now, adjust your csproj similar to below:
<ItemGroup>
<None Remove="Protos\employee.proto" />
<None Remove="Protos\greet.proto" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\employee.proto" />
<Protobuf Include="Protos\greet.proto" />
</ItemGroup>
A difference you’ll note here from the auto-generated ones is that it excludes the GrpcServices
attribute. Once you’ve mapped it as a Protobuf here, the properties dialog on the proto
files has a “gRPC Stub Classes” attribute. Setting it to “Server and Client” will omit the attribute. Setting it to any other value will set it in the csproj as well. We want it to generate both. See the employees.proto here.
One important thing to note in your proto
file is the option csharp_namespace = "DotNetCoreWCF.GrpcSample.Services";
line. This is the namespace that our protos will generate the client, server, and DTOs into. Ideally you’ll namespace all your different services and protos accordingly but I’m just keeping this ultra simple.
One question I had come up that Jason helped answer was how to get a nullable int (int?
) to generate. He showed me to include import "google/protobuf/wrappers.proto";
and then use the wrapper/container for Int32Value instead of simply int32. Another item of interest was how to represent an array. See the repeated
keyword. I had to bookmark this page on grpc.io for quick reference.
This library is done. We can now consume it from our client or server and it has shared DTOs that will work in both. You might wonder why I wanted that? You’ll see.
2) Adapters (converting domain to grpc and back)
One very large part of sample code I am going to heavily gloss over here are the adapters. You’ll note the DotNetCoreWCF.Logic
library (see here) which is shared to the servers and client as netstandard2.0
. Internally I use AutoMapper but intentionally expose each conversion independently such as IEmployeeAdapter
. This, of course, is so that I can easily later change my mind *how* conversion is performed.
If you choose to use AutoMapper like I did, the one gotcha I want to point out is taking an IEnumerable, Array, List or something similar and having it convert to the repeats
type in protobuf. Look at EmployeeProfile.cs. Note specifically the opt.UseDestinationValue()
. I Googled it, found it, added it. It works.
3) Handlers (allow reuse between WCF and gRPC)
Similar to my adapters, I wanted to make sure the gRPC layer was completely isolated/separated from the business logic. I went ahead and created some handlers in a new service library called DotNetCoreWCF.Service.Core
(see here). Each method that the services expose (Get, Delete, Update) are now represented by a single class and interfaces. I also moved the IEmployeeStore
here.
4) Create the gRPC server
Here’s where things start to get tricky. Before we jump to the dual-purposed server (WCF and gRPC), let’s create a standalone gRPC host. Go ahead and create a new “gRPC Service” from the template in Visual Studio. Next, delete the Protos
folder. Now it’s time to reference our shared library and hook it up. One major gotcha *you need to understand* is that the gRPC service will be registered as a singleton. You heard that. If you have anything scoped you need to make sure you handle it appropriately (aka, child scope within your method calls).
Ok, with that in mind, let’s hook up our employee service. Let’s create a new class and called it EmployeeServiceHost
. If you look at the GreeterService
for an example you’ll see we need to inherit it from our generated proto server. In this case, it’ll be EmployeeService.EmployeeServiceBase
. Seeing as this will be a singleton I guess we’re going to have to do a little dirty coding, eh? Let’s inject IServiceProvider
into the constructor. We’ll need to override each of the methods from our base. See the completed file here.
Go into the Startup.cs and then map up the endpoint endpoints.MapGrpcService();
. Our host is ready to go. See the full code here.
5) Create the dual-purpose host
We’re finally ready to get to the whole reason you’re here, amirite? Let’s take the original DotNetCoreWCF.Host
console application and rename it DotNetCoreWCF.Service
. I rearranged a ton of this project in the process. Some key changes:
- Added IHostBuilder support (generic host) and configured both the gRPC and WCF services as IHostedService implementations.
- Continued to use Unity for it’s “easy” wrapper for WCF, plugged into the main pipeline on IHostBuilder.
- Registered the handlers and adapters for use with gRPC and WCF so they’d use a common code path.
- Fixed the ILogger registration with Unity so it’d correctly support ILogger<T>, added a tiny bit of basic logging.
The meat of this application and how it works handled through each IHostedService. Look at both GrpcServiceHost and EmployeeServiceManager (WCF) to see what I did.
Create the gRPC client
Create a new Console Application (.NET Core) using netcoreapp3.0
. Add references to Google.Protobuf
, Grpc.Net.Client
, Grpc.Net.ClientFactory
, and Grpc.Tools
. Next, you’re really going to want to use your own domain models, aren’t you? I mean… passing around the generated DTOs might get messy, right? Let’s add a “Proxy” client shall we? Create a folder called Proxies
and then create a class in there called EmployeeClient
.
In my WCF post we created an IEmployeeService that was reused across layers. We’re going to make use of that one here. Grpc supports async pretty easily, let’s go ahead and extend the interface as IEmployeeClientAsync
like below.
public interface IEmployeeClientAsync : IEmployeeService
{
Task<EmployeeResponse> GetAsync(EmployeeRequest request);
Task<DeleteEmployeeResponse> DeleteAsync(DeleteEmployeeRequest request);
Task<Employee> UpdateAsync(Employee employee);
}
We’ll create a client that implements that interface and call it EmployeeClient
. Let’s leave it all unimplemented and come back to it. How about we transition to setting up the dependency injection now. (see the completed file here)
Client Configuration
If you’ve read previous posts you’ve likely noticed that I like keeping registration/configuration outside of my application entry. I prefer to have some sort of “Configuration” class or set of classes. Let’s create a “Configuration” folder here in the client and then add EmployeeClientConfiguration
static class to it. For the context of this demo today I don’t want to go into the security configuration aspect of gRPC. That means I’m going to do something special here to *remove* the TLS security. See file here.
The AppContext.SetSwitch is only important for this demo. It instructs somewhere in the bowels of the gRPC/HttpClient Http2 support that we’re going to use an unencrypted channel. Don’t do this in real life people! The only reason this line of code is here at all is because when I hook it up in the WCF host I didn’t want to spend the time generating and linking a certificate manually.
Another thing to note that is different here than the Microsoft example is that I have made these clients injectable. This is done via the Grpc.Net.ClientFactory
nuget package which isn’t included in their sample. We’re going to inject the gRPC client into our own EmployeeClient
we created above. Again, see completed file.
Seeing the fruits of our labor
We now have a fully functional service host that can run both WCF and gRPC at the same time. With just a tiny bit of changes we can make it work as a Windows Service as well. This is swell.
I didn’t include the screenshot from the DotNetCoreWCF.Client
and DotNetCoreWCF.FullClient
since I didn’t modify anything on those but they do still work. Feel free to reference the previous post to read up on those.
Making it better
There are a few things I left out of this application. First and foremost this solution is completely devoid of unit testing. That’s a pretty big no-no. On the bright side, I did make everything use interfaces from the top down so it wouldn’t be difficult to unit test this application.
Next, I left out SSL support from the gRPC host. That too is a no-no. The template from Visual Studio builds upon the new GenericHost
from netcoreapp3.0
/netstandard2.1
which, in turn, adds a default SSL certificate on Kestrel. Since we circumvented that and added our Grpc.Core.Server
manually we didn’t get to enjoy that benefit. Here is a StackOverflow answer that talks about implementing that.
Conclusion
Today we walked through an existing WCF service application and added gRPC support. We showed a “graceful” pathway for migrating WCF to gRPC using .NET Core while maintaining backwards compatibility.
gRPC seems to me the logical replacement for WCF services and I’m personally looking forward to working more with them.
Additional Reading
If you’re a die-hard WCF fan and you want to see it carried successfully into the new .NET world, I suggest you contribute to the Core WCF project. According to this post by Scott Hunter (Microsoft), it sounds like that’s likely the only way you’re going to continue using it long-term on newer platforms.
Credits
Photo by Gilles Rolland-Monnet on Unsplash
since i wanna to use grpc with jwt ,and the connection will be modified when the token has been empired,is there any better way to reuse the connection rather than use a static connection?
Xiyuan,
Unlike the WCF version, the Grpc connection isn’t static (singleton) for the Grpc Client example I’ve given. It uses the HttpClientFactory under the hood to instantiate an instance of it and at least in the case of my example I have the dependency injected as a transient. You would be able to inject your JWT token at the call level. See https://docs.microsoft.com/en-us/aspnet/core/grpc/authn-and-authz?view=aspnetcore-3.0 and how they use the Headers to add JWT tokens.
Let me know if that makes sense?
Jon
in my client project i do it like these code below:
1、i define an IConnectMonitorService used for keep a polling request with server,and get the lastest token from server,then store with a global variate named as accessToken;
2、i wanna to use insecurity connect and the server privode http (not the http2.0 with tls),so the server code was writen this:
—-begin my code—
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, 4000);
});
—end my code—
3、i wanna to inject the GrpcClient,but because i used the http connection,so i need to create the grpcClient like this:
—begin my code–
var httpClient = new HttpClient(httpclientHandler);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“Bearer”, accessToken);
Channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpClient = httpClient
});
—end my code—
4、but it seems the code below cannot use the httpClient to inject the TestClient.
—begin my code
serviceCollection.AddGrpcClient();
—end my code
5、so when i wanna to call the grpc client sub,i had to do it like this
—begin my code
HttpClient httpClient = CreateHttpClient(accessToken);
var Channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpClient = httpClient
});
new TestClient(Channel).SayHello();
—end my code
so could you give me any recommendations?
thank u very much.
thanks for ur recommendations for “You would be able to inject your JWT token at the call level.” i had realize it now by do this:
1、Define DelegatingHandler named as JwtTokenHeader which is used for set Authorizationrequest header;
2、When Start the Client and Create a Transient
.AddTransient();
3、Use the GrpcClientFactory and AddGrpcClient like this:
—–begin my code—–
serviceCollection.AddGrpcClient(
o =>
{
o.Address = new Uri(configuration[“address”]);
})
.AddHttpMessageHandler();
—–end my code—–
Xiyuan,
Sorry I was unable to respond to your previous message before you found this solution! I’m very glad you were able to find a solution that works and thank you for sharing it with us.
Jon
I just kown little about .NET Core,so thank you very much for your help,i have gained a lot from it.