Balance two different HttpClients without impacting throughput - asp.net-core

I have the following controller
[Route("some-value")]
[ApiController]
public class SomeValueController : ControllerBase
{
private readonly ClientProvider _clientProvider;
public SomeValueController(ClientProvider clientProvider)
{
_clientProvider = clientProvider;
}
[HttpGet]
public async Task<ActionResult> Index([FromQuery] string from, [FromQuery] string to)
{
var values = await _clientProvider.GetClient().GetValues(from, to);
return Ok(values);
}
}
I want this controller to use one HttpClient for 50% of requests and a different HttpClient for the other 50% of requests. That's why i have introduced the ClientProvider. It is currently implemented as follows:
public class ClientProvider
{
private readonly MainClient _mainClient;
private readonly BackupClient _backupClient;
private readonly ILogger<ClientProvider> _logger;
private bool _flag = false;
private object _lock = new Object();
public ClientProvider(BackupClient backupClient,
MainClient mainClient, ILogger<ClientProvider> logger)
{
_backupClient = backupClient;
_mainClient = mainClient;
_logger = logger;
}
public IExchangeClient GetClient()
{
lock (_lock)
{
var client = _flag ? _backupClient as IExchangeClient : _mainClient;
_flag = !_flag;
return client;
}
}
}
It is obviously very naive because it will block for some time when the appropriate client is being selected. The lock is required for situations in which requests arrive almost simultanously. If the lock wasn't there i would have the same client selected for all those simultanous requests because they would all execute the same line at the same time in separate threads. Can this problem be solved in a more elegant and efficient way?

Related

ASP.NET Core 6 - using memorycache in view component

I am writing an application using ASP.NET Core 6 MVC.
I have a controller that instantiates an IMemoryCache and stores some values into the cache.
public HomeController(ILogger<HomeController> logger, IMemoryCache memoryCache)
{
_logger = logger;
_cache = memoryCache;
}
In some other part of the application I assign a value to the cache.
In the _Layout.cshtml I am using a view component
#await Component.InvokeAsync("Menu")
I need to access the cache from the view component.
I set IMemoryCache in the constructor, and the try to get the data.
public class MenuViewComponent : ViewComponent
{
private const string ClientInfoCacheKey = "ClientInfo";
private IMemoryCache _cache;
public async Task<IViewComponentResult> InvokeAsync(IMemoryCache memoryCache)
{
_cache = memoryCache;
var fromCache = _cache.Get<string>(ClientInfoCacheKey);
// ......
}
}
But the problem I am facing is that _cache is always null...
Is there a way to access cache from a view component?
Thanks
You should inject IMemoryCache to your MenuViewComponent from constructor. Your code should be:
public class MenuViewComponent : ViewComponent
{
private const string ClientInfoCacheKey = "ClientInfo";
private readonly IMemoryCache _cache;
public MenuViewComponent(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var fromCache = _cache.Get<string>(ClientInfoCacheKey);
// ......
}
}

MassTransit Consumer not receiving scope context from NLog MDLC

I would like to set flowId to MDLC before consuming an item by any of my Rabbit consumers.
At first, I called SeMetaData after Consume:
public class SomeConsumer: IConsumer<ItemDto>
{
private IServiceLogger _logger;
public SomeConsumer(IServiceLogger logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext<ItemDto> context)
{
//Set flowId
_logger.SetMetaData(GetFlowId(context.Message));
...
}
}
public class ServiceLogger : IServiceLogger
{
public static void SetMetaData(string id)
{
MappedDiagnosticsLogicalContext.Set("flowId", id);
}
}
Since I have multiple consumers, I looked for a more generic solution so I implemented (MassTransit) IConsumeObserver, and tried SetMetaData on PreConsume:
public class ConsumeObserver : IConsumeObserver
{
private IServiceLogger _logger;
public ConsumeObserver(IServiceLogger logger)
{
_logger = logger;
}
public async Task PreConsume<T>(ConsumeContext<T> context) where T : class
{
//Set flowId
_logger.SetMetaData(GetFlowId(context.Message));
await context.ConsumeCompleted;
}
...
}
But flowId is set only inside PreConsume. Once the item was consumed by a consumer, flowId was empty in the logger.
How can I set MDLC before consuming an item?

