Your migrated gRPC project is based on the default Microsoft grpc
project template. For this document we will use the Basic Calculator example
which is available from our
GitHub Examples repository.
Project Structure
The gRPC projects are console applications, with a Program.cs
file that
initializes and runs the service(s), and a Startup.cs
where the service is
constructed and configured. There is a Protos
folder where Protobuf
IDL
(Interface Definition Language) files are kept, and a Services
folder
containing the C# implementation code.
The layout of the BasicCalculator project looks like this:
- Properties
|- launchSettings.json
- Protos
|- basic_calculator_core.proto
- Services
|- Calculator.cs
- appsettings.Development.json
- appsettings.json
- BasicCalculatorCore.csproj
- Program.cs
- Startup.cs
The .proto File
The Protos/basic_calculator_core.proto
file is the Protobuf IDL definition of your new
gRPC service. This is used by the Protobuf compiler to generate the message
classes (the equivalent of DataContract
classes) and the base class for the
service implementation.
The Service
The Services/Calculator.cs
contains the implementation of the gRPC service. This
is effectively a facade over your service contract implementation from the original
WCF application, with logging and error handling added. The implementation is
registered with the .NET Core Dependency Injection service in Startup.cs
, so it
can be retrieved from the constructor.
This class is declared partial
so you can add modifications to it without changing
the generated file.
public partial class Calculator : global::BasicCalculatorCore.Protos.Calculator.CalculatorBase
{
private readonly Microsoft.Samples.GettingStarted.ICalculator _serviceContract;
private readonly ILogger<Calculator> _logger;
public Calculator(Microsoft.Samples.GettingStarted.ICalculator serviceContract, ILogger<Calculator> logger)
{
_serviceContract = serviceContract;
_logger = logger;
}
public override Task<Protos.AddResponse> Add(Protos.AddRequest request, ServerCallContext context)
{
try
{
var returnValue = _serviceContract.Add(request.Value1, request.Value2);
var response = new Protos.AddResponse { Value = returnValue };
return Task.FromResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error invoking Add");
throw new RpcException(new Status(StatusCode.Internal, ex.Message));
}
}
// Other methods elided
}
Note that in this example, the original contract methods were not asynchronous so
Task.FromResult
is used to return the response. The methods in the generated gRPC
service base class are all declared with Task<Response>
as the return type by default.
If your original service methods are working with any kind of IO, such as a database,
the file system, or other services, and they are not using async/await
and Task
s,
consider changing them in the original code before running the migration. WCF works
happily with async
methods.
Startup.cs
The Startup
class is where your .NET Core gRPC application is configured. If you
want to add authentication/authorization, specific logging libraries (like
Loupe) or any other components or middleware,
this is where you do it.
In the generated Startup
class, the original service contract implementation
is registered with Dependency Injection in the ConfigureServices
method,
as shown here:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddScoped<Microsoft.Samples.GettingStarted.ICalculator,
Microsoft.Samples.GettingStarted.CalculatorService>();
}
If your original application used a [ServiceContract]
interface, then it will
be registered using that interface as the dependency. If it was directly on the
class, the class will be registered as the dependency. If you need to configure
more dependencies to support the creation of the service contract class, you should
add them here.
Using the AddScoped
method means a new instance of the class will be created
for every incoming request.
The service itself is registered with the application in the Configure
method,
using endpoints.MapGrpcService
in the app.UseEndpoints
callback, as shown here:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<Services.Calculator>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
Again, if you are adding authentication/authorization to your new application, those
calls (e.g. app.UseAuthentication
and app.UseAuthorization
) should go between
app.UseRouting
and app.UseEndpoints
.
See Also
- gRPC for WCF Developers (by Mark Rendle, creator of Visual ReCode)
- Introduction to gRPC on .NET Core
- App startup in ASP.NET Core
- Overview of ASP.NET Core Security