My site is facing unwanted robot call to a specific MVC controller with varying action name. It causes huge exception for hitting into nonsexist action. Our logging system almost flooded with error.
We have decided to adopt following approach as a work around.
Efficiently handle request for non-exist action so it doesn't throw exception
Throttle down robot call
We have written below code to achieve this. Can any one please review the approach.
protected override void HandleUnknownAction(string actionName)
{
if (this.ActionInvoker.InvokeAction(this.ControllerContext,
"ReturnErrorForUnknownAction")) return;
}
public ActionResult ReturnErrorForUnknownAction()
{
return Task.Delay(2000).ContinueWith(t =>
{
return new HttpStatusCodeResult(System.Net.HttpStatusCode.NotFound);
}).Result;
}
Above code is working fine but not sure if "ReturnErrorForUnknownAction" is a blocking call. As per my knowledge Task.Result blocks current thread.
My original intention is to implement Asynchronous delay before sending 404 status.
using Task.Result can cause locks. change the method to something like...
public async Task<ActionResult> ReturnErrorForUnknownAction()
{
return await Task.Delay(2000).ContinueWith(t =>
{
return new HttpStatusCodeResult(System.Net.HttpStatusCode.NotFound);
});
}
Related
I have a controller where the functionality required is to implement a call where two actions are done simultaneously, first we get input and do a call to an external application then respond to the call OK we are working on it and release the caller. When the external application responds, we get the response and save to the db, I am using a task.delay as
Part 1
[HttpPost]
public async Task<IActionResults> ProcessTransaction(Transactions transactions)
{
// do some processing
TransactionResults results = new TransactionResults();
Notify(transactions, results);
return Ok("We are working on it, you will get a notification");
}
The delayed task
private void Notify(Transactions transactions, TransactionResults results)
{
Task.Delay(10000).ContinueWith(t => SendNotification(transactions, results));
}
on the SendNotification I am attempting to save the results
private void SendNotification(Transactions transactions, TransactionResults results)
{
// some processing
_context.Add(results); // this gives an error context has already been disposed
_context.SaveChanges();
}
Is there a better way to do this, or a way to re instantiate the context?
I managed to do a work around to the problem I am facing, I created an endpoint that I would call once the notification results came back and the data would be saved on the callback not at that particular event. Once the controller has respond with an Ok, the controller is disposed and its difficult to re instantiate it. The call back work around works for now, I will update if I find another way to do it.
Im struggling with two ways to return a HttpStatusCode.NotFound (and other Http Status Codes).
1.) My app service will throw a RestException(HttpStatusCode code) which is caught by Middleware and then a proper response is sent back and the response code is set. For example, if I have [Httpget]GetEmployeeById(int Id) and Id is not a real Employee Id, respond with 404.
2.) In my controller, the Application Service call to GetByEmployee() might return null, if so I return NotFound() which does the same as the middleware. But instead, this is done in the controller and the Application Service remains unaware of http status codes.
RestException Pros:
Thinner controllers.
Less complicated code. If I have an action called PutEmployee(Employee employeeToUpdate) I no longer need to write extra logic to fetch first to determine if it exists, pass around tracked entities, etc. The Application Service handles all of it.
Returning Codes in Controller Pros:
Since I am using ActionResult, and I either return an Employee or NotFound() I dont need a [ProducesResponseType(typeof(FluentValidationDataTableFieldError), StatusCodes.Status400BadRequest))] because this can be inferred.
Http/API/Rest stuff stays where it belongs, in the controller.
Essentially, its the difference between the following snippets:
[HttpPost("", Name = EmployeeControllerRoute.PostEmployee)]
[ProducesResponseType(typeof(EmployeeCreateUpdateJsonModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(SpecialErrorViewModel), StatusCodes.Status400BadRequest)]
public async Task<EmployeePageJsonViewModel> Post([FromBody]EmployeeCreateUpdateJsonModel employeeToAdd)
=> await _employeeService.AddAsync(employeeToAdd); // AddAsync throws `RestException(HttpStatusCode code)` if `employeeToAdd.Id` does not pull up a real item.
VS
[HttpGet("{productIds}", Name = ProductControllerRoute.GetProducts)]
public async Task<ActionResult<List<ProductViewModel>>> Get(EntityIdList productIds)
{
var products = await _productService.GetMultipleByIdAsync(productIds);
if(products.Count != productIds.Count)
{
return NotFound();
}
return products;
}
I recently ran into a problem where I was developing an API which talked to two data sources in some methods. The POST for a couple methods modified SQL data through the use of entity framework as well a data source using as an old SDK that was STA COM based. To get the STA COM SDK code to work correctly from within the API methods, I had to create method attributes that identified the methods as needing to be single threaded. I forced single threading by overriding the InvokeActionAsync() method from ApiControllerActionInvoker. If a method was not given an attribute to be single threaded, the overridden invoker simply used the normal base class InvokeActionAsync().
public class SmartHttpActionInvoker: ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext context, CancellationToken cancellationToken)
{
// Determine whether action has attribute UseStaThread
bool useStaThread = context.ActionDescriptor.GetCustomAttributes<UseStaThreadAttribute>().Any();
// If it doesn't, simply return the result of the base method
if (!useStaThread)
{
return base.InvokeActionAsync(context, cancellationToken);
}
// Otherwise, create an single thread and then call the base method
Task<HttpResponseMessage> responseTask = Task.Factory.StartNewSta(() => base.InvokeActionAsync(context, cancellationToken).Result);
return responseTask;
}
}
public static class TaskFactoryExtensions
{
private static readonly TaskScheduler _staScheduler = new StaTaskScheduler(numberOfThreads: 1);
public static Task<TResult> StartNewSta<TResult>(this TaskFactory factory, Func<TResult> action)
{
return factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _staScheduler);
}
}
public static void Register(HttpConfiguration config)
{
....
config.Services.Replace(typeof(IHttpActionInvoker), new SmartHttpActionInvoker());
...
}
This worked well until I noticed something odd. My Logging database was logging duplicate records when a method NOT marked as single threaded was throwing a HttpResponseException back to the client. This behavior did not exist when the same method returned OK().
Debugging, I noticed the code execute in the API method, then reach the throw statement. The next line after the exception was thrown to be shown in debugger was the InvokeActionAsync() code I wrote. Following this the method was run again, in full, hitting the thrown exception, the action invoker, and then returning the result to the client. Effectively, it appears my use of overriding the InvokeActionAsync causes the Action invoker to be called twice somehow... but I am not sure how.
EDIT: Confirmed that the System.Threading.Thread.CurrentThread.ManagedThreadId for the current thread when it is thrown and logged is different for each execution of the API method. So, this reinforces my belief two threads are being created instead of one. Still not sure why.
Anyone have any experience with overriding the InvokeActionAsync behavior that might be able to explain this behavior? Thanks!
I am trying to load a document out of RavenDb via a WebAPI call. When I open an async IDocumentSession and call LoadAsync, I get no exception or result, and the thread exits instantly with no error code.
I was able to bypass all the structure of my API and reproduce the error.
Here is the code that will not work:
public IHttpActionResult GetMyObject(long id)
{
try
{
var session = RavenDbStoreHolderSingleton.Store.OpenAsyncSession();
var myObject= session.LoadAsync<MyObject>("MyObject/1").Result;
return Ok(myObject);
}
catch (Exception e)
{
return InternalServerError(e);
}
}
I simply hard coded the object's Id to 1 for testing, but calling the function for an object that doesn't exist (such as "MyObject/1") has the same result.
However, this code works:
public async Task<IHttpActionResult> GetMyObject(long id)
{
try
{
var session = RavenDbStoreHolderSingleton.Store.OpenAsyncSession();
var myObject= await session.LoadAsync<MyObject>("MyObject/1");
return Ok(myObject);
}
catch (Exception e)
{
return InternalServerError(e);
}
}
Things I tried/fiddled with:
Changing the exceptions that are caught in debugging
Carefully monitoring Raven Studio to see if I could find any problems (I didn't, but I'm not sure I was looking in the right places)
Running the API without the debugger attached to see if the error occurred or if something showed up in Raven Studio (no changes)
So I guess I have stumbled on a "fix", but can someone explain why one of these would fail in such an odd way while the other one would work perfectly fine?
In the real application, the API call did not have the async/await pair, but the code that was making the call was actually using async/await.
Here is the repository class that was failing which caused me to look into this issue:
public async Task<MyObject> Load(string id)
{
return await _session.LoadAsync<MyObject>(id);
}
The first part that is failing is as per design, for ASP.Net async call, you are blocking the Synchronization context, when you call the Result on a Task returned and same Synchronization context is required for call to return the data. Check out the following link by Stephen Cleary, where the same mechanism is explained in detail.
Second part works since that is correct way of using it and it's not getting into the deadlock anymore. First part can only work if you are using the Console application, which doesn't have a synchronization context to block, even other UI like winforms will have a similar issue and need to use the use the Second part of the code
Is AsyncForwardingAppender of the Log4Net.Async package safe to use in an ASP.NET MVC Web Application? I'm worried that it will clog up the available threads.
It comes down to me wanting to make a call to an async method to send the logs to an HTTP API. I could use an async void method like the way this guy did it:
protected override async void Append(log4net.Core.LoggingEvent loggingEvent)
{
var message = new SplunkMessage(loggingEvent.Level, loggingEvent.ExceptionObject);
var success = await _splunkLogger.Report(message);
//do something with the success status, not really relevant
}
He later updated his code:
public void DoAppend(log4net.Core.LoggingEvent loggingEvent)
{
var clientIp = _clientIpRetriever.GetClientIp();
var message = new SplunkMessage(loggingEvent.Level, loggingEvent.ExceptionObject, clientIp);
SendMessageToSplunk(message);
}
private async void SendMessageToSplunk(SplunkMessage message)
{
try
{
var success = await _splunkLogger.Report(message);
//do something unimportant
}
catch(Exception x)
{
LogLog.Error(GetType(), "Error in SplunkAppender.", x);
}
}
But I'm too scared to actually try it because of the dangers involved: "First off, let me point out that "fire and forget" is almost always a mistake in ASP.NET applications".
Any suggestions?
Looking at the source you can see that the AsyncForwardingAppender uses only one thread to dequeue the events. Using it won't kill your MVC app since only one thread will be used.
Regarding "Fire and forget" as a bad pattern in web apps, you have to add a grain of salt to the statement since the answer talks about the danger of letting a functional operation go unsupervised, not a logging one. Logging should be able to fail without your application ceasing working (which is why log4net never says anything when configuration or logging fails)