Halifax Service Foundation API library
$ dotnet add package Halifax.ApiSimplistic 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.
| Package | NuGet |
|---|---|
| Halifax.Api | |
| Halifax.Core | |
| Halifax.Domain | |
| Halifax.Http | |
| Halifax.Excel |
ApiResponse wrapper for all endpoints.env files, map to strongly-typed classes/recordsGuard helpers for common checksHttpClient with automatic error mappingdotnet 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, 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=120
record 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.
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.
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 only
Pre-configured serialization (camelCase, case-insensitive, enums as strings, UTC dates):
var json = Json.Serialize(obj);
var obj = Json.Deserialize<MyType>(json);
Global structured logging via Serilog:
L.Info("User created", userId);
L.Warning("Timeout exceeded");
L.Error(exception, "Failed to process");
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 | Purpose | Dependencies |
|---|---|---|
| Halifax.Domain | Response models, exceptions, pagination | None |
| Halifax.Core | JWT, config, validation, crypto, logging, JSON | Halifax.Domain, Serilog, System.IdentityModel.Tokens.Jwt |
| Halifax.Api | ASP.NET Core integration, middleware, Swagger | Halifax.Core, Swashbuckle, Scalar, JwtBearer |
| Halifax.Http | Typed HttpClient base class | Halifax.Core |
| Halifax.Excel | Excel/CSV import and export | CsvHelper, ExcelMapper, NPOI |
All packages target .NET 10.
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.