Xpandables.Net
9.0.0-rc.1
dotnet add package Xpandables.Net --version 9.0.0-rc.1
NuGet\Install-Package Xpandables.Net -Version 9.0.0-rc.1
<PackageReference Include="Xpandables.Net" Version="9.0.0-rc.1" />
paket add Xpandables.Net --version 9.0.0-rc.1
#r "nuget: Xpandables.Net, 9.0.0-rc.1"
// Install Xpandables.Net as a Cake Addin #addin nuget:?package=Xpandables.Net&version=9.0.0-rc.1&prerelease // Install Xpandables.Net as a Cake Tool #tool nuget:?package=Xpandables.Net&version=9.0.0-rc.1&prerelease
Introduction
Provides with useful interfaces contracts in .Net 9.0 and some implementations mostly following the spirit of SOLID principles, Commands... The library is strongly-typed, which means it should be hard to make invalid requests and it also makes it easy to discover available methods and properties though IntelliSense.
Feel free to fork this project, make your own changes and create a pull request.
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.
Getting Started
Optional
The Optional< T> is a C# implementation of an optional value that may or may not be present. This implementation is part of the Xpandables.Net library and is designed to work with .NET 9. The Optional< T> struct provides a way to represent optional values, similar to the Nullable< T> type but for reference types and value types alike.
Features
- Is a struct, immutable, a generic type, so it can hold a value of any type.
- Represents an optional value that may or may not be present.
- Provides a way to work with optional values in a functional way.
- Provides a way to create an optional value from a value or from an empty value.
- Supports JSON serialization through OptionalJsonConverterFactory.
- Implements IEnumerable< T> to allow iteration over the optional value.
Usage
Creating an Optional Value
You can create an Optional< T> value using helpers or implicit conversion.
using Xpandables.Net.Optionals;
// Creating an optional with a value
Optional<int> optionalWithValue = Optional.Some(42);
// Creating an empty optional
Optional<int> emptyOptional = Optional.Empty<int>();
Checking for Value Presence
You can check if the optional has a value using the IsEmpty or IsNotEmpty properties.
if (emptyOptional.IsEmpty)
{
Console.WriteLine("Optional is empty.");
}
if (optionalWithValue.IsNotEmpty)
{
Console.WriteLine("Optional is not empty.");
}
Accessing the Value
You can access the value of the Optional using the Value property. Note that accessing the value when it is not present will throw an InvalidOperationException.
try
{
int value = optionalWithValue.Value;
Console.WriteLine($"Value: {value}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
Iterating over the Value
Since Optional< T> implements IEnumerable< T>, you can use it in a foreach loop.
foreach (var value in optionalWithValue)
{
Console.WriteLine($"Value: {value}");
}
foreach (var value in emptyOptional)
{
Console.WriteLine($"Value: {value}"); // This will not execute
}
Using the Map Method
The Map method allows you to act on the value inside the optional if it is present.
Optional<int> optional = 42;
Optional<int> mappedOptional = optional.Map(value => value * 2);
if (mappedOptional.IsNotEmpty)
{
Console.WriteLine($"Mapped Value: {mappedOptional.Value}");
// Output: Mapped Value: 84
}
Using the Bind Method
The Bind method allows you to transform the value inside the optional to another type and return a new optional.
Optional<string> optional = Optional.Some("Hello Word");
Optional<int> boundOptional = optional.Bind(value => value.Length);
if (boundOptional.HasValue)
{
Console.WriteLine($"Bound Value: {boundOptional.Value}");
// Output: Bound Value: 10
}
Using the Empty Method
The Empty method allows you to provide a value if the current optional is empty.
public string GetName()
{
Optional<Name> optional = function call;
return optional
.Empty("No Name");
// If the optional has a value, the function value will be returned.
// Otherwise, the Empty value will be returned.
}
JSON Serialization
The Optional< T> struct is decorated with OptionalJsonConverterFactory to support JSON serialization.
using System.Text.Json;
var optional = Optional.Some(42);
string json = JsonSerializer.Serialize(optional);
Console.WriteLine(json); // Output: {"Value":42}
var deserializedOptional = JsonSerializer.Deserialize<Optional<int>>(json);
Console.WriteLine(deserializedOptional.IsNotEmpty); // True
Console.WriteLine($"Deserialized Value: {deserializedOptional.Value}");
// Output: Deserialized Value: 42
// anonymous type
var anonymous = Optional.Some(new { Name = "Hello World" });
string anonymousJson = JsonSerializer.Serialize(anonymous);
Console.WriteLine(anonymousJson); // Output: {"Name":"Hello World"}
var deserializedAnonymous = DeserializeAnonymousType(anonymousJson, anonymous);
// or you can use an anonymous instance
// var deserializedAnonymous = DeserializeAnonymousType(
// anonymousJson,
// Optional.Some(new { Name = string.Empty }));
Console.WriteLine($"Deserialized Anonymous Value: {deserializedAnonymous.Value.Name}");
// Output: Deserialized Anonymous Value: Name: Hello World
static T? DeserializeAnonymousType<T>(
string json, T _, JsonSerializerOptions? options = default) =>
JsonSerializer.Deserialize<T>(json, options);
Chaining Methods in a Fluent Manner
You can chain the methods of Optional< T> in a fluent manner to produce the expected result.
Optional<int> optional = Optional.Some(42);
Optional<string> result = optional
.Map(value => value * 2) // Double the value
.Bind(value => Optional.Some(value.ToString())) // Convert to string
.Empty(() => "Default Value"); // Provide a default value if empty
if (result.IsNotEmpty)
{
Console.WriteLine($"Result: {result.Value}"); // Output: Result: 84
}
else
{
Console.WriteLine("Result is empty.");
}
IOperationResult and OperationResults
Overview
The IOperationResult
and OperationResults
classes are part of the Xpandables.Net.Operations
namespace. They provide a structured way to handle the results of operations, encapsulating both success and failure scenarios with detailed information.
IOperationResult
The IOperationResult
interface represents the result of an operation. It includes properties for status code, title, detail, location, result, errors, headers, and extensions. It also provides methods to check if the operation was successful and to retrieve any associated exceptions.
OperationResults
The OperationResults
class provides static methods to create instances of IOperationResult
for both success and failure scenarios. It includes methods to set various HTTP status codes and to include additional details like titles, details, locations, and errors.
Usage
Creating a Success Operation Result
To create a success operation result, you can use the Success
method from the OperationResults
class. You can specify the status code, result, and other details.
using System.Net;
using Xpandables.Net.Operations;
public class SampleUsage
{
public IOperationResult CreateSuccessResult()
{
return OperationResults.Success(HttpStatusCode.OK)
.WithTitle("Operation Successful")
.WithDetail("The operation completed successfully.")
.WithLocation(new Uri("http://example.com"))
.Build();
}
public IOperationResult<string> CreateSuccessResultWithData()
{
return OperationResults.Success("Success Data", HttpStatusCode.OK)
.WithTitle("Operation Successful")
.WithDetail("The operation completed successfully with data.")
.WithLocation(new Uri("http://example.com"))
.Build();
}
}
Creating a Failure Operation Result
To create a failure operation result, you can use the Failure
method from the OperationResults
class. You can specify the status code, errors, and other details.
using System.Net;
using Xpandables.Net.Operations;
public class SampleUsage
{
public IOperationResult CreateFailureResult()
{
return OperationResults.Failure(HttpStatusCode.BadRequest)
.WithTitle("Operation Failed")
.WithDetail("The operation failed due to bad request.")
.WithError("ErrorKey", "ErrorMessage")
.Build();
}
public IOperationResult<string> CreateFailureResultWithData()
{
return OperationResults.Failure<string>(HttpStatusCode.BadRequest)
.WithTitle("Operation Failed")
.WithDetail("The operation failed due to bad request with data.")
.WithError("ErrorKey", "ErrorMessage")
.Build();
}
}
Using Predefined Methods
The OperationResults
class also provides predefined methods for common HTTP status codes like Ok
, Created
, NoContent
, NotFound
, BadRequest
, Conflict
, Unauthorized
, InternalServerError
, and ServiceUnavailable
.
using Xpandables.Net.Operations;
public class SampleUsage
{
public IOperationResult CreateOkResult()
{
return OperationResults.Ok()
.WithTitle("Operation Successful")
.WithDetail("The operation completed successfully.")
.Build();
}
public IOperationResult<string> CreateNotFoundResult()
{
return OperationResults.NotFound<string>()
.WithTitle("Resource Not Found")
.WithDetail("The requested resource was not found.")
.Build();
}
}
The IOperationResult
and OperationResults
classes provide a flexible and structured way to handle operation results in your application. By using these classes, you can ensure that your operations return consistent and detailed results, making it easier to handle both success and failure scenarios.
IHttpClientDispatcher and Related Classes
Overview
The IHttpClientDispatcher
interface and related classes in the Xpandables.Net.Http
namespace provide a structured way to handle HTTP client requests and responses. These classes and interfaces allow you to configure, send, and process HTTP requests with detailed options and builders.
IHttpClientDispatcher
The IHttpClientDispatcher
interface provides methods to handle HTTP client requests using a typed client HTTP client. It supports sending requests that do not return a response, requests that return a response of a specific type, and requests that return a stream that can be async-enumerated.
IHttpClientRequestOptionsBuilder
The IHttpClientRequestOptionsBuilder
interface defines a builder for creating HttpClientRequestOptionsAttribute
. This interface takes priority over the HttpClientRequestOptionsAttribute
.
HttpClientRequestOptionsAttribute
The HttpClientRequestOptionsAttribute
class is an attribute used to configure options for HTTP client requests. It should decorate implementations of IHttpClientRequest
, IHttpClientAsyncRequest<TResponse>
, or IHttpClientRequest<TResponse>
to be used with IHttpClientDispatcher
.
Usage
Creating and Sending a Simple Request
To create and send a simple request using IHttpClientDispatcher
, you can define a request class and decorate it with HttpClientRequestOptionsAttribute
.
using System.Net;
using Xpandables.Net.Http;
[HttpClientRequestOptions(Path = "/api/data",
Method = Method.GET)]
public class GetDataRequest : IHttpClientRequest { }
public class SampleUsage
{
private readonly IHttpClientDispatcher _dispatcher;
public SampleUsage(IHttpClientDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public async Task SendRequestAsync()
{
var request = new GetDataRequest();
var response = await _dispatcher.SendAsync(request);
if (response.IsValid)
{
Console.WriteLine("Request was successful.");
}
else
{
Console.WriteLine("Request failed.");
}
}
}
Creating and Sending a Request with a Response
To create and send a request that returns a response of a specific type, you can define a request class and a response class.
using System.Net;
using Xpandables.Net.Http;
[HttpClientRequestOptions(Path = "/api/data",
Method = Method.GET)]
public class GetDataRequest : IHttpClientRequest<string> { }
public class SampleUsage
{
private readonly IHttpClientDispatcher _dispatcher;
public SampleUsage(IHttpClientDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public async Task SendRequestWithResponseAsync()
{
var request = new GetDataRequest();
var response = await _dispatcher.SendAsync(request);
if (response.IsValid)
{
Console.WriteLine($"Response data: {response.Result}");
}
else
{
Console.WriteLine("Request failed.");
}
}
}
Using a Custom Request Options Builder
To use a custom request options builder, implement the IHttpClientRequestOptionsBuilder
interface in your request class.
using System.Net;
using Xpandables.Net.Http;
public class CustomRequestOptionsBuilder : IHttpClientRequestOptionsBuilder
{
public HttpClientRequestOptionsAttribute Build(HttpClientOptions options)
{
return new HttpClientRequestOptionsAttribute
{
Path = "/api/custom",
Method = Method.POST,
ContentType = "application/json"
};
}
}
public class CustomRequest : CustomRequestOptionsBuilder;
public class SampleUsage
{
private readonly IHttpClientDispatcher _dispatcher;
public SampleUsage(IHttpClientDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public async Task SendCustomRequestAsync()
{
var request = new CustomRequest();
var response = await _dispatcher.SendAsync(request);
if (response.IsValid)
{
Console.WriteLine("Custom request was successful.");
}
else
{
Console.WriteLine("Custom request failed.");
}
}
}
The IHttpClientDispatcher
interface and related classes provide a flexible and structured way to handle HTTP client requests and responses in your application. By using these classes, you can ensure that your HTTP operations are consistent and detailed, making it easier to handle various HTTP scenarios.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. |
-
net9.0
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0-rc.2.24473.5)
- Microsoft.Extensions.Http (>= 9.0.0-rc.2.24473.5)
- Microsoft.Extensions.Logging (>= 9.0.0-rc.2.24473.5)
- System.ComponentModel.Composition (>= 9.0.0-rc.2.24473.5)
- System.Linq.Async.Queryable (>= 6.0.1)
NuGet packages (12)
Showing the top 5 NuGet packages that depend on Xpandables.Net:
Package | Downloads |
---|---|
Xpandables.Net.DependencyInjection
A utility library in .Net7.0 to easily add dependency injection using the Xpandables.Net library |
|
Xpandables.Net.EntityFramework
A utility library in .Net9.0 to easily add patterns such as CQRS |
|
Xpandables.NetCore
A utility library in .Net5.0 to add AspNetCore behaviors to Xpandables.Net. |
|
Xpandables.Net.AspNetCore
A utility library in .Net9.0 that adds AspNetCore behaviors to Xpandables.Net |
|
Xpandables.Net.Windows
A utility library in .Net5.0 to extend Windows Forms and WPF behaviors. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
9.0.0-rc.1 | 43 | 10/26/2024 |
8.1.2 | 127 | 9/12/2024 |
8.0.8 | 128 | 6/21/2024 |
8.0.6 | 111 | 5/25/2024 |
8.0.5 | 114 | 5/18/2024 |
8.0.1 | 296 | 2/11/2024 |
8.0.0 | 480 | 12/3/2023 |
8.0.0-rc.2.1.1 | 91 | 11/12/2023 |
8.0.0-rc.2.1 | 75 | 11/6/2023 |
8.0.0-rc.2.0 | 75 | 11/5/2023 |
7.3.3 | 615 | 5/9/2023 |
7.1.4 | 733 | 2/26/2023 |
7.1.3 | 807 | 2/19/2023 |
7.0.0 | 858 | 11/9/2022 |
7.0.0-rc2.0.1 | 110 | 10/12/2022 |
7.0.0-rc1.0.0 | 143 | 9/26/2022 |
6.1.1 | 1,356 | 8/6/2022 |
6.0.9 | 1,407 | 7/9/2022 |
6.0.8 | 1,387 | 6/27/2022 |
6.0.4 | 1,405 | 3/15/2022 |
6.0.3 | 1,389 | 2/22/2022 |
6.0.2 | 1,029 | 1/4/2022 |
6.0.1 | 1,057 | 12/4/2021 |
6.0.0 | 1,211 | 11/8/2021 |
6.0.0-rc.4.3 | 157 | 11/3/2021 |
6.0.0-rc.3.1 | 160 | 10/15/2021 |
6.0.0-rc.3 | 154 | 10/14/2021 |
6.0.0-rc.2 | 147 | 9/21/2021 |
6.0.0-preview.5 | 155 | 8/26/2021 |
5.6.1 | 1,543 | 6/30/2021 |
5.6.0 | 1,551 | 6/9/2021 |
5.5.1 | 1,523 | 5/26/2021 |
5.4.4 | 1,146 | 4/12/2021 |
5.4.0 | 970 | 3/11/2021 |
5.3.14 | 913 | 3/2/2021 |
5.3.13 | 903 | 2/25/2021 |
5.3.12 | 872 | 2/21/2021 |
5.3.11 | 882 | 2/18/2021 |
5.3.10 | 850 | 2/18/2021 |
5.3.9 | 970 | 2/11/2021 |
5.3.8 | 972 | 2/10/2021 |
5.3.7 | 883 | 2/7/2021 |
5.3.6 | 688 | 2/7/2021 |
5.3.5 | 884 | 2/7/2021 |
5.3.4 | 901 | 2/7/2021 |
5.3.3 | 894 | 2/5/2021 |
5.3.2 | 925 | 2/2/2021 |
5.3.1 | 938 | 1/31/2021 |
5.3.0 | 905 | 1/31/2021 |
5.2.14 | 928 | 1/26/2021 |
5.2.13 | 876 | 1/25/2021 |
5.2.12 | 918 | 1/22/2021 |
5.2.11 | 929 | 1/19/2021 |
5.2.10 | 889 | 1/16/2021 |
5.2.9 | 923 | 1/13/2021 |
5.2.8 | 927 | 1/8/2021 |
5.2.7 | 923 | 1/6/2021 |
5.2.6 | 913 | 1/6/2021 |
5.2.5 | 946 | 12/17/2020 |
5.2.4 | 885 | 12/12/2020 |
5.2.3 | 948 | 12/8/2020 |
5.2.2 | 942 | 12/7/2020 |
5.2.1 | 959 | 12/7/2020 |
5.2.0 | 1,044 | 12/6/2020 |
5.1.1 | 1,058 | 12/6/2020 |
5.1.0 | 938 | 12/5/2020 |
5.0.6 | 967 | 12/5/2020 |
5.0.5 | 923 | 11/23/2020 |
5.0.4 | 1,072 | 11/22/2020 |
5.0.3 | 1,188 | 11/20/2020 |
5.0.2 | 1,062 | 11/19/2020 |
5.0.1 | 1,420 | 11/16/2020 |
5.0.0 | 1,334 | 11/12/2020 |
5.0.0-rc.2.1.4 | 268 | 11/6/2020 |
5.0.0-rc.2.1.3 | 268 | 11/1/2020 |
5.0.0-rc.2.1.2 | 240 | 10/31/2020 |
5.0.0-rc.2.1.0 | 296 | 10/24/2020 |
5.0.0-rc.2.0.2 | 201 | 10/22/2020 |
5.0.0-rc.2.0.1 | 242 | 10/17/2020 |
5.0.0-rc.2.0.0 | 239 | 10/17/2020 |
5.0.0-rc.1.1.5 | 310 | 10/11/2020 |
5.0.0-rc.1.1.4 | 300 | 10/11/2020 |
5.0.0-rc.1.1.3 | 351 | 10/10/2020 |
5.0.0-rc.1.1.2 | 229 | 10/4/2020 |
5.0.0-rc.1.1.1 | 257 | 10/2/2020 |
5.0.0-rc.1.1.0 | 223 | 10/1/2020 |
5.0.0-rc.1.0.9 | 282 | 9/29/2020 |
5.0.0-rc.1.0.8 | 212 | 9/28/2020 |
5.0.0-rc.1.0.7 | 253 | 9/28/2020 |
5.0.0-rc.1.0.6 | 308 | 9/26/2020 |
5.0.0-rc.1.0.5 | 262 | 9/25/2020 |
5.0.0-rc.1.0.4 | 222 | 9/24/2020 |
5.0.0-rc.1.0.3 | 287 | 9/23/2020 |
5.0.0-rc.1.0.2 | 246 | 9/23/2020 |
5.0.0-rc.1.0.1 | 230 | 9/21/2020 |
5.0.0-preview.2.0.0 | 242 | 8/16/2020 |
5.0.0-preview.1.0.8 | 235 | 8/10/2020 |
5.0.0-preview.1.0.7 | 295 | 8/10/2020 |
5.0.0-preview.1.0.6 | 298 | 8/5/2020 |
5.0.0-preview.1.0.5 | 285 | 8/3/2020 |
5.0.0-preview.1.0.4 | 247 | 8/3/2020 |
5.0.0-preview.1.0.3 | 300 | 8/2/2020 |
5.0.0-preview.1.0.2 | 333 | 8/2/2020 |
5.0.0-preview.1.0.1 | 350 | 8/2/2020 |
5.0.0-preview.1.0.0 | 391 | 7/25/2020 |
3.2.1 | 1,367 | 9/17/2020 |
3.2.0 | 1,385 | 9/15/2020 |
3.1.9 | 1,353 | 9/13/2020 |
3.1.8 | 1,445 | 9/13/2020 |
3.1.7 | 1,364 | 9/12/2020 |
3.1.6 | 1,536 | 9/5/2020 |
3.1.5 | 1,367 | 9/4/2020 |
3.1.4 | 1,477 | 8/29/2020 |
3.1.3 | 1,337 | 8/28/2020 |
3.1.2 | 1,359 | 8/27/2020 |
3.1.1 | 1,374 | 8/24/2020 |
3.1.0 | 1,402 | 8/19/2020 |
Optional