Rest APi How to route based api versioning - c#

I have a method called GetMessage, it is called like so:
localhost/GetMessage?id={{NewMessage}}
i have now decided to start following standard practises and ommit the "get" keyword from the route and make it like so:
localhost/message?id={{NewMessage}}
however, i do not want to break any existing clients I already have looking at the first route and force them to update. the underlying function will not change, it will just be the route.
my first solution to this problem was to do this:
Although THIS WORKS i realised, i have like 35 functions just like this one, i do not wanna duplicate all those functions.
Is it not possible to add 2 [httpget] attributes to a function each with a different apiversion that would only be accessible if the header specifically has that version.
so something like this:
this way client will be able to upgrade in their own time when they need.
is this possible or would i have to go with my first solution?
Extra info:
my configure services method:

Generally , the method with different version has the same route, So we configure like this:
services.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});
And user add request header(x-api-version=xxxx) to access method with the specified version
But in your code, Different versions of methods have different routes, So We can't use the method mentioned above.
I use Action filters to achieve a similar effect, I write a simple demo here.
Action filters
public class Action1filter : Attribute,IActionFilter
{
private readonly string _header;
private readonly string _value;
public void OnActionExecuted(ActionExecutedContext context){}
public void OnActionExecuting(ActionExecutingContext context )
{
if ((context.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == _header).Value.FirstOrDefault() != _value) || context.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == _header).Value.FirstOrDefault() == null)
{
context.Result= new BadRequestObjectResult("");
}
}
}
your method
HttpGet("message"), ApiVersion("2.0"), Action1filter("version", "2.0")]
public IActionResult GetMessage2([FromQuery] string Id)
{
return GetMessage(Id);
}
[HttpGet("GetMessage"),ApiVersion("1.0",Deprecated =true), Action1filter("version", "1.0")]
public IActionResult GetMessage([FromQuery]string Id)
{
return Ok(Id);
}
I add the ActionFilter in your action, So only the request with header version=2.0 can access the method by localhost/message?id={{NewMessage}}
But you can't use this method in your second solution, Because one [Action1filter()] will work on both paths.
In my opinion, I prefer to add [Route("xxx/{version:apiVersion}")] to access method with different versions in this situation

Related

How to control response wrapping in ABP on a per-route basis?

I'm using ABP (aspnetboilerplate) 7.0 thru ASP.NET Zero 11 and I'm trying to get OData working. I've followed the article over at ABP and I've taken inspiration from their sample.
The response of OData routes (/odata and /odata/$metadata) should not be wrapped. ABP does provide an attribute to prevent wrapping called DontWrapResult. However, since these routes are not on controllers that I have direct access to, I can't set the attribute.
The same question has been asked here: Disable Wrapping of Controller Results
However, they wanted to disable wrapping altogether, which is not what I want to do.
The answer to that question is to use a ResultFilter to set the attribute's value. I have, however, found that setting the value thru the attribute also sets the value that comes from the injected IAbpAspNetCoreConfiguration.
For example:
public class ODataResultFilter : IResultFilter, ITransientDependency
{
private readonly IAbpAspNetCoreConfiguration _configuration;
public ODataResultFilter(IAbpAspNetCoreConfiguration configuration)
{
_configuration = configuration;
}
public void OnResultExecuting(ResultExecutingContext context)
{
var methodInfo = context.ActionDescriptor.GetMethodInfo();
var wrapResultAttribute =
GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
methodInfo,
_configuration.DefaultWrapResultAttribute,
false
);
if (context.HttpContext.Request.Path.Value.Equals("/odata/$metadata") ||
context.HttpContext.Request.Path.Value.Equals("/odata"))
{
wrapResultAttribute.WrapOnSuccess = false;
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
// No action
}
private TAttribute GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true)
where TAttribute : class
{
return memberInfo.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
?? memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
?? defaultValue;
}
}
As soon as I hit wrapResultAttribute.WrapOnSuccess = false;, _configuration.DefaultWrapResultAttribute becomes false and every other request ends up not being wrapped. My front-end expects wrapped responses and thus the front-end stops working as soon as I hit an OData route once.
How can I manipulate this attribute and prevent wrapping for OData routes but leave the default + attribute-configured wrapping behavior for the other routes?
GetSingleAttributeOfMemberOrDeclaringTypeOrDefault method should work fine, except right now, since _configuration.DefaultWrapResultAttribute gets modified, a controller that doesn't explicitly set a WrapResult attribute will get the default, overridden by the last value set.
Implement IWrapResultFilter, which was introduced in ABP v6.5:
using Abp.Web.Results.Filters;
using System;
namespace AbpODataDemo.Web.Host.ResultWrapping
{
public class ODataWrapResultFilter : IWrapResultFilter
{
public bool HasFilterForWrapOnError(string url, out bool wrapOnError)
{
wrapOnError = false;
return new Uri(url).AbsolutePath.StartsWith("/odata", StringComparison.InvariantCultureIgnoreCase);
}
public bool HasFilterForWrapOnSuccess(string url, out bool wrapOnSuccess)
{
wrapOnSuccess = false;
return new Uri(url).AbsolutePath.StartsWith("/odata", StringComparison.InvariantCultureIgnoreCase);
}
}
}
Add it to WrapResultFilters in the PreInitialize method of your module:
Configuration.Modules.AbpWebCommon().WrapResultFilters.Add(new ODataWrapResultFilter());
Reference:
https://aspnetboilerplate.com/Pages/Documents/AspNet-Core#wrapresultfilters
https://github.com/aspnetboilerplate/sample-odata/pull/20
OData with ABP v7.1 and later
Abp.AspNetCore.OData implements AbpODataDontWrapResultFilter to disable result wrapping for paths that start with "/odata".
Add it to WrapResultFilters in the PreInitialize method of your module:
Configuration.Modules.AbpWebCommon().WrapResultFilters.Add(new AbpODataDontWrapResultFilter());
The rationale of letting library users configure this explicitly is to highlight this interaction between the default result wrapping and OData use case.
References:
https://aspnetboilerplate.com/Pages/Documents/OData-AspNetCore-Integration#result-wrapping
https://github.com/aspnetboilerplate/aspnetboilerplate/pull/6375

