У меня есть приложение xamarin.forms, и там ViewModel должен сделать несколько запросов post / put к веб-API. Я думал, что сделаю асинхронно, сначала создам список задач PostAsync / PutAsyn c, а затем жду всех с Task.WhenAll
.
Кажется, все работает нормально, хотя иногда (иногда очень часто) я получаю код ответа 500 внутренняя ошибка сервера. Так как это довольно общее описание ошибки, и я не могу запросить у локального сервера (не могу запустить эмулятор), трудно понять, что не так. Я думаю, что это связано с тем, что я делаю все это асинхронно, потому что у меня есть много других запросов в приложении (каждый ожидал), и я никогда не получал эту ошибку.
Вот код в моей ViewModel:
public async Task<string> Save(int handlingId, int processId)
{
List<Task<string>> listOfTask = new List<Task<string>>();
foreach(ProcessAction pa in CheckedItems.Where(p=>(bool)p.IsMutable))
{
pa.HandlingId = handlingId;
pa.ProcessId = processId;
if ((bool)pa.IsMutable && (bool)pa.IsChecked)
{
if (pa.ProcessActionId == 0)
{
listOfTask.Add(pa.Add());
}
else
{
listOfTask.Add(pa.Edit());
}
}
}
IEnumerable<string> results = await Task.WhenAll<string>(listOfTask);
if(results.Where(r => r != "OK").Any())
{
return string.Join("; ", results.Where(r => r != "OK"));
}
else
{
return "OK";
}
}
Вот ProcessAction
, который наследуется от класса Entity и имеет методы Add () и Edit (), содержащие generi c T:
public virtual async Task<string> Add()
{
using (var client = new HttpClient())
{
string _Result = "OK";
string url = Secrets.ApiAddress + $"Create{typeof(T).Name}?token=" + Secrets.TenantToken + "&UserId=" + RuntimeSettings.UserId;
try
{
HttpClient httpClient = new HttpClient(new NativeMessageHandler() { Timeout = new TimeSpan(0, 0, 20), EnableUntrustedCertificates = true, DisableCaching = true });
var serialized = JsonConvert.SerializeObject(this);
var content = new StringContent(serialized, Encoding.UTF8, "application/json");
HttpResponseMessage httpResponse = await Static.Functions.GetPostRetryAsync(() => httpClient.PostAsync(new Uri(url), content), TimeSpan.FromSeconds(3));
if (!httpResponse.IsSuccessStatusCode)
{
_Result = httpResponse.ReasonPhrase;
}
else
{
var rString = await httpResponse.Content.ReadAsStringAsync();
AddedItem = rString;
}
}
catch (Exception ex)
{
_Result = ex.Message;
Static.Functions.CreateError(ex, "No connection", nameof(this.Add), this.GetType().Name);
}
return _Result;
}
}
public async Task<string> Edit()
{
string url = Secrets.ApiAddress + $"Edit{typeof(T).Name}?token=" + Secrets.TenantToken + $"&id={this.Id}&UserId={RuntimeSettings.CurrentUser.UserId}";
string _Result = "OK";
try
{
HttpClient httpClient = new HttpClient(new NativeMessageHandler() { Timeout = new TimeSpan(0, 0, 20), EnableUntrustedCertificates = true, DisableCaching = true });
var serializedProduct = JsonConvert.SerializeObject(this);
var content = new StringContent(serializedProduct, Encoding.UTF8, "application/json");
HttpResponseMessage result = await Static.Functions.GetPostRetryAsync(() => httpClient.PutAsync(new Uri(url), content), TimeSpan.FromSeconds(3));
if (!result.IsSuccessStatusCode)
{
_Result = result.ReasonPhrase;
}
}
catch (Exception ex)
{
_Result = ex.Message;
Static.Functions.CreateError(ex, "No connection", nameof(this.Edit), this.GetType().Name);
}
return _Result;
}
Сам метод PostAsync / PutAsyn c обернут в метод GetPostRetryAsyn c, который повторяет запрос в случае разрыва сети. Я выкладываю это, хотя я думаю, что нет проблем там:
public static async Task<HttpResponseMessage> GetPostRetryAsync(Func<Task<HttpResponseMessage>> action, TimeSpan sleepPeriod, int tryCount = 3)
{
int attempted = 0;
bool pingable = false;
CancellationTokenSource PingCts;
CancellationTokenSource actionCts;
HttpResponseMessage res = new HttpResponseMessage();
Exception exc;
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true)
{
try
{
attempted++;
if (attempted > 1)
{
DependencyService.Get<IToaster>().LongAlert($"Próba {attempted}");
}
if (RuntimeSettings.IsVpnConnection || Secrets.ApiAddress==Secrets.WebioApiAddress)
{
tryCount = 1;
}
else
{
WiFiInfo w = await DependencyService.Get<IWifiHandler>().ConnectPreferredWifi();
var formattedSsid = $"\"{Static.Secrets.PreferredWifi}\"";
if (w.SSID == formattedSsid)
{
tryCount = 1;
}
}
PingCts = new CancellationTokenSource();
var ping = Task.Run(() => DependencyService.Get<IWifiHandler>().PingHost(),PingCts.Token);
actionCts = new CancellationTokenSource();
var resTask = Task.Run(() => action(),actionCts.Token);
Task firstFinieshed = await Task.WhenAny(ping, resTask);
if (ping.Status == TaskStatus.RanToCompletion)
{
pingable = await ping;
if (!pingable)
{
actionCts.Cancel();
exc = new ServerUnreachableException();
throw exc;
}
else
{
res = await resTask;
}
}
else
{
PingCts.Cancel();
res = await resTask;
}
return res ; // success!
}
catch(Exception ex)
{
--tryCount;
if (tryCount <= 0)
{
throw;
}
await Task.Delay(sleepPeriod);
}
}
}
А вот методы веб-API. Они не в контроллере asyn c:
[HttpPut]
[Route("EditProcessAction")]
[ResponseType(typeof(void))]
public IHttpActionResult EditProcessAction(string token, int id, int UserId, JDE_ProcessActions item)
{
if (token != null && token.Length > 0)
{
var tenants = db.JDE_Tenants.Where(t => t.TenantToken == token.Trim());
if (tenants.Any())
{
var items = db.JDE_ProcessActions.AsNoTracking().Where(u => u.TenantId == tenants.FirstOrDefault().TenantId && u.ProcessActionId == id);
if (items.Any())
{
string descr = "Edycja przypisania czynności do zgłoszenia";
item.LmOn = DateTime.Now;
item.LmBy = UserId;
JDE_Logs Log = new JDE_Logs { UserId = UserId, Description = descr, TenantId = tenants.FirstOrDefault().TenantId, Timestamp = DateTime.Now, OldValue = new JavaScriptSerializer().Serialize(items.FirstOrDefault()), NewValue = new JavaScriptSerializer().Serialize(item) };
db.JDE_Logs.Add(Log);
db.Entry(item).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!JDE_ProcessActionExists(id))
{
return NotFound();
}
else
{
throw;
}
}
}
}
}
return StatusCode(HttpStatusCode.NoContent);
}
[HttpPost]
[Route("CreateProcessAction")]
[ResponseType(typeof(JDE_ProcessActions))]
public IHttpActionResult CreateProcessAction(string token, JDE_ProcessActions item, int UserId)
{
if (token != null && token.Length > 0)
{
var tenants = db.JDE_Tenants.Where(t => t.TenantToken == token.Trim());
if (tenants.Any())
{
item.TenantId = tenants.FirstOrDefault().TenantId;
item.CreatedOn = DateTime.Now;
db.JDE_ProcessActions.Add(item);
db.SaveChanges();
JDE_Logs Log = new JDE_Logs { UserId = UserId, Description = "Utworzenie przypisania czynności do zgłoszenia", TenantId = tenants.FirstOrDefault().TenantId, Timestamp = DateTime.Now, NewValue = new JavaScriptSerializer().Serialize(item) };
db.JDE_Logs.Add(Log);
db.SaveChanges();
return Ok(item);
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
Мне нужен способ отследить эту ошибку. Если я могу включить подробные описания ошибок, отправляемых в ответ, это может быть так. Может быть, вы уже можете сказать, что причина в том, что я запрашиваю сервер (множественные запросы изменяют SQL Server db через EF)? Если это так, как я могу сделать это правильно, не ожидая каждого запроса?