Code Quality Design Help

Extension Methods Reference

Extension methods enable fluent API composition, allowing complex workflows to be expressed as readable pipelines.

Transformation

public static class ResultExtensions { public static Result<TDest> Map<TSource, TDest>( this Result<TSource> source, TDest defaultResult) { try { return source is SuccessResult<TSource> successResult ? CreateSuccessResult( successResult, defaultResult) : ConvertNonSuccessTo<TSource, TDest>(source); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TDest>( source.SourceId, "Exception during Map operation", ex.Message, source.CallerMemberName); } } public static Result<TDest> Map<TSource, TDest>( this Result<TSource> source, Func<TSource, TDest> transform) { try { return source is SuccessResult<TSource> successResult ? CreateSuccessResult( successResult, transform(successResult.Value)) : ConvertNonSuccessTo<TSource, TDest>(source); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TDest>( source.SourceId, "Exception during Map operation", ex.Message, source.CallerMemberName); } } public static async Task<Result<TDest>> MapAsync< TSource, TDest>( this Task<Result<TSource>> sourceTask, Func<TSource, TDest> transform, [CallerMemberName] string callerMemberName = "") { try { var sourceResult = await sourceTask; return sourceResult.Map(transform); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TDest>( callerMemberName, "Exception in MapAsync operation", ex.Message, callerMemberName); } } private static SuccessResult<TDest> CreateSuccessResult<TSource, TDest>( Result<TSource> sourceResult, TDest transformedValue) { return new SuccessResult<TDest> { Value = transformedValue, SourceId = sourceResult.SourceId, StatusCode = sourceResult.StatusCode, CallerMemberName = sourceResult.CallerMemberName }; } private static Result<TDest> ConvertNonSuccessTo<TSource, TDest>( Result<TSource> source) { return source switch { ProblemDetailsResult<TSource> p => new ProblemDetailsResult<TDest> { SourceId = p.SourceId, StatusCode = p.StatusCode, ProblemDetails = p.ProblemDetails, CallerMemberName = p.CallerMemberName }, SuccessNullResult<TSource> sn => new SuccessNullResult<TDest> { SourceId = sn.SourceId, StatusCode = sn.StatusCode, CallerMemberName = sn.CallerMemberName }, EmptyContentResult ec when typeof(TSource) == typeof(EmptyContent) => typeof(TDest) == typeof(EmptyContent) ? (Result<TDest>)(object) new EmptyContentResult { SourceId = ec.SourceId, StatusCode = ec.StatusCode, CallerMemberName = ec.CallerMemberName } : throw new InvalidOperationException( $"Cannot convert EmptyContentResult " + $"to {typeof(Result<TDest>).Name}."), SuccessResult<TSource> => throw new InvalidOperationException( $"SuccessResult cannot be handled by " + $"{nameof(ConvertNonSuccessTo)}."), _ => throw new InvalidOperationException( $"Unknown non-success Result type: " + $"{source.GetType().Name}") }; } }

Transformation Example

// Transform User entity to UserDto var userResult = await GetUserAsync(id); var dtoResult = userResult.Map(user => new UserDto { Id = user.Id, FullName = $"{user.FirstName} {user.LastName}" });

Chaining

public static class ResultExtensions { public static Result<TDest> FlatMap<TSource, TDest>( this Result<TSource> source, Func<Result<TSource>, Result<TDest>> nextOperation, [CallerMemberName] string callerMemberName = "") { try { return nextOperation(source); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TDest>( callerMemberName, $"Exception in {nameof(FlatMap)} " + "operation", ex.Message, callerMemberName); } } public static async Task<Result<TDest>> FlatMapAsync<TSource, TDest>( this Task<Result<TSource>> sourceTask, Func<TSource, Task<Result<TDest>>> nextOperation, [CallerMemberName] string callerMemberName = "") { try { var sourceResult = await sourceTask; return sourceResult is SuccessResult<TSource> success ? await nextOperation(success.Value) : ConvertNonSuccessTo<TSource, TDest>( sourceResult); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TDest>( callerMemberName, $"Exception in {nameof(FlatMapAsync)} " + "operation", ex.Message, callerMemberName); } } public static Task<Result<TDest>> FlatMapAsync<TSource, TDest>( this Result<TSource> sourceResult, Func<TSource, Task<Result<TDest>>> nextOperation) { return Task.FromResult(sourceResult) .FlatMapAsync(nextOperation); } }