.Net Core 3.1 IActionFilter get required param in URL

As the title says i am trying to fetch the required param in my route:
https://localhost:44386/api/Users/c6b44b17-1b05-4ab6-a8c0-6968b4ea1ced
This is my Filter which i put a breakpoint to see what is the value of each.
Unfortunately the first two doesn't have anything even if i specify the key (e.g. Query["_id"]).
The third one fetches the array of route which is correct but i don't want to use the last index value as it feels to be just a hack.
I've seen the docs and i can't find a simpler way below without touching the endpoint routing config and explicitly defining this specific route (since i might end up with tons of routes which might require this same scenario).
Any help will be truly appreciated.
public class ProfileIsCreatorFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var query = context.HttpContext.Request.Query;
var queryString = context.HttpContext.Request.QueryString;
var route = context.HttpContext.Request.RouteValues;
}
}
[HttpGet("{_id}"), Authorize, ProfileIsCreatorFilter]
public ActionResult<User> GetUser(Guid _id)
{
return Ok();
}
I have simplified everything to avoid confusion.
You can access all route specific values in the filter-context. The context contains a RouteData dictionary:
public override void OnActionExecuting(ActionExecutingContext context) {
string myvalue = context.RouteData.Values["mykey"];
}
You can find more infos to ActionFilters in the docs.

Possible to run all handlers for a policy in-line instead of with an attribute?

