Search code examples
c#windows-servicesgrpc

How create Windows Service from VS 2022 created gRPC server?


I've created a gRPC server in Visual Studio 2022 Community Preview by selecting the "ASP NET Core gRPC Service" template and .Net 6 Core. I intend to replace four existing .Net Framework Windows services who are all using WCF. So, I'm not looking for an alternative on how to create a Windows service.

The code generated from VS 2022 creates a program.cs (sans comments) that looks like:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<GreeterService>();
app.MapGet("/", () => "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");
app.Run();

Every example I can find does not match this program.cs's contents. Additionally, all the examples include a generated startup.cs file. My project did not create a startup.cs file. All the examples show including the NuGet package Microsoft.Extensions.Hosting.WindowsServices and adding a UseWindowsServices parameter.

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    ...

I don't have a Host or a CreateDefaultBuilder method. I tried adding the line:

builder.Host.UseWindowsService();

The program compiles and works perfectly when running in VS or the command line. I can see the ports with netstat:

netstat -an | find "6276"
C:\Users\Steve>netstat -an | find "6276"
  TCP    127.0.0.1:6276         0.0.0.0:0              LISTENING
  TCP    [::1]:6276             [::]:0                 LISTENING

But when I run it as a Windows Service, it is not listening on the identified port.

netstat -an | find "6276"
C:\Users\Steve>

I tried .Net 6.0 and .Net 7.0 preview 7, checking and unchecking "Do not use top level statements" on the later. No change in the behavior.

So, apparently Visual Studio changed the template output for gRPC and nobody has created a Windows Service with it yet... or at least has not shown how it was done.

Does anyone know how to take the latest gRPC template and create a Windows Service from it?


Solution

  • Here is an example of the minimal application created by the default template with a few modifications (see code comments)

    The project file

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    
      <ItemGroup>
        <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
      </ItemGroup>
    
      <ItemGroup>
        <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
        <!-- Install additional packages -->
        <PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.40.0" />
        <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
      </ItemGroup>
    
    </Project>
    

    The Program.cs

    using GrpcService1.Services;
    using Microsoft.Extensions.Hosting.WindowsServices;
    
    // Use WebApplicationOptions to set the ContentRootPath
    var builder = WebApplication.CreateBuilder(new WebApplicationOptions
    {
        Args = args,
        ContentRootPath = WindowsServiceHelpers.IsWindowsService()
            ? AppContext.BaseDirectory
            : default
    });
    
    // Set WindowsServiceLifetime
    builder.Host.UseWindowsService();
    
    builder.Services.AddGrpc();
    
    // Add reflection services
    builder.Services.AddGrpcReflection();
    
    var app = builder.Build();
    
    app.MapGrpcService<GreeterService>();
    
    // Map reflection endpoint
    app.MapGrpcReflectionService();
    
    app.Run();
    

    Now open cmd in the project folder and execute

    dotnet publish
    

    The publish command will produce the exe file (I assume you are working on a Windows machine, otherwise how would you test Windows Service? 😁). On my machine the path to exe is

    "C:\github\junk\GrpcService1\bin\Debug\net6.0\publish\GrpcService1.exe"
    

    Now open cmd as Administrator and run the following command

    sc.exe create "GrpcService" binpath="C:\github\junk\GrpcService1\bin\Debug\net6.0\publish\GrpcService1.exe"
    

    Open Services and start the GrpcService. Now install and run grpcui tool:

    grpcui -plaintext localhost:5000
    

    The grpcui tool will open the UI where you should be able to see the Greeter serviceenter image description here

    Notes:

    • I Used WebApplication.CreateBuilder(new WebApplicationOptions()) because without that the service won't start. The Windows Event Viewer shows the error:

    Description: The process was terminated due to an unhandled exception. Exception Info: System.NotSupportedException: The content root changed from "C:\Windows\system32" to "C:\github\junk\GrpcService1\bin\Debug\net6.0\publish". Changing the host configuration using WebApplicationBuilder.Host is not supported. Use WebApplication.CreateBuilder(WebApplicationOptions) instead.

    Found the solution here.

    • By default, the application will start on port 5000. If you wish to use another port add --urls argument when creating the service
    sc.exe create "GrpcService" binpath="C:\github\junk\GrpcService1\bin\Debug\net6.0\publish\GrpcService1.exe --urls \"http://localhost:6276\""