Introduction
Now let me preface this post with after looking at this I am not convince this is ever a good idea. Having an exception which knows how to serialize something which is injected into the constructor of the exception is not a good idea.
Why am I doing this?
Well I was contacted on Twitter after my injection of JsonSerializerOptions post and the question was this.
I just saw your post about JsonOptionsInjection DI and it was exactly what I needed, but now I have do inject this json DI into a custom Exception Class, > but I’m having some problemas with base class. Any ideas?
So even though I think this is a bad idea I thought “but if it wasn’t, how could you do it?” and this is the beginning of the rabbit hole I went down.
Exception Definition
Let’s start off by defining the exception. This will need to derive from Exception
and take the dependency on JsonSerializerOptions
as defined in the original question. I’ve added in a couple of items to make it “look more real”.
public class MyExceptionWhichNeedsDependency : Exception
{
private readonly string _jsonObject;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public MyExceptionWhichNeedsDependency(string message,
string jsonObject,
Exception innerException,
JsonSerializerOptions jsonSerializerOptions)
: base(message)
{
_jsonObject = jsonObject;
_jsonSerializerOptions = jsonSerializerOptions;
}
}
Ok, so now we have an exception we can’t just create a new one and throw it.
One option is for the service/class which is potentially going to throw this exception to have the JsonSerializerOptions
injected into that services constructor just in case. This didn’t feel right. We’re glazing over the fact that none of this feels right at this time.
So how can we get around this?
Factory Pattern to the Rescue?
To create something which you don’t have potentially full control over or it has dependencies which you don’t have access to or understand you can use the Factory Pattern. The factory pattern essentially says that if you provide some items to construct the item you want I will work out the rest.
So we start off with an interface …
public interface IMyExceptionFactory
{
MyExceptionWhichNeedsDependency Create(string message, string jsonObject, Exception innerException);
}
And then an implementation. I am using the new C# syntax for targeted new expressions to do this but you can create a new instance “traditionally”.
public class MyExceptionFactory : IMyExceptionFactory
{
private readonly JsonSerializerOptions _jsonSerializerOptions;
public MyExceptionFactory(JsonSerializerOptions jsonSerializerOptions)
{
_jsonSerializerOptions = jsonSerializerOptions;
}
public MyExceptionWhichNeedsDependency Create(string message, string jsonObject, Exception innerException) => new(message, jsonObject, innerException, _jsonSerializerOptions);
}
Now we have a factory and if we register this with the IoC container we can resolve this, it will have the options injected into it’s constructor and we can then use it.
Well that’s solved one point, but we now have another problem. How do we use the factory?
We’re now down the rabbit hole so let’s see how it goes.
Throwing the Exception
To use the factory we now need to resolve this in the service which may or may not throw the exception. So we’ve not improved it at this point.
We do however need less parameters to create the required exception so that’s good (?).
try
{
// do something which throws
}
catch (Exception e)
{
var exception = sp.GetService<IMyExceptionFactory>()?.Create("message", "{ \"name\" : \"Adam\" }", e);
throw exception;
}
Where sp
is the IServiceProvider
created when we register all the dependencies in IServiceCollection and build the service provider.
Again, we’re taking a dependency in the service which may throw the exception and we’re using the locator pattern which is seen in some circumstances as an anti pattern.
Conclusion
At the end of the day this was an interesting thought exercise but I would strongly recommend against doing anything like this. If you have data which needs serializing in the exception, then maybe make the exception generics based? If you want the data to be accessible when the exception is thrown pass in the jsonObject
as above and then the consumer can worry about hydrating a POCO for the data. Either way this is not a good idea.
Let me know your thoughts? Please contact me on Twitter @WestDiscGolf