Skip to content

Move IFeatureCollection to new Extensions.Features assembly #32043

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

Merged
merged 4 commits into from
Apr 26, 2021

Conversation

Tratcher
Copy link
Member

@Tratcher Tratcher commented Apr 21, 2021

Contributes #31723

  • Create new project Microsoft.Extensions.Features (netstandard2.0)
  • Move IFeatureCollection, FeatureCollection etc to this assembly. Add type-forwarding from Http.Features.
  • SignalR will reference this new project. This is a breaking change for SignalR clients but it's ok since this is already broken
  • Update Microsoft.AspNetCore.Http.Features to target net6.0 and stop publishing it as a NuGet package.

This allows us to use new language features like DIMs in Http.Features without cross compiling.
#31495
#31625

@Tratcher Tratcher added the breaking-change This issue / pr will introduce a breaking change, when resolved / merged. label Apr 21, 2021
@Tratcher Tratcher added this to the 6.0-preview5 milestone Apr 21, 2021
@Tratcher Tratcher self-assigned this Apr 21, 2021
@Tratcher Tratcher marked this pull request as ready for review April 21, 2021 23:17
@Tratcher Tratcher requested review from halter73, jkotalik and a team as code owners April 21, 2021 23:17
@davidfowl
Copy link
Member

@BrennanConroy please measure signalr impact

@Tratcher
Copy link
Member Author

Tratcher commented Apr 21, 2021

@BrennanConroy please measure signalr impact

Microsoft.AspNetCore.SignalR.Client ->
Microsoft.AspNetCore.SignalR.Client.Core ->
Microsoft.AspNetCore.SignalR.Common ->
Microsoft.AspNetCore.Connections.Abstractions ->
Microsoft.Extensions.Features

What other impact are you interested in?

@davidfowl
Copy link
Member

New client on old asp.net core apps. Old client in new apps.

@halter73
Copy link
Member

halter73 commented Apr 22, 2021

I expect both will work fine. If Microsoft.Extensions.Features is loaded, the type forward takes precedence over anything else. Otherwise, the client will continue to use the old assembly. Worth checking though. Edit: Not so sure of this anymore. Definitely worth checking 😄

@halter73
Copy link
Member

halter73 commented Apr 22, 2021

SignalR will reference this new project. This is a breaking change for SignalR clients but it's ok since this is already broken

Why is changing dependencies breaking?

@davidfowl
Copy link
Member

Are we keeping Http.Features as a package targeting netstandard?

@Tratcher
Copy link
Member Author

Are we keeping Http.Features as a package targeting netstandard?

No, that's the main reason for this change, and the reason it's breaking.

@davidfowl
Copy link
Member

No, that's the main reason for this change, and the reason it's breaking.

Lets consider this then:

App (.NET 6) -> Package1 -> Http.Features 3.1 (for IFeatureCollection)
App (.NET 6) -> Package2 -> Extensions.Features 6.0 (for IFeatureCollection)

Consider App is a console app and then App as an ASP.NET Core app.

Do you get a source time type conflict?

@Tratcher
Copy link
Member Author

Keep in mind that Http.Features and Extensions.Features are also both in the shared framework and should unify to the latest version (needs verification). A standalone publish should also be able to resolve the conflict (needs verification). To really get into trouble I expect you'd need to not be using the shared framework. I'll do some experiments.

@davidfowl
Copy link
Member

I just want to explore the breaks so we can document them and anticipate what and how packages will snap

@benaadams
Copy link
Member

Create new project Microsoft.Extensions.Features (netstandard2.0)
Move IFeatureCollection, FeatureCollection etc to this assembly. Add type-forwarding from Http.Features.
...
This allows us to use new language features like DIMs in Http.Features without cross compiling.

Is a layering issue for DIMs if want to do the same thing with the FeatureCollection itself to get the same benefits as Headers?

So would maybe need to introduce a derived type IHttpFeatureCollection

IHttpFeatureCollection : IFeatureCollection
{
    IRouteValuesFeature RouteValuesFeature => Get<IRouteValuesFeature>();
    // ...
}

Which would then go viral and IFeatureCollection couldn't be used for the Http side as it wouldn't be the derived type etc

As I assume you wouldn't want to add Http DIMs to a more general non-Http IFeatures collection in Extensions?

@davidfowl
Copy link
Member

As I assume you wouldn't want to add Http DIMs to a more general non-Http IFeatures collection in Extensions?

