Skip to content

Commit 03d7b65

Browse files
Custom handlers not override built in handlers correctly (#392)
* Fixed a bug in configuration (configuration values were case-sensitive). Added ability to disable default handlers for language client / language server. Added unit tests demonstrating Configuration.Binder, and Options usages * fixed an issue where the MedatR handler was being registered in the container for 'built-in' handlers, these were overriding any custom ones that were added later * added default request handler, and decorator to ensure that if a IRequestContext is given, then that handler is used. * removed some changes that are no longer needed * Removed extra dependencies, we can add these at another time * cleanup * removed unneeded code * Added additional assertions
1 parent 8b88655 commit 03d7b65

20 files changed

+600
-42
lines changed

Directory.Build.targets

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
<PackageReference Update="Microsoft.Extensions.Logging" Version="2.0.0" />
1818
<PackageReference Update="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
1919
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.0.0" />
20+
<PackageReference Update="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
21+
<PackageReference Update="Microsoft.Extensions.Options" Version="2.0.0" />
22+
<PackageReference Update="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
2023
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
2124
<PackageReference Update="Newtonsoft.Json" Version="11.0.2" />
2225
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.7.1" />
@@ -45,4 +48,4 @@
4548
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" />
4649
<PackageReference Update="DryIoc.Internal" Version="4.5.0" />
4750
</ItemGroup>
48-
</Project>
51+
</Project>

src/Client/LanguageClientOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public LanguageClientOptions()
1818
{
1919
WithAssemblies(typeof(LanguageClientOptions).Assembly, typeof(LspRequestRouter).Assembly);
2020
}
21+
2122
public ClientCapabilities ClientCapabilities { get; set; } = new ClientCapabilities {
2223
Experimental = new Dictionary<string, JToken>(),
2324
Window = new WindowClientCapabilities(),
@@ -52,7 +53,8 @@ ILanguageClientRegistry IJsonRpcHandlerRegistry<ILanguageClientRegistry>.AddHand
5253
ILanguageClientRegistry IJsonRpcHandlerRegistry<ILanguageClientRegistry>.AddHandler(JsonRpcHandlerFactory handlerFunc, JsonRpcHandlerOptions? options) =>
5354
AddHandler(handlerFunc, options);
5455

55-
ILanguageClientRegistry IJsonRpcHandlerRegistry<ILanguageClientRegistry>.AddHandler(IJsonRpcHandler handler, JsonRpcHandlerOptions? options) => AddHandler(handler, options);
56+
ILanguageClientRegistry IJsonRpcHandlerRegistry<ILanguageClientRegistry>.AddHandler(IJsonRpcHandler handler, JsonRpcHandlerOptions? options) =>
57+
AddHandler(handler, options);
5658

5759
ILanguageClientRegistry IJsonRpcHandlerRegistry<ILanguageClientRegistry>.AddHandler<TTHandler>(JsonRpcHandlerOptions? options) => AddHandler<TTHandler>(options);
5860

src/Client/LanguageClientServiceCollectionExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Reflection;
55
using DryIoc;
6+
using MediatR;
67
using Microsoft.Extensions.Configuration;
78
using Microsoft.Extensions.DependencyInjection;
89
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -28,7 +29,7 @@ internal static IContainer AddLanguageClientInternals(this IContainer container,
2829
nonPublicServiceTypes: true,
2930
ifAlreadyRegistered: IfAlreadyRegistered.Keep
3031
);
31-
if (!EqualityComparer<OnUnhandledExceptionHandler?>.Default.Equals( options.OnUnhandledException, default))
32+
if (!EqualityComparer<OnUnhandledExceptionHandler?>.Default.Equals(options.OnUnhandledException, default))
3233
{
3334
container.RegisterInstance(options.OnUnhandledException);
3435
}
@@ -79,7 +80,7 @@ internal static IContainer AddLanguageClientInternals(this IContainer container,
7980

8081
if (providedConfiguration != null)
8182
{
82-
builder.CustomAddConfiguration((providedConfiguration.ImplementationInstance as IConfiguration)!);
83+
builder.CustomAddConfiguration(( providedConfiguration.ImplementationInstance as IConfiguration )!);
8384
}
8485

8586
//var didChangeConfigurationProvider = _.GetRequiredService<DidChangeConfigurationProvider>();

src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,9 @@ MemberDeclarationSyntax MakeAction(TypeSyntax syntax)
171171
yield return MakeAction(CreateAsyncAction(true, requestType));
172172
if (capability != null)
173173
{
174-
yield return MakeAction(CreateAction(requestType, capability));
175-
yield return MakeAction(CreateAsyncAction(requestType, capability));
174+
method = method.WithExpressionBody(
175+
GetNotificationCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, capability)
176+
);
176177
yield return MakeAction(CreateAction(requestType, capability));
177178
yield return MakeAction(CreateAsyncAction(requestType, capability));
178179
}

src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.IO.Pipelines;
33
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
46
using DryIoc;
57
using MediatR;
68
using Microsoft.Extensions.DependencyInjection;
@@ -79,23 +81,51 @@ internal static IContainer AddJsonRpcMediatR(this IContainer container)
7981
container.RegisterMany(new[] { typeof(IMediator).GetAssembly() }, Registrator.Interfaces, Reuse.ScopedOrSingleton);
8082
container.RegisterMany<RequestContext>(Reuse.Scoped);
8183
container.RegisterDelegate<ServiceFactory>(context => context.Resolve, Reuse.ScopedOrSingleton);
84+
container.Register(typeof(IRequestHandler<,>), typeof(RequestHandler<,>));
85+
container.Register(typeof(IRequestHandler<,>), typeof(RequestHandlerDecorator<,>), setup: Setup.Decorator);
86+
87+
return container;
88+
}
89+
90+
class RequestHandler<T, R> : IRequestHandler<T, R> where T : IRequest<R>
91+
{
92+
private readonly IRequestContext _requestContext;
93+
94+
public RequestHandler(IRequestContext requestContext)
95+
{
96+
_requestContext = requestContext;
97+
}
98+
public Task<R> Handle(T request, CancellationToken cancellationToken)
99+
{
100+
return ((IRequestHandler<T, R>) _requestContext.Descriptor.Handler).Handle(request, cancellationToken);
101+
}
102+
}
103+
104+
class RequestHandlerDecorator<T, R> : IRequestHandler<T, R> where T : IRequest<R>
105+
{
106+
private readonly IRequestHandler<T, R>? _handler;
107+
private readonly IRequestContext? _requestContext;
108+
109+
public RequestHandlerDecorator(IRequestHandler<T, R>? handler = null, IRequestContext? requestContext = null)
110+
{
111+
_handler = handler;
112+
_requestContext = requestContext;
113+
}
114+
public Task<R> Handle(T request, CancellationToken cancellationToken)
115+
{
116+
if (_requestContext == null)
117+
{
118+
if (_handler == null)
119+
{
120+
throw new NotImplementedException($"No request handler was registered for type {typeof(IRequestHandler<T, R>).FullName}");
82121

83-
return container.With(
84-
rules => rules.WithUnknownServiceResolvers(
85-
request => {
86-
if (request.ServiceType.IsGenericType && typeof(IRequestHandler<,>).IsAssignableFrom(request.ServiceType.GetGenericTypeDefinition()))
87-
{
88-
var context = request.Container.Resolve<IRequestContext?>();
89-
if (context != null)
90-
{
91-
return new RegisteredInstanceFactory(context.Descriptor.Handler);
92-
}
93-
}
94-
95-
return null;
96122
}
97-
)
98-
);
123+
124+
return _handler.Handle(request, cancellationToken);
125+
}
126+
127+
return ((IRequestHandler<T, R>) _requestContext.Descriptor.Handler).Handle(request, cancellationToken);
128+
}
99129
}
100130

101131
internal static IContainer AddJsonRpcServerInternals(this IContainer container, JsonRpcServerOptions options)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#if false
2+
using System;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Options;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.Configuration.Binder;
7+
using Microsoft.Extensions.Primitives;
8+
9+
// ReSharper disable once CheckNamespace
10+
namespace Microsoft.Extensions.Configuration
11+
{
12+
public static class ConfigureByConfigurationPathExtension
13+
{
14+
/// <summary>
15+
/// Registers a injected configuration service which TOptions will bind against.
16+
/// </summary>
17+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
18+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
19+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
20+
public static IServiceCollection Configure<TOptions>(this IServiceCollection services)
21+
where TOptions : class
22+
{
23+
if (services == null)
24+
{
25+
throw new ArgumentNullException(nameof(services));
26+
}
27+
28+
return Configure<TOptions>(services, null);
29+
}
30+
31+
/// <summary>
32+
/// Registers a injected configuration service which TOptions will bind against.
33+
/// </summary>
34+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
35+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
36+
/// <param name="sectionName">The name of the options instance.</param>
37+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
38+
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? sectionName)
39+
where TOptions : class
40+
{
41+
if (services == null)
42+
{
43+
throw new ArgumentNullException(nameof(services));
44+
}
45+
46+
services.AddOptions();
47+
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(
48+
_ => new ConfigurationChangeTokenSource<TOptions>(
49+
Options.Options.DefaultName,
50+
sectionName == null ? _.GetRequiredService<IConfiguration>() : _.GetRequiredService<IConfiguration>().GetSection(sectionName)
51+
)
52+
);
53+
return services.AddSingleton<IConfigureOptions<TOptions>>(
54+
_ => new NamedConfigureFromConfigurationOptions<TOptions>(
55+
Options.Options.DefaultName,
56+
sectionName == null ? _.GetRequiredService<IConfiguration>() : _.GetRequiredService<IConfiguration>().GetSection(sectionName)
57+
)
58+
);
59+
}
60+
61+
/// <summary>
62+
/// Registers a injected configuration service which TOptions will bind against.
63+
/// </summary>
64+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
65+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
66+
/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
67+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
68+
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<BinderOptions> configureBinder)
69+
where TOptions : class
70+
{
71+
if (services == null)
72+
{
73+
throw new ArgumentNullException(nameof(services));
74+
}
75+
76+
return Configure<TOptions>(services, Options.Options.DefaultName, configureBinder);
77+
}
78+
79+
/// <summary>
80+
/// Registers a injected configuration service which TOptions will bind against.
81+
/// </summary>
82+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
83+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
84+
/// <param name="sectionName">The name of the options instance.</param>
85+
/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
86+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
87+
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string sectionName, Action<BinderOptions> configureBinder)
88+
where TOptions : class
89+
{
90+
if (services == null)
91+
{
92+
throw new ArgumentNullException(nameof(services));
93+
}
94+
95+
services.AddOptions();
96+
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(_ => new ConfigurationChangeTokenSource<TOptions>(Options.Options.DefaultName, _.GetRequiredService<IConfiguration>().GetSection(sectionName)));
97+
return services.AddSingleton<IConfigureOptions<TOptions>>(_ => new NamedConfigureFromConfigurationOptions<TOptions>(Options.Options.DefaultName, _.GetRequiredService<IConfiguration>().GetSection(sectionName), configureBinder));
98+
}
99+
100+
/// <summary>
101+
/// Registers a injected configuration service which TOptions will bind against.
102+
/// </summary>
103+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
104+
/// <param name="builder">The <see cref="OptionsBuilder&lt;TOptions&gt;"/> to configure.</param>
105+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
106+
public static OptionsBuilder<TOptions> Configure<TOptions>(this OptionsBuilder<TOptions> builder)
107+
where TOptions : class
108+
{
109+
if (builder == null)
110+
{
111+
throw new ArgumentNullException(nameof(builder));
112+
}
113+
114+
return Configure(builder, Options.Options.DefaultName);
115+
}
116+
117+
/// <summary>
118+
/// Registers a injected configuration service which TOptions will bind against.
119+
/// </summary>
120+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
121+
/// <param name="builder">The <see cref="OptionsBuilder&lt;TOptions&gt;"/> to configure.</param>
122+
/// <param name="sectionName">The name of the options instance.</param>
123+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
124+
public static OptionsBuilder<TOptions> Configure<TOptions>(this OptionsBuilder<TOptions> builder, string sectionName)
125+
where TOptions : class
126+
{
127+
if (builder == null)
128+
{
129+
throw new ArgumentNullException(nameof(builder));
130+
}
131+
132+
Configure<TOptions>(builder.Services, name);
133+
return builder;
134+
}
135+
136+
/// <summary>
137+
/// Registers a injected configuration service which TOptions will bind against.
138+
/// </summary>
139+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
140+
/// <param name="builder">The <see cref="OptionsBuilder&lt;TOptions&gt;"/> to configure.</param>
141+
/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
142+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
143+
public static OptionsBuilder<TOptions> Configure<TOptions>(this OptionsBuilder<TOptions> builder, Action<BinderOptions> configureBinder)
144+
where TOptions : class
145+
{
146+
if (builder == null)
147+
{
148+
throw new ArgumentNullException(nameof(builder));
149+
}
150+
151+
return Configure(builder, Options.Options.DefaultName, configureBinder);
152+
}
153+
154+
/// <summary>
155+
/// Registers a injected configuration service which TOptions will bind against.
156+
/// </summary>
157+
/// <typeparam name="TOptions">The type of options being configured.</typeparam>
158+
/// <param name="builder">The <see cref="OptionsBuilder&lt;TOptions&gt;"/> to configure.</param>
159+
/// <param name="sectionName">The name of the options instance.</param>
160+
/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
161+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
162+
public static OptionsBuilder<TOptions> Configure<TOptions>(this OptionsBuilder<TOptions> builder, string sectionName, Action<BinderOptions> configureBinder)
163+
where TOptions : class
164+
{
165+
if (builder == null)
166+
{
167+
throw new ArgumentNullException(nameof(builder));
168+
}
169+
170+
171+
Configure<TOptions>(builder.Services, name, configureBinder);
172+
return builder;
173+
}
174+
}
175+
}
176+
#endif

