Skip to content

Commit

Permalink
Added better exceptions to the service factory. (#1040)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Aug 27, 2019
1 parent 378ac50 commit 2d2ea3b
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 12 deletions.
55 changes: 55 additions & 0 deletions src/Core/Utilities.Tests/ServiceFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Snapshooter.Xunit;
using Xunit;

namespace HotChocolate.Utilities
Expand Down Expand Up @@ -57,6 +58,52 @@ public void CreateInstanceWithServiceProvider()
Assert.NotNull(classWithDependencies.Dependency);
}

[Fact]
public void Catch_Exception_On_Create()
{
// arrange
var factory = new ServiceFactory();
var type = typeof(ClassWithNoException);

// act
Action action = () => factory.CreateInstance(type);

// assert
Assert.Throws<CreateServiceException>(action)
.Message.MatchSnapshot();
}

[Fact]
public void Cannot_Resolve_Dependencies()
{
// arrange
var factory = new ServiceFactory();
factory.Services = new EmptyServiceProvider();
var type = typeof(ClassWithDependencies);

// act
Action action = () => factory.CreateInstance(type);

// assert
Assert.Throws<CreateServiceException>(action)
.Message.MatchSnapshot();
}

[Fact]
public void No_Services_Available()
{
// arrange
var factory = new ServiceFactory();
var type = typeof(ClassWithDependencies);

// act
Action action = () => factory.CreateInstance(type);

// assert
Assert.Throws<CreateServiceException>(action)
.Message.MatchSnapshot();
}

private class ClassWithNoDependencies
{
}
Expand All @@ -70,5 +117,13 @@ public ClassWithDependencies(ClassWithNoDependencies dependency)

public ClassWithNoDependencies Dependency { get; }
}

private class ClassWithNoException
{
public ClassWithNoException()
{
throw new NullReferenceException();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unable to find a constructor on type `HotChocolate.Utilities.ServiceFactoryTests+ClassWithDependencies` that can be fulfilled with the currently available set of services. Check if all dependent services for this type are registered.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unable to create service `HotChocolate.Utilities.ServiceFactoryTests+ClassWithNoException` see inner exception for more details.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The instance type `HotChocolate.Utilities.ServiceFactoryTests+ClassWithDependencies` must have a parameterless constructor .
25 changes: 25 additions & 0 deletions src/Core/Utilities/CreateServiceException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace HotChocolate.Utilities
{
#if !NETSTANDARD1_4
[Serializable]
#endif
public class CreateServiceException
: Exception
{
public CreateServiceException()
{
}

public CreateServiceException(string message)
: base(message)
{
}

public CreateServiceException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
50 changes: 38 additions & 12 deletions src/Core/Utilities/ServiceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ public class ServiceFactory

public object CreateInstance(Type type)
{
if (type == null)
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}

object service = Services?.GetService(type);

if (service != null)
{
return service;
}


#if NETSTANDARD1_4
if (type.GetTypeInfo().IsInterface || type.GetTypeInfo().IsAbstract)
TypeInfo typeInfo = type.GetTypeInfo();
if (typeInfo.IsInterface || typeInfo.IsAbstract)
#else
if (type.IsInterface || type.IsAbstract)
#endif
Expand All @@ -33,17 +36,26 @@ public object CreateInstance(Type type)
}

FactoryInfo factoryInfo = CreateFactoryInfo(Services, type);
ParameterInfo[] parameters =
factoryInfo.Constructor.GetParameters();
ParameterInfo[] parameters = factoryInfo.Constructor.GetParameters();
object[] arguments = new object[parameters.Length];

for (int i = 0; i < parameters.Length; i++)
{
arguments[i] =
factoryInfo.Arguments[parameters[i].ParameterType];
arguments[i] = factoryInfo.Arguments[parameters[i].ParameterType];
}

return factoryInfo.Constructor.Invoke(arguments);
try
{
return factoryInfo.Constructor.Invoke(arguments);
}
catch (Exception ex)
{
// TODO : Resource
throw new CreateServiceException(
$"Unable to create service `{type.FullName}`" +
" see inner exception for more details.",
ex);
}
}

object IServiceProvider.GetService(Type serviceType) =>
Expand All @@ -65,26 +77,40 @@ private static FactoryInfo CreateFactoryInfo(

if (constructors.Length == 0)
{
throw new InvalidOperationException(
// TODO : Resource
throw new CreateServiceException(
$"The instance type `{type.FullName}` " +
"must have at least on public constructor.");
}

if (services == null)
if (services is null)
{
ConstructorInfo constructor = constructors
.FirstOrDefault(t => !t.GetParameters().Any());
if (constructor == null)
if (constructor is null)
{
throw new InvalidOperationException(
// TODO : Resource
throw new CreateServiceException(
$"The instance type `{type.FullName}` " +
"must have a parameterless constructor .");
}
return new FactoryInfo(type, constructor);
}

return GetBestMatchingConstructor(
FactoryInfo factoryInfo = GetBestMatchingConstructor(
services, type, constructors);

if (factoryInfo is null)
{
// TODO : Resource
throw new CreateServiceException(
$"Unable to find a constructor on type `{type.FullName}` " +
"that can be fulfilled with the currently " +
"available set of services. Check if " +
"all dependent services for this type are registered.");
}

return factoryInfo;
}

private static FactoryInfo GetBestMatchingConstructor(
Expand Down

0 comments on commit 2d2ea3b

Please sign in to comment.