Improved logging and fixed queued failure handling.

This commit is contained in:
Roger Far 2020-04-18 08:07:06 -06:00
parent dc4992a360
commit 731daddc99
12 changed files with 184 additions and 305 deletions

View file

@ -53,7 +53,7 @@ namespace RdtClient.Data.Data
Status = DownloadStatus.PendingDownload Status = DownloadStatus.PendingDownload
}; };
_dataContext.Downloads.Add(download); await _dataContext.Downloads.AddAsync(download);
await _dataContext.SaveChangesAsync(); await _dataContext.SaveChangesAsync();

View file

@ -92,7 +92,7 @@ namespace RdtClient.Data.Data
AutoDelete = autoDelete AutoDelete = autoDelete
}; };
_dataContext.Torrents.Add(torrent); await _dataContext.Torrents.AddAsync(torrent);
await _dataContext.SaveChangesAsync(); await _dataContext.SaveChangesAsync();

View file

@ -0,0 +1,29 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
namespace RdtClient.Service.Middleware
{
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
await context.Response.WriteAsync(contextFeature.Error.Message);
}
});
});
}
}
}

View file

@ -78,7 +78,7 @@ namespace RdtClient.Service.Services
while (fileStream.Length < response.ContentLength) while (fileStream.Length < response.ContentLength)
{ {
var read = stream.Read(buffer, 0, buffer.Length); var read = await stream.ReadAsync(buffer, 0, buffer.Length);
if (read > 0) if (read > 0)
{ {

View file

@ -231,59 +231,58 @@ namespace RdtClient.Service.Services
var prio = 0; var prio = 0;
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
var result = new TorrentInfo var result = new TorrentInfo();
result.AddedOn = torrent.RdAdded.ToUnixTimeSeconds();
result.AmountLeft = (Int64) (torrent.RdSize * (100.0 - torrent.RdProgress) / 100.0);
result.AutoTmm = false;
result.Availability = 2;
result.Category = torrent.Category ?? "";
result.Completed = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
result.CompletionOn = torrent.RdEnded?.ToUnixTimeSeconds();
result.DlLimit = -1;
result.Dlspeed = torrent.RdSpeed ?? 0;
result.Downloaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
result.DownloadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
result.Eta = 0;
result.FlPiecePrio = false;
result.ForceStart = false;
result.Hash = torrent.Hash;
result.LastActivity = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result.MagnetUri = "";
result.MaxRatio = -1;
result.MaxSeedingTime = -1;
result.Name = torrent.RdName;
result.NumComplete = 10;
result.NumIncomplete = 0;
result.NumLeechs = 100;
result.NumSeeds = 100;
result.Priority = ++prio;
result.Progress = (Int64) (torrent.RdProgress / 100.0);
result.Ratio = 1;
result.RatioLimit = 1;
result.SavePath = savePath;
result.SeedingTimeLimit = 1;
result.SeenComplete = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result.SeqDl = false;
result.Size = torrent.RdSize;
result.SuperSeeding = false;
result.Tags = "";
result.TimeActive = (Int64) (torrent.RdAdded - DateTimeOffset.UtcNow).TotalMinutes;
result.TotalSize = torrent.RdSize;
result.Tracker = "udp://tracker.opentrackr.org:1337";
result.UpLimit = -1;
result.Uploaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
result.UploadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
result.Upspeed = torrent.RdSpeed ?? 0;
result.State = torrent.Status switch
{ {
AddedOn = torrent.RdAdded.ToUnixTimeSeconds(), TorrentStatus.RealDebrid => "downloading",
AmountLeft = (Int64) (torrent.RdSize * (100.0 - torrent.RdProgress) / 100.0), TorrentStatus.WaitingForDownload => "downloading",
AutoTmm = false, TorrentStatus.DownloadQueued => "downloading",
Availability = 2, TorrentStatus.Downloading => "downloading",
Category = torrent.Category ?? "", TorrentStatus.Finished => "pausedUP",
Completed = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0)), TorrentStatus.Error => "error",
CompletionOn = torrent.RdEnded?.ToUnixTimeSeconds(), _ => throw new ArgumentOutOfRangeException()
DlLimit = -1,
Dlspeed = torrent.RdSpeed ?? 0,
Downloaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0)),
DownloadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0)),
Eta = 0,
FlPiecePrio = false,
ForceStart = false,
Hash = torrent.Hash,
LastActivity = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
MagnetUri = "",
MaxRatio = -1,
MaxSeedingTime = -1,
Name = torrent.RdName,
NumComplete = 10,
NumIncomplete = 0,
NumLeechs = 100,
NumSeeds = 100,
Priority = ++prio,
Progress = (Int64) (torrent.RdProgress / 100.0),
Ratio = 1,
RatioLimit = 1,
SavePath = savePath,
SeedingTimeLimit = 1,
SeenComplete = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
SeqDl = false,
Size = torrent.RdSize,
SuperSeeding = false,
Tags = "",
TimeActive = (Int64) (torrent.RdAdded - DateTimeOffset.UtcNow).TotalMinutes,
TotalSize = torrent.RdSize,
Tracker = "udp://tracker.opentrackr.org:1337",
UpLimit = -1,
Uploaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0)),
UploadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0)),
Upspeed = torrent.RdSpeed ?? 0,
State = torrent.Status switch
{
TorrentStatus.RealDebrid => "downloading",
TorrentStatus.WaitingForDownload => "downloading",
TorrentStatus.Downloading => "downloading",
TorrentStatus.Finished => "pausedUP",
TorrentStatus.Error => "error",
_ => throw new ArgumentOutOfRangeException()
}
}; };
results.Add(result); results.Add(result);

View file

@ -72,7 +72,7 @@ namespace RdtClient.Service.Services
{ {
var allTorrents = await torrents.Get(); var allTorrents = await torrents.Get();
allTorrents = allTorrents.Where(m => m.Status == TorrentStatus.WaitingForDownload && m.AutoDownload && m.Downloads.Count == 0) allTorrents = allTorrents.Where(m => (m.Status == TorrentStatus.WaitingForDownload && m.AutoDownload && m.Downloads.Count == 0) || m.Status == TorrentStatus.DownloadQueued)
.ToList(); .ToList();
foreach (var torrent in allTorrents) foreach (var torrent in allTorrents)

View file

@ -33,8 +33,6 @@ namespace RdtClient.Service.Services
{ {
private static RdNetClient _rdtClient; private static RdNetClient _rdtClient;
private static DateTime _rdtLastUpdate = DateTime.UtcNow;
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
private readonly IDownloads _downloads; private readonly IDownloads _downloads;
private readonly ISettings _settings; private readonly ISettings _settings;
@ -71,14 +69,7 @@ namespace RdtClient.Service.Services
public async Task<IList<Torrent>> Get() public async Task<IList<Torrent>> Get()
{ {
var torrents = await _torrentData.Get(); var torrents = await _torrentData.Get();
if (DateTime.UtcNow > _rdtLastUpdate)
{
_rdtLastUpdate = DateTime.UtcNow.AddSeconds(10);
torrents = await Update();
}
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
foreach (var download in torrent.Downloads) foreach (var download in torrent.Downloads)
@ -204,7 +195,7 @@ namespace RdtClient.Service.Services
public async Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoDelete) public async Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoDelete)
{ {
var torrent = MonoTorrent.Torrent.Load(bytes); var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
var rdTorrent = await RdNetClient.AddTorrentFileAsync(bytes); var rdTorrent = await RdNetClient.AddTorrentFileAsync(bytes);
@ -233,10 +224,17 @@ namespace RdtClient.Service.Services
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId); var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId);
var existingDownloads = await _downloads.GetForTorrent(id);
foreach (var link in rdTorrent.Links) foreach (var link in rdTorrent.Links)
{ {
var unrestrictedLink = await RdNetClient.UnrestrictLinkAsync(link); var unrestrictedLink = await RdNetClient.UnrestrictLinkAsync(link);
if (existingDownloads.Any(m => m.Link == unrestrictedLink.Download))
{
continue;
}
await _downloads.Add(torrent.TorrentId, unrestrictedLink.Download); await _downloads.Add(torrent.TorrentId, unrestrictedLink.Download);
} }
} }
@ -244,17 +242,16 @@ namespace RdtClient.Service.Services
public void Reset() public void Reset()
{ {
_rdtClient = null; _rdtClient = null;
_rdtLastUpdate = DateTime.UtcNow;
} }
public async Task<Profile> GetProfile() public async Task<Profile> GetProfile()
{ {
if (_rdtClient == null) if (RdNetClient == null)
{ {
return new Profile(); return new Profile();
} }
var user = await _rdtClient.GetUserAsync(); var user = await RdNetClient.GetUserAsync();
var profile = new Profile var profile = new Profile
{ {

View file

@ -22,48 +22,34 @@ namespace RdtClient.Web.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult> Login([FromBody] AuthControllerLoginRequest request) public async Task<ActionResult> Login([FromBody] AuthControllerLoginRequest request)
{ {
try var user = await _authentication.GetUser();
if (user == null)
{ {
var user = await _authentication.GetUser(); var registerResult = await _authentication.Register(request.UserName, request.Password);
if (user == null) if (!registerResult.Succeeded)
{ {
var registerResult = await _authentication.Register(request.UserName, request.Password); return BadRequest(registerResult.Errors.First().Description);
if (!registerResult.Succeeded)
{
return BadRequest(registerResult.Errors.First().Description);
}
} }
var result = await _authentication.Login(request.UserName, request.Password);
if (!result.Succeeded)
{
return BadRequest("Invalid credentials");
}
return Ok();
} }
catch(Exception ex)
var result = await _authentication.Login(request.UserName, request.Password);
if (!result.Succeeded)
{ {
return BadRequest(ex.Message); return BadRequest("Invalid credentials");
} }
return Ok();
} }
[Route("Logout")] [Route("Logout")]
[HttpPost] [HttpPost]
public async Task<ActionResult> Logout() public async Task<ActionResult> Logout()
{ {
try await _authentication.Logout();
{ return Ok();
await _authentication.Logout();
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
} }
} }

View file

@ -31,21 +31,14 @@ namespace RdtClient.Web.Controllers
[HttpGet] [HttpGet]
public async Task<ActionResult> AuthLogin([FromQuery] QBAuthLoginRequest request) public async Task<ActionResult> AuthLogin([FromQuery] QBAuthLoginRequest request)
{ {
try var result = await _qBittorrent.AuthLogin(request.UserName, request.Password);
{
var result = await _qBittorrent.AuthLogin(request.UserName, request.Password);
if (result) if (result)
{
return Ok("Ok.");
}
return Ok("Fails.");
}
catch (Exception ex)
{ {
return BadRequest(ex.Message); return Ok("Ok.");
} }
return Ok("Fails.");
} }
[AllowAnonymous] [AllowAnonymous]
@ -62,15 +55,8 @@ namespace RdtClient.Web.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult> AuthLogout() public async Task<ActionResult> AuthLogout()
{ {
try await _qBittorrent.AuthLogout();
{ return Ok();
await _qBittorrent.AuthLogout();
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[Route("app/version")] [Route("app/version")]
@ -121,15 +107,8 @@ namespace RdtClient.Web.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult<AppPreferences>> AppPreferences() public async Task<ActionResult<AppPreferences>> AppPreferences()
{ {
try var result = await _qBittorrent.AppPreferences();
{ return Ok(result);
var result = await _qBittorrent.AppPreferences();
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[Authorize] [Authorize]
@ -147,15 +126,8 @@ namespace RdtClient.Web.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult<AppPreferences>> AppDefaultSavePath() public async Task<ActionResult<AppPreferences>> AppDefaultSavePath()
{ {
try var result = await _qBittorrent.AppDefaultSavePath();
{ return Ok(result);
var result = await _qBittorrent.AppDefaultSavePath();
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[Authorize] [Authorize]
@ -164,15 +136,8 @@ namespace RdtClient.Web.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsInfo() public async Task<ActionResult<IList<TorrentInfo>>> TorrentsInfo()
{ {
try var result = await _qBittorrent.TorrentInfo();
{ return Ok(result);
var result = await _qBittorrent.TorrentInfo();
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[Authorize] [Authorize]
@ -180,21 +145,14 @@ namespace RdtClient.Web.Controllers
[HttpGet] [HttpGet]
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsProperties([FromQuery] QBTorrentsHashRequest request) public async Task<ActionResult<IList<TorrentInfo>>> TorrentsProperties([FromQuery] QBTorrentsHashRequest request)
{ {
try var result = await _qBittorrent.TorrentProperties(request.Hash);
{
var result = await _qBittorrent.TorrentProperties(request.Hash);
if (result == null) if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (Exception ex)
{ {
return BadRequest(ex.Message); return NotFound();
} }
return Ok(result);
} }
[Authorize] [Authorize]
@ -237,21 +195,14 @@ namespace RdtClient.Web.Controllers
[HttpGet] [HttpGet]
public async Task<ActionResult> TorrentsDelete([FromQuery] QBTorrentsDeleteRequest request) public async Task<ActionResult> TorrentsDelete([FromQuery] QBTorrentsDeleteRequest request)
{ {
try var hashes = request.Hashes.Split("|");
{
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes) foreach (var hash in hashes)
{
await _qBittorrent.TorrentsDelete(hash, request.DeleteFiles);
}
return Ok();
}
catch (Exception ex)
{ {
return BadRequest(ex.Message); await _qBittorrent.TorrentsDelete(hash, request.DeleteFiles);
} }
return Ok();
} }
[Authorize] [Authorize]
@ -267,21 +218,14 @@ namespace RdtClient.Web.Controllers
[HttpGet] [HttpGet]
public async Task<ActionResult> TorrentsAdd([FromQuery] QBTorrentsAddRequest request) public async Task<ActionResult> TorrentsAdd([FromQuery] QBTorrentsAddRequest request)
{ {
try var urls = request.Urls.Split("\n");
{
var urls = request.Urls.Split("\n");
foreach (var url in urls) foreach (var url in urls)
{
await _qBittorrent.TorrentsAdd(url.Trim(), true, true);
}
return Ok();
}
catch (Exception ex)
{ {
return BadRequest(ex.Message); await _qBittorrent.TorrentsAdd(url.Trim(), true, true);
} }
return Ok();
} }
[Authorize] [Authorize]
@ -315,21 +259,14 @@ namespace RdtClient.Web.Controllers
[HttpGet] [HttpGet]
public async Task<ActionResult> TorrentsSetCategory([FromQuery] QBTorrentsSetCategoryRequest request) public async Task<ActionResult> TorrentsSetCategory([FromQuery] QBTorrentsSetCategoryRequest request)
{ {
try var hashes = request.Hashes.Split("|");
{
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes) foreach (var hash in hashes)
{
await _qBittorrent.TorrentsSetCategory(hash, request.Category);
}
return Ok();
}
catch (Exception ex)
{ {
return BadRequest(ex.Message); await _qBittorrent.TorrentsSetCategory(hash, request.Category);
} }
return Ok();
} }
[Authorize] [Authorize]
@ -346,15 +283,8 @@ namespace RdtClient.Web.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult<IDictionary<String, TorrentCategory>>> TorrentsCategories() public async Task<ActionResult<IDictionary<String, TorrentCategory>>> TorrentsCategories()
{ {
try var categories = await _qBittorrent.TorrentsCategories();
{ return Ok(categories);
var categories = await _qBittorrent.TorrentsCategories();
return Ok(categories);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[Authorize] [Authorize]

View file

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -26,47 +25,26 @@ namespace RdtClient.Web.Controllers
[Route("")] [Route("")]
public async Task<ActionResult<IList<Setting>>> Get() public async Task<ActionResult<IList<Setting>>> Get()
{ {
try var result = await _settings.GetAll();
{ return Ok(result);
var result = await _settings.GetAll();
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[HttpPut] [HttpPut]
[Route("")] [Route("")]
public async Task<ActionResult> Update([FromBody] SettingsControllerUpdateRequest request) public async Task<ActionResult> Update([FromBody] SettingsControllerUpdateRequest request)
{ {
try await _settings.Update(request.Settings);
{ _torrents.Reset();
await _settings.Update(request.Settings);
_torrents.Reset();
return Ok(); return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[HttpGet] [HttpGet]
[Route("Profile")] [Route("Profile")]
public async Task<ActionResult<Profile>> Profile() public async Task<ActionResult<Profile>> Profile()
{ {
try var profile = await _torrents.GetProfile();
{ return Ok(profile);
var profile = await _torrents.GetProfile();
return Ok(profile);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
} }

View file

@ -26,37 +26,23 @@ namespace RdtClient.Web.Controllers
[Route("")] [Route("")]
public async Task<ActionResult<IList<Torrent>>> Get() public async Task<ActionResult<IList<Torrent>>> Get()
{ {
try var result = await _torrents.Get();
{ return Ok(result);
var result = await _torrents.Get();
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[HttpGet] [HttpGet]
[Route("{id}")] [Route("{id}")]
public async Task<ActionResult<Torrent>> Get(Guid id) public async Task<ActionResult<Torrent>> Get(Guid id)
{ {
try var result = await _torrents.GetById(id);
{
var result = await _torrents.GetById(id);
if (result == null) if (result == null)
{
throw new Exception("Torrent not found");
}
return Ok(result);
}
catch (Exception ex)
{ {
return BadRequest(ex.Message); throw new Exception("Torrent not found");
}
return Ok(result);
} }
}
[HttpPost] [HttpPost]
[Route("UploadFile")] [Route("UploadFile")]
@ -64,77 +50,49 @@ namespace RdtClient.Web.Controllers
[ModelBinder(BinderType = typeof(JsonModelBinder))] [ModelBinder(BinderType = typeof(JsonModelBinder))]
TorrentControllerUploadFileRequest formData) TorrentControllerUploadFileRequest formData)
{ {
try if (file == null || file.Length <= 0)
{ {
if (file == null || file.Length <= 0) throw new Exception("Invalid torrent file");
{
throw new Exception("Invalid torrent file");
}
var fileStream = file.OpenReadStream();
await using var memoryStream = new MemoryStream();
fileStream.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
await _torrents.UploadFile(bytes, formData.AutoDownload, formData.AutoDelete);
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
} }
var fileStream = file.OpenReadStream();
await using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream);
var bytes = memoryStream.ToArray();
await _torrents.UploadFile(bytes, formData.AutoDownload, formData.AutoDelete);
return Ok();
} }
[HttpPost] [HttpPost]
[Route("UploadMagnet")] [Route("UploadMagnet")]
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request) public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
{ {
try await _torrents.UploadMagnet(request.MagnetLink, request.AutoDownload, request.AutoDelete);
{
await _torrents.UploadMagnet(request.MagnetLink, request.AutoDownload, request.AutoDelete);
return Ok(); return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[HttpDelete] [HttpDelete]
[Route("{id}")] [Route("{id}")]
public async Task<ActionResult> Delete(Guid id) public async Task<ActionResult> Delete(Guid id)
{ {
try await _torrents.Delete(id);
{
await _torrents.Delete(id);
return Ok(); return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
[HttpGet] [HttpGet]
[Route("Download/{id}")] [Route("Download/{id}")]
public async Task<ActionResult> Download(Guid id) public async Task<ActionResult> Download(Guid id)
{ {
try await _torrents.Download(id);
{
await _torrents.Download(id);
return Ok(); return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
} }
} }

View file

@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
using RdtClient.Data; using RdtClient.Data;
using RdtClient.Data.Data; using RdtClient.Data.Data;
using RdtClient.Data.Models.Internal; using RdtClient.Data.Models.Internal;
using RdtClient.Service.Middleware;
namespace RdtClient.Web namespace RdtClient.Web
{ {
@ -89,9 +90,10 @@ namespace RdtClient.Web
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseCors("Dev"); app.UseCors("Dev");
app.UseDeveloperExceptionPage();
} }
app.ConfigureExceptionHandler();
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
await next.Invoke(); await next.Invoke();