IMemoryCache not instantiated in Class library

2

I'm pretty new to ASP.Net Core, C#, OOP, Javascript … basically to everything I'm using at the moment. Last couple of months I've been reading and studying in order to start a new development project. All in all I'm progressing steadily but I've bumped into an issue with IMemoryCache that I can't really get my head around (or is it with DI?).

I'm using ASP.Net Core 2.0 and VS2017. I have a solution with 2 projects, my main ASP.Net Core MVC Web app and a .Net Core 2.0 Class Library.

In the Class Library I added a class in which I want to use caching so I added IMemoryCache to the constructor. To instantiate the class I have another constructor that calls the function GetModelGuidAsync.

public class MdsEntityCRUD
    {
        #region ClassConstruct

        private readonly IMemoryCache _cache;
        public MdsEntityCRUD(IMemoryCache cache)
        {
            _cache = cache;
        }

        public MdsEntityCRUD(ServiceClient client, string modelName, string entityName, string versionFlag)
        {
            this.client = client;
            this.ModelId = Task.Run(async () => await GetModelGuidAsync(modelName)).Result;
            this.EntityName = entityName;
            mdsWS.Version ver = Task.Run(async () => await GetVersionByPolicyAsync(client, VersionPolicy.Flag, versionFlag, this.ModelId)).Result;
            this.VersionId = ver.Identifier.Id;
        }

        public async Task<Guid> GetModelGuidAsync(string modelName)
        {
            Dictionary<string, Guid> modelNames;

            if (!_cache.TryGetValue("ModelNames", out modelNames) || !modelNames.ContainsKey(modelName))
            {
                modelNames = await GetModelNamesAsync();
                _cache.Set("ModelNames", modelNames, new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60) });  // CFG
            }

            return modelNames[modelName];
        }

I've added services.AddMemoryCache(); to ConfigureServices in Startup of my MVC project. I'm calling the class from a Controller in my MVC project using the second constructor. At runtime I get an error at the if (!_cache.TryGetValue-statement. These are the details shown in Exception Helper and from the exception window:

System.AggregateException HResult=0x80131500 Message=One or more errors occurred. (Object reference not set to an instance of an object.) Source=System.Private.CoreLib StackTrace: at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at mdXmdsWS.MdsEntityCRUD..ctor(ServiceClient client, String modelName, String entityName) in C:\Users..\MdsEntityCRUD.cs:line 59 at MDS_MVC_Proto2.Controllers.DataExplorerController.EntityDataSource(DataManagerRequest dmr) in C:\Users..\Controllers\DataExplorerController.cs:line 165
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()

Inner Exception 1: NullReferenceException: Object reference not set to an instance of an object.

and from ($exception).InnerException

  • InnerException {System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.Extensions.Caching.Memory.CacheExtensions.TryGetValue[TItem](IMemoryCache cache, Object key, TItem& value) at mdXmdsWS.MdsEntityCRUD.d__18.MoveNext() in C:\Users..\MdsEntityCRUD.cs:line 84 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at mdXmdsWS.MdsEntityCRUD.<>c__DisplayClass16_0.<<-ctor>b__0>d.MoveNext() in C:\Users..\MdsEntityCRUD.cs:line 59} System.Exception {System.NullReferenceException}

I don't know why I'm getting the error: Is it because the cache is in the Class library and not in the MVC project? Is it because I'm trying to use the cache in a function executed from the constructor? Am I missing some configuration?

Eager to learn where I'm going wrong ...

c#
caching
asp.net-core
dependency-injection
asked on Stack Overflow Jul 18, 2018 by RJD

1 Answer

0

As pointed in the comments, Dependency Injection (DI)/Inversion of Control (IoC) is no magic. At its very base, Dependency Injection just means: "Pass instance of object A to the constructor/method instead of new-ing it inside the constructor/method".

var service = new MyService(new MyDependency());

This above is already dependency injection, you inject a dependency to MyService and if its accepts a base class/interface, its implementation can be changed without changing MyService. This approach is commonly called "poor mans DI", without any external tools or libraries.

Then there are DI/IoC frameworks, which make that easier, so you don't have to new the instances yourself and inject it into the services as well as manage the objects live times for you (should every class get the same instance? (Singleton Lifetime) Or every time a new instance? (Transient Lifetime) Should specific group of classes get one instance and other group another ones? (Scoped Lifetime)).

When you use DI/IoC with or without a framework, you have to use it all the way down. There is no magic involved. The IoC frameworks do the same as above, new-ing a class and its dependencies and pass to the their constructors.

In case of ASP.NET Core the controller is resolved via the built-in DI/IoC Framework, so you can inject any registered class (inside Startup's ConfigureServices method).

In your case you obviously are new-ing the class with the second constructor, so the first one is never called and never setting the _cache member variable. When you try to access it, you get the dreaded NullReferenceException exception. You have to add the dependency to the main constructor.

If like in your case you like have to new a class, you have to pass the dependencies in yourself. In your case, you need to inject IMemoryCache into your controller and then pass it to your new-ed class.

IMemoryCache cache

public class MyController : Controller 
{
    private readonly MdsEntityCRUD mdsCrud;

    public MyController(IMemoryCache memoryCache) 
    {
        ServiceClient client = ...;
        mdsCrud = new MdsEntityCRUD(memoryCache, client, "modelname", "entityName", "v1");
    }
}

This way you get the correct IMemoryCache instance from the Controller which you can pass to the services.

There are other, more elegant ways to abstract it (using factory classes or factory methods within ConfigureServices) to perform that, but its out of scope of the question.

answered on Stack Overflow Jul 19, 2018 by Tseng

User contributions licensed under CC BY-SA 3.0