Web API 2 return OK response but continue processing in the background - asp.net-mvc-4

I have create an mvc web api 2 webhook for shopify:
public class ShopifyController : ApiController
{
// PUT: api/Afilliate/SaveOrder
[ResponseType(typeof(string))]
public IHttpActionResult WebHook(ShopifyOrder order)
{
// need to return 202 response otherwise webhook is deleted
return Ok(ProcessOrder(order));
}
}
Where ProcessOrder loops through the order and saves the details to our internal database.
However if the process takes too long then the webhook calls the api again as it thinks it has failed. Is there any way to return the ok response first but then do the processing after?
Kind of like when you return a redirect in an mvc controller and have the option of continuing with processing the rest of the action after the redirect.
Please note that I will always need to return the ok response as Shopify in all it's wisdom has decided to delete the webhook if it fails 19 times (and processing too long is counted as a failure)

I have managed to solve my problem by running the processing asynchronously by using Task:
// PUT: api/Afilliate/SaveOrder
public IHttpActionResult WebHook(ShopifyOrder order)
{
// this should process the order asynchronously
var tasks = new[]
{
Task.Run(() => ProcessOrder(order))
};
// without the await here, this should be hit before the order processing is complete
return Ok("ok");
}

There are a few options to accomplish this:
Let a task runner like Hangfire or Quartz run the actual processing, where your web request just kicks off the task.
Use queues, like RabbitMQ, to run the actual process, and the web request just adds a message to the queue... be careful this one is probably the best but can require some significant know-how to setup.
Though maybe not exactly applicable to your specific situation as you are having another process wait for the request to return... but if you did not, you could use Javascript AJAX kick off the process in the background and maybe you can turn retry off on that request... still that keeps the request going in the background so maybe not exactly your cup of tea.

I used Response.CompleteAsync(); like below. I also added a neat middleware and attribute to indicate no post-request processing.
[SkipMiddlewareAfterwards]
[HttpPost]
[Route("/test")]
public async Task Test()
{
/*
let them know you've 202 (Accepted) the request
instead of 200 (Ok), because you don't know that yet.
*/
HttpContext.Response.StatusCode = 202;
await HttpContext.Response.CompleteAsync();
await SomeExpensiveMethod();
//Don't return, because default middleware will kick in. (e.g. error page middleware)
}
public class SkipMiddlewareAfterwards : ActionFilterAttribute
{
//ILB
}
public class SomeMiddleware
{
private readonly RequestDelegate next;
public SomeMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
if (context.Features.Get<IEndpointFeature>().Endpoint.Metadata
.Any(m => m is SkipMiddlewareAfterwards)) return;
//post-request actions here
}
}

Task.Run(() => ImportantThing() is not an appropriate solution, as it exposes you to a number of potential problems, some of which have already been explained above. Imo, the most nefarious of these are probably unhandled exceptions on the worker process that can actually straight up kill your worker process with no trace of the error outside of event logs or something at captured at the OS, if that's even available. Not good.
There are many more appropriate ways to handle this scenarion, like a handoff a service bus or implementing a HostedService.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio

Related

asp.net core favicon.ico goes through custom middleware

In my CustomMiddleware, I have a simple logging and some Authenticated user related code.
It seems like favicon.ico request goes through CustomMiddleware, but request path is "/" same as index page page. can not differentiate.
If I open up a link like this - https://localhost:5001/favicon.ico, it does not hit my debug point.
I need help to understand why first time ONLY request "/", it goes through CustomMiddleware ???
In the CustomMiddleware, first two request path "/" (one is Index), IsAuthenticated is false.
after that, it is always true as it goes through OIDC authentication.
You could read the offcial document:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0
The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other.
Each delegate can perform operations before and after the next delegate. Exception-handling delegates should be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline.
When a delegate doesn't pass a request to the next delegate, it's called short-circuiting the request pipeline. Short-circuiting is often desirable because it avoids unnecessary work. For example, Static File Middleware can act as a terminal middleware by processing a request for a static file and short-circuiting the rest of the pipeline.
You could write two custommiddle and understand how middlewareworks
public class MyCustomMiddleWare
{
private RequestDelegate _next;
public MyCustomMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var pathstring = context.Request.Path.ToString();
var pathlist = pathstring.Split("/").ToList();
if (pathlist[1]=="")
{
await _next.Invoke(context);
}
else
{
await context.Response.WriteAsync("Result");
}
}
}
public class MyAnotherCustomMiddleWare
{
private RequestDelegate _next;
public MyAnotherCustomMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
await _next.Invoke(context);
}
}
in startupclass:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.......
app.UseStaticFiles();
app.UseMiddleware<MyCustomMiddleWare>();
app.UseMiddleware<MyAnotherCustomMiddleWare>();
.......
}
Test Result:
If you open up a link of staticfile and the request hit your custommiddleware behind UseStaticFile Middleware,check if the static file exists.
(Has the BuildAction property of file set as "content"?and check the codes in csproj related which update content files)
The order of the Middlewares is very important and they run from top to bottom They can receive the request, process it, and pass that request to the next Middleware, or not. When the file request like https://localhost:5001/favicon.ico reaches the UseStaticFile Middleware, it processes that request and no longer sends it to its lower Middleware
But when the request is not a file request like https://localhost:5001/, the UseStaticFile Middleware receives the request and passes it to the next middleware.
This is why the request does not reach your custom Middleware. If you want it to be called, you must register your Middleware before the UseStaticFile Middleware like this :
app.UseMiddleware<CustomMiddlware>();
app.UseStaticFiles();
You only need to pay attention to one point: static files like css and ... are cached by the browser after first request. If you request them again, your request will not reach your APP and will be processed by the browser.