ASP.net Core - Handle Client Info Server-Side After Signalr Disconnect

I'm basically trying to do some database work if the user has been disconnected from signalR for more than 10 minutes. I put a timer in the Hub's OnDisconnectedAsync(). The issue I'm running into is ObjectDisposedException on things that I need to access the database. I get the same problems when I bypass the timer and try to make the calls directly in OnDisconnectedAsync(). I guess everything has been destroyed by the time I make it into OnDisconnectedAsync().
I've been trying everything I can find for days, but nothing seems to work. What is the correct way to handle this kind of problem?
public class RangeHub : Hub
{
private readonly IHubContext<RangeHub> _hubContext;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IDashboardService _service;
private readonly ApplicationDbContext _context;
private readonly ISubscriptionService _subscription;
private readonly HOptions _hOptions;
//private Timer disconnectTimer = new Timer();
private CustomTimer disconnectTimer;
private string UserId
{
get
{
//return User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier).Value;
return Context.UserIdentifier;
}
}
public RangeHub(IHubContext<RangeHub> hubContext, UserManager<ApplicationUser> userManager, IDashboardService service, ApplicationDbContext context,
ISubscriptionService subscription, IOptionsMonitor<HOptions> hOptions)
{
_hubContext = hubContext;
_context = context;
_userManager = userManager;
_service = service;
_subscription = subscription;
_hOptions = hOptions.CurrentValue;
disconnectTimer = new CustomTimer
{
Interval = 30000,
AutoReset = false
};
disconnectTimer.Elapsed += DisconnectTimedEvent;
//disconnectTimer.Elapsed += (sender, e) => DisconnectTimedEvent(sender, e, UserId);
//disconnectTimer.Interval = 30000;//120000;
//disconnectTimer.AutoReset = false;
}
public override Task OnConnectedAsync()
{
Log.Information("OnConnectedAsync: CONNECT TO CLIENT");
System.Diagnostics.Debug.WriteLine("OnConnectedAsync: CONNECT TO CLIENT");
//If timer was running, stop timer/reset
if(disconnectTimer.Enabled)
{
//reset timer
disconnectTimer.Stop();
}
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
//TODO: This is firing off when the dashboard is left, need to fix
Log.Information("OnDisconnectedAsync: DISCONNECTED FROM CLIENT");
System.Diagnostics.Debug.WriteLine("OnDisconnectedAsync: DISCONNECTED FROM CLIENT");
_service.StopActiveEnvironment(UserId);
//_hubContext.Clients.
//TODO: place logic here to stop environment and stop user's time
disconnectTimer.userId = UserId;
disconnectTimer.dashService = _service;
disconnectTimer.dBContext = _context;
//disconnectTimer.Start();
//*is this for a single user? will this effect all users at once?
return base.OnDisconnectedAsync(exception);
}
public void DisconnectTimedEvent(object sender, ElapsedEventArgs e)//, IDashboardService dashService)
{
//Shut down the range
Log.Information("DEBUG Timer");
Log.Information("UserId: " + ((CustomTimer)sender).userId);
Log.Information("Shutting down environment due to signalR disconnection timer.");
//Van - 9-28-2019: Not using await here. This should be the last thing done before the instance of this class is destroyed.
// Using await takes too long and the Hub object is already destroyed before its finished.
//_service.StopActiveEnvironment(((CustomTimer)sender).userId);
//Task.Run(async () => await _service.StopActiveEnvironment("051735c4-fa6d-4d90-9f76-b540aaa110bc"));
//Task.Run(async () => await _service.StopActiveEnvironment("051735c4-fa6d-4d90-9f76-b540aaa110bc")).WaitAndUnwrapException();
try
{
var func = ((CustomTimer)sender).dashService.StopActiveEnvironment(((CustomTimer)sender).userId);
func.WaitAndUnwrapException();
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
}
}
class CustomTimer : Timer
{
public string userId;
public IDashboardService dashService;
public ApplicationDbContext dBContext;
}

A second operation started on this context before a previous operation completed

I have a project with asp.net core and entity framework core, for performance reasons I use MemoryCache. ForumQueryManager class is for querying forum data. This class for data uses the CacheManager Get method and passes cache key and timeout cache time and a method for when the cache is empty for retrieving data from the database. this code work almost always. but sometimes throw an exception
Exception:
An unhandled exception occurred while processing the request.
InvalidOperationException: A second operation started on this context
before a previous operation completed. Any instance members are not
guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
ForumQueryManager:
public class ForumQueryManager : IForumQueryManager
{
private readonly ApplicationDbContext _dbContext;
private readonly ICalender _calender;
private readonly ICacheManager _cacheManager;
public ForumQueryManager(ApplicationDbContext dbContext, ICacheManager cacheManager)
{
_dbContext = dbContext;
_cacheManager = cacheManager;
}
public async Task<List<ForumCategoryDto>> GetAll()
{
var items = await _cacheManager.Get(CacheConstants.ForumCategories, 20, GetForumCategories);
return items;
}
private async Task<List<ForumCategoryDto>> GetForumCategories()
{
var categories = await _dbContext.ForumCategories
.Select(e => new ForumCategoryDto
{
Name = e.Name,
ForumCategoryId = e.ForumCategoryId
}).ToListAsync();
return categories;
}
}
CacheManager:
public class CacheManager: ICacheManager
{
private readonly IMemoryCache _cache;
private readonly CacheSetting _cacheSetting;
public CacheManager(IMemoryCache cache, IOptions<CacheSetting> cacheOption)
{
_cache = cache;
_cacheSetting = cacheOption.Value;
}
public async Task<List<T>> Get<T>(string cacheKey, int expirationMinutes, Func<Task<List<T>>> function)
{
List<T> items;
if (_cacheSetting.MemeoryEnabled)
{
var value = _cache.Get<string>(cacheKey);
if (value == null)
{
items = await function();
value = JsonConvert.SerializeObject(items, Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
_cache.Set(cacheKey, value,
new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(expirationMinutes)));
}
else
{
items = JsonConvert.DeserializeObject<List<T>>(value);
}
}
else
{
items = await function();
}
return items;
}
}
ForumQueryManager must be transient, otherwise the _dbContext variable will be reused.

Migrating to core, but stuck with old "Module" (HttpApplication)

I've done some parts for the Middleware in startup etc..
So this is my class
public class AcceptQueryMiddleware
{
private const string Realm = "Basic realm=My Sales System";
private readonly RequestDelegate _next;
private static readonly IDictionary<Regex, string> FormatAcceptMap = new Dictionary<Regex, string>();
public AcceptQueryMiddleware(RequestDelegate next)
{
_next = next;
}
public void AcceptQueryHttpModule()
{
RegisterFormatAcceptQuery("csv", "text/csv");
RegisterFormatAcceptQuery("excel", "application/vnd.ms-excel");
RegisterFormatAcceptQuery("nav", "application/navision");
}
private static void RegisterFormatAcceptQuery(string format, string acceptHeader)
{
var regex = new Regex(#"([?]|[&])format=" + format);
FormatAcceptMap.Add(regex, acceptHeader);
}
private static void OnApplicationBeginRequest(object sender, EventArgs eventArgs)
{
var app = sender as HttpApplication;
if (app == null) { return; }
var url = app.Request.RawUrl;
foreach (var format in FormatAcceptMap)
{
if (format.Key.Match(url).Success)
{
app.Request.Headers["Accept"] = format.Value;
break;
}
}
}
public void Dispose()
{
}
}
How do I convert it to Core? Way around it?
Specifically the HttpApplication that is not supported in .NET Core..
Or do you have any links or tips I can follow?
You want to migrate your HTTP module to a middleware. See the documentation to learn how.
In your case, you're using the HttpApplication to access request-related data. You're getting the raw URL and adding some headers. That's something you can easily do in a middleware. Here's an example:
public class AcceptQueryMiddleware
{
private readonly RequestDelegate _next;
public AcceptQueryMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Do your "before" stuff here (use the HttpContext)
await _next.Invoke(context);
// Do your "after" stuff here
}
}
and call app.UseMiddleware<AcceptQueryMiddleware>(); in your Startup class (in the Configure method).