On most of my APIs, I simply do authorization like this:
[Authorize(Policy = "Foo")]
public MyApi()
I get this policy from a NuGet though and can't modify it.
For some of my APIs, I don't always want to have this policy. This needs to get figured out at runtime based on some config. I'd like some way to run this in-line, and ensure all the handlers that are setup run.
After a lot of searching i've found that I create an IAuthorizationService, and use that to call AuthorizeAsync. This seems like it's what I want, but the issue i'm running into now is that all the handlers rely on an AuthorizationFilterContext as the resource on the context. This seems to happen automatically when the Authorization is done through the attribute, but not through the call to AuthorizeAsync. It needs to be passed in manually in this case. My code right now looks like this:
public MyApi()
{
var allowed = await _authorizationService.AuthorizeAsync(User, null, "Foo").ConfigureAwait(false);
}
This seems to go through all my handlers correctly, but they don't work due to missing the AuthorizationFilterContext.
1) Is this the correct approach to begin with, or is there some other way to do this in-line? I'm guessing there's probably some way to create my own policy that wraps this one and I can check the config there, but if there's a simple in-line approach i'd prefer that.
2) If this way is valid, is there a good way to get the AuthorizationFilterContext? I've tried creating it manually, but i'm afraid this isn't actually correct without passing in more data from the context, but I can't find any good examples/doc:
new AuthorizationFilterContext(new ActionContext(HttpContext, HttpContext.GetRouteData(), new ActionDescriptor()), new IFilterMetadata[] { });
There will be no AuthorizationFilterContext when you are outside of the authorization pipeline. You should therefore not handle the authentication inline with IAuthorizationService.
This seems to go through all my handlers correctly, but they don't work due to missing the AuthorizationFilterContext.
Sounds like you have the control over the authentication handlers. Have you tried short-circuit authentication inside the handler if it is not required?
The handler can get services via the DI so you can put your required runtime config via IOptions or IHttpContextAccessor and what so ever.
Can't you create you own Authorize attribute which would inherit current one and resolve policy internally? Or even better try using IAuthorizationPolicyProvider
class MyPolicyProvider : IAuthorizationPolicyProvider
{
private DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }
public MyPolicyProvider()
{
BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.Equals("Foo"))
{
bool myConditionToAvoidPolicy = true;
if (myConditionToAvoidPolicy)
{
return Task.FromResult<AuthorizationPolicy>(null);
}
}
return BackupPolicyProvider.GetPolicyAsync(policyName);
}
}
This is not tested, but you can find more about it here.
Your check condition looks like happening at later point which I dont think it is a good idea. Your api method is being vulnerable and still open as your check is done at later point. But by using attribute you can capture it at earlier level and still can apply the custom logic. At the end of the day, all it decides is either "yes, have an access", or "no, no access for you!!" Below is not tested but should get you going:
public class CustomAuthorize : AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
var currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity;
if (!currentIdentity.IsAuthenticated) {
// no access
}
bool myCondition = "money" == "happiness";
if(myCondition){
// do your magic here...
}
else{
// another magic...
}
}
}

Override global action filter in controller/action in ASP.NET Core MVC 1.0.1 (ASP.NET Core 1.1)

