MichaCo / CacheManager

CacheManager is an open source caching abstraction layer for .NET written in C#. It supports various cache providers and implements many advanced features.
http://cachemanager.michaco.net
Apache License 2.0
2.33k stars 458 forks source link

ASP.NET Core DI Register CacheManager #94

Open Tazer opened 7 years ago

Tazer commented 7 years ago

Hello,

I have tried to find some good examples of registering CacheManager in ASP.NET Core with DI. Is there any examples of this , cause I couldn't really get it working with the "Built-In" DI. So wanted to ask if I'm missing something or do a need to use StrucutureMap, To be able to register CacheManager Generic?

This is the non-working code I wrote:

services.TryAddTransient(typeof(ICacheManager<>), t => CacheFactory.FromConfiguration(t.GetType().GetGenericArguments()[0],cacheConfig));

That throws an error Open generic service type '{typeof(IEnumerable<>)}' requires registering an open generic implementation type.

MichaCo commented 7 years ago

Pretty good question. I had to play with the default ASP.NET Core DI a little bit to figure that out.

First of all, you probably should not use transient instances and use singleton instead for CacheManager. Then, in your code, t.GetType() gives you the type of IServiceProvider, that is what t is. So that totally wouldn't work. In that overload, you do not get to the Type requested. And there is no overload to do so.

Instead what you can do, and what actually works pretty well:

  1. You register the cache configuration as singleton
  2. You register the open generic interface of cache manager and the implementation type for it, which is BaseCacheManager<> also as open generic
 var config = ...build cache configuartion...
/* add it to the services */
services.AddSingleton(config);
/* add the cache manager interface and impl type as open generics */
services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));

Later, in your controller for example, you can constructor inject a CacheManager instance

    public ValuesController(ICacheManager<string> cache, ICacheManager<int> intCache)
    {
        this.cache = cache;
        this.intCache = intCache;
    }

Works fine! Let me know if you have any issues and, in general, this kind of questions are probably more suited for stackoverflow ;)

frumkinwy commented 7 years ago

Is there an implementation of SystemRuntimeCaching that is compatible with dotnet core?

MichaCo commented 7 years ago

@frumkinwy No, System.Runtime.Caching doesn't exist anymore in dotnet core and you have to use something else if you want cross platform support.

You can use System.Runtime.Caching as long as you are targeting the full .NET Framework of course (new or old project system).

raRaRa commented 7 years ago

@MichaCo What about using Microsoft.Extensions.Caching.Memory for memory caching in dotnet core?

(Nevermind, just found out there's CacheManager.Microsoft.Extensions.Caching.Memory - isn't that cross platform or is it Windows specific?)

MichaCo commented 7 years ago

@raRaRa CacheManager.Microsoft.Extensions.Caching.Memory is cross platform (netstandard1.3), yup

ivlevkonstantin commented 6 years ago

@MichaCo , please, put the code from your sample to the documentation. I just spent several hours before reading this, trying to figure out how to use CacheFactory with open-generic registration in MicrosoftExtensions and/or Castle Windsor DI containers. I even tried to create a fork of the repo, moving typed parameter from class to method level :D

vanikulkarniAtFIS commented 5 years ago

Can anyone please post sample code with .net core configuration ?

Tazer commented 5 years ago

I did something like this

            var cacheConfig = CacheManager.Core.ConfigurationBuilder.BuildConfiguration(settings =>
            {
                settings.WithSerializer(typeof(CacheManager.Serialization.Json.JsonCacheSerializer)).WithMicrosoftMemoryCacheHandle("in-memory").And.WithRedisConfiguration("redis", redisIpAddress)
                    .WithMaxRetries(1000)
                    .WithRetryTimeout(100)
                    .WithRedisBackplane("redis")
                    .WithRedisCacheHandle("redis", true);

            });
            services.AddSingleton<ICacheManager<SerieViewModel>>(t => CacheFactory.FromConfiguration<SerieViewModel>(cacheConfig));
            services.AddSingleton<ICacheManager<TokenResponse>>(t => CacheFactory.FromConfiguration<TokenResponse>(cacheConfig));
            services.AddSingleton<ICacheManager<string>>(t => CacheFactory.FromConfiguration<string>(cacheConfig));