That's correct.

@benaadams
Copy link
Member

As I assume you wouldn't want to add Http DIMs to a more general non-Http IFeatures collection in Extensions?

That's correct.

But an issue is if a IHttpFeaturesCollection then wouldn't be interopative with a IFeaturesCollection to get the benefits of DIMs without adding an is check; which would diminish the benefits of DIMs?

HttpFeatureCollection : IHttpFeatureCollection
{
    private IFeatureCollection _collection;

    public HttpFeatureCollection(IFeatureCollection collection)
    {
        _collection = collection;
    }

    IRouteValuesFeature RouteValuesFeature => 
        _collection is IHttpFeatureCollection httpCollection ? 
            httpCollection.RouteValuesFeature :  
            _collection.Get<IRouteValuesFeature>();

    // ...
}

Or do it in contructor and then null check

HttpFeatureCollection : IHttpFeatureCollection
{
    private IHttpFeatureCollection _httpCollection;
    private IFeatureCollection _collection;

    public HttpFeatureCollection(IFeatureCollection collection)
    {
        if (collection is IHttpFeatureCollection _httpCollection)
        {
            _httpCollection = collection;
        }
        else
        {
            _collection = collection;
        }
    }

    IRouteValuesFeature RouteValuesFeature => 
        _httpCollection is not null ? 
            _httpCollection.RouteValuesFeature :  
            _collection.Get<IRouteValuesFeature>();

    // ...
}

Ofc its more complicated as there are othertypes of IFeatureCollection in use; like the TransportConnection is one but it isn't part of the Http kinda stuff but is available via the Http collection, so if also wanted Transport DIMs to be usable to the Http then they would need to go on top

ITransportFeatureCollection : IFeatureCollection
{
    IConnectionLifetimeFeature ConnectionLifetimeFeature=> Get<IConnectionLifetimeFeature>();
    // ...
}

IHttpFeatureCollection : ITransportFeatureCollection
{
    IRouteValuesFeature RouteValuesFeature => Get<IRouteValuesFeature>();
    // ...
}

Then accept that Http was always over a connection (it probably is); but then that's quite a formal api heirachy using DIMs vs it being a bit more fluid currently (although is a guess if the featureCollection will resolve the type as its not guaranteeing types via contract; whereas the DIMs would be strongly suggesting it)

Just wondering if there is an opperturnity for DIMs over the IFeatureCollection itself; how that would work and what type DefaultHttpContext would be taking in is contructor (where it resolves IHttpRequestFeature, IHttpResponseFeature etc from); and then what would Kestrel's HttpProtocol be implementing?

Or would it not work?

@davidfowl
Copy link
Member

Just wondering if there is an opperturnity for DIMs over the IFeatureCollection itself; how that would work and what type DefaultHttpContext would be taking in is contructor (where it resolves IHttpRequestFeature, IHttpResponseFeature etc from); and then what would Kestrel's HttpProtocol be implementing?

I don't think it would work with the layering we want. At least, not with the way that we want to push feature collection down into the stack. It feels bad to attempt to use the same trick here. We would need to replat everything on top of a more specific one and do the type check.

@Tratcher
Copy link
Member Author

Lets consider this then:

App (.NET 6) -> Package1 -> Http.Features 3.1 (for IFeatureCollection)
App (.NET 6) -> Package2 -> Extensions.Features 6.0 (for IFeatureCollection)

Consider App is a console app and then App as an ASP.NET Core app.

Do you get a source time type conflict?

I was able to contrive a setup like this to produce the error.

Console App net6.0

  • Project reference to library1 net5.0
    • Package reference to Microsoft.AspNetCore.Http.Features 5.0.0
  • Project reference to library2 net6.0
    • Project reference (in repo) to Microsoft.Extensions.Features
Error	CS0433	The type 'FeatureCollection' exists in both 'Microsoft.AspNetCore.Http.Features, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' and 'Microsoft.Extensions.Features, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'

Interestingly I only get the error at compile time when I directly reference IFeatureCollection in the App Program.cs. If it's only referenced by type in the libraries everything compiles and runs fine with it treated as two distinct types. Then you only get compilation failures if you try to pass the IFeatureCollection from one library to the other.

Workaround: In the base App add a direct reference to Microsoft.AspNetCore.Http.Features 6.0 to lift the dependency and include the type forward. Note this works with in-repo references, we'll want to confirm it after we have a full build and real packages.

