Asp.Net Core: Use memory cache outside controller - c#

In ASP.NET Core its very easy to access your memory cache from a controller
In your startup you add:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
}
and then from your controller
[Route("api/[controller]")]
public class MyExampleController : Controller
{
private IMemoryCache _cache;
public MyExampleController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
[HttpGet("{id}", Name = "DoStuff")]
public string Get(string id)
{
var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromHours(1));
_cache.Set("key", "value", cacheEntryOptions);
}
}
But, how can I access that same memory cache outside of the controller. eg. I have a scheduled task that gets initiated by HangFire, How do I access the memorycache from within my code that starts via the HangFire scheduled task?
public class ScheduledStuff
{
public void RunScheduledTasks()
{
//want to access the same memorycache here ...
}
}

Memory cache instance may be injected to the any component that is controlled by DI container; this means that you need configure ScheduledStuff instance in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services) {
services.AddMemoryCache();
services.AddSingleton<ScheduledStuff>();
}
and declare IMemoryCache as dependency in ScheduledStuff constructor:
public class ScheduledStuff {
IMemoryCache MemCache;
public ScheduledStuff(IMemoryCache memCache) {
MemCache = memCache;
}
}

I am bit late here, but just wanted to add a point to save someone's time. You can access IMemoryCache through HttpContext anywhere in application
var cache = HttpContext.RequestServices.GetService<IMemoryCache>();
Please make sure to add MemeoryCache in Startup
services.AddMemoryCache();

There is another very flexible and easy way to do it is using System.Runtime.Caching/MemoryCache
System.Runtime.Caching/MemoryCache:
This is pretty much the same as the old day's ASP.Net MVC's HttpRuntime.Cache. You can use it on ASP.Net CORE without any dependency injection, in any class you want to. This is how to use it:
// First install 'System.Runtime.Caching' (NuGet package)
// Add a using
using System.Runtime.Caching;
// To get a value
var myString = MemoryCache.Default["itemCacheKey"];
// To store a value
MemoryCache.Default["itemCacheKey"] = myString;

Related

Asp.Net Core: reset memoryCache for Integration Tests

I have created some basic Integration tests to call my Api and see if the permissions work properly. Now I have encountered a problem where running more all of the tests one of them fails - if run seperately though, it doesnt.
The reason is, that I am using IMemoryCache to store certain permissions once a user is logged in. But for my integration tests, the permissions are stored in the cache and when I try to change them for a test they are not refreshed.
In general, is there a way to invalidate the MemoryCache for every Integration test?
One of my integrationtest class basically does this:
public IntegrationTest(CustomWebApplicationFactory<Namespace.Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient();
// init the DB here etc...
var response = await _client.GetAsync("api/Some/Path");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
is there a way to tell the factory not to use a cache or use a mock cache oder something like that?
Edit:
The cache is setup in my startup.cs like this:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
[...]
}
}
And that is injected via DependenyInjection into my controllers, like this:
private IMemoryCache _cache;
private MemoryCacheEntryOptions _cacheOptions;
const int CACHE_LIFETIME_IN_DAYS = 7;
public SomeController(IMemoryCache cache) {
_cache = cache;
_cacheOptions = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromDays(CACHE_LIFETIME_IN_DAYS));
}
and I'm using it in my controllers with _cache.TryGetValue and _cache.Set
As a quick fix you can try to do something like this:
var memoryCache = _factory.Services.GetService<IMemoryCache>() as MemoryCache;
memoryCache.Compact(1.0);
When you need to reset cache.
But I would recommend either to look into not sharing _factory between tests (though it can have some performance implications) or overwriting (like it is done in the docs with context) IMemoryCache to something that you can control outside as you need.
UPD
Since tests by default are not run in parrallel you can just manually register instance of MemoryCache. Something like this:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
internal readonly MemoryCache MemoryCache;
public CustomWebApplicationFactory()
{
MemoryCache = new MemoryCache(new MemoryCacheOptions());
}
public void ClearCache() => MemoryCache.Compact(1.0);
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(IMemoryCache));
services.Remove(descriptor);
services.AddSingleton<IMemoryCache>(MemoryCache);
});
}
}
And in test call factory.ClearCache():
public void Test1()
{
var factory = new CustomWebApplicationFactory<Startup>();
var memoryCache = factory.Services.GetService<IMemoryCache>() as MemoryCache;
memoryCache.Set("test", "test");
factory.ClearCache();
Assert.IsFalse(memoryCache.TryGetValue("test", out var val));
}
If you will need to run tests with the same factory in parallel (though I would say better just create different factories) then you can create IMemoryCache implementation which will determine somehow (for example passing some specific header in client request) different test runs and return different instances of MemoryCache for them.