Trouble Getting Twilio SMS Response from ASP.NET Core MVC Endpoint

When I setup request bin, I get a response that seems totally normal (see screen shot below). When I use the command
ngrok http 5000
And I send the response to my local http endpoint, ngrok reports 200OK if my POST method in the controller has no parameters. Even if I add one parameter ([FromBody] string content), I get a 400 bad request out of ngrok's console.
I'm pasting below a couple different POST method's I've tried. I've tried inheriting my controller from controllerbase and controller and get the same behavior.
[HttpPost]
public string JsonStringBody([FromBody] TwilioSmsModel twilioSmsModel)
{
return "";
}
POST: api/SmsBody
[HttpPost]
public async Task<IActionResult> PostTwilioSmsModel([FromBody] TwilioSmsModel twilioSmsModel)
public async Task<IActionResult> Post()
{
var twilioSmsModel = new TwilioSmsModel();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.TwilioSmsModels.Add(twilioSmsModel);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTwilioSmsModel", new { id = twilioSmsModel.SmsSid }, twilioSmsModel);
}
If there is a github example of sms notifications working with asp.net core 2.1, that would be a big help.
Twilio developer evangelist here. Most likely the error you're seeing has nothing to do with the way you're building your application, but with the fact that .NET expect some host headers to be passed in order to process your request. And why it works with requestbin.
You haven't specified any error message in your question, so this is guesswork, but try changing your ngrok commend to the following:
ngrok http 5000 -host-header="localhost:5000"
And you should stop seeing the 400 error you're getting and the requests should go through normally.
Hope this helps.

asp.net api needs to globally return a status during shutdown

We have a system, which is not public facing, that has to do a bunch of work when it goes down for an update, etc.
During this time, the REST interface can still receive requests and I'm looking for a global solution where any call would not be processed by the regular controllers but just return a unique code (for example a 503).
I'd like to avoid something like "if (shutdown) ..." in each controller method :)
Is there a provision for something like this in the asp.net core api?
You may try to register IApplicationLifetime.ApplicationStopping action to catch up when shutdown is starting. But note that this is only for a graceful shutdown.
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// Requests may still be in flight. Shutdown will block until this event completes.
/// </summary>
CancellationToken ApplicationStopping { get; }
Look into SO Where can I log an ASP.NET Core app's start/stop/error events? for implementation sample.
Then you may add a simple middleware that stops the pipeline execution and returns 503 status while an app is stopping:
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
bool yourFlag = ... ;// should be true if application topping;
if (yourFlag)
{
context.Response.StatusCode = 503;
// stop further pipeline execution
return;
}
await next.Invoke();
});
...
}

easynetQ delayed respond/request resulting in timeout

I've run into a problem with using the request/respond pattern of EasyNetQ while using it on our server (Windows Server 2008). Not able to reproduce it locally at the moment.
The setup is we have 2 windows services (running as console applications for testing) which are connected through the request/respond pattern in EasyNetQ. This has been working as expected until recently on the server where the request side does not "consume" the responses until after the request timeouts.
I have included 2 links to pastebin which contain the console logging of EasyNetQ which will hopefully make my problem a bit more clear.
RequestSide
RespondSide
Besides that, my request code looks like this:
var request = new foobar();
var response = _bus.Request<foobar, foobar2>(request);
and on the respond side:
var response = new response();
_bus.Respond<foobar, foobar2>(request =>
{
try
{
....
return response;
}
catch (Exception e)
{
....
return response;
}
});
As I've said, the request side sends the request as expected and the respond side consumes/catches it. This works as it should, but when the respond side is done processing and responds (which it does, the messages can be seen in the RabbitMQ management thingy) the request doesn't consume/catch the response until after the request has timed out (default timeout is 10s, tried setting to 60s aswell, makes no difference). This is also evident in the logs linked above as you'll see on the RequestSide, with the 5 or so messages received from the response queue which previously timed out.
I've tried using RespondAsync in case the processing was taking too long and messing something up, didn't help. Tried using both RespondAsync & RequestAsync, just messed everything up even more (I was probably doing something wrong with the request :)).
I might be missing something, but I'm not sure what to try from here.
EDIT: Noticed I messed something up. As well as added more context below:
The IBus used for the request/response is created and injected with Ninject:
class FooModule : NinjectModule
{
public override void Load()
{
Bind<IBus>().ToMethod(ctx => RabbitHutch.CreateBus("host=localhost", x => x.Register<IEasyNetQLogger>(_ => logger))).InSingletonScope();
}
}
And it's all tied together by the service being constructed using Topshelf with Ninject like so:
static void Main(string[] args)
{
HostFactory.Run(x =>
{
x.UseNinject(new FooModule());
x.Service<FooService>(s =>
{
s.ConstructUsingNinject();
s.WhenStarted((service, control) => service.Start(control));
s.WhenStopped((service, control) => service.Stop(control));
});
x.RunAsLocalSystem();
});
}
The Topshelf setup has all been tested pretty thoroughly and it works as intended, and should not really be relevant for the request/respond problem, but I thought I would provide a bit more context.
I had this same issue, my problem was i set the timeout only in the response but not in the request side, after i set the timeoute in both side it worked fine
my connection for eg.
host=hostname;timeout=120;virtualHost=myhost;username=myusername;passw
ord=mypassword

