S3Lab.Box.Auth
1.2.0
dotnet add package S3Lab.Box.Auth --version 1.2.0
NuGet\Install-Package S3Lab.Box.Auth -Version 1.2.0
<PackageReference Include="S3Lab.Box.Auth" Version="1.2.0" />
<PackageVersion Include="S3Lab.Box.Auth" Version="1.2.0" />
<PackageReference Include="S3Lab.Box.Auth" />
paket add S3Lab.Box.Auth --version 1.2.0
#r "nuget: S3Lab.Box.Auth, 1.2.0"
#:package S3Lab.Box.Auth@1.2.0
#addin nuget:?package=S3Lab.Box.Auth&version=1.2.0
#tool nuget:?package=S3Lab.Box.Auth&version=1.2.0
S3Lab.Box
Решение для ASP.NET Core приложений, включающее библиотеки для аутентификации, работы с данными и обработки результатов.
Проекты в решении
S3Lab.Box.Result
Библиотека для обработки результатов операций в стиле Railway Oriented Programming.
Описание
S3Lab.Box.Result предоставляет минималистичную реализацию паттерна Result/Result<T> для обработки успешных и неуспешных операций. Библиотека идеально подходит для CQRS архитектуры и функционального программирования.
Возможности
- ✅ Простой и понятный API для обработки результатов
- 🚫 Типобезопасная обработка ошибок
- 🔄 Поддержка Railway Oriented Programming
- ⚡ Минимальные накладные расходы
- 🛠️ Расширения для удобной работы с результатами
Установка
dotnet add package S3Lab.Box.Result
Быстрый старт
using S3Lab.Box.Result;
// Успешный результат
var successResult = Result<int>.Success(42);
// Неуспешный результат
var error = new Error("Validation failed", "INVALID_INPUT");
var failureResult = Result<int>.Failure(error);
// Обработка результатов
var result = await SomeOperationAsync();
if (result.IsSuccess)
{
Console.WriteLine($"Success: {result.Value}");
}
else
{
Console.WriteLine($"Error: {result.Error.Message}");
}
// Использование с расширениями
var mappedResult = result
.Map(value => value * 2)
.OnSuccess(value => Console.WriteLine($"Doubled: {value}"))
.OnFailure(error => Console.WriteLine($"Failed: {error.Message}"));
Основные типы
Result<T> - результат операции с типизированным значением:
public class Result<T>
{
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public T Value { get; } // Доступно только при IsSuccess
public Error Error { get; } // Доступно только при IsFailure
public static Result<T> Success(T value);
public static Result<T> Failure(Error error);
}
Error - информация об ошибке:
public record Error(string Message, string Code);
Расширения
Библиотека предоставляет набор расширений для удобной работы:
// Map - преобразование значения
var result = Result<int>.Success(42);
var stringResult = result.Map(x => x.ToString()); // Result<string>
// Bind - цепочка операций
var result = await GetUserAsync(1)
.Bind(user => GetUserOrdersAsync(user.Id))
.Bind(orders => CalculateTotalAsync(orders));
// OnSuccess/OnFailure - побочные эффекты
var result = await SaveUserAsync(user)
.OnSuccess(savedUser => LogSuccess(savedUser))
.OnFailure(error => LogError(error));
S3Lab.Box.Auth
Библиотека для аутентификации ASP.NET Core приложений с использованием API ключей.
Описание
S3Lab.Box.Auth предоставляет простое и эффективное решение для защиты API endpoints с помощью API ключей. Библиотека включает в себя middleware для проверки заголовка X-API-Key
и расширения для удобной интеграции в ASP.NET Core приложения.
Возможности
- 🔐 Аутентификация по API ключу через заголовок
X-API-Key
- 🚫 Автоматическое отклонение запросов без валидного ключа
- 🔓 Исключения для определенных маршрутов (Scalar, OpenAPI)
- ⚡ Поддержка preflight запросов (OPTIONS)
- 🛠️ Простая интеграция через extension методы
Установка
dotnet add package S3Lab.Box.Auth
Быстрый старт
- Настройка конфигурации
Добавьте API ключ в ваш appsettings.json
:
{
"ApiKey": "your-secret-api-key-here"
}
- Регистрация сервисов
В Program.cs
или Startup.cs
:
using S3Lab.Box.Auth.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Добавление сервисов
builder.Services.AddApplicationReferences(builder.Configuration);
var app = builder.Build();
// Регистрация middleware
app.UseApiKeyMiddleware();
app.Run();
- Использование
После настройки все запросы к вашему API должны включать заголовок X-API-Key
с валидным ключом:
curl -H "X-API-Key: your-secret-api-key-here" https://your-api.com/endpoint
Исключения
Middleware автоматически пропускает следующие типы запросов:
- OPTIONS запросы (preflight для CORS)
- Scalar endpoints (
/scalar/*
) - OpenAPI endpoints (
/openApi/*
)
Конфигурация
API ключ должен быть указан в конфигурации приложения:
{
"ApiKey": "your-secret-api-key-here"
}
Для production окружения рекомендуется использовать переменные окружения:
export ApiKey="your-production-api-key"
Обработка ошибок
При отсутствии или неверном API ключе middleware возвращает:
- HTTP Status Code: 401 Unauthorized
- Response Body: "Unauthorized: Invalid or missing X-API-Key"
Безопасность
- API ключ должен быть достаточно длинным и случайным
- Используйте HTTPS в production окружении
- Не храните API ключи в исходном коде
- Регулярно ротируйте API ключи
S3Lab.Box.Data
Библиотека для работы с данными в ASP.NET Core приложениях, предоставляющая базовые сущности, интерфейсы и репозиторий для Entity Framework Core.
Описание
S3Lab.Box.Data предоставляет набор базовых классов и интерфейсов для упрощения работы с данными в .NET приложениях. Библиотека включает в себя готовые сущности с аудитом и архивированием, универсальный репозиторий и расширения для Entity Framework Core.
Возможности
- 🏗️ Базовые сущности с поддержкой различных типов ключей
- 📝 Аудит сущностей (создание, изменение, пользователи)
- 🗄️ Архивирование сущностей (мягкое удаление)
- 📚 Универсальный репозиторий с полным набором CRUD операций
- 🔍 Поддержка Include для связанных сущностей
- ⚡ Асинхронные операции с поддержкой CancellationToken
- 🛠️ Расширения для Entity Framework Core
- 🔄 Интеграция с S3Lab.Box.Result для обработки результатов
Установка
dotnet add package S3Lab.Box.Data
Требования
- .NET 9.0 или выше
- Entity Framework Core 9.0.6 или выше
Структура библиотеки
Сущности (Entities)
Entity<TKey>
Базовая сущность с идентификатором:
public abstract class Entity<TKey> : IEntity<TKey>, IEntity
{
public required TKey Id { get; set; }
}
AuditableEntity<TKey, TUserKey>
Сущность с аудитом (отслеживание создания и изменения):
public abstract class AuditableEntity<TKey, TUserKey> : Entity<TKey>, IAuditableEntity<TUserKey>
{
public TUserKey CreatedBy { get; set; }
public TUserKey UpdatedBy { get; set; }
public DateTime Created { get; set; }
public DateTime? Updated { get; set; }
}
ArchivableEntity<TKey, TUserKey>
Архивируемая сущность с мягким удалением:
public class ArchivableEntity<TKey, TUserKey> : AuditableEntity<TKey, TUserKey>, IArchivableEntity
{
public bool IsDeleted { get; set; }
}
DictionaryEntity<TKey>
Справочная сущность с именем:
public abstract class DictionaryEntity<TKey> : IDictionaryEntity<TKey>
{
public TKey Id { get; set; }
public required string Name { get; set; }
}
Интерфейсы (Interfaces)
IEntity<TKey>
Базовый интерфейс для сущностей с идентификатором:
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
IAuditableEntity<TUserKey>
Интерфейс для аудируемых сущностей:
public interface IAuditableEntity<TUserKey>
{
TUserKey CreatedBy { get; set; }
TUserKey UpdatedBy { get; set; }
DateTime Created { get; set; }
DateTime? Updated { get; set; }
}
IArchivableEntity
Интерфейс для архивируемых сущностей:
public interface IArchivableEntity
{
bool IsDeleted { get; set; }
}
IRepository<TEntity, TKey>
Универсальный интерфейс репозитория с полным набором CRUD операций:
public interface IRepository<TEntity, TKey>
{
// Получение по ID
Task<TEntity?> GetByIdAsync(TKey id);
Task<TEntity?> GetByIdAsync(TKey id, IEnumerable<string> includePaths);
Task<TEntity?> GetByIdAsync(TKey id, bool asNoTracking);
// Получение списка по ID
Task<List<TEntity>> GetByIdListAsync(IEnumerable<TKey> idList);
// Добавление
Task<TEntity> AddAsync(TEntity entity);
Task AddRangeAsync(IEnumerable<TEntity> entities);
// Обновление
Task UpdateAsync(TEntity entity);
// Удаление
Task DeleteAsync(TEntity entity);
Task DeleteRangeAsync(IEnumerable<TEntity> entities);
// Сохранение
Task SaveAsync();
// Проверка существования
Task<bool> IsExistAsync(TKey id);
}
Репозиторий (Repository)
Repository<TEntity, TKey>
Универсальная реализация репозитория с поддержкой:
- Асинхронных операций
- CancellationToken
- Include для связанных сущностей
- AsNoTracking для оптимизации производительности
- Пакетных операций
UnitOfWork
Реализация паттерна Unit of Work для управления транзакциями:
public interface IUnitOfWork : IDisposable
{
IRepository<TEntity, TKey> Repository<TEntity, TKey>();
Task<int> SaveChangesAsync();
Task<IDbContextTransaction> BeginTransactionAsync();
Task<T> ExecuteInTransactionAsync<T>(Func<Task<T>> operation);
}
Возможности UnitOfWork:
- 🏗️ Централизованное управление репозиториями
- 💾 Автоматическое управление транзакциями
- 🔄 Поддержка отката изменений при ошибках
- ⚡ Кэширование репозиториев для оптимизации
- 🛡️ Безопасное освобождение ресурсов
Расширения (Extensions)
QueryableExtensions
Расширения для работы с IQueryable:
public static IQueryable<TEntity> AddIncludes<TEntity, TKey>(
this IQueryable<TEntity> entities,
IEnumerable<string>? includePaths)
ServiceCollectionExtensions
Расширения для регистрации сервисов в DI:
// Регистрация UnitOfWork
services.AddS3LabBoxData();
// Регистрация конкретного репозитория
services.AddRepository<User, int, UserRepository>();
// Регистрация базового репозитория
services.AddRepository<Product, int>();
ResultExtensions
Расширения для работы с результатами операций:
public static class ResultExtensions
{
public static Result<TResult> Map<T, TResult>(this Result<T> result, Func<T, TResult> mapper);
public static Result<TResult> Bind<T, TResult>(this Result<T> result, Func<T, Result<TResult>> binder);
public static Result<T> OnSuccess<T>(this Result<T> result, Action<T> action);
public static Result<T> OnFailure<T>(this Result<T> result, Action<Error> action);
}
Быстрый старт
1. Создание сущности
public class User : AuditableEntity<int, int>
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
public class Category : DictionaryEntity<int>
{
public Category(int id, string name) : base(id, name)
{
}
}
2. Создание репозитория
public class UserRepository : Repository<User, int>
{
public UserRepository(DbContext context) : base(context)
{
}
// Дополнительные методы специфичные для User
public async Task<User?> GetByEmailAsync(string email)
{
return await DbSet.FirstOrDefaultAsync(u => u.Email == email);
}
}
3. Регистрация в DI
// Program.cs
builder.Services.AddScoped<UserRepository>();
builder.Services.AddScoped<IRepository<User, int>, UserRepository>();
4. Использование с Result
public class UserService
{
private readonly IRepository<User, int> _userRepository;
public UserService(IRepository<User, int> userRepository)
{
_userRepository = userRepository;
}
public async Task<Result<User>> CreateUserAsync(User user)
{
try
{
user.Created = DateTime.UtcNow;
user.CreatedBy = 1; // ID текущего пользователя
var createdUser = await _userRepository.AddAsync(user);
return Result<User>.Success(createdUser);
}
catch (Exception ex)
{
var error = new Error($"Failed to create user: {ex.Message}", "USER_CREATION_FAILED");
return Result<User>.Failure(error);
}
}
public async Task<Result<User?>> GetUserWithDetailsAsync(int id)
{
try
{
var user = await _userRepository.GetByIdAsync(id, new[] { "Orders", "Profile" });
return Result<User?>.Success(user);
}
catch (Exception ex)
{
var error = new Error($"Failed to get user: {ex.Message}", "USER_RETRIEVAL_FAILED");
return Result<User?>.Failure(error);
}
}
public async Task<Result<List<User>>> GetUsersByIdsAsync(IEnumerable<int> ids)
{
try
{
var users = await _userRepository.GetByIdListAsync(ids);
return Result<List<User>>.Success(users);
}
catch (Exception ex)
{
var error = new Error($"Failed to get users: {ex.Message}", "USERS_RETRIEVAL_FAILED");
return Result<List<User>>.Failure(error);
}
}
}
5. Использование UnitOfWork с Result
public class OrderService
{
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<Result<Order>> CreateOrderWithItemsAsync(Order order, List<OrderItem> items)
{
try
{
// Использование транзакции для атомарной операции
var result = await _unitOfWork.ExecuteInTransactionAsync(async () =>
{
var orderRepo = _unitOfWork.Repository<Order, int>();
var itemRepo = _unitOfWork.Repository<OrderItem, int>();
// Создаем заказ
var createdOrder = await orderRepo.AddAsync(order);
// Добавляем товары к заказу
foreach (var item in items)
{
item.OrderId = createdOrder.Id;
await itemRepo.AddAsync(item);
}
// Сохраняем все изменения
await _unitOfWork.SaveChangesAsync();
return createdOrder;
});
return Result<Order>.Success(result);
}
catch (Exception ex)
{
var error = new Error($"Failed to create order: {ex.Message}", "ORDER_CREATION_FAILED");
return Result<Order>.Failure(error);
}
}
public async Task<Result<bool>> TransferBetweenOrdersAsync(int fromOrderId, int toOrderId, int itemId)
{
try
{
using var transaction = await _unitOfWork.BeginTransactionAsync();
var itemRepo = _unitOfWork.Repository<OrderItem, int>();
// Получаем товар
var item = await itemRepo.GetByIdAsync(itemId);
if (item == null)
{
var error = new Error("Item not found", "ITEM_NOT_FOUND");
return Result<bool>.Failure(error);
}
// Обновляем заказ
item.OrderId = toOrderId;
await itemRepo.UpdateAsync(item);
// Сохраняем изменения
await _unitOfWork.SaveChangesAsync();
// Подтверждаем транзакцию
await transaction.CommitAsync();
return Result<bool>.Success(true);
}
catch (Exception ex)
{
// Транзакция автоматически откатится при исключении
var error = new Error($"Failed to transfer item: {ex.Message}", "ITEM_TRANSFER_FAILED");
return Result<bool>.Failure(error);
}
}
}
Примеры использования
Работа с аудируемыми сущностями
public class Product : AuditableEntity<int, int>
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
// Создание
var product = new Product
{
Name = "Sample Product",
Price = 99.99m,
Created = DateTime.UtcNow,
CreatedBy = currentUserId
};
await _repository.AddAsync(product);
// Обновление
product.Price = 89.99m;
product.Updated = DateTime.UtcNow;
product.UpdatedBy = currentUserId;
await _repository.UpdateAsync(product);
Работа с архивируемыми сущностями
public class Order : ArchivableEntity<int, int>
{
public string OrderNumber { get; set; } = string.Empty;
public decimal Total { get; set; }
}
// Мягкое удаление
var order = await _repository.GetByIdAsync(orderId);
if (order != null)
{
order.IsDeleted = true;
order.Updated = DateTime.UtcNow;
order.UpdatedBy = currentUserId;
await _repository.UpdateAsync(order);
}
Использование Include
// Получение пользователя с заказами и профилем
var user = await _userRepository.GetByIdAsync(
userId,
new[] { "Orders", "Profile", "Orders.Items" }
);
// Получение без отслеживания изменений
var user = await _userRepository.GetByIdAsync(
userId,
new[] { "Orders" },
asNoTracking: true
);
Пакетные операции
// Добавление нескольких сущностей
var users = new List<User>
{
new User { Name = "User1", Email = "user1@example.com" },
new User { Name = "User2", Email = "user2@example.com" }
};
await _userRepository.AddRangeAsync(users);
await _userRepository.SaveAsync();
// Удаление нескольких сущностей
var usersToDelete = await _userRepository.GetByIdListAsync(userIds);
await _userRepository.DeleteRangeAsync(usersToDelete);
await _userRepository.SaveAsync();
Работа с результатами операций
// Цепочка операций с обработкой ошибок
var result = await userService.CreateUserAsync(user)
.Bind(createdUser => orderService.CreateOrderAsync(createdUser.Id, order))
.Map(order => $"Order {order.Id} created successfully")
.OnSuccess(message => Console.WriteLine(message))
.OnFailure(error => Console.WriteLine($"Error: {error.Message}"));
// Обработка результатов в контроллере
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest request)
{
var user = new User { Name = request.Name, Email = request.Email };
var result = await _userService.CreateUserAsync(user);
return result.Match(
success => Ok(success),
error => BadRequest(new { error = error.Message, code = error.Code })
);
}
Конфигурация Entity Framework
DbContext
public class ApplicationDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Конфигурация для аудируемых сущностей
modelBuilder.Entity<User>()
.Property(u => u.Created)
.HasDefaultValueSql("GETUTCDATE()");
// Конфигурация для архивируемых сущностей
modelBuilder.Entity<Order>()
.HasQueryFilter(o => !o.IsDeleted);
}
}
Производительность
Рекомендации
Используйте AsNoTracking для операций только чтения:
var users = await _repository.GetByIdAsync(id, asNoTracking: true);
Применяйте Include только когда необходимо:
// Только при необходимости связанных данных var user = await _repository.GetByIdAsync(id, new[] { "Orders" });
Используйте пакетные операции для множественных изменений:
await _repository.AddRangeAsync(entities); await _repository.SaveAsync(); // Одно сохранение для всех
Применяйте CancellationToken для длительных операций:
var users = await _repository.GetByIdListAsync(ids, cancellationToken);
Используйте Result для обработки ошибок:
var result = await _repository.GetByIdAsync(id); return result.Match( success => Ok(success), error => NotFound(error.Message) );
Зависимости
S3Lab.Box.Auth
- Microsoft.Extensions.Configuration 9.0.6
- Microsoft.Extensions.DependencyInjection 9.0.6
- Scalar.AspNetCore 2.5.3
S3Lab.Box.Data
- Microsoft.EntityFrameworkCore 9.0.6
- S3Lab.Box.Result 2.0.0
S3Lab.Box.Result
- Нет внешних зависимостей
Лицензия
MIT License
Автор
Sazonov Andrei
Версия
2.0.0
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.Extensions.Configuration (>= 9.0.6)
- Microsoft.Extensions.DependencyInjection (>= 9.0.6)
- Scalar.AspNetCore (>= 2.5.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.