Using a session wrapper in ASP.Net Core Razor Pages

To improve the session handling in our application, we wanted to create a wrapper class which manages session access via properties (to prevent the common issues when working with strings).
Something like this:
public class Wrapper
{
public string Username
{
get { return HttpContext.Current.Session.GetString("username"); }
set { HttpContext.Current.Session.SetString("username", value); }
}
}
There are quite a few examples of how to implement this in classic ASP.Net, but we weren't able to port them to ASP.Net Core. The only other question about a session wrapper in ASP.Net Core (that I could find) is using a Singleton, which we didn't find suitable for our case.
Our problem comes down to two main issues:
How to wrap and access the current Session inside of another class properly (avoiding anti-patterns and security issues)
How to share the class across our application
We decided to use the dependency injection functionality of ASP.Net Core, since it seemed perfectly suitable for the job. To access the session inside of a service the IHttpContextAccessor is necessary. As stated in David Fowler's ASP.NET Core Diagnostic Scenarios it is recommended to not store the IHttpContextAccessor.HttpContext in a field, however using a scoped service should mitigate this issue, since it gets created for each request.
Our setup
Here we use the HttpContextAccessor to reference the current session. Access to the session is restricted through the use of properties (the strings could further be replaced with constants).
public class SessionService
{
private readonly ISession _session;
public SessionService(IHttpContextAccessor accessor)
{
_session = accessor.HttpContext.Session;
}
public string Username
{
get { return _session.GetString("username"); }
set { _session.SetString("username", value); }
}
}
The session wrapper then is configured as a scoped service inside of Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
// ...
// Register the service we use for session handling.
services.AddScoped<SessionService>();
}
Last but not least the service is injected into our page model and used to access the session through its property.
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly SessionService _session;
public IndexModel(ILogger<IndexModel> logger, SessionService session)
{
_logger = logger;
_session = session;
}
public void OnPost()
{
// Set the session variable.
_session.Username = "foo#bar";
}
}
I am posting the answer in hopes of getting feedback on my solution and also providing a reference point for others struggling with this.

Static class/values available only for current connection/user

