-
Notifications
You must be signed in to change notification settings - Fork 10.3k
MapAction MVP #29878
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
MapAction MVP #29878
Conversation
[HttpPost("/EchoTodo")] | ||
JsonResult EchoTodo([FromBody] Todo todo) => new(todo); | ||
|
||
endpoints.MapAction((Func<Todo, JsonResult>)EchoTodo); |
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 cast is a bit weird here - do any of the language enhancements we've discussed help out with this kind of methodgroup-to-delegate conversion?
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.
Yep
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.
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.
Brain is a bit fried today but not sure which enhancement we discussed that would fix this.
If this were written as a lambda I agree the discussions we've had would help. It would have a natural type, we'd synthesize a delegate
, use that and it would become the type passed to MapAction(Delegate)
. This is a method group though, not a lambda. Don't recall us talking about enhancing those.
It's not outside the realm of possibilities, I just haven't thought about it a bunch yet.
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 came up as well. The idea is that overloads aren't supported we didn't understand why this would be problematic.
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.
Brain is feeling a bit better today: medium rare, no longer well done.
Yeah we did talk about this. It's the same idea: giving method groups a natural type. That lets them have a tie breaker in cases where a Delegate
overload exists.
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.
@jaredpar We should figure out what preview release of .NET 6 we could expect a version of this. We'll likely have something in preview2 and I don't expect we'd have anything that early in the compiler timeframe but we should target a release because this is pretty unusable in C# without it 😄
src/Http/Http.Api/src/MapActionEndpointRouteBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns> | ||
public static IEndpointConventionBuilder MapAction( | ||
this IEndpointRouteBuilder endpoints, | ||
Delegate action) |
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.
Linker unfriendly 😄 .
cc @eerhardt
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.
What's linker unfriendly about the API? You can have APIs that take delegates, and the linker won't trim them away.
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.
We do a ton of reflection on the delegate that's passed in.
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 Expression
stuff is probably going to get you into more trouble. I'm in the middle of annotating Linq.Expressions now, and it isn't pretty how much stuff is not trim compatible.
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.
We're also considering Roslyn source generators as an alternative which I assume is more linker-friendly. @davidfowl has already started experimenting with this at https://github.com/davidfowl/uController/tree/aa8bcb4b30764e42bd72d326cb2718a4d4eaf4a9/src/uController.SourceGenerator.
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.
Yeah, source generators are usually better, if possible
// paramterExpression = BindParamenter(formProperty, parameter, parameter.FromForm); | ||
//} | ||
//else if (parameter.FromBody) | ||
if (parameter.CustomAttributes.Any(a => typeof(IFromBodyAttribute).IsAssignableFrom(a.AttributeType))) |
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.
We spent a bit of time trying to declutter ApiControllers by inferring default sources for parameters. The rules are fairly simple:
- Complex type -> FromBody
- Name appears as a route value -> FromRoute
- Everything else -> FromQuery
It would be crummy to lose that as part of this programming model.
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.
{ | ||
var bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType!); | ||
|
||
await invoker(target, httpContext, bodyValue); |
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.
We have to think about validation looks like here. The way this works in MVC is that deserialization errors and validation errors end up in model state. For ApiControllers a filter turns the model state in a 400 response (which includes the a serialized form of model state).
- We could re-use ModelState here. It's not necessarily the best data structure to represent a graph of values, but MVC's validation system already understands it so that's one fewer thing to deal with.
- How do we surface the error to the user? We could bind a ModelState and let you deal with it. If we wanted to support MVC's auto validation as a feature (it's pretty neat and removes boiler plate), we could add it as a filter that's configured by default.
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.
We shouldn't add any implicit behavior until we figure out a way to remove said behavior. We currently don't have an application model so we need something reasonable that describes this behavior.
If we support something like model state, it would need to be an explicit parameter to the method (which is a much cleaner way of opting-into functionality).
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.
We're going to have some way to configure filters for these globally (services.AddRouting(options => options.MapActionFilters. [Insert / Remove ])
). That should be sufficient to enable some implicit runtime behavior that's also removable.
But binding the type once we enable validation would be a good start.
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.
We should do that after, not in this PR
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.
Oh yeah, absolutely. Just pointing out that we have a gap here.
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.
We're going to have some way to configure filters for these globally
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.
So you're going to end up with an application model, just without the "see and modify" part of it 😆
What |
I think |
Lets give it both labels to confuse everyone 😄 |
@Pilchie after meeting with @mkArtakMSFT today, I think it makes sense to use the |
07726b8
to
f9c248a
Compare
src/Http/Routing/src/MapAction/MapActionEndpointRouteBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
src/Http/Routing/src/MapAction/MapActionEndpointRouteBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
src/Http/Routing/src/MapAction/MapActionEndpointRouteBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
src/Http/Routing/src/MapAction/MapActionEndpointRouteBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
|
||
const int defaultOrder = 0; | ||
|
||
foreach (var routeAttribute in routeAttributes) |
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.
Would it be problematic to say
a) one action -> one route attribute (exactly one method constraint \ template)
b) each route attribute must have a template
The current implementation simply skips over route attributes without a template.
af6b13f
to
d09f7f1
Compare
catch (IOException ex) | ||
{ | ||
LogRequestBodyIOException(httpContext, ex); | ||
httpContext.Abort(); |
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.
@pranavkm is this how MVC handles it? I thought it set a 400 response status instead?
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.
Yea, why is it aborting the connection?
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.
JSON parsing errors result in a 400 response that includes details from the parser. Other IO errors result in 400s.
IMO, I assumed the entirety of this type was WIP so I hadn't looked at it in detail. We should factor it because this isn't a sustainable format to maintain.
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 very much a work in progress. I imagine we'll want to make logic like this reusable from different kinds of code-gen and move it out of this class at some point.
As for the status code, it now sets a 400 for InvalidDataExceptions. For aborted request, the status code doesn't matter much since the response doesn't get written to a socket and Kestrel will set the status code to 0 before hosting logs it anyway.
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.
Hmm, we missed the invalid Json scenario here. That should also return a 400. edit Nevermind, is InvalidDataException the exception thrown for invalid Json?
d09f7f1
to
15f2e6d
Compare
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:
|
This is an early prototype for MapAction. This is a good time to provide feedback on names and project structure.
Before this can be merged we need to support more parameter binding attributes and add tests. Input and output formatters, model binding validation, Swagger/OpenApi support, MapAction filters and more will all come later.
Addresses #29684