Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

6.0.0 Preview 1 #811

Locked
commonsensesoftware started this conversation in Show and tell
Discussion options

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

  • Sunset Policies (RFC 8594)
  • Web Linking (RFC 8288)
  • API version-aware HTTP clients
    • ex: services.AddHttpClient("test").AddApiVersion(1.0)
    • If the server reports API versions, the use of deprecated API versions and their sunset polices are passed to ILogger

ASP.NET Core

  • Support .NET 6.0 (LTS)
  • Support Minimal APIs
  • Support OData 8.0 (finally! 🎉)

Enhancements

All Platforms

  • Performance (ex: more Span<T>, no Regex, fewer allocations, etc)
  • ApiVersion and related attributes now allows double (ex: new ApiVersion(1.0), [ApiVersion(1.0)])
  • ApiVersion.Status now allows . (ex: 2.0-beta.1, 2022年04月01日-preview.1)
  • ApiVersion.Parse is supplanted by IApiVersionParser via ApiVersionParser.Default
    • This now makes end-to-end customization of ApiVersion possible (ex: you can add, parse, and format a patch or revision version component if you really want it)
  • All errors now use Problem Details (RFC 7807)
  • 404, 405, and 415 are now properly returned in accordance with configured versioning methods without requiring additional configuration or customization
  • Namespace-to-API Version parsing has been lifted out into NamespaceParser
    • You can extend/customize the behavior or simply use NamespaceParser.Default for the same old out-of-the-box implementation

.NET 6.0

  • ApiVersion supports ISpanFormattable

ASP.NET Web API

  • .NET Framework 4.5 is still supported, but .NET Framework 4.7.2 is added as a target for performance benefits when supported
  • UrlHelper.WithoutApiVersion() extension (useful when generating links from versioned to version-neutral routes)

ASP.NET Core

  • IUrlHelper.WithoutApiVersion() extension (useful when generating links from versioned to version-neutral endpoints)

Fixes

All 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 Changes

This release is essentially a reset and rewrite so many things will be broken from 5.x. The most obvious issues will be new packages. Here is the mapping of old packages to new packages:

Old Package ID New Package ID
Asp.Versioning.Abstractions
Microsoft.AspNet.OData.Versioning Asp.Versioning.WebApi.OData
Microsoft.AspNet.OData.Versioning.ApiExplorer Asp.Versioning.WebApi.OData.ApiExplorer
Microsoft.AspNet.WebApi.Versioning Asp.Versioning.WebApi
Microsoft.AspNet.WebApi.Versioning.ApiExplorer Asp.Versioning.WebApi.ApiExplorer
Asp.Versioning.Http
Asp.Versioning.Http.Client
Microsoft.AspNetCore.Mvc.Versioning Asp.Versioning.Mvc
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer Asp.Versioning.Mvc.ApiExplorer
Microsoft.AspNetCore.OData.Versioning Asp.Versioning.OData
Microsoft.AspNetCore.OData.Versioning.ApiExplorer Asp.Versioning.OData.ApiExplorer

The second obvious big change will be different namespaces. All previous Microsoft.* namespaces are now Asp.Versioning.*. If you are a library author that targets multiple platforms, this should make things easier going forward; it certainly made it easier in the project itself. Extension methods continue to use the same namespace as the type they correspond to.

Significant individual changes:

  • IApiVersionReader.Read now returns IReadOnlyList<string> instead of string?
    • Solves the problem of multiple values for a single key (ex: query string or header)
    • Simplifies reader composition (which naturally may have multiple values)
    • Makes reading duplicate values non-exceptional, which is also valuable in error reporting
  • IReportApiVersions.Report now accepts the entire response instead of just headers
  • IErrorResponseProvider no longer exists; it is supplanted by IProblemDetailsFactory and Problem Details (RFC 7807)
  • The following old extension methods were moved to the new ApiVersionMetadata type:
    • GetApiVersionModel(ApiVersionMapping) → GetApiVersionMetadata()
    • GetApiVersionModel() → ApiVersionMetadata.Map(ApiVersionMapping.Explicit)
    • MappingTo(ApiVersion) → ApiVersionMetadata.MappingTo(ApiVersion)
    • IsMappedTo(ApiVersion) → ApiVersionMetadata.IsMappedTo(ApiVersion)

Routing behavior has changed to more accurately return 404, 405, or 415 when appropriate. 400 is still returned in known, invalid requests such as an unspecified API version when required.

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 Release

The following are features and enhancements still under consideration before the final release:

Feedback

I 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.

Administrative

TL;DR

  • This project is now officially part of the .NET Foundation (🎉)
  • Code signing still isn't setup, which will hold up the official release, including the 5.x patches

This discussion was created from the release 6.0.0 Preview 1.
You must be logged in to vote

Replies: 9 comments 35 replies

Comment options

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

You must be logged in to vote
1 reply
Comment options

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.

Comment options

I'm curious about this change:

All previous Microsoft.* namespaces are now Asp.Versioning.*.

Can someone elaborate on the history behind this? Is this now not a Microsoft project/product anymore?

You must be logged in to vote
3 replies
Comment options

Comment options

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."

Comment options

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.

Comment options

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

  1. HasApiVersion extension still accepting only ApiVersion class, do not accept doubles.
  2. VersionedEndpointBuilder do not contain Produces extension, which I was using for Swagger schema.
  3. Swagger (swashbuckle) output do not contain new minimal endpoint.
You must be logged in to vote
1 reply
Comment options

@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();.

  1. The other extension methods require bringing in using Asp.Versioning.Conventions;.
  2. 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.0 release to minimize the future impact.
  3. 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.

Comment options

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

You must be logged in to vote
11 replies
Comment options

@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.

Comment options

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.

Comment options

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.

Comment options

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.

Comment options

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! 🎉

Comment options

Is there any way to make OData work with Minimal API, ie without Controllers? With OpenApi as well

You must be logged in to vote
3 replies
Comment options

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.

Comment options

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?

Comment options

Comment options

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)
You must be logged in to vote
6 replies
Comment options

Wow, I like new version. it has much less boilerplate

Comment options

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.

Comment options

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.

Comment options

@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.

Comment options

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);
 }
Comment options

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; }
}
You must be logged in to vote
1 reply
Comment options

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.

Comment options

Really great work! Do you have an ETA for 6.0.0 official release?

You must be logged in to vote
3 replies
Comment options

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 🔥 . 😉

Comment options

@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?

Comment options

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.

Comment options

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

You must be logged in to vote
6 replies
Comment options

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"?

Comment options

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");
 });
Comment options

That works, thank you! @commonsensesoftware

Comment options

@commonsensesoftware Can I achieve this by using IModelConfiguration?

Comment options

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"));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /