C#: Non-blocking Action methods in ASP.NET MVC - c#

I am creating an ASP.NET MVC 5 Web site, where I have one operation, which requires a lot of time to be executed(importing e-mails from exchange with EWS2.0 Managed API).
The problem is when a client triggers Import action method, the whole site is blocking and no one can open /Home/Index for example or can't make any request to the server, after while exception is throwed(Timeout) if no one interracts with site during the import process - import is successful otherwise it is not guaranteed because of the timeout exception.
How can I manage to start Importing and then redirect users to /home/index and continue importing on server side..?
Here is what I've tried:
public ActionResult Exchange(DateTime? id)
{
string url = ....;
try
{
ExchangeToDatabase etd = new ExchangeToDatabase(username, password, domain, url, id);
etd.ExportFromExchange();
}
catch (InvalidDateException ex)
{
return RedirectToAction("Display", "Error", new { returnUrl = "/", Message = ex.Message });
}
And tried with threads also:
/*System.Threading.Tasks.Task.Factory.StartNew(() =>
{
ExchangeToDatabase etd = new ExchangeToDatabase("cbstest", "ch#rteRsmarter", "vlaeynatie", url, id);
etd.ExportFromExchange();
});
or: doesn't work..
new Thread(() =>
{
ExchangeToDatabase etd = new ExchangeToDatabase("cbstest", "ch#rteRsmarter", "vlaeynatie", url, id);
etd.ExportFromExchange();
}).Start();*/
return Redirect("/");
}

After days of research and trying whatever possible to prevent blocking of the UI, I found an answer: make the user session readonly. An answer from #SamStephens in this post gave me the result that I want.
Here it is:
[SessionState(SessionStateBehavior.ReadOnly)]

I've done this recently for a project and I used Task.Run()
Task.Run(() => SomeMethod(someVariable));

Related

How to wait for all clients to answer to a request?

I have an ASP.NET core WebApi project which also uses SignalR to communicate with clients. This app has an action that is called by a third-party service and requires that all clients currently connected should send some info back.
The SignalR infrastructure is already being used between the server and clients, so I added this particular action:
public async Task<ActionResult> GetClientInfo()
{
await hubContext.Clients.All.GetClientInfo();
//var infos...
return Ok(infos);
}
So basically, this is what should happen:
The third-party service calls the action
The server asks all clients to send their info
The server returns OK with all the client info
Is it possible to somehow wait and make sure that all clients sent their info before returning OK?
I implemented the code as suggested like this:
public async Task<ActionResult> GetClientInfo()
{
try
{
var tasks = new List<Task<IEnumerable<ClientInfo>>>();
foreach (var client in cache.Clients.Values)
{
tasks.Add(Task.Run(async () =>
{
return await hubContext.Clients.Client(client.Id).GetClientInfo();
}));
}
var list = await Task.WhenAll(tasks);
return Ok(list);
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
cache is MemoryCache implementation that is available throughout the whole app and is based on code similar to this SO answer.

Call Web API from MVC Controller Hanging Up

I've tried many different approaches for the past couple of hours, but my method call is hanging up the thread.
Here is my Web API code, which works fine when making AJAX call from the MVC client, but I'm trying to test calling from the server:
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Below is my MVC controller code and model code:
public async Task<ActionResult> TestApi()
{
try
{
var result = await VoipModels.GetValues();
return MVCUtils.JsonContent(result);
}
catch (Exception ex)
{
return MVCUtils.HandleError(ex);
}
}
...
public static async Task<string[]> GetValues()
{
string[] result = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44305/api/");
//THIS IS THE LINE THAT HANGS UP - I'VE TRIED MANY VARIATIONS
var response = await client.GetAsync("values", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsAsync<string[]>();
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
return result;
}
I've used this format successfully when calling a separate, 3rd party API. I've run out of examples to try from my couple of hours of Googling.
What am I doing wrong here?
Check your port number. Based on your code, you have "hard coded" the port "http://localhost:44305/api/" which may likely be incorrect, you should convert that to grab it from the host
configuration instead.
Check your local machine's firewall. Make sure that your local machine's firewall is allowing connections to the port assigned.
Check your protocol. Ensure that you are using http or https appropriately in your request URL.
As a special note, there are very rare cases / exception cases that you would want to have a web API server call itself. Doing so, is rather inefficient design as it will consume resources for no gain (such a generating request and response).

ASP.NET Web Api SendAsync keeps server waiting

I have seen other very similar questions, but I still haven't found a solution, I have the Postal nuget package installed to handle email, and I have a web method that sends email asynchronously (I suppose). Based on other examples, here is my code:
[ActionName("PostEnviarCorreoReserva")]
[HttpPost]
public async Task<IHttpActionResult> PostEnviarCorreoReserva(
[FromBody] ReservaEmail vermodel,
String ver_gkey)
{
var ReservaId = Convert.ToInt32(vermodel.Reserva);
CultureInfo es = new CultureInfo("es-ES");
Thread.CurrentThread.CurrentCulture = es;
DtContex = new DTPPublicDataContext();
var RSPD = DtContex.res_reservas_usuario_det.First(i => i.reserva_gkey == ReservaId);
dynamic emailReserva = new Email(TipoEmail);
emailReserva.To = RSPD.email_reserva;
emailReserva.CodReserva = RSPD.reserva_gkey.ToString();
...
await emailReserva.SendAsync();
return Ok();
}
So I'm still a newbie, but I understand that this code should execute asynchronously, so I can later perform other operations to the Web API, but until it return the Ok response, Web API is busy handling this threat, what exactly Im doing wrong? Sending email takes a really long time
If you don't want to wait, while sendout will be completed, execute code in new thread.
new Thread(async () =>
{
await emailReserva.SendAsync();
}).Start();

Mvc Application Async Methods Are Hanging

We have SOA for our solution. We are using .net framework 4.5.1, asp.net mvc 4.6, sql server, windows server and thinktecture identity server 3 ( for token based webapi calls. )
Solution structure looks like;
Our mvc frontend application talks with our webapi application via a httpClient wrapper. Here is the generic http client wrapper code;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace Cheetah.HttpClientWrapper
{
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
public ResourceServerRestClient(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
public string BaseAddress { get; set; }
public Task<T> GetAsync<T>(string uri, string clientId)
{
return CheckAndInvokeAsync(async token =>
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
});
}
private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName)
{
if (!string.IsNullOrEmpty(resourceServiceClientName))
{
client.DefaultRequestHeaders.Add("CN", resourceServiceClientName);
}
if (string.IsNullOrEmpty(BaseAddress))
{
throw new Exception("BaseAddress is required!");
}
client.BaseAddress = new Uri(BaseAddress);
client.Timeout = new TimeSpan(0, 0, 0, 10);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
}
private async Task<T> CheckAndInvokeAsync<T>(Func<string, Task<T>> method)
{
try
{
string token = await _tokenProvider.IsTokenNullOrExpired();
if (!string.IsNullOrEmpty(token))
{
return await method(token);
}
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
catch (Exception ex)
{
if (ex.Data.Contains("StatusCode") && ((HttpStatusCode)ex.Data["StatusCode"]) == HttpStatusCode.Unauthorized)
{
string token = await _tokenProvider.GetTokenAsync();
if (!string.IsNullOrEmpty(token))
{
return await method(token);
}
}
throw;
}
}
public void ThrowResourceServerException(List<string> messages)
{
string message = messages.Aggregate((p, q) => q + " - " + p);
var exception = new Exception(message);
exception.Data.Add("ServiceOperationException", message);
throw exception;
}
}
}
Also, sometimes this http client wrapper using with NitoAsync manager ( Call async methods as sync. ), and sometimes we are using this generic method directly with await - async task wait like;
var result = await _resourceServerRestClient.GetAsync<ServiceOperation<DailyAgendaModel>>("dailyAgenda/" + id);
So here is our problem:
When we test our mvc application with jmeter (for making some-kind-of load test / 10 threads per 1 sec), after a couple of minutes, mvc application stops working [ exception is task canceled due to timeout ] ( maybe only 1-2 requests timeouts ) on this line: HttpResponseMessage response = await client.GetAsync(uri);. But after that request, all requests will be failed like they are in row. So mvc application is hanging for 2-15 minutes ( randomly ) but in that time I can send new requests from postman to webapi. They are ok, I mean webapi is responding well. After a couple of minutes mvc application turnback to normal.
Note: We have load-balancer for mvc-ui and webapi. Because sometimes we get 120K requests in a minute in a busy day. But it gives same error if there is no load balancer in front of webapi or mvc application. So it's not LB problem.
Note2: We tried to use RestSharp for mvc-ui and webapi communication. We got same error here. When a reuqest is failing, all requests will be failed in a row. It looks like it's a network error but we can't find a proof for it.
Can you see any error on my httpClient wrapper ? or better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?
Update1: We moved projects .net 4.5.1 to 4.6.1. Same deadlock happened again. And than we temporary moved all source codes of the layer: "Business & Repository" as dll level. There is no webapi between business & presentation level now. Dead lock solved. We are still searching why httpClientWrapper codes are not working properly when we called webapi methods from our webapplication controllers.
better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?
A best practice here is for the client (browser in your case) to directly retrieve data from the Web API Controllers and for the MVC controllers to only serve pure HTML views which include layout, styles (css), visual structure, scripts (ie. javascript) etc and not the data.
Image credit: Ode to Code. Incidentally the author on that site also does not recommend your approach although it is listed as an option.
This servers as a good SOC between your views and your data allowing you more easily to make changes to either part.
It allows for the client (browser) to retrieve data asynchronously which creates for a better user experience.
By not doing this and adding a network request step in the call stack you have created an unnecessary expensive step in the flow of data (call from MVC Controller(s) to Web API deployment). The more boundaries are crossed during executing the slower the execution.
The fast solution, as you have already figured out, is to call your business code library directly from your MVC project. This will avoid the additional and unnecessary network step. There is nothing wrong with doing this and many more traditional sites serve both the view (html) and data in the same call. It makes for a more tightly coupled design but it is better than what you had.
The best long term solution is to change the MVC views so they call your Web API deployment directly. This can be done using frameworks like Angular, React, Backbone, etc. If the Web API method calls are limited and are not expected to grow you can also use JQuery or pure javascript BUT I would not try to build a complex application on this, there is a reason why frameworks like Angular have become so popular.
As to the actual underlying technical problem in this case we can't be sure without a memory dump to see what resources are causing the deadlock. It might be something as simple as making sure your MVC Action Methods are also returning async Task<ActionResult> (instead of just ActionResult which, I am guessing, is how you have them structured now) so they can call the HttpClient using an actual async/await pattern. Honestly, because its a bad design, I would not spend any time into trying to get this to work.
I'm not exactly sure whu, but I'll start by refactoring the GetAsync() method
public async Task<T> GetAsync<T>(string uri, string clientId)
{
try
{
string token = await _tokenProvider.IsTokenNullOrExpired();
if (!string.IsNullOrEmpty(token))
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
}
else
{
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
}
catch (Exception ex)
{
throw;
}
}
You should put .ConfigureAwait(false) to your inner awaits statements:
HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
(...)
return await response.Content.ReadAsAsync<T>().ConfigureAwait(false);
(...)
string token = await _tokenProvider.IsTokenNullOrExpired().ConfigureAwait(false);
(...)
return await method(token).ConfigureAwait(false);;
(...)
string token = await _tokenProvider.GetTokenAsync().ConfigureAwait(false);;
(...)
return await method(token).ConfigureAwait(false);
This way you will avoid to capture the synchronization context before the await is done. Otherwise the continuation will be done in this context, which might result in a lock if this one is in use by other threads.
Doing so will allow the continuation to be done whithin the context of the task which is awaited.

MVC4 Async Controller Action not completed

I have an async Action that gets called by jquery ajax request:
View:
$.ajax({
url: "#Url.Action("StartVerification", "Devices")",
global: false,
data: JSON.stringify(machineIds),
contentType: 'application/json',
type: 'POST'
...
Controller:
[HttpPost]
[SessionExpireFilter(Order = 1)]
[CheckPermissions(Order = 2)]
[AjaxMessagesFilter(Order = 3)]
[AsyncTimeout(30000, Order = 4)]
[HandleError(ExceptionType = typeof(TimeoutException), View = "TimeoutError", Order = 5)]
public async Task<JsonResult> StartVerification(ICollection<Machine> machines)
{
Dictionary<int, bool> collection = new Dictionary<int, bool>();
foreach (var machine in machines)
{
Response response = new Response();
try
{
response = await this.deviceRepository.StartVerification(machine);
}
catch (Exception ex)
{
response.Success = false;
}
collection.Add(machine.MachineID, response.Success);
}
return this.Json(collection.ToDictionary(x => x.Key.ToString(), y => y.Value));
}
Web service call:
public async Task<Response> StartVerification(Machine machine, CancellationToken cancelToken = default(CancellationToken))
{
WebService WebServiceForTask = WebServiceFactory.NewInstance;
return await Task.Run(() => WebServiceForTask.StartVerificationForWebSite(machine.SiteID, machine.MachineID));
}
The problem I'm having is that when StartVerification action is executed which calls then queries a web service. The query for that result may take up to several seconds during which time a user may press a refresh button of their browser. What's the best way to handle this scenario and simply abort the call etc.
EDIT:
Maybe I'm asking the question wrong. The issue here is that when I StartVerification and hit refresh page F5 the page will NOT refresh until I get a response from webservice...and it looks like Action is not run async. I want it to work so that if a controller action is already called and waiting on a response from webservice I still should be able to simply browse away from the page that I'm calling the action from.
What's the best way to handle this scenario and simply abort the call etc
You could subscribe to the onbeforeunload event before you start the AJAX request:
window.onbeforeunload = function() {
return 'There\'s an ongoing operation. If you leave this page you might lose some data';
};
and when the AJAX call completes remove the subscription to this event.
Since you have an AsyncTimeout attribute, you should take a CancellationToken that represents that timeout.
There is another CancellationToken that represents a user disconnecting early (Response.ClientDisconnectedToken). However, there is currently a race condition on ClientDisconnectedToken so I do not recommend using it with the current release of ASP.NET (4.5). The best policy right now is to honor the AsyncTimeout and just ignore early client disconnects.
However, if you really wanted to detect client disconnect, you could periodically poll for Response.IsClientConnected.

Categories