I am building a ASP.NET Core MVC application and am trying to create a global action filter that logs how much time is spent executing an action (it should only log if spent time is above some threshold). I have succesfully done this but now I want to be able to say that a single action or a single controller should have a different threshold. When I try this, my action filter is applied twice(which is not what I want) but with the correct two different thresholds.
I have tried quite a few things and searched around. In an MVC 3 and an MVC 4 project I have successfully done this using RegisterGlobalFilters() in Global.asax and it automatically overrides the global one when I used the attribute on a controller/action. I have also tried the approach listed in this post, without luck:
Override global authorize filter in ASP.NET Core MVC 1.0
My code for my ActionFilterAttribute:
public class PerformanceLoggingAttribute : ActionFilterAttribute
{
public int ExpectedMax = -1; // Log everything unless this is explicitly set
private Stopwatch sw;
public override void OnActionExecuting(ActionExecutingContext context)
{
sw = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext context)
{
sw.Stop();
if (sw.ElapsedMilliseconds >= ExpectedMax)
{
// Log here
}
}
//public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
//{
// // If there is another performance filter, do nothing
// if (context.Filters.Any(item => item is PerformanceLoggingAttribute && item != this))
// {
// return Task.FromResult(0);
// }
// return base.OnActionExecutionAsync(context, next);
//}
}
I am applying this global filter in my Startup.cs:
services.AddMvc(options =>
{
if (_env.IsProduction()) options.Filters.Add(new RequireHttpsAttribute());
//options.Filters.Add(new PerformanceLoggingFilter() { ExpectedMax = 1 }); // Add Performance Logging filter
options.Filters.Add(new PerformanceLoggingAttribute() { ExpectedMax = 1 }); // Add Performance Logging filter
});
And in my controller I am applying the attribute:
//[TypeFilter(typeof(PerformanceLoggingFilter))]
[PerformanceLogging(ExpectedMax = 2)]
public IActionResult Index()
{
var vm = _performanceBuilder.BuildPerformanceViewModel();
return View(vm);
}
As you can tell from the code snippets above I have tried the OnActionExecutionAsync approach and I have also tried a IActionFilter instead and using [TypeFilter(typeof(PerformanceLoggingFilter))] on actions, but no luck.
Can anyone help me out?
May suggest you a bit different implementation of what you try to achieve by using one action filter and additional custom attribute:
create a new simple attribute (let's name it ExpectedMaxAttribute), that just holds the ExpectedMax value. Apply this attribute to controller's actions with different values.
keep your PerformanceLogging action filter as global, but modify implementation. On OnActionExecuted method check if controller's action has ExpectedMaxAttribute. If yes, then read ExpectedMax value from attribute, otherwise use the default value from the action filter.
Also, I recommend you to rename action filter accordingly to convention naming something like PerformanceLoggingActionFilter.
I got it working thanks to #Set's answer above in combination with this answer:
https://stackoverflow.com/a/36932793/5762645
I ended up with a global action that is applied to all actions and then having a simple ExpectedMaxAttribute that I put on actions where the threshold should be different. In the OnActionExecuted of my global action filter, I then check if the action in question has the ExpectedMaxAttribute attached to it and then read the ExpectedMax from that. Below is my attribute:
public class PerformanceLoggingExpectedMaxAttribute : ActionFilterAttribute
{
public int ExpectedMax = -1;
}
And the OnActionExecuted part that I added to my ActionFilter:
public override void OnActionExecuted(ActionExecutedContext context)
{
sw.Stop();
foreach (var filterDescriptor in context.ActionDescriptor.FilterDescriptors)
{
if (filterDescriptor.Filter is PerformanceLoggingExpectedMaxAttribute)
{
var expectedMaxAttribute = filterDescriptor.Filter as PerformanceLoggingExpectedMaxAttribute;
if (expectedMaxAttribute != null) ExpectedMax = expectedMaxAttribute.ExpectedMax;
break;
}
}
if (sw.ElapsedMilliseconds >= ExpectedMax)
{
_logger.LogInformation("Test log from PerformanceLoggingActionFilter");
}
}

How can I test for the presence of an Action Filter with constructor arguments?

I am trying to test that my base controller is decorated with a certain action filter. Because this filter's constructor looks to web.config, my first try at testing fails because the test project doesn't have a valid config file. Moving on, I used a TestConfigProvider that I inject into the filter constructor, but the following test fails because the config provider isn't passed to the constructor. How else can I test if this filter is applied?
[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
var att = typeof(BaseController).GetCustomAttribute<MaxLengthFilter>();
Assert.IsNotNull(att);
}
Well, you have taken a good first step by recognizing that Web.config is just another dependency and wrapping it into a ConfigProvider to inject is an excellent solution.
But, you are getting tripped up on one of the design problems of MVC - namely, that to be DI-friendly, attributes should only provide meta-data, but never actually define behavior. This isn't an issue with your approach to testing, it is an issue with the approach to the design of the filter.
As pointed out in the post, you can get around this issue by splitting your action filter attribute into 2 parts.
An attribute that contains no behavior to flag your controllers and action methods with.
A DI-friendly class that implements IActionFilter and contains the desired behavior.
The approach is to use the IActionFilter to test for the presence of the attribute, and then execute the desired behavior. The action filter can be supplied with all dependencies and then injected when the application is composed.
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
GlobalFilters.Filters.Add(filter);
NOTE: If you need any of the filter's dependencies to have a lifetime shorter than singleton, you will need to use a GlobalFilterProvider as in this answer.
The implementation of MaxLengthActionFilter would look something like this:
public class MaxLengthActionFilter : IActionFilter
{
public readonly IConfigProvider configProvider;
public MaxLengthActionFilter(IConfigProvider configProvider)
{
if (configProvider == null)
throw new ArgumentNullException("configProvider");
this.configProvider = configProvider;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here, and use the configProvider as needed
}
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here, and use the configProvider as needed
}
}
public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
{
MaxLengthAttribute result = null;
// Check if the attribute exists on the controller
result = (MaxLengthAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
if (result != null)
{
return result;
}
// NOTE: You might need some additional logic to determine
// which attribute applies (or both apply)
// Check if the attribute exists on the action method
result = (MaxLengthAttribute)actionDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
return result;
}
}
And, your attribute which should not contain any behavior should look something like this:
// This attribute should contain no behavior. No behavior, nothing needs to be injected.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
this.MaxLength = maxLength;
}
public int MaxLength { get; private set; }
}
With a more loosely coupled design, testing for the existence of the attribute is much more straightforward.
[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>();
Assert.IsNotNull(att);
}
Perhaps you can add the valid config file to your test project via "add file as link"
Recently I here more and more question regarding config "problems". They all have a common base - you have several projects, servers, services that need to use the same config. My advise to you - stop using Web.config.
Place all your configuration into the database!
Add a table (or maybe several tables) with all your configuration keys an values and read them when the application starts (global.asax).
This way you don't need to worry about coping your config to every project or injecting it to different constructors.

Categories