AssumeDefaultVersionWhenUnspecified is not working as expected - asp.net-core

I have been using asp net core versioning component for my WebAPI. Need your help in understanding how AssumeDefaultVersionWhenUnspecified is working. (tried searching for documentation, but couldn't find one)
My startup looks like below
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(2, 0);
o.ApiVersionReader = new UrlSegmentApiVersionReader();
});
When the route attribute is something like below
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/values")]
[ApiController]
public class ValuesV2Controller : ControllerBase
{
...
}
The above route works only when the api version is specified. ie: http://localhost:55401/api/v2/values
If I call like http://localhost:55401/api/values, getting 404 error
My question is this... How AssumeDefaultVersionWhenUnspecified works. Wouldn't it ignore the version in Route? Looks like Route attribute takes precedence over AssumeDefaultVersionWhenUnspecified. If I choose QueryString or Header versioning and when the Route looks like
[ApiVersion("2.0")]
[Route("api/values")]
the default routing reaches the API
Am I missing anything or is my understanding wrong? How shall I achieve default routing to the latest version API using url versioning?

I am also trying to achieve the same functionality. By looking into [https://github.com/Microsoft/aspnet-api-versioning/issues/351#issuecomment-425106940]
I am assuming that we can't achieve default API version AssumeDefaultVersionWhenUnspecified functionality with only a single style of versioning uses a URL segment [Route("api/v{version:apiVersion}/[controller]")]
We have to define two routes separately as follow
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
and to hide the two implementations from swagger you can achieve using this link

Summarizing the the solution from the github issue linked by Athi S, here's what you need to do :
In ConfigureServices inside Startup.cs file :
services.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
// o.DefaultApiVersion = new ApiVersion(1, 0);
});
You can optionally set ApiVersionSelector to a new instance of CurrentImplementationApiVersionSelector. What this does is, it automatically selects the highest api version registered in controllers. E.g. A controller decorated with [ApiVersion("1.2")] takes precedence over [ApiVersion("1.1")].
If you want to specify default api version explicitly, you can do so by leaving ApiVersionSelector to DefaultApiVersionSelector and setting DefaultApiVersion to your required api version.
In your controllers :
Register the required routes by decorating your controllers with the given Route attributes
[Route("api/[controller]")]
Or if you want the api to work both with and without the api version number specified, you can do so by declaring two routes for the controller.
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]

Related

Get Swagger URL programmatically in ASP.NET Core

I'm using ASP.NET Core 5, and Swagger. I know how to use Swagger, and it works properly.
Swagger is served on foo:5001/swagger - but I need to determine that URL programmatically at runtime.
How can I do that?
I already tried:
Getting it by injecting IEnumerable<EndpointDataSource> into some helper/controller class, but that shows me all routes EXCEPT swagger's.
Getting it while setting up endpoint routing and inspecting IEndpointRouteBuilder, but once again it shows me all routes EXCEPT swagger's.
According to sources at https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs you can use an instance of class SwaggerUIOptions:
Register instance in DI container:
var options = new SwaggerUIOptions
{
RoutePrefix = "swagger"
};
options.SwaggerEndpoint("/swagger/v1/swagger.json", "waiting_list v1");
services.AddSingleton(options);
Use configured instance:
app.UseSwaggerUI(app.ApplicationServices.GetRequiredService<SwaggerUIOptions>());
Inject instance to any controller/class:
public WeatherForecastController(ILogger<WeatherForecastController> logger, SwaggerUIOptions swaggerOptions)
{
}
Property RoutePrefix contains swagger prefix (without leading '/')
This idea works only if options object passed to UseSwaggerUI method (available since version 6.0.0). If UseSwaggerUI invoked using callback (like a UseSwaggerUI(a => { a.RoutePrefix = string.Empty; })) it won't work.

ASP.net Core web API Swagger UI version field - Is it possible to set this value in code?

I have a ASP.Net Core Web API with Swagger configured that shows the API End Points.Also API Versioning is enabled. However, the swagger UI is not populating the mandatory field Version when checking the End Point.See image below:
Is it possible to populate this field automatically by code given that the API Action already configures this value i.e. the MaptoApiVersion. In theory this field should be populated automatically??
[MapToApiVersion("2")]
[HttpGet("GetV2")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IEnumerable<TodoDto>> GetV2()
{
var query = new AllTodosQuery(_context);
var todos = await query.ExecuteAsync();
return todos;
}
The issue is at least two-fold. First, the extensions to the API Explorer from API Versioning do provide the version parameter with a default value, but many Swagger/OpenAPI generators (such as Swashbuckle) still do not yet honor it. If you want to enable this behavior, you need a custom IOperationFilter which does something to the effect of:
var parameter = operation.Parameters.First(p => p.Name == "version");
var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == "version");
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
}
You can find a complete end-to-end example in the API Versioning repo in:
SwaggerDefaultValues.cs
Since you're versioning by URL segment, if you want that inlined into the route template without a corresponding parameter, you need only configure the API Explorer extensions to do so like this:
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
This option only applies to the URL segment versioning method.
A complete end-to-end Swashbuckle example with API Versioning can be found inside Startup.cs inside the repo.

Route matching from static Uri + route data extract