It worked .. but very long time ago I touched the code :)

MichaCo commented 5 years ago

Thanks @Tazer for adding some examples ;)

Actually, if you install the CacheManager.Microsoft.Extensions.Configuration package, there are several extensions on IServiceCollection which should help with that

You can inject one common configuration and then a open generic cache manager

services.AddCacheManagerConfiguration(...);
services.AddCacheManager();

and/or inject type specific cache manager instances with specific configurations

services.AddCacheManager<string>(c => c.WithDictionaryHandle());

You can use both methods together.

vanikulkarniAtFIS commented 5 years ago

Hi Micha,

Thanks for the code snippet , it worked well.

Now we are using JSON file to define the cache manager config instead of doing it from code. I have few doubts:

Regards, Vani.

MichaCo commented 5 years ago

No, there is no support for configuring logging nor serialization via MS JSON configuration.

You can use a hybrid approach, load most configuration from JSON and then further configure logging and serialization. There is even an example in the docs

vanikulkarniAtFIS commented 5 years ago

Hi Micha,

We want to use Redis cache through cache manager. I added cache.config find attached, I am getting error for Redis handle, I can perfectly use it for Dictionary handle

System.InvalidOperationException: No configuration added for configuration name cacheManager.Redis

I feel I am missing on some connection string to Redis. Could you give me some cache.json where Redis is being used. I downloaded your sample code which has cache.json sample, But that is not being loaded in sample code. I have created attached cache.json looking at the sample json only, please help me with this.

System.ArgumentNullException: Value cannot be null. Parameter name: key

at CacheManager.Core.BaseCacheManager1..ctor(String name, ICacheManagerConfiguration configuration) at CacheManager.Core.BaseCacheManager1..ctor(ICacheManagerConfiguration configuration) at CacheManager.Core.CacheFactory.FromConfiguration[TCacheValue](String cacheName, ICacheManagerConfiguration configuration) at FIS.Risk.Core.Examples.ServiceFactory..ctor(IDependencyContainer container, IConfiguration config, ILogging log, ICacheManagerConfigurationFactory cacheManagerConfigurationFactory) in C:\Projects\fis-risk-core\src\Examples\ExampleHostCore\ServiceFactory.cs:line 63 at lambda_method(Closure , LifetimeContext , CompositionOperation ) at System.Composition.Hosting.Core.LifetimeContext.GetOrCreate(Int32 sharingId, CompositionOperation operation, CompositeActivator creator) at System.Composition.Hosting.Providers.ImportMany.ImportManyExportDescriptorProvider.<>cDisplayClass3_21.<GetImportManyDescriptor>b__4(ExportDescriptor e) at System.Linq.Enumerable.SelectArrayIterator2.ToArray() at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source) at lambda_method(Closure , LifetimeContext , CompositionOperation ) at System.Composition.TypedParts.ActivationFeatures.DisposalFeature.<>cDisplayClass0_0.b0(LifetimeContext c, CompositionOperation o) at System.Composition.Hosting.Core.LifetimeContext.GetOrCreate(Int32 sharingId, CompositionOperation operation, CompositeActivator creator) at System.Composition.Hosting.Core.CompositionOperation.Run(LifetimeContext outermostLifetimeContext, CompositeActivator compositionRootActivator) at System.Composition.Hosting.Core.LifetimeContext.TryGetExport(CompositionContract contract, Object& export) at System.Composition.CompositionContext.TryGetExport(Type exportType, String contractName, Object& export) at FIS.Risk.Core.Composition.Autofac.RegistrationSource.Autofac.Core.IRegistrationSource.RegistrationsFor(Service service, Func2 registrationAccessor) in C:\Projects\fis-risk-core\src\Implementations\Autofac\RegistrationSource.cs:line 38 at Autofac.Core.Registration.ComponentRegistry.GetInitializedServiceInfo(Service service) at Autofac.Core.Registration.ComponentRegistry.TryGetRegistration(Service service, IComponentRegistration& registration) at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable1 parameters, Object& instance) at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) at FIS.Risk.Core.Composition.Autofac.AutofacDependencyContainer.Resolve(Type type) in C:\Projects\fis-risk-core\src\Implementations\Autofac\AutofacDependencyContainer.cs:line 38 at FIS.Risk.Core.Composition.DependencyContainerExtensions.Resolve[T](IDependencyContainer container) in C:\Projects\fis-risk-core\src\Core\Composition\IDependencyContainer.cs:line 81 at FIS.Risk.Core.Examples.Program.<>c.