I get the same results testing a web app in-repo, but I think that's because it's not using the shared framework. I'll need to re-test after we have a full build.

I don't see anything surprising or blocking here. I'll plan to re-test after it's merged and we have a build.

@davidfowl is that enough coverage for us to proceed?

@davidfowl
Copy link
Member

Workaround: In the base App add a direct reference to Microsoft.AspNetCore.Http.Features 6.0 to lift the dependency and include the type forward. Note this works with in-repo references, we'll want to confirm it after we have a full build and real packages.

This is why I wanted to ask if we should ship a package with type forwards for 6.0 as a way to bridge the gap until 7.

@benaadams
Copy link
Member

Fair point. I wonder if the FrameworkReference will work just as well. I won't be able to test that until there's a real build.

The external Grpc tests were failing for me as they ref'd the previous framework (5.0) and the auto-update to 6.0 broke because the type moved, so a type forwards is probably recommended

@pranavkm
Copy link
Contributor

For the announcement, technically it's every API in the current package, not just the few.

@Tratcher
Copy link
Member Author

For the announcement, technically it's every API in the current package, not just the few.

@pranavkm true, but the template calls for specific links. Any suggestions for an abbreviated format to cover the others? The docs don't list APIs by assembly, only namespace, and it's not a one-to-one match.

@pranavkm
Copy link
Contributor

Yeah, I'm not super sure there's a good way to present it. @JamesNK any suggestions?

@JamesNK
Copy link
Member

JamesNK commented Apr 23, 2021

Sorry, I'm not sure what the question for me is.

Fair point. I wonder if the FrameworkReference will work just as well. I won't be able to test that until there's a real build.

The external Grpc tests were failing for me as they ref'd the previous framework (5.0) and the auto-update to 6.0 broke because the type moved, so a type forwards is probably recommended

So with the change in this PR, the package Grpc.AspNetCore will break when used with .NET 6? If so that definitely needs to be improved because the package isn't doing anything special or hacky. If Grpc.AspNetCore is broken then tons of packages will be broken. This break would be too big.

@benaadams
Copy link
Member

So with the change in this PR, the package Grpc.AspNetCore will break when used with .NET 6? If so that definitely needs to be improved because the package isn't doing anything special or hacky. If Grpc.AspNetCore is broken then tons of packages will be broken. This break would be too big.

Shouldn't do; the TypeFowarders are there, was just pointing out they were necessary

Sorry, I'm not sure what the question for me is.

Think its about the announcement? #32043 (comment)

For the announcement, technically it's every API in the current package, not just the few.

@pranavkm true, but the template calls for specific links. Any suggestions for an abbreviated format to cover the others? The docs don't list APIs by assembly, only namespace, and it's not a one-to-one match.

@davidfowl
Copy link
Member

@Tratcher that looks good. I think we should also mention the types of errors that can show up:

Error	CS0433	The type 'FeatureCollection' exists in both 'Microsoft.AspNetCore.Http.Features, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' and 'Microsoft.Extensions.Features, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'

And likely a TypeLoadException runtime error because the type moved and you're missing the reference?

@Tratcher
Copy link
Member Author

And likely a TypeLoadException runtime error because the type moved and you're missing the reference?

Updated. Actually getting a TypeLoadException would be tough, I wasn't able to trigger one in testing. If you had a nuget dependency you would at least have the old type. You'd have to manually copy your old library dll to a project that only referenced the new Extensions.Features and not the shared framework.

@Tratcher
Copy link
Member Author

Reviewers: This should be ready. We think the Code Check issue is a false positive that @JunTaoLuo will review and override as needed.

Copy link
Member

@halter73 halter73 left a comment

Choose a reason for hiding this comment

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

I imagine testing just how breaking this is will be easier after we merge it. We can adjust/revert later.

@davidfowl
Copy link
Member

I imagine testing just how breaking this is will be easier after we merge it. We can adjust/revert later.

Right, we just need to document all the things.

@@ -0,0 +1,2 @@
#nullable enable
Copy link
Member

Choose a reason for hiding this comment

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

Should PublicAPI.Shipped.txt be changing?

Copy link
Member

Choose a reason for hiding this comment

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

Why? Nothing has shipped yet in this assembly. Did you think this was src/Http/Http.Features/? I wonder if we should rename the folders. That tripped me up earlier in the review too.

Copy link
Member

Choose a reason for hiding this comment

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

