Referência nula na chamada da API da Web
Esse é um assunto estranho, e estou sem entender o que está acontecendo aqui. Eu tenho um projeto de API da Web que em um controlador uma chamada para um determinado método eventualmente chama uma função em um serviço que se parece com isso:
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
é simplesmente um dicionário:
protected Dictionary<Guid, MyClassBase> cache { get; set; }
Isso é criado no construtor para esta classe:
cache = new Dictionary<Guid, MyClassBase>();
Isso funciona perfeitamente em 99,9% do tempo, mas ocasionalmente, ao iniciar o aplicativo pela primeira vez, a primeira solicitação gera umNullReferenceException
- e a parte estranha é que afirma que a fonte é esta linha:
cache[id] = param;
Mas o problema é que, secache
é nulo (o que não pode ser, foi definido no construtor, é privado e esse é osó método que até o toca), então deveria ter jogado em:
if (cache.ContainsKey(id))
e seid
era nulo, então eu teria recebido uma solicitação ruim da API porque ela não mapeava, além da minha instrução linq para obter o tipo com uma correspondênciaGUID
retornaria nulo, pelo qual também estou testando. E separam
é nulo, não importa, você pode definir uma entrada de dicionário para um valor nulo.
Parece uma condição de corrida com algo que não está sendo totalmente inicializado, mas não consigo ver de onde vem ou como se defender.
Aqui está um exemplo do que ele (ocasionalmente) lança (como JSON porque a API da Web retorna json e, atualmente, eu o recebo enviando mensagens de erro para que eu possa encontrá-las):
{
"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)"
}
oline 85
é a linha que destaquei acima.
É uma espécie deHeisenbug, mas eu consegui fazer isso imediatamente na minha página html (é claro, fazer isso uma segunda vez funcionou muito bem):
$.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")});
Ativando quatro solicitações em rápida sucessão, duas delas falharam no mesmo ponto, mas é aqui que isso fica realmente irritante, que quebra nessa linha e posso passar o mouse sobrecache
, id
eparam
no Visual Studio e veja seu valor atual eNenhum deles são nulos! E em cada casocache
tem umCount
de 1, o que também é estranho, porque esse deveria ser um singleton criado por Ninject, então estou começando a suspeitar de algo maluco com o Ninject.
Para referência, no NinjectWebCommon, estou registrando este serviço assim:
kernel.Bind<Services.IMyFinderService>().To<Services.MyFinderService>().InSingletonScope();
Nota: Eu também postei issoSingletons e condições de corrida porque não tenho 100% de certeza de que o problema não é o Ninject, mas não queria confundir essa pergunta com muitas conjecturas sobre o quepoderia seja a causa.