I have Asp.NET core 2.I'm using a static class to keep some info available about current user (accessible in all controllers,etc),but seem to me,sometimes,static values from a user are visible to another.how to make static values available only for current connection/user?
public static class globalVariables
{
public static string Value1;
public static string getUserValue1()..
{
....
}
......
I'm using it that way:
globalVariables.getUserValue1(..)
thanks
Seems the problem solved using sessions,thanks to Henk and also info from that link:
Open up startup.cs and add the AddSession() and AddDistributedMemoryCache() lines to the ConfigureServices(IServiceCollection services)
// Add MVC services to the services container.
services.AddMvc();
services.AddDistributedMemoryCache(); // Adds a default in-memory implementation of IDistributedCache
services.AddSession();
Next, we’ll tell ASP.NET Core to use a Memory Cache to store the session data. Add the UseSession() call below to the Configure(IApplicationBulider app, ...)
// IMPORTANT: This session call MUST go before UseMvc()
app.UseSession();
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Where’s the Session variable gone?
Relax it’s still there, just not where you think it is. You can now find the session object by using HttpContext.Session. HttpContext is just the current HttpContext exposed to you by the Controller class.
If you’re not in a controller, you can still access the HttpContext by injecting IHttpContextAccessor.
Let’s go ahead and add sessions to our Home Controller:
using Microsoft.AspNetCore.Http; // Needed for the SetString and GetString extension methods
public class HomeController : Controller
{
public IActionResult Index()
{
HttpContext.Session.SetString("Test", "Ben Rules!");
return View();
}
public IActionResult About()
{
ViewBag.Message = HttpContext.Session.GetString("Test");
return View();
}
}
You’ll see the Index() and About() methods making use of the Session object. It’s pretty easy here, just use one of the Set() methods to store your data and one of the Get() methods to retrieve it.
Just for fun, let’s inject the context into a random class:
public class SomeOtherClass
{
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession _session => _httpContextAccessor.HttpContext.Session;
public SomeOtherClass(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void TestSet()
{
_session.SetString("Test", "Ben Rules!");
}
public void TestGet()
{
var message = _session.GetString("Test");
}
}

How can I use session variables in a static method?

I am using ASP.NET Core. How can I use session variables in a static method?
In ASP.NET, this looked like this:
protected static string AssignSession()
{
return HttpContext.Current.Session["UserName"].ToString();
}
protected void Page_Load(object sender, EventArgs e)
{
Session["UserName"] = "super user";
}
When I try that in ASP.NET Core, I get the following error:
An object reference is required for the non-static field, method,
or property 'ControllerBase.HttpContext'.
The answer is generally: You don’t.
In ASP.NET Core, you pretty much avoid static code. Instead, ASP.NET Core uses dependency injection to make services available as dependencies and to control their lifetime.
A static utility class in ASP.NET would probably translate to a singleton service in ASP.NET Core. Using that is very straightforward; you start by creating a non-static service that does whatever you want to do. Since this is using dependency injection, you can just depend on other services as well:
public class MyService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void SetSomeSessionValue(string value)
{
var httpContext = _httpContextAccessor.HttpContext;
httpContext.Session["example"] = value;
}
}
You can do whatever you want there. The IHttpContextAccessor is used to retrieve the current HttpContext.
Then, you need to register your service with the dependency injection container. You do that in the ConfigureServices method in your Startup.cs:
services.AddSingleton<MyService>();
// we also add the HttpContextAccessor, in case it wasn’t already (implicitly) registered before
services.AddHttpContextAccessor();
And now, you can depend on this MyService within controllers or other services by simply adding it as a constructor argument:
public class HomeController
{
private readonly MyService _myService;
public HomeController(MyService myService)
{
_myService = myService;
}
public IActionResult Index()
{
_myService.SetSomeSessionValue("test");
return View();
}
}
Now, you have a non-static service that has clear dependencies and which you can test properly.
That all being said, many constructs already have access to the current HttpContext and as such to the session. For example, in controllers, Razor pages, or even Razor views, you can just access the HttpContext directly as it is an instance variable.
So if you are not building some reusable utility code, you don’t actually need to create a service for this. You could just for example create a (non-static) utility method within your controller that then accesses the HttpContext directly.

IServiceProvider in ASP.NET Core

I starting to learn changes in ASP.NET 5(vNext)
and cannot find how to get IServiceProvider, for example in "Model"'s method
public class Entity
{
public void DoSomething()
{
var dbContext = ServiceContainer.GetService<DataContext>(); //Where is ServiceContainer or something like that ?
}
}
I know, we configuring services at startup, but where all service collection staying or IServiceProvider?
You have to bring in Microsoft.Extensions.DependencyInjection namespace to gain access to the generic
GetService<T>();
extension method that should be used on
IServiceProvider
Also note that you can directly inject services into controllers in ASP.NET 5. See below example.
public interface ISomeService
{
string ServiceValue { get; set; }
}
public class ServiceImplementation : ISomeService
{
public ServiceImplementation()
{
ServiceValue = "Injected from Startup";
}
public string ServiceValue { get; set; }
}
Startup.cs
public void ConfigureService(IServiceCollection services)
{
...
services.AddSingleton<ISomeService, ServiceImplementation>();
}
HomeController
using Microsoft.Extensions.DependencyInjection;
...
public IServiceProvider Provider { get; set; }
public ISomeService InjectedService { get; set; }
public HomeController(IServiceProvider provider, ISomeService injectedService)
{
Provider = provider;
InjectedService = Provider.GetService<ISomeService>();
}
Either approach can be used to get access to the service. Additional service extensions for Startup.cs
AddInstance<IService>(new Service())
A single instance is given all the time. You are responsible for initial object creation.
AddSingleton<IService, Service>()
A single instance is created and it acts like a singleton.
AddTransient<IService, Service>()
A new instance is created every time it is injected.
AddScoped<IService, Service>()
A single instance is created inside of the current HTTP Request scope. It is equivalent to Singleton in the current scope context.
Updated 18 October 2018
See: aspnet GitHub - ServiceCollectionServiceExtensions.cs
I don't think it is a good idea for an entity (or a model) to have access to any service.
Controllers, on the other hand, do have access to any registered service in their constructors, and you don't have to worry about it.
public class NotifyController : Controller
{
private static IEmailSender emailSender = null;
protected static ISessionService session = null;
protected static IMyContext dbContext = null;
protected static IHostingEnvironment hostingEnvironment = null;
public NotifyController(
IEmailSender mailSenderService,
IMyContext context,
IHostingEnvironment env,
ISessionService sessionContext)
{
emailSender = mailSenderService;
dbContext = context;
hostingEnvironment = env;
session = sessionContext;
}
}
use GetRequiredService instead of GetService, like the example on ASP.NET Core tutorials ( https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/working-with-sql )
documentation on the method:
https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions#Microsoft_Extensions_DependencyInjection_ServiceProviderServiceExtensions_GetRequiredService__1_System_IServiceProvider_
using Microsoft.Extensions.DependencyInjection;
using (var context = new ApplicationDbContext(serviceProvicer.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
Do not use GetService()
The difference between GetService and GetRequiredService is related with exception.
GetService() returns null if a service does not exist.
GetRequiredService() will throw exception.
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider)
{
return (T)provider.GetService(typeof(T));
}
public static T GetRequiredService<T>(this IServiceProvider provider)
{
return (T)provider.GetRequiredService(typeof(T));
}
}
Generally you want to have the DI do its thing and inject that for you:
public class Entity
{
private readonly IDataContext dbContext;
// The DI will auto inject this for you
public class Entity(IDataContext dbContext)
{
this.dbContext = dbContext;
}
public void DoSomething()
{
// dbContext is already populated for you
var something = dbContext.Somethings.First();
}
}
However, Entity would have to be automatically instantiated for you... like a Controller or a ViewComponent. If you need to manually instantiate this from a place where this dbContext is not available to you, then you can do this:
using Microsoft.Extensions.PlatformAbstractions;
public class Entity
{
private readonly IDataContext dbContext;
public class Entity()
{
this.dbContext = (IDataContext)CallContextServiceLocator.Locator.ServiceProvider
.GetService(typeof(IDataContext));
}
public void DoSomething()
{
var something = dbContext.Somethings.First();
}
}
But just to emphasize, this is considered an anti-pattern and should be avoided unless absolutely necessary. And... at the risk of making some pattern people really upset... if all else fails, you can add a static IContainer in a helper class or something and assign it in your StartUp class in the ConfigureServices method: MyHelper.DIContainer = builder.Build(); And this is a really ugly way to do it, but sometimes you just need to get it working.
I think the OP is getting confused. Entities should be as “thin” as possible. They should try not to contain logic, and or external references other than navigation properties. Look up some common patterns like repository pattern which helps to abstract your logic away from the entities themselves
Instead of getting your service inline, try injecting it into the constructor.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(typeof(DataContext));
}
}
public class Entity
{
private DataContext _context;
public Entity(DataContext context)
{
_context = context;
}
public void DoSomething()
{
// use _context here
}
}
I also suggest reading up on what AddTransient means, as it will have a significant impact on how your application shares instances of DbContext. This is a pattern called Dependency Injection. It takes a while to get used to, but you will never want to go back once you do.

Categories

Resources