How can we detect when a WCF client has disconnected?

Is there any way of finding out when a WCF client has disconnected. Currently the only approach seems to be to wait until a call on the client from the service eventually times out.
I have tried subscribing to the OperationContext.Current.Channel.Faulted event but unfortunately it is never called; my understanding was that this event should be fired when the client disappears. On the other hand, when things close down gracefully OperationContext.Current.Channel.Closed is called.
In our application we only support a single client connection at a time, hence when somebody closes and re-starts the client app it would be nice if the server could be made aware of the the disconnection, tidy up gracefully and then accept another connection.
Yes, clients will disconnect gracefully most of the time, but this can't be guaranteed. Currently the only option seems to be to poll the client and wait for a CommunicationTimeout, which is hardly ideal.
Any suggestions greatly appreciated.
Theoretically, a service need not have knowledge of client's state. But it can insist on whom to serve for by dictating the authentication needs, concurrency limitation etc.
If you intention is to make sure only one client is served at a time, you can simply opt for Single Concurrency mode.
For example.
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single)]
public class CalculatorService : ICalculatorConcurrency
This will ensure only one client request is served at a time. Following link may help you as well.
http://msdn.microsoft.com/en-us/library/ms731193.aspx
EDIT
If you think an user's action of keeping the channel open does disturb the other user's work, it may not be the usual case.
Because each user's call is considered to be a different session. By default WCF calls are considered to be instantiated per call.
If you would like to persist data between user's calls, you may opt for perSession instancing mode.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorInstance
This would make sure that each user would have an instance of the service which would not inturrupt servicing the other user.
You can set the concurrency mode accordingly i.e Multiple or Reentrant if you wish. Even if the concurrency mode is single, when a response is sent back to the user the service would be ready to serve the next user. It won't wait for the client to close the connection. User's connection would be useful only to keep the session live.
You can use IChannelInitializer and hook up Channel Close and Channel faulted events to detect graceful or abrupt closing of the client. Refer to a very nice post on this by Carlos - http://blogs.msdn.com/b/carlosfigueira/archive/2012/02/14/wcf-extensibility-initializers-instance-context-channel-call-context.aspx
You could use Callback Operations to make a call to the client to see if its still connected.
Take a look at this article on MSDN magazine
if (HttpContext.Current.Response.IsClientConnected == false
{
...
}
it can help you
I've had success using a "disconnection detector" like this:
// Code based on https://blogs.msdn.microsoft.com/carlosfigueira/2012/02/13/wcf-extensibility-initializers-instance-context-channel-call-context/
public class WcfDisconnectionDetector : IEndpointBehavior, IChannelInitializer
{
public event Action Disconnected;
public int ConnectionCount { get; set; } = 0;
public WcfDisconnectionDetector() { }
public WcfDisconnectionDetector(Action onDisconnected) => Disconnected += onDisconnected;
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime cr)
=> cr.ChannelInitializers.Add(this);
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher ed)
=> ed.ChannelDispatcher.ChannelInitializers.Add(this);
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
void IChannelInitializer.Initialize(IClientChannel channel)
{
ConnectionCount++;
Trace.WriteLine($"Client {channel.SessionId} initialized");
channel.Closed += OnDisconnect;
channel.Faulted += OnDisconnect;
}
void OnDisconnect(object sender, EventArgs e)
{
ConnectionCount--;
Disconnected?.Invoke();
}
}
Install it before calling ServiceHost.Open:
var detector = new WcfDisconnectionDetector();
serviceHost.Description.Endpoints.Single().EndpointBehaviors.Add(
new WcfDisconnectionDetector(() => {/*disconnected*/}));