Skip to content

Update Endpoint metadata providers to include EndpointBuilder param #43125

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

Closed
halter73 opened this issue Aug 6, 2022 · 9 comments · Fixed by #43543
Closed

Update Endpoint metadata providers to include EndpointBuilder param #43125

halter73 opened this issue Aug 6, 2022 · 9 comments · Fixed by #43543
Assignees
Labels
api-approved API was approved in API review, it can be implemented old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Milestone

Comments

@halter73
Copy link
Member

halter73 commented Aug 6, 2022

Background and Motivation

With #43000, we've approved adding RequestDelegateFactoryOptions.EndpointBuilder and EndpointFilterFactoryContext.EndpointBuilder so filters can get full access to more metadata like the RoutePattern (with a downcast) and DisplayName.

I think IEndpointMetadataProvider and IEndpointParameterMetadataProvider deserve the same treatment.

Also see #42827 (comment) for more context.

Proposed API

namespace Microsoft.AspNetCore.Http.Metadata;


public interface IEndpointMetadataProvider
{
-     static abstract void PopulateMetadata(EndpointMetadataContext context);
+     static abstract void PopulateMetadata(MethodInfo method, EndpointBuilder builder);
}

public interface IEndpointParameterMetadataProvider
{
-     static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
+     static abstract void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder);
}

