Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for configuring a custom servicebinder using fluent methods (#121) #130

Closed
wants to merge 2 commits into from

Conversation

Euan-McVie
Copy link
Contributor

@Euan-McVie Euan-McVie commented Sep 21, 2020

@mgravell
As per discussion on #121 i've prepared a PR based on moving the metadata lookup to the service binder.
I've split into 3 commits to make it easier to see the actual implementation vs an update to your example to test the scenario.

Note that I moved the entire GetMetadata as calling a separate GetMetadata(MethodInfo methodInfo) is too late as the MethodInfo for the service type would already be incorrect in my use case.
We could split into seperate GetContractMetadata() or GetServiceMetaData() overrides, but the method ones need the service type for me to resolve before getting the methodInfo so I didn't think it was worth it if I could resolve the ServiceType before calling the base implementation.

If you want to see it with the existing behaviour simply comment out the .WithBinderConfiguration line from the Startup.cs and it will then start ignoring the [Authorize] attribute in the counter service.

Let me know if you need any changes or want the test example changes dropped from the PR.

Thanks
Euan

@Euan-McVie
Copy link
Contributor Author

Hi @mgravell,
Any chance of a review for what I need to change in order to get this to an acceptable state?
Thanks

@@ -17,7 +18,7 @@ public static class ServicesExtensions
/// <summary>
/// Registers a provider that can recognize and handle code-first services
/// </summary>
public static void AddCodeFirstGrpc(this IServiceCollection services) => AddCodeFirstGrpc(services, null);
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services) => AddCodeFirstGrpc(services, null);
Copy link
Member

@mgravell mgravell Oct 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that this represents a hard break for semver purposes; I wonder whether it is possible to do instead:

// note no "this", so it is no longer an extension method and will not be selected by new builds
public static void AddCodeFirstGrpc(IServiceCollection services) => AddCodeFirstGrpc(services, null);
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services, Action<BinderOptions>? binderOptions = null)
{...}

This avoids a hard break but gives you the same results.

Copy link
Contributor Author

@Euan-McVie Euan-McVie Oct 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the preference for when we also need the other override for GrpcServiceOptions? It's why I added as an extension instead of an override to AddCodeFirstGrpc
I could either add as an extra field to the BinderOptions(name change to CodeFirstGrpcConfiguration) or would you prefer dual options, Not sure I like adding it as an extra field as it would be added as an Action<GrpcServiceOptions> property as the underlying Configure is passed the action to lazy evaluate, which is slightly different to the eager evaluation of the CodeFirstGrpcConfiguration

//Existing + suggested override
public static void AddCodeFirstGrpc(IServiceCollection services) => AddCodeFirstGrpc(services, null);
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services, Action<GrpcServiceOptions>? configureOptions);
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services, Action<CodeFirstGrpcConfiguration>? grpcConfiguration = null);
// Extra
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services, Action<GrpcServiceOptions>? configureOptions, Action<CodeFirstGrpcConfiguration>? grpcConfiguration);

// Or just switch to non action/option/config style
public static void AddCodeFirstGrpc(IServiceCollection services) => AddCodeFirstGrpc(services, null);
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services, Action<GrpcServiceOptions>? configureOptions);
public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection services, Action<BinderConfiguration>? binderConfiguration = null);
// Add Create override to to allow not setting a marshaller.
services.AddCodeFirstGrpc(() => BinderConfiguration.Create(new MyServiceBinder));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think my preference would be just to get rid of my BinderOptions/CodeFirstGrpcConfiguration
and go with an extra override to Create to remove the null marshaller requirement.

@@ -30,6 +31,21 @@ public static IGrpcServerBuilder AddCodeFirstGrpc(this IServiceCollection servic
return builder;
}

public static IGrpcServerBuilder WithBinderConfiguration(this IGrpcServerBuilder builder, Action<BinderOptions> binderOptions)
{
var options = new BinderOptions();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that as I understand it (I'm not a DI expect), this not the normal pattern for "option" scenarios; not sure whether that matters much - this will probably work, although it may have atypical timing in the DI pipe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'll switch to your AddCodeFirstGrpc suggestion as it fixes both issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not an options pattern. but it is fairly common when the config object passed in is not just an options object to be registered, but is actually used inside to configure how things are registered with DI.
I was going to name it BinderConfiguration, but that is what the main one is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note it doesn't affect the DI pipeline either as everything is still registered/created explicitly as I invoke the BinderOptions() so it is all in place before the serviceprovider is built and requests made of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if you were meaning that I should have been wrapping in a "value factory"

builder.Services.TryAddSingleton(() => BinderConfiguration.Create(options.MarshallerFactories, options.ServiceBinder));

then I agree.


public sealed class BinderOptions
{
public IList<MarshallerFactory>? MarshallerFactories { get; set; } = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this is doing or why it exists; PR doesn't mention it; what is the goal here? how is this different to the ambient marshaller setup that can be injected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just a wrapper as it felt a little odd explicitly listing null for the marshallers
i.e.

config => 
{
  config.ServiceBinder = new MyServiceBinder()
}

instead of just passing in

BinderConfiguration.Create(null, new MyServiceBinder)

return builder;
}

public sealed class BinderOptions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the public API should have intellisense; if this didn't raise a warning, that's a bug that I should fix

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was building in Debug so it wasn't flagging them locally and I didn't review CI beyond seeing it passed. Maybe TreatWarningsAsErrors should/could be turned on?

@Euan-McVie
Copy link
Contributor Author

@mgravell - Thanks for the review.
I've commented on a few bits.
Is the general approach for the ServiceBinder changes themselves ok. If so I might move the commit to a seperate PR as the fluent stuff is just nice to have so can continue discussions with yourself without holding up the functional change.

Thanks

@Euan-McVie
Copy link
Contributor Author

@mgravell - I still intend to tidy this up a bit further as per some of your comments as I would still prefer a cleaner fluent style of setting a custom ServiceBinder, but have moved the ServiceBinder changes themselves to a seperate PR #138.

@Euan-McVie Euan-McVie changed the title Add support for defining a custom metadata lookup in the servicebinder (#121) Add support for configuring a custom servicebinder using fluent methods (#121) Nov 10, 2020
@Euan-McVie Euan-McVie closed this Dec 15, 2020
@Euan-McVie Euan-McVie deleted the ISSUE-121 branch December 15, 2020 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants