Add header to response if requested version is deprecated - c#

I would like to add a custom header to my service's response if a deprecated version is requested.
I already have URL-based versioning set up using Microsoft.AspNetCore.Mvc.Versioning, and I additionally have an existing custom ActionFilter class that can write custom headers into the response. I can also get the version requested by the client using context.HttpContext.GetRequestedApiVersion() inside my ActionFilter's definition for OnActionExecuted(ActionExecutedContext context).
However, I'm not sure how I can check whether the requested version is a deprecated version or not from my custom ActionFilter. The documentation on deprecating a service version does not answer this question and I can't find the answer among any of the rest of the documentation on github.
My controller class is annotated as follows:
[ApiVersion("2", Deprecated=true)]
[Route("api/v{v:apiVersion}/[action]")]
[ApiController]
public class CustomControllerV2 : ControllerBase { ... }
[ApiVersion("3")]
[Route("api/v{v:apiVersion}/[action]")]
[ApiController]
public class CustomControllerV3 : ControllerBase { ... }
And this is my custom ActionFilter:
public class CustomActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context) {
// not implemented
}
public void OnActionExecuted(ActionExecutedContext context) {
var requestedApiVersion = context.HttpContext.GetRequestedApiVersion();
if (
// Check whether version is deprecated here
) {
context.HttpContext.Response.Headers.Add("warning", "Requested version "+requestedApiVersion.ToString()+" is deprecated.");
}
}
}

From the incoming request you will see what the URL is, and therefore you can determine which controller it will be mapped to. Controllers are classes, and the deprecated ones are marked with the [ApiVersion(Deprecated = true)] attribute. So you can grab the controller, and with reflection you can check its attributes and see if it's deprecated. Documentation here

Related

Is ASP.NET Core MVC filter singleton?

I have an AuthorizationFilter as follows:
public class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter
{
public AuthorizationFilterAttribute()
{
//Constructor of AuthorizationFilter will be called one time
}
public void OnAuthorization(AuthorizationFilterContext context)
{
//OnAuthorization method will be called per http request
}
}
I found the constructor of AuthorizationFilter will just be called one time during the whole ASP.NET Core application lifetime. But its OnAuthorization method will be called per HTTP request.
Does it mean all filters (including IAuthorizationFilter,IActionFilter,IResourceFilter,IExceptionFilter etc) in ASP.NET Core MVC are singletons, which means they will be created just one time during ASP.NET Core application lifetime?
It depends on the IFilterFactory.IsReusable property that is associated with your filter.
When the IFilterProvider (which by default is DefaultFilterProvider) is about to provide the desired instance, it first checks whether the filter implements IFilterFactory as well:
If it does, it uses the filter's own IsReusable property to determine the lifetime of the instance.
If not, it assumes the filter is reusable and IsReusable is set to true.
In the case of your custom AuthorizationFilterAttribute, since you don't implement IFilterProvider, it's indeed considered as reusable and will be created only once.
See Source
Thanks for haim770's answer.
I found an implementation that can create a filter instance per http request rather than ASP.NET Core application lifetime.
Actually, we need to create the filter internally and wrap it into an IFilterFactory like below:
using Microsoft.AspNetCore.Mvc.Filters;
using System;
namespace AspNetCoreFilterDemo.Filters
{
public class AuthorizationFilterWithFactoryAttribute : Attribute, IFilterFactory
{
//Return false, IFilterFactory.CreateInstance method will be called per http request
//Return true, InternalAuthorizationFilter will still be singleton, since IFilterFactory.CreateInstance will be called only one time during the whole ASP.NET Core application lifetime
public bool IsReusable
{
get
{
return false;
}
}
private class InternalAuthorizationFilter : IAuthorizationFilter
{
public InternalAuthorizationFilter()
{
//This InternalAuthorizationFilter constructor will be called per http request rather than ASP.NET Core application lifetime
}
public void OnAuthorization(AuthorizationFilterContext context)
{
//OnAuthorization method will be called per http request
}
}
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
//Create InternalAuthorizationFilter instance per http request
return new InternalAuthorizationFilter();
}
}
}
Please be aware of IFilterFactory.IsReusable property, we need to return false, otherwise the IFilterFactory.CreateInstance method will be called only one time during ASP.NET Core application lifetime, and InternalAuthorizationFilter is still singleton.
Then, we need to specify the AuthorizationFilterWithFactoryAttribute on controller instead of InternalAuthorizationFilter, but AuthorizationFilterWithFactoryAttribute will actually create and operate on an InternalAuthorizationFilter instance per http request:
using Microsoft.AspNetCore.Mvc;
using AspNetCoreFilterDemo.Filters;
namespace AspNetCoreFilterDemo.Controllers
{
public class HomeController : Controller
{
public HomeController()
{
}
[AuthorizationFilterWithFactory]
public IActionResult Index()
{
return View();
}
}
}
AuthorizationFilterWithFactoryAttribute will still be singleton and created one time, but we approached the target to create the filter (InternalAuthorizationFilter) per http request.
You can also take a reference from MSDN.

Authorize with a specific scheme in ASP.NET MVC 4

Is there a way to require a specific Authorization Scheme when using the [Authorize] Attribute on a Controller in asp.net MVC 4?
I expected something like this (which is totally possible in .net core btw)
[Authorize(AuthenticationSchemes = "Bearer")]
public class MyController : Controller { }
As far as I know, there is nothing out of the box that would allow you to write this.
The standard authorize attribute doesn't support this.
But you could write your own attribute and check the claims of the identity coming in.
I used an backport of ASP.NET Core authorization policies to .NET Full framework: https://github.com/DavidParks8/Owin-Authorization to write such rules.
How to check of you come from which token?
Normally you will see a claim similar to "idp": "oidc"
How to get the claims? ((ClaimsPrinciple)User).Claims ( in Controller code)
As suggested by #Chetan Ranpariya in the comments I ended up implementing a derived attribute (from AuthorizeAttribute). According to the documentation, overriding the AuthroizeCore method is the way to do it.
When overridden, provides an entry point for custom authorization checks.
Here is a working example for future reference
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public string AuthSchemes { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (this.AuthSchemes != null)
{
string scheme = httpContext?.User?.Identity?.AuthenticationType;
if (string.IsNullOrWhiteSpace(scheme))
{
return false;
}
return this.AuthSchemes.Split(',').Contains(scheme);
}
return base.AuthorizeCore(httpContext);
}
}
The attribute can be used like this
[MyAuthorize(AuthSchemes = CookieAuthenticationDefaults.AuthenticationType)]
public class MyController : Controller { }

How can versioning be done in ASP.NET Core Web Api

In previous asp.net web api, I implement DefaultHttpControllerSelector to specify how I want the request to locate my controller. I often have different controllers with different names but intended for same processes. The only difference is that one is of higher version than the other.
For example, I could have a controller named BookingV1Controller, which would be meant to handle the version one of the service. I would also have BookingV2Controller, which was designed to handle the version two of the service. A client application would then make a request to the service with this url http://myservice.com/api/v2/booking/someaction?id=12. To handle the request, I would provide a custom implementation of DefaultHttpControllerSelector to select the appropriate version of the controller required based on the requested version.
However, I seems not to have a way to do this in ASP.NET Core. I have searched everywhere to no avail. No documentation that could help either.
I would appreciate if anyone can be of help to me here. Thanks.
UPDATE
I would also like to know what to do if the version is specified in a custom header. E.g X-Version:v1
UPDATE 2
The requirement was that the version of the service should not be exposed in the URL. If no version is present, the service returns with instruction on how to add the version. If a requested controller is not present in the version requested, the system searches through the lower versions. If it finds it in any lower versions, it uses that. The reason for this is to prevent repetition of controllers on all versions. But with ASP.NET Core, this might not be possible.
This is a very old question that I stumbled upon, but there are much better solutions now. There is this package
Microsoft.AspNetCore.Mvc.Versioning
Which has a much more feature rich way of implementing versioning controls. These include being able to use URL query strings, url paths, headers, or custom version readers. Being able to read the version from HTTPContext etc.
In short, you add the following into your ConfigureServices method in startup.cs
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
});
Then you have to decorate your controllers with an ApiVersion.
[ApiVersion("1.0")]
[Route("api/home")]
public class HomeV1Controller : Controller
{
[HttpGet]
public string Get() => "Version 1";
}
[ApiVersion("2.0")]
[Route("api/home")]
public class HomeV2Controller : Controller
{
[HttpGet]
public string Get() => "Version 2";
}
You can also implement it in the path by putting it in the route.
[ApiVersion("1.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV1Controller : Controller
{
[HttpGet]
public string Get() => "Version 1";
}
[ApiVersion("2.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV2Controller : Controller
{
[HttpGet]
public string Get() => "Version 2";
}
When you go down this method of actually having it implemented via the Microsoft package, it also means that you are able to deprecate versions, have version discovery, access the version number from the HttpContext easily etc. None of which you could really do if it's just hardcoded in your route.
For more info (Including using it in a header) :
http://dotnetcoretutorials.com/2017/01/17/api-versioning-asp-net-core/
http://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
https://github.com/Microsoft/aspnet-api-versioning/wiki
I created a package for this purpose exactly after banging my head on this problem for a few days. It doesn't require attributes.
https://github.com/GoAheadTours/NamespaceVersioning
In summary, you can register an IApplicationModelConvention in your startup file that can iterate through controllers and register routes based on the namespaces. I created a v1 folder, and put my controller inside
The class that implements IApplicationModelConvention implements an Apply method with an ApplicationModel parameter that will have access to the Controllers in your app and their existing routes. If I see a controller does not have a route set up in my class I get the version from the namespace and use a pre-defined URL prefix to generate a route for that version.
public void Apply(ApplicationModel application) {
foreach (var controller in application.Controllers) {
var hasRouteAttribute = controller.Selectors.Any(x => x.AttributeRouteModel != null);
if (hasRouteAttribute) {
continue;
}
var nameSpace = controller.ControllerType.Namespace.Split('.');
var version = nameSpace.FirstOrDefault(x => Regex.IsMatch(x, #"[v][\d*]"));
if (string.IsNullOrEmpty(version)) {
continue;
}
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel() {
Template = string.Format(urlTemplate, apiPrefix, version, controller.ControllerName)
};
}
}
I have all the code up on github and a link to the package on nuget as well
Use the routing attributes to control versions.
i.e.
[Route("api/v1/[controller]")]
public class BookingV1Controller : Controller
{
....
}
[Route("api/v2/[controller]")]
public class BookingV2Controller : Controller
{
....
}
For more information relating to migrating from standard Web Api and .NET Core ASP.NET have a look at: MSDN: Migrating from ASP.NET Web Api
For that Add service API versioning to your ASP.NET Core applications
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning();
// remaining other stuff omitted for brevity
}
QUERYSTRING PARAMETER VERSIONING
[ApiVersion( "2.0" )]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world!";
}
So this means to get 2.0 over 1.0 in another Controller with the same route, you'd go here:
/api/helloworld?api-version=2.0
we can have the same controller name with different namespaces
URL PATH SEGMENT VERSIONING
[ApiVersion( "1.0" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class HelloWorldController : Controller {
public string Get() => "Hello world!";
}
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world v2!";
[HttpGet, MapToApiVersion( "3.0" )]
public string GetV3() => "Hello world v3!";
}
Header Versioning
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning(o => o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
}
When you do HeaderApiVersioning you won't be able to just do a GET in your browser, so I'll use Postman to add the header (or I could use Curl, or WGet, or PowerShell, or a Unit Test):
Image
please refer https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx

ASP.NET Web API message handler

I want to implement my custom message handler that will check for a custom header that must be present in every request.
If my custom header is present the request passes through and will hit the controller if the header is not there the request is rejected with custom error message.
No my question is: if I implement my handler that way that means all requests MUST have the header however I need to have a gate where I can call without that header and the request must be ignored by message handler and hit the controller even without the custom header.
Is it possible to achieve this? Or how can I implement my message handler that will ignore certain calls to specific controller or something like this ...?
You can try this.. (untested)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
public abstract class EnforceMyBusinessRulesController : ApiController
{
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
/*
Use any of these to enforce your rules;
http://msdn.microsoft.com/en-us/library/system.web.http.apicontroller%28v=vs.108%29.aspx
Public property Configuration Gets or sets the HttpConfiguration of the current ApiController.
Public property ControllerContext Gets the HttpControllerContext of the current ApiController.
Public property ModelState Gets the model state after the model binding process.
Public property Request Gets or sets the HttpRequestMessage of the current ApiController.
Public property Url Returns an instance of a UrlHelper, which is used to generate URLs to other APIs.
Public property User Returns the current principal associated with this request.
*/
base.Initialize(controllerContext);
bool iDontLikeYou = true; /* Your rules here */
if (iDontLikeYou)
{
throw new HttpResponseException(new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
}
}
}
public class ProductsController : EnforceMyBusinessRulesController
{
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
}
}

Override a Global Filter in MVC for One Method

In my filterConfig, I have registered a global attribute filter, which requires authorization on each of my methods.
However, I have one particular method where I want to apply a different authorization filter attribute. How does one accomplish this, if at all possible?
Note: I do not want to use the [AllowAnonymous] attribute (which works seamlessly and completely ignores my filter), since I want the request to be authorized, just through a different set of authorization logic on the method.
You can alter your filter to allow multiple by setting AllowMultiple = true in the AttributeUsage attribute on your attribute class, and add a check so that if the filter is present multiple times, the globally-applied one doesn't execute. The ActionExecutingContext that gets passed into OnActionExecuting() lets you get the filters applied via filterContext.ActionDescriptor.GetCustomAttributes(), so you can use that here.
Then, alter the constructor so that you can pass in a parameter (probably an enum) that it can use to decide which authorisation method to use - the normal one, or this other one. Give that parameter a default value that makes it select the normal auth method. Then, on that one method that needs a different auth method, you can apply the filter with the other value of the parameter. So it might look like this:
public class CustomAuthAttribute : AuthorizeAttribute
{
public CustomAuthAttribute(AuthMethod method = AuthMethod.StandardAuth)
{
//stuff
}
}
[CustomAuth(AuthMethod.WeirdAuth)]
public ActionResult MethodThatNeedsDifferentAuth()
{
//stuff
}
you can write your own version of the authorize attribute and pass specific parameter to depending on what action would you like your attribute to do for example
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string currentAction { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (currentAction != "notallowed")
{
HandleUnauthorizedRequest(filterContext);
}
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
context.Result = new RedirectResult("/home/login");
}
and then apply it to your class or action
[CustomAuthorize(currentAction = "notallowed")]
public class HomeController : Controller

Categories