I have a .Net Core 2.2 MVC app with routes defined as route attributes on my MVC actions.
I would like to find the matching route (if any) and what the route data are from a given Uri (i.e. not the current HTTP request but a static Uri coming from a database for instance).
I already use the LinkGenerator.GetPathByAction() method to get the "route URL" for a specific action with route data. What I am after would be the opposite: a method that takes a URL/Uri and return the matching route and its route data.
For instance if I have a route registered with the following template:
[Route("/my-action/{id:int}/{name}")]
the URL "/my-action/5/my-test-name" would return the following route data:
id: 5
name: my-test-name
I went through the routing documentation but I haven't found anything.
https://github.com/aspnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/routing.md
The only option that I see would be to somehow call the RouteMiddleware (https://github.com/aspnet/AspNetCore/blob/master/src/Http/Routing/src/RouterMiddleware.cs) with a mock HttpContext which seems overkill if even doable?
I would like to find the matching route (if any) and what the route data are from a given Uri.
If you want to get the route data in the action with the matching route , you could directly use GetRouteData in the current HttpContext object like below :
[Route("/GetRouteData/{id:int}/{name}")]
public void GetRouteData()
{
var routeData = HttpContext.GetRouteData();
var routeCollection = routeData.Values;
var id = routeData?.Values["id"]?.ToString();
var name = routeData?.Values["name"]?.ToString();
}
About finding out if a URL matches an action in ASP.NET MVC Core , you could refer to the following links :
https://joonasw.net/view/find-out-if-url-matches-action
https://weblog.west-wind.com/posts/2019/May/15/Accessing-RouteData-in-an-ASPNET-Core-Controller-Constructor
https://rimdev.io/asp-net-core-routes-middleware/

Passing Controller Action output as SupplyData in UseSpaPrerendering of .Net Core

In .Net Core application, I have below code in Configure method of Startup.cs file.
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.UseSpaPrerendering(options =>
{
options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.js";
options.BootModuleBuilder = env.IsDevelopment() ? new AngularCliBuilder(npmScript: "build:ssr") : null;
options.ExcludeUrls = new[] { "/sockjs-node" };
});
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
UseSpaPrerendering has an option to provide SupplyData callback which lets you pass arbitrary, per-request, JSON-serializable data.
In my case there are pages in my Angular application which makes http requests to fetch data. Since these requests are made to the same application. I see a potential of optimization i.e. if we could just call the corresponding Controller Action method and supply its data to Angular, so that we dont have to make an http request for SSR.
Can anyone please guide how to achieve this.
I know that below is how we pass data using SupplyData
options.SupplyData = (context, data) =>
{
// Creates a new value called isHttpsRequest that's passed to TypeScript code
data["isHttpsRequest"] = context.Request.IsHttps;
};
But how to we pass the results/output of a Controller Actions (which returns json).
I wrote a package to determine the currently activated SPA route from the supplydata delegate.
https://github.com/MusicDemons/AspNetSpaPrerendering
You have to define all your SPA routes using the SpaRouteBuilder and then you can check which route was activated and get the route data (like an id). Based on that you get data from your database through your repositories and add this data to the array. A complete example is included.

Use of AuthConfig, BundleConfig, FilterConfig , RouteConfig and WebApiConfig in App_Start() folder in MVC

Can you please explain in detail, the use of App_Start() folder in MVC4?
I see that this folder is actually not available in previous versions of MVC.
There are 5 files in this folder.
AuthConfig
BundleConfig,
FilterConfig,
RouteConfig,
WebApiConfig.
App_Start is just another folder that groups together ASP.NET MVC configuration, which in previous versions of ASP.NET MVC was done in Global.asax.
ASP.NET MVC introduces more and more configuration elements, and this folder is ideal to place this configuration. For example, MVC 5's new auth. configuration, such as for third-party login providers, are also placed within this folder (in Startup.Auth.cs).
App_Start is not a ASP.NET special folder recognized by ASP.NET/IIS. You can rename the folder if you want. The name is just a convention, like App_GlobalResouces etc.
Update:
Below are some information and reference points for each file. The use of these files are pretty straightforward. I have included few online references that might help your to understand more.
AuthConfig — registers external authentication providers.
See ASP.NET MVC external authentication providers for more information.
BundleConfig — registers your CSS and JS so they can be bundled and minified. See also ASP.NET MVC: Guidance: Bundling and Minification.
WebApiConfig — only applicable if you are using Web API. It can be used to configure Web API-specific routes, any Web API settings and Web API services. See also configuring ASP.NET MVC Web API 2
FilterConfig — registered global filters. These filters are applied to all actions and controllers. See also ASP.NET MVC 3: Global action filters
RouteConfig — you already found information.
App_start folder has been introduced in Mvc4. It contains various configurations files like:
BundleConnfig.cs
FilterConfig.cs
RouteConfig.cs
WebApiConfig.cs
AuthConfig.cs
App_start is not a special folder in MVC nor the class files inside this, these are just normal class files with different application
configurations(filtering, bundling, routing etc.) and all these settings gets registered within Application_Start method of Global.asax.cs file.
BundleConfig.cs:
This is used to create and register bundles for CSS and JS files. for
eg. jQuery,jQueryUI,jQuery validation,Modernizr and Site CSS..
Bundling and minification are two techniques to improve request load time by
reducing the number of requests to the server and reducing the size of
requested assets (such as CSS and JavaScript.)
Microsoft provides assembly Microsoft.Web.Optimization for the same
for eg.
Lets create two Bundles. one for style(css) and another for script(javascript)
You can create bundle for css and javascripts respectively by calling
BundleCollection class Add() method within BundleConfig.cs file.
STEP 1:
Creating Style Bundle
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.min.css",
"~/Content/mystyle.min.css"));
Creating Script Bundle
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery-1.7.1.min.js",
"~/Scripts/jquery.validate.min.js"));
STEP 2:
Above bundles are defined in BundleConfig class as:
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
//Adding StyleBundle to BundleCollection
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.min.css",
"~/Content/mystyle.min.css"));
//Adding ScriptBundle to BundleCollection
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery-1.7.1.min.js",
"~/Scripts/jquery.validate.min.js"));
}
}
STEP 3:
Registering Bundle
All bundles are registered in the Application_Start event of Global.asax:
protected void Application_Start()
{
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Minification is a technique for removing unnecessary characters (like
white space, newline, tab) and comments from the JavaScript and CSS
files to reduce the size, in turn improve load time of a web page. for
eg. jquery-1.7.1.min.js is the minified js file for jquery-1.7.1,
mostly used for production environment, for non-prod you can better use non-
minified js to have better readability.
for eg.
A Jquery function in uncompressed js may look something like:
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
same above function in compressed or minified js will look like:
!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}
FilterConfig.cs:
This is used to create and register global MVC filter:
for eg.
Authentication filters (Executed First)
Authorization filters
Action filters
Result filters
Exception filters (Executed Last)
Note: As mentioned above Filters are executed in an order.
for eg. Authentication Filters introduced with MVC5:
public interface IAuthenticationFilter
{
void OnAuthentication(AuthenticationContext filterContext);
void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext);
}
You can create your CustomAuthentication filter attribute by implementing
IAuthenticationFilter as shown below-
public class CustomAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
//logic goes here
}
Runs after the OnAuthentication method
public void OnAuthenticationChallenge(AuthenticationChallengeContext
filterContext)
{
{
//logic goes here
}
}
Configuring Filters
You can configure your own custom filter into your application at
following three levels:
Global level
By registering your filter into Application_Start event of
Global.asax.cs file:
protected void Application_Start()
{
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
Controller level
By putting your filter on the top of the controller name:
[Authorize(Roles="Admin")]
public class AdminController : Controller
{
// Logic goes here
}
Action level
By putting your filter on the top of the action name:
public class UserController : Controller
{
[Authorize(Users="User1,User2")]
public ActionResult LinkLogin(string provider)
{
// Logic goes here
return View();
}
}
RouteConfig.cs:
This is used to register various route patterns for your Asp.Net MVC
application. Routing plays an important role in an ASP.NET MVC Application
execution flow, it maps request URL to a specific controller action using a
Routing Table. We can define Routing Rules for the engine, so that it can map > incoming URLs to appropriate controller. Routing Engine uses routing rules
that are defined in Global.asax file in order to parse the URL and find out
the path of corresponding controller. We can find the following piece of code > in Application_Start() method of Global.asax file.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
We can find RouteConfig.cs file under App_Start folder. If we follow
this method in RouteConfig class, we will find one default configured
route as follows. Line 3 to 7 is configuring one default route.
public static void RegisterRoutes(RouteCollection routes)
{
1. routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
2.
3. routes.MapRoute(
4. name: “Default”,
5. url: “{controller}/{action}/{id}”,
6. defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }
7. );
}
Line 4 : Name for the route. Line 5 : represent URL : Controller,
action followed by id (if any). Line 6 : default controller will be
Home, default action will be Index and Id is optional.
WebApiConfig.cs:
This is used to register various WEB API routes like as Asp.Net MVC, as well as set any addtional WEB API configurations settings.
AuthConfig.cs:
Used to register external authentication providers
for eg. if you want to enable users to log in with credentials from an external provider, such as Facebook, Twitter, Microsoft, or Google, and then integrate some of the functionality from those providers into your web application.
App_start folder has been introduced in Mvc4. It contains various configurations files like as :
BundleConnfig.cs,
FilterConfig.cs,
RouteConfig.cs,
WebApiConfig.cs
for you application.All those settings are registered within App_Start method of Global.asax.cs file
BundleConfig.cs:
This is used to create and register bundles for CS and JS files.By default various bundles are added in this file including jQuery,jQueryUI,jQuery validation,Modernizer and Site Css..
FilterConfig.cs-
This is used to create and register global MVC filter error filter,action filter etc.By default it contains HandleErrorAttribute filter.
RouteConfig.cs-
This is used to register various route patterns for your Asp.Net MVC application. By default,one route is registered here named as Default Route.
WebApiConfig.cs-
This is used to register various WEB API routes like as Asp.Net MVC,as well as set any additional WEB API configurations settings.
In the previous versions of MVC, we just have RegisterRoutes() method in global.asax file to configure the routing. MVC 4 has built in templates to develop mobile web applications, web apis(restful http services).so to configure the routings for all those template driven development ,we will be using the
AuthConfig.cs(Authorization),BundleConfig.cs(web potimization),,FilterConfig.cs,RouteConfig.cs,WebApiConfig.cs(WEB API) files and they will be mainteained in App_start folder.
Lets have a look at each those config.cs files.
AuthConfig.cs - settings in this file allows you to login to ASP.NET MVC site using third party client credentials like fb account,google account,yahoo account etc..or you can register also.
BundleConfig.cs: settings in this file allows to improve the performance of an applications using bundling...
WebApiConfig: sets the routing settings for WEB API (Note:No action needed in url)
AuthConfi g.cs: Used to confi gure security settings, including sites for OAuth login.
BundleConfi g.cs: Used to register bundles used by the bundling and minifi cation
system. Several bundles are added by default, including jQuery, jQueryUI, jQuery
validation, Modernizr, and default CSS references.
FilterConfi g.cs: Unsurprisingly, this is used to register global MVC fi lters. The only
fi lter registered by default is the HandleErrorAttribute, but this is a great place to put
other fi lter registrations.
RouteConfi g.cs: Holds the granddaddy of the MVC confi g statements, Route confi
guration.
WebApiConfi g.cs: Used to register Web API routes, as well as set any additional Web
API confi guration settings.
All those settings are registered within the App_Start method of Global.asax.cs file
BundleConfig.cs:
This is used to create and register bundles for CS and JS files that we have in the template.
FilterConfig.cs-
This is used to create and register global MVC filter error filter
RouteConfig.cs-
This is used to register various route patterns for your Asp.Net MVC application.
WebApiConfig.cs-
This is used to register various WEB API routes like want login with credentials from an external provider, such as Facebook, Twitter, Microsoft, or Google,