diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 20dc66fc3..f3fc244ef 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -112,11 +112,6 @@ True ContainerResources.resx - - True - True - DependencyResolutionExceptionResources.resx - True True @@ -309,10 +304,6 @@ ResXFileCodeGenerator ContainerResources.Designer.cs - - ResXFileCodeGenerator - DependencyResolutionExceptionResources.Designer.cs - ResXFileCodeGenerator LifetimeScopeResources.Designer.cs diff --git a/src/Autofac/Core/DependencyResolutionException.cs b/src/Autofac/Core/DependencyResolutionException.cs index 084293340..cb5f82e05 100644 --- a/src/Autofac/Core/DependencyResolutionException.cs +++ b/src/Autofac/Core/DependencyResolutionException.cs @@ -24,7 +24,6 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; -using System.Globalization; namespace Autofac.Core { @@ -57,27 +56,5 @@ public DependencyResolutionException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// Gets a message that describes the current exception. - /// - /// - /// The error message that explains the reason for the exception, or an empty string(""). - /// - public override string Message - { - get - { - // Issue 343: Including the inner exception message with the - // main message for easier debugging. - var message = base.Message; - if (InnerException == null) - return message; - - var inner = InnerException.Message; - message = string.Format(CultureInfo.CurrentCulture, DependencyResolutionExceptionResources.MessageNestingFormat, message, inner); - return message; - } - } } } diff --git a/src/Autofac/Core/DependencyResolutionExceptionResources.Designer.cs b/src/Autofac/Core/DependencyResolutionExceptionResources.Designer.cs deleted file mode 100644 index 5e3043c33..000000000 --- a/src/Autofac/Core/DependencyResolutionExceptionResources.Designer.cs +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Autofac.Core { - using System; - using System.Reflection; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class DependencyResolutionExceptionResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal DependencyResolutionExceptionResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.DependencyResolutionExceptionResources", typeof(DependencyResolutionExceptionResources).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to {0} ---> {1} (See inner exception for details.). - /// - internal static string MessageNestingFormat { - get { - return ResourceManager.GetString("MessageNestingFormat", resourceCulture); - } - } - } -} diff --git a/src/Autofac/Core/DependencyResolutionExceptionResources.resx b/src/Autofac/Core/DependencyResolutionExceptionResources.resx deleted file mode 100644 index da3c9db0e..000000000 --- a/src/Autofac/Core/DependencyResolutionExceptionResources.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - {0} ---> {1} (See inner exception for details.) - - \ No newline at end of file diff --git a/src/Autofac/Core/Resolving/ActivatorExtensions.cs b/src/Autofac/Core/Resolving/ActivatorExtensions.cs new file mode 100644 index 000000000..5003f6f24 --- /dev/null +++ b/src/Autofac/Core/Resolving/ActivatorExtensions.cs @@ -0,0 +1,43 @@ +// This software is part of the Autofac IoC container +// Copyright © 2011 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Activators.Delegate; + +namespace Autofac.Core.Resolving +{ + internal static class ActivatorExtensions + { + // This shorthand name for the activator is used in exception messages; for activator types + // where the limit type generally describes the activator exactly, we use that; for delegate + // activators, a variation on the type name is used to indicate this. + public static string DisplayName(this IInstanceActivator activator) + { + var fullName = activator.LimitType.FullName ?? ""; + return activator is DelegateActivator ? + $"λ:{fullName}" : + fullName; + } + } +} diff --git a/src/Autofac/Core/Resolving/CircularDependencyDetector.cs b/src/Autofac/Core/Resolving/CircularDependencyDetector.cs index 3e5c24e06..d83902b58 100644 --- a/src/Autofac/Core/Resolving/CircularDependencyDetector.cs +++ b/src/Autofac/Core/Resolving/CircularDependencyDetector.cs @@ -52,7 +52,7 @@ private static string CreateDependencyGraphTo(IComponentRegistration registratio private static string Display(IComponentRegistration registration) { - return registration.Activator.LimitType.FullName ?? string.Empty; + return registration.Activator.DisplayName(); } public static void CheckForCircularDependency(IComponentRegistration registration, Stack activationStack, int callDepth) diff --git a/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs b/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs index 2b6a4733a..d8ce5035b 100644 --- a/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs +++ b/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs @@ -8,9 +8,10 @@ // //------------------------------------------------------------------------------ +using System.Reflection; + namespace Autofac.Core.Resolving { using System; - using System.Reflection; /// @@ -71,7 +72,7 @@ internal class ComponentActivationResources { } /// - /// Looks up a localized string similar to An error occurred during the activation of a particular registration. See the inner exception for details. Registration: {0}. + /// Looks up a localized string similar to An exception was thrown while activating {0}.. /// internal static string ErrorDuringActivation { get { diff --git a/src/Autofac/Core/Resolving/ComponentActivationResources.resx b/src/Autofac/Core/Resolving/ComponentActivationResources.resx index 36576edc3..28895a5b7 100644 --- a/src/Autofac/Core/Resolving/ComponentActivationResources.resx +++ b/src/Autofac/Core/Resolving/ComponentActivationResources.resx @@ -121,7 +121,7 @@ The activation has already been executed: {0} - An error occurred during the activation of a particular registration. See the inner exception for details. Registration: {0} + An exception was thrown while activating {0}. Unable to resolve the type '{0}' because the lifetime scope it belongs in can't be located. The following services are exposed by this registration: diff --git a/src/Autofac/Core/Resolving/InstanceLookup.cs b/src/Autofac/Core/Resolving/InstanceLookup.cs index d1ee8485e..9c6b27ff2 100644 --- a/src/Autofac/Core/Resolving/InstanceLookup.cs +++ b/src/Autofac/Core/Resolving/InstanceLookup.cs @@ -30,6 +30,8 @@ using System.Linq; using System.Text; using Autofac.Builder; +using Autofac.Core.Activators; +using Autofac.Core.Activators.Delegate; using Autofac.Features.Decorators; namespace Autofac.Core.Resolving @@ -42,6 +44,7 @@ internal class InstanceLookup : IComponentContext, IInstanceLookup private readonly ISharingLifetimeScope _activationScope; private object _newInstance; private bool _executed; + private const string ActivatorChainExceptionData = "ActivatorChain"; public InstanceLookup( IComponentRegistration registration, @@ -130,7 +133,7 @@ private object Activate(IEnumerable parameters, out object decoratorT } catch (Exception ex) { - throw new DependencyResolutionException(String.Format(CultureInfo.CurrentCulture, ComponentActivationResources.ErrorDuringActivation, this.ComponentRegistration), ex); + throw PropagateActivationException(this.ComponentRegistration.Activator, ex); } if (ComponentRegistration.Ownership == InstanceOwnership.OwnedByLifetimeScope) @@ -148,6 +151,23 @@ private object Activate(IEnumerable parameters, out object decoratorT return _newInstance; } + private static DependencyResolutionException PropagateActivationException(IInstanceActivator activator, Exception exception) + { + var activatorChain = activator.DisplayName(); + var innerException = exception; + + if (exception.Data.Contains(ActivatorChainExceptionData) && + exception.Data[ActivatorChainExceptionData] is string innerChain) + { + activatorChain = activatorChain + " -> " + innerChain; + innerException = exception.InnerException; + } + + var result = new DependencyResolutionException(String.Format(CultureInfo.CurrentCulture, ComponentActivationResources.ErrorDuringActivation, activatorChain), innerException); + result.Data[ActivatorChainExceptionData] = activatorChain; + return result; + } + public void Complete() { if (!NewInstanceActivated) return; diff --git a/test/Autofac.Test/Core/DependencyResolutionExceptionTests.cs b/test/Autofac.Test/Core/DependencyResolutionExceptionTests.cs index 7cf50a8fb..d757268a0 100644 --- a/test/Autofac.Test/Core/DependencyResolutionExceptionTests.cs +++ b/test/Autofac.Test/Core/DependencyResolutionExceptionTests.cs @@ -1,52 +1,75 @@ using System; -using System.Globalization; using Autofac.Core; using Xunit; namespace Autofac.Test.Core { + // ReSharper disable ClassNeverInstantiated.Local, UnusedParameter.Local public class DependencyResolutionExceptionTests { - [Fact] - public void Message_InnerExceptionMessageIncluded() + public class A { - // Issue 343: The inner exception message should be included in the main exception message. - var inner = new Exception("Can't find file."); - var dre = new DependencyResolutionException("Unable to resolve component.", inner); - Assert.True(dre.Message.Contains("Can't find file."), "The exception message should include the inner exception message."); - } + public const string Message = "This is the original exception."; - [Fact] - public void Message_NoInnerException() - { - // Issue 343: If there is no inner exception specified, the main exception message should not be modified. - var dre = new DependencyResolutionException("Unable to resolve component."); - Assert.Equal("Unable to resolve component.", dre.Message); + public A() + { + throw new InvalidOperationException(Message); + } } - [Fact] - public void Message_NoMessageOrInnerException() + public class B { - // Issue 343: If there is no message or inner exception specified, the main exception message should not be modified. - var dre = new DependencyResolutionException(null); - Assert.True(dre.Message.Contains("Autofac.Core.DependencyResolutionException"), "The exception message should be the default exception message."); + public B(A a) + { + } } - [Fact] - public void Message_NullInnerException() + public class C { - // Issue 343: If there is a null inner exception specified, the main exception message should not be modified. - var dre = new DependencyResolutionException("Unable to resolve component.", null); - Assert.Equal("Unable to resolve component.", dre.Message); + public C(B b) + { + } } [Fact] - public void Message_NullMessageWithInnerException() + public void ExceptionMessageUnwrapsNestedResolutionFailures() { - // Issue 343: If there is no message but there is an inner exception specified, the main exception message should be modified. - var inner = new Exception("Can't find file."); - var dre = new DependencyResolutionException(null, inner); - Assert.True(dre.Message.Contains("Can't find file."), "The exception message should include the inner exception message."); + var builder = new ContainerBuilder(); + builder.RegisterType(); + builder.Register(c => new B(c.Resolve())); + builder.RegisterType(); + + Exception ex; + using (var container = builder.Build()) + { + ex = Assert.Throws(() => container.Resolve()); + } + + // Without unwrapping, the exception message is: + // + // An error occurred during the activation of a particular registration. See the inner exception + // for details. Registration: Activator = C (ReflectionActivator), Services = + // [Autofac.Test.ExceptionReportingTests+C], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, + // Sharing = None, Ownership = OwnedByLifetimeScope ---> An error occurred during the activation of + // a particular registration. See the inner exception for details. Registration: Activator = B + // (DelegateActivator), Services = [Autofac.Test.ExceptionReportingTests+B], Lifetime = + // Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope ---> + // An error occurred during the activation of a particular registration. See the inner exception for + // details. Registration: Activator = A (ReflectionActivator), Services = + // [Autofac.Test.ExceptionReportingTests+A], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, + // Sharing = None, Ownership = OwnedByLifetimeScope ---> An exception was thrown while invoking the + // constructor 'Void .ctor()' on type 'A'. ---> This is the original exception. (See inner exception + // for details.) (See inner exception for details.) (See inner exception for details.) (See inner + // exception for details.) + var n = GetType().FullName; + Assert.Equal($"An exception was thrown while activating {n}+C -> λ:{n}+B -> {n}+A.", ex.Message); + + var inner = ex.InnerException; + Assert.IsType(inner); + Assert.Equal("An exception was thrown while invoking the constructor 'Void .ctor()' on type 'A'.", inner.Message); + + Assert.IsType(inner.InnerException); + Assert.Equal(A.Message, inner.InnerException.Message); } } } diff --git a/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs b/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs index c5ca13975..079dff59b 100644 --- a/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs +++ b/test/Autofac.Test/Core/Resolving/CircularDependencyDetectorTests.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; using Autofac.Core; -using Autofac.Core.Resolving; using Xunit; namespace Autofac.Test.Core.Resolving @@ -19,11 +15,11 @@ public void OnCircularDependency_MessageDescribesCycle() var target = builder.Build(); var de = Assert.Throws(() => target.Resolve()); - Assert.Contains("System.Object -> System.Object", de.Message); - Assert.DoesNotContain("System.Object -> System.Object -> System.Object", de.Message); + Assert.Contains("λ:System.Object -> λ:System.Object", de.ToString()); + Assert.DoesNotContain("λ:System.Object -> λ:System.Object -> λ:System.Object", de.Message); } - [Fact(Skip = "Issue #648")] + [Fact] public void ManualEnumerableRegistrationDoesNotCauseCircularDependency() { var builder = new ContainerBuilder(); diff --git a/test/Autofac.Test/IntegrationTests.cs b/test/Autofac.Test/IntegrationTests.cs index 4b984f489..165d6f4f8 100644 --- a/test/Autofac.Test/IntegrationTests.cs +++ b/test/Autofac.Test/IntegrationTests.cs @@ -49,7 +49,7 @@ public void DetectsAndIdentifiesCircularDependencies() var container = builder.Build(); var de = Assert.Throws(() => container.Resolve()); - Assert.Contains("Autofac.Test.Scenarios.Dependencies.Circularity.D -> Autofac.Test.Scenarios.Dependencies.Circularity.A -> Autofac.Test.Scenarios.Dependencies.Circularity.BC -> Autofac.Test.Scenarios.Dependencies.Circularity.A", de.Message); + Assert.Contains("Autofac.Test.Scenarios.Dependencies.Circularity.D -> Autofac.Test.Scenarios.Dependencies.Circularity.A -> Autofac.Test.Scenarios.Dependencies.Circularity.BC -> Autofac.Test.Scenarios.Dependencies.Circularity.A", de.ToString()); } [Fact]