What is Outcome.NET?

Outcome.NET is a fluent wrapper for .NET that eliminates plumbing code around failure-prone functions.

Ever write a method that could fail intermittently, like a call to a particularly flaky web service?

Typically, you handle this one of three ways: by throwing/catching exceptions, by tacking boilerplate code onto your result with metadata to indicate success or failure, or by wrapping the result in another object.

All of these work, but they're plumbing, and that's wasteful. Outcome.NET takes the third approach and extends it, providing an expressive, fluent wrapper that just works.

How do I use Outcome.NET?

Here's the classic scenario: you're calling a resource that works 99% of the time, but it's out on the internet somewhere, and could fail. You need wrap all your results in an object with metadata like:

  • Did the request succeed?
  • If it failed, why?
  • The actual result needs to be in there too, if we actually have one.

So lets say, specifically, that we want to call a web service and then, if we get a valid response, do some important things with it. If it's a bad response, we need to handle that. In this example, we will just write the errors to the console and return, but in the real world you might do some logging and show a message to the user, set up a retry, etc. That part is beyond our scope.

Our code might look like this:

public void ProcessRequest()
{
IOutcome<ResultExample> ResultOutcome = ExecuteCallToWebService();

if (!ResultOutcome.Success)
{
Console.Write(ResultOutcome.Messages[0]);//Calling .ToString() is better, it concats all messages. 
return;
}

DoImportantThingsWithResult(ResultOutcome.Value);
}

As you can see, IOutcome is a generic wrapper that contains the result and some very basic metadata. The metadata includes:

  • .Success: a Boolean indicating whether the request succeeded.
  • .Messages[]: a List that usually contains failure messages.
  • .Value: Not metadata strictly speaking, this is the data itself. It's of T, in this case ResultExample.

That's it. Nice and simple.

So how do we go about building these outcomes up inside the request?

Here's the code for ExecuteCallToWebService(), where the IOutcome is generated:

public IOutcome<ResultExample> ExecuteCallToWebService()
{
ResultExample Result;

try
{
Result = RiskyCall();
}
catch (Exception Ex)
{
return Outcomes.Failure<ResultExample>().FromException(Ex); //Adds Ex.Message to the message list
}

return Outcomes.Success<ResultExample>().WithValue(Result)
.WithMessage("It worked!");
}

Handling Failures

So let's break down our two calls into Outcome.NET:

return Outcomes.Failure<ResultExample>().FromException(Ex);

Our call has failed and we need to return a failure state and a message. FromException() is a shortcut that grabs Exception.Message and puts it into IOutcome.Messages[] with a prefix of 'Exception: '. There are a lot of other ways we could have done this:

return Outcomes.Failure<ResultExample>().WithMessage("The stupid web service is down again!")
.FromException(Ex)
.WithMessage("Call IT!");

The API is very flexible, there's also a shortcut for just adding an exception with a prefix message:

return Outcomes.Failure<ResultExample>(Ex, "The stupid web service is down again!");

This works as well, although it doesn't give you any messaging:

return Outcomes.Failure<ResultExample>();

There's also a .FromOutcome() option that comes in handy in more complex scenarios, where you have a deep chain of method calls and they each add some detail.

Handling Successes

The success API is very similar, but a bit simpler.

Here's our success outcome from the example above:

return Outcomes.Success<ResultExample>().WithValue(Result)
.WithMessage("It worked!");
  • .WithValue(): Sets the value that we're wrapping.
  • .WithMessage(): Adds a string to IOutcome.Messages[]. Usually you leave this empty for successes, but it can be useful in complicated scenarios.

There is also shorthand for just creating a success outcome with a value:

return Outcomes.Success<ResultExample>(Result);

And quite a few others, which you will find if you explore the API. They are functionally identical, it's just a matter of style.

Why Use Outcome.NET?

I've written this code probably a hundred times over my career, and I'm sure across the world that number is more like hundreds of thousands. After working on a project that did a lot of integration, I decided to perfect my version of this wrapper and never write it again.

I'm sure you could slam together something that solves the same problem. You probably have in fact. It is fairly simple. But it might not be quite as expressive, it might not provide an easily recognizable pattern, and it definitely won't come with a dozen free unit tests.