-
Notifications
You must be signed in to change notification settings - Fork 33
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 intercept on decorator #55
base: develop
Are you sure you want to change the base?
Conversation
Created a new ProxyHelpers class to improve code structure and readability. This class centralizes proxy operations that were previously distributed across multiple classes. Refactored RegistrationExtensions.cs to replace direct method calls with references to the newly created ProxyHelpers.
A description on the PR here, possibly explaining it and linking back to the source issue, would be good. That way in a year from now when we come back and look to see what the point of the PR was (say, during supporting an issue) we have that to reference. (Update the actual PR description; don't respond with the description here as a comment. Thanks!) |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #55 +/- ##
===========================================
+ Coverage 93.44% 95.31% +1.86%
===========================================
Files 1 3 +2
Lines 122 128 +6
Branches 22 22
===========================================
+ Hits 114 122 +8
+ Misses 4 3 -1
+ Partials 4 3 -1 ☔ View full report in Codecov by Sentry. |
From what I gather, this PR needs to go through first to help this one. I probably won't look at this one until that one is done. |
DelegateMiddleware class is copied from autofac, after autofac add the overload method, remove this. Additionally, ServiceMiddlewareRegistrationExtensions has been added to provide static methods for registering middleware services. Tests have been provided to ensure correct interception of public interfaces. The Moq testing library has been added as a package reference.
The new changes is base on Autofac 8.2.0, any feed back let me know, I'll try to fix it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the premise is good but I'm worried about the confusing syntax to enable this to happen. We might need to iterate over that a little to get it right.
@@ -44,6 +44,7 @@ | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" /> | |||
<PackageReference Include="Moq" Version="4.20.70" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use NSubstitute here instead of Moq? I am a fan of supporting open source, but some of the phone-home stuff that was implemented leaves Moq on vulnerability lists for some secure enterprises. In places where we already had Moq, it should be pinned to the version just before that was implemented; for tests that are adding new mocking frameworks, we should go NSubstitute.
.RegisterType<Interceptable>() | ||
.InterceptedBy(typeof(StringMethodInterceptor)) | ||
.As<IPublicInterface>(); | ||
builder.EnableInterfaceInterceptors<IPublicInterface>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this syntax to be confusing. We already registered a type to have interceptors, but then there's another line to indicate interception.
I feel like it should be maybe more like...
builder.RegisterDecorator<T, U>().InterceptedBy(typeof(Z));
Something analogous to the current syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this syntax to be confusing. We already registered a type to have interceptors, but then there's another line to indicate interception.
I feel like it should be maybe more like...
builder.RegisterDecorator<T, U>().InterceptedBy(typeof(Z));Something analogous to the current syntax.
If I change it to all in one line Is it ok? like this,
builder.RegisterType<Interceptable>().InterceptedBy(typeof(StringMethodInterceptor)).As<IPublicInterface>();
but I saw other file like InterfaceInterceptionWithPropertyInjectionFixture.cs
use
builder
.RegisterType<InterceptableWithProperty>()
.PropertiesAutowired()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor))
.As<IPublicInterface>();
should we keep same style?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing that makes the new syntax weird is that it's not connected to a RegisterType or RegisterDecorator. I think if it reads like a sentence - "register X as Y intercepted by Z" - that would be consistent and easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the bit currently pending. I'm not sure we want a syntax where it's some sort of builder level registration. I find the syntax to be challenging because there are now two ways to register things. It seems like if the interception needs to apply to a decorator, it should be attached to the decorator registration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I've only had my phone, away for the holiday season, and I'm trying to answer quickly to help but also haven't been able to really look at anything on a big monitor and provide robust feedback.
In the test highlighted here, I see the new interception syntax like this:
// Register the decorator.
builder.RegisterDecorator<Decorator, IPublicInterface>();
// Register a type that needs to be intercepted.
builder
.RegisterType<Interceptable>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor))
.As<IPublicInterface>();
// This is the confusing/inconsistent part - this standalone interception line.
builder.EnableInterfaceInterceptors<IPublicInterface>();
The thing that's inconsistent here is that, generally speaking, the syntax for registering things is all built up and attached to one registration.
// Start a registration
builder
// Register a type `Interceptable`
.RegisterType<Interceptable>()
// I want to enable interface interception for this type
.EnableInterfaceInterceptors()
// Say what that type is intercepted by
.InterceptedBy(typeof(StringMethodInterceptor))
// Expose the type as `IPublicInterface`
.As<IPublicInterface>();
It's a chained builder style syntax. I know that when I register the interceptor it will be intercepting things for RegisterType
. I know the As
applies to that registration.
The new line here:
builder.EnableInterfaceInterceptors<IPublicInterface>();
This is inconsistent. It reads like it's enabling some sort of global interception, but if I read that in context of the other syntax...
- Why would I need
.EnableInterfaceInterceptors()
on theRegisterType<T>
call if I'm supposed to use this new standalonebuilder.EnableInterfaceInterceptors<T>()
syntax? - What happens if I drop
.EnableInterfaceInterceptors()
on theRegisterType<T>
call and only use the new standalone syntax? - I'm enabling interface interception, but by what? What's intercepting it? How do I control that?
I know that you can probably answer that here in comments, but that's not the point - the point is that someone later reading the code or trying to figure out how to make interception work is going to have these questions. The syntax shouldn't be confusing like that or cause folks to wonder what it means.
That's what I'm stuck on here.
If the goal of this PR is to allow interface interception on decorators, I would expect to see something more like:
[Fact]
public void InterceptsPublicInterfacesUseGenericMethod()
{
ContainerBuilder builder = new();
builder.RegisterType<StringMethodInterceptor>();
// Same support for adding interception to a decorator
// that we have for existing registrations. Familiar, consistent
// syntax.
builder
.RegisterDecorator<Decorator, IPublicInterface>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor);
// Same syntax as before.
builder
.RegisterType<Interceptable>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor))
.As<IPublicInterface>();
// ... and the rest of the test
}
Perhaps a good test to verify the syntax works is to see if we can get the decorator to be intercepted by a different interceptor than the thing being decorated.
[Fact]
public void DifferentInterceptorsForEachComponent()
{
ContainerBuilder builder = new();
builder.RegisterType<StringMethodInterceptor>();
builder.RegisterType<CallCountInterceptor>();
// Decorator gets intercepted by one thing
builder
.RegisterDecorator<Decorator, IPublicInterface>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(CallCountInterceptor);
// Base service gets intercepted by something else
builder
.RegisterType<Interceptable>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(StringMethodInterceptor))
.As<IPublicInterface>();
// ... and the rest of the test
}
There are other features, too, that need to work the same if we add this.
- Being able to add proxy generation options when enabling interception for the decorator.
- Being able to mark the decorator with an attribute to select the interceptor.
- Possibly needing to differentiate between class and interface interceptors - can the class being decorated have
EnableClassInterceptors
but the decorator hasEnableInterfaceInterceptors
? What about the other way around?
That's the syntax and functionality that I'm stuck on, and it's why the standalone builder.EnableInterfaceInterceptors<T>()
doesn't really work in the ecosystem.
Replaced Moq with NSubstitute in the test project to handle mocking. Change RegisterType style in test.
Removed the use of NSubstitute from the test project and simplified the testing structure. Adjusted assertions to account for the modified interceptor and decorator behavior, ensuring accurate validation of method outputs. Fix ServiceMiddlewareInterfaceInterceptorsFixture test cases
Although all checks have passed, I still have a few questions about the code.
Actually, this code lacks code coverage. My question is regarding line 52: after calling EnsureInterfaceInterceptionApplies, can we confirm that proxiedInterfaces will never be empty? if yes, I think we can remove line 61 to 64.
ProxyHelpers line 127, this code lacks code coverage too, I just worry about may be this code will never reach too.
|
Re: Line 61 - the only difference I see is that when we check to see if interface interception applies, we're checking the service registration type, while when we check to see if there are any proxied interfaces, we're checking the actual instance type. If memory serves, there was something with WCF proxy interception where we might end up with a service registration of, say, It's just my memory, though, I can't point you to a specific test or issue that indicated this. There was a lot with WCF proxies that wasn't awesome. I'm glad we don't have that so much in .NET Core but we still do support .NET Desktop. If I had to guess, the way to test this would be to try creating a registration that has an event handler that replaces the instance with something like a TransparentProxy: builder.RegisterType<MyType>()
.As<IMyInterface>()
.OnActivating(e => e.ReplaceInstance(new TransparentProxy())); Something like that - the idea being the instance doesn't actually implement the interfaces the way we expect. I don't remember the exact syntax off the top of my head. I won't hold up the PR on that. Some of these runtime things can be hard to replicate. Re: Line 127 - I think you're right, the implementation type will only ever be a class so that line should never practically be hit. Nice catch! Minor point - we probably want to make that |
Refactored `ProxyHelpers` to be `internal` for better encapsulation. Fixed a typo in exception documentation and removed unreachable code in `GetInterceptorServicesFromAttributes()`. Added `InternalsVisibleTo` for unit testing support.
I already change ProxyHelpers to internal and remove line 127 and related code. So may be this is not the case. |
Well, like I said, I can't remember the exact syntax for Don't worry about coverage on that one line. I'm a little bit afraid of removing it, though, since, again, I have a vague memory from many years ago where we had to add that for something. |
ok, nice, can you approve the pull request or any comment on it? |
The comment on the syntax is what's holding this up. |
the RegisterDecorator mehod is from autofac main project and currently no return value. so it can not be chained. |
Add support for register service with Interceptor and multiple decorator, apply Interceptor to the outermost decorator,