- public sealed class EndpointMetadataContext
- {
-     public EndpointMetadataContext(MethodInfo method, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public MethodInfo Method { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
- }

- public sealed class EndpointParameterMetadataContext
- {
-     public EndpointParameterMetadataContext(ParameterInfo parameter, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public ParameterInfo Parameter { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
- }

Usage Examples

static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
    builder.Metadata.Add(new ProducesResponseTypeMetadata(typeof(TValue), StatusCodes.Status200OK, "application/json"));
}

Alternative Designs

We could change context. to reference a builder in case we want to add more parameters in the future that are not on the EndpointBuilder in some way. I find this is clunky though, and I'm not sure how likely additional future parameters really are.

namespace Microsoft.AspNetCore.Http.Metadata;

public sealed class EndpointMetadataContext
{
-     public EndpointMetadataContext(MethodInfo method, IList<object> endpointMetadata, IServiceProvider applicationServices);
+     public EndpointMetadataContext(MethodInfo method, EndpointBuilder builder);

      public MethodInfo Method { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
+     public EndpointBuilder EndpointBuilder { get; }
}

public sealed class EndpointParameterMetadataContext
{
-     public EndpointParameterMetadataContext(ParameterInfo parameter, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public EndpointParameterMetadataContext(ParameterInfo prameter, EndpointBuilder builder);

       public ParameterInfo Parameter { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
+     public EndpointBuilder EndpointBuilder { get; }
}

Risks

With the primary proposal, we lose out on having a context, so it's harder to add parameters in the future if we find it will be necesarry.

Third-party IEndpointMetadataProvider implementations need to rewrite their PopulateMetadata methods with either change, but it should be pretty mechanical.

@DamianEdwards

@halter73 halter73 added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Aug 6, 2022
@ghost
Copy link

ghost commented Aug 6, 2022

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@halter73
Copy link
Member Author

API Review Notes:

API Rejected! (unless we find an easy way to do this in MVC)

@halter73 halter73 added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Aug 10, 2022
@halter73
Copy link
Member Author

halter73 commented Aug 15, 2022

Okay. I think this is workable. But we also need to add a new API to RDF to allow IEndpointMetadataProvider IEndpointParameterMetadataProvider metadata to be added before we run any route-specific conventions. This requires making RDF two-phase so metadata can be inferred before all the filters run because filters can be added by route-specific conventions.

namespace Microsoft.AspNetCore.Http.Metadata;

public interface IEndpointMetadataProvider
{
-     static abstract void PopulateMetadata(EndpointMetadataContext context);
+     static abstract void PopulateMetadata(MethodInfo method, EndpointBuilder builder);
}

public interface IEndpointParameterMetadataProvider
{
-     static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
+     static abstract void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder);
}

- public sealed class EndpointMetadataContext
- {
-     public EndpointMetadataContext(MethodInfo method, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public MethodInfo Method { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
- }

- public sealed class EndpointParameterMetadataContext
- {
-     public EndpointParameterMetadataContext(ParameterInfo parameter, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public ParameterInfo Parameter { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
- }

namespace Microsoft.AspNetCore.Http;

+ public sealed class RequestDelegateMetadataResult
+ {
+     public required IReadOnlyList<object> EndpointMetadata { get; init; }
+ }

public static class RequestDelegateFactory
{
+     public static RequestDelegateMetadataResult InferMetadata(MethodInfo methodInfo, RequestDelegateFactoryOptions? options = null);
}

@halter73 halter73 added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Aug 15, 2022
@ghost
Copy link

ghost commented Aug 15, 2022

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@halter73
Copy link
Member Author

halter73 commented Aug 15, 2022

API Review Notes:

  1. The current proposal with EndpointBuilders passed to the metadata providers is dependent on the RequestDelegateFactoryOptions in Expose entire EndpointBuilder to filter factories #43000.
  2. There seems to be general consensus that it makes sense to make RDF two phase so route-specific conventions can observe and override inferred metadata.

On a personal note, I would much prefer to use EndpointBuilder. If we make RDF two phase, we can run the filter factory in the inference phase and make it visible to route-specific conventions just like these metadata providers.

@halter73 halter73 added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Aug 15, 2022
@captainsafia captainsafia added this to the 7.0-rc1 milestone Aug 16, 2022
@captainsafia
Copy link
Member

Cross-refing #43330 as a scenario for this API.

@halter73
Copy link
Member Author

halter73 commented Aug 17, 2022

Description of the order metadata is added.

Order of Metadata addition today

  1. MethodInfo (RouteEndpointDataSource)
  2. HttpMethodMetadata (RouteEndpointDataSource)
  3. Group conventions (outer to inner) (RouteEndpointDataSource)
  4. Attributes (RouteEndpointDataSource)
  5. Route-specific conventions (RouteEndpointDataSource)
  6. Implicitly inferred metadata (RDF.Create)
    1. AcceptsMetadata (inserted at beginning)
  7. Static interface metadata providers (RDF.Create)
  8. Endpoint Filters (RDF.Create)
  9. Route-specific finally conventions (RouteEndpointDataSource)
  10. Group finally conventions (inner to outer) (RouteEndpointDataSource)

Order of Metadata addition tomorrow

  1. MethodInfo (RouteEndpointDataSource)
  2. HttpMethodMetadata (RouteEndpointDataSource)
  3. Implicitly inferred metadata (RDF.InferMetadata)
    1. AcceptsMetadata
    2. ProducesMetadata
  4. Static interface metadata providers (RDF.InferMetadata)
  5. Group conventions (outer to inner) (RouteEndpointDataSource)
  6. Attributes (RouteEndpointDataSource)
  7. Route-specific conventions (RouteEndpointDataSource)
  8. Endpoint Filters mutate metadata (RDF.Create)
  9. Route-specific finally conventions (RouteEndpointDataSource)
  10. Group finally conventions (inner to outer) (RouteEndpointDataSource)

There is a subtle behavior where metadata is not re-added only if the options is reused between InferMetadata() and Create().

  • Can we make InferMetadata void?
    • Actually, let's flow the result to create to make it more explicit that we've already done inference.
namespace Microsoft.AspNetCore.Http.Metadata;

public interface IEndpointMetadataProvider
{
-     static abstract void PopulateMetadata(EndpointMetadataContext context);
+     static abstract void PopulateMetadata(MethodInfo method, EndpointBuilder builder);
}

public interface IEndpointParameterMetadataProvider
{
-     static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
+     static abstract void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder);
}

- public sealed class EndpointMetadataContext
- {
-     public EndpointMetadataContext(MethodInfo method, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public MethodInfo Method { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
- }

- public sealed class EndpointParameterMetadataContext
- {
-     public EndpointParameterMetadataContext(ParameterInfo parameter, IList<object> endpointMetadata, IServiceProvider applicationServices);
-     public ParameterInfo Parameter { get; }
-     public IList<object> EndpointMetadata { get; }
-     public IServiceProvider ApplicationServices { get; }
- }

namespace Microsoft.AspNetCore.Http;

+ public sealed class RequestDelegateMetadataResult
+ {
+     public RequestDelegateMetadataResult();
+     public required IReadOnlyList<object> EndpointMetadata { get; init; }
+ }

public static class RequestDelegateFactory
{
+     public static RequestDelegateMetadataResult InferMetadata(MethodInfo methodInfo, RequestDelegateFactoryOptions? options = null);

-     public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null);
+     public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options);
+     public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null);

-     public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory = null, RequestDelegateFactoryOptions? options = null)
+     public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory, RequestDelegateFactoryOptions? options);
+     public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory = null, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null);
}

API Approved!

@halter73 halter73 added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Aug 17, 2022
@ghost
Copy link

ghost commented Aug 17, 2022

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@halter73 halter73 added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Aug 19, 2022
@DamianEdwards
Copy link
Member

@halter73 seems this should this be moved to rc.2?

@halter73 halter73 modified the milestones: 7.0-rc1, 7.0-rc2 Aug 30, 2022
@halter73 halter73 closed this as completed Sep 2, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Oct 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants