Simplistic libraries for complex projects. Halifax eliminates boilerplate in .NET API services — standardized responses, JWT auth, configuration, logging, and more — so you can focus on business logic.
- Standardized API responses — consistent
ApiResponse<T>wrapper for all endpoints - Exception handling — throw typed exceptions, get proper HTTP status codes automatically
- JWT authentication — configure auth in one line, create and validate tokens easily
- Environment configuration — load
.envfiles, map to strongly-typed classes/records - Input validation — fluent
Guardhelpers for common checks - Correlation IDs — automatic
X-Correlation-Idpropagation across services - HTTP client base class — typed
HttpClientwith automatic error mapping and resilience policies - OpenAPI + Scalar UI — Swagger docs with interactive API explorer out of the box
- Logging — Serilog-based structured logging
- CORS — configurable cross-origin policy
- Excel/CSV — import and export with column mapping
- Cryptography — AES-256 encrypt/decrypt helpers
- Short IDs — thread-safe random ID generation
- Nullable reference types — fully annotated across all packages
dotnet add package Halifax.Api
using Halifax.Api;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHalifax();
var app = builder.Build();
app.UseHalifax();
app.Run("https://*:5000");This gives you controller routing, exception handling, Swagger, Scalar UI, CORS, correlation IDs, and structured logging. Explore the Peggy's Cove sample project for a full working example.
All endpoints return a consistent format using ApiResponse:
// Return data
return ApiResponse.With(user);
// Return empty success
return ApiResponse.Empty;Response format:
{
"data": { ... },
"success": true,
"error": null
}On error:
{
"data": null,
"success": false,
"error": {
"type": "HalifaxNotFoundException",
"message": "User not found"
}
}[HttpGet]
public ApiResponse<Paging<UserDto>> GetUsers([FromQuery] PagingQuery query)
{
var items = db.Users.Skip(query.Skip).Take(query.Take).ToList();
var total = db.Users.Count();
return ApiResponse.With(new Paging<UserDto>(items, query.Skip, query.Take, total));
}PagingQuery binds Skip, Take, OrderBy, and OrderDirection from query string parameters.
Throw typed exceptions anywhere in your code — the middleware handles the HTTP response:
| Exception | HTTP Status |
|---|---|
HalifaxException |
400 Bad Request |
HalifaxNotFoundException |
404 Not Found |
HalifaxUnauthorizedException |
401 Unauthorized |
var user = await db.Users.FindAsync(id);
if (user == null)
throw new HalifaxNotFoundException("User not found");For advanced scenarios, override DefaultExceptionHandler or register your own IExceptionHandler.
Halifax loads environment variables from .env files automatically. Define a class or record matching your variable names:
AppSettings__ConnectionString=localhost
AppSettings__HttpTimeout=120record AppSettings(string ConnectionString, int HttpTimeout);Register during startup:
builder.Services.AddHalifax(h => h.AddSettings<AppSettings>());Settings are registered as singletons — inject them into controllers and services, or access them directly:
var settings = Env.GetSection<AppSettings>();Supported types: string, primitives, DateTime, TimeSpan, Guid, and their nullable variants.
Enable authentication with one call:
builder.Services.AddHalifax(h => h
.ConfigureAuthentication("your_jwt_secret_min_16_chars",
validateAudience: false,
validateIssuer: false,
requireExpirationTime: false));All non-[AllowAnonymous] endpoints now require Authorization: Bearer {token}.
var claims = new List<Claim>
{
new("sub", user.Id.ToString()),
new("email", user.Email),
new("role", user.Role)
};
var token = Jwt.Create("your_jwt_secret", claims, DateTime.UtcNow.AddDays(30));var principal = Jwt.Read("your_jwt_secret", token);Create custom authorization filters by extending ClaimsAuthorizeFilterAttribute:
class AdminOnly : ClaimsAuthorizeFilterAttribute
{
protected override bool IsAuthorized(ActionExecutingContext context, List<Claim> claims)
{
claims.ClaimExpected("role", "admin");
return true;
}
}
[HttpGet("admin")]
[AdminOnly]
public ApiResponse GetAdminData() => ApiResponse.With("secret");Available claim extensions: ClaimExpected, ClaimNotNullOrWhiteSpace, ClaimIsEmail, ClaimIsInt, ClaimIsDouble, ClaimIsEnum<T>, ClaimIsGuid, ClaimIsBoolean, ClaimIs<T> (for any IParsable<T> type).
Halifax automatically propagates correlation IDs across your services. The CorrelationIdMiddleware is enabled by default when you call UseHalifax():
- Reads the incoming
X-Correlation-Idheader, or generates a new GUID if absent - Sets
HttpContext.TraceIdentifierfor logging and downstream use - Adds
X-Correlation-Idto the response headers
For service-to-service calls, the CorrelationIdDelegatingHandler automatically forwards the correlation ID to outgoing HTTP requests made through HalifaxHttpClient.
Guard provides fluent validation that throws HalifaxException (400) on failure:
Guard.NotNullOrWhiteSpace(request.Name, nameof(request.Name));
Guard.Email(request.Email);
Guard.Length(request.Password, nameof(request.Password), lower: 8, upper: 64);
Guard.Range(request.Age, nameof(request.Age), from: 18, to: 120);
Guard.Url(request.Website, nameof(request.Website));
Guard.Ensure(request.AcceptedTerms, "Terms must be accepted");
Guard.NotNull(request.Address, nameof(request.Address));
Guard.NotEmptyList(request.Tags, nameof(request.Tags));
Guard.Color(request.Theme, nameof(request.Theme));Create typed HTTP clients for service-to-service communication by extending HalifaxHttpClient:
public class PaymentClient(HttpClient http) : HalifaxHttpClient(http)
{
public async Task<PaymentDto> GetAsync(string id)
{
var msg = CreateMessage(HttpMethod.Get, $"/api/payments/{id}");
return await SendAsync<PaymentDto>(msg);
}
public async Task<HttpStatusCode> CreateAsync(CreatePaymentRequest request)
{
var msg = CreateMessage(HttpMethod.Post, "/api/payments", request);
return await SendAsync(msg);
}
}Register with optional defaults:
services.AddHalifaxHttpClient<PaymentClient>(
defaultBaseUrl: "https://payments.api.com",
defaultBearerToken: token);Error responses (400, 401, 404) from downstream services are automatically mapped to the corresponding Halifax exceptions.
Add retry, circuit breaker, and timeout policies using the built-in resilience support:
// Standard resilience (retry + circuit breaker + timeout)
services.AddHalifaxHttpClientWithResilience<PaymentClient>(
defaultBaseUrl: "https://payments.api.com");
// Or get the IHttpClientBuilder for custom configuration
services.AddHalifaxHttpClientBuilder<PaymentClient>(
defaultBaseUrl: "https://payments.api.com",
defaultBearerToken: null,
configure: null)
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
});dotnet add package Halifax.Excel
var converter = new ExcelConverter<Person>();
converter.AddMapping("Full Name", p => p.Name);
converter.AddMapping("Age", p => p.Age);
// Write
using var stream = new MemoryStream();
await converter.WriteExcelAsync(stream, people, "Sheet1");
await converter.WriteCsvAsync(stream, people);
// Read (auto-detects format from content type)
var records = await converter.ReadAsync(fileStream, contentType);AES-256 encryption:
var encrypted = Crypto.Encrypt("secret", "sensitive data");
var decrypted = Crypto.Decrypt("secret", encrypted);
Crypto.TryDecrypt("secret", encrypted, out var result);Thread-safe random ID generation:
var id = ShortId.Create(); // e.g. "kX9mBnQ"
var id = ShortId.Create(length: 12); // longer ID
var id = ShortId.Create(useNumbers: false); // letters onlyPre-configured serialization (camelCase, case-insensitive, enums as strings, UTC dates):
var json = Json.Serialize(obj);
var obj = Json.Deserialize<MyType>(json);
Json.TryDeserialize<MyType>(json, out var result);DateTimeHelper.ValidateRange(from, to); // throws if from > to
DateTimeHelper.IsIn(from, to, pointInTime); // true if within range
DateTime? date = DateTime.UtcNow;
date.ToIsoFormat(); // "2024-03-15T10:30:00Z"Global structured logging via Serilog:
L.Info("User created", userId);
L.Warning("Timeout exceeded");
L.Error(exception, "Failed to process");Halifax exposes a built-in GET /halifax/version endpoint that returns the loaded Halifax assembly versions:
{
"data": [
{ "name": "Halifax.Api", "version": "5.2.1" },
{ "name": "Halifax.Core", "version": "5.1.1" }
],
"success": true
}builder.Services.AddHalifax(h => h
.SetName("My Service")
.AddSettings<AppSettings>()
.AddSettings<DatabaseSettings>()
.ConfigureAuthentication(jwtSecret, false, false, false)
.ConfigureCors(cors => cors
.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://myapp.com"))
.ConfigureOpenApi(swagger => { /* customize Swashbuckle */ })
.ConfigureJson(opts => { /* customize System.Text.Json */ }));| Package | Dependencies |
|---|---|
| Halifax.Domain | None |
| Halifax.Core | Halifax.Domain, Serilog, System.IdentityModel.Tokens.Jwt |
| Halifax.Api | Halifax.Core, Swashbuckle, Scalar, JwtBearer |
| Halifax.Http | Halifax.Core, Microsoft.Extensions.Http.Resilience |
| Halifax.Excel | CsvHelper, ExcelMapper, NPOI |
All packages target .NET 10 with nullable reference types enabled.
Copyright (c) 2020 Andrei M
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.