b0_1(IDependencyContainer container) in C:\Projects\fis-risk-core\src\Examples\ExampleHostCore\Program.cs:line 64 at FIS.Risk.Core.Hosting.StartupManager.HandleStartup[T](String product, String component, Func1 buildContainer, Func2 resolveRoot, Action`1 run) in C:\Projects\fis-risk-core\src\Hosting\StartupManager.cs:line 81

Regards, Vani.

From: MichaC [mailto:notifications@github.com] Sent: 17 October 2018 13:25 To: MichaCo/CacheManager CacheManager@noreply.github.com Cc: Kulkarni, Vani Vani.Kulkarni@fisglobal.com; Comment comment@noreply.github.com Subject: Re: [MichaCo/CacheManager] ASP.NET Core DI Register CacheManager (#94)

No, there is no support for configuring logging nor serialization via MS JSON configuration.

You can use a hybrid approach, load most configuration from JSON and then further configure logging and serialization. There is even an example in the docshttps://emea01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fcachemanager.michaco.net%2Fdocumentation%2FCacheManagerConfiguration%23loading-configuration-from-json-file&data=02%7C01%7Cvani.kulkarni%40fisglobal.com%7C8b87aa5febd54c5c572d08d63405e579%7Ce3ff91d834c84b15a0b418910a6ac575%7C0%7C0%7C636753597310886167&sdata=8rQrjfjAYG239a5nAT%2BzAz068vdL8fXfjWkCgQfibYQ%3D&reserved=0

— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FMichaCo%2FCacheManager%2Fissues%2F94%23issuecomment-430527800&data=02%7C01%7Cvani.kulkarni%40fisglobal.com%7C8b87aa5febd54c5c572d08d63405e579%7Ce3ff91d834c84b15a0b418910a6ac575%7C0%7C0%7C636753597310886167&sdata=1PnbTxbgDaJMxiFAMTy5mBupNPZQL0Jr20J5jLZsSd4%3D&reserved=0, or mute the threadhttps://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAluDJ3-qS1FK5bbJIq9KJMoNcac0KKr6ks5uluJqgaJpZM4J-2ia&data=02%7C01%7Cvani.kulkarni%40fisglobal.com%7C8b87aa5febd54c5c572d08d63405e579%7Ce3ff91d834c84b15a0b418910a6ac575%7C0%7C0%7C636753597310886167&sdata=KAVYQ4uZt%2FnhcsBZbPM2c%2BWWy7KmYS%2BeU%2FMiDFslInM%3D&reserved=0.

The information contained in this message is proprietary and/or confidential. If you are not the intended recipient, please: (i) delete the message and all copies; (ii) do not disclose, distribute or use the message in any manner; and (iii) notify the sender immediately. In addition, please be aware that any message addressed to our domain is subject to archiving and review by persons other than the intended recipient. Thank you.

vanikulkarniAtFIS commented 5 years ago

Hi Micha,

I don’t want to use Redis as backplane. We are not using multi layered caching.

I could find app.config files in sample using Redis. I want simple cache.config that uses Redis cache. Could you please share one or point me there ?

Regards, Vani.

From: Kulkarni, Vani Sent: 22 October 2018 11:05 To: 'MichaCo/CacheManager' reply@reply.github.com; MichaCo/CacheManager CacheManager@noreply.github.com Cc: Comment comment@noreply.github.com Subject: RE: [MichaCo/CacheManager] ASP.NET Core DI Register CacheManager (#94)

Hi Micha,

We want to use Redis cache through cache manager. I added cache.config find attached, I am getting error for Redis handle, I can perfectly use it for Dictionary handle

System.InvalidOperationException: No configuration added for configuration name cacheManager.Redis

I feel I am missing on some connection string to Redis. Could you give me some cache.json where Redis is being used. I downloaded your sample code which has cache.json sample, But that is not being loaded in sample code. I have created attached cache.json looking at the sample json only, please help me with this.

System.ArgumentNullException: Value cannot be null. Parameter name: key

at CacheManager.Core.BaseCacheManager1..ctor(String name, ICacheManagerConfiguration configuration) at CacheManager.Core.BaseCacheManager1..ctor(ICacheManagerConfiguration configuration) at CacheManager.Core.CacheFactory.FromConfiguration[TCacheValue](String cacheName, ICacheManagerConfiguration configuration) at FIS.Risk.Core.Examples.ServiceFactory..ctor(IDependencyContainer container, IConfiguration config, ILogging log, ICacheManagerConfigurationFactory cacheManagerConfigurationFactory) in C:\Projects\fis-risk-core\src\Examples\ExampleHostCore\ServiceFactory.cs:line 63 at lambda_method(Closure , LifetimeContext , CompositionOperation ) at System.Composition.Hosting.Core.LifetimeContext.GetOrCreate(Int32 sharingId, CompositionOperation operation, CompositeActivator creator) at System.Composition.Hosting.Providers.ImportMany.ImportManyExportDescriptorProvider.<>cDisplayClass3_21.<GetImportManyDescriptor>b__4(ExportDescriptor e) at System.Linq.Enumerable.SelectArrayIterator2.ToArray() at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source) at lambda_method(Closure , LifetimeContext , CompositionOperation ) at System.Composition.TypedParts.ActivationFeatures.DisposalFeature.<>cDisplayClass0_0.b0(LifetimeContext c, CompositionOperation o) at System.Composition.Hosting.Core.LifetimeContext.GetOrCreate(Int32 sharingId, CompositionOperation operation, CompositeActivator creator) at System.Composition.Hosting.Core.CompositionOperation.Run(LifetimeContext outermostLifetimeContext, CompositeActivator compositionRootActivator) at System.Composition.Hosting.Core.LifetimeContext.TryGetExport(CompositionContract contract, Object& export) at System.Composition.CompositionContext.TryGetExport(Type exportType, String contractName, Object& export) at FIS.Risk.Core.Composition.Autofac.RegistrationSource.Autofac.Core.IRegistrationSource.RegistrationsFor(Service service, Func2 registrationAccessor) in C:\Projects\fis-risk-core\src\Implementations\Autofac\RegistrationSource.cs:line 38 at Autofac.Core.Registration.ComponentRegistry.GetInitializedServiceInfo(Service service) at Autofac.Core.Registration.ComponentRegistry.TryGetRegistration(Service service, IComponentRegistration& registration) at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable1 parameters, Object& instance) at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) at FIS.Risk.Core.Composition.Autofac.AutofacDependencyContainer.Resolve(Type type) in C:\Projects\fis-risk-core\src\Implementations\Autofac\AutofacDependencyContainer.cs:line 38 at FIS.Risk.Core.Composition.DependencyContainerExtensions.Resolve[T](IDependencyContainer container) in C:\Projects\fis-risk-core\src\Core\Composition\IDependencyContainer.cs:line 81 at FIS.Risk.Core.Examples.Program.<>c.

b0_1(IDependencyContainer container) in C:\Projects\fis-risk-core\src\Examples\ExampleHostCore\Program.cs:line 64 at FIS.Risk.Core.Hosting.StartupManager.HandleStartup[T](String product, String component, Func1 buildContainer, Func2 resolveRoot, Action`1 run) in C:\Projects\fis-risk-core\src\Hosting\StartupManager.cs:line 81

