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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="S3Lab.Box.Auth" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="S3Lab.Box.Auth" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="S3Lab.Box.Auth" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add S3Lab.Box.Auth --version 1.2.0
                    
#r "nuget: S3Lab.Box.Auth, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package S3Lab.Box.Auth@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=S3Lab.Box.Auth&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=S3Lab.Box.Auth&version=1.2.0
                    
Install as a Cake Tool

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
Быстрый старт
  1. Настройка конфигурации

Добавьте API ключ в ваш appsettings.json:

{
  "ApiKey": "your-secret-api-key-here"
}
  1. Регистрация сервисов

В 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();
  1. Использование

После настройки все запросы к вашему 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);
    }
}

Производительность

Рекомендации

  1. Используйте AsNoTracking для операций только чтения:

    var users = await _repository.GetByIdAsync(id, asNoTracking: true);
    
  2. Применяйте Include только когда необходимо:

    // Только при необходимости связанных данных
    var user = await _repository.GetByIdAsync(id, new[] { "Orders" });
    
  3. Используйте пакетные операции для множественных изменений:

    await _repository.AddRangeAsync(entities);
    await _repository.SaveAsync(); // Одно сохранение для всех
    
  4. Применяйте CancellationToken для длительных операций:

    var users = await _repository.GetByIdListAsync(ids, cancellationToken);
    
  5. Используйте 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 66 6/27/2025
1.1.0 65 6/27/2025