Pr.BuildingBlocks.Cms.Core
5.0.1
dotnet add package Pr.BuildingBlocks.Cms.Core --version 5.0.1
NuGet\Install-Package Pr.BuildingBlocks.Cms.Core -Version 5.0.1
<PackageReference Include="Pr.BuildingBlocks.Cms.Core" Version="5.0.1" />
<PackageVersion Include="Pr.BuildingBlocks.Cms.Core" Version="5.0.1" />
<PackageReference Include="Pr.BuildingBlocks.Cms.Core" />
paket add Pr.BuildingBlocks.Cms.Core --version 5.0.1
#r "nuget: Pr.BuildingBlocks.Cms.Core, 5.0.1"
#:package Pr.BuildingBlocks.Cms.Core@5.0.1
#addin nuget:?package=Pr.BuildingBlocks.Cms.Core&version=5.0.1
#tool nuget:?package=Pr.BuildingBlocks.Cms.Core&version=5.0.1
Pr.BuildingBlocks.Cms.Core
Biblioteka building blocków warstwy domeny dla mikroserwisów CMS Polskiego Radia. Zawiera podstawowe abstrakcje, kontrakty i klasy bazowe — bez zależności od ASP.NET Core, EF Core, Wolverine ani konkretnej infrastruktury. Pakiet startowy każdego mikroserwisu CMS.
- Target framework:
net8.0 - PackageId:
Pr.BuildingBlocks.Cms.Core - Wersja:
5.0.1
Spis treści
- Instalacja
- Typy domenowe
- Zdarzenia domenowe
- Komendy
- Wyjątki domenowe
- Guard clauses
- Czas
- Slugi
- DTO
- Konwertery JSON
Instalacja
<PackageReference Include="Pr.BuildingBlocks.Cms.Core" Version="5.0.1" />
Typy domenowe
Bazowe abstrakcje budowy modelu domenowego (DDD).
Entity<TId>
Bazowa encja z typowanym identyfikatorem. Implementuje równość na podstawie typu i wartości Id
(operatory == i != plus Equals/GetHashCode). Po zmianie Id zmienia się też hash, więc
encje należy traktować jako niezmienne pod tym względem.
public sealed class Episode : Entity<EpisodeId>
{
public string Title { get; private set; }
private Episode(EpisodeId id, string title) : base(id) { Title = title; }
}
AggregateRoot<TId>
Bazowy agregat (dziedziczy Entity<TId>, implementuje IAggregateRoot). Udostępnia:
IReadOnlyCollection<IDomainEvent> DomainEvents— zdarzenia zarejestrowane podczas operacji.int Version— token wersjonowania dla optimistic concurrency.protected void AddDomainEvent(IDomainEvent @event)— dodaje zdarzenie do kolekcji.protected void IncrementVersion()— inkrementujeVersion.void ClearDomainEvents()— czyści listę po publikacji (wołane przezDomainEventsMiddleware).
IAggregateRoot
Interfejs marker eksponujący DomainEvents i ClearDomainEvents(). Wykorzystywany przez
DomainEventsMiddleware w warstwie Infrastructure do automatycznego wykrywania agregatów
w ChangeTracker EF Core i publikacji ich zdarzeń.
TypedId
Bazowy rekord dla strongly-typed ID. Opakowuje Guid Value. W trybie isRequired = true (domyślnym)
walida, że wartość nie jest Guid.Empty (rzuca ArgumentException z komunikatem
„Wartość ID nie może być wartością domyślną.").
public sealed record EpisodeId(Guid Value) : TypedId(Value);
Zapewnia bezpieczeństwo typów — nie pomylisz EpisodeId z ArticleId, choć oba opakowują Guid.
IValueObject<T>
Kontrakt obiektu wartości. Wymusza pojedynczą właściwość T Value. Współpracuje
z ValueObjectConverterFactory — typy implementujące IValueObject<T> są automatycznie
serializowane jako prymityw w JSON.
public sealed record Slug(string Value) : IValueObject<string>;
Zdarzenia domenowe
IDomainEvent
Kontrakt zdarzenia: Guid Id, DateTimeOffset OccurredOn, void SetTime(DateTimeOffset).
OccurredOn jest ustawiane na końcu pipeline'u przez DomainEventsMiddleware (Infrastructure)
za pomocą IClock.UtcNow, dzięki czemu w testach jednostkowych domena nie zna czasu.
DomainEvent
Bazowa klasa zdarzenia. W konstruktorze generuje Guid.NewGuid() jako Id. Pochodne klasy
zwykle są rekordami niosącymi tylko dane:
public sealed record EpisodePublished(EpisodeId EpisodeId, string Title) : DomainEvent;
Komendy
Markery dla komend obsługiwanych przez Wolverine.
IInternalCommand— komenda lokalna (in-process). Routing/konwencje Wolverine traktują ten interfejs jako sygnał, że wiadomość ma być obsługiwana przez handler w tym samym procesie.IExternalCommand— komenda przekraczająca granicę procesu (message bus, RabbitMQ, inny mikroserwis). Routing kieruje ją na zewnętrzny transport.
Oba interfejsy są puste (markerami) — używaj ich jako : IInternalCommand / : IExternalCommand
na rekordzie komendy.
Wyjątki domenowe
Wszystkie wyjątki domenowe dziedziczą po BaseException, co pozwala middleware
ExceptionHandlerMiddleware (Infrastructure) automatycznie mapować je na odpowiedzi HTTP
z polskimi komunikatami.
| Wyjątek | HTTP | Tytuł odpowiedzi | Kiedy używać |
|---|---|---|---|
DomainException |
400 | „Błąd aplikacji" (przez BaseException) |
Ogólny błąd domenowy. |
DomainRuleException |
400 | „Naruszenie reguły domenowej" | Naruszenie konkretnej, nazwanej reguły domenowej. |
InvalidValueException |
400 | „Nieprawidłowa wartość" | Walidacja pojedynczego pola; konstruktory z propertyName i providedValue. |
InvalidArgumentException |
400 | „Błąd aplikacji" | Niepoprawny argument metody — semantyka domenowa zamiast ArgumentException. |
InvalidActionException |
400 | „Błąd aplikacji" | Próba wykonania niedozwolonej akcji w bieżącym stanie. |
NotFoundException |
404 | „Nie znaleziono zasobu" | Brak żądanego zasobu (resourceName + resourceId). |
ConflictException |
409 | „Konflikt zasobu" | Ogólny konflikt operacji. |
ResourceAlreadyExistsException |
400 | „Błąd aplikacji" | Próba utworzenia istniejącego już zasobu. |
ForbiddenException |
403 | „Odmowa dostępu" | Brak uprawnień do operacji. |
OptimisticConcurrencyException |
409 | „Konflikt optymistycznej współbieżności" | Konflikt wersji agregatu (EntityType, opcjonalne EntityId). |
UniqueConstraintViolationException |
409 | „Naruszenie unikalności" | Naruszenie unikalnego klucza w bazie (z opcjonalnym ConstraintName). |
Konstruktory większości wyjątków przyjmują albo dwie wartości (np. resourceName + resourceId),
albo gotowy komunikat — wybierz to, co lepiej pasuje do kontekstu.
if (episode is null)
throw new NotFoundException(nameof(Episode), episodeId);
if (versionMismatch)
throw new OptimisticConcurrencyException(nameof(Episode), episodeId);
Guard clauses
Walidacja wejściowa w stylu Ardalis.GuardClauses. Punkt wejścia: Guard.Against.
| Metoda | Rzuca | Działanie |
|---|---|---|
Null<T>(T?) |
ArgumentNullException |
null (klasa) lub HasValue == false (struct). |
NullOrWhiteSpace(string?) |
ArgumentException |
null, pusty lub same białe znaki. |
Empty(Guid) |
ArgumentException |
Guid.Empty. |
LengthOutOfRange(string, int min, int max) |
InvalidValueException |
Długość poza zakresem [min, max]. |
OutOfRange<T>(T value, T min, T max) gdzie T : IComparable<T> |
InvalidValueException |
Wartość poza zakresem [min, max] (włącznie). |
InvalidEnum<TEnum>(string raw) |
InvalidValueException |
Niepoprawna nazwa enum (case-sensitive). Komunikat zawiera dozwolone wartości. |
NegativeOrZero(int) |
InvalidValueException |
value <= 0. |
Negative(int) |
InvalidValueException |
value < 0. |
public sealed record CreateEpisodeRequest(string Title, int DurationSeconds, string Status)
{
public void Validate()
{
Guard.Against.NullOrWhiteSpace(Title);
Guard.Against.LengthOutOfRange(Title, 1, 200);
Guard.Against.NegativeOrZero(DurationSeconds);
Guard.Against.InvalidEnum<EpisodeStatus>(Status);
}
}
Wszystkie metody używają atrybutu [CallerArgumentExpression], dzięki czemu nazwa parametru
w komunikacie jest wykrywana automatycznie.
Czas
IClock
Abstrakcja dostawcy czasu — wstrzykuj zamiast DateTime.UtcNow/DateTimeOffset.UtcNow,
żeby testy mogły kontrolować czas.
public interface IClock
{
DateTimeOffset UtcNow { get; }
DateOnly Today { get; }
}
Domyślna implementacja SystemDateTimeProvider znajduje się w Pr.BuildingBlocks.Cms.Infrastructure
i zwraca czas w strefie Europe/Warsaw.
Slugi
ISlugifier
Kontrakt komponentu generującego slug z dowolnego tekstu (string GenerateSlug(string text)).
Implementacja Slugifier znajduje się w Pr.BuildingBlocks.Cms.Infrastructure i jest
konfigurowalna od v5.0.0 przez services.AddSlugifier(opt => ...) (lower case,
collapse dashes, trim, custom replacements). Domyślnie obsługuje polskie znaki diakrytyczne
i transliteruje cyrylicę. Szczegóły: README pakietu Infrastructure.
DTO
PagedResultDto<T>
Generyczny DTO wyniku stronicowanego:
| Pole | Opis |
|---|---|
Data |
Lista elementów na stronie (IReadOnlyList<T>). |
Total |
Łączna liczba rekordów (przed paginacją). |
PageNumber |
Numer strony (1-based). |
PageSize |
Rozmiar strony. |
TotalPages |
Wyliczane: (int)Math.Ceiling(Total / (double)PageSize). |
Filtr ApiResponseWrapperFilter (Infrastructure) rozpoznaje ten typ i nie opakowuje go
w { data: ... } — paginacja ma już własną strukturę.
return new PagedResultDto<EpisodeDto>(items, totalCount, pageNumber, pageSize);
Konwertery JSON
ValueObjectConverterFactory
Fabryka konwerterów System.Text.Json automatycznie obsługująca każdy typ implementujący
IValueObject<T>. Przy odczycie woła statyczną metodę Create(TPrimitive) lub publiczny
konstruktor; przy zapisie serializuje samą wartość prymitywną.
services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(new ValueObjectConverterFactory());
});
Po rejestracji Slug (z przykładu wyżej) jest serializowany jako goły string, nie obiekt:
"abc" zamiast { "value": "abc" }.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Pr.BuildingBlocks.Cms.Core:
| Package | Downloads |
|---|---|
|
Pr.BuildingBlocks.Cms.Infrastructure
Implementacje infrastruktury (ASP.NET Core, EF Core PostgreSQL, Wolverine) dla mikroserwisów CMS. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.1 | 263 | 4/28/2026 |
| 5.0.0 | 103 | 4/28/2026 |
| 4.0.1 | 117 | 4/27/2026 |
| 4.0.0 | 112 | 4/27/2026 |
| 3.5.2 | 212 | 3/15/2026 |
| 3.5.1 | 103 | 3/9/2026 |
| 3.5.0 | 112 | 3/9/2026 |
| 3.3.0 | 525 | 1/17/2026 |
| 3.2.0 | 152 | 1/12/2026 |
| 3.1.6 | 186 | 12/30/2025 |
| 3.1.2 | 937 | 10/1/2025 |
| 3.1.1 | 238 | 10/1/2025 |
| 3.1.0 | 242 | 9/30/2025 |
| 3.0.5 | 316 | 9/29/2025 |
| 3.0.3 | 174 | 9/27/2025 |
| 3.0.1 | 219 | 9/26/2025 |
| 3.0.0 | 288 | 9/23/2025 |
| 2.1.4 | 308 | 9/15/2025 |
| 2.1.3 | 182 | 9/12/2025 |
| 2.1.2 | 215 | 9/11/2025 |