diff --git a/ApiVersioning.sln b/ApiVersioning.sln index 393bcbf9..b9a903ef 100644 --- a/ApiVersioning.sln +++ b/ApiVersioning.sln @@ -28,12 +28,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{2957BAF3-9 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{915BB224-B1D0-4E27-A348-67FCC77AAA44}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "webapi", "webapi", "{F446ED94-368F-4F67-913B-16E82CA80DFC}" ProjectSection(SolutionItems) = preProject - samples\webapi\directory.build.targets = samples\webapi\directory.build.targets + samples\directory.build.props = samples\directory.build.props EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "webapi", "webapi", "{F446ED94-368F-4F67-913B-16E82CA80DFC}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcore", "aspnetcore", "{900DD210-8500-4D89-A05D-C9526935A719}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicSample", "samples\aspnetcore\BasicSample\BasicSample.csproj", "{59389B47-8280-411E-B840-D097AA1DCDEE}" diff --git a/build/code-analysis.props b/build/code-analysis.props index c1e73db8..a50752bb 100644 --- a/build/code-analysis.props +++ b/build/code-analysis.props @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/samples/aspnetcore/BasicSample/BasicSample.csproj b/samples/aspnetcore/BasicSample/BasicSample.csproj index 0f0062bb..4ae09dda 100644 --- a/samples/aspnetcore/BasicSample/BasicSample.csproj +++ b/samples/aspnetcore/BasicSample/BasicSample.csproj @@ -1,25 +1,18 @@  - netcoreapp2.0 + netcoreapp2.2 + InProcess + Microsoft.Examples - - PreserveNewest - + + - - - - - - - - \ No newline at end of file diff --git a/samples/aspnetcore/BasicSample/Controllers/HelloWorldController.cs b/samples/aspnetcore/BasicSample/Controllers/HelloWorldController.cs index cb760e65..18ef0a81 100644 --- a/samples/aspnetcore/BasicSample/Controllers/HelloWorldController.cs +++ b/samples/aspnetcore/BasicSample/Controllers/HelloWorldController.cs @@ -1,17 +1,12 @@ namespace Microsoft.Examples.Controllers { - using AspNetCore.Mvc.Routing; using AspNetCore.Routing; - using Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/v{version:apiVersion}/[controller]" )] - public class HelloWorldController : Controller + public class HelloWorldController : ControllerBase { // GET api/v{version}/helloworld [HttpGet] diff --git a/samples/aspnetcore/BasicSample/Controllers/Values2Controller.cs b/samples/aspnetcore/BasicSample/Controllers/Values2Controller.cs index d28dfc02..ff430ab3 100644 --- a/samples/aspnetcore/BasicSample/Controllers/Values2Controller.cs +++ b/samples/aspnetcore/BasicSample/Controllers/Values2Controller.cs @@ -1,17 +1,14 @@ namespace Microsoft.Examples.Controllers { using Microsoft.AspNetCore.Mvc; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + [ApiController] [ApiVersion( "2.0" )] [Route( "api/values" )] - public class Values2Controller : Controller + public class Values2Controller : ControllerBase { // GET api/values?api-version=2.0 [HttpGet] - public string Get() => $"Controller = {GetType().Name}\nVersion = {HttpContext.GetRequestedApiVersion()}"; + public string Get( ApiVersion apiVersion ) => $"Controller = {GetType().Name}\nVersion = {apiVersion}"; } -} +} \ No newline at end of file diff --git a/samples/aspnetcore/BasicSample/Controllers/ValuesController.cs b/samples/aspnetcore/BasicSample/Controllers/ValuesController.cs index ef7f5472..7115910a 100644 --- a/samples/aspnetcore/BasicSample/Controllers/ValuesController.cs +++ b/samples/aspnetcore/BasicSample/Controllers/ValuesController.cs @@ -1,14 +1,11 @@ namespace Microsoft.Examples.Controllers { using Microsoft.AspNetCore.Mvc; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/[controller]" )] - public class ValuesController : Controller + public class ValuesController : ControllerBase { // GET api/values?api-version=1.0 [HttpGet] diff --git a/samples/aspnetcore/BasicSample/Program.cs b/samples/aspnetcore/BasicSample/Program.cs index 915cc391..3ef6eb9c 100644 --- a/samples/aspnetcore/BasicSample/Program.cs +++ b/samples/aspnetcore/BasicSample/Program.cs @@ -1,25 +1,16 @@ namespace Microsoft.Examples { + using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - public class Program + public static class Program { - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .Build(); + public static void Main( string[] args ) => + CreateWebHostBuilder( args ).Build().Run(); - host.Run(); - } + public static IWebHostBuilder CreateWebHostBuilder( string[] args ) => + WebHost.CreateDefaultBuilder( args ) + .UseStartup(); } -} +} \ No newline at end of file diff --git a/samples/aspnetcore/BasicSample/Startup.cs b/samples/aspnetcore/BasicSample/Startup.cs index 35c2bfbf..aa6567ee 100644 --- a/samples/aspnetcore/BasicSample/Startup.cs +++ b/samples/aspnetcore/BasicSample/Startup.cs @@ -4,41 +4,34 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - - using Microsoft.AspNetCore.Mvc.Routing; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; public class Startup { - public Startup( IHostingEnvironment env ) + public Startup( IConfiguration configuration ) { - var builder = new ConfigurationBuilder() - .SetBasePath( env.ContentRootPath ) - .AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true ) - .AddJsonFile( $"appsettings.{env.EnvironmentName}.json", optional: true ) - .AddEnvironmentVariables(); - Configuration = builder.Build(); + Configuration = configuration; } - public IConfigurationRoot Configuration { get; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices( IServiceCollection services ) { - services.AddMvc(); - - // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" - services.AddApiVersioning( o => o.ReportApiVersions = true ); + // the sample application always uses the latest version, but you may want an explict version such as Version_2_2 + // note: Endpoint Routing is enabled by default; however, if you need legacy style routing via IRouter, change it to false + services.AddMvc( options => options.EnableEndpointRouting = true ).SetCompatibilityVersion( Latest ); + services.AddApiVersioning( + options => + { + // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; + } ); } - public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory ) + public void Configure( IApplicationBuilder app, IHostingEnvironment env ) { - loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) ); - loggerFactory.AddDebug(); app.UseMvc(); } } -} +} \ No newline at end of file diff --git a/samples/aspnetcore/BasicSample/appsettings.json b/samples/aspnetcore/BasicSample/appsettings.json index fa8ce71a..d713e815 100644 --- a/samples/aspnetcore/BasicSample/appsettings.json +++ b/samples/aspnetcore/BasicSample/appsettings.json @@ -1,10 +1,8 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning" } - } -} + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/samples/aspnetcore/ByNamespaceSample/ByNamespaceSample.csproj b/samples/aspnetcore/ByNamespaceSample/ByNamespaceSample.csproj index 0f0062bb..4ae09dda 100644 --- a/samples/aspnetcore/ByNamespaceSample/ByNamespaceSample.csproj +++ b/samples/aspnetcore/ByNamespaceSample/ByNamespaceSample.csproj @@ -1,25 +1,18 @@  - netcoreapp2.0 + netcoreapp2.2 + InProcess + Microsoft.Examples - - PreserveNewest - + + - - - - - - - - \ No newline at end of file diff --git a/samples/aspnetcore/ByNamespaceSample/Program.cs b/samples/aspnetcore/ByNamespaceSample/Program.cs index ce02b86a..3ef6eb9c 100644 --- a/samples/aspnetcore/ByNamespaceSample/Program.cs +++ b/samples/aspnetcore/ByNamespaceSample/Program.cs @@ -1,24 +1,16 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; - -namespace Microsoft.Examples +namespace Microsoft.Examples { - public class Program + using Microsoft.AspNetCore; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + + public static class Program { - public static void Main( string[] args ) - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot( Directory.GetCurrentDirectory() ) - .UseIISIntegration() - .UseStartup() - .Build(); + public static void Main( string[] args ) => + CreateWebHostBuilder( args ).Build().Run(); - host.Run(); - } + public static IWebHostBuilder CreateWebHostBuilder( string[] args ) => + WebHost.CreateDefaultBuilder( args ) + .UseStartup(); } } \ No newline at end of file diff --git a/samples/aspnetcore/ByNamespaceSample/Startup.cs b/samples/aspnetcore/ByNamespaceSample/Startup.cs index bf99dedb..26a9a9f6 100644 --- a/samples/aspnetcore/ByNamespaceSample/Startup.cs +++ b/samples/aspnetcore/ByNamespaceSample/Startup.cs @@ -1,18 +1,27 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Versioning.Conventions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.Examples +namespace Microsoft.Examples { + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc.Versioning.Conventions; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; + public class Startup { + public Startup( IConfiguration configuration ) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices( IServiceCollection services ) { - services.AddMvc(); + // the sample application always uses the latest version, but you may want an explict version such as Version_2_2 + // note: Endpoint Routing is enabled by default; however, if you need legacy style routing via IRouter, change it to false + services.AddMvc( options => options.EnableEndpointRouting = true ).SetCompatibilityVersion( Latest ); services.AddApiVersioning( options => { @@ -25,15 +34,8 @@ public void ConfigureServices( IServiceCollection services ) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory ) + public void Configure( IApplicationBuilder app, IHostingEnvironment env ) { - loggerFactory.AddConsole(); - - if ( env.IsDevelopment() ) - { - app.UseDeveloperExceptionPage(); - } - app.UseMvc(); } } diff --git a/samples/aspnetcore/ByNamespaceSample/V1/Controllers/AgreementsController.cs b/samples/aspnetcore/ByNamespaceSample/V1/Controllers/AgreementsController.cs index f18b4cf3..b6b6dec8 100644 --- a/samples/aspnetcore/ByNamespaceSample/V1/Controllers/AgreementsController.cs +++ b/samples/aspnetcore/ByNamespaceSample/V1/Controllers/AgreementsController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Models; + [ApiController] [Route( "[controller]" )] [Route( "v{version:apiVersion}/[controller]" )] - public class AgreementsController : Controller + public class AgreementsController : ControllerBase { // GET ~/v1/agreements/{accountId} // GET ~/agreements/{accountId}?api-version=1.0 diff --git a/samples/aspnetcore/ByNamespaceSample/V1/Controllers/OrdersController.cs b/samples/aspnetcore/ByNamespaceSample/V1/Controllers/OrdersController.cs index a918e3de..15581485 100644 --- a/samples/aspnetcore/ByNamespaceSample/V1/Controllers/OrdersController.cs +++ b/samples/aspnetcore/ByNamespaceSample/V1/Controllers/OrdersController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Models; + [ApiController] [Route( "[controller]" )] [Route( "v{version:apiVersion}/[controller]" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { // GET ~/v1/orders/{accountId} // GET ~/orders/{accountId}?api-version=1.0 diff --git a/samples/aspnetcore/ByNamespaceSample/V2/Controllers/AgreementsController.cs b/samples/aspnetcore/ByNamespaceSample/V2/Controllers/AgreementsController.cs index daedce1f..bc53c767 100644 --- a/samples/aspnetcore/ByNamespaceSample/V2/Controllers/AgreementsController.cs +++ b/samples/aspnetcore/ByNamespaceSample/V2/Controllers/AgreementsController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Models; + [ApiController] [Route( "[controller]" )] [Route( "v{version:apiVersion}/[controller]" )] - public class AgreementsController : Controller + public class AgreementsController : ControllerBase { // GET ~/v2/agreements/{accountId} // GET ~/agreements/{accountId}?api-version=2.0 diff --git a/samples/aspnetcore/ByNamespaceSample/V2/Controllers/OrdersController.cs b/samples/aspnetcore/ByNamespaceSample/V2/Controllers/OrdersController.cs index d8cd6e48..05d7da3b 100644 --- a/samples/aspnetcore/ByNamespaceSample/V2/Controllers/OrdersController.cs +++ b/samples/aspnetcore/ByNamespaceSample/V2/Controllers/OrdersController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Models; + [ApiController] [Route( "[controller]" )] [Route( "v{version:apiVersion}/[controller]" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { // GET ~/v2/orders/{accountId} // GET ~/orders/{accountId}?api-version=2.0 diff --git a/samples/aspnetcore/ByNamespaceSample/V3/Controllers/AgreementsController.cs b/samples/aspnetcore/ByNamespaceSample/V3/Controllers/AgreementsController.cs index 7eca09c2..5aceb53f 100644 --- a/samples/aspnetcore/ByNamespaceSample/V3/Controllers/AgreementsController.cs +++ b/samples/aspnetcore/ByNamespaceSample/V3/Controllers/AgreementsController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Models; + [ApiController] [Route( "[controller]" )] [Route( "v{version:apiVersion}/[controller]" )] - public class AgreementsController : Controller + public class AgreementsController : ControllerBase { // GET ~/v3/agreements/{accountId} // GET ~/agreements/{accountId}?api-version=3.0 diff --git a/samples/aspnetcore/ByNamespaceSample/V3/Controllers/OrdersController.cs b/samples/aspnetcore/ByNamespaceSample/V3/Controllers/OrdersController.cs index 17a33e8b..a1460d4d 100644 --- a/samples/aspnetcore/ByNamespaceSample/V3/Controllers/OrdersController.cs +++ b/samples/aspnetcore/ByNamespaceSample/V3/Controllers/OrdersController.cs @@ -3,13 +3,14 @@ using Microsoft.AspNetCore.Mvc; using Models; + [ApiController] [Route( "[controller]" )] [Route( "v{version:apiVersion}/[controller]" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { // GET ~/v3/orders/{accountId} // GET ~/orders/{accountId}?api-version=3.0 [HttpGet( "{accountId}" )] public IActionResult Get( string accountId, ApiVersion apiVersion ) => Ok( new Order( GetType().FullName, accountId, apiVersion.ToString() ) ); } -} +} \ No newline at end of file diff --git a/samples/aspnetcore/ByNamespaceSample/appsettings.json b/samples/aspnetcore/ByNamespaceSample/appsettings.json index fa8ce71a..d713e815 100644 --- a/samples/aspnetcore/ByNamespaceSample/appsettings.json +++ b/samples/aspnetcore/ByNamespaceSample/appsettings.json @@ -1,10 +1,8 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning" } - } -} + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/samples/aspnetcore/ConventionsSample/Controllers/HelloWorldController.cs b/samples/aspnetcore/ConventionsSample/Controllers/HelloWorldController.cs index 8bd298c9..b6719178 100644 --- a/samples/aspnetcore/ConventionsSample/Controllers/HelloWorldController.cs +++ b/samples/aspnetcore/ConventionsSample/Controllers/HelloWorldController.cs @@ -1,13 +1,10 @@ namespace Microsoft.Examples.Controllers { using Microsoft.AspNetCore.Mvc; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + [ApiController] [Route( "api/v{version:apiVersion}/[controller]" )] - public class HelloWorldController : Controller + public class HelloWorldController : ControllerBase { // GET api/v{version}/helloworld [HttpGet] diff --git a/samples/aspnetcore/ConventionsSample/Controllers/Values2Controller.cs b/samples/aspnetcore/ConventionsSample/Controllers/Values2Controller.cs index 7cb2d6fa..03774d76 100644 --- a/samples/aspnetcore/ConventionsSample/Controllers/Values2Controller.cs +++ b/samples/aspnetcore/ConventionsSample/Controllers/Values2Controller.cs @@ -1,28 +1,25 @@ namespace Microsoft.Examples.Controllers { using Microsoft.AspNetCore.Mvc; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + [ApiController] [Route( "api/values" )] - public class Values2Controller : Controller + public class Values2Controller : ControllerBase { // GET api/values?api-version=2.0 [HttpGet] - public string Get() => $"Controller = {GetType().Name}\nVersion = {HttpContext.GetRequestedApiVersion()}"; + public string Get( ApiVersion apiVersion ) => $"Controller = {GetType().Name}\nVersion = {apiVersion}"; // GET api/values/{id}?api-version=2.0 [HttpGet( "{id:int}" )] - public string Get( int id ) => $"Controller = {GetType().Name}\nId = {id}\nVersion = {HttpContext.GetRequestedApiVersion()}"; + public string Get( int id, ApiVersion apiVersion ) => $"Controller = {GetType().Name}\nId = {id}\nVersion = {apiVersion}"; // GET api/values?api-version=3.0 [HttpGet] - public string GetV3() => $"Controller = {GetType().Name}\nVersion = {HttpContext.GetRequestedApiVersion()}"; + public string GetV3( ApiVersion apiVersion ) => $"Controller = {GetType().Name}\nVersion = {apiVersion}"; // GET api/values/{id}?api-version=3.0 [HttpGet( "{id:int}" )] - public string GetV3( int id ) => $"Controller = {GetType().Name}\nId = {id}\nVersion = {HttpContext.GetRequestedApiVersion()}"; + public string GetV3( int id, ApiVersion apiVersion ) => $"Controller = {GetType().Name}\nId = {id}\nVersion = {apiVersion}"; } } \ No newline at end of file diff --git a/samples/aspnetcore/ConventionsSample/Controllers/ValuesController.cs b/samples/aspnetcore/ConventionsSample/Controllers/ValuesController.cs index c80463ed..3f631b96 100644 --- a/samples/aspnetcore/ConventionsSample/Controllers/ValuesController.cs +++ b/samples/aspnetcore/ConventionsSample/Controllers/ValuesController.cs @@ -1,13 +1,10 @@ namespace Microsoft.Examples.Controllers { using Microsoft.AspNetCore.Mvc; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + [ApiController] [Route( "api/[controller]" )] - public class ValuesController : Controller + public class ValuesController : ControllerBase { // GET api/values?api-version=1.0 [HttpGet] diff --git a/samples/aspnetcore/ConventionsSample/ConventionsSample.csproj b/samples/aspnetcore/ConventionsSample/ConventionsSample.csproj index 0f0062bb..458c8aa3 100644 --- a/samples/aspnetcore/ConventionsSample/ConventionsSample.csproj +++ b/samples/aspnetcore/ConventionsSample/ConventionsSample.csproj @@ -1,25 +1,18 @@  - netcoreapp2.0 + netcoreapp2.2 + InProcess + Microsoft.Examples - - PreserveNewest - + + - + - - - - - - - - \ No newline at end of file diff --git a/samples/aspnetcore/ConventionsSample/Program.cs b/samples/aspnetcore/ConventionsSample/Program.cs index 06c6a4ae..3ef6eb9c 100644 --- a/samples/aspnetcore/ConventionsSample/Program.cs +++ b/samples/aspnetcore/ConventionsSample/Program.cs @@ -1,25 +1,16 @@ namespace Microsoft.Examples { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; - public class Program + public static class Program { - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .Build(); + public static void Main( string[] args ) => + CreateWebHostBuilder( args ).Build().Run(); - host.Run(); - } + public static IWebHostBuilder CreateWebHostBuilder( string[] args ) => + WebHost.CreateDefaultBuilder( args ) + .UseStartup(); } } \ No newline at end of file diff --git a/samples/aspnetcore/ConventionsSample/Startup.cs b/samples/aspnetcore/ConventionsSample/Startup.cs index 7c1871b7..f09315eb 100644 --- a/samples/aspnetcore/ConventionsSample/Startup.cs +++ b/samples/aspnetcore/ConventionsSample/Startup.cs @@ -3,45 +3,41 @@ using Controllers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Versioning.Conventions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; public class Startup { - public Startup( IHostingEnvironment env ) + public Startup( IConfiguration configuration ) { - var builder = new ConfigurationBuilder() - .SetBasePath( env.ContentRootPath ) - .AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true ) - .AddJsonFile( $"appsettings.{env.EnvironmentName}.json", optional: true ) - .AddEnvironmentVariables(); - Configuration = builder.Build(); + Configuration = configuration; } - public IConfigurationRoot Configuration { get; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices( IServiceCollection services ) { - services.AddMvc(); + // the sample application always uses the latest version, but you may want an explict version such as Version_2_2 + // note: Endpoint Routing is enabled by default; however, if you need legacy style routing via IRouter, change it to false + services.AddMvc( options => options.EnableEndpointRouting = true ).SetCompatibilityVersion( Latest ); services.AddApiVersioning( options => { // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; - // apply api versions using conventions rather than attributes options.Conventions.Controller().HasApiVersion( 1, 0 ); + options.Conventions.Controller() .HasApiVersion( 2, 0 ) .HasApiVersion( 3, 0 ) - .Action( c => c.GetV3() ).MapToApiVersion( 3, 0 ) - .Action( c => c.GetV3( default( int ) ) ).MapToApiVersion( 3, 0 ); + .Action( c => c.GetV3( default ) ).MapToApiVersion( 3, 0 ) + .Action( c => c.GetV3( default, default ) ).MapToApiVersion( 3, 0 ); + options.Conventions.Controller() .HasApiVersion( 1, 0 ) .HasApiVersion( 2, 0 ) @@ -49,10 +45,9 @@ public void ConfigureServices( IServiceCollection services ) } ); } - public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory ) + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure( IApplicationBuilder app, IHostingEnvironment env ) { - loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) ); - loggerFactory.AddDebug(); app.UseMvc(); } } diff --git a/samples/aspnetcore/ConventionsSample/appsettings.json b/samples/aspnetcore/ConventionsSample/appsettings.json index fa8ce71a..d713e815 100644 --- a/samples/aspnetcore/ConventionsSample/appsettings.json +++ b/samples/aspnetcore/ConventionsSample/appsettings.json @@ -1,10 +1,8 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning" } - } -} + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/samples/aspnetcore/ODataBasicSample/ODataBasicSample.csproj b/samples/aspnetcore/ODataBasicSample/ODataBasicSample.csproj index b7f3c012..269a2a67 100644 --- a/samples/aspnetcore/ODataBasicSample/ODataBasicSample.csproj +++ b/samples/aspnetcore/ODataBasicSample/ODataBasicSample.csproj @@ -1,25 +1,18 @@  - netcoreapp2.0 + netcoreapp2.2 + InProcess + Microsoft.Examples - - PreserveNewest - + + - + - - - - - - - - \ No newline at end of file diff --git a/samples/aspnetcore/ODataBasicSample/Program.cs b/samples/aspnetcore/ODataBasicSample/Program.cs index 915cc391..3ef6eb9c 100644 --- a/samples/aspnetcore/ODataBasicSample/Program.cs +++ b/samples/aspnetcore/ODataBasicSample/Program.cs @@ -1,25 +1,16 @@ namespace Microsoft.Examples { + using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - public class Program + public static class Program { - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .Build(); + public static void Main( string[] args ) => + CreateWebHostBuilder( args ).Build().Run(); - host.Run(); - } + public static IWebHostBuilder CreateWebHostBuilder( string[] args ) => + WebHost.CreateDefaultBuilder( args ) + .UseStartup(); } -} +} \ No newline at end of file diff --git a/samples/aspnetcore/ODataBasicSample/Startup.cs b/samples/aspnetcore/ODataBasicSample/Startup.cs index 1daf56e0..af1d6744 100644 --- a/samples/aspnetcore/ODataBasicSample/Startup.cs +++ b/samples/aspnetcore/ODataBasicSample/Startup.cs @@ -6,35 +6,35 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; public class Startup { - public Startup( IHostingEnvironment env ) + public Startup( IConfiguration configuration ) { - var builder = new ConfigurationBuilder() - .SetBasePath( env.ContentRootPath ) - .AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true ) - .AddJsonFile( $"appsettings.{env.EnvironmentName}.json", optional: true ) - .AddEnvironmentVariables(); - Configuration = builder.Build(); + Configuration = configuration; } - public IConfigurationRoot Configuration { get; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices( IServiceCollection services ) { - services.AddMvc(); - - // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" - services.AddApiVersioning( options => options.ReportApiVersions = true ); + // the sample application always uses the latest version, but you may want an explict version such as Version_2_2 + // note: Endpoint Routing is enabled by default; however, it is unsupported by OData and MUST be false + services.AddMvc( options => options.EnableEndpointRouting = false ).SetCompatibilityVersion( Latest ); + services.AddApiVersioning( + options => + { + // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; + } ); services.AddOData().EnableApiVersioning(); } - public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, VersionedODataModelBuilder modelBuilder ) + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure( IApplicationBuilder app, IHostingEnvironment env, VersionedODataModelBuilder modelBuilder ) { - loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) ); - loggerFactory.AddDebug(); app.UseMvc( routeBuilder => { diff --git a/samples/aspnetcore/ODataBasicSample/appsettings.json b/samples/aspnetcore/ODataBasicSample/appsettings.json index fa8ce71a..d713e815 100644 --- a/samples/aspnetcore/ODataBasicSample/appsettings.json +++ b/samples/aspnetcore/ODataBasicSample/appsettings.json @@ -1,10 +1,8 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning" } - } -} + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Program.cs b/samples/aspnetcore/SwaggerODataSample/Program.cs index db17b9b5..9012829f 100644 --- a/samples/aspnetcore/SwaggerODataSample/Program.cs +++ b/samples/aspnetcore/SwaggerODataSample/Program.cs @@ -6,22 +6,22 @@ /// /// Represents the current application. /// - public class Program + public static class Program { /// /// The main entry point to the application. /// /// The arguments provides at start-up, if any. - public static void Main( string[] args ) => BuildWebHost( args ).Run(); + public static void Main( string[] args ) => + CreateWebHostBuilder( args ).Build().Run(); /// /// Builds a new web host for the application. /// /// The command-line arguments, if any. - /// A newly constructed web host. - public static IWebHost BuildWebHost( string[] args ) => + /// A new web host builder. + public static IWebHostBuilder CreateWebHostBuilder( string[] args ) => WebHost.CreateDefaultBuilder( args ) - .UseStartup() - .Build(); + .UseStartup(); } } \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Startup.cs b/samples/aspnetcore/SwaggerODataSample/Startup.cs index 94a566bc..2d25f399 100644 --- a/samples/aspnetcore/SwaggerODataSample/Startup.cs +++ b/samples/aspnetcore/SwaggerODataSample/Startup.cs @@ -13,6 +13,7 @@ using System.IO; using System.Reflection; using static Microsoft.AspNet.OData.Query.AllowedQueryOptions; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; /// /// Represents the startup process for the application. @@ -25,7 +26,9 @@ public class Startup /// The collection of services to configure the application with. public void ConfigureServices( IServiceCollection services ) { - services.AddMvc(); + // the sample application always uses the latest version, but you may want an explict version such as Version_2_2 + // note: Endpoint Routing is enabled by default; however, it is unsupported by OData and MUST be false + services.AddMvc( options => options.EnableEndpointRouting = false ).SetCompatibilityVersion( Latest ); services.AddApiVersioning( options => options.ReportApiVersions = true ); services.AddOData().EnableApiVersioning(); services.AddODataApiExplorer( @@ -41,10 +44,10 @@ public void ConfigureServices( IServiceCollection services ) // configure query options (which cannot otherwise be configured by OData conventions) options.QueryOptions.Controller() - .Action( c => c.Get( default( ODataQueryOptions ) ) ).Allow( Skip | Count ).AllowTop( 100 ); + .Action( c => c.Get( default ) ).Allow( Skip | Count ).AllowTop( 100 ); options.QueryOptions.Controller() - .Action( c => c.Get( default( ODataQueryOptions ) ) ).Allow( Skip | Count ).AllowTop( 100 ); + .Action( c => c.Get( default ) ).Allow( Skip | Count ).AllowTop( 100 ); } ); services.AddSwaggerGen( options => @@ -107,7 +110,7 @@ static Info CreateInfoForApiVersion( ApiVersionDescription description ) { var info = new Info() { - Title = $"Sample API {description.ApiVersion}", + Title = "Sample API", Version = description.ApiVersion.ToString(), Description = "A sample application with Swagger, Swashbuckle, and API versioning.", Contact = new Contact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, diff --git a/samples/aspnetcore/SwaggerODataSample/SwaggerDefaultValues.cs b/samples/aspnetcore/SwaggerODataSample/SwaggerDefaultValues.cs index 65ff33f7..fc3e8e04 100644 --- a/samples/aspnetcore/SwaggerODataSample/SwaggerDefaultValues.cs +++ b/samples/aspnetcore/SwaggerODataSample/SwaggerDefaultValues.cs @@ -28,24 +28,18 @@ public void Apply( Operation operation, OperationFilterContext context ) foreach ( var parameter in operation.Parameters.OfType() ) { var description = context.ApiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); - var routeInfo = description.RouteInfo; if ( parameter.Description == null ) { parameter.Description = description.ModelMetadata?.Description; } - if ( routeInfo == null ) - { - continue; - } - if ( parameter.Default == null ) { - parameter.Default = routeInfo.DefaultValue; + parameter.Default = description.DefaultValue; } - parameter.Required |= !routeInfo.IsOptional; + parameter.Required |= description.IsRequired; } } } diff --git a/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj b/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj index ca929c95..eed4cc1d 100644 --- a/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj +++ b/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj @@ -1,23 +1,21 @@  - netcoreapp2.0 + netcoreapp2.2 + InProcess Microsoft.Examples bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml - - + + + + - + - - - - - - + - + diff --git a/samples/aspnetcore/SwaggerODataSample/V2/PeopleController.cs b/samples/aspnetcore/SwaggerODataSample/V2/PeopleController.cs index 10929dc9..f1ed2974 100644 --- a/samples/aspnetcore/SwaggerODataSample/V2/PeopleController.cs +++ b/samples/aspnetcore/SwaggerODataSample/V2/PeopleController.cs @@ -3,8 +3,8 @@ using Microsoft.AspNet.OData; using Microsoft.AspNet.OData.Query; using Microsoft.AspNetCore.Mvc; - using Microsoft.Data.OData; using Microsoft.Examples.Models; + using Microsoft.OData; using System; using System.Collections.Generic; using System.Linq; diff --git a/samples/aspnetcore/SwaggerODataSample/V3/PeopleController.cs b/samples/aspnetcore/SwaggerODataSample/V3/PeopleController.cs index 5531ae12..c77c7782 100644 --- a/samples/aspnetcore/SwaggerODataSample/V3/PeopleController.cs +++ b/samples/aspnetcore/SwaggerODataSample/V3/PeopleController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.OData; using Microsoft.AspNet.OData.Query; using Microsoft.AspNetCore.Mvc; - using Microsoft.Data.OData; + using Microsoft.OData; using Microsoft.Examples.Models; using System; using System.Collections.Generic; diff --git a/samples/aspnetcore/SwaggerODataSample/appsettings.json b/samples/aspnetcore/SwaggerODataSample/appsettings.json new file mode 100644 index 00000000..d713e815 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerSample/Program.cs b/samples/aspnetcore/SwaggerSample/Program.cs index d3f7ab74..fd733cbd 100644 --- a/samples/aspnetcore/SwaggerSample/Program.cs +++ b/samples/aspnetcore/SwaggerSample/Program.cs @@ -1,28 +1,27 @@ namespace Microsoft.Examples { - using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; - using System.IO; /// /// Represents the current application. /// - public class Program + public static class Program { /// /// The main entry point to the application. /// - /// The arguments provides at start-up, if any. - public static void Main( string[] args ) - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot( Directory.GetCurrentDirectory() ) - .UseIISIntegration() - .UseStartup() - .Build(); + /// The arguments provided at start-up, if any. + public static void Main( string[] args ) => + CreateWebHostBuilder( args ).Build().Run(); - host.Run(); - } + /// + /// Builds a new web host for the application. + /// + /// The command-line arguments, if any. + /// A new web host builder. + public static IWebHostBuilder CreateWebHostBuilder( string[] args ) => + WebHost.CreateDefaultBuilder( args ) + .UseStartup(); } } \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerSample/Startup.cs b/samples/aspnetcore/SwaggerSample/Startup.cs index 78b7cb87..fbf784aa 100644 --- a/samples/aspnetcore/SwaggerSample/Startup.cs +++ b/samples/aspnetcore/SwaggerSample/Startup.cs @@ -5,11 +5,11 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; using Microsoft.Extensions.PlatformAbstractions; using Swashbuckle.AspNetCore.Swagger; using System.IO; using System.Reflection; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; /// /// Represents the startup process for the application. @@ -19,23 +19,17 @@ public class Startup /// /// Initializes a new instance of the class. /// - /// The current hosting environment. - public Startup( IHostingEnvironment env ) + /// The current configuration. + public Startup( IConfiguration configuration ) { - var builder = new ConfigurationBuilder() - .SetBasePath( env.ContentRootPath ) - .AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true ) - .AddJsonFile( $"appsettings.{env.EnvironmentName}.json", optional: true ) - .AddEnvironmentVariables(); - - Configuration = builder.Build(); + Configuration = configuration; } /// /// Gets the current configuration. /// /// The current application configuration. - public IConfigurationRoot Configuration { get; } + public IConfiguration Configuration { get; } /// /// Configures services for the application. @@ -43,20 +37,26 @@ public Startup( IHostingEnvironment env ) /// The collection of services to configure the application with. public void ConfigureServices( IServiceCollection services ) { - // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service - // note: the specified format code will format the version as "'v'major[.minor][-status]" + // the sample application always uses the latest version, but you may want an explict version such as Version_2_2 + // note: Endpoint Routing is enabled by default; however, if you need legacy style routing via IRouter, change it to false + services.AddMvc( options => options.EnableEndpointRouting = true ).SetCompatibilityVersion( Latest ); + services.AddApiVersioning( + options => + { + // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; + } ); services.AddVersionedApiExplorer( options => { + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" options.GroupNameFormat = "'v'VVV"; // note: this option is only necessary when versioning by url segment. the SubstitutionFormat // can also be used to control the format of the API version in route templates options.SubstituteApiVersionInUrl = true; } ); - - services.AddMvc(); - services.AddApiVersioning( options => options.ReportApiVersions = true ); services.AddSwaggerGen( options => { @@ -83,17 +83,13 @@ public void ConfigureServices( IServiceCollection services ) } /// - /// Configures the application using the provided builder, hosting environment, and logging factory. + /// Configures the application using the provided builder, hosting environment, and API version description provider. /// /// The current application builder. /// The current hosting environment. - /// The logging factory used for instrumentation. /// The API version descriptor provider used to enumerate defined API versions. - public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApiVersionDescriptionProvider provider ) + public void Configure( IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider ) { - loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) ); - loggerFactory.AddDebug(); - app.UseMvc(); app.UseSwagger(); app.UseSwaggerUI( @@ -121,7 +117,7 @@ static Info CreateInfoForApiVersion( ApiVersionDescription description ) { var info = new Info() { - Title = $"Sample API {description.ApiVersion}", + Title = "Sample API", Version = description.ApiVersion.ToString(), Description = "A sample application with Swagger, Swashbuckle, and API versioning.", Contact = new Contact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, diff --git a/samples/aspnetcore/SwaggerSample/SwaggerDefaultValues.cs b/samples/aspnetcore/SwaggerSample/SwaggerDefaultValues.cs index 65ff33f7..fc3e8e04 100644 --- a/samples/aspnetcore/SwaggerSample/SwaggerDefaultValues.cs +++ b/samples/aspnetcore/SwaggerSample/SwaggerDefaultValues.cs @@ -28,24 +28,18 @@ public void Apply( Operation operation, OperationFilterContext context ) foreach ( var parameter in operation.Parameters.OfType() ) { var description = context.ApiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); - var routeInfo = description.RouteInfo; if ( parameter.Description == null ) { parameter.Description = description.ModelMetadata?.Description; } - if ( routeInfo == null ) - { - continue; - } - if ( parameter.Default == null ) { - parameter.Default = routeInfo.DefaultValue; + parameter.Default = description.DefaultValue; } - parameter.Required |= !routeInfo.IsOptional; + parameter.Required |= description.IsRequired; } } } diff --git a/samples/aspnetcore/SwaggerSample/SwaggerSample.csproj b/samples/aspnetcore/SwaggerSample/SwaggerSample.csproj index 4a603647..57313d98 100644 --- a/samples/aspnetcore/SwaggerSample/SwaggerSample.csproj +++ b/samples/aspnetcore/SwaggerSample/SwaggerSample.csproj @@ -1,29 +1,21 @@  - netcoreapp2.0 + netcoreapp2.2 + InProcess Microsoft.Examples bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml - - PreserveNewest - + + + + - + - - - - - - - - - - \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerSample/V1/Controllers/OrdersController.cs b/samples/aspnetcore/SwaggerSample/V1/Controllers/OrdersController.cs index e2c6d70d..67f3a466 100644 --- a/samples/aspnetcore/SwaggerSample/V1/Controllers/OrdersController.cs +++ b/samples/aspnetcore/SwaggerSample/V1/Controllers/OrdersController.cs @@ -6,10 +6,11 @@ /// /// Represents a RESTful service of orders. /// + [ApiController] [ApiVersion( "1.0" )] [ApiVersion( "0.9", Deprecated = true )] [Route( "api/[controller]" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { /// /// Gets a single order. @@ -19,6 +20,7 @@ public class OrdersController : Controller /// The order was successfully retrieved. /// The order does not exist. [HttpGet( "{id:int}", Name = "GetOrderById" )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Order ), 200 )] [ProducesResponseType( 404 )] public IActionResult Get( int id ) => Ok( new Order() { Id = id, Customer = "John Doe" } ); @@ -32,6 +34,7 @@ public class OrdersController : Controller /// The order is invalid. [HttpPost] [MapToApiVersion( "1.0" )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Order ), 201 )] [ProducesResponseType( 400 )] public IActionResult Post( [FromBody] Order order ) diff --git a/samples/aspnetcore/SwaggerSample/V1/Controllers/PeopleController.cs b/samples/aspnetcore/SwaggerSample/V1/Controllers/PeopleController.cs index 4fd48a17..0a20474b 100644 --- a/samples/aspnetcore/SwaggerSample/V1/Controllers/PeopleController.cs +++ b/samples/aspnetcore/SwaggerSample/V1/Controllers/PeopleController.cs @@ -7,10 +7,11 @@ /// /// Represents a RESTful people service. /// + [ApiController] [ApiVersion( "1.0" )] [ApiVersion( "0.9", Deprecated = true )] [Route( "api/v{api-version:apiVersion}/[controller]" )] - public class PeopleController : Controller + public class PeopleController : ControllerBase { /// /// Gets a single person. @@ -20,6 +21,7 @@ public class PeopleController : Controller /// The person was successfully retrieved. /// The person does not exist. [HttpGet( "{id:int}" )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Person ), 200 )] [ProducesResponseType( 404 )] public IActionResult Get( int id ) => diff --git a/samples/aspnetcore/SwaggerSample/V2/Controllers/OrdersController.cs b/samples/aspnetcore/SwaggerSample/V2/Controllers/OrdersController.cs index 312cc223..9419770a 100644 --- a/samples/aspnetcore/SwaggerSample/V2/Controllers/OrdersController.cs +++ b/samples/aspnetcore/SwaggerSample/V2/Controllers/OrdersController.cs @@ -8,9 +8,10 @@ /// /// Represents a RESTful service of orders. /// + [ApiController] [ApiVersion( "2.0" )] [Route( "api/[controller]" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { const string ByIdRouteName = "GetOrderById-" + nameof( V2 ); @@ -20,6 +21,7 @@ public class OrdersController : Controller /// All available orders. /// The successfully retrieved orders. [HttpGet] + [Produces( "application/json" )] [ProducesResponseType( typeof( IEnumerable ), 200 )] public IActionResult Get() { @@ -41,6 +43,7 @@ public IActionResult Get() /// The order was successfully retrieved. /// The order does not exist. [HttpGet( "{id:int}", Name = ByIdRouteName )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Order ), 200 )] [ProducesResponseType( 400 )] [ProducesResponseType( 404 )] @@ -54,6 +57,7 @@ public IActionResult Get() /// The order was successfully placed. /// The order is invalid. [HttpPost] + [Produces( "application/json" )] [ProducesResponseType( typeof( Order ), 201 )] [ProducesResponseType( 400 )] public IActionResult Post( [FromBody] Order order ) diff --git a/samples/aspnetcore/SwaggerSample/V2/Controllers/PeopleController.cs b/samples/aspnetcore/SwaggerSample/V2/Controllers/PeopleController.cs index 4ca147f4..2f587c08 100644 --- a/samples/aspnetcore/SwaggerSample/V2/Controllers/PeopleController.cs +++ b/samples/aspnetcore/SwaggerSample/V2/Controllers/PeopleController.cs @@ -8,9 +8,10 @@ /// /// Represents a RESTful people service. /// + [ApiController] [ApiVersion( "2.0" )] [Route( "api/v{api-version:apiVersion}/[controller]" )] - public class PeopleController : Controller + public class PeopleController : ControllerBase { const string ByIdRouteName = "GetPersonById" + nameof( V2 ); @@ -20,6 +21,7 @@ public class PeopleController : Controller /// All available people. /// The successfully retrieved people. [HttpGet] + [Produces( "application/json" )] [ProducesResponseType( typeof( IEnumerable ), 200 )] public IActionResult Get() { @@ -59,6 +61,7 @@ public IActionResult Get() /// The person was successfully retrieved. /// The person does not exist. [HttpGet( "{id:int}", Name = ByIdRouteName )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Person ), 200 )] [ProducesResponseType( 404 )] public IActionResult Get( int id ) => diff --git a/samples/aspnetcore/SwaggerSample/V3/Controllers/OrdersController.cs b/samples/aspnetcore/SwaggerSample/V3/Controllers/OrdersController.cs index ea67d0d4..6c1551a7 100644 --- a/samples/aspnetcore/SwaggerSample/V3/Controllers/OrdersController.cs +++ b/samples/aspnetcore/SwaggerSample/V3/Controllers/OrdersController.cs @@ -8,9 +8,10 @@ /// /// Represents a RESTful service of orders. /// + [ApiController] [ApiVersion( "3.0" )] [Route( "api/[controller]" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { const string ByIdRouteName = "GetOrderById-" + nameof( V3 ); @@ -21,6 +22,7 @@ public class OrdersController : Controller /// Orders successfully retrieved. /// The order is invalid. [HttpGet] + [Produces( "application/json" )] [ProducesResponseType( typeof( IEnumerable ), 200 )] [ProducesResponseType( 400 )] public IActionResult Get() @@ -43,6 +45,7 @@ public IActionResult Get() /// The order was successfully retrieved. /// The order does not exist. [HttpGet( "{id:int}", Name = ByIdRouteName )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Order ), 200 )] [ProducesResponseType( 400 )] [ProducesResponseType( 404 )] @@ -56,6 +59,7 @@ public IActionResult Get() /// The order was successfully placed. /// The order is invalid. [HttpPost] + [Produces( "application/json" )] [ProducesResponseType( typeof( Order ), 201 )] [ProducesResponseType( 400 )] public IActionResult Post( [FromBody] Order order ) diff --git a/samples/aspnetcore/SwaggerSample/V3/Controllers/PeopleController.cs b/samples/aspnetcore/SwaggerSample/V3/Controllers/PeopleController.cs index 3ed4ee93..c47550d5 100644 --- a/samples/aspnetcore/SwaggerSample/V3/Controllers/PeopleController.cs +++ b/samples/aspnetcore/SwaggerSample/V3/Controllers/PeopleController.cs @@ -8,9 +8,10 @@ /// /// Represents a RESTful people service. /// + [ApiController] [ApiVersion( "3.0" )] [Route( "api/v{api-version:apiVersion}/[controller]" )] - public class PeopleController : Controller + public class PeopleController : ControllerBase { const string ByIdRouteName = "GetPersonById" + nameof( V3 ); @@ -20,6 +21,7 @@ public class PeopleController : Controller /// All available people. /// The successfully retrieved people. [HttpGet] + [Produces( "application/json" )] [ProducesResponseType( typeof( IEnumerable ), 200 )] public IActionResult Get() { @@ -62,6 +64,7 @@ public IActionResult Get() /// The person was successfully retrieved. /// The person does not exist. [HttpGet( "{id:int}", Name = ByIdRouteName )] + [Produces( "application/json" )] [ProducesResponseType( typeof( Person ), 200 )] [ProducesResponseType( 404 )] public IActionResult Get( int id ) => @@ -83,6 +86,7 @@ public IActionResult Get( int id ) => /// The person was successfully created. /// The person was invalid. [HttpPost] + [Produces( "application/json" )] [ProducesResponseType( typeof( Person ), 201 )] [ProducesResponseType( 400 )] public IActionResult Post( [FromBody] Person person ) diff --git a/samples/aspnetcore/SwaggerSample/appsettings.json b/samples/aspnetcore/SwaggerSample/appsettings.json index fa8ce71a..d713e815 100644 --- a/samples/aspnetcore/SwaggerSample/appsettings.json +++ b/samples/aspnetcore/SwaggerSample/appsettings.json @@ -1,10 +1,8 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning" } - } -} + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/samples/directory.build.props b/samples/directory.build.props new file mode 100644 index 00000000..4132bd00 --- /dev/null +++ b/samples/directory.build.props @@ -0,0 +1,8 @@ + + + + + latest + + + \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj b/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj index ef7dcc79..e1fec955 100644 --- a/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj +++ b/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj @@ -1,195 +1,23 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {E496EED0-F8C9-4FE9-83E6-75E47A3C41A1} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - AdvancedODataWebApiSample - v4.5 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\..\packages\Microsoft.AspNet.OData.7.0.1\lib\net45\Microsoft.AspNet.OData.dll - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\..\..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - - - ..\..\..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\..\..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - - - - - - - - WebApi - - - WebApi.OData - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - - - {48a2b488-23ab-4c83-ae30-0b8b735c4562} - Microsoft.AspNet.OData.Versioning - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 1044 - / - http://localhost:1044/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/Program.cs b/samples/webapi/AdvancedODataWebApiSample/Program.cs new file mode 100644 index 00000000..4435130e --- /dev/null +++ b/samples/webapi/AdvancedODataWebApiSample/Program.cs @@ -0,0 +1,35 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Threading; + + public class Program + { + const string Url = "http://localhost:9006/"; + const string LaunchUrl = Url + "api"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/AdvancedODataWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index d2a17363..00000000 --- a/samples/webapi/AdvancedODataWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "AdvancedODataWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "AdvancedODataWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2016" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "e496eed0-f8c9-4fe9-83e6-75e47a3c41a1" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/AdvancedODataWebApiSample/Startup.cs b/samples/webapi/AdvancedODataWebApiSample/Startup.cs index 5237f5c8..708cd1fa 100644 --- a/samples/webapi/AdvancedODataWebApiSample/Startup.cs +++ b/samples/webapi/AdvancedODataWebApiSample/Startup.cs @@ -11,6 +11,7 @@ namespace Microsoft.Examples using Microsoft.OData; using Microsoft.OData.UriParser; using Microsoft.Web.Http.Versioning; + using System; using System.Web.Http; using static Microsoft.OData.ODataUrlKeyDelimiter; using static Microsoft.OData.ServiceLifetime; @@ -24,11 +25,17 @@ public void Configuration( IAppBuilder appBuilder ) var httpServer = new HttpServer( configuration ); configuration.AddApiVersioning( - o => + options => { - o.ReportApiVersions = true; - o.AssumeDefaultVersionWhenUnspecified = true; - o.ApiVersionReader = ApiVersionReader.Combine( + // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; + + // allows a client to make a request without specifying an api version. the value of + // options.DefaultApiVersion will be 'assumed'; this is meant to grandfather in legacy apis + options.AssumeDefaultVersionWhenUnspecified = true; + + // allow multiple locations to request an api version + options.ApiVersionReader = ApiVersionReader.Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader( "api-version", "x-ms-version" ) ); } ); @@ -44,8 +51,14 @@ public void Configuration( IAppBuilder appBuilder ) var models = modelBuilder.GetEdmModels(); var batchHandler = new DefaultODataBatchHandler( httpServer ); + // NOTE: when you mix OData and non-Data controllers in Web API, it's RECOMMENDED to only use + // convention-based routing. using attribute routing may not work as expected due to limitations + // in the underlying routing system. the order of route registration is important as well. + // + // DO NOT use configuration.MapHttpAttributeRoutes(); configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureContainer, batchHandler ); configuration.Routes.MapHttpRoute( "orders", "api/{controller}/{id}", new { id = Optional } ); + appBuilder.UseWebApi( httpServer ); } @@ -54,5 +67,20 @@ static void ConfigureContainer( IContainerBuilder builder ) builder.AddService( Singleton, sp => new DefaultODataPathHandler() { UrlKeyDelimiter = Parentheses } ); builder.AddService( Singleton, sp => new UnqualifiedCallAndEnumPrefixFreeResolver() { EnableCaseInsensitive = true } ); } + + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } } } \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/Web.Debug.config b/samples/webapi/AdvancedODataWebApiSample/Web.Debug.config deleted file mode 100644 index 2e302f9f..00000000 --- a/samples/webapi/AdvancedODataWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/Web.Release.config b/samples/webapi/AdvancedODataWebApiSample/Web.Release.config deleted file mode 100644 index c3584446..00000000 --- a/samples/webapi/AdvancedODataWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/Web.config b/samples/webapi/AdvancedODataWebApiSample/Web.config deleted file mode 100644 index 0eee3764..00000000 --- a/samples/webapi/AdvancedODataWebApiSample/Web.config +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/packages.config b/samples/webapi/AdvancedODataWebApiSample/packages.config deleted file mode 100644 index 31a889a4..00000000 --- a/samples/webapi/AdvancedODataWebApiSample/packages.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj b/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj index 957610a6..e1fec955 100644 --- a/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj +++ b/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj @@ -1,190 +1,23 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {8C09CD67-5153-413C-B870-2FC7488C2D53} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - BasicODataWebApiSample - v4.5 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\..\packages\Microsoft.AspNet.OData.7.0.1\lib\net45\Microsoft.AspNet.OData.dll - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\..\..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - - - ..\..\..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\..\..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - {48a2b488-23ab-4c83-ae30-0b8b735c4562} - Microsoft.AspNet.OData.Versioning - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - False - True - 4129 - / - http://localhost:4129/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/Program.cs b/samples/webapi/BasicODataWebApiSample/Program.cs new file mode 100644 index 00000000..40dcc411 --- /dev/null +++ b/samples/webapi/BasicODataWebApiSample/Program.cs @@ -0,0 +1,37 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Diagnostics; + using System.Threading; + + public class Program + { + const string Url = "http://localhost:9004/"; + const string LaunchUrl = Url + "api"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + Process.Start( LaunchUrl ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/BasicODataWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index c21c4bed..00000000 --- a/samples/webapi/BasicODataWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "BasicODataWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "BasicODataWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2016" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "8c09cd67-5153-413c-b870-2fc7488c2d53" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/BasicODataWebApiSample/Startup.cs b/samples/webapi/BasicODataWebApiSample/Startup.cs index a6fdcde5..42f4b164 100644 --- a/samples/webapi/BasicODataWebApiSample/Startup.cs +++ b/samples/webapi/BasicODataWebApiSample/Startup.cs @@ -10,6 +10,7 @@ namespace Microsoft.Examples using Microsoft.Examples.Configuration; using Microsoft.OData; using Microsoft.OData.UriParser; + using System; using System.Web.Http; using static Microsoft.OData.ODataUrlKeyDelimiter; using static Microsoft.OData.ServiceLifetime; @@ -22,7 +23,7 @@ public void Configuration( IAppBuilder appBuilder ) var httpServer = new HttpServer( configuration ); // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" - configuration.AddApiVersioning( o => o.ReportApiVersions = true ); + configuration.AddApiVersioning( options => options.ReportApiVersions = true ); var modelBuilder = new VersionedODataModelBuilder( configuration ) { @@ -35,8 +36,12 @@ public void Configuration( IAppBuilder appBuilder ) var models = modelBuilder.GetEdmModels(); var batchHandler = new DefaultODataBatchHandler( httpServer ); + // NOTE: you do NOT and should NOT use both the query string and url segment methods together. + // this configuration is merely illustrating that they can coexist and allows you to easily + // experiment with either configuration. one of these would be removed in a real application. configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureContainer, batchHandler ); - configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, ConfigureContainer ); + configuration.MapVersionedODataRoutes( "odata-bypath", "api/v{apiVersion}", models, ConfigureContainer ); + appBuilder.UseWebApi( httpServer ); } @@ -45,5 +50,20 @@ static void ConfigureContainer( IContainerBuilder builder ) builder.AddService( Singleton, sp => new DefaultODataPathHandler() { UrlKeyDelimiter = Parentheses } ); builder.AddService( Singleton, sp => new UnqualifiedCallAndEnumPrefixFreeResolver() { EnableCaseInsensitive = true } ); } + + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } } } \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/Web.Debug.config b/samples/webapi/BasicODataWebApiSample/Web.Debug.config deleted file mode 100644 index 2e302f9f..00000000 --- a/samples/webapi/BasicODataWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/Web.Release.config b/samples/webapi/BasicODataWebApiSample/Web.Release.config deleted file mode 100644 index c3584446..00000000 --- a/samples/webapi/BasicODataWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/Web.config b/samples/webapi/BasicODataWebApiSample/Web.config deleted file mode 100644 index 1e3e566e..00000000 --- a/samples/webapi/BasicODataWebApiSample/Web.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/packages.config b/samples/webapi/BasicODataWebApiSample/packages.config deleted file mode 100644 index 31a889a4..00000000 --- a/samples/webapi/BasicODataWebApiSample/packages.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/BasicWebApiSample.csproj b/samples/webapi/BasicWebApiSample/BasicWebApiSample.csproj index 602f1f82..6d72d965 100644 --- a/samples/webapi/BasicWebApiSample/BasicWebApiSample.csproj +++ b/samples/webapi/BasicWebApiSample/BasicWebApiSample.csproj @@ -1,163 +1,22 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {D1DF9ECC-7D2F-4982-8E45-BD690EF80906} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - BasicWebApiSample - v4.5 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 11008 - / - http://localhost:25282/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/Program.cs b/samples/webapi/BasicWebApiSample/Program.cs new file mode 100644 index 00000000..05dee1d5 --- /dev/null +++ b/samples/webapi/BasicWebApiSample/Program.cs @@ -0,0 +1,34 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Threading; + + public class Program + { + const string Url = "http://localhost:9000/"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/BasicWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index 42477829..00000000 --- a/samples/webapi/BasicWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "BasicWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "BasicWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2016" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "d1df9ecc-7d2f-4982-8e45-bd690ef80906" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/BasicWebApiSample/Startup.cs b/samples/webapi/BasicWebApiSample/Startup.cs index f1803886..77632525 100644 --- a/samples/webapi/BasicWebApiSample/Startup.cs +++ b/samples/webapi/BasicWebApiSample/Startup.cs @@ -4,6 +4,7 @@ namespace Microsoft.Examples { using global::Owin; using Microsoft.Web.Http.Routing; + using System; using System.Web.Http; using System.Web.Http.Routing; @@ -17,9 +18,24 @@ public void Configuration( IAppBuilder builder ) var httpServer = new HttpServer( configuration ); // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" - configuration.AddApiVersioning( o => o.ReportApiVersions = true ); + configuration.AddApiVersioning( options => options.ReportApiVersions = true ); configuration.MapHttpAttributeRoutes( constraintResolver ); builder.UseWebApi( httpServer ); } + + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } } } \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/Web.Debug.config b/samples/webapi/BasicWebApiSample/Web.Debug.config deleted file mode 100644 index 2e302f9f..00000000 --- a/samples/webapi/BasicWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/Web.Release.config b/samples/webapi/BasicWebApiSample/Web.Release.config deleted file mode 100644 index c3584446..00000000 --- a/samples/webapi/BasicWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/Web.config b/samples/webapi/BasicWebApiSample/Web.config deleted file mode 100644 index 693df44d..00000000 --- a/samples/webapi/BasicWebApiSample/Web.config +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/BasicWebApiSample/packages.config b/samples/webapi/BasicWebApiSample/packages.config deleted file mode 100644 index 01cf5055..00000000 --- a/samples/webapi/BasicWebApiSample/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/ByNamespaceWebApiSample.csproj b/samples/webapi/ByNamespaceWebApiSample/ByNamespaceWebApiSample.csproj index 744c545b..6d72d965 100644 --- a/samples/webapi/ByNamespaceWebApiSample/ByNamespaceWebApiSample.csproj +++ b/samples/webapi/ByNamespaceWebApiSample/ByNamespaceWebApiSample.csproj @@ -1,170 +1,22 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {A02A4245-3AEB-4549-9037-D89DFDC7E74D} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - ByNamespaceWebApiSample - v4.5 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - - - - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 1676 - / - http://localhost:1676/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/Program.cs b/samples/webapi/ByNamespaceWebApiSample/Program.cs new file mode 100644 index 00000000..0f38534f --- /dev/null +++ b/samples/webapi/ByNamespaceWebApiSample/Program.cs @@ -0,0 +1,34 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Threading; + + public class Program + { + const string Url = "http://localhost:9002/"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/ByNamespaceWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index f27d3a87..00000000 --- a/samples/webapi/ByNamespaceWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "ByNamespaceWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "ByNamespaceWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2016" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "a02a4245-3aeb-4549-9037-d89dfdc7e74d" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/ByNamespaceWebApiSample/Startup.cs b/samples/webapi/ByNamespaceWebApiSample/Startup.cs index 9f2c9159..e4bfc6c7 100644 --- a/samples/webapi/ByNamespaceWebApiSample/Startup.cs +++ b/samples/webapi/ByNamespaceWebApiSample/Startup.cs @@ -5,8 +5,8 @@ namespace Microsoft.Examples using global::Owin; using Microsoft.Web.Http.Routing; using Microsoft.Web.Http.Versioning.Conventions; + using System; using System.Web.Http; - using System.Web.Http.Routing; public class Startup { @@ -25,6 +25,9 @@ public void Configuration( IAppBuilder builder ) options.Conventions.Add( new VersionByNamespaceConvention() ); } ); + // NOTE: you do NOT and should NOT use both the query string and url segment methods together. + // this configuration is merely illustrating that they can coexist and allows you to easily + // experiment with either configuration. one of these would be removed in a real application. configuration.Routes.MapHttpRoute( "VersionedQueryString", "api/{controller}/{accountId}", @@ -38,5 +41,20 @@ public void Configuration( IAppBuilder builder ) builder.UseWebApi( httpServer ); } + + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } } } \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/Web.Debug.config b/samples/webapi/ByNamespaceWebApiSample/Web.Debug.config deleted file mode 100644 index 2e302f9f..00000000 --- a/samples/webapi/ByNamespaceWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/Web.Release.config b/samples/webapi/ByNamespaceWebApiSample/Web.Release.config deleted file mode 100644 index c3584446..00000000 --- a/samples/webapi/ByNamespaceWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/Web.config b/samples/webapi/ByNamespaceWebApiSample/Web.config deleted file mode 100644 index 8f8abb27..00000000 --- a/samples/webapi/ByNamespaceWebApiSample/Web.config +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ByNamespaceWebApiSample/packages.config b/samples/webapi/ByNamespaceWebApiSample/packages.config deleted file mode 100644 index 01cf5055..00000000 --- a/samples/webapi/ByNamespaceWebApiSample/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj b/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj index b45473a3..e1fec955 100644 --- a/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj +++ b/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj @@ -1,187 +1,23 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {9A22600C-7768-4D16-B67D-514F55942FAF} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - ConventionsODataWebApiSample - v4.5 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\..\packages\Microsoft.AspNet.OData.7.0.1\lib\net45\Microsoft.AspNet.OData.dll - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\..\..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - - - ..\..\..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\..\..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - {48a2b488-23ab-4c83-ae30-0b8b735c4562} - Microsoft.AspNet.OData.Versioning - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 3024 - / - http://localhost:3024/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/Program.cs b/samples/webapi/ConventionsODataWebApiSample/Program.cs new file mode 100644 index 00000000..26cd5953 --- /dev/null +++ b/samples/webapi/ConventionsODataWebApiSample/Program.cs @@ -0,0 +1,37 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Diagnostics; + using System.Threading; + + public class Program + { + const string Url = "http://localhost:9005/"; + const string LaunchUrl = Url + "api"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + Process.Start( LaunchUrl ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/ConventionsODataWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index af4d7e73..00000000 --- a/samples/webapi/ConventionsODataWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "ConventionsODataWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "ConventionsODataWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2016" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "9a22600c-7768-4d16-b67d-514f55942faf" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/ConventionsODataWebApiSample/Startup.cs b/samples/webapi/ConventionsODataWebApiSample/Startup.cs index c228297f..191a7d1b 100644 --- a/samples/webapi/ConventionsODataWebApiSample/Startup.cs +++ b/samples/webapi/ConventionsODataWebApiSample/Startup.cs @@ -12,6 +12,7 @@ namespace Microsoft.Examples using Microsoft.OData; using Microsoft.OData.UriParser; using Microsoft.Web.Http.Versioning.Conventions; + using System; using System.Web.Http; using static Microsoft.OData.ODataUrlKeyDelimiter; using static Microsoft.OData.ServiceLifetime; @@ -36,7 +37,7 @@ public void Configuration( IAppBuilder appBuilder ) options.Conventions.Controller() .HasApiVersion( 1, 0 ) .HasApiVersion( 2, 0 ) - .Action( c => c.Patch( default( int ), null, null ) ).MapToApiVersion( 2, 0 ); + .Action( c => c.Patch( default, default, default ) ).MapToApiVersion( 2, 0 ); options.Conventions.Controller() .HasApiVersion( 3, 0 ); @@ -53,8 +54,12 @@ public void Configuration( IAppBuilder appBuilder ) var models = modelBuilder.GetEdmModels(); var batchHandler = new DefaultODataBatchHandler( httpServer ); + // NOTE: you do NOT and should NOT use both the query string and url segment methods together. + // this configuration is merely illustrating that they can coexist and allows you to easily + // experiment with either configuration. one of these would be removed in a real application. configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureContainer, batchHandler ); - configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, ConfigureContainer ); + configuration.MapVersionedODataRoutes( "odata-bypath", "api/v{apiVersion}", models, ConfigureContainer ); + appBuilder.UseWebApi( httpServer ); } @@ -63,5 +68,20 @@ static void ConfigureContainer( IContainerBuilder builder ) builder.AddService( Singleton, sp => new DefaultODataPathHandler() { UrlKeyDelimiter = Parentheses } ); builder.AddService( Singleton, sp => new UnqualifiedCallAndEnumPrefixFreeResolver() { EnableCaseInsensitive = true } ); } + + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } } } \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/Web.Debug.config b/samples/webapi/ConventionsODataWebApiSample/Web.Debug.config deleted file mode 100644 index 2e302f9f..00000000 --- a/samples/webapi/ConventionsODataWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/Web.Release.config b/samples/webapi/ConventionsODataWebApiSample/Web.Release.config deleted file mode 100644 index c3584446..00000000 --- a/samples/webapi/ConventionsODataWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/Web.config b/samples/webapi/ConventionsODataWebApiSample/Web.config deleted file mode 100644 index 1e3e566e..00000000 --- a/samples/webapi/ConventionsODataWebApiSample/Web.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/packages.config b/samples/webapi/ConventionsODataWebApiSample/packages.config deleted file mode 100644 index 31a889a4..00000000 --- a/samples/webapi/ConventionsODataWebApiSample/packages.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/ConventionsWebApiSample.csproj b/samples/webapi/ConventionsWebApiSample/ConventionsWebApiSample.csproj index b3192e5b..6d72d965 100644 --- a/samples/webapi/ConventionsWebApiSample/ConventionsWebApiSample.csproj +++ b/samples/webapi/ConventionsWebApiSample/ConventionsWebApiSample.csproj @@ -1,161 +1,22 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {C1F89961-7134-4D97-BA3A-2693FD1CBF4E} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - ConventionsWebApiSample - v4.5 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - True - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 12330 - / - http://localhost:12330/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/Program.cs b/samples/webapi/ConventionsWebApiSample/Program.cs new file mode 100644 index 00000000..99926a4a --- /dev/null +++ b/samples/webapi/ConventionsWebApiSample/Program.cs @@ -0,0 +1,34 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Threading; + + public class Program + { + const string Url = "http://localhost:9001/"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/ConventionsWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index b9dd0658..00000000 --- a/samples/webapi/ConventionsWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "ConventionsWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "ConventionsWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2016" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "c1f89961-7134-4d97-ba3a-2693fd1cbf4e" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/ConventionsWebApiSample/Startup.cs b/samples/webapi/ConventionsWebApiSample/Startup.cs index fca1972e..8d18deaf 100644 --- a/samples/webapi/ConventionsWebApiSample/Startup.cs +++ b/samples/webapi/ConventionsWebApiSample/Startup.cs @@ -6,6 +6,7 @@ namespace Microsoft.Examples using global::Owin; using Microsoft.Web.Http.Routing; using Microsoft.Web.Http.Versioning.Conventions; + using System; using System.Web.Http; using System.Web.Http.Routing; @@ -24,13 +25,14 @@ public void Configuration( IAppBuilder builder ) // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" options.ReportApiVersions = true; - // apply api versions using conventions rather than attributes options.Conventions.Controller().HasApiVersion( 1, 0 ); + options.Conventions.Controller() .HasApiVersion( 2, 0 ) .HasApiVersion( 3, 0 ) .Action( c => c.GetV3() ).MapToApiVersion( 3, 0 ) - .Action( c => c.GetV3( default( int ) ) ).MapToApiVersion( 3, 0 ); + .Action( c => c.GetV3( default ) ).MapToApiVersion( 3, 0 ); + options.Conventions.Controller() .HasApiVersion( 1, 0 ) .HasApiVersion( 2, 0 ) @@ -40,5 +42,20 @@ public void Configuration( IAppBuilder builder ) configuration.MapHttpAttributeRoutes( constraintResolver ); builder.UseWebApi( httpServer ); } + + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } } } \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/Web.Debug.config b/samples/webapi/ConventionsWebApiSample/Web.Debug.config deleted file mode 100644 index 2e302f9f..00000000 --- a/samples/webapi/ConventionsWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/Web.Release.config b/samples/webapi/ConventionsWebApiSample/Web.Release.config deleted file mode 100644 index c3584446..00000000 --- a/samples/webapi/ConventionsWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/Web.config b/samples/webapi/ConventionsWebApiSample/Web.config deleted file mode 100644 index 693df44d..00000000 --- a/samples/webapi/ConventionsWebApiSample/Web.config +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/ConventionsWebApiSample/packages.config b/samples/webapi/ConventionsWebApiSample/packages.config deleted file mode 100644 index 01cf5055..00000000 --- a/samples/webapi/ConventionsWebApiSample/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerODataWebApiSample/Program.cs b/samples/webapi/SwaggerODataWebApiSample/Program.cs new file mode 100644 index 00000000..e35af21a --- /dev/null +++ b/samples/webapi/SwaggerODataWebApiSample/Program.cs @@ -0,0 +1,44 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Diagnostics; + using System.Threading; + + /// + /// Represents the current application. + /// + public class Program + { + const string Url = "http://localhost:9007/"; + const string LaunchUrl = Url + "swagger"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + /// + /// The main entry point to the application. + /// + /// The arguments provided at start-up, if any. + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + Process.Start( LaunchUrl ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/SwaggerODataWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/SwaggerODataWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index 499130a9..00000000 --- a/samples/webapi/SwaggerODataWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "SwaggerODataWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "SwaggerODataWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2017" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "f3986f7b-af76-43d1-a44f-303023a08cd3" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/SwaggerODataWebApiSample/Startup.cs b/samples/webapi/SwaggerODataWebApiSample/Startup.cs index 44400476..6261673b 100644 --- a/samples/webapi/SwaggerODataWebApiSample/Startup.cs +++ b/samples/webapi/SwaggerODataWebApiSample/Startup.cs @@ -33,12 +33,11 @@ public class Startup /// The current application builder. public void Configuration( IAppBuilder builder ) { - const string routePrefix = default( string ); var configuration = new HttpConfiguration(); var httpServer = new HttpServer( configuration ); // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" - configuration.AddApiVersioning( o => o.ReportApiVersions = true ); + configuration.AddApiVersioning( options => options.ReportApiVersions = true ); // note: this is required to make the default swagger json settings match the odata conventions applied by EnableLowerCamelCase() configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); @@ -57,10 +56,10 @@ public void Configuration( IAppBuilder builder ) // INFO: while you can use both, you should choose only ONE of the following; comment, uncomment, or remove as necessary // WHEN VERSIONING BY: query string, header, or media type - configuration.MapVersionedODataRoutes( "odata", routePrefix, models, ConfigureContainer ); + configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureContainer ); // WHEN VERSIONING BY: url segment - //configuration.MapVersionedODataRoutes( "odata-bypath", "api/v{apiVersion}", models, ConfigureContainer ); + // configuration.MapVersionedODataRoutes( "odata-bypath", "api/v{apiVersion}", models, ConfigureContainer ); // add the versioned IApiExplorer and capture the strongly-typed implementation (e.g. ODataApiExplorer vs IApiExplorer) // note: the specified format code will format the version as "'v'major[.minor][-status]" @@ -75,10 +74,10 @@ public void Configuration( IAppBuilder builder ) // configure query options (which cannot otherwise be configured by OData conventions) options.QueryOptions.Controller() - .Action( c => c.Get( default( ODataQueryOptions ) ) ).Allow( Skip | Count ).AllowTop( 100 ); + .Action( c => c.Get( default ) ).Allow( Skip | Count ).AllowTop( 100 ); options.QueryOptions.Controller() - .Action( c => c.Get( default( ODataQueryOptions ) ) ).Allow( Skip | Count ).AllowTop( 100 ); + .Action( c => c.Get( default ) ).Allow( Skip | Count ).AllowTop( 100 ); } ); configuration.EnableSwagger( @@ -118,13 +117,31 @@ public void Configuration( IAppBuilder builder ) builder.UseWebApi( httpServer ); } + /// + /// Get the root content path. + /// + /// The root content path of the application. + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } + static string XmlCommentsFilePath { get { - var basePath = AppDomain.CurrentDomain.RelativeSearchPath; var fileName = typeof( Startup ).GetTypeInfo().Assembly.GetName().Name + ".xml"; - return Path.Combine( basePath, fileName ); + return Path.Combine( ContentRootPath, fileName ); } } diff --git a/samples/webapi/SwaggerODataWebApiSample/SwaggerODataWebApiSample.csproj b/samples/webapi/SwaggerODataWebApiSample/SwaggerODataWebApiSample.csproj index 079b29f7..c8e666b0 100644 --- a/samples/webapi/SwaggerODataWebApiSample/SwaggerODataWebApiSample.csproj +++ b/samples/webapi/SwaggerODataWebApiSample/SwaggerODataWebApiSample.csproj @@ -1,205 +1,25 @@ - - - - - - Debug - AnyCPU - - - 2.0 - {F3986F7B-AF76-43D1-A44F-303023A08CD3} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - SwaggerODataWebApiSample - v4.5.2 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - bin\SwaggerODataWebApiSample.xml - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - bin\SwaggerODataWebApiSample.xml - - - - ..\..\..\packages\Microsoft.AspNet.OData.7.0.1\lib\net45\Microsoft.AspNet.OData.dll - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\..\..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - - - ..\..\..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\..\..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - - - ..\..\..\packages\Swashbuckle.Core.5.5.3\lib\net40\Swashbuckle.Core.dll - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {1b255310-a2b7-437f-804f-6e1d8c940a17} - Microsoft.AspNet.OData.Versioning.ApiExplorer - - - {48a2b488-23ab-4c83-ae30-0b8b735c4562} - Microsoft.AspNet.OData.Versioning - - - {91e1f0b5-905d-446c-a2dd-4c1edabfaf6c} - Microsoft.AspNet.WebApi.Versioning.ApiExplorer - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 1874 - / - http://localhost:1874/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/SwaggerODataWebApiSample/Web.Debug.config b/samples/webapi/SwaggerODataWebApiSample/Web.Debug.config deleted file mode 100644 index fae9cfef..00000000 --- a/samples/webapi/SwaggerODataWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerODataWebApiSample/Web.Release.config b/samples/webapi/SwaggerODataWebApiSample/Web.Release.config deleted file mode 100644 index da6e960b..00000000 --- a/samples/webapi/SwaggerODataWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerODataWebApiSample/Web.config b/samples/webapi/SwaggerODataWebApiSample/Web.config deleted file mode 100644 index bbd13666..00000000 --- a/samples/webapi/SwaggerODataWebApiSample/Web.config +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerODataWebApiSample/packages.config b/samples/webapi/SwaggerODataWebApiSample/packages.config deleted file mode 100644 index 0675ce3f..00000000 --- a/samples/webapi/SwaggerODataWebApiSample/packages.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerWebApiSample/Program.cs b/samples/webapi/SwaggerWebApiSample/Program.cs new file mode 100644 index 00000000..e9845d81 --- /dev/null +++ b/samples/webapi/SwaggerWebApiSample/Program.cs @@ -0,0 +1,44 @@ +namespace Microsoft.Examples +{ + using Microsoft.Owin.Hosting; + using System; + using System.Diagnostics; + using System.Threading; + + /// + /// Represents the current application. + /// + public class Program + { + const string Url = "http://localhost:9003/"; + const string LaunchUrl = Url + "swagger"; + static readonly ManualResetEvent resetEvent = new ManualResetEvent( false ); + + /// + /// The main entry point to the application. + /// + /// The arguments provided at start-up, if any. + public static void Main( string[] args ) + { + Console.CancelKeyPress += OnCancel; + + using ( WebApp.Start( Url ) ) + { + Console.WriteLine( "Content root path: " + Startup.ContentRootPath ); + Console.WriteLine( "Now listening on: " + Url ); + Console.WriteLine( "Application started. Press Ctrl+C to shut down." ); + Process.Start( LaunchUrl ); + resetEvent.WaitOne(); + } + + Console.CancelKeyPress -= OnCancel; + } + + static void OnCancel( object sender, ConsoleCancelEventArgs e ) + { + Console.Write( "Application is shutting down..." ); + e.Cancel = true; + resetEvent.Set(); + } + } +} \ No newline at end of file diff --git a/samples/webapi/SwaggerWebApiSample/Properties/AssemblyInfo.cs b/samples/webapi/SwaggerWebApiSample/Properties/AssemblyInfo.cs deleted file mode 100644 index 2bbb1aa3..00000000 --- a/samples/webapi/SwaggerWebApiSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "SwaggerWebApiSample" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "SwaggerWebApiSample" )] -[assembly: AssemblyCopyright( "Copyright © 2017" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "6bede228-4be9-499e-b1e6-93b6b0ac62da" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/samples/webapi/SwaggerWebApiSample/Startup.cs b/samples/webapi/SwaggerWebApiSample/Startup.cs index e4f6c36e..8f09ca9f 100644 --- a/samples/webapi/SwaggerWebApiSample/Startup.cs +++ b/samples/webapi/SwaggerWebApiSample/Startup.cs @@ -5,6 +5,7 @@ namespace Microsoft.Examples using global::Owin; using Microsoft.Web.Http.Routing; using Swashbuckle.Application; + using System; using System.IO; using System.Reflection; using System.Web.Http; @@ -28,7 +29,7 @@ public void Configuration( IAppBuilder builder ) var httpServer = new HttpServer( configuration ); // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" - configuration.AddApiVersioning( o => o.ReportApiVersions = true ); + configuration.AddApiVersioning( options => options.ReportApiVersions = true ); configuration.MapHttpAttributeRoutes( constraintResolver ); // add the versioned IApiExplorer and capture the strongly-typed implementation (e.g. VersionedApiExplorer vs IApiExplorer) @@ -80,13 +81,31 @@ public void Configuration( IAppBuilder builder ) builder.UseWebApi( httpServer ); } + /// + /// Get the root content path. + /// + /// The root content path of the application. + public static string ContentRootPath + { + get + { + var app = AppDomain.CurrentDomain; + + if ( string.IsNullOrEmpty( app.RelativeSearchPath ) ) + { + return app.BaseDirectory; + } + + return app.RelativeSearchPath; + } + } + static string XmlCommentsFilePath { get { - var basePath = System.AppDomain.CurrentDomain.RelativeSearchPath; var fileName = typeof( Startup ).GetTypeInfo().Assembly.GetName().Name + ".xml"; - return Path.Combine( basePath, fileName ); + return Path.Combine( ContentRootPath, fileName ); } } } diff --git a/samples/webapi/SwaggerWebApiSample/SwaggerWebApiSample.csproj b/samples/webapi/SwaggerWebApiSample/SwaggerWebApiSample.csproj index 431aa3a3..0d8d019d 100644 --- a/samples/webapi/SwaggerWebApiSample/SwaggerWebApiSample.csproj +++ b/samples/webapi/SwaggerWebApiSample/SwaggerWebApiSample.csproj @@ -1,179 +1,25 @@ - - - - - - Debug - AnyCPU - - - 2.0 - {6BEDE228-4BE9-499E-B1E6-93B6B0AC62DA} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Microsoft.Examples - SwaggerWebApiSample - v4.6 - true - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - bin\SwaggerWebApiSample.xml - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - bin\SwaggerWebApiSample.xml - - - - ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True - - - ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll - - - ..\..\..\packages\Swashbuckle.Core.5.5.3\lib\net40\Swashbuckle.Core.dll - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - - - - - - - - - - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - - - ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - - - ..\..\..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.30506.0\lib\net40\System.Web.Http.WebHost.dll - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - - - - - - - {91e1f0b5-905d-446c-a2dd-4c1edabfaf6c} - Microsoft.AspNet.WebApi.Versioning.ApiExplorer - - - {3bac97ed-1a8e-4f5a-a716-db5255f51c81} - Microsoft.AspNet.WebApi.Versioning - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 15721 - / - http://localhost:15721/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + Exe + net461 + Microsoft.Examples + bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/SwaggerWebApiSample/Web.Debug.config b/samples/webapi/SwaggerWebApiSample/Web.Debug.config deleted file mode 100644 index fae9cfef..00000000 --- a/samples/webapi/SwaggerWebApiSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerWebApiSample/Web.Release.config b/samples/webapi/SwaggerWebApiSample/Web.Release.config deleted file mode 100644 index da6e960b..00000000 --- a/samples/webapi/SwaggerWebApiSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerWebApiSample/Web.config b/samples/webapi/SwaggerWebApiSample/Web.config deleted file mode 100644 index 809d891e..00000000 --- a/samples/webapi/SwaggerWebApiSample/Web.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/SwaggerWebApiSample/packages.config b/samples/webapi/SwaggerWebApiSample/packages.config deleted file mode 100644 index 7fc093ac..00000000 --- a/samples/webapi/SwaggerWebApiSample/packages.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/webapi/directory.build.targets b/samples/webapi/directory.build.targets deleted file mode 100644 index 50a7388a..00000000 --- a/samples/webapi/directory.build.targets +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs b/src/Common.OData.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs index 4d722bb9..bf48e1c2 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs @@ -139,23 +139,6 @@ protected virtual ApiParameterDescription NewCountParameter( ODataQueryOptionDes return NewParameterDescription( GetName( Count ), description, typeof( bool ), defaultValue: false ); } - static bool IsSupportHttpMethod( string httpMethod ) - { - Contract.Requires( !string.IsNullOrEmpty( httpMethod ) ); - - switch ( httpMethod.ToUpperInvariant() ) - { - // query or function - case "GET": - - // action - case "POST": - return true; - } - - return false; - } - string GetName( AllowedQueryOptions option ) { Contract.Requires( option == Filter || ( option > Filter && option < Supported && ( (int) option % 2 == 0 ) ) ); diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/TypeExtensions.cs b/src/Common.OData.ApiExplorer/AspNet.OData/TypeExtensions.cs index 5cc96e91..c27d49c7 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/TypeExtensions.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/TypeExtensions.cs @@ -15,7 +15,6 @@ using System.Net.Http; using System.Reflection; using System.Reflection.Emit; - using static System.StringComparison; #if WEBAPI using IActionResult = System.Web.Http.IHttpActionResult; #endif @@ -26,11 +25,14 @@ public static partial class TypeExtensions { static readonly Type VoidType = typeof( void ); - static readonly Type ActionResultType = typeof( IActionResult ); static readonly Type HttpResponseType = typeof( HttpResponseMessage ); static readonly Type IEnumerableOfT = typeof( IEnumerable<> ); static readonly Type ODataValueOfT = typeof( ODataValue<> ); static readonly Type SingleResultOfT = typeof( SingleResult<> ); + static readonly Type ActionResultType = typeof( IActionResult ); +#if !WEBAPI + static readonly Type ActionResultOfT = typeof( ActionResult<> ); +#endif /// /// Substitutes the specified type, if required. @@ -157,7 +159,11 @@ internal static Type ExtractInnerType( this Type type ) var typeArg = typeArgs[0]; - if ( typeDef.IsDelta() || typeDef.Equals( ODataValueOfT ) || typeDef.IsActionResult() || typeDef.Equals( SingleResultOfT ) ) +#if WEBAPI + if ( typeDef.IsDelta() || typeDef.IsODataValue() || typeDef.IsSingleResult() ) +#else + if ( typeDef.IsDelta() || typeDef.IsODataValue() || typeDef.IsSingleResult() || typeDef.IsActionResult() ) +#endif { return typeArg; } @@ -189,7 +195,11 @@ static bool IsSubstitutableGeneric( Type type, Stack openTypes, out Type i var typeArg = typeArgs[0]; - if ( typeDef.Equals( IEnumerableOfT ) || typeDef.IsDelta() || typeDef.Equals( ODataValueOfT ) || typeDef.IsActionResult() || typeDef.Equals( SingleResultOfT ) ) +#if WEBAPI + if ( typeDef.Equals( IEnumerableOfT ) || typeDef.IsDelta() || typeDef.IsODataValue() || typeDef.IsSingleResult() ) +#else + if ( typeDef.Equals( IEnumerableOfT ) || typeDef.IsDelta() || typeDef.IsODataValue() || typeDef.IsSingleResult() || typeDef.IsActionResult() ) +#endif { innerType = typeArg; } @@ -284,12 +294,18 @@ internal static bool IsEnumerable( this Type type, out Type itemType ) return false; } - static bool IsActionResult( this Type type ) => - type.IsGenericType && - type.GetGenericTypeDefinition().FullName.Equals( "Microsoft.AspNetCore.Mvc.ActionResult`1", Ordinal ); + static bool IsSingleResult( this Type type ) => type.Is( SingleResultOfT ); + + static bool IsODataValue( this Type type ) => type.Is( ODataValueOfT ); - static bool IsSingleResult( this Type type ) => SingleResultOfT.IsAssignableFrom( type ); + static bool Is( this Type type, Type typeDefinition ) => type.IsGenericType && type.GetGenericTypeDefinition().Equals( typeDefinition ); +#if WEBAPI + static bool ShouldExtractInnerType( this Type type ) => type.IsDelta() || type.IsSingleResult(); +#else static bool ShouldExtractInnerType( this Type type ) => type.IsDelta() || type.IsSingleResult() || type.IsActionResult(); + + static bool IsActionResult( this Type type ) => type.Is( ActionResultOfT ); +#endif } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs index 323f861b..f0858a0e 100644 --- a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs +++ b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs @@ -1,6 +1,7 @@ namespace Microsoft.AspNet.OData.Builder { using Microsoft.AspNet.OData.Extensions; + using Microsoft.OData.Edm; using System; using System.Diagnostics.Contracts; using System.Web.Http.Controllers; @@ -18,7 +19,7 @@ public virtual void ApplyTo( ApiDescription apiDescription ) { Arg.NotNull( apiDescription, nameof( apiDescription ) ); - if ( !IsSupportHttpMethod( apiDescription.HttpMethod.Method ) ) + if ( !IsSupported( apiDescription ) ) { return; } @@ -136,5 +137,22 @@ static ApiParameterDescription SetAction( ApiParameterDescription parameter, Htt return parameter; } + + static bool IsSupported( ApiDescription apiDescription ) + { + Contract.Requires( apiDescription != null ); + + switch ( apiDescription.HttpMethod.Method.ToUpperInvariant() ) + { + case "GET": + // query or function + return true; + case "POST": + // action + return apiDescription.Operation()?.IsAction() == true; + } + + return false; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs index d5f15257..30cc7af3 100644 --- a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs +++ b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs @@ -72,5 +72,22 @@ public static IEdmEntityType EntityType( this ApiDescription apiDescription ) Arg.NotNull( apiDescription, nameof( apiDescription ) ); return apiDescription.EntitySet()?.EntityType(); } + + /// + /// Gets the operation associated with the API description. + /// + /// The API description to get the operation for. + /// The associated EDM operation or null if there is no associated operation. + public static IEdmOperation Operation( this ApiDescription apiDescription ) + { + Arg.NotNull( apiDescription, nameof( apiDescription ) ); + + if ( apiDescription is VersionedApiDescription description ) + { + return description.GetProperty(); + } + + return default; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Web.Http.Description/ODataApiExplorer.cs b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Web.Http.Description/ODataApiExplorer.cs index c0a1c3f9..33c1153f 100644 --- a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Web.Http.Description/ODataApiExplorer.cs +++ b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Web.Http.Description/ODataApiExplorer.cs @@ -461,6 +461,16 @@ void PopulateActionDescriptions( Properties = { [typeof( IEdmModel )] = routeBuilderContext.EdmModel }, }; + if ( routeBuilderContext.EntitySet != null ) + { + apiDescription.Properties[typeof( IEdmEntitySet )] = routeBuilderContext.EntitySet; + } + + if ( routeBuilderContext.Operation != null ) + { + apiDescription.Properties[typeof( IEdmOperation )] = routeBuilderContext.Operation; + } + apiDescription.ParameterDescriptions.AddRange( routeBuilderContext.ParameterDescriptions ); apiDescription.SupportedRequestBodyFormatters.AddRange( requestFormatters ); apiDescription.SupportedResponseFormatters.AddRange( responseFormatters ); diff --git a/src/Microsoft.AspNet.WebApi.Versioning/Dispatcher/HttpResponseExceptionFactory.cs b/src/Microsoft.AspNet.WebApi.Versioning/Dispatcher/HttpResponseExceptionFactory.cs index 32f50a0e..e47c9fae 100644 --- a/src/Microsoft.AspNet.WebApi.Versioning/Dispatcher/HttpResponseExceptionFactory.cs +++ b/src/Microsoft.AspNet.WebApi.Versioning/Dispatcher/HttpResponseExceptionFactory.cs @@ -1,5 +1,6 @@ namespace Microsoft.Web.Http.Dispatcher { + using Microsoft.Web.Http.Versioning; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -9,7 +10,6 @@ using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Http.Tracing; - using Microsoft.Web.Http.Versioning; using static ApiVersion; using static System.Net.HttpStatusCode; using static System.String; diff --git a/src/Microsoft.AspNet.WebApi.Versioning/Routing/ApiVersionRouteConstraint.cs b/src/Microsoft.AspNet.WebApi.Versioning/Routing/ApiVersionRouteConstraint.cs index 4de0dd9f..9709ddb2 100644 --- a/src/Microsoft.AspNet.WebApi.Versioning/Routing/ApiVersionRouteConstraint.cs +++ b/src/Microsoft.AspNet.WebApi.Versioning/Routing/ApiVersionRouteConstraint.cs @@ -29,13 +29,7 @@ public bool Match( HttpRequestMessage request, IHttpRoute route, string paramete return false; } - var properties = request.ApiVersionProperties(); - - if ( values.TryGetValue( parameterName, out string value ) ) - { - properties.RawRequestedApiVersion = value; - } - else + if ( !values.TryGetValue( parameterName, out string value ) ) { return false; } @@ -45,6 +39,10 @@ public bool Match( HttpRequestMessage request, IHttpRoute route, string paramete return !IsNullOrEmpty( value ); } + var properties = request.ApiVersionProperties(); + + properties.RawRequestedApiVersion = value; + if ( TryParse( value, out var requestedVersion ) ) { properties.RequestedApiVersion = requestedVersion; diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiVersionParameterDescriptionContext.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiVersionParameterDescriptionContext.cs index c5d4b6ab..acecb453 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiVersionParameterDescriptionContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiVersionParameterDescriptionContext.cs @@ -1,7 +1,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { using Microsoft.AspNetCore.Mvc.Abstractions; - using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Versioning; @@ -158,7 +157,7 @@ protected virtual void AddHeader( string name ) } /// - /// Adds the description for an API version expressed as a header. + /// Adds the description for an API version expressed as a route parameter in a URL segment. /// protected virtual void UpdateUrlSegment() { @@ -175,10 +174,22 @@ where constraints.OfType().Any() return; } + parameter.IsRequired = true; + parameter.DefaultValue = ApiVersion.ToString(); parameter.ModelMetadata = ModelMetadata; parameter.Type = ModelMetadata.ModelType; parameter.RouteInfo.IsOptional = false; - parameter.RouteInfo.DefaultValue = ApiVersion.ToString(); + parameter.RouteInfo.DefaultValue = parameter.DefaultValue; + + if ( parameter.ParameterDescriptor == null ) + { + parameter.ParameterDescriptor = new ParameterDescriptor() + { + Name = parameter.Name, + ParameterType = typeof( ApiVersion ), + }; + } + RemoveAllParametersExcept( parameter ); } @@ -239,17 +250,29 @@ ApiParameterDescription NewApiVersionParameter( string name, BindingSource sourc var parameter = new ApiParameterDescription() { - Name = name, + DefaultValue = ApiVersion.ToString(), + IsRequired = !optional, ModelMetadata = ModelMetadata, - Source = source, - RouteInfo = new ApiParameterRouteInfo() + Name = name, + ParameterDescriptor = new ParameterDescriptor() { - DefaultValue = ApiVersion.ToString(), - IsOptional = optional, + Name = name, + ParameterType = typeof( ApiVersion ), }, + Source = source, Type = ModelMetadata.ModelType, }; + if ( source == BindingSource.Path ) + { + parameter.IsRequired = true; + parameter.RouteInfo = new ApiParameterRouteInfo() + { + DefaultValue = ApiVersion.ToString(), + IsOptional = false, + }; + } + optional = true; parameters.Add( parameter ); @@ -258,7 +281,7 @@ ApiParameterDescription NewApiVersionParameter( string name, BindingSource sourc void RemoveAllParametersExcept( ApiParameterDescription parameter ) { - // note: in a scenario where multiple api version parameters are allowed, we can remove all other parameters because + // in a scenario where multiple api version parameters are allowed, we can remove all other parameters because // the api version must be specified in the path. this will avoid unwanted, duplicate api version parameters var collections = new ICollection[] { ApiDescription.ParameterDescriptions, parameters }; diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj index d0dfb35e..5f12a5d2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj @@ -1,8 +1,8 @@  - 3.0.0 - 3.0.0.0 + 3.1.0 + 3.1.0.0 netstandard2.0 2.0.0-* 2.0.0-* @@ -17,7 +17,7 @@ - + diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ReleaseNotes.txt b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ReleaseNotes.txt index 5f282702..5e27ff88 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ReleaseNotes.txt +++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ReleaseNotes.txt @@ -1 +1 @@ - \ No newline at end of file +Support exploring default parameters values (#414) \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Abstractions/ActionDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Abstractions/ActionDescriptorExtensions.cs index fc7bf834..651ea560 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Abstractions/ActionDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Abstractions/ActionDescriptorExtensions.cs @@ -1,10 +1,12 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions { using Microsoft.AspNetCore.Mvc.ApplicationModels; + using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Versioning; using System; using System.Diagnostics.Contracts; using System.Linq; + using System.Text; using static Microsoft.AspNetCore.Mvc.Versioning.ApiVersionMapping; using static System.Linq.Enumerable; @@ -101,5 +103,47 @@ public static bool IsMappedTo( this ActionDescriptor action, ApiVersion apiVersi Arg.NotNull( action, nameof( action ) ); return action.MappingTo( apiVersion ) > None; } + + internal static string ExpandSignature( this ActionDescriptor action ) + { + Contract.Requires( action != null ); + Contract.Ensures( !string.IsNullOrEmpty( Contract.Result() ) ); + + if ( !( action is ControllerActionDescriptor controllerAction ) ) + { + return action.DisplayName; + } + + var signature = new StringBuilder(); + var controllerType = controllerAction.ControllerTypeInfo; + + signature.Append( controllerType.GetTypeDisplayName() ); + signature.Append( '.' ); + signature.Append( controllerAction.MethodInfo.Name ); + signature.Append( '(' ); + + using ( var parameter = action.Parameters.GetEnumerator() ) + { + if ( parameter.MoveNext() ) + { + var parameterType = parameter.Current.ParameterType; + + signature.Append( parameterType.GetTypeDisplayName( false ) ); + + while ( parameter.MoveNext() ) + { + parameterType = parameter.Current.ParameterType; + signature.Append( ", " ); + signature.Append( parameterType.GetTypeDisplayName( false ) ); + } + } + } + + signature.Append( ") (" ); + signature.Append( controllerType.Assembly.GetName().Name ); + signature.Append( ')' ); + + return signature.ToString(); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/ApiBehaviorSpecification.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/ApiBehaviorSpecification.cs new file mode 100644 index 00000000..9ba4f128 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/ApiBehaviorSpecification.cs @@ -0,0 +1,34 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using System; + using System.Linq; + using System.Reflection; + + /// + /// Represents a specification that matches API controllers by the presence of API behaviors. + /// + [CLSCompliant( false )] + public sealed class ApiBehaviorSpecification : IApiControllerSpecification + { + /// + /// Initializes a new instance of the class. + /// + public ApiBehaviorSpecification() + { + const string ApiBehaviorApplicationModelProviderTypeName = "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiBehaviorApplicationModelProvider, Microsoft.AspNetCore.Mvc.Core"; + var type = Type.GetType( ApiBehaviorApplicationModelProviderTypeName, throwOnError: true ); + var method = type.GetRuntimeMethods().Single( m => m.Name == "IsApiController" ); + + IsApiController = (Func) method.CreateDelegate( typeof( Func ) ); + } + + Func IsApiController { get; } + + /// + public bool IsSatisfiedBy( ControllerModel controller ) + { + Arg.NotNull( controller, nameof( controller ) ); + return IsApiController( controller ); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/DefaultApiControllerFilter.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/DefaultApiControllerFilter.cs new file mode 100644 index 00000000..75e350f5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/DefaultApiControllerFilter.cs @@ -0,0 +1,67 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + + /// + /// Represents the default API controller filter. + /// + [CLSCompliant( false )] + public sealed class DefaultApiControllerFilter : IApiControllerFilter + { + readonly IReadOnlyList specifications; + + /// + /// Initializes a new instance of the class. + /// + /// The sequence of + /// specifications used by the filter + /// to identify API controllers. + public DefaultApiControllerFilter( IEnumerable specifications ) + { + Arg.NotNull( specifications, nameof( specifications ) ); + this.specifications = specifications.ToArray(); + } + + /// + public IList Apply( IList controllers ) + { + Arg.NotNull( controllers, nameof( controllers ) ); + Contract.Ensures( Contract.Result>() != null ); + + if ( specifications.Count == 0 ) + { + return controllers; + } + + var filtered = controllers.ToList(); + + for ( var i = filtered.Count - 1; i >= 0; i-- ) + { + if ( !IsApiController( filtered[i] ) ) + { + filtered.RemoveAt( i ); + } + } + + return filtered; + } + + bool IsApiController( ControllerModel controller ) + { + Contract.Requires( controller != null ); + + for ( var i = 0; i < specifications.Count; i++ ) + { + if ( specifications[i].IsSatisfiedBy( controller ) ) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/IApiControllerFilter.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/IApiControllerFilter.cs new file mode 100644 index 00000000..eda2a5dd --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/IApiControllerFilter.cs @@ -0,0 +1,21 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using System; + using System.Collections.Generic; + + /// + /// Defines the behavior of an API controller filter. + /// + [CLSCompliant( false )] + public interface IApiControllerFilter + { + /// + /// Applies the filter to the provided list of controllers. + /// + /// The list of + /// controllers to filter. + /// A new, filtered list of API + /// controllers. + IList Apply( IList controllers ); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/IApiControllerSpecification.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/IApiControllerSpecification.cs new file mode 100644 index 00000000..9142ae7e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/ApplicationModels/IApiControllerSpecification.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using System; + + /// + /// Defines the behavior of an API controller specification. + /// + [CLSCompliant( false )] + public interface IApiControllerSpecification + { + /// + /// Determines whether the specification is satisified by the provided controller model. + /// + /// The controller model to evaluate. + /// True if the satisifies the specification; otherwise, false. + bool IsSatisfiedBy( ControllerModel controller ); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.AspNetCore.Mvc.Versioning.csproj b/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.AspNetCore.Mvc.Versioning.csproj index 9335d10b..0a9ae1c6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.AspNetCore.Mvc.Versioning.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.AspNetCore.Mvc.Versioning.csproj @@ -1,8 +1,8 @@  - 3.0.0 - 3.0.0.0 + 3.1.0 + 3.1.0.0 netstandard2.0 2.0.0-* 2.0.0-* @@ -13,7 +13,7 @@ - + diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/AutoRegisterMiddleware.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/AutoRegisterMiddleware.cs index 11cec8d4..be3bde0f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/AutoRegisterMiddleware.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/AutoRegisterMiddleware.cs @@ -2,6 +2,7 @@ { using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.Extensions.Options; @@ -12,14 +13,20 @@ sealed class AutoRegisterMiddleware : IStartupFilter { readonly IApiVersionRoutePolicy routePolicy; readonly IOptions options; + readonly IOptions mvcOptions; - public AutoRegisterMiddleware( IApiVersionRoutePolicy routePolicy, IOptions options ) + public AutoRegisterMiddleware( + IApiVersionRoutePolicy routePolicy, + IOptions options, + IOptions mvcOptions ) { Contract.Requires( routePolicy != null ); Contract.Requires( options != null ); + Contract.Requires( mvcOptions != null ); this.routePolicy = routePolicy; this.options = options; + this.mvcOptions = mvcOptions; } public Action Configure( Action next ) @@ -35,7 +42,11 @@ public Action Configure( Action next ) } next( app ); - app.UseRouter( builder => builder.Routes.Add( new CatchAllRouteHandler( routePolicy ) ) ); + + if ( !mvcOptions.Value.EnableEndpointRouting ) + { + app.UseRouter( builder => builder.Routes.Add( new CatchAllRouteHandler( routePolicy ) ) ); + } }; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensions.cs index 41e51fca..55164076 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensions.cs @@ -60,12 +60,15 @@ static void AddApiVersioningServices( IServiceCollection services ) services.Add( Singleton( sp => sp.GetRequiredService>().Value.ErrorResponses ) ); services.Replace( Singleton() ); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton( OnRequestIReportApiVersions ); services.TryAddEnumerable( Transient, ApiVersioningMvcOptionsSetup>() ); services.TryAddEnumerable( Transient, ApiVersioningRouteOptionsSetup>() ); services.TryAddEnumerable( Transient() ); services.TryAddEnumerable( Transient() ); + services.TryAddEnumerable( Transient() ); + services.TryAddEnumerable( Singleton() ); services.AddTransient(); } diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/ReleaseNotes.txt b/src/Microsoft.AspNetCore.Mvc.Versioning/ReleaseNotes.txt index 5f282702..94e53af6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/ReleaseNotes.txt +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/ReleaseNotes.txt @@ -1 +1,4 @@ - \ No newline at end of file +Support Endpoint Routing (#413) +ApiVersioningOptions.UseApiBehavior now defaults to true +Enhanced API controller filtering with IApiControllerSpecification +Add ApiBehaviorSpecification to match ApiControllerAttribute \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionMatcherPolicy.cs new file mode 100644 index 00000000..98c487b2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionMatcherPolicy.cs @@ -0,0 +1,248 @@ +namespace Microsoft.AspNetCore.Mvc.Routing +{ + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.Versioning; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Matching; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Threading.Tasks; + using static Microsoft.AspNetCore.Mvc.Versioning.ApiVersionMapping; + using static Microsoft.AspNetCore.Mvc.Versioning.ErrorCodes; + using static System.Threading.Tasks.Task; + + /// + /// Represents the endpoint selector policy for API versions. + /// + [CLSCompliant( false )] + public sealed class ApiVersionMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy + { + readonly IOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options associated with the action selector. + /// The object used to report API versions. + /// The factory used to create loggers. + public ApiVersionMatcherPolicy( + IOptions options, + IReportApiVersions reportApiVersions, + ILoggerFactory loggerFactory ) + { + Arg.NotNull( options, nameof( options ) ); + Arg.NotNull( reportApiVersions, nameof( reportApiVersions ) ); + Arg.NotNull( loggerFactory, nameof( loggerFactory ) ); + + this.options = options; + ApiVersionReporter = reportApiVersions; + Logger = loggerFactory.CreateLogger( GetType() ); + } + + /// + public override int Order => 0; + + ApiVersioningOptions Options => options.Value; + + IApiVersionSelector ApiVersionSelector => Options.ApiVersionSelector; + + IReportApiVersions ApiVersionReporter { get; } + + ILogger Logger { get; } + + /// + public bool AppliesToEndpoints( IReadOnlyList endpoints ) + { + Arg.NotNull( endpoints, nameof( endpoints ) ); + + for ( var i = 0; i < endpoints.Count; i++ ) + { + var action = endpoints[i].Metadata?.GetMetadata(); + + if ( action?.GetProperty() != null ) + { + return true; + } + } + + return false; + } + + /// + public Task ApplyAsync( HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates ) + { + Arg.NotNull( httpContext, nameof( httpContext ) ); + Arg.NotNull( context, nameof( context ) ); + Arg.NotNull( candidates, nameof( candidates ) ); + + if ( IsRequestedApiVersionAmbiguous( httpContext, context, out var apiVersion ) ) + { + return CompletedTask; + } + + if ( apiVersion == null && Options.AssumeDefaultVersionWhenUnspecified ) + { + apiVersion = TrySelectApiVersion( httpContext, candidates ); + httpContext.Features.Get().RequestedApiVersion = apiVersion; + } + + var finalMatches = EvaluateApiVersion( httpContext, candidates, apiVersion ); + + if ( finalMatches.Count == 0 ) + { + context.Endpoint = ClientError( httpContext, candidates ); + } + else + { + for ( var i = 0; i < finalMatches.Count; i++ ) + { + candidates.SetValidity( finalMatches[i].index, true ); + } + } + + return CompletedTask; + } + + static IReadOnlyList<(int index, ActionDescriptor action)> EvaluateApiVersion( + HttpContext httpContext, + CandidateSet candidates, + ApiVersion apiVersion ) + { + Contract.Requires( httpContext != null ); + Contract.Requires( candidates != null ); + Contract.Ensures( Contract.Result>() != null ); + + var bestMatches = new List<(int index, ActionDescriptor action)>(); + var implicitMatches = new List<(int, ActionDescriptor)>(); + + for ( var i = 0; i < candidates.Count; i++ ) + { + if ( !candidates.IsValidCandidate( i ) ) + { + continue; + } + + ref var candidate = ref candidates[i]; + var action = candidate.Endpoint.Metadata?.GetMetadata(); + + if ( action == null ) + { + candidates.SetValidity( i, false ); + continue; + } + + switch ( action.MappingTo( apiVersion ) ) + { + case Explicit: + bestMatches.Add( (i, action) ); + break; + case Implicit: + implicitMatches.Add( (i, action) ); + break; + } + + // perf: always make the candidate invalid so we only need to loop through the + // final, best matches for any remaining, valid candidates + candidates.SetValidity( i, false ); + } + + switch ( bestMatches.Count ) + { + case 0: + bestMatches.AddRange( implicitMatches ); + break; + case 1: + var model = bestMatches[0].action.GetApiVersionModel(); + + if ( model.IsApiVersionNeutral ) + { + bestMatches.AddRange( implicitMatches ); + } + + break; + } + + return bestMatches.ToArray(); + } + + bool IsRequestedApiVersionAmbiguous( HttpContext httpContext, EndpointSelectorContext context, out ApiVersion apiVersion ) + { + Contract.Requires( httpContext != null ); + Contract.Requires( context != null ); + + try + { + apiVersion = httpContext.GetRequestedApiVersion(); + } + catch ( AmbiguousApiVersionException ex ) + { + Logger.LogInformation( ex.Message ); + apiVersion = default; + + var handlerContext = new RequestHandlerContext( Options.ErrorResponses ) + { + Code = AmbiguousApiVersion, + Message = ex.Message, + }; + + context.Endpoint = new BadRequestHandler( handlerContext ); + return true; + } + + return false; + } + + ApiVersion TrySelectApiVersion( HttpContext httpContext, CandidateSet candidates ) + { + Contract.Requires( httpContext != null ); + Contract.Requires( candidates != null ); + + var models = new List(); + + for ( var i = 0; i < candidates.Count; i++ ) + { + ref var candidate = ref candidates[i]; + var model = candidate.Endpoint.Metadata?.GetMetadata()?.GetApiVersionModel(); + + if ( model != null ) + { + models.Add( model ); + } + } + + return ApiVersionSelector.SelectVersion( httpContext.Request, models.Aggregate() ); + } + + RequestHandler ClientError( HttpContext httpContext, CandidateSet candidateSet ) + { + var candidates = new List( candidateSet.Count ); + + for ( var i = 0; i < candidateSet.Count; i++ ) + { + ref var candidate = ref candidateSet[i]; + var action = candidate.Endpoint.Metadata?.GetMetadata(); + + if ( action != null ) + { + candidates.Add( action ); + } + } + + var builder = new ClientErrorBuilder() + { + Options = Options, + ApiVersionReporter = ApiVersionReporter, + HttpContext = httpContext, + Candidates = candidates, + Logger = Logger, + }; + + return builder.Build(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionRouteConstraint.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionRouteConstraint.cs index ff821cbc..06978824 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionRouteConstraint.cs @@ -30,13 +30,7 @@ public bool Match( HttpContext httpContext, IRouter route, string routeKey, Rout return false; } - var feature = httpContext.Features.Get(); - - if ( values.TryGetValue( routeKey, out string value ) ) - { - feature.RawRequestedApiVersion = value; - } - else + if ( !values.TryGetValue( routeKey, out string value ) ) { return false; } @@ -46,6 +40,10 @@ public bool Match( HttpContext httpContext, IRouter route, string routeKey, Rout return !IsNullOrEmpty( value ); } + var feature = httpContext.Features.Get(); + + feature.RawRequestedApiVersion = value; + if ( TryParse( value, out var requestedVersion ) ) { feature.RequestedApiVersion = requestedVersion; diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ClientErrorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ClientErrorBuilder.cs new file mode 100644 index 00000000..e9e15e96 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ClientErrorBuilder.cs @@ -0,0 +1,178 @@ +namespace Microsoft.AspNetCore.Mvc.Routing +{ + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.Internal; + using Microsoft.AspNetCore.Mvc.Versioning; + using Microsoft.Extensions.Logging; + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using static Microsoft.AspNetCore.Mvc.ApiVersion; + using static Microsoft.AspNetCore.Mvc.Versioning.ErrorCodes; + using static System.Environment; + using static System.Linq.Enumerable; + using static System.String; + + sealed class ClientErrorBuilder + { + internal ApiVersioningOptions Options { get; set; } + + internal IReportApiVersions ApiVersionReporter { get; set; } + + internal HttpContext HttpContext { get; set; } + + internal IReadOnlyCollection Candidates { get; set; } + + internal ILogger Logger { get; set; } + + IErrorResponseProvider ErrorResponseProvider => Options.ErrorResponses; + + internal RequestHandler Build() + { + var feature = HttpContext.Features.Get(); + var request = HttpContext.Request; + var method = request.Method; + var requestedVersion = feature.RawRequestedApiVersion; + var parsedVersion = feature.RequestedApiVersion; + var actionNames = new Lazy( () => Join( NewLine, Candidates.Select( a => a.DisplayName ) ) ); + var allowedMethods = new Lazy>( () => AllowedMethodsFromCandidates( Candidates, parsedVersion ) ); + var apiVersions = new Lazy( Candidates.Select( a => a.GetApiVersionModel() ).Aggregate ); + var handlerContext = new RequestHandlerContext( ErrorResponseProvider, ApiVersionReporter, apiVersions ); + var url = new Uri( request.GetDisplayUrl() ).SafeFullPath(); + + if ( parsedVersion == null ) + { + if ( IsNullOrEmpty( requestedVersion ) ) + { + if ( Options.AssumeDefaultVersionWhenUnspecified || Candidates.Any( c => c.GetApiVersionModel().IsApiVersionNeutral ) ) + { + return VersionNeutralUnmatched( handlerContext, url, method, allowedMethods.Value, actionNames.Value ); + } + + return UnspecifiedApiVersion( handlerContext, actionNames.Value ); + } + else if ( !TryParse( requestedVersion, out parsedVersion ) ) + { + return MalformedApiVersion( handlerContext, url, requestedVersion ); + } + } + else if ( IsNullOrEmpty( requestedVersion ) ) + { + return VersionNeutralUnmatched( handlerContext, url, method, allowedMethods.Value, actionNames.Value ); + } + else + { + requestedVersion = parsedVersion.ToString(); + } + + return Unmatched( handlerContext, url, method, allowedMethods.Value, actionNames.Value, parsedVersion, requestedVersion ); + } + + static HashSet AllowedMethodsFromCandidates( IEnumerable candidates, ApiVersion apiVersion ) + { + Contract.Requires( candidates != null ); + Contract.Ensures( Contract.Result>() != null ); + + var httpMethods = new HashSet( StringComparer.OrdinalIgnoreCase ); + + foreach ( var candidate in candidates ) + { + if ( candidate.ActionConstraints == null || !candidate.IsMappedTo( apiVersion ) ) + { + continue; + } + + foreach ( var constraint in candidate.ActionConstraints.OfType() ) + { + httpMethods.AddRange( constraint.HttpMethods ); + } + } + + return httpMethods; + } + + RequestHandler VersionNeutralUnmatched( + RequestHandlerContext context, + string requestUrl, + string method, + IReadOnlyCollection allowedMethods, + string actionNames ) + { + Contract.Requires( context != null ); + Contract.Requires( !IsNullOrEmpty( requestUrl ) ); + Contract.Requires( !IsNullOrEmpty( method ) ); + Contract.Requires( allowedMethods != null ); + + Logger.ApiVersionUnspecified( actionNames ); + context.Code = UnsupportedApiVersion; + + if ( allowedMethods.Count == 0 || allowedMethods.Contains( method ) ) + { + context.Message = SR.VersionNeutralResourceNotSupported.FormatDefault( requestUrl ); + return new BadRequestHandler( context ); + } + + context.Message = SR.VersionNeutralMethodNotSupported.FormatDefault( requestUrl, method ); + context.AllowedMethods = allowedMethods.ToArray(); + + return new MethodNotAllowedHandler( context ); + } + + RequestHandler UnspecifiedApiVersion( RequestHandlerContext context, string actionNames ) + { + Contract.Requires( context != null ); + + Logger.ApiVersionUnspecified( actionNames ); + context.Code = ApiVersionUnspecified; + context.Message = SR.ApiVersionUnspecified; + + return new BadRequestHandler( context ); + } + + RequestHandler MalformedApiVersion( RequestHandlerContext context, string requestUrl, string requestedVersion ) + { + Contract.Requires( context != null ); + Contract.Requires( !IsNullOrEmpty( requestUrl ) ); + Contract.Requires( !IsNullOrEmpty( requestedVersion ) ); + + Logger.ApiVersionInvalid( requestedVersion ); + context.Code = InvalidApiVersion; + context.Message = SR.VersionedResourceNotSupported.FormatDefault( requestUrl, requestedVersion ); + + return new BadRequestHandler( context ); + } + + RequestHandler Unmatched( + RequestHandlerContext context, + string requestUrl, + string method, + IReadOnlyCollection allowedMethods, + string actionNames, + ApiVersion parsedVersion, + string requestedVersion ) + { + Contract.Requires( context != null ); + Contract.Requires( !IsNullOrEmpty( requestUrl ) ); + Contract.Requires( !IsNullOrEmpty( method ) ); + Contract.Requires( allowedMethods != null ); + Contract.Requires( parsedVersion != null ); + Contract.Requires( !IsNullOrEmpty( requestedVersion ) ); + + Logger.ApiVersionUnmatched( parsedVersion, actionNames ); + context.Code = UnsupportedApiVersion; + + if ( allowedMethods.Count == 0 || allowedMethods.Contains( method ) ) + { + context.Message = SR.VersionedResourceNotSupported.FormatDefault( requestUrl, requestedVersion ); + return new BadRequestHandler( context ); + } + + context.Message = SR.VersionedMethodNotSupported.FormatDefault( requestUrl, requestedVersion, method ); + context.AllowedMethods = allowedMethods.ToArray(); + + return new MethodNotAllowedHandler( context ); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/DefaultApiVersionRoutePolicy.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/DefaultApiVersionRoutePolicy.cs index 550bb408..2141dcbb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/DefaultApiVersionRoutePolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/DefaultApiVersionRoutePolicy.cs @@ -1,21 +1,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing { - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.Abstractions; - using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Linq; - using System.Text; - using static Microsoft.AspNetCore.Mvc.ApiVersion; - using static Microsoft.AspNetCore.Mvc.Versioning.ErrorCodes; using static System.Environment; using static System.Linq.Enumerable; using static System.String; @@ -125,7 +117,25 @@ protected virtual void OnUnmatched( RouteContext context, ActionSelectionResult Arg.NotNull( context, nameof( context ) ); Arg.NotNull( selectionResult, nameof( selectionResult ) ); - context.Handler = ClientError( context.HttpContext, selectionResult ); + const RequestHandler NotFound = default; + var candidates = selectionResult.CandidateActions; + var handler = NotFound; + + if ( candidates.Count > 0 ) + { + var builder = new ClientErrorBuilder() + { + Options = Options, + ApiVersionReporter = ApiVersionReporter, + HttpContext = context.HttpContext, + Candidates = candidates, + Logger = Logger, + }; + + handler = builder.Build(); + } + + context.Handler = handler; } /// @@ -139,7 +149,7 @@ protected virtual void OnMultipleMatches( RouteContext context, ActionSelectionR Arg.NotNull( context, nameof( context ) ); Arg.NotNull( selectionResult, nameof( selectionResult ) ); - var actionNames = Join( NewLine, selectionResult.MatchingActions.Select( ExpandActionSignature ) ); + var actionNames = Join( NewLine, selectionResult.MatchingActions.Select( a => a.ExpandSignature() ) ); Logger.AmbiguousActions( actionNames ); @@ -147,203 +157,5 @@ protected virtual void OnMultipleMatches( RouteContext context, ActionSelectionR throw new AmbiguousActionException( message ); } - - static string ExpandActionSignature( ActionDescriptor match ) - { - Contract.Requires( match != null ); - Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) ); - - if ( !( match is ControllerActionDescriptor action ) ) - { - return match.DisplayName; - } - - var signature = new StringBuilder(); - var controllerType = action.ControllerTypeInfo; - - signature.Append( controllerType.GetTypeDisplayName() ); - signature.Append( '.' ); - signature.Append( action.MethodInfo.Name ); - signature.Append( '(' ); - - using ( var parameter = action.Parameters.GetEnumerator() ) - { - if ( parameter.MoveNext() ) - { - var parameterType = parameter.Current.ParameterType; - - signature.Append( parameterType.GetTypeDisplayName( false ) ); - - while ( parameter.MoveNext() ) - { - parameterType = parameter.Current.ParameterType; - signature.Append( ", " ); - signature.Append( parameterType.GetTypeDisplayName( false ) ); - } - } - } - - signature.Append( ") (" ); - signature.Append( controllerType.Assembly.GetName().Name ); - signature.Append( ')' ); - - return signature.ToString(); - } - - RequestHandler ClientError( HttpContext httpContext, ActionSelectionResult selectionResult ) - { - Contract.Requires( httpContext != null ); - Contract.Requires( selectionResult != null ); - - const RequestHandler NotFound = default; - var candidates = selectionResult.CandidateActions; - - if ( candidates.Count == 0 ) - { - return NotFound; - } - - var feature = httpContext.Features.Get(); - var method = httpContext.Request.Method; - var requestUrl = new Lazy( httpContext.Request.GetDisplayUrl ); - var requestedVersion = feature.RawRequestedApiVersion; - var parsedVersion = feature.RequestedApiVersion; - var actionNames = new Lazy( () => Join( NewLine, candidates.Select( a => a.DisplayName ) ) ); - var allowedMethods = new Lazy>( () => AllowedMethodsFromCandidates( candidates, parsedVersion ) ); - var apiVersions = new Lazy( candidates.Select( a => a.GetApiVersionModel() ).Aggregate ); - var handlerContext = new RequestHandlerContext( ErrorResponseProvider, ApiVersionReporter, apiVersions ); - var url = new Uri( requestUrl.Value ); - var safeUrl = url.SafeFullPath(); - - if ( parsedVersion == null ) - { - if ( IsNullOrEmpty( requestedVersion ) ) - { - if ( Options.AssumeDefaultVersionWhenUnspecified || candidates.Any( c => c.GetApiVersionModel().IsApiVersionNeutral ) ) - { - return VersionNeutralUnmatched( handlerContext, safeUrl, method, allowedMethods.Value, actionNames.Value ); - } - - return UnspecifiedApiVersion( handlerContext, actionNames.Value ); - } - else if ( !TryParse( requestedVersion, out parsedVersion ) ) - { - return MalformedApiVersion( handlerContext, safeUrl, requestedVersion ); - } - } - else if ( IsNullOrEmpty( requestedVersion ) ) - { - return VersionNeutralUnmatched( handlerContext, safeUrl, method, allowedMethods.Value, actionNames.Value ); - } - else - { - requestedVersion = parsedVersion.ToString(); - } - - return Unmatched( handlerContext, safeUrl, method, allowedMethods.Value, actionNames.Value, parsedVersion, requestedVersion ); - } - - static HashSet AllowedMethodsFromCandidates( IEnumerable candidates, ApiVersion apiVersion ) - { - Contract.Requires( candidates != null ); - Contract.Ensures( Contract.Result>() != null ); - - var httpMethods = new HashSet( StringComparer.OrdinalIgnoreCase ); - - foreach ( var candidate in candidates ) - { - if ( candidate.ActionConstraints == null || !candidate.IsMappedTo( apiVersion ) ) - { - continue; - } - - foreach ( var constraint in candidate.ActionConstraints.OfType() ) - { - httpMethods.AddRange( constraint.HttpMethods ); - } - } - - return httpMethods; - } - - RequestHandler VersionNeutralUnmatched( RequestHandlerContext context, string requestUrl, string method, IReadOnlyCollection allowedMethods, string actionNames ) - { - Contract.Requires( context != null ); - Contract.Requires( !IsNullOrEmpty( requestUrl ) ); - Contract.Requires( !IsNullOrEmpty( method ) ); - Contract.Requires( allowedMethods != null ); - - Logger.ApiVersionUnspecified( actionNames ); - context.Code = UnsupportedApiVersion; - - if ( allowedMethods.Count == 0 || allowedMethods.Contains( method ) ) - { - context.Message = SR.VersionNeutralResourceNotSupported.FormatDefault( requestUrl ); - return new BadRequestHandler( context ); - } - - context.Message = SR.VersionNeutralMethodNotSupported.FormatDefault( requestUrl, method ); - context.AllowedMethods = allowedMethods.ToArray(); - - return new MethodNotAllowedHandler( context ); - } - - RequestHandler UnspecifiedApiVersion( RequestHandlerContext context, string actionNames ) - { - Contract.Requires( context != null ); - - Logger.ApiVersionUnspecified( actionNames ); - - context.Code = ApiVersionUnspecified; - context.Message = SR.ApiVersionUnspecified; - - return new BadRequestHandler( context ); - } - - RequestHandler MalformedApiVersion( RequestHandlerContext context, string requestUrl, string requestedVersion ) - { - Contract.Requires( context != null ); - Contract.Requires( !IsNullOrEmpty( requestUrl ) ); - Contract.Requires( !IsNullOrEmpty( requestedVersion ) ); - - Logger.ApiVersionInvalid( requestedVersion ); - - context.Code = InvalidApiVersion; - context.Message = SR.VersionedResourceNotSupported.FormatDefault( requestUrl, requestedVersion ); - - return new BadRequestHandler( context ); - } - - RequestHandler Unmatched( - RequestHandlerContext context, - string requestUrl, - string method, - IReadOnlyCollection allowedMethods, - string actionNames, - ApiVersion parsedVersion, - string requestedVersion ) - { - Contract.Requires( context != null ); - Contract.Requires( !IsNullOrEmpty( requestUrl ) ); - Contract.Requires( !IsNullOrEmpty( method ) ); - Contract.Requires( allowedMethods != null ); - Contract.Requires( parsedVersion != null ); - Contract.Requires( !IsNullOrEmpty( requestedVersion ) ); - - Logger.ApiVersionUnmatched( parsedVersion, actionNames ); - - context.Code = UnsupportedApiVersion; - - if ( allowedMethods.Count == 0 || allowedMethods.Contains( method ) ) - { - context.Message = SR.VersionedResourceNotSupported.FormatDefault( requestUrl, requestedVersion ); - return new BadRequestHandler( context ); - } - - context.Message = SR.VersionedMethodNotSupported.FormatDefault( requestUrl, requestedVersion, method ); - context.AllowedMethods = allowedMethods.ToArray(); - - return new MethodNotAllowedHandler( context ); - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningApplicationModelProvider.cs index 5cc1236c..3f1675c2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningApplicationModelProvider.cs @@ -4,10 +4,7 @@ using Microsoft.AspNetCore.Mvc.Versioning.Conventions; using Microsoft.Extensions.Options; using System; - using System.Collections.Generic; using System.Diagnostics.Contracts; - using System.Linq; - using System.Reflection; /// /// Represents an application model provider, which @@ -22,10 +19,14 @@ public class ApiVersioningApplicationModelProvider : IApplicationModelProvider /// Initializes a new instance of the class. /// /// The current API versioning options. - public ApiVersioningApplicationModelProvider( IOptions options ) + /// The filter used for API controllers. + public ApiVersioningApplicationModelProvider( IOptions options, IApiControllerFilter controllerFilter ) { Arg.NotNull( options, nameof( options ) ); + Arg.NotNull( controllerFilter, nameof( controllerFilter ) ); + this.options = options; + ControllerFilter = controllerFilter; } /// @@ -34,6 +35,12 @@ public ApiVersioningApplicationModelProvider( IOptions opt /// The current API versioning options. protected ApiVersioningOptions Options => options.Value; + /// + /// Gets the filter used for API controllers. + /// + /// The used to filter API controllers. + protected IApiControllerFilter ControllerFilter { get; } + /// public int Order { get; protected set; } @@ -45,7 +52,12 @@ public virtual void OnProvidersExecuted( ApplicationModelProviderContext context var implicitVersionModel = new ApiVersionModel( Options.DefaultApiVersion ); var conventionBuilder = Options.Conventions; var application = context.Result; - var controllers = FilterControllers( application.Controllers ); + var controllers = application.Controllers; + + if ( Options.UseApiBehavior ) + { + controllers = ControllerFilter.Apply( controllers ); + } foreach ( var controller in controllers ) { @@ -59,37 +71,6 @@ public virtual void OnProvidersExecuted( ApplicationModelProviderContext context /// public virtual void OnProvidersExecuting( ApplicationModelProviderContext context ) { } - IEnumerable FilterControllers( IEnumerable controllers ) - { - Contract.Requires( controllers != null ); - Contract.Ensures( Contract.Result>() != null ); - - if ( !Options.UseApiBehavior ) - { - return controllers; - } - - var assembly = typeof( ControllerAttribute ).Assembly; - var apiBehaviorSupported = assembly.ExportedTypes.Any( t => t.Name == "ApiControllerAttribute" ); - - return apiBehaviorSupported ? controllers.Where( IsApiController ) : controllers; - } - - static bool IsApiController( ControllerModel controller ) - { - if ( controller.Attributes.Any( IsApiBehaviorMetadata ) ) - { - return true; - } - - var controllerAssembly = controller.ControllerType.Assembly; - var assemblyAttributes = controllerAssembly.GetCustomAttributes(); - - return assemblyAttributes.Any( IsApiBehaviorMetadata ); - } - - static bool IsApiBehaviorMetadata( object attribute ) => attribute.GetType().GetInterfaces().Any( i => i.Name == "IApiBehaviorMetadata" ); - static bool IsDecoratedWithAttributes( ControllerModel controller ) { Contract.Requires( controller != null ); diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningOptions.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningOptions.cs index 312eb072..b65fa4e3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersioningOptions.cs @@ -1,6 +1,7 @@ namespace Microsoft.AspNetCore.Mvc.Versioning { using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Mvc.ApplicationModels; /// /// Provides additional implementation specific to ASP.NET Core. @@ -21,11 +22,11 @@ public partial class ApiVersioningOptions /// /// Gets or sets a value indicating whether to use web API behaviors. /// - /// True to use web API behaviors; otherwise, false. The default value is false. - /// Setting this property to true applies API versioning policies only to controllers that - /// have the ApiControllerAttribute applied. A value of true is only effective when using ASP.NET Core - /// 2.1 or above. The default value of false retains backward capability with the existing behaviors - /// before the API behavior feature was introduced. - public bool UseApiBehavior { get; set; } + /// True to use web API behaviors; otherwise, false. The default value is true. + /// When this property is set to true, API versioning policies only apply to controllers + /// that remain after the has been applied. When this property is set + /// to false, API versioning policies are considers for all controllers. This was default behavior + /// behavior in previous versions. + public bool UseApiBehavior { get; set; } = true; } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandler.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandler.cs index 9dff1391..fce2c72d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandler.cs @@ -30,9 +30,20 @@ internal Task ExecuteAsync( HttpContext httpContext ) return result.ExecuteResultAsync( actionContext ); } -#pragma warning disable CA2225 // Operator overloads have named alternates; intentionally one-way +#pragma warning disable CA2225 // Operator overloads have named alternates; implicit cast only intended public static implicit operator RequestDelegate( RequestHandler handler ) => handler == null ? default( RequestDelegate ) : handler.ExecuteAsync; #pragma warning restore CA2225 + + public static implicit operator Endpoint( RequestHandler handler ) => handler?.ToEndpoint(); + + internal Endpoint ToEndpoint() + { + var metadata = Context.Metadata == null ? + new EndpointMetadataCollection() : + new EndpointMetadataCollection( Context.Metadata ); + + return new Endpoint( ExecuteAsync, metadata, default ); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandlerContext.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandlerContext.cs index ccb19653..9196b617 100644 --- a/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandlerContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/RequestHandlerContext.cs @@ -2,6 +2,7 @@ { using Microsoft.AspNetCore.Http; using System; + using System.Collections.Generic; using System.Diagnostics.Contracts; sealed class RequestHandlerContext @@ -32,6 +33,8 @@ internal RequestHandlerContext( internal string[] AllowedMethods { get; set; } + internal IList Metadata { get; set; } + internal void ReportApiVersions( HttpResponse response ) { Contract.Requires( response != null ); diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs index 3587c52e..d4692c95 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNet.OData/Builder/ODataValidationSettingsConvention.cs @@ -1,10 +1,14 @@ namespace Microsoft.AspNet.OData.Builder { + using Microsoft.AspNet.OData.Routing; + using Microsoft.AspNet.OData.Routing.Template; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ApiExplorer; + using Microsoft.OData.Edm; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.Linq; using static Microsoft.AspNet.OData.Query.AllowedQueryOptions; using static Microsoft.AspNetCore.Http.StatusCodes; using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource; @@ -20,7 +24,7 @@ public virtual void ApplyTo( ApiDescription apiDescription ) { Arg.NotNull( apiDescription, nameof( apiDescription ) ); - if ( !IsSupportHttpMethod( apiDescription.HttpMethod ) ) + if ( !IsSupported( apiDescription ) ) { return; } @@ -94,6 +98,8 @@ public virtual void ApplyTo( ApiDescription apiDescription ) return new ApiParameterDescription() { + DefaultValue = defaultValue, + IsRequired = false, ModelMetadata = new ODataQueryOptionModelMetadata( Settings.ModelMetadataProvider, type, description ), Name = name, ParameterDescriptor = new ParameterDescriptor() @@ -101,11 +107,6 @@ public virtual void ApplyTo( ApiDescription apiDescription ) Name = name, ParameterType = type, }, - RouteInfo = new ApiParameterRouteInfo() - { - DefaultValue = defaultValue, - IsOptional = true, - }, Source = Query, Type = type, }; @@ -159,5 +160,22 @@ static bool IsSingleResult( ApiDescription description, out Type resultType ) resultType = responseType; return true; } + + static bool IsSupported( ApiDescription apiDescription ) + { + Contract.Requires( apiDescription != null ); + + switch ( apiDescription.HttpMethod.ToUpperInvariant() ) + { + case "GET": + // query or function + return true; + case "POST": + // action + return apiDescription.Operation()?.IsAction() == true; + } + + return false; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs index 5bb905b2..ac9dc326 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs @@ -63,5 +63,16 @@ public static IEdmEntityType EntityType( this ApiDescription apiDescription ) Arg.NotNull( apiDescription, nameof( apiDescription ) ); return apiDescription.EntitySet()?.EntityType(); } + + /// + /// Gets the operation associated with the API description. + /// + /// The API description to get the operation for. + /// The associated EDM operation or null if there is no associated operation. + public static IEdmOperation Operation( this ApiDescription apiDescription ) + { + Arg.NotNull( apiDescription, nameof( apiDescription ) ); + return apiDescription.GetProperty(); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ODataApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ODataApiDescriptionProvider.cs index b909b047..7d2543fa 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ODataApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ODataApiDescriptionProvider.cs @@ -412,6 +412,16 @@ IEnumerable NewODataApiDescriptions( ControllerActionDescriptor Properties = { [typeof( IEdmModel )] = routeContext.EdmModel }, }; + if ( routeContext.EntitySet != null ) + { + apiDescription.Properties[typeof( IEdmEntitySet )] = routeContext.EntitySet; + } + + if ( routeContext.Operation != null ) + { + apiDescription.Properties[typeof( IEdmOperation )] = routeContext.Operation; + } + foreach ( var parameter in parameters ) { apiDescription.ParameterDescriptions.Add( parameter ); diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj index 82f5c590..a110103a 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj @@ -1,7 +1,7 @@  - 3.0.0 + 3.0.1 3.0.0.0 netstandard2.0 Microsoft ASP.NET Core Versioned API Explorer for OData v4.0 @@ -16,7 +16,6 @@ - diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/ReleaseNotes.txt b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/ReleaseNotes.txt index 5f282702..03a735fd 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/ReleaseNotes.txt +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/ReleaseNotes.txt @@ -1 +1 @@ - \ No newline at end of file +Fixed exploring query options for `POST` when the controller action is not an EDM action \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBindingInfoConvention.cs b/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBindingInfoConvention.cs index 8211472a..a43a6e57 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBindingInfoConvention.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBindingInfoConvention.cs @@ -12,6 +12,7 @@ using System.Linq; using static Microsoft.AspNet.OData.Routing.ODataRouteActionType; using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource; + using static Microsoft.AspNetCore.Mvc.Versioning.ApiVersionMapping; using static System.Linq.Enumerable; using static System.StringComparison; @@ -37,7 +38,7 @@ public void Apply( ActionDescriptorProviderContext context, ControllerActionDesc Contract.Requires( context != null ); Contract.Requires( action != null ); - var model = action.GetApiVersionModel(); + var model = action.GetApiVersionModel( Explicit | Implicit ); var mappings = RouteCollectionProvider.Items; var routeInfos = new HashSet( new ODataAttributeRouteInfoComparer() ); diff --git a/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBuilderContext.Core.cs b/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBuilderContext.Core.cs index dc1eb0b8..0160859f 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBuilderContext.Core.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBuilderContext.Core.cs @@ -6,7 +6,6 @@ using Microsoft.OData; using Microsoft.OData.Edm; using System; - using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Reflection; using static Microsoft.OData.ODataUrlKeyDelimiter; diff --git a/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/ApplicationModels/ODataControllerSpecification.cs b/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/ApplicationModels/ODataControllerSpecification.cs new file mode 100644 index 00000000..ee0ba94c --- /dev/null +++ b/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/ApplicationModels/ODataControllerSpecification.cs @@ -0,0 +1,19 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using Microsoft.AspNet.OData; + using System; + + /// + /// Represents a specification that matches API controllers if they use the OData protocol. + /// + [CLSCompliant( false )] + public sealed class ODataControllerSpecification : IApiControllerSpecification + { + /// + public bool IsSatisfiedBy( ControllerModel controller ) + { + Arg.NotNull( controller, nameof( controller ) ); + return controller.ControllerType.IsODataController(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Routing/ActionCandidate.cs b/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Routing/ActionCandidate.cs new file mode 100644 index 00000000..82311d91 --- /dev/null +++ b/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Routing/ActionCandidate.cs @@ -0,0 +1,50 @@ +namespace Microsoft.AspNetCore.Mvc.Routing +{ + using Microsoft.AspNet.OData; + using Microsoft.AspNetCore.Mvc.Abstractions; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource; + + [DebuggerDisplay( "{Action.DisplayName,nq}" )] + sealed class ActionCandidate + { + internal ActionCandidate( ActionDescriptor action ) + { + Contract.Requires( action != null ); + + TotalParameterCount = action.Parameters.Count; + + var filteredParameters = new List( TotalParameterCount ); + + for ( var i = 0; i < TotalParameterCount; i++ ) + { + var parameter = action.Parameters[i]; + + if ( parameter.ParameterType.IsModelBound() ) + { + continue; + } + + var bindingSource = parameter.BindingInfo?.BindingSource; + + if ( bindingSource != Custom && bindingSource != Path ) + { + continue; + } + + filteredParameters.Add( parameter.Name ); + } + + Action = action; + FilteredParameters = filteredParameters; + } + + internal ActionDescriptor Action { get; } + + internal int TotalParameterCount { get; } + + internal IReadOnlyList FilteredParameters { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Versioning/ODataApiVersionActionSelector.cs b/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Versioning/ODataApiVersionActionSelector.cs index f144639e..c24a8654 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Versioning/ODataApiVersionActionSelector.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/Versioning/ODataApiVersionActionSelector.cs @@ -165,46 +165,5 @@ public override ActionDescriptor SelectBestCandidate( RouteContext context, IRea return RoutePolicy.Evaluate( context, selectionResult ); } - - [DebuggerDisplay( "{Action.DisplayName,nq}" )] - sealed class ActionCandidate - { - internal ActionCandidate( ActionDescriptor action ) - { - Contract.Requires( action != null ); - - TotalParameterCount = action.Parameters.Count; - - var filteredParameters = new List( TotalParameterCount ); - - for ( var i = 0; i < TotalParameterCount; i++ ) - { - var parameter = action.Parameters[i]; - - if ( parameter.ParameterType.IsModelBound() ) - { - continue; - } - - var bindingSource = parameter.BindingInfo?.BindingSource; - - if ( bindingSource != Custom && bindingSource != Path ) - { - continue; - } - - filteredParameters.Add( parameter.Name ); - } - - Action = action; - FilteredParameters = filteredParameters; - } - - internal ActionDescriptor Action { get; } - - internal int TotalParameterCount { get; } - - internal IReadOnlyList FilteredParameters { get; } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData.Versioning/Extensions.DependencyInjection/IODataBuilderExtensions.cs b/src/Microsoft.AspNetCore.OData.Versioning/Extensions.DependencyInjection/IODataBuilderExtensions.cs index 2dc8053c..85eedc18 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning/Extensions.DependencyInjection/IODataBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.OData.Versioning/Extensions.DependencyInjection/IODataBuilderExtensions.cs @@ -72,6 +72,7 @@ static void AddODataServices( IServiceCollection services ) services.AddTransient(); services.AddTransient(); services.AddSingleton( ODataActionDescriptorChangeProvider.Instance ); + services.TryAddEnumerable( Transient() ); services.AddModelConfigurationsAsServices( partManager ); } diff --git a/src/Microsoft.AspNetCore.OData.Versioning/Microsoft.AspNetCore.OData.Versioning.csproj b/src/Microsoft.AspNetCore.OData.Versioning/Microsoft.AspNetCore.OData.Versioning.csproj index 3a4e096b..f167c241 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning/Microsoft.AspNetCore.OData.Versioning.csproj +++ b/src/Microsoft.AspNetCore.OData.Versioning/Microsoft.AspNetCore.OData.Versioning.csproj @@ -1,7 +1,7 @@  - 3.0.0 + 3.0.1 3.0.0.0 netstandard2.0 Microsoft ASP.NET Core API Versioning for OData v4.0 diff --git a/src/Microsoft.AspNetCore.OData.Versioning/ReleaseNotes.txt b/src/Microsoft.AspNetCore.OData.Versioning/ReleaseNotes.txt index 5f282702..f601ceec 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning/ReleaseNotes.txt +++ b/src/Microsoft.AspNetCore.OData.Versioning/ReleaseNotes.txt @@ -1 +1,2 @@ - \ No newline at end of file +Fixed a scenario where **ODataAttributeRouteInfo** was not generated for some actions +Added ODataControllerSpecification to identify OData controllers as API controllers \ No newline at end of file diff --git a/test/Acceptance.Test.Shared/AcceptanceTest.cs b/test/Acceptance.Test.Shared/AcceptanceTest.cs index 9dedf29a..4a9f70a9 100644 --- a/test/Acceptance.Test.Shared/AcceptanceTest.cs +++ b/test/Acceptance.Test.Shared/AcceptanceTest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc #else [Trait( "Framework", "ASP.NET Core" )] #endif - public abstract class AcceptanceTest + public abstract partial class AcceptanceTest { const string JsonMediaType = "application/json"; static readonly HttpMethod Patch = new HttpMethod( "PATCH" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/AcceptanceTest.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/AcceptanceTest.cs new file mode 100644 index 00000000..bc4982d9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/AcceptanceTest.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc +{ + using System; + + public abstract partial class AcceptanceTest + { + public bool UsingEndpointRouting => fixture.EnableEndpointRouting; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/HttpServerFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/HttpServerFixture.cs index 07c9c6a1..a489fdcc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/HttpServerFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/HttpServerFixture.cs @@ -11,6 +11,7 @@ using System.IO; using System.Net.Http; using System.Reflection; + using static Microsoft.AspNetCore.Mvc.CompatibilityVersion; using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor; public abstract partial class HttpServerFixture @@ -28,6 +29,8 @@ protected HttpServerFixture() public HttpClient Client => client.Value; + public bool EnableEndpointRouting { get; protected set; } + protected virtual void Dispose( bool disposing ) { if ( disposed ) @@ -87,7 +90,7 @@ void OnDefaultConfigureServices( IServiceCollection services ) OnConfigurePartManager( partManager ); services.Add( Singleton( partManager ) ); - services.AddMvc(); + services.AddMvc( options => options.EnableEndpointRouting = EnableEndpointRouting ).SetCompatibilityVersion( Latest ); services.AddApiVersioning( OnAddApiVersioning ); OnConfigureServices( services ); } diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Microsoft.AspNetCore.Mvc.Acceptance.Tests.csproj b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Microsoft.AspNetCore.Mvc.Acceptance.Tests.csproj index 9f3dd8f7..22c137ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Microsoft.AspNetCore.Mvc.Acceptance.Tests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Microsoft.AspNetCore.Mvc.Acceptance.Tests.csproj @@ -1,8 +1,8 @@  - netcoreapp2.0;net461 - netcoreapp2.0 + netcoreapp2.2;net461 + netcoreapp2.2 Microsoft.AspNetCore $(AssetTargetFallback);portable-net451+win8 true @@ -14,9 +14,9 @@ - - - + + + diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/BasicEndpointCollection.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/BasicEndpointCollection.cs new file mode 100644 index 00000000..55b94567 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/BasicEndpointCollection.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Basic +{ + using System; + using Xunit; + + [CollectionDefinition( nameof( BasicEndpointCollection ) )] + public sealed class BasicEndpointCollection : ICollectionFixture + { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/BasicEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/BasicEndpointFixture.cs new file mode 100644 index 00000000..c2d97488 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/BasicEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Basic +{ + public class BasicEndpointFixture : BasicFixture + { + public BasicEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorld2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorld2Controller.cs index 58feb26e..1187fd17 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorld2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorld2Controller.cs @@ -4,9 +4,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "2.0" )] [Route( "api/v{version:apiVersion}/HelloWorld" )] - public class HelloWorld2Controller : Controller + public class HelloWorld2Controller : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = GetType().Name, Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorldController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorldController.cs index d87fae51..55e57791 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorldController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/HelloWorldController.cs @@ -4,9 +4,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/v{version:apiVersion}/[controller]" )] - public class HelloWorldController : Controller + public class HelloWorldController : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = GetType().Name, Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OrdersController.cs index 610f28c1..661f0ec5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OrdersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [Route( "api/[controller]" )] public class OrdersController : ControllerBase { diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OverlappingRouteTemplateController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OverlappingRouteTemplateController.cs index 8778d721..c068624a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OverlappingRouteTemplateController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/OverlappingRouteTemplateController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/v{version:apiVersion}/values" )] - public class OverlappingRouteTemplateController : Controller + public class OverlappingRouteTemplateController : ControllerBase { [HttpGet( "{id:int}/{childId}" )] public IActionResult Get( int id, string childId ) => Ok( new { id, childId } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/PingController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/PingController.cs index bd255140..7925bdf3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/PingController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/PingController.cs @@ -2,9 +2,10 @@ { using System; + [ApiController] [ApiVersionNeutral] [Route( "api/[controller]" )] - public class PingController : Controller + public class PingController : ControllerBase { [HttpGet] public IActionResult Get() => NoContent(); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/Values2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/Values2Controller.cs index 1807ba36..05e193a0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/Values2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/Values2Controller.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "2.0" )] [Route( "api/values" )] - public class Values2Controller : Controller + public class Values2Controller : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( Values2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/ValuesController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/ValuesController.cs index f6517f17..9c4d6558 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/ValuesController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/ValuesController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/[controller]" )] - public class ValuesController : Controller + public class ValuesController : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingAttributes/HomeController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingAttributes/HomeController.cs index a1d6d722..f203bb11 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingAttributes/HomeController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingAttributes/HomeController.cs @@ -2,7 +2,6 @@ { using System; - [ApiVersionNeutral] [Route( "" )] [Route( "[controller]" )] public class HomeController : Controller diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingConventions/HomeController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingConventions/HomeController.cs index 464d3ab9..e19fc4d2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingConventions/HomeController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/Controllers/WithViewsUsingConventions/HomeController.cs @@ -2,7 +2,6 @@ { using System; - [ApiVersionNeutral] public class HomeController : Controller { public IActionResult Index() => View(); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/OverlappingRouteTemplateEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/OverlappingRouteTemplateEndpointFixture.cs new file mode 100644 index 00000000..5354e372 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/OverlappingRouteTemplateEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Basic +{ + public class OverlappingRouteTemplateEndpointFixture : OverlappingRouteTemplateFixture + { + public OverlappingRouteTemplateEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIEndpointFixture.cs new file mode 100644 index 00000000..e4ed31f2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Basic +{ + public class UIEndpointFixture : UIFixture + { + public UIEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIFixture.cs index c889a8d0..9671307c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/UIFixture.cs @@ -1,13 +1,7 @@ namespace Microsoft.AspNetCore.Mvc.Basic { using Microsoft.AspNetCore.Mvc.ApplicationParts; - using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Versioning; - using Microsoft.Extensions.DependencyInjection; - using System.Linq; - using System.Reflection; - using static Microsoft.CodeAnalysis.MetadataReference; - using static System.Reflection.Assembly; public class UIFixture : HttpServerFixture { @@ -18,27 +12,5 @@ protected override void OnConfigurePartManager( ApplicationPartManager partManag partManager.FeatureProviders.Add( (IApplicationFeatureProvider) FilteredControllerTypes ); partManager.ApplicationParts.Add( new AssemblyPart( GetType().Assembly ) ); } - - protected override void OnConfigureServices( IServiceCollection services ) - { - services.Configure( options => - { - options.CompilationCallback += context => - { - var assembly = GetType().GetTypeInfo().Assembly; - var assemblies = assembly.GetReferencedAssemblies().Select( x => CreateFromFile( Load( x ).Location ) ).ToList(); - - assemblies.Add( CreateFromFile( Load( new AssemblyName( "mscorlib" ) ).Location ) ); -#if NET461 - assemblies.Add( CreateFromFile( Load( new AssemblyName( "netstandard" ) ).Location ) ); -#else - assemblies.Add( CreateFromFile( Load( new AssemblyName( "System.Private.Corelib" ) ).Location ) ); -#endif - assemblies.Add( CreateFromFile( Load( new AssemblyName( "System.Dynamic.Runtime" ) ).Location ) ); - assemblies.Add( CreateFromFile( Load( new AssemblyName( "Microsoft.AspNetCore.Razor" ) ).Location ) ); - context.Compilation = context.Compilation.AddReferences( assemblies ); - }; - } ); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral Controller/when no version is specified.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral Controller/when no version is specified.cs index 664bc2ca..9d2cb000 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral Controller/when no version is specified.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral Controller/when no version is specified.cs @@ -32,11 +32,18 @@ public async Task then_post_should_return_405() // act var response = await PostAsync( "api/ping", entity ); - var content = await response.Content.ReadAsAsync(); // assert response.StatusCode.Should().Be( MethodNotAllowed ); response.Content.Headers.Allow.Should().BeEquivalentTo( "GET" ); + + if ( UsingEndpointRouting ) + { + return; + } + + var content = await response.Content.ReadAsAsync(); + content.Error.Should().BeEquivalentTo( new { @@ -54,11 +61,18 @@ public async Task then_unsupported_api_version_error_should_only_contain_the_pat // act var response = await PostAsync( "api/ping?additionalQuery=true", entity ); - var content = await response.Content.ReadAsAsync(); // assert response.StatusCode.Should().Be( MethodNotAllowed ); response.Content.Headers.Allow.Should().BeEquivalentTo( "GET" ); + + if ( UsingEndpointRouting ) + { + return; + } + + var content = await response.Content.ReadAsAsync(); + content.Error.Should().BeEquivalentTo( new { @@ -70,5 +84,10 @@ public async Task then_unsupported_api_version_error_should_only_contain_the_pat public when_no_version_is_specified( BasicFixture fixture ) : base( fixture ) { } } -} + [Collection( nameof( BasicEndpointCollection ) )] + public class when_no_version_is_specified_ : when_no_version_is_specified + { + public when_no_version_is_specified_( BasicEndpointFixture fixture ) : base( fixture ) { } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using attribute routing.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using attribute routing.cs index 9d5baa6c..379846cf 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using attribute routing.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using attribute routing.cs @@ -34,4 +34,9 @@ public when_accessing_a_view_using_attribute_routing( UIFixture fixture ) : base fixture.FilteredControllerTypes.Add( typeof( HomeController ).GetTypeInfo() ); } } + + public class when_accessing_a_view_using_attribute_routing_ : when_accessing_a_view_using_attribute_routing, IClassFixture + { + public when_accessing_a_view_using_attribute_routing_( UIEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using convention routing.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using convention routing.cs index 7b437783..d4d3bc96 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using convention routing.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a version-neutral UI Controller/when accessing a view using convention routing.cs @@ -33,4 +33,9 @@ public when_accessing_a_view_using_convention_routing( UIFixture fixture ) : bas fixture.FilteredControllerTypes.Add( typeof( HomeController ).GetTypeInfo() ); } } + + public class when_accessing_a_view_using_convention_routing_ : when_accessing_a_view_using_convention_routing, IClassFixture + { + public when_accessing_a_view_using_convention_routing_( UIEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when two route templates overlap.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when two route templates overlap.cs index ea9af900..81ae27d0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when two route templates overlap.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when two route templates overlap.cs @@ -54,9 +54,22 @@ public async Task then_the_higher_precedence_route_should_result_in_ambiguous_ac // assert result1.Should().Be( "{\"id\":42,\"childId\":\"abc\"}" ); - act.Should().Throw(); + + if ( UsingEndpointRouting ) + { + act.Should().Throw().And.GetType().Name.Should().Be( "AmbiguousMatchException" ); + } + else + { + act.Should().Throw(); + } } public when_two_route_templates_overlap( OverlappingRouteTemplateFixture fixture ) : base( fixture ) { } } + + public class when_two_route_templates_overlap_ : when_two_route_templates_overlap, IClassFixture + { + public when_two_route_templates_overlap_( OverlappingRouteTemplateEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a query string and split into two types.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a query string and split into two types.cs index 73ebca68..a866e47c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a query string and split into two types.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a query string and split into two types.cs @@ -109,4 +109,10 @@ public async Task then_action_segment_should_not_be_ambiguous_with_route_paramet public when_using_a_query_string_and_split_into_two_types( BasicFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( BasicEndpointCollection ) )] + public class when_using_a_query_string_and_split_into_two_types_ : when_using_a_query_string_and_split_into_two_types + { + public when_using_a_query_string_and_split_into_two_types_( BasicEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a url segment.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a url segment.cs index 388df182..56d43430 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a url segment.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using a url segment.cs @@ -97,4 +97,10 @@ public async Task then_action_segment_should_not_be_ambiguous_with_route_paramet public when_using_a_url_segment( BasicFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( BasicEndpointCollection ) )] + public class when_using_a_url_segment_ : when_using_a_url_segment + { + public when_using_a_url_segment_( BasicEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using an action.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using an action.cs index 38342bec..0cfdb64c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using an action.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Basic/given a versioned Controller/when using an action.cs @@ -74,4 +74,10 @@ public async Task then_delete_should_return_204( string requestUrl ) public when_using_an_action( BasicFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( BasicEndpointCollection ) )] + public class when_using_an_action_ : when_using_an_action + { + public when_using_an_action_( BasicEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsEndpointCollection.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsEndpointCollection.cs new file mode 100644 index 00000000..5cb76d6c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsEndpointCollection.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.ByNamespace +{ + using System; + using Xunit; + + [CollectionDefinition( nameof( AgreementsEndpointCollection ) )] + public class AgreementsEndpointCollection : ICollectionFixture + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsEndpointFixture.cs new file mode 100644 index 00000000..bca377c4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.ByNamespace +{ + public class AgreementsEndpointFixture : AgreementsFixture + { + public AgreementsEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsFixture.cs index b69910ab..cd79c75c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/AgreementsFixture.cs @@ -18,6 +18,7 @@ public AgreementsFixture() protected override void OnAddApiVersioning( ApiVersioningOptions options ) { options.ReportApiVersions = true; + options.UseApiBehavior = false; options.Conventions.Add( new VersionByNamespaceConvention() ); } diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/AgreementsController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/AgreementsController.cs index 82504fe1..24b1250c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/AgreementsController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/AgreementsController.cs @@ -2,7 +2,7 @@ { using Models; - public class AgreementsController : Controller + public class AgreementsController : ControllerBase { [HttpGet] public IActionResult Get( string accountId, ApiVersion apiVersion ) => Ok( new Agreement( GetType().FullName, accountId, apiVersion.ToString() ) ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/OrdersController.cs index 565b7aec..62e565f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V1/OrdersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.ByNamespace.Models; using System; + [ApiController] [Route( "api/[controller]" )] public class OrdersController : ControllerBase { diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/AgreementsController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/AgreementsController.cs index a07e412b..00ab4656 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/AgreementsController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/AgreementsController.cs @@ -2,7 +2,7 @@ { using Models; - public class AgreementsController : Controller + public class AgreementsController : ControllerBase { [HttpGet] public IActionResult Get( string accountId, ApiVersion apiVersion ) => Ok( new Agreement( GetType().FullName, accountId, apiVersion.ToString() ) ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/OrdersController.cs index 43d20d06..453ace6c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V2/OrdersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.ByNamespace.Models; using System; + [ApiController] [Route( "api/[controller]" )] public class OrdersController : V1.OrdersController { diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/AgreementsController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/AgreementsController.cs index 62d8955b..1b19dd03 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/AgreementsController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/AgreementsController.cs @@ -2,7 +2,7 @@ { using Models; - public class AgreementsController : Controller + public class AgreementsController : ControllerBase { [HttpGet] public IActionResult Get( string accountId, ApiVersion apiVersion ) => Ok( new Agreement( GetType().FullName, accountId, apiVersion.ToString() ) ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/OrdersController.cs index aebd4cb6..66bd94b3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/Controllers/V3/OrdersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.ByNamespace.Models; using System; + [ApiController] [Route( "api/[controller]" )] public class OrdersController : V2.OrdersController { diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/OrdersEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/OrdersEndpointFixture.cs new file mode 100644 index 00000000..fdd59aa1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/OrdersEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.ByNamespace +{ + public class OrdersEndpointFixture : OrdersFixture + { + public OrdersEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a query string.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a query string.cs index ba8a2fac..5546464c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a query string.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a query string.cs @@ -62,4 +62,10 @@ public async Task then_get_should_return_400_for_an_unspecified_version() public when_using_a_query_string( AgreementsFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( AgreementsEndpointCollection ) )] + public class when_using_a_query_string_ : when_using_a_query_string + { + public when_using_a_query_string_( AgreementsEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a url segment.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a url segment.cs index 2321dd81..5149db59 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a url segment.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using a url segment.cs @@ -47,4 +47,10 @@ public async Task then_get_should_return_400_for_an_unsupported_version() public when_using_a_url_segment( AgreementsFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( AgreementsEndpointCollection ) )] + public class when_using_a_url_segment_ : when_using_a_url_segment + { + public when_using_a_url_segment_( AgreementsEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using an action.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using an action.cs index fda21ae4..530cd2ca 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using an action.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/ByNamespace/given a versioned Controller per namespace/when using an action.cs @@ -70,4 +70,9 @@ public async Task then_delete_should_return_204( string requestUrl ) public when_using_an_action( OrdersFixture fixture ) : base( fixture ) { } } + + public class when_using_an_action_ : when_using_an_action, IClassFixture + { + public when_using_an_action_( OrdersEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorld2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorld2Controller.cs index 35086dd3..638cb986 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorld2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorld2Controller.cs @@ -4,8 +4,9 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [Route( "api/v{version:apiVersion}/helloworld" )] - public class HelloWorld2Controller : Controller + public class HelloWorld2Controller : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( HelloWorld2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorldController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorldController.cs index 4f487ef4..64c2839a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorldController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/HelloWorldController.cs @@ -4,8 +4,9 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [Route( "api/v{version:apiVersion}/[controller]" )] - public class HelloWorldController : Controller + public class HelloWorldController : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( HelloWorldController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/OrdersController.cs index 1793dd41..560b873c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/OrdersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [Route( "api/[controller]" )] public class OrdersController : ControllerBase { diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/Values2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/Values2Controller.cs index ef82b8e1..2d0d1124 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/Values2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/Values2Controller.cs @@ -3,8 +3,9 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [Route( "api/values" )] - public class Values2Controller : Controller + public class Values2Controller : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( Values2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/ValuesController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/ValuesController.cs index 30b11769..d105416f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/ValuesController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/Controllers/ValuesController.cs @@ -3,8 +3,9 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [Route( "api/[controller]" )] - public class ValuesController : Controller + public class ValuesController : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/ConventionsEndpointCollection.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/ConventionsEndpointCollection.cs new file mode 100644 index 00000000..2e72f986 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/ConventionsEndpointCollection.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Conventions +{ + using System; + using Xunit; + + [CollectionDefinition( nameof( ConventionsEndpointCollection ) )] + public class ConventionsEndpointCollection : ICollectionFixture + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/ConventionsEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/ConventionsEndpointFixture.cs new file mode 100644 index 00000000..8d282198 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/ConventionsEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Conventions +{ + public class ConventionsEndpointFixture : ConventionsFixture + { + public ConventionsEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a query string and split into two types.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a query string and split into two types.cs index d4ef29d0..912596e3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a query string and split into two types.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a query string and split into two types.cs @@ -63,4 +63,10 @@ public async Task then_get_should_return_400_for_an_unspecified_version() public when_using_a_query_string_and_split_into_two_types( ConventionsFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( ConventionsEndpointCollection ) )] + public class when_using_a_query_string_and_split_into_two_types_ : when_using_a_query_string_and_split_into_two_types + { + public when_using_a_query_string_and_split_into_two_types_( ConventionsEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a url segment.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a url segment.cs index bd67192f..21c0084f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a url segment.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using a url segment.cs @@ -68,4 +68,10 @@ public async Task then_get_should_return_400_for_an_unsupported_version() public when_using_a_url_segment( ConventionsFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( ConventionsEndpointCollection ) )] + public class when_using_a_url_segment_ : when_using_a_url_segment + { + public when_using_a_url_segment_( ConventionsEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using an action.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using an action.cs index 97aaca8d..478f31c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using an action.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/Conventions/given a versioned Controller using conventions/when using an action.cs @@ -74,4 +74,10 @@ public async Task then_delete_should_return_204( string requestUrl ) public when_using_an_action( ConventionsFixture fixture ) : base( fixture ) { } } + + [Collection( nameof( ConventionsEndpointCollection ) )] + public class when_using_an_action_ : when_using_an_action + { + public when_using_an_action_( ConventionsEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/HelloWorldController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/HelloWorldController.cs index 37fe5247..ba771518 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/HelloWorldController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/HelloWorldController.cs @@ -3,11 +3,10 @@ using AspNetCore.Routing; using Microsoft.AspNetCore.Mvc; using Models; - using System; - using System.Collections.Generic; + [ApiController] [Route( "api/[controller]" )] - public class HelloWorldController : Controller + public class HelloWorldController : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( HelloWorldController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/Values2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/Values2Controller.cs index f058e9f4..5f8930b8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/Values2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/Values2Controller.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "2.0" )] [Route( "api/values" )] - public class Values2Controller : Controller + public class Values2Controller : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( Values2Controller ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/ValuesController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/ValuesController.cs index 62903c2b..d40c117c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/ValuesController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/Controllers/ValuesController.cs @@ -3,9 +3,10 @@ using Microsoft.AspNetCore.Mvc; using System; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/[controller]" )] - public class ValuesController : Controller + public class ValuesController : ControllerBase { [HttpGet] public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } ); diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/MediaTypeNegotiationEndpointFixture.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/MediaTypeNegotiationEndpointFixture.cs new file mode 100644 index 00000000..ea7db7a5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/MediaTypeNegotiationEndpointFixture.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.MediaTypeNegotiation +{ + public class MediaTypeNegotiationEndpointFixture : MediaTypeNegotiationFixture + { + public MediaTypeNegotiationEndpointFixture() => EnableEndpointRouting = true; + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/given a versioned Controller/when using media type negotiation.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/given a versioned Controller/when using media type negotiation.cs index 3978ffab..88032739 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/given a versioned Controller/when using media type negotiation.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Mvc/MediaTypeNegotiation/given a versioned Controller/when using media type negotiation.cs @@ -60,6 +60,8 @@ public async Task then_get_should_return_current_version_for_an_unspecified_vers // arrange var example = new { controller = "", version = "" }; + Client.DefaultRequestHeaders.Clear(); + // act var response = await GetAsync( requestUrl ).EnsureSuccessStatusCode(); var content = await response.Content.ReadAsExampleAsync( example ); @@ -74,6 +76,7 @@ public async Task then_post_should_return_201() // arrange var content = new StringContent( "{\"text\":\"Test\"}", UTF8 ); + Client.DefaultRequestHeaders.Clear(); content.Headers.ContentType = Parse( "application/json;v=1.0" ); // act @@ -85,4 +88,9 @@ public async Task then_post_should_return_201() public when_using_media_type_negotiation( MediaTypeNegotiationFixture fixture ) : base( fixture ) { } } + + public class when_using_media_type_negotiation_ : when_using_media_type_negotiation, IClassFixture + { + public when_using_media_type_negotiation_( MediaTypeNegotiationEndpointFixture fixture ) : base( fixture ) { } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs index a9338e4f..9158245e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/Orders3Controller.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OData.Models; + [ApiController] [ApiVersion( "3.0" )] [Route( "api/orders" )] public class Orders3Controller : ControllerBase diff --git a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs index 411c84be..db27a2f0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/OData/Advanced/Controllers/OrdersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OData.Models; + [ApiController] [Route( "api/orders" )] public class OrdersController : ControllerBase { diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/ApiVersionParameterDescriptionContextTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/ApiVersionParameterDescriptionContextTest.cs index c56ff847..8d100bda 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/ApiVersionParameterDescriptionContextTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/ApiVersionParameterDescriptionContextTest.cs @@ -21,13 +21,13 @@ public void add_parameter_should_add_descriptor_for_query_parameter() // arrange var version = new ApiVersion( 1, 0 ); var description = NewApiDescription( version ); - var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ); + var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ).Object; var options = new ApiExplorerOptions() { DefaultApiVersion = version, ApiVersionParameterSource = new QueryStringApiVersionReader() }; - var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); + var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata, options ); // act context.AddParameter( "api-version", Query ); @@ -39,12 +39,9 @@ public void add_parameter_should_add_descriptor_for_query_parameter() Name = "api-version", ModelMetadata = modelMetadata, Source = BindingSource.Query, - RouteInfo = new ApiParameterRouteInfo() - { - DefaultValue = "1.0", - IsOptional = false - }, - Type = typeof( string ) + DefaultValue = (object) "1.0", + IsRequired = true, + Type = typeof( string ), }, o => o.ExcludingMissingMembers() ); } @@ -55,13 +52,13 @@ public void add_parameter_should_add_descriptor_for_header() // arrange var version = new ApiVersion( 1, 0 ); var description = NewApiDescription( version ); - var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ); + var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ).Object; var options = new ApiExplorerOptions() { DefaultApiVersion = version, ApiVersionParameterSource = new HeaderApiVersionReader() }; - var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); + var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata, options ); // act context.AddParameter( "api-version", Header ); @@ -73,12 +70,9 @@ public void add_parameter_should_add_descriptor_for_header() Name = "api-version", ModelMetadata = modelMetadata, Source = BindingSource.Header, - RouteInfo = new ApiParameterRouteInfo() - { - DefaultValue = "1.0", - IsOptional = false - }, - Type = typeof( string ) + DefaultValue = (object) "1.0", + IsRequired = true, + Type = typeof( string ), }, o => o.ExcludingMissingMembers() ); } @@ -98,13 +92,13 @@ public void add_parameter_should_add_descriptor_for_path() }; var version = new ApiVersion( 1, 0 ); var description = NewApiDescription( version, parameter ); - var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ); + var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ).Object; var options = new ApiExplorerOptions() { DefaultApiVersion = version, ApiVersionParameterSource = new UrlSegmentApiVersionReader() }; - var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); + var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata, options ); // act context.AddParameter( "api-version", Path ); @@ -116,13 +110,15 @@ public void add_parameter_should_add_descriptor_for_path() Name = "api-version", ModelMetadata = modelMetadata, Source = BindingSource.Path, + DefaultValue = (object) "1.0", + IsRequired = true, RouteInfo = new ApiParameterRouteInfo() { DefaultValue = "1.0", IsOptional = false, - Constraints = parameter.RouteInfo.Constraints + Constraints = parameter.RouteInfo.Constraints, }, - Type = typeof( string ) + Type = typeof( string ), }, o => o.ExcludingMissingMembers() ); } @@ -161,15 +157,17 @@ public void add_parameter_should_remove_other_descriptors_after_path_parameter_i new { Name = "api-version", - ModelMetadata = modelMetadata, + ModelMetadata = modelMetadata.Object, Source = BindingSource.Path, + DefaultValue = (object) "1.0", + IsRequired = true, RouteInfo = new ApiParameterRouteInfo() { DefaultValue = "1.0", IsOptional = false, - Constraints = parameter.RouteInfo.Constraints + Constraints = parameter.RouteInfo.Constraints, }, - Type = typeof( string ) + Type = typeof( string ), }, o => o.ExcludingMissingMembers() ); } @@ -183,7 +181,7 @@ public void add_parameter_should_not_add_query_parameter_after_path_parameter_ha Name = "api-version", RouteInfo = new ApiParameterRouteInfo() { - Constraints = new IRouteConstraint[] { new ApiVersionRouteConstraint() } + Constraints = new IRouteConstraint[] { new ApiVersionRouteConstraint() }, }, Source = BindingSource.Path }; @@ -193,7 +191,7 @@ public void add_parameter_should_not_add_query_parameter_after_path_parameter_ha var options = new ApiExplorerOptions() { DefaultApiVersion = version, - ApiVersionParameterSource = Combine( new QueryStringApiVersionReader(), new UrlSegmentApiVersionReader() ) + ApiVersionParameterSource = Combine( new QueryStringApiVersionReader(), new UrlSegmentApiVersionReader() ), }; var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); @@ -218,26 +216,26 @@ public void add_parameter_should_add_descriptor_for_media_type_parameter() ActionDescriptor = new ActionDescriptor() { Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( version ) } }, SupportedRequestFormats = { - new ApiRequestFormat() { MediaType = Json } + new ApiRequestFormat() { MediaType = Json }, }, SupportedResponseTypes = { - new ApiResponseType() - { - ApiResponseFormats = + new ApiResponseType() + { + ApiResponseFormats = { - new ApiResponseFormat() { MediaType = Json } - } - } - } + new ApiResponseFormat() { MediaType = Json }, + }, + }, + }, }; - var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ); + var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ).Object; var options = new ApiExplorerOptions() { DefaultApiVersion = version, - ApiVersionParameterSource = new MediaTypeApiVersionReader() + ApiVersionParameterSource = new MediaTypeApiVersionReader(), }; - var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); + var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata, options ); // act context.AddParameter( "v", MediaTypeParameter ); @@ -253,14 +251,14 @@ public void add_parameter_should_add_optional_parameter_when_allowed() // arrange var version = new ApiVersion( 1, 0 ); var description = NewApiDescription( version ); - var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ); + var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ).Object; var options = new ApiExplorerOptions() { DefaultApiVersion = version, ApiVersionParameterSource = new QueryStringApiVersionReader(), - AssumeDefaultVersionWhenUnspecified = true + AssumeDefaultVersionWhenUnspecified = true, }; - var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); + var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata, options ); // act context.AddParameter( "api-version", Query ); @@ -272,12 +270,9 @@ public void add_parameter_should_add_optional_parameter_when_allowed() Name = "api-version", ModelMetadata = modelMetadata, Source = BindingSource.Query, - RouteInfo = new ApiParameterRouteInfo() - { - DefaultValue = "1.0", - IsOptional = true - }, - Type = typeof( string ) + DefaultValue = (object) "1.0", + IsRequired = false, + Type = typeof( string ), }, o => o.ExcludingMissingMembers() ); } @@ -288,21 +283,21 @@ public void add_parameter_should_make_parameters_optional_after_first_parameter( // arrange var version = new ApiVersion( 1, 0 ); var description = NewApiDescription( version ); - var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ); + var modelMetadata = new Mock( ModelMetadataIdentity.ForType( typeof( string ) ) ).Object; var options = new ApiExplorerOptions() { DefaultApiVersion = version, ApiVersionParameterSource = Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader() ) }; - var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata.Object, options ); + var context = new ApiVersionParameterDescriptionContext( description, version, modelMetadata, options ); // act context.AddParameter( "api-version", Query ); context.AddParameter( "api-version", Header ); // assert - description.ParameterDescriptions[0].RouteInfo.IsOptional.Should().BeFalse(); - description.ParameterDescriptions[1].RouteInfo.IsOptional.Should().BeTrue(); + description.ParameterDescriptions[0].IsRequired.Should().BeTrue(); + description.ParameterDescriptions[1].IsRequired.Should().BeFalse(); } static ApiDescription NewApiDescription( ApiVersion apiVersion, params ApiParameterDescription[] parameters ) diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests.csproj b/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests.csproj index 51805313..d35eba7c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests.csproj @@ -1,8 +1,8 @@  - netcoreapp2.0;net461 - netcoreapp2.0 + netcoreapp2.2;net461 + netcoreapp2.2 Microsoft.AspNetCore.Mvc.ApiExplorer true @@ -12,8 +12,8 @@ - - + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/ApplicationModels/ApiBehaviorSpecificationTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/ApplicationModels/ApiBehaviorSpecificationTest.cs new file mode 100644 index 00000000..915e943f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/ApplicationModels/ApiBehaviorSpecificationTest.cs @@ -0,0 +1,42 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using FluentAssertions; + using System; + using System.Reflection; + using Xunit; + + public class ApiBehaviorSpecificationTest + { + [Theory] + [InlineData( typeof( ApiBehaviorController ), true )] + [InlineData( typeof( NonApiBehaviorController ), false )] + public void is_satisfied_by_should_return_expected_result( Type controllerType, bool expected ) + { + // arrange + var specification = new ApiBehaviorSpecification(); + var attributes = controllerType.GetCustomAttributes( inherit: false ); + var controller = new ControllerModel( controllerType.GetTypeInfo(), attributes ); + + // act + var result = specification.IsSatisfiedBy( controller ); + + // assert + result.Should().Be( expected ); + } + + [ApiController] + [Route( "api/test" )] + sealed class ApiBehaviorController : ControllerBase + { + [HttpGet] + public IActionResult Get() => Ok(); + } + + [Route( "/" )] + sealed class NonApiBehaviorController : Controller + { + [HttpGet] + public IActionResult Index() => View(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs new file mode 100644 index 00000000..d1d4fbcc --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/ApplicationModels/DefaultApiControllerFilterTest.cs @@ -0,0 +1,61 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using FluentAssertions; + using Moq; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Xunit; + + public class DefaultApiControllerFilterTest + { + [Fact] + public void apply_should_not_filter_list_without_specifications() + { + // arrange + var filter = new DefaultApiControllerFilter( Enumerable.Empty() ); + var controllerType = typeof( ControllerBase ).GetTypeInfo(); + var attributes = Array.Empty(); + var controllers = new List() + { + new ControllerModel( controllerType, attributes ), + new ControllerModel( controllerType, attributes ), + new ControllerModel( controllerType, attributes ), + }; + + // act + var result = filter.Apply( controllers ); + + // assert + result.Should().BeSameAs( controllers ); + } + + [Fact] + public void apply_should_filter_controllers() + { + // arrange + var specification = new Mock(); + var controllerBaseType = typeof( ControllerBase ).GetTypeInfo(); + var controllerType = typeof( Controller ).GetTypeInfo(); + + specification.Setup( s => s.IsSatisfiedBy( It.Is( m => m.ControllerType.Equals( controllerBaseType ) ) ) ).Returns( true ); + specification.Setup( s => s.IsSatisfiedBy( It.Is( m => m.ControllerType.Equals( controllerType ) ) ) ).Returns( false ); + + var filter = new DefaultApiControllerFilter( new[] { specification.Object } ); + var attributes = Array.Empty(); + var controllers = new List() + { + new ControllerModel( controllerType, attributes ), + new ControllerModel( controllerBaseType, attributes ), + new ControllerModel( controllerType, attributes ), + }; + + // act + var result = filter.Apply( controllers ); + + // assert + result.Single().Should().BeSameAs( controllers[1] ); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.AspNetCore.Mvc.Versioning.Tests.csproj b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.AspNetCore.Mvc.Versioning.Tests.csproj index d46bd37a..aa49bda8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.AspNetCore.Mvc.Versioning.Tests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.AspNetCore.Mvc.Versioning.Tests.csproj @@ -1,8 +1,8 @@  - netcoreapp2.0;net461 - netcoreapp2.0 + netcoreapp2.2;net461 + netcoreapp2.2 Microsoft.AspNetCore.Mvc true @@ -12,7 +12,7 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensionsTest.cs index 76a012b1..ac9a729d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Microsoft.Extensions.DependencyInjection/IServiceCollectionExtensionsTest.cs @@ -30,12 +30,15 @@ public void add_api_versioning_should_configure_mvc_with_default_options() services.Single( sd => sd.ServiceType == typeof( IErrorResponseProvider ) ).ImplementationFactory.Should().NotBeNull(); services.Single( sd => sd.ServiceType == typeof( IActionSelector ) ).ImplementationType.Should().Be( typeof( ApiVersionActionSelector ) ); services.Single( sd => sd.ServiceType == typeof( IApiVersionRoutePolicy ) ).ImplementationType.Should().Be( typeof( DefaultApiVersionRoutePolicy ) ); + services.Single( sd => sd.ServiceType == typeof( IApiControllerFilter ) ).ImplementationType.Should().Be( typeof( DefaultApiControllerFilter ) ); services.Single( sd => sd.ServiceType == typeof( ReportApiVersionsAttribute ) ).ImplementationType.Should().Be( typeof( ReportApiVersionsAttribute ) ); services.Single( sd => sd.ServiceType == typeof( IReportApiVersions ) ).ImplementationFactory.Should().NotBeNull(); services.Any( sd => sd.ServiceType == typeof( IPostConfigureOptions ) && sd.ImplementationType == typeof( ApiVersioningMvcOptionsSetup ) ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IPostConfigureOptions ) && sd.ImplementationType == typeof( ApiVersioningRouteOptionsSetup ) ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IApplicationModelProvider ) && sd.ImplementationType == typeof( ApiVersioningApplicationModelProvider ) ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IActionDescriptorProvider ) && sd.ImplementationType == typeof( ApiVersionCollator ) ).Should().BeTrue(); + services.Any( sd => sd.ServiceType == typeof( IApiControllerSpecification ) && sd.ImplementationType == typeof( ApiBehaviorSpecification ) ).Should().BeTrue(); + services.Any( sd => sd.ServiceType == typeof( MatcherPolicy ) && sd.ImplementationType == typeof( ApiVersionMatcherPolicy ) ).Should().BeTrue(); } [Fact] @@ -59,12 +62,15 @@ public void add_api_versioning_should_configure_mvc_with_custom_options() services.Single( sd => sd.ServiceType == typeof( IErrorResponseProvider ) ).ImplementationFactory.Should().NotBeNull(); services.Single( sd => sd.ServiceType == typeof( IActionSelector ) ).ImplementationType.Should().Be( typeof( ApiVersionActionSelector ) ); services.Single( sd => sd.ServiceType == typeof( IApiVersionRoutePolicy ) ).ImplementationType.Should().Be( typeof( DefaultApiVersionRoutePolicy ) ); + services.Single( sd => sd.ServiceType == typeof( IApiControllerFilter ) ).ImplementationType.Should().Be( typeof( DefaultApiControllerFilter ) ); services.Single( sd => sd.ServiceType == typeof( ReportApiVersionsAttribute ) ).ImplementationType.Should().Be( typeof( ReportApiVersionsAttribute ) ); services.Single( sd => sd.ServiceType == typeof( IReportApiVersions ) ).ImplementationFactory.Should().NotBeNull(); services.Any( sd => sd.ServiceType == typeof( IPostConfigureOptions ) && sd.ImplementationType == typeof( ApiVersioningMvcOptionsSetup ) ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IPostConfigureOptions ) && sd.ImplementationType == typeof( ApiVersioningRouteOptionsSetup ) ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IApplicationModelProvider ) && sd.ImplementationType == typeof( ApiVersioningApplicationModelProvider ) ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IActionDescriptorProvider ) && sd.ImplementationType == typeof( ApiVersionCollator ) ).Should().BeTrue(); + services.Any( sd => sd.ServiceType == typeof( IApiControllerSpecification ) && sd.ImplementationType == typeof( ApiBehaviorSpecification ) ).Should().BeTrue(); + services.Any( sd => sd.ServiceType == typeof( MatcherPolicy ) && sd.ImplementationType == typeof( ApiVersionMatcherPolicy ) ).Should().BeTrue(); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Routing/ApiVersionMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Routing/ApiVersionMatcherPolicyTest.cs new file mode 100644 index 00000000..890aeb6f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Routing/ApiVersionMatcherPolicyTest.cs @@ -0,0 +1,343 @@ +namespace Microsoft.AspNetCore.Mvc.Routing +{ + using FluentAssertions; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Features; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.ActionConstraints; + using Microsoft.AspNetCore.Mvc.Internal; + using Microsoft.AspNetCore.Mvc.Versioning; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Matching; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Moq; + using System; + using System.Threading.Tasks; + using Xunit; + using static Moq.Times; + using static System.Array; + using static System.Threading.Tasks.Task; + + public class ApiVersionMatcherPolicyTest + { + [Fact] + public void applies_to_endpoints_should_return_true_for_api_versioned_actions() + { + // arrange + var policy = new ApiVersionMatcherPolicy( NewDefaultOptions(), NewReporter(), NewLoggerFactory() ); + var items = new object[] + { + new ActionDescriptor() + { + Properties = { [typeof(ApiVersionModel)] = ApiVersionModel.Default }, + }, + }; + var endpoints = new[] + { + new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ), + }; + + // act + var result = policy.AppliesToEndpoints( endpoints ); + + // assert + result.Should().BeTrue(); + } + + [Fact] + public void applies_to_endpoints_should_return_false_for_normal_actions() + { + // arrange + var policy = new ApiVersionMatcherPolicy( NewDefaultOptions(), NewReporter(), NewLoggerFactory() ); + var items = new object[] { new ActionDescriptor() }; + var endpoints = new[] + { + new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ), + }; + + // act + var result = policy.AppliesToEndpoints( endpoints ); + + // assert + result.Should().BeFalse(); + } + + [Fact] + public async Task apply_should_use_400_endpoint_for_ambiguous_api_version() + { + // arrange + var feature = new Mock(); + var errorResponses = new Mock(); + var result = new Mock(); + + feature.SetupGet( f => f.RequestedApiVersion ).Throws( new AmbiguousApiVersionException( "Test", new[] { "1.0", "2.0" } ) ); + result.Setup( r => r.ExecuteResultAsync( It.IsAny() ) ).Returns( CompletedTask ); + errorResponses.Setup( er => er.CreateResponse( It.IsAny() ) ).Returns( result.Object ); + + var options = Options.Create( new ApiVersioningOptions() { ErrorResponses = errorResponses.Object } ); + var policy = new ApiVersionMatcherPolicy( options, NewReporter(), NewLoggerFactory() ); + var httpContext = NewHttpContext( feature ); + var context = new EndpointSelectorContext(); + var candidates = new CandidateSet( Empty(), Empty(), Empty() ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + await context.Endpoint.RequestDelegate( httpContext ); + + // assert + result.Verify( r => r.ExecuteResultAsync( It.IsAny() ), Once() ); + errorResponses.Verify( er => er.CreateResponse( It.Is( c => c.StatusCode == 400 && c.ErrorCode == "AmbiguousApiVersion" ) ), Once() ); + } + + [Fact] + public async Task apply_should_have_candidate_for_matched_api_version() + { + // arrange + var feature = new Mock(); + var context = new EndpointSelectorContext(); + var items = new object[] + { + new ActionDescriptor() + { + Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ) }, + }, + }; + var endpoint = new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ); + var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 } ); + var policy = new ApiVersionMatcherPolicy( NewDefaultOptions(), NewReporter(), NewLoggerFactory() ); + + feature.SetupProperty( f => f.RequestedApiVersion, new ApiVersion( 1, 0 ) ); + + var httpContext = NewHttpContext( feature ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + + // assert + candidates.IsValidCandidate( 0 ).Should().BeTrue(); + } + + [Fact] + public async Task apply_should_use_400_endpoint_for_unmatched_api_version() + { + // arrange + var feature = new Mock(); + var errorResponses = new Mock(); + var result = new Mock(); + + feature.SetupProperty( f => f.RawRequestedApiVersion, "2.0" ); + feature.SetupProperty( f => f.RequestedApiVersion, new ApiVersion( 2, 0 ) ); + result.Setup( r => r.ExecuteResultAsync( It.IsAny() ) ).Returns( CompletedTask ); + errorResponses.Setup( er => er.CreateResponse( It.IsAny() ) ).Returns( result.Object ); + + var options = Options.Create( new ApiVersioningOptions() { ErrorResponses = errorResponses.Object } ); + var policy = new ApiVersionMatcherPolicy( options, NewReporter(), NewLoggerFactory() ); + var context = new EndpointSelectorContext(); + var items = new object[] + { + new ActionDescriptor() + { + DisplayName = "Test", + ActionConstraints = new IActionConstraintMetadata[]{ new HttpMethodActionConstraint(new[] { "GET" }) }, + Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ) }, + }, + }; + var endpoint = new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ); + var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 } ); + var httpContext = NewHttpContext( feature ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + await context.Endpoint.RequestDelegate( httpContext ); + + // assert + result.Verify( r => r.ExecuteResultAsync( It.IsAny() ), Once() ); + errorResponses.Verify( er => er.CreateResponse( It.Is( c => c.StatusCode == 400 && c.ErrorCode == "UnsupportedApiVersion" ) ), Once() ); + } + + [Fact] + public async Task apply_should_use_405_endpoint_for_unmatched_api_version() + { + // arrange + var feature = new Mock(); + var errorResponses = new Mock(); + var result = new Mock(); + + feature.SetupProperty( f => f.RawRequestedApiVersion, "1.0" ); + feature.SetupProperty( f => f.RequestedApiVersion, new ApiVersion( 1, 0 ) ); + result.Setup( r => r.ExecuteResultAsync( It.IsAny() ) ).Returns( CompletedTask ); + errorResponses.Setup( er => er.CreateResponse( It.IsAny() ) ).Returns( result.Object ); + + var options = Options.Create( new ApiVersioningOptions() { ErrorResponses = errorResponses.Object } ); + var policy = new ApiVersionMatcherPolicy( options, NewReporter(), NewLoggerFactory() ); + var context = new EndpointSelectorContext(); + var items = new object[] + { + new ActionDescriptor() + { + DisplayName = "Test", + ActionConstraints = new IActionConstraintMetadata[]{ new HttpMethodActionConstraint(new[] { "POST" }) }, + Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ) }, + }, + }; + var endpoint = new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ); + var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 } ); + var httpContext = NewHttpContext( feature ); + + candidates.SetValidity( 0, false ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + await context.Endpoint.RequestDelegate( httpContext ); + + // assert + result.Verify( r => r.ExecuteResultAsync( It.IsAny() ), Once() ); + errorResponses.Verify( er => er.CreateResponse( It.Is( c => c.StatusCode == 405 && c.ErrorCode == "UnsupportedApiVersion" ) ), Once() ); + } + + [Fact] + public async Task apply_should_use_400_endpoint_for_invalid_api_version() + { + // arrange + var feature = new Mock(); + var errorResponses = new Mock(); + var result = new Mock(); + + feature.SetupProperty( f => f.RawRequestedApiVersion, "blah" ); + feature.SetupProperty( f => f.RequestedApiVersion, default ); + result.Setup( r => r.ExecuteResultAsync( It.IsAny() ) ).Returns( CompletedTask ); + errorResponses.Setup( er => er.CreateResponse( It.IsAny() ) ).Returns( result.Object ); + + var options = Options.Create( new ApiVersioningOptions() { ErrorResponses = errorResponses.Object } ); + var policy = new ApiVersionMatcherPolicy( options, NewReporter(), NewLoggerFactory() ); + var context = new EndpointSelectorContext(); + var items = new object[] + { + new ActionDescriptor() + { + DisplayName = "Test", + ActionConstraints = new IActionConstraintMetadata[]{ new HttpMethodActionConstraint(new[] { "GET" }) }, + Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ) }, + }, + }; + var endpoint = new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ); + var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 } ); + var httpContext = NewHttpContext( feature ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + await context.Endpoint.RequestDelegate( httpContext ); + + // assert + result.Verify( r => r.ExecuteResultAsync( It.IsAny() ), Once() ); + errorResponses.Verify( er => er.CreateResponse( It.Is( c => c.StatusCode == 400 && c.ErrorCode == "InvalidApiVersion" ) ), Once() ); + } + + [Fact] + public async Task apply_should_use_400_endpoint_for_unspecified_api_version() + { + // arrange + var feature = new Mock(); + var errorResponses = new Mock(); + var result = new Mock(); + + feature.SetupProperty( f => f.RawRequestedApiVersion, default ); + feature.SetupProperty( f => f.RequestedApiVersion, default ); + result.Setup( r => r.ExecuteResultAsync( It.IsAny() ) ).Returns( CompletedTask ); + errorResponses.Setup( er => er.CreateResponse( It.IsAny() ) ).Returns( result.Object ); + + var options = Options.Create( new ApiVersioningOptions() { ErrorResponses = errorResponses.Object } ); + var policy = new ApiVersionMatcherPolicy( options, NewReporter(), NewLoggerFactory() ); + var context = new EndpointSelectorContext(); + var items = new object[] + { + new ActionDescriptor() + { + DisplayName = "Test", + ActionConstraints = new IActionConstraintMetadata[]{ new HttpMethodActionConstraint(new[] { "GET" }) }, + Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ) }, + }, + }; + var endpoint = new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ); + var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 } ); + var httpContext = NewHttpContext( feature ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + await context.Endpoint.RequestDelegate( httpContext ); + + // assert + result.Verify( r => r.ExecuteResultAsync( It.IsAny() ), Once() ); + errorResponses.Verify( er => er.CreateResponse( It.Is( c => c.StatusCode == 400 && c.ErrorCode == "ApiVersionUnspecified" ) ), Once() ); + } + + [Fact] + public async Task apply_should_have_candidate_for_unspecified_api_version() + { + // arrange + var feature = new Mock(); + var context = new EndpointSelectorContext(); + var items = new object[] + { + new ActionDescriptor() + { + Properties = { [typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) ) }, + }, + }; + var endpoint = new Endpoint( c => CompletedTask, new EndpointMetadataCollection( items ), default ); + var candidates = new CandidateSet( new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 } ); + var options = Options.Create( new ApiVersioningOptions() { AssumeDefaultVersionWhenUnspecified = true } ); + var policy = new ApiVersionMatcherPolicy( options, NewReporter(), NewLoggerFactory() ); + + feature.SetupProperty( f => f.RawRequestedApiVersion, default ); + feature.SetupProperty( f => f.RequestedApiVersion, default ); + + var httpContext = NewHttpContext( feature ); + + // act + await policy.ApplyAsync( httpContext, context, candidates ); + + // assert + candidates.IsValidCandidate( 0 ).Should().BeTrue(); + feature.Object.RequestedApiVersion.Should().Be( new ApiVersion( 1, 0 ) ); + } + + static IOptions NewDefaultOptions() => Options.Create( new ApiVersioningOptions() ); + + static IReportApiVersions NewReporter() => Mock.Of(); + + static ILoggerFactory NewLoggerFactory() + { + var logger = new Mock(); + var loggerFactory = new Mock(); + + logger.Setup( l => l.IsEnabled( It.IsAny() ) ).Returns( false ); + loggerFactory.Setup( lf => lf.CreateLogger( It.IsAny() ) ).Returns( logger.Object ); + + return loggerFactory.Object; + } + + static HttpContext NewHttpContext( Mock feature ) + { + var features = new FeatureCollection(); + var request = new Mock(); + var response = new Mock(); + var httpContext = new Mock(); + + features.Set( feature.Object ); + request.SetupProperty( r => r.Scheme, Uri.UriSchemeHttp ); + request.SetupProperty( r => r.Host, new HostString( "tempuri.org" ) ); + request.SetupProperty( r => r.Path, PathString.Empty ); + request.SetupProperty( r => r.PathBase, PathString.Empty ); + request.SetupProperty( r => r.QueryString, QueryString.Empty ); + request.SetupProperty( r => r.Method, "GET" ); + response.SetupGet( r => r.Headers ).Returns( Mock.Of() ); + httpContext.SetupGet( hc => hc.Features ).Returns( features ); + httpContext.SetupGet( hc => hc.Request ).Returns( request.Object ); + httpContext.SetupGet( hc => hc.Response ).Returns( response.Object ); + + return httpContext.Object; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousController.cs index 4a49cbed..d4328550 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; [ApiVersion( "1.0" )] - public sealed class AmbiguousController : Controller + public sealed class AmbiguousController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousNeutralController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousNeutralController.cs index faebf0d5..67a0f9ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousNeutralController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousNeutralController.cs @@ -5,7 +5,7 @@ [ApiVersionNeutral] [ControllerName( "Ambiguous" )] - public sealed class AmbiguousNeutralController : Controller + public sealed class AmbiguousNeutralController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousToo2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousToo2Controller.cs index c9cef013..0bfce725 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousToo2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousToo2Controller.cs @@ -5,7 +5,7 @@ [ApiVersion( "1.0" )] [ControllerName( "AmbiguousToo" )] - public sealed class AmbiguousToo2Controller : Controller + public sealed class AmbiguousToo2Controller : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousTooController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousTooController.cs index 74590aeb..0718ddd9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousTooController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AmbiguousTooController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; [ApiVersion( "1.0" )] - public sealed class AmbiguousTooController : Controller + public sealed class AmbiguousTooController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRoute2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRoute2Controller.cs index 24ed1dfd..cd98934d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRoute2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRoute2Controller.cs @@ -3,10 +3,11 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersion( "5.0" )] [ApiVersion( "4.0", Deprecated = true )] [Route( "api/v{version:apiVersion}/attributed" )] - public sealed class ApiVersionedRoute2Controller : Controller + public sealed class ApiVersionedRoute2Controller : ControllerBase { [HttpGet] [MapToApiVersion( "4.0" )] diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRouteController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRouteController.cs index fbbfe429..b03854f8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRouteController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ApiVersionedRouteController.cs @@ -3,11 +3,12 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersion( "1.0" )] [ApiVersion( "2.0" )] [ApiVersion( "3.0" )] [Route( "api/v{version:apiVersion}/attributed" )] - public sealed class ApiVersionedRouteController : Controller + public sealed class ApiVersionedRouteController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous2Controller.cs index a38405df..ee6309db 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous2Controller.cs @@ -3,9 +3,10 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/attributed/ambiguous" )] - public sealed class AttributeRoutedAmbiguous2Controller : Controller + public sealed class AttributeRoutedAmbiguous2Controller : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous3Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous3Controller.cs index 5dcffbe4..653d7158 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous3Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguous3Controller.cs @@ -3,9 +3,10 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/attributed/ambiguous" )] - public sealed class AttributeRoutedAmbiguous3Controller : Controller + public sealed class AttributeRoutedAmbiguous3Controller : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousController.cs index 8760003f..6d6e740a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousController.cs @@ -3,9 +3,10 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersion( "1.0" )] [Route( "api/attributed-ambiguous" )] - public sealed class AttributeRoutedAmbiguousController : Controller + public sealed class AttributeRoutedAmbiguousController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousNeutralController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousNeutralController.cs index 9953e6a5..c44eb8ea 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousNeutralController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedAmbiguousNeutralController.cs @@ -3,9 +3,10 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersionNeutral] [Route( "api/attributed-ambiguous" )] - public sealed class AttributeRoutedAmbiguousNeutralController : Controller + public sealed class AttributeRoutedAmbiguousNeutralController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest2Controller.cs index 367a06a5..88730437 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest2Controller.cs @@ -3,11 +3,12 @@ using System; using System.Threading.Tasks; + [ApiController] [AdvertiseApiVersions( "1.0" )] [ApiVersion( "2.0" )] [ApiVersion( "3.0" )] [Route( "api/attributed" )] - public sealed class AttributeRoutedTest2Controller : Controller + public sealed class AttributeRoutedTest2Controller : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest4Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest4Controller.cs index 594b22c4..5d2903e9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest4Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTest4Controller.cs @@ -3,11 +3,12 @@ using System; using System.Threading.Tasks; + [ApiController] [AdvertiseApiVersions( "1.0", "2.0", "3.0" )] [AdvertiseApiVersions( "3.0-Alpha", Deprecated = true )] [ApiVersion( "4.0" )] [Route( "api/attributed" )] - public sealed class AttributeRoutedTest4Controller : Controller + public sealed class AttributeRoutedTest4Controller : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTestController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTestController.cs index 4ea81719..546bc7df 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTestController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedTestController.cs @@ -3,8 +3,9 @@ using System; using System.Threading.Tasks; + [ApiController] [Route( "api/attributed" )] - public sealed class AttributeRoutedTestController : Controller + public sealed class AttributeRoutedTestController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedVersionNeutralController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedVersionNeutralController.cs index fb557361..84c71880 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedVersionNeutralController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/AttributeRoutedVersionNeutralController.cs @@ -3,9 +3,10 @@ using System; using System.Threading.Tasks; + [ApiController] [ApiVersionNeutral] [Route( "api/attributed-neutral" )] - public sealed class AttributeRoutedVersionNeutralController : Controller + public sealed class AttributeRoutedVersionNeutralController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ConventionsController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ConventionsController.cs index b23cc9a3..e71b17c9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ConventionsController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/ConventionsController.cs @@ -3,8 +3,9 @@ using System; using System.Threading.Tasks; + [ApiController] [Route( "api/[controller]" )] - public sealed class ConventionsController : Controller + public sealed class ConventionsController : ControllerBase { [HttpGet] public string Get() => "Test (1.0)"; diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/NeutralController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/NeutralController.cs index 77c8799e..093adda8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/NeutralController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/NeutralController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; [ApiVersionNeutral] - public sealed class NeutralController : Controller + public sealed class NeutralController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/OrdersController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/OrdersController.cs index faaed31f..dc11d1cb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/OrdersController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/OrdersController.cs @@ -6,7 +6,7 @@ [ApiVersion( "2015-11-15" )] [ApiVersion( "2016-06-06" )] - public class OrdersController : Controller + public class OrdersController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( Ok( "Version 2015-11-15" ) ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestController.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestController.cs index bef28126..46444592 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestController.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestController.cs @@ -3,7 +3,7 @@ using System; using System.Threading.Tasks; - public sealed class TestController : Controller + public sealed class TestController : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestVersion2Controller.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestVersion2Controller.cs index 4ffe62a7..a8c5ec19 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestVersion2Controller.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Simulators/TestVersion2Controller.cs @@ -8,7 +8,7 @@ [ApiVersion( "1.8", Deprecated = true )] [ApiVersion( "1.9", Deprecated = true )] [ControllerName( "Test" )] - public sealed class TestVersion2Controller : Controller + public sealed class TestVersion2Controller : ControllerBase { [HttpGet] public Task Get() => Task.FromResult( "Test" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersionActionSelectorTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersionActionSelectorTest.cs index a429afed..491e0cc3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersionActionSelectorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersionActionSelectorTest.cs @@ -50,7 +50,9 @@ public async Task select_best_candidate_should_return_correct_versionedX2C_attri public async Task select_best_candidate_should_return_correct_versionedX2C_conventionX2Dbased_controller( string version, Type controllerType ) { // arrange - using ( var server = new WebServer( setupRoutes: routes => routes.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) + void ConfigureOptions( ApiVersioningOptions options ) => options.UseApiBehavior = false; + + using ( var server = new WebServer( ConfigureOptions, r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { var response = await server.Client.GetAsync( $"api/test?api-version={version}" ); @@ -139,7 +141,13 @@ public async Task select_best_candidate_should_return_400_for_attributeX2Dbased_ public async Task select_best_candidate_should_return_400_for_unmatchedX2C_conventionX2Dbased_controller_version() { // arrange - using ( var server = new WebServer( options => options.ReportApiVersions = true, routes => routes.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) + void ConfigureOptions( ApiVersioningOptions options ) + { + options.ReportApiVersions = true; + options.UseApiBehavior = false; + } + + using ( var server = new WebServer( ConfigureOptions, routes => routes.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { // act var response = await server.Client.GetAsync( "http://localhost/api/test?api-version=4.0" ); @@ -155,7 +163,13 @@ public async Task select_best_candidate_should_return_400_for_unmatchedX2C_conve public async Task select_best_candidate_should_return_400_for_conventionX2Dbased_controller_with_bad_version() { // arrange - using ( var server = new WebServer( options => options.ReportApiVersions = true, routes => routes.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) + void ConfigureOptions( ApiVersioningOptions options ) + { + options.ReportApiVersions = true; + options.UseApiBehavior = false; + } + + using ( var server = new WebServer( ConfigureOptions, routes => routes.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { // act var response = await server.Client.GetAsync( "http://localhost/api/test?api-version=2016-06-32" ); @@ -213,7 +227,7 @@ public async Task return_only_path_for_unmatched_action() var response = await server.Client.SendAsync( request ); // assert - var content = await response.Content.ReadAsStringAsync(); + var content = await response.Content.ReadAsStringAsync(); content.Should().Contain( "api/attributed" ); content.Should().NotContain( "?api-version=1.0" ); } @@ -260,7 +274,11 @@ public async Task select_best_candidate_should_assume_1X2E0_for_conventionX2Dbas { // arrange var controllerType = typeof( TestController ).GetTypeInfo(); - Action versioningSetup = o => o.AssumeDefaultVersionWhenUnspecified = true; + Action versioningSetup = options => + { + options.UseApiBehavior = false; + options.AssumeDefaultVersionWhenUnspecified = true; + }; Action routesSetup = r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ); using ( var server = new WebServer( versioningSetup, routesSetup ) ) @@ -280,10 +298,14 @@ public async Task select_best_candidate_should_assume_configured_default_api_ver { // arrange var controllerType = typeof( TestController ).GetTypeInfo(); - Action versioningSetup = o => o.DefaultApiVersion = new ApiVersion( 42, 0 ); - Action routesSetup = r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ); - using ( var server = new WebServer( versioningSetup, routesSetup ) ) + void ConfigureOptions( ApiVersioningOptions options ) + { + options.UseApiBehavior = false; + options.DefaultApiVersion = new ApiVersion( 42, 0 ); + } + + using ( var server = new WebServer( ConfigureOptions, r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { await server.Client.GetAsync( "api/test?api-version=42.0" ); @@ -300,15 +322,16 @@ public async Task select_best_candidate_should_use_api_version_selector_for_conv { // arrange var controllerType = typeof( OrdersController ).GetTypeInfo(); - Action versioningSetup = o => + + void ConfigureOptions( ApiVersioningOptions options ) { - o.AssumeDefaultVersionWhenUnspecified = true; - o.ApiVersionSelector = new LowestImplementedApiVersionSelector( o ); + options.UseApiBehavior = false; + options.AssumeDefaultVersionWhenUnspecified = true; + options.ApiVersionSelector = new LowestImplementedApiVersionSelector( options ); - }; - Action routesSetup = r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ); + } - using ( var server = new WebServer( versioningSetup, routesSetup ) ) + using ( var server = new WebServer( ConfigureOptions, r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { await server.Client.GetAsync( "api/orders" ); @@ -349,13 +372,17 @@ public void select_best_candidate_should_throw_exception_for_ambiguously_version public void select_best_candidate_should_throw_exception_for_ambiguously_versionedX2C_conventionX2Dbased_controller() { // arrange - Action versioningSetup = o => o.AssumeDefaultVersionWhenUnspecified = true; - Action routesSetup = r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ); + void ConfigureOptions( ApiVersioningOptions options ) + { + options.UseApiBehavior = false; + options.AssumeDefaultVersionWhenUnspecified = true; + } + var message = $"Multiple actions matched. The following actions matched route data and had all constraints satisfied:{NewLine}{NewLine}" + $"Microsoft.AspNetCore.Mvc.Versioning.AmbiguousToo2Controller.Get() (Microsoft.AspNetCore.Mvc.Versioning.Tests){NewLine}" + $"Microsoft.AspNetCore.Mvc.Versioning.AmbiguousTooController.Get() (Microsoft.AspNetCore.Mvc.Versioning.Tests)"; - using ( var server = new WebServer( versioningSetup, routesSetup ) ) + using ( var server = new WebServer( ConfigureOptions, r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { Func test = () => server.Client.GetAsync( "api/ambiguoustoo" ); @@ -374,7 +401,7 @@ public void select_best_candidate_should_throw_exception_for_ambiguous_neutral_a $"Microsoft.AspNetCore.Mvc.Versioning.AttributeRoutedAmbiguousNeutralController.Get() (Microsoft.AspNetCore.Mvc.Versioning.Tests){NewLine}" + $"Microsoft.AspNetCore.Mvc.Versioning.AttributeRoutedAmbiguousController.Get() (Microsoft.AspNetCore.Mvc.Versioning.Tests)"; - using ( var server = new WebServer( o => o.AssumeDefaultVersionWhenUnspecified = true ) ) + using ( var server = new WebServer( options => options.AssumeDefaultVersionWhenUnspecified = true ) ) { Func test = () => server.Client.GetAsync( "api/attributed-ambiguous" ); @@ -389,13 +416,17 @@ public void select_best_candidate_should_throw_exception_for_ambiguous_neutral_a public void select_best_candidate_should_throw_exception_for_ambiguous_neutral_and_versionedX2C_conventionX2Dbased_controller() { // arrange - Action versioningSetup = o => o.AssumeDefaultVersionWhenUnspecified = true; - Action routesSetup = r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ); + void ConfigureOptions( ApiVersioningOptions options ) + { + options.UseApiBehavior = false; + options.AssumeDefaultVersionWhenUnspecified = true; + } + var message = $"Multiple actions matched. The following actions matched route data and had all constraints satisfied:{NewLine}{NewLine}" + $"Microsoft.AspNetCore.Mvc.Versioning.AmbiguousNeutralController.Get() (Microsoft.AspNetCore.Mvc.Versioning.Tests){NewLine}" + $"Microsoft.AspNetCore.Mvc.Versioning.AmbiguousController.Get() (Microsoft.AspNetCore.Mvc.Versioning.Tests)"; - using ( var server = new WebServer( versioningSetup, routesSetup ) ) + using ( var server = new WebServer( ConfigureOptions, r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { Func test = () => server.Client.GetAsync( "api/ambiguous" ); @@ -412,13 +443,14 @@ public async Task select_best_candidate_should_assume_current_version_for_attrib // arrange var currentVersion = new ApiVersion( 4, 0 ); var controllerType = typeof( AttributeRoutedTest4Controller ).GetTypeInfo(); - Action setup = o => + + void ConfigureOptions( ApiVersioningOptions options ) { - o.AssumeDefaultVersionWhenUnspecified = true; - o.ApiVersionSelector = new CurrentImplementationApiVersionSelector( o ); - }; + options.AssumeDefaultVersionWhenUnspecified = true; + options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options ); + } - using ( var server = new WebServer( setupApiVersioning: setup ) ) + using ( var server = new WebServer( setupApiVersioning: ConfigureOptions ) ) { await server.Client.GetAsync( "api/attributed" ); @@ -436,14 +468,15 @@ public async Task select_best_candidate_should_assume_current_version_for_conven // arrange var currentVersion = new ApiVersion( 3, 0 ); var controllerType = typeof( TestVersion2Controller ).GetTypeInfo(); - Action versioningSetup = o => + + void ConfigureOptions( ApiVersioningOptions options ) { - o.AssumeDefaultVersionWhenUnspecified = true; - o.ApiVersionSelector = new CurrentImplementationApiVersionSelector( o ); - }; - Action routeSetup = routes => routes.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ); + options.UseApiBehavior = false; + options.AssumeDefaultVersionWhenUnspecified = true; + options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options ); + } - using ( var server = new WebServer( versioningSetup, routeSetup ) ) + using ( var server = new WebServer( ConfigureOptions, r => r.MapRoute( "default", "api/{controller}/{action=Get}/{id?}" ) ) ) { await server.Client.GetAsync( "api/test" ); @@ -470,7 +503,7 @@ public async Task select_best_candidate_should_return_correct_controller_for_ver var deprecated = new[] { new ApiVersion( 4, 0 ) }; var implemented = supported.Union( deprecated ).OrderBy( v => v ).ToArray(); - using ( var server = new WebServer( o => o.ReportApiVersions = true ) ) + using ( var server = new WebServer( options => options.ReportApiVersions = true ) ) { await server.Client.GetAsync( $"api/{versionSegment}/attributed" ); @@ -501,9 +534,10 @@ public async Task select_best_candidate_should_return_correct_controller_for_ver public async Task select_controller_should_return_400_when_requested_api_version_is_ambiguous() { // arrange - Action versioningSetup = o => o.ApiVersionReader = ApiVersionReader.Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader( "api-version" ) ); + void ConfigureOptions( ApiVersioningOptions options ) => + options.ApiVersionReader = ApiVersionReader.Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader( "api-version" ) ); - using ( var server = new WebServer( versioningSetup ) ) + using ( var server = new WebServer( ConfigureOptions ) ) { server.Client.DefaultRequestHeaders.TryAddWithoutValidation( "api-version", "1.0" ); @@ -519,13 +553,15 @@ public async Task select_controller_should_return_400_when_requested_api_version public async Task select_controller_should_resolve_controller_action_using_api_versioning_conventions() { // arrange - Action versioningSetup = o => o.Conventions.Controller() - .HasApiVersion( 1, 0 ) - .HasApiVersion( 2, 0 ) - .AdvertisesApiVersion( 3, 0 ) - .Action( c => c.GetV2() ).MapToApiVersion( 2, 0 ) - .Action( c => c.GetV2( default ) ).MapToApiVersion( 2, 0 ); - using ( var server = new WebServer( versioningSetup ) ) + void ConfigureOptions( ApiVersioningOptions options ) => + options.Conventions.Controller() + .HasApiVersion( 1, 0 ) + .HasApiVersion( 2, 0 ) + .AdvertisesApiVersion( 3, 0 ) + .Action( c => c.GetV2() ).MapToApiVersion( 2, 0 ) + .Action( c => c.GetV2( default ) ).MapToApiVersion( 2, 0 ); + + using ( var server = new WebServer( ConfigureOptions ) ) { var response = await server.Client.GetAsync( $"api/conventions/1?api-version=2.0" ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersioningApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersioningApplicationModelProviderTest.cs index c4e62ac5..3fd93acb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersioningApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/ApiVersioningApplicationModelProviderTest.cs @@ -20,6 +20,7 @@ public void on_providers_executed_should_apply_api_version_model_conventions() var type = typeof( object ); var attributes = new object[] { + new ApiControllerAttribute(), new ApiVersionAttribute( "1.0" ), new ApiVersionAttribute( "2.0" ), new ApiVersionAttribute( "3.0" ), @@ -30,9 +31,10 @@ public void on_providers_executed_should_apply_api_version_model_conventions() { Actions = { new ActionModel( actionMethod, Array.Empty() ) } }; - var options = Options.Create( new ApiVersioningOptions() ); + var options = Options.Create( new ApiVersioningOptions() { UseApiBehavior = true } ); + var filter = new DefaultApiControllerFilter( Array.Empty() ); var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); - var provider = new ApiVersioningApplicationModelProvider( options ); + var provider = new ApiVersioningApplicationModelProvider( options, filter ); context.Result.Controllers.Add( controller ); @@ -63,9 +65,10 @@ public void on_providers_executed_should_apply_api_versionX2Dneutral_model_conve { Actions = { new ActionModel( actionMethod, new object[0] ) } }; - var options = Options.Create( new ApiVersioningOptions() ); + var options = Options.Create( new ApiVersioningOptions() { UseApiBehavior = false } ); + var filter = new DefaultApiControllerFilter( Array.Empty() ); var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); - var provider = new ApiVersioningApplicationModelProvider( options ); + var provider = new ApiVersioningApplicationModelProvider( options, filter ); context.Result.Controllers.Add( controller ); @@ -87,9 +90,10 @@ public void on_providers_executed_should_apply_implicit_api_version_model_conven { Actions = { new ActionModel( actionMethod, new object[0] ) } }; - var options = Options.Create( new ApiVersioningOptions() ); + var options = Options.Create( new ApiVersioningOptions() { UseApiBehavior = false } ); + var filter = new DefaultApiControllerFilter( Array.Empty() ); var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); - var provider = new ApiVersioningApplicationModelProvider( options ); + var provider = new ApiVersioningApplicationModelProvider( options, filter ); context.Result.Controllers.Add( controller ); @@ -120,9 +124,10 @@ public void on_providers_executed_should_not_apply_implicit_api_version_model_co { Actions = { new ActionModel( actionMethod, new object[0] ) } }; - var options = Options.Create( new ApiVersioningOptions() { DefaultApiVersion = v1 } ); + var options = Options.Create( new ApiVersioningOptions() { DefaultApiVersion = v1, UseApiBehavior = false } ); + var filter = new DefaultApiControllerFilter( Array.Empty() ); var context = new ApplicationModelProviderContext( new[] { controller.ControllerType } ); - var provider = new ApiVersioningApplicationModelProvider( options ); + var provider = new ApiVersioningApplicationModelProvider( options, filter ); context.Result.Controllers.Add( controller ); @@ -142,8 +147,8 @@ public void on_providers_executed_should_only_apply_api_version_model_convention var type = typeof( object ); var attributes = new object[] { - new ApiVersionAttribute( "1.0" ), new ApiControllerAttribute(), + new ApiVersionAttribute( "1.0" ), }; var actionMethod = type.GetRuntimeMethod( nameof( object.ToString ), EmptyTypes ); var apiController = new ControllerModel( type.GetTypeInfo(), attributes ) @@ -157,8 +162,9 @@ public void on_providers_executed_should_only_apply_api_version_model_convention var controllers = new[] { apiController, uiController }; var controllerTypes = new[] { apiController.ControllerType, uiController.ControllerType }; var options = Options.Create( new ApiVersioningOptions() { UseApiBehavior = true } ); + var filter = new DefaultApiControllerFilter( new IApiControllerSpecification[] { new ApiBehaviorSpecification() } ); var context = new ApplicationModelProviderContext( controllerTypes ); - var provider = new ApiVersioningApplicationModelProvider( options ); + var provider = new ApiVersioningApplicationModelProvider( options, filter ); context.Result.Controllers.Add( apiController ); context.Result.Controllers.Add( uiController ); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTTest.cs index cf1f1ec2..1627a36e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTTest.cs @@ -117,12 +117,14 @@ public void action_should_call_action_on_controller_builder() controllerBuilder.Verify( cb => cb.Action( method ), Once() ); } - public sealed class UndecoratedController : Controller + [ApiController] + public sealed class UndecoratedController : ControllerBase { public IActionResult Get() => Ok(); } - public sealed class DecoratedController : Controller + [ApiController] + public sealed class DecoratedController : ControllerBase { public IActionResult Get() => Ok(); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTest.cs index 4f3f65b8..9b7bd7f7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ActionApiVersionConventionBuilderTest.cs @@ -117,12 +117,14 @@ public void action_should_call_action_on_controller_builder() controllerBuilder.Verify( cb => cb.Action( method ), Once() ); } - public sealed class UndecoratedController : Controller + [ApiController] + public sealed class UndecoratedController : ControllerBase { public IActionResult Get() => Ok(); } - public sealed class DecoratedController : Controller + [ApiController] + public sealed class DecoratedController : ControllerBase { public IActionResult Get() => Ok(); diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ApiVersionConventionBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ApiVersionConventionBuilderTest.cs index 3d89d604..ebf95d29 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ApiVersionConventionBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ApiVersionConventionBuilderTest.cs @@ -141,7 +141,8 @@ sealed class TestApiVersionConventionBuilder : ApiVersionConventionBuilder internal IDictionary ProtectedControllerConventionBuilders => ControllerConventionBuilders; } - sealed class StubController : Controller + [ApiController] + sealed class StubController : ControllerBase { public IActionResult Get() => Ok(); } @@ -149,7 +150,8 @@ sealed class StubController : Controller namespace v2 { - sealed class UndecoratedController : Controller + [ApiController] + sealed class UndecoratedController : ControllerBase { public IActionResult Get() => Ok(); } diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTTest.cs index c8168e36..7a9b27e8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTTest.cs @@ -101,16 +101,18 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr } ); } - sealed class UndecoratedController : Controller + [ApiController] + sealed class UndecoratedController : ControllerBase { public IActionResult Get() => Ok(); } + [ApiController] [ApiVersion( "2.0" )] [ApiVersion( "0.9", Deprecated = true )] [AdvertiseApiVersions( "3.0" )] [AdvertiseApiVersions( "3.0-Beta", Deprecated = true )] - sealed class DecoratedController : Controller + sealed class DecoratedController : ControllerBase { public IActionResult Get() => Ok(); } diff --git a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTest.cs index cfd9551c..0fb48c86 100644 --- a/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Versioning.Tests/Versioning/Conventions/ControllerApiVersionConventionBuilderTest.cs @@ -101,16 +101,18 @@ public void apply_to_should_assign_model_to_controller_from_conventions_and_attr } ); } - sealed class UndecoratedController : Controller + [ApiController] + sealed class UndecoratedController : ControllerBase { public IActionResult Get() => Ok(); } + [ApiController] [ApiVersion( "2.0" )] [ApiVersion( "0.9", Deprecated = true )] [AdvertiseApiVersions( "3.0" )] [AdvertiseApiVersions( "3.0-Beta", Deprecated = true )] - sealed class DecoratedController : Controller + sealed class DecoratedController : ControllerBase { public IActionResult Get() => Ok(); } diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Builder/ODataValidationSettingsConventionTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Builder/ODataValidationSettingsConventionTest.cs index 47ae641b..e0813f18 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Builder/ODataValidationSettingsConventionTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Builder/ODataValidationSettingsConventionTest.cs @@ -65,17 +65,14 @@ public void apply_to_should_add_filter_parameter_description( string name ) Name = name, Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( Filter, It.IsAny() ), Once() ); @@ -103,17 +100,14 @@ public void apply_to_should_add_expand_parameter_description( string name ) Name = name, Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( Expand, It.IsAny() ), Once() ); @@ -141,17 +135,14 @@ public void apply_to_should_add_select_parameter_description( string name ) Name = name, Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( Select, It.IsAny() ), Once() ); @@ -179,17 +170,14 @@ public void apply_to_should_add_orderby_parameter_description( string name ) Name = name, Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( OrderBy, It.IsAny() ), Once() ); @@ -217,17 +205,14 @@ public void apply_to_should_add_top_parameter_description( string name ) Name = name, Source = Query, Type = typeof( int ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( int ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( Top, It.IsAny() ), Once() ); @@ -255,17 +240,14 @@ public void apply_to_should_add_skip_parameter_description( string name ) Name = name, Source = Query, Type = typeof( int ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( int ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( Skip, It.IsAny() ), Once() ); @@ -293,17 +275,14 @@ public void apply_to_should_add_count_parameter_description( string name ) Name = name, Source = Query, Type = typeof( bool ), + DefaultValue = (object) false, + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = name, ParameterType = typeof( bool ), }, - RouteInfo = new - { - DefaultValue = false, - IsOptional = true, - }, }, options => options.ExcludingMissingMembers() ); settings.MockDescriptionProvider.Verify( p => p.Describe( Count, It.IsAny() ), Once() ); @@ -360,51 +339,42 @@ public void apply_to_should_use_enable_query_attribute( ApiDescription descripti Name = "$select", Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$select", ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, new { Name = "$expand", Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$expand", ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, new { Name = "$filter", Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$filter", ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, }, options => options.ExcludingMissingMembers() ); @@ -442,68 +412,56 @@ public void apply_to_should_use_model_bound_query_attributes() Name = "$select", Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$select", ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, new { Name = "$filter", Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$filter", ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, new { Name = "$orderby", Source = Query, Type = typeof( string ), + DefaultValue = default( object ), + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$orderby", ParameterType = typeof( string ), }, - RouteInfo = new - { - DefaultValue = default( object ), - IsOptional = true, - }, }, new { Name = "$count", Source = Query, Type = typeof( bool ), + DefaultValue = (object) false, + IsRequired = false, ModelMetadata = new { Description = "Test" }, ParameterDescriptor = new { Name = "$count", ParameterType = typeof( bool ), }, - RouteInfo = new - { - DefaultValue = ( object ) false, - IsOptional = true, - }, }, }, options => options.ExcludingMissingMembers() ); diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests.csproj b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests.csproj index 01079d25..382476fc 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests.csproj +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests.csproj @@ -1,8 +1,8 @@  - netcoreapp2.0;net461 - netcoreapp2.0 + netcoreapp2.2;net461 + netcoreapp2.2 Microsoft @@ -11,7 +11,7 @@ - + diff --git a/test/Microsoft.AspNetCore.OData.Versioning.Tests/AspNetCore.Mvc/ApplicationModels/ODataControllerSpecificationTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.Tests/AspNetCore.Mvc/ApplicationModels/ODataControllerSpecificationTest.cs new file mode 100644 index 00000000..ae84b403 --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Versioning.Tests/AspNetCore.Mvc/ApplicationModels/ODataControllerSpecificationTest.cs @@ -0,0 +1,53 @@ +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + using FluentAssertions; + using Microsoft.AspNet.OData; + using Microsoft.AspNet.OData.Routing; + using System; + using System.Reflection; + using Xunit; + + public class ODataControllerSpecificationTest + { + [Theory] + [InlineData( typeof( NormalODataController ), true )] + [InlineData( typeof( CustomODataController ), true )] + [InlineData( typeof( NonODataController ), false )] + public void is_satisified_by_should_return_expected_value( Type controllerType, bool expected ) + { + // arrange + var specification = new ODataControllerSpecification(); + var attributes = controllerType.GetCustomAttributes( inherit: true ); + var controller = new ControllerModel( controllerType.GetTypeInfo(), attributes ); + + // act + var result = specification.IsSatisfiedBy( controller ); + + // assert + result.Should().Be( expected ); + } + + [ODataRoutePrefix( "Normal" )] + sealed class NormalODataController : ODataController + { + [ODataRoute] + public IActionResult Get() => Ok(); + } + + [ODataRouting] + [ODataFormatting] + [ODataRoutePrefix( "Custom" )] + sealed class CustomODataController : ControllerBase + { + [ODataRoute] + public IActionResult Get() => Ok(); + } + + [Route( "api/test" )] + sealed class NonODataController : ControllerBase + { + [HttpGet] + public IActionResult Get() => Ok(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.OData.Versioning.Tests/Extensions.DependencyInjection/IODataBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.Tests/Extensions.DependencyInjection/IODataBuilderExtensionsTest.cs index d909b26c..5badd749 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.Tests/Extensions.DependencyInjection/IODataBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.Tests/Extensions.DependencyInjection/IODataBuilderExtensionsTest.cs @@ -36,6 +36,7 @@ public void enable_api_versioning_should_register_expected_services() services.Any( sd => sd.ServiceType == typeof( IApplicationModelProvider ) && sd.ImplementationType.Name == "ODataApplicationModelProvider" ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IActionDescriptorProvider ) && sd.ImplementationType.Name == "ODataActionDescriptorProvider" ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IActionDescriptorChangeProvider ) && sd.ImplementationInstance.GetType().Name == "ODataActionDescriptorChangeProvider" ).Should().BeTrue(); + services.Any( sd => sd.ServiceType == typeof( IApiControllerSpecification ) && sd.ImplementationType == typeof( ODataControllerSpecification ) ).Should().BeTrue(); } [Fact] @@ -60,6 +61,7 @@ public void enable_api_versioning_should_configure_custom_options() services.Any( sd => sd.ServiceType == typeof( IApplicationModelProvider ) && sd.ImplementationType.Name == "ODataApplicationModelProvider" ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IActionDescriptorProvider ) && sd.ImplementationType.Name == "ODataActionDescriptorProvider" ).Should().BeTrue(); services.Any( sd => sd.ServiceType == typeof( IActionDescriptorChangeProvider ) && sd.ImplementationInstance.GetType().Name == "ODataActionDescriptorChangeProvider" ).Should().BeTrue(); + services.Any( sd => sd.ServiceType == typeof( IApiControllerSpecification ) && sd.ImplementationType == typeof( ODataControllerSpecification ) ).Should().BeTrue(); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.OData.Versioning.Tests/Microsoft.AspNetCore.OData.Versioning.Tests.csproj b/test/Microsoft.AspNetCore.OData.Versioning.Tests/Microsoft.AspNetCore.OData.Versioning.Tests.csproj index 11c1b8aa..e457cf3b 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.Tests/Microsoft.AspNetCore.OData.Versioning.Tests.csproj +++ b/test/Microsoft.AspNetCore.OData.Versioning.Tests/Microsoft.AspNetCore.OData.Versioning.Tests.csproj @@ -1,8 +1,8 @@  - netcoreapp2.0;net461 - netcoreapp2.0 + netcoreapp2.2;net461 + netcoreapp2.2 Microsoft @@ -11,7 +11,7 @@ - + diff --git a/test/directory.build.props b/test/directory.build.props index 99691719..1bd8a35a 100644 --- a/test/directory.build.props +++ b/test/directory.build.props @@ -4,6 +4,7 @@ + true acceptance. $(MSBuildProjectName.StartsWith('Microsoft.AspNetCore')) diff --git a/tools/code-analysis.ruleset b/tools/code-analysis.ruleset index 9476f272..1f1bbcf0 100644 --- a/tools/code-analysis.ruleset +++ b/tools/code-analysis.ruleset @@ -1,97 +1,98 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file