csharp-language-server-protocol icon indicating copy to clipboard operation
csharp-language-server-protocol copied to clipboard

JSON RPC documentation or example app request

Open panayot-cankov opened this issue 4 years ago • 3 comments

Would it be possible to add to the documentation or as an example app something like:

using MediatR;
using Microsoft.Extensions.Hosting;
using OmniSharp.Extensions.JsonRpc;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static Task Main(string[] args) =>
            new HostBuilder()
                .ConfigureServices((context, services) =>
                    services.AddJsonRpcServer(options =>
                        options
                            .WithInput(Console.OpenStandardInput())
                            .WithOutput(Console.OpenStandardOutput())
                            .WithHandler<PingHandler>()))
                .Build()
                .RunAsync();

        class PingHandler : IJsonRpcRequestHandler<PingHandler.PingRequest, PingHandler.PongResponse>
        {
            [Method("ping", Direction.ClientToServer)]
            public Task<PongResponse> Handle(PingRequest request, CancellationToken cancellationToken)
            {
                return Task.FromResult(new PongResponse
                {
                    pong = request.ping + 1
                });
            }

            public class PongResponse
            {
                public int pong;
            }

            public class PingRequest : IRequest<PongResponse>
            {
                public int ping;
            }
        }
    }
}

It requres the nuget packages:

Microsoft.Extensions.Hosting
OmniSharp.Extensions.JsonRpc

When run, it should be able to read from the terminal something like:

{ "jsonrpc":"2.0", "id":2, "method": "ping", "params": { "ping": 41 }}

and throw out a { pong: 42} response.

It currently doesn't work, it never creates the json rpc service really, I will be checking now the source code of the tests, but if there was a minimal working example app, it'd get me going much faster, it'd probably help with the adoption of this tool as well.

panayot-cankov avatar Sep 14 '21 06:09 panayot-cankov

Ok, this seems to work, I just have a gut-feeling it is not very correct. I am not that confident in the way it await at the end, and if there is a better way to initialize the JsonRpcServer:

using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OmniSharp.Extensions.JsonRpc;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var cts = new CancellationTokenSource();

            var host = new HostBuilder()
                .ConfigureServices((context, services) =>
                    services.AddJsonRpcServer(options =>
                        options
                            .WithInput(Console.OpenStandardInput())
                            .WithOutput(Console.OpenStandardOutput())
                            .WithHandler<PingHandler>()))
                .Build();

            var jsonRPCServer = host.Services.GetRequiredService<JsonRpcServer>();
            await jsonRPCServer.Initialize(cts.Token);
            await host.RunAsync(cts.Token);
        }

        [Method("ping", Direction.ClientToServer)]
        class PingHandler : IJsonRpcRequestHandler<PingHandler.PingRequest, PingHandler.PongResponse>
        {
            public Task<PongResponse> Handle(PingRequest request, CancellationToken cancellationToken)
            {
                return Task.FromResult(new PongResponse
                {
                    pong = request.ping + 1
                });
            }

            public class PongResponse
            {
                public int pong;
            }

            public class PingRequest : IRequest<PongResponse>
            {
                public int ping;
            }
        }
    }
}

And the actual input you have to paste through the terminal to get a response and hit a breakpoint inside:

Content-Length: 61

{"jsonrpc":"2.0","id":1,"method":"ping","params":{"ping":15}}

panayot-cankov avatar Sep 14 '21 06:09 panayot-cankov

Works well with the following vscode-jsonrpc TypeScript:

import * as cp from "child_process";
import * as rpc from "vscode-jsonrpc/node";

// ... some code ommited here
// dotnetPath is path like: C:\Program Files\dotnet\dotnet.exe
// args is an array with a single string element - a path to a framework-dependent .dll
// with a console app from the above snippets

console.log(`Start .NET host...`);
const childProcess = cp.spawn(dotnetPath, args);

const connection = rpc.createMessageConnection(
    new rpc.StreamMessageReader(childProcess.stdout),
    new rpc.StreamMessageWriter(childProcess.stdin)
);

console.log(`Initialize .NET host comms...`);

let ping = new rpc.RequestType<{ ping: number }, { pong: number}, Error>("ping");

console.log(`.NET host listen...`);
connection.listen();

console.log(`Ping? 2`);
var response = await connection.sendRequest(ping, { ping: 2 });
this.log(`Pong! ${response.pong}`);

panayot-cankov avatar Sep 14 '21 07:09 panayot-cankov

Thanks for your code. It is very useful for me. I initialized JsonRpcServer by using IHostedService and it worked.


using MediatR;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using OmniSharp.Extensions.JsonRpc;

var builder = Host.CreateApplicationBuilder();
builder.Services.AddHostedService<Initializer>();
builder.Services.AddJsonRpcServer(options =>
            options
                .WithInput(Console.OpenStandardInput())
                .WithOutput(Console.OpenStandardError())
                .WithHandler<PingHandler>());

var host = builder.Build();

await host.RunAsync();

class Initializer(JsonRpcServer server) : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken) => server.Initialize(cancellationToken);
    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

[Method("ping", Direction.ClientToServer)]
class PingHandler : IJsonRpcRequestHandler<PingHandler.PingRequest, PingHandler.PongResponse>
{
    public Task<PongResponse> Handle(PingRequest request, CancellationToken cancellationToken)
    {
        return Task.FromResult(new PongResponse
        {
            pong = request.ping + 1
        });
    }

    public class PongResponse
    {
        public int pong;
    }

    public class PingRequest : IRequest<PongResponse>
    {
        public int ping;
    }
}

frg2089 avatar Dec 29 '23 19:12 frg2089