From 3e752b226bc9bc3832a09f5b03adba9c1fd05750 Mon Sep 17 00:00:00 2001 From: dotnetjunkie Date: Thu, 7 Jan 2021 16:43:40 +0100 Subject: [PATCH] Improved exception messages thrown in case of type initialization errors. Fixes #812 --- .../InstanceProducerTests.cs | 100 ++++++++++++++++++ src/SimpleInjector/InstanceProducer.cs | 37 ++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/SimpleInjector.Tests.Unit/InstanceProducerTests.cs b/src/SimpleInjector.Tests.Unit/InstanceProducerTests.cs index 5804e5d5b..f03041df9 100644 --- a/src/SimpleInjector.Tests.Unit/InstanceProducerTests.cs +++ b/src/SimpleInjector.Tests.Unit/InstanceProducerTests.cs @@ -356,6 +356,98 @@ public void GetInstance_ForSingletonValueType_CanBeResolved() Assert.AreEqual(expectedValue, actualValue); } + // #812 + [TestMethod] + public void GetInstance_ResolvingATypeWithTypeInitializationException_ThrowsExpressiveException() + { + // Act + var container = new Container(); + + container.Register(); + + // Act + Action action = () => container.GetInstance(); + + // Assert + AssertThat.ThrowsWithExceptionMessageContains( + "The type initializer for TopLevelClassWithFailingTypeInitializer threw an " + + "exception. .", + action); + } + + // #812 + [TestMethod] + public void GetInstance_ResolvingANestedTypeWithTypeInitializationException_ThrowsExpressiveException() + { + // Act + var container = new Container(); + + container.Register(); + + // Act + Action action = () => container.GetInstance(); + + // Assert + AssertThat.ThrowsWithExceptionMessageContains( + "The type initializer for InstanceProducerTests.NestedClassWithFailingTypeInitializer " + + "threw an exception. .", + action); + } + + // #812 + [TestMethod] + public void GetInstance_ResolvingAGenericNestedTypeWithTypeInitializationException_ThrowsExpressiveException() + { + // Act + var container = new Container(); + + container.Register(typeof(NestedClassWithFailingTypeInitializer<>)); + + // Act + Action action = () => container.GetInstance>(); + + // Assert + AssertThat.ThrowsWithExceptionMessageContains( + "The type initializer for InstanceProducerTests" + + ".NestedClassWithFailingTypeInitializer threw an exception. .", + action); + } + + // #812 + [TestMethod] + public void GetInstance_ResolvingASingletonWithTypeInitializationException_ThrowsExpressiveException() + { + // Act + var container = new Container(); + + container.RegisterSingleton(); + + // Act + Action action = () => container.GetInstance(); + + // Assert + AssertThat.ThrowsWithExceptionMessageContains( + "The type initializer for InstanceProducerTests.NestedClassWithFailingTypeInitializer " + + "threw an exception. .", + action); + } + + public class NestedClassWithFailingTypeInitializer + { + static NestedClassWithFailingTypeInitializer() + { + throw new Exception("."); + } + } + + public class NestedClassWithFailingTypeInitializer + { + static NestedClassWithFailingTypeInitializer() + { + throw new Exception("."); + } + } + public class OneAndTwo : IOne, ITwo { } @@ -381,4 +473,12 @@ public NodeFactory(IEnumerable nodes) } } } + + public class TopLevelClassWithFailingTypeInitializer + { + static TopLevelClassWithFailingTypeInitializer() + { + throw new Exception("."); + } + } } \ No newline at end of file diff --git a/src/SimpleInjector/InstanceProducer.cs b/src/SimpleInjector/InstanceProducer.cs index 1a669a3ec..d413183f3 100644 --- a/src/SimpleInjector/InstanceProducer.cs +++ b/src/SimpleInjector/InstanceProducer.cs @@ -329,6 +329,10 @@ public Expression BuildExpression() throw; } + catch (TypeInitializationException ex) + { + throw this.MakeMoreExpressiveTypeInitializationException(ex); + } catch (Exception ex) { if (this.MustWrapThrownException(ex)) @@ -610,9 +614,17 @@ private string BuildActivationExceptionMessage(Exception innerException) private object BuildAndReplaceInstanceCreatorAndCreateFirstInstance() { this.instanceCreator = this.BuildInstanceCreator(); - var instance = this.instanceCreator(); - this.InstanceSuccessfullyCreated = true; - return instance; + + try + { + var instance = this.instanceCreator(); + this.InstanceSuccessfullyCreated = true; + return instance; + } + catch (TypeInitializationException ex) + { + throw this.MakeMoreExpressiveTypeInitializationException(ex); + } } // Prevents any recursive calls from taking place. @@ -674,6 +686,25 @@ private static bool ShouldBeRegisteredAsAnExternalProducer(Registration registra return !(registration is ExpressionRegistration); } + private Exception MakeMoreExpressiveTypeInitializationException( + TypeInitializationException ex) + { + Type implementationType = this.Registration.ImplementationType; + + // #812 The exceptions thrown by the runtime in case of a type initialization error are + // unproductive. Here we throw a more expressive exception. This is done by appending the + // inner exception's message to the exception message and replacing the type name with a + // 'friendly type name', which is especially useful in the case of generic types. + return new ActivationException( + ex.Message + // When the type in question is nested, the exception will contain just the simple + // type name, while for non-nested types, the full name is used (don't ask why). + .Replace($"'{implementationType.FullName}'", implementationType.ToFriendlyName()) + .Replace($"'{implementationType.Name}'", implementationType.ToFriendlyName()) + + " " + ex.InnerException?.Message, + ex); + } + [ExcludeFromCodeCoverage] internal sealed class InstanceProducerDebugView {