Нулевая ссылка в вызове web api
Это странно, и я не могу понять, что здесь происходит. У меня есть проект веб-API, который в одном контроллере вызов определенного метода в конечном итоге вызывает функцию в службе, которая выглядит следующим образом:
public MyClassBase GetThing(Guid id)
{
if (cache.ContainsKey(id))
{
return cache[id];
}
else
{
var type = typeof(MyClassBase).Assembly.GetTypes().FirstOrDefault
(
t => t.IsClass &&
t.Namespace == typeof(MyClassBase).Namespace + ".Foo" &&
t.IsSubclassOf(typeof(MyClassBase)) &&
(t.GetCustomAttribute<MyIdAttribute>()).GUID == id
);
if (type != null)
{
System.Diagnostics.Debug.WriteLine(string.Format("Cache null: {0}",cache == null));
var param = (MyClassBase)Activator.CreateInstance(type, userService);
cache[id] = param;
return param;
}
return null;
}
}
cache
это просто словарь:
protected Dictionary<Guid, MyClassBase> cache { get; set; }
Это создается в конструкторе для этого класса:
cache = new Dictionary<Guid, MyClassBase>();
Это прекрасно работает в 99,9% случаев, но иногда, при первом запуске приложения, первый запросNullReferenceException
- и странная часть, он утверждает, что источником является эта строка:
cache[id] = param;
Но дело в том, еслиcache
является нулевым (что не может быть, это было установлено в конструкторе, это личное, и этотолько метод, который даже затрагивает это), тогда это должно было бы бросить в:
if (cache.ContainsKey(id))
и еслиid
был бы нулевым, тогда я получил бы неправильный запрос от API, потому что это не сопоставило бы, плюс мой оператор linq, чтобы получить тип с соответствующимGUID
возвратил бы нуль, который я также проверяю. И еслиparam
является нулевым, это не должно иметь значения, вы можете установить словарную запись в нулевое значение.
Это похоже на состояние гонки с чем-то, что не полностью инициализировано, но я не вижу, откуда оно исходит или как защититься от него.
Вот пример того, что он (иногда) выдает (как JSON, потому что веб-интерфейс возвращает json, и я получил его, выплевывая мне сообщения об ошибках, чтобы я мог их найти):
{
"message": "An error has occurred.",
"exceptionMessage": "Object reference not set to an instance of an object.",
"exceptionType": "System.NullReferenceException",
"stackTrace": " at System.Collections.Generic.Dictionary`2.Insert(TKey key,
TValue value, Boolean add)\r\n at
System.Collections.Generic.Dictionary`2.set_Item(TKey key, TValue value)\r\n
at MyNameSpace.Services.MyFinderService.GetThing(Guid id) in
c:\\...\\MyFinderService.cs:line 85\r\n
at MyNameSpace.Controllers.MyController.GetMyParameters(Guid id) in
c:\\...\\Controllers\\MyController.cs:line 28\r\n at
lambda_method(Closure , Object , Object[] )\r\n at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.
<GetExecutor>b__c(Object instance, Object[] methodParameters)\r\n at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance,
Object[] arguments)\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.
<>c__DisplayClass5.<ExecuteAsync>b__4()\r\n at
System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken
cancellationToken)"
}
line 85
это линия, которую я выделил выше.
Это своего родагейзенбаг, но мне просто удалось заставить его бросить, выполнив это немедленно на моей html-странице (конечно, делая это во второй раз, это работало просто отлично):
$.ajax({
url: "@Url.Content("~/api/MyController/GetMyParameters")",
data: { id: '124c5a71-65b7-4abd-97c0-f5a7cf1c4150' },
type: "GET"
}).done(function () { console.log("done"); }).fail(function () { console.log("failed") });
$.ajax({
url: "@Url.Content("~/api/MyController/GetMyParameters")",
data: { id: '7cc9d80c-e7c7-4205-9b0d-4e6cb8adbb57' },
type: "GET"
}).done(function () { console.log("done"); }).fail(function () { console.log("failed") });
$.ajax({
url: "@Url.Content("~/api/MyController/GetMyParameters")",
data: { id: '1ea79444-5fae-429c-aabd-2075d95a1032' },
type: "GET"
}).done(function () { console.log("done"); }).fail(function () { console.log("failed") });
$.ajax({
url: "@Url.Content("~/api/MyController/GetMyParameters")",
data: { id: 'cede07f3-4180-44fe-843b-f0132e3ccebe' },
type: "GET"
}).done(function() { console.log("done"); }).fail(function() { console.log("failed")});
Быстро выполнив четыре запроса, два из них потерпели неудачу в одной и той же точке, но вот где это действительно приводит в бешенство, оно прерывается на этой линии, и я могу наводить курсорcache
, id
а такжеparam
в Visual Studio и увидеть их текущее значение иникто из них нулевые! И в каждом случаеcache
имеетCount
из 1, что также странно, поскольку предполагается, что это синглтон, созданный Ninject, поэтому я начинаю подозревать, что с Ninject что-то пошло не так.
Для справки, в NinjectWebCommon я регистрирую этот сервис следующим образом:
kernel.Bind<Services.IMyFinderService>().To<Services.MyFinderService>().InSingletonScope();
Заметка: Я тоже это опубликовалСинглтоны и условия гонки потому что я не уверен на 100%, что проблема не в Ninject, но я не хотел путать этот вопрос со слишком большим количеством догадок о том, чтоможет быть быть причиной.