I want to apply TransactionScope for every async controller actions. Rather than doing it in every action I want to do it in a central place so that it would be applicable for all actions. I tried creating a custom IHttpActionInvoker which inherits from ApiControllerActionInvoker
public class ControllerActionTransactionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Task<HttpResponseMessage> result;
result = base.InvokeActionAsync(actionContext, cancellationToken);
scope.Complete();
return result;
}
}
}
then in the Startup class of Web Api replaced default IHttpActionInvoker with the newly created one
config.Services.Replace(typeof(IHttpActionInvoker), new ControllerActionTransactionInvoker());
Now, I can call a controller action and get result but I manually raised exceptions for a series of Db operations and the desired Transaction process does not work. So there is partial work done in DB.
And it does not work at all after hosting the api in Azure api app. It says the controller action was not found.
How to resolve this?
Your scope is completing and being disposed before the controller action is finished executing, as you do not await the response. Try it like this instead:
public class ControllerActionTransactionInvoker : ApiControllerActionInvoker
{
public override async Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
HttpResponseMessage result = await base.InvokeActionAsync(actionContext, cancellationToken);
scope.Complete();
return result;
}
}
}
While this might well work, you might also want to consider doing this higher up the Web API stack - perhaps in a handler - as you might miss other transactional activity in e.g. handlers, filters, etc. Depends on what you want in and out of scope.
Related
I have a bunch of functions that are meant to have similar logic that i could wrap using attributes in c#, but the wrapper i'm currently trying to create should return a response value that is a custom type that simply represents a respond from server
Response class:
public class Response
{
public int StatusCode { get; set; }
public string Message { get; set; }
}
Method i want to wrap to (simple logic for demonstration purposes):
void SentRequest()
{
Request(parameter: "simple text");
}
And lets say i want to wrap this method with attribute class, which can create a response value:
class StorageServiceFilter : IAsyncActionFilter
{
private Response _response;
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await next();
_response.StatusCode = 200;
_response.Message = "Upload successful";
}
}
Now, is it possible to return my _response? I already know about ActionExecutingContext.Result property, but unfortunately it returns only IActionResult type, which is not suitable for my case.
PS:
Forgot to mention that IAsyncActionFilter has only the implementation for Task OnActionExecutionAsync that makes impossible to use Task<T> as a return type
You can get the context returned by await next() and set the Result property. Something like this:
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var resultContext = await next();
resultContext.Result = new ObjectResult(
new Response() { StatusCode = 200, Message = "Upload successful" });
}
Please note that it will cause bypassing the remaining action filters:
From ASP.NET Core In Action book:
Setting the Result property on context short-circuits the pipeline.
But, due to the position of the action filter stage, only the action
method execution and later action filters are bypassed; all the other
stages of the pipeline run as though the action had executed as
normal.
I have an web api on dotnet core 3.1 and I want to set different timeout specific controller action.I try to create an actionfilter something like below
public class TimeOutAttribute : ActionFilterAttribute
{
private readonly int _timeout;
public TimeOutAttribute(int timeout)
{
_timeout = timeout;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(_timeout));
await Task.Run(async () => await next(), cts.Token);
}
catch (TaskCanceledException)
{
var request = context.HttpContext.Request;
var message = $"Action exceeded the set timeout limit {_timeout} milisecond for {request.PathBase}{request.Path}";
throw new ActionTimeOutException(message);
}
}
}
and I use it on controller method
[TimeOut(100)]
public async Task<IActionResult> Get()
{
}
Although Get method takes more than 100 ms I can not get exception.Could you see any problem on code or If you have a another options for controller timeout Im ready to try it
Could you see any problem on code
Yes; passing a cancellation token to Task.Run isn't going to work. The token for that method only cancels the scheduling of the task to the thread pool, not the delegate itself.
The only way to cancel your delegate code is to have your delegate take a CancellationToken and observe that (usually by passing it to other methods). I have a blog post series on the subject of cancellation.
If you have a another options for controller timeout Im ready to try it
So, that's a harder problem.
There is built-in support for CancellationToken in ASP.NET Core; you can add a CancellationToken argument to any controller action method. However, this token doesn't have anything to do with timeouts; it cancels if the user request is aborted (e.g., the user closes their browser).
One approach is to add a CancellationToken parameter to your action method and have your filter modify the model binding results, replacing the provided CancellationToken. Something like this should work:
public sealed class TimeoutAttribute : ActionFilterAttribute
{
private readonly TimeSpan _timeout;
public TimeoutAttribute(int timeoutMilliseconds) => _timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Find the CancellationToken argument passed to the action
var cancellationTokenArgument = context.ActionArguments.FirstOrDefault(x => x.Value is CancellationToken);
if (cancellationTokenArgument.Key == null || cancellationTokenArgument.Value == null)
throw new InvalidOperationException("TimeoutAttribute must be used on an action with a CancellationToken");
// Create a new CancellationToken that will be cancelled if *either* the user disconnects *or* a timeout
using var cts = CancellationTokenSource.CreateLinkedTokenSource((CancellationToken)cancellationTokenArgument.Value);
cts.CancelAfter(_timeout);
// Replace the action's CancellationToken argument with our own
context.ActionArguments[cancellationTokenArgument.Key] = cts.Token;
await next();
}
}
This will work - to an extent. Your host (i.e., IIS) likely has its own timeout, and this timeout is completely separate from that one.
I am trying to roll out authorization in an entire environment and would like to feature flag this for quick rollback if it goes south. Once we know all services are aligned with OAuth this feature will be removed and become permanent. I have chosen the IAutofacAuthorizationFilter to inject an object to determine the feature flag state which a typical attribute doesn't offer.
I'd like to enable the default behavior as if I had decorated the controller with [Authorize] if the feature is true otherwise let the methods execute without it, but I'm having trouble enabling the default behavior from inside a IAutofacAuthorizationFilter where there is no base class to override like await base.OnAuthorizationAsync(actionContext, cancellationToken); inside a AuthorizeAttribute.
What I have working so far:
public class FeatureBasedAuthorizeAttribute : IAutofacAuthorizationFilter
{
private readonly IFeatureManager _featureManager;
public FeatureBasedAuthorizeAttribute(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_featureManager.IsEnabled<EnableAppAuthorization>())
{
// Return result of default ASP.Net authorization here... How?
}
// Return without Authorization (current state)
await Task.FromResult(0);
}
}
// Wire up in startup.cs
builder.Register(c => new FeatureBasedAuthorizeAttribute(c.Resolve<IFeatureManager>()))
.AsWebApiAuthorizationFilterForAllControllers()
.InstancePerRequest();
Ultimately time away from the screen solved it for me. My solution was this:
public async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_featureManager.IsEnabled<EnableAppAuthorization>())
{
// Return result of default ASP.Net authorization
var authorizeAttribute = new AuthorizeAttribute();
await authorizeAttribute.OnAuthorizationAsync(actionContext, cancellationToken);
}
// Return without Authorization (current state)
await Task.FromResult(0);
}
In an ASP.NET Web API project, I have an action filter which checks for model state errors and returns the Bad Request status code if there are any. It looks like this:
public class ValidationFilter : IActionFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext context,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
if(!actionContext.ModelState.IsValid)
{
return new Task<HttpResponseMessage>(() =>
actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
return continuation();
}
}
Now, for some reason, any request with a model state error never returns. It just hangs there. If I debug, I get to the next filter in the pipeline, which starts with
var result = await continuation();
If I "Step Over" that line, the debugger sort of drops out to "waiting" mode, but no more code seems to be run.
I assume all of this is because I've somehow misunderstood how all these things interact, but despite hours of googling and reading, I still can't figure out how to make this work properly. Any help - both for understanding and bugfixing - is deeply appreciated.
You never start your task. You need to call Start when you use the Task constructor. Instead of calling the constructor and then Start a better option would be to use Task.Run:
if(!actionContext.ModelState.IsValid)
{
return Task.Run(() =>
actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
In your case there's nothing really asynchronous about your operation, so you can simply use Task.FromResult to create a task with the result you get synchronously:
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext context,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
if(!actionContext.ModelState.IsValid)
{
return Task.FromResult(actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
actionContext.ModelState);
}
return continuation();
}
For logging purposes, I am trying to monitor the requests being made through a WebAPI. I have created and I am looking for a way to get back the body sent through in a request after the request has been fulfilled and responded to. I am trying to do this through using a ActionFilter but thus far have failed in reading the body from the request.
Can anybody give some advice how I may access this information?
For context I am trying to do this within this code:
public class LoggingActionFilter : ActionFilterAttribute
{
public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
var test = actionExecutedContext.Request.Content.ReadAsStringAsync().Result;
return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
}
}
I have tried reading back the Content on the actionExecutedContext variable in order to get back the body but have found this to return just blank so far.
you're just dealing with request body so don't need to use OnActionExecutedAsync method, you can just override OnActionExecuting like this,
public override void OnActionExecuting(HttpActionContext actionContext)
{
var test = (actionContext.Request.Content as ObjectContent).Value.ToString();
// your logging code here
}
Another option available in WebAPI is DelegatingHandler. if you want to log just request body then override SendAsync method,
public class ApiLogHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestBody = request.Content.ReadAsStringAsync().Result;
// your logging code here
return base.SendAsync(request, cancellationToken);
}
}
If you decided to choose DelegatingHandler then you need to register that handler to Global message handlers.