Now that we know whether the operation succeeded or not, we want to know what went wrong. To do that, we add an ErrorMessage property to the OperationResult record class.With that in place, we no longer need to set whether the operation succeeded or not; we can compute that using the ErrorMessage property instead. The logic behind this improvement goes as follows:
- When there is no error message, the operation succeeded.
- When there is an error message, the operation failed.
The OperationResult record class implementing this logic looks like the following:
namespace OperationResult.SingleError
public record class OperationResult
{
public bool Succeeded => string.IsNullOrWhiteSpace(ErrorMessage);
public string?
ErrorMessage { get; init; }
}
In the preceding code, we have the following:
- The Succeeded property checks for an error message.
- The ErrorMessage property contains an error message settable when instantiating the object.
The executor of that operation looks similar but uses the new constructor, setting an error message instead of directly setting the success indicator:
namespace OperationResult.SingleError
public class Executor
{
public OperationResult Operation()
{
// Randomize the success indicator
// This should be real logic
var randomNumber = Random.Shared.Next(100);
var success = randomNumber % 2 == 0;
// Return the operation result
return success
?
new()
: new() { ErrorMessage = $”Something went wrong with the number ‘{randomNumber}’.”
};
}
}
The consuming code does the same as in the previous sample but writes the error message in the response output instead of a generic failure string:
app.MapGet(
“/single-error”,
(OperationResult.SingleError.Executor executor) =>
{
var result = executor.Operation();
if (result.Succeeded)
{
// Handle the success
return “Operation succeeded”;
}
else
{
// Handle the failure
return result.ErrorMessage;
}
}
);
When looking at this example, we can begin to comprehend the Operation Result pattern’s usefulness. It furthers us from the simple success indicator that looked like an overcomplicated Boolean.Next, we add the possibility of setting a value when the operation succeeds.
Adding a return value
Now that we have a reason for failure, we may want the operation to return a value. To achieve this, let’s build over the previous example and add a Value property to the OperationResult class:
namespace OperationResult.SingleErrorWithValue;
public record class OperationResult
{
public bool Succeeded => string.IsNullOrWhiteSpace(ErrorMessage);
public string?
ErrorMessage { get; init; }
public int?
Value { get; init; }
}
By adding a second init-only property, we can set the Value property when the operation succeeds and fails.
In a real-world scenario, that Value property could be null in the case of an error, hence the nullable int property.
The operation is also very similar, but we are setting the Value property as well as using the object initializer in both cases (highlighted lines):
namespace OperationResult.SingleErrorWithValue;
public class Executor
{
public OperationResult Operation()
{
// Randomize the success indicator
// This should be real logic
var randomNumber = Random.Shared.Next(100);
var success = randomNumber % 2 == 0;
// Return the operation result
return success
?
new() { Value = randomNumber }
: new()
{
ErrorMessage = $”Something went wrong with the number ‘{randomNumber}’.”,
Value = randomNumber,
};
}
}
With that in place, the consumer can use the Value property. In our case, the program displays it when the operation succeeds:
app.MapGet(
“/single-error-with-value”,
(OperationResult.SingleErrorWithValue.Executor executor) =>
{
var result = executor.Operation();
if (result.Succeeded)
{
// Handle the success
return $”Operation succeeded with a value of ‘{result.Value}’.”;
}
else
{
// Handle the failure
return result.ErrorMessage;
}
}
);
The preceding code displays the ErrorMessage property when the operation fails or uses the Value property when it succeeds. With this, the power of the Operation Result pattern continues to emerge.But we are not done yet, so let’s jump into the next evolution.