-
Couldn't load subscription status.
- Fork 716
6.0.0 Preview 1 #811
-
|
This is the first preview release, which includes support for .NET 6.0. Be sure to follow the announcement for more background and the roadmap for in-depth review of the release. The wiki has not been updated, but it's a work in progress. While the wiki has been useful and informative, it is reaching the limits of what is possible. Consideration is being made to lift the current wiki into a fully-fledged documentation website. All of the examples have been updated and include new examples such as Minimal APIs. This is currently the best place to start and review how things have changed. Features
ASP.NET Core
EnhancementsAll Platforms
.NET 6.0
ASP.NET Web API
ASP.NET Core
FixesAll of the open issues that could be fixed, have been fixed in this release. These will listed in the forthcoming 5.1 release, but it includes 65+ issues. I will accept reported issues and backport them to 5.1 if they are applicable. Breaking ChangesThis release is essentially a reset and rewrite so many things will be broken from
The second obvious big change will be different namespaces. All previous Significant individual changes:
Routing behavior has changed to more accurately return For ASP.NET Core, the setup is now a fluent interface of chained configuration options: services.AddApiVersioning() // Core API Versioning services with support for Minimal APIs .AddMvc() // API versioning extensions for MVC Core .AddApiExplorer() // API version-aware API Explorer extensions .AddOData() // API versioning extensions for OData .AddODataApiExplorer(); // API version-aware API Explorer extensions for OData Future ReleaseThe following are features and enhancements still under consideration before the final release:
FeedbackI want to hear your feedback. What do you like? What do you not like? I want to give the community a chance to play with the latest bits and provide any final thoughts before committing to the final release. Now would be the time for any other name changes or other significant refactoring that could cause additional disruption going forward. AdministrativeTL;DR
This discussion was created from the release 6.0.0 Preview 1. |
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 9 comments 35 replies
-
Changelog has incorrect url :)
Be sure to follow the announcement for more background and the roadmap for in-depth review of the release.
Same for other url, like examples link
Beta Was this translation helpful? Give feedback.
All reactions
-
Thank you for pointing that out. This is the first time I'm using a discussion with a release. Apparently, GitHub just copies the entire markdown, verbatim, to the discussion. I had checked the links before posting the release. The relative links are different from the discussion than the release. Good to know for the future. I have fixed the links here. They should work now.
Beta Was this translation helpful? Give feedback.
All reactions
-
I'm curious about this change:
All previous
Microsoft.*namespaces are nowAsp.Versioning.*.
Can someone elaborate on the history behind this? Is this now not a Microsoft project/product anymore?
Beta Was this translation helpful? Give feedback.
All reactions
-
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
TL;DR. The short, short version. This project isn't nor ever was run by the ASP.NET team. The "Microsoft" here was me and I'm no longer at Microsoft. There has never been a team; it's just me. Ultimately, the powers that be decided it was best to transfer the project out of the Microsoft GitHub organization and into the .NET Foundation (e.g. dotnet) organization, which you may have noticed a while back.
If it was purely lift and shift or I could have gotten this work done earlier (say 18 months ago), then the package identifiers and namespaces could have remained. I fought hard to get ownership of the existing packages so that I can service them for the community (it took months). When I thought it wouldn't happen and I started considering new features and new packages, using new namespaces seemed to be the direction to go. I think it would have been very confusing to mix and match package names or namespaces. I even considered starting over in a new repo, but think that would be equally confusing.
I know there will be some pain during this transition. I've felt the pain as much as anyone. I could have washed my hands of it and walked away, but love the community that has been built around the project. Any fundamental changes to naming, packages, and branding should happen now so it never happens again. Even using A.S.P. or the likes could cause complications. It took a while to accidentally on-purpose land on Asp. My goal was for things to seem as familiar as possible without running into conflicts or continuing to imply there is a "Microsoft" in the project.
"The Great Oz has spoken! Pay no attention to that man behind the curtain... I'm afraid there's no other wizard here except me."
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 3
-
The clarifications are much appreciated @Lonli-Lokli and @commonsensesoftware .
I'm glad you decided to keep pushing this one @commonsensesoftware since I imagine it could've very easily died instead.
Beta Was this translation helpful? Give feedback.
All reactions
-
I've tried to integrate it with my project
I have removed previous packages and installed <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="6.0.0-preview.1" />
Your sample did not mention but I use services.AddVersionedApiExplorer, so leave it as it is
- HasApiVersion extension still accepting only ApiVersion class, do not accept doubles.
- VersionedEndpointBuilder do not contain Produces extension, which I was using for Swagger schema.
- Swagger (swashbuckle) output do not contain new minimal endpoint.
Beta Was this translation helpful? Give feedback.
All reactions
-
@Lonli-Lokli, you did the correct thing. For the API Explorer with Minimal APIs you have the correct package. It's honestly kind of strange because Minimal APIs only require Microsoft.AspNetCore.Routing, but any use of the API Explorer requires Microsoft.AspNetCore.Mvc.Core and Microsoft.AspNetCore.Mvc.ApiExplorer. It's of little consequence to consumers, but it is kind of weird (IMO) for library authors trying to keep their dependencies clean.
The new setup is services.AddApiVersioning().AddApiExplorer();.
- The other extension methods require bringing in
using Asp.Versioning.Conventions;. - The OpenAPI extensions, as they are intrinsically called, were intentionally missing. There's a pretty significant design flaw in ASP.NET Core. It doesn't seem like a lot of thought was put into how these features would eventually be used and customized. I filed Design: IEndpointConventionBuilder Extensions Unusable By Other Extensions aspnetcore#39604 back in January. There hasn't been much traction. I was hoping to have some direction to minimize the pain. Since this is a preview, I've added enough to get past the hurdle. Please, please, please go cast your vote and spread the word to have the ASP.NET team fix it. That's the only way it will ever happen. No matter where we land, I'm sure it will change by .NET 7.0. My goal is to have signature parity before the final
6.0release to minimize the future impact. - Most of the API Explorer extensions just work for Minimal APIs, but I did find a few 🐞s which I captured in API Explorer Extensions Should Support Minimal APIs #812 .
I understand the importance of 2.. Unfortunately, my hands are tied. A majority of the types involved are internal with internal members which means I had to resort to Reflection to make it work 🤮. I've forked a workable implementation to unblock people and foster adoption. There's a chance this could change going forward. Here's what it will look like:
api.MapGet("/weatherforecast", (int id) => new WeatherForecast()) .Produces(response => response.Body<WeatherForecast>()) .HasApiVersion(1.0);
I'm not one to just make you figure it out. I've added a new Minimal OpenAPI Example that shows how to put everything together. This should have parity with the existing OpenAPI Example.
All of these changes are available in Preview 2, which you can pick up from Asp.Versioning.Mvc.ApiExplorer/6.0.0-preview.2. Give that a try and let me know if there are any issues or remaining gaps.
Beta Was this translation helpful? Give feedback.
All reactions
-
Just in case somebody will need it, native support for IFormFile/IFormFileCollection binding in minimal API will be available only in .net 7 (dotnet/aspnetcore#34303) but adding unused IFormFile to minimal API request will add this parameter to Swagger
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
@Lonli-Lokli and @julealgon, I accept your challenge (to show it in OpenAPI). 😉 I will try to put together a working example to share this week. I'll probably eventually throw it into official set of examples. I've used this approach many times, just not with OpenAPI. It'd be nice to get it into official MS docs. Let's start with a working example and work backward.
Beta Was this translation helpful? Give feedback.
All reactions
-
🚀 1
-
Ok, the following is a working example. It is pretty unnatural for it work with regular 'ol controller actions. Making it work with Minimal APIs isn't horrible, but it doesn't work the way that it should IMHO. Stream should be bindable from Request.Body if the receiving parameter is [FromBody] and of type Stream. Other special types like JsonElement already work this way.
It is a PITA to make it work with Swashbuckle, but it can be done. It's not 100% clear if Swashbuckle is unable to do the right thing because the core API Explorer doesn't provide the correct metadata or whether Swashbuckle simply doesn't do the right thing. OpenAPI (and hence Swagger) most definitely support plain 'ol file uploads as noted in the spec. It also supports multiple file uploads as noted here; specifically, multipart/mixed. It's not entirely clear if or how Swashbuckle supports it. This example does not show multiple file uploads.
Minimal API
app.MapPost( "/api/orders/import", async ( HttpRequest request, [FromHeader( Name = "Content-Disposition" )] string contentDisposition, CancellationToken cancellationToken ) => { var source = request.Body; var name = GetFileName( contentDisposition ); var userProfile = Environment.GetFolderPath( Environment.SpecialFolder.UserProfile ); var path = Path.Combine( userProfile, "Downloads", name.ToString() ); using var destination = new FileStream( path, FileMode.Create ); await source.CopyToAsync( destination, cancellationToken ); await destination.FlushAsync( cancellationToken ); var scheme = request.Scheme; var host = request.Host; var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/42" ); return Results.Created( location, default ); static string GetFileName( string header ) { if ( !ContentDispositionHeaderValue.TryParse( header, out var contentDisposition ) ) { return Guid.NewGuid().ToString( "n" ) + ".pdf"; } var name = contentDisposition.FileName.HasValue ? contentDisposition.FileName.Value : ( contentDisposition.Name.HasValue ? contentDisposition.Name.Value : Guid.NewGuid().ToString( "n" ) ); if ( !name.EndsWith( ".pdf", StringComparison.OrdinalIgnoreCase ) ) { name += ".pdf"; } return name; } } ) .Accepts<Stream>( "application/pdf" ) .Produces( 201 );
OpenAPI Integration with Swashbuckle
This will fix-up file upload APIs so that the Swagger UI will show a file picker.
sealed class FileFilter : IOperationFilter { public void Apply( OpenApiOperation operation, OperationFilterContext context ) { if ( operation.RequestBody is OpenApiRequestBody body && context.ApiDescription.HttpMethod is string httpMethod && ( httpMethod == "POST" || httpMethod == "PUT" || httpMethod == "PATCH" ) ) { var consumes = context.ApiDescription.SupportedRequestFormats; var keys = consumes.Count == 0 ? body.Content.Keys.ToArray() : consumes.Where( c => c.Formatter is null ).Select( c => c.MediaType ).ToArray(); for ( var i = 0; i < keys.Length; i++ ) { var key = keys[i]; body.Required = true; body.Content.Remove( key ); body.Content.Add( key, new() { Schema = new() { Type = "string", Format = "binary" } } ); } } } }
This will set things up the way you'd expect:
image
Content-Disposition isn't strictly required, but it is a common way to specify the file name and/or other file-related information. A typical value would be something like inline; filename="file.ext". It's unclear if you can break header parameters into constituent parts. There is probably a way if you're willing to put in the work. If Content-Disposition is not specified, you have the choice to reject the request (e.g. 404) or assume a default file name. It's just a suggestion to the server anyway; it's not required to use it, even when specified. The disposition type can be just about anything and - again - up to you to validate. inline or attachment are probably the most appropriate. Content-Disposition is usually used for downloads, but it can be used for uploads and most of the form posts I've seen include it.
I'm going to file an issue with the ASP.NET team. Stream should be intrinsically supported for parameter binding. After I've done so, I'll link it back here for reference. If someone understands how IFormFileCollection is wired up in Swashbuckle or can confirm whether multipart/mixed is even supported, it would be neat to see it work.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 1
-
Your reply reminded me of how poor .NET's "support" for multipart requests currently is @commonsensesoftware . I had a requirement to "upload a file in one part and post a json in another" and this is basically unsupported.... I wish there was native content-negotiation support with multipart requests in ASPNET... would make things so much more intuitive.
Beta Was this translation helpful? Give feedback.
All reactions
-
It certainly leaves something to be desired. The fact that there is any support for it all is a relief; otherwise, it would probably be TH;DT (Too Hard; Didn't Try). It is definitely possible to achieve your scenario. I have no idea if or how that would manifest in the Swagger UI. I guess each part would allow "one of" types of content?
What you cannot do, intrinsically, is use multipart/mixed to perform batching. This has been called out several times. The ASP.NET team feels that enough improvements have come in HTTP/2+ that there is no benefit to supporting it. The biggest challenge to making it work is that Features in the HttpContext do not come from DI nor can they be safely cloned or recreated. To achieve true batching, you need to create a separate HttpContext for each part in the incoming message. Each part also needs to be a nested HTTP message (e.g. application/http; msgtype=request). If you do this work very early in the middleware pipeline, it might work as intended. This is effectively how OData has retained its own batching support.
There are likely some other ways this capability can be achieved. Supporting heterogenous payloads in a single request is definitely a use case for multipart/mixed; specifically for batching, not so much - anymore.
Beta Was this translation helpful? Give feedback.
All reactions
-
👀 1
-
It wasn't really expected, but this sidebar really fanned out into something meaningful. I opened #41426 and it looks like it will actually be addressed in 7.0! 🎉
Beta Was this translation helpful? Give feedback.
All reactions
-
Is there any way to make OData work with Minimal API, ie without Controllers? With OpenApi as well
Beta Was this translation helpful? Give feedback.
All reactions
-
😕 1
-
To the best of my knowledge, currently - no. I didn't find any open issues on the topic. I don't believe it will be a tremendous amount of work, but there are certain limitations in the design of Minimal APIs that are holding it up. A good example is model binding. Even for API Versioning, you cannot declare ApiVersion as an argument and have it work. Enhancements in .NET 7.0 should make this possible.
I don't expect any, and certainly not significant, changes for the API Explorer for OData. There weren't a huge number of changes to make it work without OData. I would expect that OData support will add a ton more work.
I haven't tried it, but mixing vanilla Minimal APIs with OData controllers should work, including for the API Explorer. If you happen to test it out, I'd love to hear your experience.
Beta Was this translation helpful? Give feedback.
All reactions
-
I think you are right - EnableQuery extending ActionFilterAttribute, which will not work for Minimal API. Does it make sense to open new one, and if yes what is the correct repository for that?
Beta Was this translation helpful? Give feedback.
All reactions
-
This is where I would start: https://github.com/OData/AspNetCoreOData/issues
Beta Was this translation helpful? Give feedback.
All reactions
-
Why did you choose
.Produces( response => response.Body<IEnumerable<OrderV1>>() )
.Produces( response => response.Body<IEnumerable<OrderV2>>(), 201 )
instead of
.Produces<IEnumerable<OrderV1>>()
.Produces<IEnumerable<OrderV2>>(201)
Beta Was this translation helpful? Give feedback.
All reactions
-
Wow, I like new version. it has much less boilerplate
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
It's better, but I still don't like that you have to build something external, attach it, and then potentially keep building. The Minimal API builder API only seems to really consider adding metadata to an endpoint. For API Versioning, I feel developers may add one or more pieces of versioning metadata to an endpoint. To that end, extending the intrinsic builders is not very easy.
For 6.0, I'm fine with staying in alignment with the confines of what's expected. The MapGroup should address those considers for 7.0 🤞🏽 . We'll see soon enough.
Beta Was this translation helpful? Give feedback.
All reactions
-
Adding a different generic signature wouldn't work either, since you'd have to specify both types.
You could split the arguments into 2 calls using a fluent interface. This allows you to have the compiler infer one part while specifying the other explicitly using generic types for both values.
Beta Was this translation helpful? Give feedback.
All reactions
-
@julealgon That's basically how it worked. The problem was how do you turn one logical operation into two just for the sake of naming? If the first method isn't called Accepts or Produces, it would strange. Similarly, it would be strange to have any other fluent methods afterward (ex: Accepts().Body<Foo>().Produces().Body<Foo>().HasApiVersion(1.0)). Since these are extension methods, there is no other way to override their behavior unless you change the signature. That is more or less how the callback approach was born.
Ultimately, this issue is mute and irrelevant. Preview 3 will see it revert to the intrinsic implementation. 😃 The feedback has been heard and incorporated.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
One note that previous implementation was closer to the group in terms of .NET7 - It had an ability to create simplifid approach
public void MapGroup(WebApplication app) =>
app.DefineApi("Documents")
.HasApiVersion(1.0)
.ReportApiVersions()
.HasMapping(api =>
{
api.MapUpdateDocument();
api.MapGetDocuments();
api.MapGetDocument();
api.MapAddDocument();
api.MapDeleteDocument();
});
became
public void MapGroup(WebApplication app)
{
var group = app.NewApiVersionSet("Documents")
.Build();
app.MapUpdateDocument().WithApiVersionSet(group).HasApiVersion(1.0);
app.MapGetDocuments().WithApiVersionSet(group).HasApiVersion(1.0);
app.MapGetDocument().WithApiVersionSet(group).HasApiVersion(1.0);
app.MapAddDocument().WithApiVersionSet(group).HasApiVersion(1.0);
app.MapDeleteDocument()
.WithApiVersionSet(group)
.HasApiVersion(1.0);
}
Beta Was this translation helpful? Give feedback.
All reactions
-
Can you add\rewrite example for OpenApi with C# records? It's not always possible to find the correct way for specifying new way of Required attribute with new typs, so extending example will be a great help
NEW:
public record Order( int Id, DateTimeOffset CreatedDate, [property: Required] string Customer);
OLD
public class Order
{
public int Id { get; set; }
public DateTimeOffset CreatedDate { get; set; };
[Required]
public string Customer { get; set; }
}
Beta Was this translation helpful? Give feedback.
All reactions
-
I'm not wholly opposed this idea, but I try not to go too far off into the weeds for things that aren't specific to API versioning. Many of these variants are subjective and opinionated. OpenAPI, for example, is not a specific objective of API Versioning, but the API Explorer is. The examples simply show how to put all the pieces together in a meaningful way (because, honestly, it's very common).
If you're interested in expanding the examples in a PR, I'd be willing to accept it. That doesn't mean that's the only way it will ever be added, but - admittedly - adding this won't be at the top my list. I certainly can appreciate the lack-luster of some of the public examples out there and I try to create examples that represent what people would typically want to do.
I have a future task to lift the wiki out into a full website. Including or linking to additional examples is something I'm considering as part of that.
Beta Was this translation helpful? Give feedback.
All reactions
-
Really great work! Do you have an ETA for 6.0.0 official release?
Beta Was this translation helpful? Give feedback.
All reactions
-
There's a couple of issues that have been reported that need to flush out, but that won't take long. The main issue/blocker is code signing. I have the issue open with DNF. I'll ping them again today. If I don't gain traction, I'll advertise to the community to light a 🔥 . 😉
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1 -
❤️ 1 -
🚀 1
-
@commonsensesoftware Could you share the latest status of official release? And do you know if there is any known gap between 6.0.0 preview and Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer 5.0.0?
Beta Was this translation helpful? Give feedback.
All reactions
-
I'm still waiting on code signing. I've been trying to wrap it up through the back and official channels to no avail. I don't know that it will make any difference, but the DNF code signing onboarding task is here. Comments from the community might drive awareness and trigger action, but - honestly - IDK.
I can't think of any specific gaps unless you uncovered something. If you see or know of something, please share. 6.0.0 will naturally have more features along with all the other enhancements. The 5.1 release will contain fixes within the previous libraries and packages.
Beta Was this translation helpful? Give feedback.
All reactions
-
How can I add custom route string for methods?
I found when I add some prefix like "weatherForcast/" to method, it can't be identified as OData API:
image
image
Beta Was this translation helpful? Give feedback.
All reactions
-
In my project, there is a nested structure for EDM model. The code works fine with OData 8.
The model is like:
[DataContract(Name = "virtualEndpoint")]
public class VirtualEndpoint : VirtualEndpointBase
{
[Contained]
[DataMember(Name = "weatherForecasts")]
public ICollection<WeatherForecast> WeatherForecasts { get; set; }
}
The startup configuration is like:
mvcBuilder.AddOData(options =>
{
options.Expand().Select().Filter().OrderBy().Count().SetMaxTop(10)
.AddRouteComponents("api/hello/odata", this.GetEdmModel());
});
private IEdmModel GetEdmModel()
{
var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EnableLowerCamelCase();
odataBuilder.Singleton<VirtualEndpoint>("virtualEndpoint");
return odataBuilder.GetEdmModel();
}
The controller is like:
[Route("api/hello/odata/virtualEndpoint")]
public class WeatherForecastsController : ODataController
{
// GET https://localhost:44382/api/hello/odata/virtualEndpoint/weatherForecasts
[HttpGet("weatherForecasts")]
[EnableQuery]
public async Task<ActionResult<IEnumerable<WeatherForecast>>> GetWeatherForecasts()
{
return storedWeatherForecasts;
}
}
How can I make it work with "Asp.Versioning.OData"?
Beta Was this translation helpful? Give feedback.
All reactions
-
The overall approach for versioning with OData hasn't changed all that much. The fundamentals are the same, but the configuration points are a little different to have parity with the changes to OData itself.
API Versioning support for OData encourages, but does not require, you to use IModelConfiguration for configuring an EDM per API version and route prefix combination. All OData examples demonstrate this approach. This can also be done via the default configuration callback or you can do it completely manually yourself, but that's not recommended.
The setup should look something like:
// OData does NOT (and cannot) support multiple EDMs per route prefix. // calls to AddRouteComponents here are ignored. Defining the query options // here are used as a 'template' for all versioned EDMs services.AddOData(options => options.Expand().Select().Filter().OrderBy().Count().SetMaxTop(10)); services.AddApiVersioning() .AddOData(options => { // options.ModelBuilderFactory defaults to new ODataConventionModelBuilder().EnableLowerCamelCase() // default callback instead of IModelConfiguration. this is called with a new ODataModelBuilder created from // options.ModelBuilderFactory for every API version and registered route prefix options.ModelBuilder.DefaultModelConfiguration = (builder, apiVersion, routePrefix) => { builder.Singleton<VirtualEndpoint>("virtualEndpoint"); }; options.AddRouteComponents("api/hello/odata"); });
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1 -
🚀 1
-
That works, thank you! @commonsensesoftware
Beta Was this translation helpful? Give feedback.
All reactions
-
🎉 1
-
@commonsensesoftware Can I achieve this by using IModelConfiguration?
Beta Was this translation helpful? Give feedback.
All reactions
-
Sure.
public class VirtualEndpointConfiguration : IModelConfiguration { public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string? routePrefix ) { builder.Singleton<VirtualEndpoint>("virtualEndpoint"); } }
The setup then just becomes:
services.AddOData(options => options.Expand().Select().Filter().OrderBy().Count().SetMaxTop(10)); services.AddApiVersioning().AddOData(options => options.AddRouteComponents("api/hello/odata"));
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1