FlatMap is similar to Map, but is used when the transformation function itself returns a Result<T>. This prevents nested Result<Result<T>> structures, keeping the pipeline flat and composable.

Chaining Example

// Chain async operations: get user, then get their orders // GetOrdersForUserAsync returns Result<Orders>, not Orders var ordersResult = await GetUserAsync(userId) .FlatMapAsync(user => GetOrdersForUserAsync(user.Id)); // Without FlatMap, this would create Result<Result<Orders>>

Validation

public static class ResultExtensions { public static Result<T> RejectIf<T>( this Result<T> source, Func<T, bool> when, Func<Result<T>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { if (source is SuccessResult<T> success) { return when(success.Value) ? errorFactory() : success; } return source; } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, $"Exception in {nameof(RejectIf)} " + "operation", ex.Message, callerMemberName); } } public static async Task<Result<T>> RejectIfAsync<T>( this Task<Result<T>> sourceTask, Func<T, bool> when, Func<Result<T>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { var source = await sourceTask; return source.RejectIf( when, errorFactory, callerMemberName); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, $"Exception in {nameof(RejectIfAsync)} " + "operation", ex.Message, callerMemberName); } } public static Result<T> Ensure<T>( this Result<T> source, Func<T, bool> condition, Func<Result<T>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { if (source is SuccessResult<T> success) { return condition(success.Value) ? success : errorFactory(); } return source; } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, $"Exception in {nameof(Ensure)} " + "operation", ex.Message, callerMemberName); } } public static async Task<Result<T>> EnsureAsync<T>( this Task<Result<T>> sourceTask, Func<T, bool> condition, Func<Result<T>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { var source = await sourceTask; return source.Ensure( condition, errorFactory, callerMemberName); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, $"Exception in {nameof(EnsureAsync)} " + "operation", ex.Message, callerMemberName); } } }

Validation Example

// Ensure age is valid, reject if negative var result = userResult .Ensure( user => user.Age >= 0, () => ResultFactory.Problem<User>( "UserService", 400, ("Age", "Age cannot be negative"))) .RejectIf( user => user.Age > 150, () => ResultFactory.Problem<User>( "UserService", 400, ("Age", "Age seems unrealistic")));

Side Effects

public static class ResultExtensions { public static Result<T> OnAny<T>( this Result<T> sourceResult, Action<Result<T>> onAnyAction, [CallerMemberName] string callerMemberName = "") { try { onAnyAction(sourceResult); return sourceResult; } catch (Exception sideEffectEx) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, "Exception in OnAny side-effect " + "operation", sideEffectEx.Message, callerMemberName); } } public static async Task<Result<T>> OnAnyAsync<T>( this Task<Result<T>> sourceTask, Action<Result<T>> onAnyAction, [CallerMemberName] string callerMemberName = "") { try { var result = await sourceTask; onAnyAction(result); return result; } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, "Exception in OnAnyAsync operation", ex.Message, callerMemberName); } } public static Result<T> OnSuccess<T>( this Result<T> sourceResult, Action<T> onSuccessAction) { return sourceResult.OnAny(result => { if (result is SuccessResult<T> success) { onSuccessAction(success.Value); } }); } public static Task<Result<T>> OnSuccessAsync<T>( this Task<Result<T>> sourceTask, Action<T> onSuccessAction) { return sourceTask.OnAnyAsync(result => { if (result is SuccessResult<T> success) { onSuccessAction(success.Value); } }); } public static Task<Result<T>> OnErrorAsync<T>( this Task<Result<T>> sourceTask, Action<ProblemDetails, HttpStatusCode> onErrorAction) { return sourceTask.OnAnyAsync(result => { if (result is ProblemDetailsResult<T> problem) { onErrorAction( problem.ProblemDetails, problem.StatusCode); } }); } }

Side Effects Example

// Log success or error for monitoring var result = await ProcessOrderAsync(orderId) .OnSuccessAsync(order => _logger.LogInformation( "Order {OrderId} processed", order.Id)) .OnErrorAsync((problem, status) => _logger.LogError( "Order processing failed: {Detail}", problem.Detail));

Combination