Well CodeCheck agrees with you. I guess it doesn't like the file at all even if empty. Still think the folder names are confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

@JunTaoLuo? This is a new assembly and the tools failed if it didn't have this file.

Copy link
Member

Choose a reason for hiding this comment

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

Why? Nothing has shipped yet in this assembly.

That's why I was asking why it was changing

Copy link
Member Author

Choose a reason for hiding this comment

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

Note Code Check is complaining about different files because they were deleted:

error : Modified API baseline files:
error : src/Http/Http.Features/src/PublicAPI/net6.0/PublicAPI.Shipped.txt
error : src/Http/Http.Features/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt

Copy link
Contributor

Choose a reason for hiding this comment

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

Once you get the CI to green ignoring the Code Check job, let @dotnet/aspnet-build know and we'll override the required checks to squishy-merge your change.

Copy link
Contributor

Choose a reason for hiding this comment

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

From build perspective, the changes here make sense and simplify our PublicAPI files.

@@ -0,0 +1,2 @@
#nullable enable
Copy link
Contributor

Choose a reason for hiding this comment

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

From build perspective, the changes here make sense and simplify our PublicAPI files.

<ItemGroup>
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
</ItemGroup>
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@dougbu
Copy link
Contributor

dougbu commented Apr 23, 2021

the Code Check issue is a false positive

Not exactly a "false positive" but a case where the error should be overridden.

Now that I think about it, is / are the relevant PublicAPI.Shipped.txt file or files moving along with the existing assembly name❔ Should still have files containing exactly what we shipped in 5.0 even if the project moved. That is, use *REMOVED* if necessary rather than letting VS edit the shipped files.

@Tratcher
Copy link
Member Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@Tratcher
Copy link
Member Author

Now that I think about it, is / are the relevant PublicAPI.Shipped.txt file or files moving along with the existing assembly name❔ Should still have files containing exactly what we shipped in 5.0 even if the project moved. That is, use *REMOVED* if necessary rather than letting VS edit the shipped files.

These files:
error : src/Http/Http.Features/src/PublicAPI/net6.0/PublicAPI.Shipped.txt
error : src/Http/Http.Features/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt

Were recently added in 6.0 by #31625. We're removing them now that we've found a better solution.

The 5.0 APIs are still covered by src/PublicAPI.Shipped.txt

@JunTaoLuo @dougbu this is ready to merge.

@wtgodbe wtgodbe merged commit fc1f919 into dotnet:main Apr 26, 2021
@Tratcher Tratcher deleted the tratcher/split-features branch April 26, 2021 16:21
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
@PMExtra
Copy link

PMExtra commented Nov 26, 2024

Why don't we change its namespace?

The reason for extracting it from the original library is that it is, in fact, quite generic, which is also why the package is named Microsoft.Extensions.Features instead of something that contains AspNetCore or Http. However, I don't understand why it wasn't migrated to a corresponding namespace.

In fact, I am writing a message-bus library that borrows from ASP.NET Core's middleware design and provides a MessageHandlingContext. I reused the Microsoft.Extensions.Features library, which works awesome, but the namespace Microsoft.AspNetCore.Http might be confusing.

@davidfowl
Copy link
Member

Because a breaking change (for cosmetic reasons) like that are world ending for a product of this size and maturity. It's just not worth the tradeoff. If we invent a way to rename without breaking then I'd agree with you.

@PMExtra
Copy link

PMExtra commented Nov 26, 2024

I don’t quite understand which compatibilities have been broken.

I tend to think that Microsoft.Extensions.Features is primarily used internally within AspNetCore, and any internal compatibility breaks should be solvable by synchronizing the release of new versions for both.

Externally, I noticed that the discussion above repeatedly mentioned type-forward. I believe this is the solution to the problem. We can keep the current definitions in the original namespace, but mark them as [Obsolete], and then add a series of forward types in the correct namespace. These forward types would be empty and inherit from the original definitions. Then we could consider completely moving those obsolete definitions after a few major version transitions.

On the other hand, I’ve also hoped that .NET would offer a feature similar to export SomeThing from Another.Namespace;, which is common in NodeJS or Python. I believe this could solve such problems very effectively.

@davidfowl
Copy link
Member

Agree, if we invent features to make this scenario work then we can consider it, until that day comes, we're not going to rename namespaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions breaking-change This issue / pr will introduce a breaking change, when resolved / merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.