Regards, Vani.

From: MichaC [mailto:notifications@github.com] Sent: 17 October 2018 13:25 To: MichaCo/CacheManager CacheManager@noreply.github.com<mailto:CacheManager@noreply.github.com> Cc: Kulkarni, Vani Vani.Kulkarni@fisglobal.com<mailto:Vani.Kulkarni@fisglobal.com>; Comment comment@noreply.github.com<mailto:comment@noreply.github.com> Subject: Re: [MichaCo/CacheManager] ASP.NET Core DI Register CacheManager (#94)

No, there is no support for configuring logging nor serialization via MS JSON configuration.

You can use a hybrid approach, load most configuration from JSON and then further configure logging and serialization. There is even an example in the docshttps://emea01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fcachemanager.michaco.net%2Fdocumentation%2FCacheManagerConfiguration%23loading-configuration-from-json-file&data=02%7C01%7Cvani.kulkarni%40fisglobal.com%7C8b87aa5febd54c5c572d08d63405e579%7Ce3ff91d834c84b15a0b418910a6ac575%7C0%7C0%7C636753597310886167&sdata=8rQrjfjAYG239a5nAT%2BzAz068vdL8fXfjWkCgQfibYQ%3D&reserved=0

— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FMichaCo%2FCacheManager%2Fissues%2F94%23issuecomment-430527800&data=02%7C01%7Cvani.kulkarni%40fisglobal.com%7C8b87aa5febd54c5c572d08d63405e579%7Ce3ff91d834c84b15a0b418910a6ac575%7C0%7C0%7C636753597310886167&sdata=1PnbTxbgDaJMxiFAMTy5mBupNPZQL0Jr20J5jLZsSd4%3D&reserved=0, or mute the threadhttps://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAluDJ3-qS1FK5bbJIq9KJMoNcac0KKr6ks5uluJqgaJpZM4J-2ia&data=02%7C01%7Cvani.kulkarni%40fisglobal.com%7C8b87aa5febd54c5c572d08d63405e579%7Ce3ff91d834c84b15a0b418910a6ac575%7C0%7C0%7C636753597310886167&sdata=KAVYQ4uZt%2FnhcsBZbPM2c%2BWWy7KmYS%2BeU%2FMiDFslInM%3D&reserved=0.

The information contained in this message is proprietary and/or confidential. If you are not the intended recipient, please: (i) delete the message and all copies; (ii) do not disclose, distribute or use the message in any manner; and (iii) notify the sender immediately. In addition, please be aware that any message addressed to our domain is subject to archiving and review by persons other than the intended recipient. Thank you.

MichaCo commented 5 years ago

@vanikulkarniAtFIS I'd appreciate if you could clean up the comments. Pretty hard to read.

Didn't really get what you are looking for, a json configuration example for Redis? Something like this? http://cachemanager.michaco.net/documentation/CacheManagerConfiguration#json-schema

vvadyak commented 4 years ago

Hi @MichaCo , Could you recommend any kind of generic registration of an ICacheManager in ASP.NET Core DI? For instance, I have a controller with a bunch of methods which results I would like to cache. There are hundreds of types I would like to cache. As for me, it is not convenient to inject tons of ICacheManager`s into each controller. It looks like:

public ValuesController(ICacheManager cache1, ICacheManager cache2, ..... , ICacheManager cacheN) { this.cache1= cache1; this.cache2= cache2; ..... this.cacheN= cacheN; }

Moreover, I would like to have the ability to use in a single controller one CacheManager configured to work with dictionary/in-memory cache and another CacheManager configured to work with a Redis cache.

As a result, I would like to get something like:

public ValuesController(IDictionaryCacheManager dictionaryCache, IRedisCacheManager redisCache) { this.dictionaryCache= dictionaryCache; this.redisCache= redisCache; }

Looks like services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>)); does not work for me. What would you recommend?

MichaCo commented 4 years ago

@vvadyak Yes, the generic injection (ICacheManager<>), typeof(BaseCacheManager<>)) only works for one configuration. That's basically the most convenient way to configure many caches if you want to keep those caches separated from each other (not sharing keys across cached types.)

The extension on IServiceCollection coming with CacheManager to set that up is

services.AddCacheManager();

That requires a configuration to be injected, so you also have to in setup one via services.AddCacheManagerConfiguration which can use the standard configurations extensions to load cache manager configuration from file, or you can manually define a config...

Then, you can inject more specific configurations per type if some of your caches have a different config.

For example, to configure a slightly different cache for int you can call

services.AddCacheManager<int>(Configuration, configure: builder => builder...);

This will basically overrule the open generic injection.

That being said, as you already figured out, there is no build in way to configure a cache for the same type, e.g. object with different kinds of caches / different configurations.

The DI framework doesn't really allow us to inject named singleton instances. One way to do that though is by injecting distinct types to identify the different kinds of caches. You could wrap the ICacheManager interface with something you then can inject to get a certain cache back.

Let's say I want an in-memory and a distributed cache for the same type string.

I'd define an interface per type of cache which extends ICacheManager

public interface IDistributedCacheManager<TType> : ICacheManager<TType>
{
}

public interface ILocalCacheManager<TType> : ICacheManager<TType>
{
}

Then, I need an implementation which I can inject for those interfaces, that's pretty simple, I can just extend BaseCacheManager:

private class CacheManagerWrapper<TType> : BaseCacheManager<TType>, IDistributedCacheManager<TType>, ILocalCacheManager<TType>
{
    public CacheManagerWrapper(ICacheManagerConfiguration configuration) : base(configuration)
    {
    }
}

And with that, I can inject different instances of CacheManager for different purposes:

services.AddSingleton<IDistributedCacheManager<string>>(p =>
{
    //// Get already injected configuration and modify it further
    // var config = p.GetRequiredService<ICacheManagerConfiguration>();
    // config.Builder
    //    ....

    // Or load some configuration from the configuration system (or you'd just create a new configuration from scratch...)
    var config = Configuration.GetCacheConfiguration("distributed");

    return new CacheManagerWrapper<string>(config);
});

services.AddSingleton<ILocalCacheManager<string>>(p =>
{
    var config = Configuration.GetCacheConfiguration("inMemory");

    return new CacheManagerWrapper<string>(config);
});

The wrapper can be totally hidden and isn't visible to any usage.

To use those different kinds of caches, you'd inject your custom interfaces in a controller or manager, instead of ICacheManager<T>!

I was thinking of adding something like that to the library, but I'm not sure that really makes any sense. Its just like 5 lines of code anyways to implement it and I don't really know what kind of use cases other users might have, meaning, it doesn't make sense to add random named interfaces to the library for any kind of use case...

Hope that helps

In your case, with many different types, I guess it makes most sense to use object for T in ICacheManager<T>. Otherwise, you have to inject so many different types to your controllers and such and that gets very ugly very quickly.

That being said, you have to be sure that this will work for your app. If you use one cache instance for all your types, you cannot have the same cache key for 2 different objects...

I'm still trying to figure out a better way to inject one type, a factory, which then gen be used to .Get a cache instance via name and/or type. The problem is that this use case isn't really supported by the DI framework. The Extensions.Options package did something custom to support named options for example but it gets pretty complicated...