public static class ResultExtensions { public static Result<TResult> CombineWith<T1, T2, TResult>( this Result<T1> first, Result<T2> second, Func<T1, T2, TResult> selector, [CallerMemberName] string callerMemberName = "") { try { if (first is not SuccessResult<T1> firstSuccess) return first.ConvertErrorResult< T1, TResult>(); if (second is not SuccessResult<T2> secondSuccess) return second.ConvertErrorResult< T2, TResult>(); var combinedValue = selector( firstSuccess.Value, secondSuccess.Value); return new SuccessResult<TResult> { SourceId = firstSuccess.SourceId, StatusCode = HttpStatusCode.OK, Value = combinedValue }; } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TResult>( callerMemberName, $"Exception in {nameof(CombineWith)} " + "operation", ex.Message, callerMemberName); } } public static async Task<Result<TResult>> CombineWithAsync<T1, T2, TResult>( this Task<Result<T1>> firstTask, Task<Result<T2>> secondTask, Func<T1, T2, TResult> selector, [CallerMemberName] string callerMemberName = "") { try { var first = await firstTask; var second = await secondTask; return first.CombineWith( second, selector, callerMemberName); } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<TResult>( callerMemberName, $"Exception in " + $"{nameof(CombineWithAsync)} " + "operation", ex.Message, callerMemberName); } } public static Result<TDest> ConvertErrorResult<TSource, TDest>( this Result<TSource> source) { return source switch { SuccessResult<TSource> or SuccessResult<TSource> => throw new InvalidOperationException( $"Cannot handle SuccessResult in " + $"{nameof(ConvertErrorResult)} method"), ProblemDetailsResult<TSource> result => new ProblemDetailsResult<TDest> { SourceId = result.SourceId, StatusCode = result.StatusCode, ProblemDetails = result.ProblemDetails, CallerMemberName = result.CallerMemberName }, _ => throw new InvalidOperationException( $"Unknown non-ProblemDetails " + $"{nameof(Result<TSource>)} type for " + $"{nameof(ConvertErrorResult)}: " + $"{source.GetType().Name}") }; } }

Combination Example

// Combine user and address results into a complete profile var profileResult = await GetUserAsync(userId) .CombineWithAsync( GetAddressAsync(userId), (user, address) => new UserProfile { Name = user.Name, Street = address.Street, City = address.City });

Error Recovery

public static class ResultExtensions { public static async Task<Result<T>> IfErrorAsync<T>( this Task<Result<T>> sourceTask, Func<ProblemDetails, HttpStatusCode, bool> when, Func<ProblemDetails, HttpStatusCode, Task<Result<T>>> then, [CallerMemberName] string callerMemberName = "") { try { var sourceResult = await sourceTask; return sourceResult is ProblemDetailsResult<T> problem && when(problem.ProblemDetails, problem.StatusCode) ? await then( problem.ProblemDetails, problem.StatusCode) : sourceResult; } catch (Exception ex) { return ResultFactory.Common .InternalServerErrorWithTitle<T>( callerMemberName, "Exception in OrElseAsync operation", ex.Message, callerMemberName); } } public static Result<TResult> TryOperation<T, TResult>( this Result<T> source, Func<T, TResult> operation, Func<Exception, Result<TResult>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { if (source is not SuccessResult<T> success) return source.ConvertErrorResult< T, TResult>(); var result = operation(success.Value); return new SuccessResult<TResult> { SourceId = success.SourceId, StatusCode = HttpStatusCode.OK, Value = result }; } catch (Exception ex) { return errorFactory(ex); } } public static async Task<Result<TResult>> TryOperationAsync<T, TResult>( this Task<Result<T>> sourceTask, Func<T, TResult> operation, Func<Exception, Result<TResult>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { var source = await sourceTask; return source.TryOperation( operation, errorFactory, callerMemberName); } catch (Exception ex) { return errorFactory(ex); } } public static async Task<Result<TResult>> TryOperationAsync<T, TResult>( this Task<Result<T>> sourceTask, Func<T, Task<TResult>> operation, Func<Exception, Result<TResult>> errorFactory, [CallerMemberName] string callerMemberName = "") { try { var source = await sourceTask; if (source is not SuccessResult<T> success) return source.ConvertErrorResult< T, TResult>(); var result = await operation(success.Value); return new SuccessResult<TResult> { SourceId = success.SourceId, StatusCode = HttpStatusCode.OK, Value = result }; } catch (Exception ex) { return errorFactory(ex); } } }

Error Recovery Example

// Retry on 503 Service Unavailable var result = await CallExternalServiceAsync() .IfErrorAsync( when: (problem, status) => status == HttpStatusCode.ServiceUnavailable, then: (problem, status) => Task.Delay(1000) .ContinueWith(_ => CallExternalServiceAsync()));

See Also

10 November 2025