src/Protocol/LanguageProtocolDelegatingHandlers.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,17 @@ public NotificationCapability(Func<TParams, TCapability, Task> handler) :
10881088
{
10891089
}
10901090

1091+
public NotificationCapability(Action<TParams, TCapability, CancellationToken> handler) :
1092+
this(
1093+
Guid.Empty, (request, capability, ct) => {
1094+
handler(request, capability, ct);
1095+
return Task.CompletedTask;
1096+
}
1097+
)
1098+
{
1099+
}
1100+
1101+
10911102
public NotificationCapability(Func<TParams, TCapability, CancellationToken, Task> handler) :
10921103
this(Guid.Empty, handler)
10931104
{

src/Protocol/Protocol.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.Extensions.Configuration" />
12+
<!--
13+
TODO: Add these in a future version
14+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
15+
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
16+
-->
1217
</ItemGroup>
1318

1419
<ItemGroup>

src/Protocol/Workspace/IDidChangeConfigurationHandler.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Workspace
1414
[GenerateHandlerMethods]
1515
[GenerateRequestMethods(typeof(IWorkspaceLanguageClient), typeof(ILanguageClient))]
1616
public interface IDidChangeConfigurationHandler : IJsonRpcNotificationHandler<DidChangeConfigurationParams>,
17-
IRegistration<object>, ICapability<DidChangeConfigurationCapability>
17+
IRegistration<object>, // TODO: Remove this in the future
18+
ICapability<DidChangeConfigurationCapability>
1819
{
1920
}
2021

2122
public abstract class DidChangeConfigurationHandler : IDidChangeConfigurationHandler
2223
{
23-
public object GetRegistrationOptions() => new object();
2424
public abstract Task<Unit> Handle(DidChangeConfigurationParams request, CancellationToken cancellationToken);
2525
public virtual void SetCapability(DidChangeConfigurationCapability capability) => Capability = capability;
2626
protected DidChangeConfigurationCapability Capability { get; private set; } = null!;
27+
public object GetRegistrationOptions() => new object();
2728
}
2829
}

src/Protocol/Workspace/IDidChangeWorkspaceFoldersHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Workspace
1212
[Method(WorkspaceNames.DidChangeWorkspaceFolders, Direction.ClientToServer)]
1313
[GenerateHandlerMethods]
1414
[GenerateRequestMethods(typeof(IWorkspaceLanguageClient), typeof(ILanguageClient))]
15-
public interface IDidChangeWorkspaceFoldersHandler : IJsonRpcNotificationHandler<DidChangeWorkspaceFoldersParams>, IRegistration<object>
15+
public interface IDidChangeWorkspaceFoldersHandler : IJsonRpcNotificationHandler<DidChangeWorkspaceFoldersParams>,
16+
IRegistration<object> // TODO: Remove this in the future
1617
{
1718
}
1819

0 commit comments

Comments
 (0)