Compare commits

...

149 Commits

Author SHA1 Message Date
9f2a13df6f bump to 2.5.2 2025-11-06 00:25:15 +01:00
0cf6d2690a chore(Infrastructure): bump to 2.5.0 2025-11-05 14:22:22 +01:00
afbbac7b81 core(Abstraction.Application): bump to 1.5.0 2025-11-05 14:21:29 +01:00
d16a4b6bb4 refactor(repository): make UpdateAsync more flexible with Action<TEntity>
- Introduced Action<TEntity>-based UpdateAsync method for more flexible updates
- Updated TDto-based UpdateAsync to delegate to the new method
- Reduced code duplication and Mapper dependency
- Preserved existing Create, Read, and Delete functionality
2025-11-05 14:20:12 +01:00
5567f6731b chore(Core.Abstractions): bump to 4.3.0 2025-10-29 16:25:32 +01:00
fc297d92fa feat(factory): add IServiceScopeFactory property to Factory class
Added ScopeFactory property to the Factory class to expose IServiceScopeFactory through GetRequiredService<IServiceScopeFactory>().
This enables scoped service creation from the factory instance.
2025-10-29 16:08:43 +01:00
50a056d110 feat(Factory): add PostBuildBehavior to control post-build modification behavior in Factory
Introduced PostBuildBehavior enum and BehaveOnPostBuild() method to configure behavior when modifying the service collection after the service provider is built.
Replaced EnsureNotBuilt() with EnsureBuilt() to support configurable handling (throw exception or ignore).
This improves flexibility in scenarios where post-build service modifications should be optionally allowed or silently ignored.
2025-10-29 15:57:36 +01:00
d87f36898b chore(Abstraction.Application): bump to 1.4.0 2025-10-23 10:30:50 +02:00
62e43024a6 chore(Abstractions): bump to 4.2.0 2025-10-23 10:29:41 +02:00
daa3f4d5be feat: add IServiceProvider extension to resolve IRepository<TEntity> 2025-10-23 10:27:57 +02:00
10ff9b9745 refactor(Factory): rename Instance as Shared 2025-10-23 10:19:59 +02:00
ea2340974a refactor(FactoryTests): created unit tests for Factory 2025-10-23 10:18:14 +02:00
fbf9488c55 feat(Factory): prevent service modifications after provider is built
Added an `IsBuilt` property and `EnsureNotBuilt()` helper to `Factory` class
to restrict modifications to the service collection once the service provider
has been built. This ensures immutability and prevents runtime inconsistencies
after initialization.
2025-10-23 09:53:50 +02:00
ddbb70081e fix(factory): return requested service from IServiceProvider in GetService method 2025-10-23 09:49:38 +02:00
c538a8df8c feat(Factory): add Factory class implementing IServiceProvider and IServiceCollection
- Introduced Factory class in DigitalData.Core.Abstractions namespace
- Implements both IServiceProvider and IServiceCollection interfaces
- Provides singleton instance via static readonly Instance property
- Supports lazy initialization of IServiceProvider using Lazy<T>
- Includes full collection management (Add, Remove, Clear, etc.)
- Ensures compatibility with both .NET and .NET Framework via conditional directives
2025-10-23 09:47:18 +02:00
f500d9d974 chore(Abstractions): update package references and add dependency injection packages
- Added Microsoft.Extensions.DependencyInjection and Abstractions for net462
- Added Microsoft.Extensions.DependencyInjection for net7.0, net8.0, and net9.0
- Updated Microsoft.Extensions.* package versions for net8.0 and net9.0 to 9.0.10
2025-10-23 09:27:26 +02:00
f2808d090f refactor(Infrastructure): bump to 2.4.5 2025-10-22 17:42:57 +02:00
0084e6f758 refactor(Abstraction.Application): bump to 1.3.7 2025-10-22 17:37:39 +02:00
90ce4e487c refactor: remove IRepository and DbRepository 2025-10-22 17:34:28 +02:00
1febae72c2 refactor(DbRepositoryFactory): remvoed 2025-10-22 17:26:31 +02:00
3b825d4ea3 refactor(Abstraction.Repository): removed 2025-10-13 13:48:46 +02:00
a06adfdcdf chore: bump Abstraction.Application to v1.3.6 and Infrastructure to v2.4.4 2025-10-13 12:05:03 +02:00
00e5f6c0e9 refactor(Repository): add Query getter metod to Repository calss and interface to be able to create read-only query without expression 2025-10-13 11:55:49 +02:00
1e35692a02 bump to 1.3.5 2025-10-01 15:14:25 +02:00
a6048238d4 refactor(IFactory): add Repository getter method 2025-10-01 15:14:01 +02:00
8708f54996 chore(Abstraction.Application): bump to 1.3.4 2025-10-01 15:09:48 +02:00
568f5990b2 refactor: replace Static service accessor with Factory implementation
- Replaced `Static` class with `Factory` class to unify service collection and provider handling.
- Introduced `Factory.Shared` singleton for centralized access to `IServiceProvider`.
- Updated generic repository access to use `Factory<TEntity>` instead of `Static<TEntity>`.
- Simplified lazy initialization of the service provider.
- Maintains compatibility with .NET Framework and .NET conditional compilation.
2025-10-01 15:08:54 +02:00
Developer 02
b8751379c5 Bump version numbers for core projects
Updated `DigitalData.Core.Abstraction.Application` to version 1.3.3 and `DigitalData.Core.Infrastructure` to version 2.4.3. These changes reflect minor updates, likely including bug fixes and small enhancements.
2025-09-30 21:08:32 +02:00
Developer 02
a38447a36f Refactor for .NET framework compatibility
Added preprocessor directives to conditionally include
code for different .NET frameworks. Adjusted namespace
declarations and organized the `Static` class for
improved clarity and maintainability.
2025-09-30 21:04:32 +02:00
Developer 02
fde0398c89 Bump version to 2.4.2 in project file
Updated the version number, assembly version, and file version from 2.4.1 to 2.4.2 in the DigitalData.Core.Infrastructure.csproj file.
2025-09-30 20:00:36 +02:00
Developer 02
ebf79309d1 Refactor Static class to be generic
Updated the `Static` class in the `DigitalData.Core.Infrastructure` namespace to a generic version `Static<TEntity>`. Removed the original non-generic class and the `GetRepository<TEntity>` method. The new generic class provides a method to retrieve a repository for a specific entity type `TEntity`, enhancing type safety and usability.
2025-09-30 19:58:22 +02:00
Developer 02
5a3cbe8ecf Improve exception message in Static.cs
Updated the exception message in the `Static` class to clarify that services cannot be accessed after the service provider has been created. This change enhances the clarity of the error for developers.
2025-09-30 19:56:58 +02:00
Developer 02
d5a8619b4d Add repository access methods to Static class
This commit introduces two new properties in the `Static` class: `Repository` and `GetRepository<TEntity>()`. The `Repository` property allows retrieval of an `IRepository` instance from the service provider, while `GetRepository<TEntity>()` provides access to a specific `IRepository<TEntity>`. These additions improve the ease of accessing repository instances for data operations.
2025-09-30 19:55:40 +02:00
Developer 02
7689005a14 Add conditional compilation for framework-specific code
Introduce framework-specific handling in `Static.cs` to support both NET and NETFRAMEWORK. Implement lazy initialization for service collection and provider, ensuring proper access and error handling.
2025-09-30 19:48:00 +02:00
Developer 02
05568b1551 Add conditional compilation for .NET Framework support
Updated code to support conditional compilation for .NET Framework and .NET.
Introduced nullable reference types in `DbRepository.cs` and ensured proper initialization of service registration queues in `DependencyInjection.cs`.
Modified `DbRepositoryFactory.cs` and `DbSetFactory.cs` to maintain consistent structure across frameworks.
These changes enhance compatibility and improve type safety.
2025-09-30 18:36:15 +02:00
Developer 02
e0ca11ffc0 Enhance CRUDRepository with conditional compilation
Added preprocessor directive for NET framework support.
Updated using directives to include repository and EF Core
namespaces. Adjusted code structure for improved compatibility.
2025-09-30 18:18:14 +02:00
Developer 02
f1ab8db710 Add support for .NET Framework 4.6.2 in project file
Updated `DigitalData.Core.Infrastructure.csproj` to include .NET Framework version 4.6.2 alongside net7.0, net8.0, and net9.0. Configured framework-specific settings for nullable reference types, implicit usings, and language versions. Added a new `<ItemGroup>` for net462 to reference `Microsoft.EntityFrameworkCore` version 3.1.32.
2025-09-30 18:11:38 +02:00
Developer 02
ca08709d99 Bump version to 1.3.2 in project configuration
Updated the version number, assembly version, and file version in DigitalData.Core.Abstraction.Application.csproj from 1.3.1 to 1.3.2 to reflect the new release of the application.
2025-09-30 17:52:24 +02:00
Developer 02
28e415dee1 Add .NET Framework support in Extensions.cs
Introduce conditional compilation directives for the .NET Framework, including necessary using statements and a modified namespace declaration. Add a closing brace for the class definition to ensure compatibility across different target frameworks.
2025-09-30 17:50:53 +02:00
Developer 02
2cedfbe91b Add conditional compilation for IRepositoryFactory
Updated the namespace declaration in `IRepositoryFactory.cs` to support conditional compilation for .NET and .NET Framework. The `Get<TEntity>()` method now conditionally includes the `public` access modifier based on the target framework. Adjusted the interface structure to ensure proper compilation and organization.
2025-09-30 17:49:26 +02:00
Developer 02
d4d1d2b69f Add conditional compilation for .NET Framework support
Introduce conditional compilation directives in the IRepository interface to support .NET Framework. Update method signatures to conditionally include the `public` access modifier for compatibility across frameworks. Adjust the Entity method to remove the `public` modifier, enhancing flexibility while maintaining existing functionality.
2025-09-30 17:47:24 +02:00
Developer 02
74a625a863 Enhance framework compatibility and code readability
Added preprocessor directives for .NET framework compatibility.
Modified `using` directives to be framework-specific.
Improved code formatting for better readability.
Introduced obsolete attributes for deprecated methods,
recommending `MediatR` as an alternative.
Added XML documentation for clarity and maintainability.
2025-09-30 17:33:51 +02:00
Developer 02
07ab7f0c62 refactor(DataResult): update to execute if NET 2025-09-30 17:03:39 +02:00
Developer 02
e74a740abd refactor(CookieConsentSettings): update to be executed only on NET 2025-09-30 16:57:54 +02:00
Developer 02
dfa3cd1a58 refactor(BaseDto): update to compile only in net 2025-09-30 16:57:05 +02:00
Developer 02
0dd4930f1b refactor(DigitalData.Core.Abstraction.Application): update to support net 462 2025-09-30 16:52:02 +02:00
c95f018413 refactor(Core.Abstractions): update to support net 4.6.2 2025-09-15 10:16:28 +02:00
b99ff91841 chore bump to related version 2025-09-12 15:09:14 +02:00
1c69eb48cd move IEntity and IDto to Core.Abstractionns 2025-09-12 15:05:40 +02:00
ad734f77f9 chore(DigitalData.Core.Infrastructure): bump to 2.4.0 2025-09-12 12:55:03 +02:00
4c003745ff chore(Abstractions.Application): bump to 1.3.0 2025-09-12 12:53:50 +02:00
89de237aff refactor(DbRepository); split to regions 2025-09-12 12:45:20 +02:00
a2567791b7 refactor(DbRepositoryTests): update to use Get instead of Where 2025-09-12 12:42:25 +02:00
4526ba189a refactor(DbRepository): make Mapper nullable to be able to use without adding automapper 2025-09-12 12:40:22 +02:00
f544ea4887 refactor(DbRepository): implement create method for dtros 2025-09-12 12:08:01 +02:00
173c4fdbc4 refactor(repository): restructure IRepository interface with CRUD regions
- Removed AutoMapper dependency from IRepository interface.
- Organized methods into Create, Read, Update, Delete (CRUD) regions.
- Added CreateAsync overloads for DTOs.
- Retained obsolete methods under a dedicated region.
2025-09-12 12:04:28 +02:00
babddfff83 feat(DbRepository): add GetAll and GetAllAsync, make Where read-only
- Updated Where() to use AsNoTracking() for read-only queries
- Added GetAll() and GetAllAsync() to retrieve all entities
- Improved repository methods for efficient read operations
2025-09-12 11:50:59 +02:00
56b467ddfc add CancellationToken to GetAllAsync 2025-09-12 11:45:12 +02:00
63c97b4dc7 feat(repository): add GetAll and GetAllAsync methods to IRepository
- Introduced GetAll() and GetAllAsync() to provide full entity retrieval options.
- Updated obsolete warnings:
  - Read() now marked with "Use CreateAsync, UpdateAsync or DeleteAsync".
  - ReadOnly() points to IRepository<TEntity>.Where.
- Removed old Get() method in favor of new retrieval methods.
2025-09-12 11:44:02 +02:00
e2853b64d1 refactor(repository): replace queryFactory with DbSetFactory in DbRepository
- Updated DbRepository constructor to use DbSetFactory<TDbContext, TEntity>
  instead of a Func<TDbContext, DbSet<TEntity>> queryFactory.
- Adjusted namespace imports to include DigitalData.Core.Infrastructure.Factory.
- Improved repository instantiation consistency and encapsulation.
2025-09-12 11:35:40 +02:00
db8c41368d arrange tests 2025-09-11 18:53:39 +02:00
8743325067 update DbRepository 2025-09-11 18:35:17 +02:00
be96bd0c07 refactor(repository): add query parameter to IRepository.Where and constrain IRepository.Entity
- Updated `IRepository<TEntity>.Where()` to accept an `Expression<Func<TEntity, bool>>` for filtering.
- Added `where TEntity : IEntity` constraint to `IRepository.Entity<TEntity>()` method.
- No functional logic changes, only interface refactoring for stronger typing and query support.
2025-09-11 18:30:38 +02:00
b181e1543f create IEntity and IDto interfaces 2025-09-11 18:03:55 +02:00
7c2a165479 feat(infrastructure): enhance repository DI with DbSetFactory and IRepositoryFactory
- Added `RegisterAllServices` method to centralize service registration
- Introduced `RegsDbSetFactory` queue for custom DbSetFactory registrations
- Extended `RegisterEntity` to support optional DbSetFactory
- Added `RegisterDbSetFactory` for explicit DbSetFactory registration
- Registered `IRepositoryFactory` with `DbRepositoryFactory` in DI
2025-09-11 17:56:45 +02:00
90a12f52bc feat(infrastructure): add DbSetFactory registration in DependencyInjection
- Introduced AddDbSetFactory to support DbSet factory registration for entities.
- Updated RepositoryConfiguration.RegisterFromAssembly to register both repositories and DbSet factories.
- Changed RegisterFromAssembly and RegisterEntity to void methods instead of fluent interface.
- Extracted DbRepositoryFactory into its own Factory namespace for better separation of concerns.
2025-09-11 17:20:47 +02:00
33f7ced3b2 refactor(dependency-injection): replace generic AddDbRepository with configurable registration
- Replaced `AddDbRepository<TDbContext, TEntity>` with `AddDbRepository(Action<RepositoryConfiguration>)` for more flexible DI registration.
- Added `RepositoryConfiguration` class to support:
  - Registering repositories from an assembly (`RegisterFromAssembly`)
  - Registering individual entities (`RegisterEntity`)
- Queues (`RegsFromAssembly` and `RegsEntity`) are used to defer registration until `InvokeAll` is called.
- Allows overriding and scanning multiple entities dynamically instead of static generic method.
2025-09-11 13:07:45 +02:00
84cd52cc45 feat(DbRepositoryFactory): implement IRepositoryFactory 2025-09-10 17:51:32 +02:00
272650d991 feat(IRepositoryFactory): create to capsulate repository geenration.
- update DbRepo to use IRepositoryFactory
2025-09-10 17:48:16 +02:00
7d3d3b5ae9 implement IRepository 2025-09-10 17:43:42 +02:00
453a0ccdf0 replace IEntityMapper<TEntity> with IMapper 2025-09-10 17:33:26 +02:00
0809d1215b refactor: rename Read/ReadOnly methods and mark them as obsolete
- Renamed `Read()` to `Where()` and `ReadOnly()` to `Get()` for clarity.
- Marked old `Read()` and `ReadOnly()` methods as `[Obsolete]` with guidance to use the new methods.
- Updated XML region to separate obsolete methods for better maintainability.
2025-09-10 17:30:47 +02:00
4972f05fdf refactor(repository): reorganize IRepository extensions and add missing overloads
- Added regions for Create, Update, and Delete for better structure
- Introduced `Where` extension for IRepository
- Added batch `CreateAsync` overloads for entities and DTOs
- Improved readability and consistency across IRepository extension methods
2025-09-10 17:27:33 +02:00
886a107c71 feat(repository): add extension methods for IRepository operations
- Added extensions for IRepository to support Get, Create, Update, and Delete operations.
- Maintains existing IRepository<TEntity> CreateAsync methods for DTO mapping.
- Improves consistency and usability across repository abstractions.
2025-09-10 17:22:56 +02:00
ce8e563e4e refactor(IRepository): rename Read/ReadOnly to Where/Get and mark as obsolete
- Replaced `Read()` with `Where()` and `ReadOnly()` with `Get()` in `IRepository<TEntity>`
- Marked `Read()` and `ReadOnly()` as `[Obsolete]` with guidance for the new methods
- Added non-generic `IRepository` interface with `Entity<TEntity>()` method
2025-09-10 17:04:52 +02:00
513f8c1ba3 chore: bump to x.x.1 2025-08-25 14:57:11 +02:00
75d0a6f1df feat(repository): refactor UpdateAsync and DeleteAsync to accept query functions
Changed UpdateAsync and DeleteAsync to accept Func<IQueryable<TEntity>, IQueryable<TEntity>> instead of IQueryable<TEntity>

Improved flexibility by allowing callers to compose queries dynamically

Updated overloads to wrap expressions with q => q.Where(expression) for compatibility
2025-08-25 14:56:19 +02:00
003636e243 chroe(Core.Inf); bump to 2.3.0 2025-08-25 14:46:26 +02:00
adf2ba00c3 chroe(Abstraction.Application); bump to 1.2.0 2025-08-25 14:45:53 +02:00
e80eb6aa1f rename CancellationTokens 2025-08-25 14:32:38 +02:00
70e3fe5dd7 Refactor(DbRepository): Add IQueryable overloads for UpdateAsync and DeleteAsync
- Added UpdateAsync<TDto>(TDto dto, IQueryable<TEntity> query, ...) overload
- Added DeleteAsync(IQueryable<TEntity> query, ...) overload
- Expression-based methods now delegate to the IQueryable overloads
- Reduces code duplication and allows more flexible queries
2025-08-25 14:27:55 +02:00
859c03177e Merge branch 'master' of http://git.dd:3000/AppStd/WebCoreModules 2025-08-21 18:21:59 +02:00
244ed3ebe4 chore: bump to related version 2025-08-21 18:21:00 +02:00
048ba35804 remove ReadQuery 2025-08-21 18:19:16 +02:00
Developer 02
9aa7673484 chore(Exceptions): Bump to 1.1.0 2025-08-05 19:37:46 +02:00
Developer 02
f01db9c2d7 feat(core): ForbiddenException hinzugefügt zur Behandlung von verbotenen Operationen 2025-08-05 19:36:44 +02:00
56cb3e247f fix(GlobalExceptionHandlerMiddleware): add await 2025-07-30 17:00:03 +02:00
0554cbf7bc refactor(API): upgrade auf 2.2.1 2025-07-30 15:43:45 +02:00
Developer 02
62e36f4459 refactor(DigitalData.Core.Infrastructure.AutoMapper): Upgrade 1.0.3 2025-05-27 18:36:32 +02:00
Developer 02
e09d40abc1 refactor(DigitalData.Core.Infrastructure.AutoMapper): Entfernen der Abhängigkeit von DigitalData.Core.Abstractions 2025-05-27 18:32:55 +02:00
Developer 02
9b286b023c refactor(Application.Abstractions): umbenennen in Abstractions.Application 2025-05-27 18:31:13 +02:00
Developer 02
0441f593d4 update project versions 2025-05-27 17:37:05 +02:00
Developer 02
7621d657ac refactor(DTO): Hinzufügen des Attributs Obsolete mit der Meldung „Use DigitalData.Core.Exceptions and .Middleware“ 2025-05-26 15:49:22 +02:00
Developer 02
e94efc8534 Aufrüstung auf 2.2.0 2025-05-26 15:24:27 +02:00
Developer 02
90db7a356a refactor(DigitalData.Core.API): Aktualisiert für aktuelle Application und Application.Abstraction 2025-05-26 13:55:41 +02:00
Developer 02
b6212fec55 chore(DigitalData.Core.Application.Abstraction): Build-Typ Release erstellen 2025-05-26 13:22:09 +02:00
Developer 02
63efde9e8a chore(DigitalData.Core.Application.Abstraction): Paketsymbol hinzufügen 2025-05-26 12:59:47 +02:00
Developer 02
0a9ba9be38 refactor(DigitalData.Core.Application): Aktualisiert auf 3.3.2 2025-05-26 12:52:10 +02:00
Developer 02
e05f1347f8 Enhance project metadata in .csproj file
Updated `DigitalData.Core.Application.Abstraction.csproj` to include new properties in the `<PropertyGroup>`, such as package generation, description, package ID, authors, company, product name, copyright, package icon, repository URL, tags, and versioning information. This improves project metadata and compliance with package management standards.
2025-05-26 12:47:07 +02:00
Developer 02
a0696c5e22 feat(Application.Abstraction): Erstellt, um Schnittstellen von Core.Application zu behandeln 2025-05-20 16:30:31 +02:00
Developer 02
01025ff36f refactor: update efcore 2025-05-20 16:14:52 +02:00
Developer 02
831f91ce16 Update EntityFrameworkCore to version 9.0.5
Updated the `Microsoft.EntityFrameworkCore` package version
from `9.0.4` to `9.0.5` for the target framework `net9.0`
in the `DigitalData.Core.Infrastructure.csproj` file.
2025-05-20 12:47:49 +02:00
Developer 02
980e21f27f Bump version to 3.3.1 in project file
Updated the version number, assembly version, and file version for the DigitalData.Core.Application project from 3.3.0 to 3.3.1 in DigitalData.Core.Application.csproj.
2025-05-20 12:44:54 +02:00
Developer 02
d49aaf61dc Update CreateAsync to return DataResult<TReadDto>
Modified the CreateAsync method in CRUDService.cs and ICRUDService.cs to change the return type from DataResult<TId> to DataResult<TReadDto>. The implementation now maps the created entity to a read DTO, providing the caller with the complete entity representation instead of just its ID.
2025-05-20 12:44:43 +02:00
Developer 02
b95baaef5f Enforce ID retrieval in UpdateAsync method
Updated the `UpdateAsync<TUpdateDto>` method in `CRUDService.cs` to replace the use of `GetIdOrDefault<TId>()` with `GetId<TId>()`. This change ensures that an ID must be present in the `updateDto`, enhancing the reliability of the update process.
2025-05-20 12:38:03 +02:00
Developer 02
68d78afafe Refactor GetId method and update ICRUDService comments
Modified the `GetId<TId>` method in the `EntityExtensions` class to change its return type from nullable `TId?` to non-nullable `TId`, ensuring stricter type safety by throwing an `InvalidOperationException` when the `Id` property is not readable.

Removed a summary comment in the `ICRUDService` interface related to the `CreateAsync` method, which may indicate ongoing documentation updates for clarity on the method's functionality and parameters.
2025-05-20 12:37:36 +02:00
Developer 02
dd679b79b4 Update target frameworks and version to 2.1.0
- Added support for net8.0 and net9.0 frameworks.
- Incremented package, assembly, and file versions to 2.1.0.
- Updated `Microsoft.Extensions.Options` to 7.0.1 for net7.0, 8.0.2 for net8.0, and 9.0.5 for net9.0.
- Updated `Microsoft.Extensions.Options.ConfigurationExtensions` to 8.0.0 for net8.0 and 9.0.5 for net9.0.
- Maintained existing project reference to `DigitalData.Core.Abstractions.csproj`.
2025-05-20 11:37:03 +02:00
Developer 02
cc2177e6d1 Update package for net9.0 and version 2.1.0
- Added net9.0 to target frameworks.
- Updated package description to reference DigitalData.Core.Application.
- Incremented version to 2.1.0 for package, assembly, and file versions.
- Changed package icon path to `..\Assets\core_icon.png`.
2025-05-20 10:56:49 +02:00
Developer 02
94ff231a71 Update project file and increment version to 3.3.0
- Added target frameworks: net7.0, net8.0, net9.0
- Shortened package description
- Updated versioning from 3.2.1 to 3.3.0
- Changed package icon path to ../Assets/core_icon.png
2025-05-20 10:50:28 +02:00
Developer 02
5e2ebd07db Update NuGet package description for clarity
Modified the description in the `DigitalData.Core.Abstractions.csproj` file to provide a more concise statement about the package, removing details about Clean Architecture principles.
2025-05-20 10:44:56 +02:00
Developer 02
43e714b061 Update package references by target framework version
Modified `DigitalData.Core.Abstractions.csproj` to conditionally include package references for `Microsoft.Extensions.Caching.Memory` and `Microsoft.Extensions.Configuration.Binder` based on the target framework (`net7.0`, `net8.0`, `net9.0`). This change enhances compatibility and version management for different .NET versions.
2025-05-20 10:44:28 +02:00
Developer 02
7a7f564f82 Bump version to 4.0.0 in project file
Updated versioning information in `DigitalData.Core.Abstractions.csproj` from 3.6.0 to 4.0.0, including changes to `<Version>`, `<AssemblyVersion>`, and `<FileVersion>`.
2025-05-20 10:36:20 +02:00
Developer 02
f7e2bb2434 Refactor repository pattern and mark methods obsolete
Updated `IRepository<TEntity>` to introduce new reading methods and mark existing ones as obsolete. The `CRUDRepository` class is now deprecated, encouraging a transition to alternative implementations. Significant changes in `DbRepository<TDbContext, TEntity>` include the removal of old read methods in favor of a new `Read` method returning `IReadQuery<TEntity>`. Added a `ReadQuery<TEntity>` class to enhance querying capabilities with a fluent API. Obsolete methods are organized under a `#region Obsolete` directive for better management.
2025-05-20 10:35:27 +02:00
Developer 02
5995b334eb Mark ICRUDRepository as obsolete
The ICRUDRepository interface has been marked as obsolete,
with an attribute advising users to use IRepository instead.
This change aims to direct developers towards a preferred
alternative for repository operations.
2025-05-20 10:20:42 +02:00
Developer 02
1b8dc29595 Enhance IRepository<TEntity> with new method overloads
- Added overload for CreateAsync to accept IEnumerable<TEntity>.
- Updated Read method to accept multiple expressions for flexible querying.
2025-05-20 10:11:39 +02:00
Developer 02
36c2c20eab Add CancellationToken support to IReadQuery methods
Updated the IReadQuery<TEntity> interface to include an optional CancellationToken parameter for the following asynchronous methods:
- FirstOrDefaultAsync
- SingleOrDefaultAsync
- ToListAsync
- FirstAsync
- SingleAsync

Documentation comments for these methods have also been updated to describe the new cancellation parameter and its purpose.
2025-05-20 10:10:16 +02:00
Developer 02
a7df79629d Refactor cancellation token parameter names
Updated parameter names from `ct` to `cancellation` in several asynchronous methods of the `IRepository<TEntity>` interface. This change improves code readability and clarity. Affected methods include `CreateAsync`, `UpdateAsync`, `DeleteAsync`, `ReadAllAsync`, `ReadOrDefaultAsync`, and their generic counterparts.
2025-05-20 10:07:42 +02:00
Developer 02
89a77019dc Enhance IRepository and add RepositoryExtensions
Updated the IRepository<TEntity> interface to include an overloaded CreateAsync method for handling multiple entities and removed the CancellationToken from the Read method. Introduced a new RepositoryExtensions class with a Where extension method to facilitate applying multiple filters to IReadQuery<TEntity> queries.
2025-05-20 10:05:57 +02:00
Developer 02
21c895c22b Enhance IRepository with async methods and deprecations
Updated the `IRepository<TEntity>` interface in `IRepository.cs` to include new asynchronous methods for reading, updating, and deleting entities. Added `Read`, `UpdateAsync`, and `DeleteAsync` methods while marking several existing methods as obsolete to encourage the use of the new `Read` method returning `IReadQuery<TEntity>`. Removed previous implementations of `UpdateAsync` and `DeleteAsync` for a more streamlined API.
2025-05-20 10:00:18 +02:00
Developer 02
4afeddb7f9 Enhance documentation and introduce IReadQuery interface
Updated XML documentation in ConfigurationExtension.cs for better clarity.
Removed the old implementation and retained the functionality of GetOrDefault<T>.

Introduced a new IReadQuery<TEntity> interface in IReadQuery.cs,
providing methods for both asynchronous and synchronous entity queries,
along with comprehensive documentation for each method.
2025-05-20 09:56:54 +02:00
Developer 02
14d00653d1 Update version to 1.0.1 and remove package references
Updated the versioning information in `DigitalData.Core.Exceptions.csproj` from `1.0.0` to `1.0.1` for `Version`, `AssemblyVersion`, and `FileVersion`. Removed package references for `Microsoft.AspNetCore.Http.Abstractions` version `2.3.0` and `Microsoft.Extensions.Logging.Abstractions` version `9.0.5`.
2025-05-19 17:14:14 +02:00
Developer 02
87e4c1414e Enhance ConfigureGlobalExceptionHandler method
Updated the `ConfigureGlobalExceptionHandler` method in the `DependencyInjection` class to include two optional parameters: `options` (nullable) and `addDefaultHandlers` (defaulting to true). The method now initializes `options` to a default action if null and adds default exception handlers when `addDefaultHandlers` is true, improving flexibility and usability.
2025-05-19 16:45:40 +02:00
Developer 02
fd8e976e1e Refactor HttpExceptionHandler to use properties
Changed DefaultBadRequest, DefaultNotFound, and Default
from readonly fields to properties for lazy evaluation,
enhancing performance and flexibility while maintaining
the same functionality.
2025-05-19 16:18:05 +02:00
Developer 02
2c393701e4 Add NuGet package metadata and asset inclusion
Updated `DigitalData.Core.Exceptions.Middleware.csproj` to include essential NuGet package metadata such as `PackageId`, `Authors`, `Description`, and versioning information. Added an `ItemGroup` for the `core_icon.png` asset to ensure it is packed with the NuGet package. Retained existing framework-specific references for `Microsoft.Extensions.Options` for `net7.0` and `net8.0`.
2025-05-19 16:09:45 +02:00
Developer 02
1d50fd8e5b Update NuGet metadata and project build configurations
Enhanced `DigitalData.Core.Exceptions.csproj` with NuGet package metadata including `PackageId`, `Authors`, `Description`, and versioning details. Added `core_icon.png` asset to the package.

Modified `DigitalData.Core.sln` to change project build configurations from `Debug` to `Release` for multiple projects.
2025-05-19 15:58:27 +02:00
Developer 02
701c34a251 Add assets project and update legacy icon
This commit introduces a new project named "assets" to the solution, which includes two assets: "core_icon.png" and "core_legacy_icon.png". Additionally, the file `core_legacy_icon.png` has been completely replaced with a new version, featuring a different binary structure and likely a new visual design.
2025-05-19 15:34:17 +02:00
Developer 02
63f914d188 Refactor CRUDRepository for improved flexibility
- Added using directives for new application interfaces.
- Removed constraint for IUnique<TId> from TEntity.
- Updated CountAsync method to use GetId() for identifier retrieval.
2025-05-19 15:27:08 +02:00
Developer 02
9ea2599553 Remove DependencyInjection class and global exception handler
The entire `DependencyInjection` class has been removed, including the `UseGlobalExceptionHandler` extension method and its associated `using` directive. This change eliminates the functionality for setting up global exception handling middleware in the application.
2025-05-19 15:25:59 +02:00
Developer 02
b8995da5ea Refactor global exception handling middleware
Updated `GlobalExceptionHandlerMiddleware.cs` to include
necessary using directives for logging and options handling.
Removed the `HandleExceptionAsync` method and replaced it
with a more extensible approach using a dictionary of
handlers for different exception types. Added logging for
unhandled exceptions to ensure proper error tracking.
2025-05-19 15:21:11 +02:00
Developer 02
14013bc7b7 Add default handlers to HttpExceptionHandler
Introduce static methods for creating HttpExceptionHandler instances for specific exceptions. Define default message factory and handlers for common HTTP status codes, including a generic handler that returns a JSON response for internal server errors.
2025-05-19 15:06:24 +02:00
Developer 02
f586e9eb2f Refactor exception handling to use HttpExceptionHandler
Updated GlobalExceptionHandlerOptions to replace HttpExceptionMapping with HttpExceptionHandler. Removed HttpExceptionMapping class and introduced HttpExceptionHandler for managing exceptions in HTTP requests. Added methods for creating specific exception handlers and default handlers for common exceptions, with JSON serialization for error messages.
2025-05-19 15:01:50 +02:00
Developer 02
ce786a6d42 Update package references in project file
Updated `DigitalData.Core.Exceptions.Middleware.csproj` to include specific `Microsoft.Extensions.Options` package references for target frameworks net7.0, net8.0, and net9.0. Removed the previous single `<ItemGroup>` and added three new `<ItemGroup>` entries with the corresponding package versions.
2025-05-19 14:41:46 +02:00
Developer 02
97695fb0b0 Add dependency injection for global exception handler
Introduce methods in DependencyInjection.cs for configuring
and using a global exception handler. Update the project file
to include a reference to Microsoft.Extensions.Options.
Modify GlobalExceptionHandlerMiddleware to accept
IOptions<GlobalExceptionHandlerOptions> for enhanced
configuration through dependency injection.
2025-05-19 14:38:49 +02:00
Developer 02
cb7b69a0a2 Refactor GlobalExceptionHandlerOptions for better access
Updated the accessibility of the RegisteredMappings field to internal and introduced a DefaultMapping property. Modified the Add method to support setting a mapping as default, with adjusted logic for adding mappings.
2025-05-19 14:30:50 +02:00
Developer 02
b38422256c Enhance HttpExceptionMapping with logging and defaults
Added optional 'Log' property to HttpExceptionMapping for
specifying logging behavior. Introduced static readonly
properties for default mappings of BadRequestException and
NotFoundException using a default message factory.
2025-05-19 14:24:10 +02:00
Developer 02
9b29a49ad6 Refactor exception handling with new mapping system
Updated `GlobalExceptionHandlerOptions` to use a new `_registeredMappings` dictionary for `HttpExceptionMapping` objects, enhancing flexibility in mapping exceptions to HTTP responses. Renamed `RegisterException` to `Add` to reflect its new functionality. Removed the `HttpResponse` record definition and introduced a new `HttpExceptionMapping` record, which includes a static `Create` method for easier instantiation of mappings.
2025-05-19 14:15:38 +02:00
Developer 02
83ba492b37 Add global exception handling middleware
Introduces `GlobalExceptionHandlerMiddleware` for managing exceptions in the request pipeline, logging them, and returning JSON error responses. A new project, `DigitalData.Core.Exceptions.Middleware`, is created to house this middleware and related classes. The `GlobalExceptionHandlerOptions` class allows for custom exception registration with specific HTTP status codes, while a new `HttpResponse` record encapsulates status codes and messages for structured responses. The middleware is registered in the `DependencyInjection` class for easy integration.
2025-05-19 13:27:37 +02:00
Developer 02
50c19fea31 Refactor exception handling middleware
Updated DependencyInjection to use GlobalExceptionHandler.
Removed ExceptionHandlingMiddleware and added
GlobalExceptionHandlerMiddleware for unified exception
handling across the application.
2025-05-16 15:59:38 +02:00
Developer 02
f93b197d45 Refactor ExceptionHandlingMiddleware and update dependencies
Rewrote the `ExceptionHandlingMiddleware` class to improve structure and functionality, changing its namespace to `DigitalData.Core.Exceptions`. Updated the constructor to support a nullable logger and implemented null-conditional logging for unhandled exceptions. Added new package references in `DigitalData.Core.Exceptions.csproj` for `Microsoft.AspNetCore.Http.Abstractions` and `Microsoft.Extensions.Logging.Abstractions`. Introduced a new `DependencyInjection` class to register the middleware in the application's pipeline.
2025-05-16 15:55:27 +02:00
Developer 02
eae0d9f913 Refactor DIExtensions and add exception handling middleware
- Improved documentation in DIExtensions.cs for clarity.
- Added project reference to DigitalData.Core.Exceptions.
- Updated solution file to include DigitalData.Core.Exceptions.
- Introduced ExceptionHandlingMiddleware for global exception handling.
- Added BadRequestException and NotFoundException classes.
- Created DigitalData.Core.Exceptions project with .NET 7.0, 8.0, and 9.0 support.
2025-05-16 15:37:21 +02:00
Developer 02
55eb250d7e Deprecate controllers/services; simplify generics
Added `[Obsolete("Use MediatR")]` attributes to various controller and service classes to indicate deprecation in favor of MediatR. Simplified generic type constraints in `CRUDControllerBase` and related files by removing `IUnique<TId>`. Improved structure and documentation in `CSPMiddleware.cs`. Introduced new extension methods in `EntityExtensions.cs` for safer retrieval of 'Id' properties. Removed `IUnique.cs` interface and updated project dependencies in `DigitalData.Core.Application.csproj` for caching. Overall, these changes enhance code maintainability and clarity.
2025-05-16 14:54:31 +02:00
Developer 02
e0c1b856ad Remove ServiceResultExtensions class and Try method
The `ServiceResultExtensions` class has been removed from the
`DigitalData.Core.Abstractions` namespace. This class included a
static method `Try<T>` for handling nullable results, which has
now been eliminated. This change may indicate a refactoring
or a new approach to managing nullable results in the codebase.
2025-05-16 13:12:02 +02:00
Developer 02
3a1aeb7ac3 Refactor namespaces and enhance application structure
This commit reorganizes namespaces from `DigitalData.Core.Abstractions` and `DigitalData.Core.DTO` to `DigitalData.Core.Application.Interfaces` and `DigitalData.Core.Application.DTO`, improving maintainability and clarity.

Updated using directives across multiple files to reflect the new structure, ensuring functionality remains intact.

Project references in `DigitalData.Core.API.csproj` have been consolidated to include the new Application project.

Introduced new classes and interfaces such as `BaseDTO`, `CookieConsentSettings`, `DataResult`, `Notice`, and `Result` to enhance data transfer and service result handling.

Updated `IRepository`, `ICRUDRepository`, and `IEntityMapper` interfaces to facilitate CRUD operations and entity mapping.

Added extension methods in `Extensions.cs` to improve repository usability.

New interfaces for HTTP client services have been added, enhancing external API call handling.

Overall, these changes reflect a significant restructuring aimed at improving organization and preparing for future development.
2025-05-16 11:24:58 +02:00
Developer 02
246184165f feat(Core.Abstractions): Upgrade auf 3.6 2025-04-28 16:11:26 +02:00
Developer 02
9d36ced82f feat: Hinzufügen von Erweiterungsmethoden zum Abrufen von Konfigurationsoptionen von IServiceProvider
- Implementiert GetOptions<TOptions> zum Abrufen von Optionen oder null.
- Implementiert GetRequiredOptions<TOptions> für den Abruf von Optionen oder das Auslösen einer Ausnahme.
2025-04-28 16:10:31 +02:00
Developer 02
c81ff2c628 feat(ConfigurationExtension): Hinzufügen der Klasse ConfigurationExtension mit der Methode GetOrDefault zum sichereren Abrufen von Konfigurationen
- Einführung einer neuen statischen Klasse ConfigurationExtension im Namespace DigitalData.Core.Abstractions.
- Hinzufügen der GetOrDefault-Erweiterungsmethode zu IConfiguration, die eine einfachere Abfrage von Konfigurationswerten mit Standardverhalten ermöglicht, wenn diese nicht gefunden werden.
- Aktualisierung der Versionsnummern auf 3.5.0 in den Projektdateien.
2025-04-28 15:44:01 +02:00
Developer 02
653665bb25 refactor(LazyServiceProvider.cs): rename DeferredServiceProvider.cs 2025-04-28 14:58:30 +02:00
Developer 02
17edf605e7 chore(Abstractions): Aktualisiert auf 3.4.4 2025-04-28 14:57:29 +02:00
Developer 02
b41373339e feat(core): DeferredServiceProvider für verzögerte IServiceProvider-Initialisierung hinzugefügt 2025-04-28 14:56:25 +02:00
87 changed files with 2741 additions and 843 deletions

BIN
Assets/core_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 KiB

BIN
Assets/core_legacy_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,15 +1,15 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstraction.Application;
using Microsoft.AspNetCore.Mvc;
namespace DigitalData.Core.API
{
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class BasicCRUDControllerBase<TCRUDService, TDto, TEntity, TId> : CRUDControllerBase<TCRUDService, TDto, TDto, TDto, TEntity, TId>
where TCRUDService : ICRUDService<TDto, TDto, TEntity, TId>
where TDto : class, IUnique<TId>
where TEntity : class, IUnique<TId>
where TDto : class
where TEntity : class
{
public BasicCRUDControllerBase(ILogger logger, TCRUDService service) : base(logger, service)
{

View File

@ -1,7 +1,6 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.DTO;
namespace DigitalData.Core.API
{
@ -15,12 +14,13 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class CRUDControllerBase<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCreateDto : class
where TReadDto : class
where TUpdateDto : class, IUnique<TId>
where TEntity : class, IUnique<TId>
where TUpdateDto : class
where TEntity : class
{
protected readonly ILogger _logger;
protected readonly TCRUDService _service;
@ -46,10 +46,10 @@ namespace DigitalData.Core.API
[HttpPost]
public virtual async Task<IActionResult> Create(TCreateDto createDto)
{
return await _service.CreateAsync(createDto).ThenAsync<TId, IActionResult>(
Success: id =>
return await _service.CreateAsync(createDto).ThenAsync<TReadDto, IActionResult>(
Success: dto =>
{
var createdResource = new { Id = id };
var createdResource = new { Id = dto.GetId() };
var actionName = nameof(GetById);
var routeValues = new { id = createdResource.Id };
return CreatedAtAction(actionName, routeValues, createdResource);

View File

@ -1,7 +1,6 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.DTO;
namespace DigitalData.Core.API
{
@ -16,12 +15,13 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class CRUDControllerBaseWithErrorHandling<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCreateDto : class
where TReadDto : class
where TUpdateDto : class, IUnique<TId>
where TEntity : class, IUnique<TId>
where TUpdateDto : class
where TEntity : class
{
protected readonly ILogger _logger;
protected readonly TCRUDService _service;
@ -49,10 +49,10 @@ namespace DigitalData.Core.API
{
try
{
return await _service.CreateAsync(createDto).ThenAsync<TId, IActionResult>(
Success: id =>
return await _service.CreateAsync(createDto).ThenAsync<TReadDto, IActionResult>(
Success: dto =>
{
var createdResource = new { Id = id };
var createdResource = new { Id = dto.GetId() };
var actionName = nameof(GetById);
var routeValues = new { id = createdResource.Id };
return CreatedAtAction(actionName, routeValues, createdResource);

View File

@ -1,47 +1,46 @@
namespace DigitalData.Core.API
namespace DigitalData.Core.API;
/// <summary>
/// Middleware to add Content Security Policy (CSP) headers to the HTTP response.
/// </summary>
public class CSPMiddleware
{
private readonly RequestDelegate _next;
private readonly string _policy;
/// <summary>
/// Middleware to add Content Security Policy (CSP) headers to the HTTP response.
/// Initializes a new instance of the <see cref="CSPMiddleware"/> class.
/// </summary>
public class CSPMiddleware
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="policy">The CSP policy string with placeholders for nonces.</param>
public CSPMiddleware(RequestDelegate next, string policy)
{
private readonly RequestDelegate _next;
private readonly string _policy;
_next = next;
_policy = policy;
}
/// <summary>
/// Initializes a new instance of the <see cref="CSPMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="policy">The CSP policy string with placeholders for nonces.</param>
public CSPMiddleware(RequestDelegate next, string policy)
/// <summary>
/// Invokes the middleware to add the CSP header to the response.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A task that represents the completion of request processing.</returns>
public async Task Invoke(HttpContext context)
{
// Generate a nonce (number used once) for inline scripts and styles
var nonce = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
// Store the nonce in the context items for later use
context.Items["csp-nonce"] = nonce;
// Add the CSP header to the response
context.Response.OnStarting(() =>
{
_next = next;
_policy = policy;
}
context.Response.Headers.Append("Content-Security-Policy",
string.Format(_policy, nonce));
return Task.CompletedTask;
});
/// <summary>
/// Invokes the middleware to add the CSP header to the response.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A task that represents the completion of request processing.</returns>
public async Task Invoke(HttpContext context)
{
// Generate a nonce (number used once) for inline scripts and styles
var nonce = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
// Store the nonce in the context items for later use
context.Items["csp-nonce"] = nonce;
// Add the CSP header to the response
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("Content-Security-Policy",
string.Format(_policy, nonce));
return Task.CompletedTask;
});
// Call the next middleware in the pipeline
await _next(context);
}
// Call the next middleware in the pipeline
await _next(context);
}
}

View File

@ -1,4 +1,4 @@
using DigitalData.Core.DTO;
using DigitalData.Core.Abstraction.Application.DTO;
using Microsoft.AspNetCore.Mvc;
using System.Text;

View File

@ -1,53 +1,50 @@
using Microsoft.AspNetCore.Builder;
using System.Configuration;
namespace DigitalData.Core.API;
namespace DigitalData.Core.API
/// <summary>
/// Provides extension methods for adding middleware to the application's request pipeline.
/// </summary>
public static class DIExtensions
{
/// <summary>
/// Provides extension methods for adding middleware to the application's request pipeline.
/// Adds the <see cref="CSPMiddleware"/> to the application's request pipeline to include
/// Content Security Policy (CSP) headers in the HTTP response.
/// </summary>
public static class DIExtensions
{
/// <summary>
/// Adds the <see cref="CSPMiddleware"/> to the application's request pipeline to include
/// Content Security Policy (CSP) headers in the HTTP response.
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="policy">
/// The CSP policy string with placeholders. The first format parameter {0} will be replaced
/// by the nonce value.
/// </param>
/// <returns>The application builder with the CSP middleware added.</returns>
public static IApplicationBuilder UseCSPMiddleware(this IApplicationBuilder app, string policy)
=> app.UseMiddleware<CSPMiddleware>(policy);
/// <param name="app">The application builder.</param>
/// <param name="policy">
/// The CSP policy string with placeholders. The first format parameter {0} will be replaced
/// by the nonce value.
/// </param>
/// <returns>The application builder with the CSP middleware added.</returns>
public static IApplicationBuilder UseCSPMiddleware(this IApplicationBuilder app, string policy)
=> app.UseMiddleware<CSPMiddleware>(policy);
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
public static bool IsDevOrDiP(this WebApplicationBuilder builder) => builder.Environment.IsDevelopment() || builder.IsDiP();
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
public static bool IsDevOrDiP(this WebApplicationBuilder builder) => builder.Environment.IsDevelopment() || builder.IsDiP();
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
public static bool IsDevOrDiP(this WebApplication app) => app.Environment.IsDevelopment() || app.IsDiP();
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
public static bool IsDevOrDiP(this WebApplication app) => app.Environment.IsDevelopment() || app.IsDiP();
/// <summary>
/// Configures the services with options from the specified section of the appsettings.json file.
@ -65,5 +62,4 @@ namespace DigitalData.Core.API
builder.Services.Configure<T>(section);
return builder;
}
}
}
}

View File

@ -8,7 +8,7 @@
<OutputType>Library</OutputType>
<Description>This package provides a comprehensive set of API controllers and related utilities for the DigitalData.Core library. It includes generic CRUD controllers, localization extensions, middleware for security policies, and application model conventions.</Description>
<PackageId>DigitalData.Core.API</PackageId>
<Version>2.1.1</Version>
<Version>2.2.1</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.API</Product>
@ -16,8 +16,8 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core api</PackageTags>
<PackageIcon>core_icon.png</PackageIcon>
<AssemblyVersion>2.1.1</AssemblyVersion>
<FileVersion>2.1.1</FileVersion>
<AssemblyVersion>2.2.1</AssemblyVersion>
<FileVersion>2.2.1</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -32,8 +32,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
<ProjectReference Include="..\DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +1,6 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.DTO;
namespace DigitalData.Core.API
{
@ -12,6 +12,7 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class ReadControllerBase<TReadService, TReadDto, TEntity, TId> : ControllerBase
where TReadService : IReadService<TReadDto, TEntity, TId>
where TReadDto : class

View File

@ -1,5 +1,5 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.DTO;
using Microsoft.AspNetCore.Mvc;
namespace DigitalData.Core.API
@ -13,6 +13,7 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class ReadControllerBaseWithErrorHandling<TReadService, TReadDto, TEntity, TId> : ControllerBase
where TReadService : IReadService<TReadDto, TEntity, TId>
where TReadDto : class

View File

@ -0,0 +1,19 @@
#if NET
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Represents a base Data Transfer Object (DTO) with an identifier.
/// </summary>
/// <typeparam name="TId">The type of the identifier.</typeparam>
/// <param name="Id">The identifier of the DTO.</param>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public record BaseDTO<TId>(TId Id) where TId : notnull
{
/// <summary>
/// Returns the hash code for this instance, based on the identifier.
/// This override ensures that the hash code is derived consistently from the identifier.
/// </summary>
/// <returns>A hash code for the current object, derived from the identifier.</returns>
public override int GetHashCode() => Id.GetHashCode();
}
#endif

View File

@ -0,0 +1,75 @@
#if NET
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Represents settings related to user cookie consent dialogs. Designed to be serialized into JSON format for use with JavaScript frontend libraries,
/// such as the bootstrap-cookie-consent-settings at the GitHub repository: https://github.com/shaack/bootstrap-cookie-consent-settings
/// </summary>
public class CookieConsentSettings
{
/// <summary>
/// URL to the privacy policy page.
/// </summary>
public string? PrivacyPolicyUrl { get; set; }
/// <summary>
/// URL to the legal notice page.
/// </summary>
public string? LegalNoticeUrl { get; set; }
/// <summary>
/// URL to the content of the dialog box.
/// </summary>
public string? ContentURL { get; set; }
/// <summary>
/// CSS class for the 'Agree' button.
/// </summary>
public string? ButtonAgreeClass { get; set; }
/// <summary>
/// CSS class for the 'Don't Agree' button.
/// </summary>
public string? ButtonDontAgreeClass { get; set; }
/// <summary>
/// CSS class for the 'Save' button.
/// </summary>
public string? ButtonSaveClass { get; set; }
/// <summary>
/// Language in which the modal is displayed.
/// </summary>
public string? Lang { get; set; }
/// <summary>
/// Default language for the modal if the user's browser language is not supported.
/// </summary>
public string? DefaultLang { get; set; }
/// <summary>
/// Name of the cookie used to store the consent status.
/// </summary>
public string? CookieName { get; set; }
/// <summary>
/// Number of days the cookie will be stored.
/// </summary>
public int CookieStorageDays { get; set; }
/// <summary>
/// Identifier for the modal dialog element.
/// </summary>
public string? ModalId { get; set; }
/// <summary>
/// Indicates whether to also store the settings in the browser's localStorage.
/// </summary>
public bool AlsoUseLocalStorage { get; set; }
/// <summary>
/// List of categories for cookie consent.
/// </summary>
public List<string>? Categories { get; set; }
}
#endif

View File

@ -0,0 +1,36 @@
#if NET
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Configuration;
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Provides extension methods for dependency injection.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static class DIExtensions
{
/// <summary>
/// Adds the <see cref="CookieConsentSettings"/> to the service collection.
/// </summary>
/// <param name="services">The service collection to add the settings to.</param>
/// <returns>The updated service collection.</returns>
/// <exception cref="ConfigurationErrorsException">
/// Thrown if the 'CookieConsentSettings' section is missing or improperly configured in appsettings.json.
/// </exception>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static IServiceCollection AddCookieConsentSettings(this IServiceCollection services)
{
services.AddSingleton(sp =>
{
var configuration = sp.GetRequiredService<IConfiguration>();
var settings = configuration.GetSection("CookieConsentSettings").Get<CookieConsentSettings>();
return settings is null
? throw new ConfigurationErrorsException("The 'CookieConsentSettings' section is missing or improperly configured in appsettings.json.")
: settings;
});
return services;
}
}
#endif

View File

@ -0,0 +1,395 @@
#if NET
using Microsoft.Extensions.Logging;
using System.Text;
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Provides extension methods for data transfer objects (DTOs).
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static class DTOExtensions
{
/// <summary>
/// Adds a single message to the result, if not null.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the message to.</param>
/// <param name="message">The message to add.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Message<T>(this T result, string? message) where T : Result
{
if (message is not null)
result.Messages.Add(message);
return result;
}
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
internal static IEnumerable<T> FilterNull<T>(this IEnumerable<T?> list)
{
foreach (var item in list)
if (item is not null)
yield return item;
}
/// <summary>
/// Adds multiple messages to the result, after removing nulls.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the messages to.</param>
/// <param name="messages">The messages to add.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Message<T>(this T result, params string?[] messages) where T : Result
{
result.Messages.AddRange(messages.FilterNull());
return result;
}
/// <summary>
/// Adds a collection of messages to the result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the messages to.</param>
/// <param name="messages">The collection of messages to add.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Message<T>(this T result, IEnumerable<string?> messages) where T : Result
{
result.Messages.AddRange(messages.FilterNull());
return result;
}
/// <summary>
/// Adds a notice to the result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the notice to.</param>
/// <param name="notice">The notice to add.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Notice<T>(this T result, Notice notice) where T : Result
{
result.Notices.Add(notice);
return result;
}
/// <summary>
/// Adds a collection of notices to the result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the notices to.</param>
/// <param name="notices">The collection of notices to add.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Notice<T>(this T result, IEnumerable<Notice> notices) where T : Result
{
result.Notices.AddRange(notices);
return result;
}
/// <summary>
/// Adds notices with a specific log level and flags to the result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the notices to.</param>
/// <param name="level">The log level of the notices.</param>
/// <param name="flags">The flags associated with the notices.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Notice<T>(this T result, LogLevel level, params Enum[] flags) where T : Result
{
var notices = flags.Select(flag => new Notice()
{
Flag = flag,
Level = level
});
result.Notices.AddRange(notices);
return result;
}
/// <summary>
/// Adds a notice with a specific log level, flag, and messages to the result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the notice to.</param>
/// <param name="level">The log level of the notice.</param>
/// <param name="flag">The flag associated with the notice.</param>
/// <param name="messages">The messages to add to the notice.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Notice<T>(this T result, LogLevel level, Enum flag, params string?[] messages) where T : Result
{
result.Notices.Add(new Notice()
{
Flag = flag,
Level = level,
Messages = messages.FilterNull().ToList()
});
return result;
}
/// <summary>
/// Adds a notice with a specific log level and messages to the result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="result">The result to add the notice to.</param>
/// <param name="level">The log level of the notice.</param>
/// <param name="messages">The messages to add to the notice.</param>
/// <returns>The updated result.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Notice<T>(this T result, LogLevel level, params string[] messages) where T : Result
{
result.Notices.Add(new Notice()
{
Flag = null,
Level = level,
Messages = messages.FilterNull().ToList()
});
return result;
}
/// <summary>
/// Checks if any notice has the specified flag.
/// </summary>
/// <param name="notices">The collection of notices to check.</param>
/// <param name="flag">The flag to check for.</param>
/// <returns>True if any notice has the specified flag; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static bool HasFlag(this IEnumerable<Notice> notices, Enum flag) => notices.Any(n => n.Flag?.ToString() == flag.ToString());
/// <summary>
/// Checks if any notice has any of the specified flags.
/// </summary>
/// <param name="notices">The collection of notices to check.</param>
/// <param name="flags">The flags to check for.</param>
/// <returns>True if any notice has any of the specified flags; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static bool HasAnyFlag(this IEnumerable<Notice> notices, params Enum[] flags) => flags.Any(f => notices.HasFlag(f));
/// <summary>
/// Executes a function based on the success or failure of the task result,
/// without using result data.
/// </summary>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="tResult">The task returning a result to evaluate.</param>
/// <param name="Success">The function to execute if the result is successful.</param>
/// <param name="Fail">The function to execute if the result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static I? Then<I>(this Result result, Func<I> Success)
{
return result.IsSuccess ? Success() : default;
}
/// <summary>
/// Executes a function based on the success or failure of the task result,
/// using the data in the result.
/// </summary>
/// <typeparam name="T">The type of the data in the result.</typeparam>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="tResult">The task returning a data result to evaluate.</param>
/// <param name="Success">The function to execute if the data result is successful.</param>
/// <param name="Fail">The function to execute if the data result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I?> ThenAsync<I>(this Result result, Func<Task<I>> SuccessAsync)
{
return result.IsSuccess ? await SuccessAsync() : default;
}
/// <summary>
/// Executes a function based on the success or failure of the result.
/// </summary>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="result">The result to evaluate.</param>
/// <param name="Success">The function to execute if the result is successful.</param>
/// <param name="Fail">The function to execute if the result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static I Then<I>(this Result result, Func<I> Success, Func<List<string>, List<Notice>, I> Fail)
{
return result.IsSuccess ? Success() : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Asynchronously executes a function based on the success or failure of the result.
/// </summary>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="result">The result to evaluate.</param>
/// <param name="SuccessAsync">The asynchronous function to execute if the result is successful.</param>
/// <param name="Fail">The function to execute if the result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I> ThenAsync<I>(this Result result, Func<Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
{
return result.IsSuccess ? await SuccessAsync() : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Executes a function based on the success or failure of the data result.
/// </summary>
/// <typeparam name="T">The type of the data in the result.</typeparam>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="result">The data result to evaluate.</param>
/// <param name="Success">The function to execute if the data result is successful.</param>
/// <param name="Fail">The function to execute if the data result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static I Then<T, I>(this DataResult<T> result, Func<T, I> Success, Func<List<string>, List<Notice>, I> Fail)
{
return result.IsSuccess ? Success(result.Data) : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Asynchronously executes a function based on the success or failure of the data result.
/// </summary>
/// <typeparam name="T">The type of the data in the result.</typeparam>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="result">The data result to evaluate.</param>
/// <param name="SuccessAsync">The asynchronous function to execute if the data result is successful.</param>
/// <param name="Fail">The function to execute if the data result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I> ThenAsync<T, I>(this DataResult<T> result, Func<T, Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
{
return result.IsSuccess ? await SuccessAsync(result.Data) : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Asynchronously executes a function based on the success or failure of a task returning a result.
/// </summary>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="tResult">The task returning a result to evaluate.</param>
/// <param name="Success">The function to execute if the result is successful.</param>
/// <param name="Fail">The function to execute if the result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I> ThenAsync<I>(this Task<Result> tResult, Func<I> Success, Func<List<string>, List<Notice>, I> Fail)
{
Result result = await tResult;
return result.IsSuccess ? Success() : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Asynchronously executes a function based on the success or failure of a task returning a result.
/// </summary>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="tResult">The task returning a result to evaluate.</param>
/// <param name="SuccessAsync">The asynchronous function to execute if the result is successful.</param>
/// <param name="Fail">The function to execute if the result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I> ThenAsync<I>(this Task<Result> tResult, Func<Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
{
Result result = await tResult;
return result.IsSuccess ? await SuccessAsync() : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Asynchronously executes a function based on the success or failure of a task returning a data result.
/// </summary>
/// <typeparam name="T">The type of the data in the result.</typeparam>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="tResult">The task returning a data result to evaluate.</param>
/// <param name="Success">The function to execute if the data result is successful.</param>
/// <param name="Fail">The function to execute if the data result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I> ThenAsync<T, I>(this Task<DataResult<T>> tResult, Func<T, I> Success, Func<List<string>, List<Notice>, I> Fail)
{
DataResult<T> result = await tResult;
return result.IsSuccess ? Success(result.Data) : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Asynchronously executes a function based on the success or failure of a task returning a data result.
/// </summary>
/// <typeparam name="T">The type of the data in the result.</typeparam>
/// <typeparam name="I">The type of the return value.</typeparam>
/// <param name="tResult">The task returning a data result to evaluate.</param>
/// <param name="SuccessAsync">The asynchronous function to execute if the data result is successful.</param>
/// <param name="Fail">The function to execute if the data result is a failure.</param>
/// <returns>The result of the executed function.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static async Task<I> ThenAsync<T, I>(this Task<DataResult<T>> tResult, Func<T, Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
{
DataResult<T> result = await tResult;
return result.IsSuccess ? await SuccessAsync(result.Data) : Fail(result.Messages, result.Notices);
}
/// <summary>
/// Joins the values into a single string with optional start, separator, and end strings.
/// </summary>
/// <typeparam name="T">The type of the values.</typeparam>
/// <param name="values">The values to join.</param>
/// <param name="start">The starting string.</param>
/// <param name="separator">The separator string.</param>
/// <param name="end">The ending string.</param>
/// <returns>The joined string.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static string Join<T>(this IEnumerable<T> values, string start = "", string seperator = ". ", string end = ".")
=> new StringBuilder(start).Append(string.Join(seperator, values)).Append(end).ToString();
/// <summary>
/// Logs the notices using the specified logger.
/// </summary>
/// <param name="logger">The logger to use.</param>
/// <param name="notices">The collection of notices to log.</param>
/// <param name="start">The starting string for each notice.</param>
/// <param name="separator">The separator string for messages in each notice.</param>
/// <param name="end">The ending string for each notice.</param>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static void LogNotice(this ILogger logger, IEnumerable<Notice> notices, string start = ": ", string seperator = ". ", string end = ".\n")
{
foreach (LogLevel level in Enum.GetValues(typeof(LogLevel)))
{
var logNotices = notices.Where(n => n.Level == level);
if (!logNotices.Any())
continue;
var sb = new StringBuilder();
foreach (Notice notice in logNotices)
{
if (notice.Flag is not null)
sb.Append(notice.Flag);
if (notice.Messages.Any())
sb.Append(start).Append(string.Join(seperator, notice.Messages)).AppendLine(end);
else sb.Append(end);
}
logger.Log(level, sb.ToString());
}
}
/// <summary>
/// Logs the notices from a result using the specified logger.
/// </summary>
/// <param name="logger">The logger to use.</param>
/// <param name="result">The result containing the notices to log.</param>
/// <param name="start">The starting string for each notice.</param>
/// <param name="separator">The separator string for messages in each notice.</param>
/// <param name="end">The ending string for each notice.</param>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static void LogNotice(this ILogger logger, Result result, string start = ": ", string seperator = ". ", string end = ".\n")
=> logger.LogNotice(notices: result.Notices, start: start, seperator: seperator, end: end);
/// <summary>
/// Determines if the data result is right (true).
/// </summary>
/// <param name="bResult">The data result to evaluate.</param>
/// <returns>True if the data result is true; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static bool IsRight(this DataResult<bool> bResult) => bResult.Data;
/// <summary>
/// Determines if the data result is wrong (false).
/// </summary>
/// <param name="bResult">The data result to evaluate.</param>
/// <returns>True if the data result is false; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static bool IsWrong(this DataResult<bool> bResult) => !bResult.Data;
}
#endif

View File

@ -0,0 +1,30 @@
#if NET
using System.Text.Json.Serialization;
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Represents a result of an operation that includes data, inheriting from <see cref="Result"/>.
/// </summary>
/// <typeparam name="T">The type of the data included in the result.</typeparam>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public class DataResult<T> : Result
{
/// <summary>
/// Gets or sets the data included in the result. This property is required.
/// It will be ignored during JSON serialization if the value is null.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public required T Data { get; set; }
/// <summary>
/// Converts the current <see cref="DataResult{T}"/> to a failed <see cref="DataResult{I}"/>,
/// preserving the messages and notices.
/// </summary>
/// <typeparam name="I">The type of the data in the new failed result.</typeparam>
/// <returns>A failed <see cref="DataResult{I}"/> with the current messages and notices.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public DataResult<I> ToFail<I>() => Fail<I>().Message(Messages).Notice(Notices);
}
#endif

View File

@ -0,0 +1,52 @@
#if NET
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Defines flags that indicate specific types of status or conditions in a service operation.
/// These flags help in categorizing and identifying specific circumstances or issues that may arise during execution.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public enum Flag
{
/// <summary>
/// Indicates a security breach or vulnerability has been detected during the service operation.
/// </summary>
SecurityBreach,
/// <summary>
/// Indicates a potential issue with data integrity during the service operation.
/// This flag is used when data may have been altered, corrupted, or is otherwise unreliable,
/// which could impact the accuracy or trustworthiness of the operation's results.
/// </summary>
DataIntegrityIssue,
/// <summary>
/// Indicates that either a security breach, a data integrity issue, or both have been detected during the service operation.
/// This flag is used when it is not sure whether the problem is security or data integrity. In this case, data integrity should be checked first.
/// </summary>
SecurityBreachOrDataIntegrity,
/// <summary>
/// Indicates a possible security breach during the service operation.
/// </summary>
PossibleSecurityBreach,
/// <summary>
/// Indicates a possible issue with data integrity during the service operation.
/// This flag is used when there is a suspicion of data alteration, corruption, or unreliability.
/// </summary>
PossibleDataIntegrityIssue,
/// <summary>
/// Indicates that either a possible security breach, a possible data integrity issue, or both have been detected during the service operation.
/// This flag is used when it is uncertain whether the issue is related to security, data integrity, or both.
/// </summary>
PossibleSecurityBreachOrDataIntegrity,
/// <summary>
/// Indicates that the requested resource or operation could not be found.
/// This flag is used when the specified item or condition does not exist or is unavailable.
/// </summary>
NotFound
}
#endif

View File

@ -0,0 +1,30 @@
#if NET
using Microsoft.Extensions.Logging;
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Represents a notice for logging purposes, containing a flag, log level, and associated messages.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public class Notice
{
/// <summary>
/// Gets or sets an optional flag associated with the notice.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public Enum? Flag { get; init; } = null;
/// <summary>
/// Gets or sets the log level for the notice.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public LogLevel Level { get; init; } = LogLevel.None;
/// <summary>
/// Gets a list of messages associated with the notice.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public List<string> Messages { get; init; } = new();
}
#endif

View File

@ -0,0 +1,110 @@
#if NET
using System.Text.Json.Serialization;
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary>
/// Represents the result of an operation, containing information about its success or failure,
/// messages for the client, and notices for logging.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public class Result
{
/// <summary>
/// Gets or sets a value indicating whether the operation was successful.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public bool IsSuccess { get; set; } = false;
/// <summary>
/// Gets a value indicating whether the operation failed.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public bool IsFailed => !IsSuccess;
/// <summary>
/// Gets a list of messages intended for the client.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public List<string> Messages { get; init; } = new();
/// <summary>
/// Gets a list of notices intended for logging purposes. This property is ignored during JSON serialization.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
[JsonIgnore]
public List<Notice> Notices = new();
/// <summary>
/// Creates a <see cref="DataResult{T}"/> with the specified data.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <returns>A new <see cref="DataResult{T}"/> instance.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public DataResult<T> Data<T>(T data) => new()
{
IsSuccess = IsSuccess,
Messages = Messages,
Notices = Notices,
Data = data
};
/// <summary>
/// Checks if any notice has the specified flag.
/// </summary>
/// <param name="flag">The flag to check.</param>
/// <returns>True if any notice has the specified flag; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public bool HasFlag(Enum flag) => Notices.Any(n => n.Flag?.ToString() == flag.ToString());
/// <summary>
/// Checks if any notice has any of the specified flags.
/// </summary>
/// <param name="flags">The flags to check.</param>
/// <returns>True if any notice has any of the specified flags; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public bool HasAnyFlag(params Enum[] flags) => flags.Any(HasFlag);
/// <summary>
/// Creates a new successful <see cref="Result"/>.
/// </summary>
/// <returns>A new successful <see cref="Result"/>.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static Result Success() => new() { IsSuccess = true };
/// <summary>
/// Creates a new failed <see cref="Result"/>.
/// </summary>
/// <returns>A new failed <see cref="Result"/>.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static Result Fail() => new() { IsSuccess = false };
/// <summary>
/// Creates a new successful <see cref="DataResult{T}"/> with the specified data.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <returns>A new successful <see cref="DataResult{T}"/> with the specified data.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static DataResult<T> Success<T>(T data) => new()
{
IsSuccess = true,
Data = data
};
/// <summary>
/// Creates a new failed <see cref="DataResult{T}"/> with no data.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <returns>A new failed <see cref="DataResult{T}"/> with no data.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
#pragma warning disable CS8601 // Possible null reference assignment.
public static DataResult<T> Fail<T>() => new()
{
IsSuccess = false,
Data = default
};
#pragma warning restore CS8601 // Possible null reference assignment.
}
#endif

View File

@ -0,0 +1,79 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net462;net7.0;net8.0;net9.0</TargetFrameworks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Description>This package defines the abstraction layer for the DigitalData.Core.Application module, providing interfaces and contracts for application services, repositories, and infrastructure components in alignment with Clean Architecture principles.</Description>
<PackageId>DigitalData.Core.Abstraction.Application</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Abstraction.Application</Product>
<Copyright>Copyright 2025</Copyright>
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core application clean architecture abstraction</PackageTags>
<Version>1.5.0</Version>
<AssemblyVersion>1.5.0</AssemblyVersion>
<FileVersion>1.5.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<!-- disable for net462 -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<!-- enable for net7 and more -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net7.0' Or '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net9.0'">
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,96 @@
#if NET
namespace DigitalData.Core.Abstraction.Application;
/// <summary>
/// Provides extension methods for retrieving the value of an 'Id' property from objects.
/// </summary>
public static class EntityExtensions
{
/// <summary>
/// Attempts to retrieve the value of the 'Id' property from the specified object.
/// </summary>
/// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
/// <returns>
/// The value of the 'Id' property if it exists and is of type <typeparamref name="TId"/>; otherwise, <c>default</c>.
/// </returns>
public static TId? GetIdOrDefault<TId>(this object? obj)
{
var prop = obj?.GetType().GetProperty("Id");
return prop is not null && prop.GetValue(obj) is TId id ? id : default;
}
/// <summary>
/// Retrieves the value of the 'Id' property from the specified object, or throws an exception if not found or of the wrong type.
/// </summary>
/// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
/// <returns>The value of the 'Id' property.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if the object does not have a readable 'Id' property of type <typeparamref name="TId"/>.
/// </exception>
public static TId GetId<TId>(this object? obj)
=> obj.GetIdOrDefault<TId>()
?? throw new InvalidOperationException($"The object of type '{obj?.GetType().FullName ?? "null"}' does not have a readable 'Id' property of type '{typeof(TId).FullName}'.");
/// <summary>
/// Tries to retrieve the value of the 'Id' property from the specified object.
/// </summary>
/// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
/// <param name="id">When this method returns, contains the value of the 'Id' property if found; otherwise, the default value for the type.</param>
/// <returns>
/// <c>true</c> if the 'Id' property was found and is of type <typeparamref name="TId"/>; otherwise, <c>false</c>.
/// </returns>
public static bool TryGetId<TId>(object? obj, out TId id)
{
#pragma warning disable CS8601
id = obj.GetIdOrDefault<TId>();
#pragma warning restore CS8601
return id is not null;
}
/// <summary>
/// Attempts to retrieve the value of the 'Id' property from the specified object.
/// </summary>
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
/// <returns>
/// The value of the 'Id' property if it exists; otherwise, <c>null</c>.
/// </returns>
public static object? GetIdOrDefault(this object? obj)
{
var prop = obj?.GetType().GetProperty("Id");
return prop?.GetValue(obj);
}
/// <summary>
/// Retrieves the value of the 'Id' property from the specified object, or throws an exception if not found.
/// </summary>
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
/// <returns>The value of the 'Id' property.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if the object does not have a readable 'Id' property.
/// </exception>
public static object GetId(this object? obj)
=> obj.GetIdOrDefault()
?? throw new InvalidOperationException($"The object of type '{obj?.GetType().FullName ?? "null"}' does not have a readable 'Id' property.");
/// <summary>
/// Tries to retrieve the value of the 'Id' property from the specified object.
/// </summary>
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
/// <param name="id">
/// When this method returns, contains the value of the 'Id' property if found; otherwise, <c>null</c>.
/// </param>
/// <returns>
/// <c>true</c> if the 'Id' property was found; otherwise, <c>false</c>.
/// </returns>
public static bool TryGetId(object? obj, out object id)
{
#pragma warning disable CS8601
id = obj.GetIdOrDefault();
#pragma warning restore CS8601
return id is not null;
}
}
#endif

View File

@ -1,6 +1,5 @@
using DigitalData.Core.Abstractions.Infrastructure;
namespace DigitalData.Core.Abstractions.Application
#if NET
namespace DigitalData.Core.Abstraction.Application
{
/// <summary>
/// Implements a simplified CRUD service interface that uses a single Data Transfer Object (DTO) type for all CRUD operations,
@ -15,8 +14,10 @@ namespace DigitalData.Core.Abstractions.Application
/// This interface is useful for entities that do not require different DTOs for different operations,
/// allowing for a more concise and maintainable codebase when implementing services for such entities.
/// </remarks>
[Obsolete("Use MediatR")]
public interface IBasicCRUDService<TDto, TEntity, TId> : ICRUDService<TDto, TDto, TEntity, TId>
where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
where TDto : class where TEntity : class
{
}
}
}
#endif

View File

@ -0,0 +1,26 @@
#if NET
using DigitalData.Core.Abstraction.Application.DTO;
namespace DigitalData.Core.Abstraction.Application;
[Obsolete("Use MediatR")]
public interface ICRUDService<TCreateDto, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
where TCreateDto : class where TReadDto : class where TEntity : class
{
/// <summary>
/// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>.
/// The <see cref="DataResult{TId}"/> contains the identifier of the newly created entity on success or an error message on failure.
/// </summary>
/// <param name="createDto">The data transfer object containing the information for the new entity.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="DataResult{TId}"/> containing the identifier of the created entity or an error message.</returns>
Task<DataResult<TReadDto>> CreateAsync(TCreateDto createDto);
/// <summary>
/// Updates an existing entity based on the provided updateDTO and returns the result wrapped in an IServiceMessage,
/// indicating the success or failure of the operation, including the error messages on failure.
/// </summary>
/// <param name="updateDto">The updateDTO with updated values for the entity.</param>
/// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns>
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto);
}
#endif

View File

@ -0,0 +1,96 @@
#if NET
using DigitalData.Core.Abstraction.Application.DTO;
using System.DirectoryServices;
namespace DigitalData.Core.Abstraction.Application;
[Obsolete("Use DigitalData.ActiveDirectory")]
public interface IDirectorySearchService
{
public string ServerName { get; }
public string Root { get; }
string SearchRootPath { get; }
Dictionary<string, string> CustomSearchFilters { get; }
/// <summary>
/// Creates the connections to the server and returns a Boolean value that specifies
/// whether the specified username and password are valid.
/// </summary>
/// <param name="userName">The username that is validated on the server. See the Remarks section
/// for more information on the format of userName.</param>
/// <param name="password">The password that is validated on the server.</param>
/// <returns>True if the credentials are valid; otherwise, false.</returns>
bool ValidateCredentials(string userName, string password);
/// <summary>
/// Creates the connections to the server asynchronously and returns a Boolean value that specifies
/// whether the specified username and password are valid.
/// </summary>
/// <param name="userName">The username that is validated on the server. See the Remarks section
/// for more information on the format of userName.</param>
/// <param name="password">The password that is validated on the server.</param>
/// <returns>True if the credentials are valid; otherwise, false.</returns>
Task<bool> ValidateCredentialsAsync(string userName, string password);
/// <summary>
/// Finds all directory entries matching the specified filter.
/// </summary>
/// <param name="searchRoot">The search root.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
DataResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Finds all directory entries matching the specified filter asynchronously.
/// </summary>
/// <param name="searchRoot">The search root.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllAsync(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Finds all directory entries matching the specified filter, using the user cache.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
DataResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Finds all directory entries matching the specified filter asynchronously, using the user cache.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllByUserCacheAsync(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Sets the search root in the cache.
/// </summary>
/// <param name="username">The directory entry username.</param>
/// <param name="password">The directory entry password.</param>
void SetSearchRootCache(string username, string password);
/// <summary>
/// Gets the search root from the cache.
/// </summary>
/// <param name="username">The directory entry username.</param>
/// <returns>The cached <see cref="DirectoryEntry"/> if found; otherwise, null.</returns>
DirectoryEntry? GetSearchRootCache(string username);
}
#endif

View File

@ -0,0 +1,42 @@
#if NET
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
namespace DigitalData.Core.Abstraction.Application;
/// <summary>
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>.
/// </summary>
public interface IJWTService<TClaimValue>
{
/// <summary>
/// Generates a symmetric security key with the specified byte size.
/// </summary>
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
{
using var rng = RandomNumberGenerator.Create();
var randomBytes = new byte[byteSize];
rng.GetBytes(randomBytes);
var securityKey = new SymmetricSecurityKey(randomBytes);
return securityKey;
}
/// <summary>
/// Generates a token based on the specified claim value.
/// </summary>
/// <param name="claimValue">The claim value to encode in the token.</param>
/// <returns>A JWT as a string.</returns>
string GenerateToken(TClaimValue claimValue);
/// <summary>
/// Reads and validates a security token from a string representation.
/// </summary>
/// <param name="token">The JWT to read.</param>
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
JwtSecurityToken? ReadSecurityToken(string token);
}
#endif

View File

@ -0,0 +1,40 @@
#if NET
using DigitalData.Core.Abstraction.Application.DTO;
namespace DigitalData.Core.Abstraction.Application;
[Obsolete("Use MediatR")]
public interface IReadService<TReadDto, TEntity, TId>
where TReadDto : class where TEntity : class
{
/// <summary>
/// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult,
/// including the readDTO on success or null and an error message on failure.
/// </summary>
/// <param name="id">The identifier of the entity to retrieve.</param>
/// <returns>An DataResult containing the readDTO representing the found entity or null, with an appropriate message.</returns>
Task<DataResult<TReadDto>> ReadByIdAsync(TId id);
/// <summary>
/// Retrieves all entities and returns their readDTO representations wrapped in an IServiceResult,
/// including a collection of readDTOs on success or an error message on failure.
/// </summary>
/// <returns>An DataResult containing a collection of readDTOs representing all entities or an error message.</returns>
Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync();
/// <summary>
/// Deletes an entity by its identifier and returns the result wrapped in an IServiceMessage,
/// indicating the success or failure of the operation, including the error messages on failure.
/// </summary>
/// <param name="id">The identifier of the entity to delete.</param>
/// <returns>An Result indicating the outcome of the delete operation, with an appropriate message.</returns>
Task<Result> DeleteAsyncById(TId id);
/// <summary>
/// Asynchronously checks if an entity with the specified identifier exists within the data store.
/// </summary>
/// <param name="id">The identifier of the entity to check.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
Task<bool> HasEntity(TId id);
}
#endif

View File

@ -1,11 +1,13 @@
namespace DigitalData.Core.Abstractions.Infrastructure
#if NET
namespace DigitalData.Core.Abstraction.Application.Repository
{
/// <summary>
/// Defines the contract for CRUD operations on a repository for entities of type TEntity.
/// </summary>
/// <typeparam name="TEntity">The type of the entity this repository works with.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
public interface ICRUDRepository<TEntity, TId> where TEntity : class, IUnique<TId>
[Obsolete("Use IRepository")]
public interface ICRUDRepository<TEntity, TId> where TEntity : class
{
/// <summary>
/// Adds a new entity to the repository.
@ -59,4 +61,5 @@
/// </remarks>
Task<int> CountAsync(TId id);
}
}
}
#endif

View File

@ -1,4 +1,7 @@
namespace DigitalData.Core.Abstractions.Infrastructure
#if NETFRAMEWORK
using System.Collections.Generic;
#endif
namespace DigitalData.Core.Abstraction.Application.Repository
{
/// <summary>
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).

View File

@ -0,0 +1,113 @@
using System.Linq.Expressions;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
#endif
namespace DigitalData.Core.Abstraction.Application.Repository
#if NET
;
#elif NETFRAMEWORK
{
#endif
public interface IRepository<TEntity>
{
#region Create
#if NET
public
#endif
Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default);
#if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default);
#if NET
public
#endif
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default);
#if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default);
#endregion Create
#region Read
#if NET
public
#endif
IQueryable<TEntity> Query { get; }
#if NET
public
#endif
IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression);
#if NET
public
#endif
IEnumerable<TEntity> GetAll();
#if NET
public
#endif
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default);
#endregion Read
#region Update
#if NET
public
#endif
Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync(Action<TEntity> modification, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync(Action<TEntity> modification, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#endregion Update
#region Delete
#if NET
public
#endif
Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#if NET
public
#endif
Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#endregion Delete
#region Obsolete
[Obsolete("Use CreateAsync, UpdateAsync or DeleteAsync")]
#if NET
public
#endif
IQueryable<TEntity> Read();
[Obsolete("Use IRepository<TEntity>.Where")]
#if NET
public
#endif
IQueryable<TEntity> ReadOnly();
#endregion
}
#if NETFRAMEWORK
}
#endif

View File

@ -0,0 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
#if NETFRAMEWORK
using System;
#endif
namespace DigitalData.Core.Abstraction.Application.Repository
{
public static class ServiceProviderExtensions
{
public static IRepository<TEntity> Repository<TEntity>(this IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<IRepository<TEntity>>();
}
}
}

View File

@ -1,25 +0,0 @@
using DigitalData.Core.DTO;
namespace DigitalData.Core.Abstractions.Application
{
public interface ICRUDService<TCreateDto, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
{
/// <summary>
/// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>.
/// The <see cref="DataResult{TId}"/> contains the identifier of the newly created entity on success or an error message on failure.
/// </summary>
/// <param name="createDto">The data transfer object containing the information for the new entity.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="DataResult{TId}"/> containing the identifier of the created entity or an error message.</returns>
Task<DataResult<TId>> CreateAsync(TCreateDto createDto);
/// <summary>
/// Updates an existing entity based on the provided updateDTO and returns the result wrapped in an IServiceMessage,
/// indicating the success or failure of the operation, including the error messages on failure.
/// </summary>
/// <param name="updateDto">The updateDTO with updated values for the entity.</param>
/// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns>
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>;
}
}

View File

@ -1,94 +0,0 @@
using DigitalData.Core.DTO;
using System.DirectoryServices;
namespace DigitalData.Core.Abstractions.Application
{
public interface IDirectorySearchService
{
public string ServerName { get; }
public string Root { get; }
string SearchRootPath { get; }
Dictionary<string, string> CustomSearchFilters { get; }
/// <summary>
/// Creates the connections to the server and returns a Boolean value that specifies
/// whether the specified username and password are valid.
/// </summary>
/// <param name="userName">The username that is validated on the server. See the Remarks section
/// for more information on the format of userName.</param>
/// <param name="password">The password that is validated on the server.</param>
/// <returns>True if the credentials are valid; otherwise, false.</returns>
bool ValidateCredentials(string userName, string password);
/// <summary>
/// Creates the connections to the server asynchronously and returns a Boolean value that specifies
/// whether the specified username and password are valid.
/// </summary>
/// <param name="userName">The username that is validated on the server. See the Remarks section
/// for more information on the format of userName.</param>
/// <param name="password">The password that is validated on the server.</param>
/// <returns>True if the credentials are valid; otherwise, false.</returns>
Task<bool> ValidateCredentialsAsync(string userName, string password);
/// <summary>
/// Finds all directory entries matching the specified filter.
/// </summary>
/// <param name="searchRoot">The search root.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
DataResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Finds all directory entries matching the specified filter asynchronously.
/// </summary>
/// <param name="searchRoot">The search root.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllAsync(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Finds all directory entries matching the specified filter, using the user cache.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
DataResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Finds all directory entries matching the specified filter asynchronously, using the user cache.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllByUserCacheAsync(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
/// <summary>
/// Sets the search root in the cache.
/// </summary>
/// <param name="username">The directory entry username.</param>
/// <param name="password">The directory entry password.</param>
void SetSearchRootCache(string username, string password);
/// <summary>
/// Gets the search root from the cache.
/// </summary>
/// <param name="username">The directory entry username.</param>
/// <returns>The cached <see cref="DirectoryEntry"/> if found; otherwise, null.</returns>
DirectoryEntry? GetSearchRootCache(string username);
}
}

View File

@ -1,41 +0,0 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
namespace DigitalData.Core.Application
{
/// <summary>
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>.
/// </summary>
public interface IJWTService<TClaimValue>
{
/// <summary>
/// Generates a symmetric security key with the specified byte size.
/// </summary>
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
{
using var rng = RandomNumberGenerator.Create();
var randomBytes = new byte[byteSize];
rng.GetBytes(randomBytes);
var securityKey = new SymmetricSecurityKey(randomBytes);
return securityKey;
}
/// <summary>
/// Generates a token based on the specified claim value.
/// </summary>
/// <param name="claimValue">The claim value to encode in the token.</param>
/// <returns>A JWT as a string.</returns>
string GenerateToken(TClaimValue claimValue);
/// <summary>
/// Reads and validates a security token from a string representation.
/// </summary>
/// <param name="token">The JWT to read.</param>
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
JwtSecurityToken? ReadSecurityToken(string token);
}
}

View File

@ -1,38 +0,0 @@
using DigitalData.Core.DTO;
namespace DigitalData.Core.Abstractions.Application
{
public interface IReadService<TReadDto, TEntity, TId>
where TReadDto : class where TEntity : class
{
/// <summary>
/// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult,
/// including the readDTO on success or null and an error message on failure.
/// </summary>
/// <param name="id">The identifier of the entity to retrieve.</param>
/// <returns>An DataResult containing the readDTO representing the found entity or null, with an appropriate message.</returns>
Task<DataResult<TReadDto>> ReadByIdAsync(TId id);
/// <summary>
/// Retrieves all entities and returns their readDTO representations wrapped in an IServiceResult,
/// including a collection of readDTOs on success or an error message on failure.
/// </summary>
/// <returns>An DataResult containing a collection of readDTOs representing all entities or an error message.</returns>
Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync();
/// <summary>
/// Deletes an entity by its identifier and returns the result wrapped in an IServiceMessage,
/// indicating the success or failure of the operation, including the error messages on failure.
/// </summary>
/// <param name="id">The identifier of the entity to delete.</param>
/// <returns>An Result indicating the outcome of the delete operation, with an appropriate message.</returns>
Task<Result> DeleteAsyncById(TId id);
/// <summary>
/// Asynchronously checks if an entity with the specified identifier exists within the data store.
/// </summary>
/// <param name="id">The identifier of the entity to check.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
Task<bool> HasEntity(TId id);
}
}

View File

@ -0,0 +1,31 @@
#if NET
using Microsoft.Extensions.Configuration;
namespace DigitalData.Core.Abstractions;
/// <summary>
/// Extension methods for the <see cref="IConfiguration"/> interface, providing
/// additional functionality for retrieving configuration values with default behavior.
/// </summary>
public static class ConfigurationExtension
{
/// <summary>
/// Retrieves a configuration value for the specified key, or returns a default value
/// of type <typeparamref name="T"/> if the configuration is not found or the key is null.
/// </summary>
/// <typeparam name="T">The type of the object to retrieve from the configuration.</typeparam>
/// <param name="configuration">The <see cref="IConfiguration"/> instance.</param>
/// <param name="key">The optional key to look for in the configuration. If null, the method
/// retrieves the root configuration.</param>
/// <returns>
/// An instance of <typeparamref name="T"/> populated from the configuration values, or
/// a new instance of <typeparamref name="T"/> if no matching configuration is found.
/// </returns>
public static T GetOrDefault<T>(this IConfiguration configuration, string? key = null)
where T : new()
=> (key is null
? configuration.Get<T>()
: configuration.GetSection(key).Get<T>())
?? new T();
}
#endif

View File

@ -0,0 +1,42 @@
#if NET
namespace DigitalData.Core.Abstractions;
/// <summary>
/// A deferred implementation of <see cref="IServiceProvider"/> that allows the <see cref="IServiceProvider"/> instance
/// to be provided at a later time via a factory callback.
/// </summary>
/// <remarks>
/// This is particularly useful when service registration requires an <see cref="IServiceProvider"/> instance,
/// but the service provider is not yet available at registration time.
/// By using <see cref="DeferredServiceProvider"/>, a service can safely resolve dependencies once
/// the application's service provider has been built, ensuring a single consistent scope.
/// </remarks>
public class DeferredServiceProvider : IServiceProvider
{
private Lazy<IServiceProvider>? _serviceProvider;
/// <summary>
/// Sets the factory that will be used to lazily initialize the <see cref="IServiceProvider"/> instance.
/// </summary>
public Func<IServiceProvider> Factory
{
set => _serviceProvider = new(value);
}
/// <summary>
/// Retrieves the requested service object from the deferred <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">The type of service object to retrieve.</param>
/// <returns>The requested service object, or <c>null</c> if the service is not available.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if the service provider factory has not been set before calling <see cref="GetService"/>.
/// </exception>
public object? GetService(Type serviceType)
{
if (_serviceProvider is null)
throw new InvalidOperationException("The service provider has not been initialized. Make sure 'Factory' is set before calling 'GetService'.");
return _serviceProvider.Value.GetService(serviceType);
}
}
#endif

View File

@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net462;net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- NuGet Package Metadata -->
<PackageId>DigitalData.Core.Abstractions</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Abstractions</Product>
<Description>This package contains abstractions for the DigitalData.Core library, developed according to the principles of Clean Architecture. It promotes separation of concerns and enables independent core logic.</Description>
<Description>This package contains abstractions for the DigitalData.Core library.</Description>
<PackageTags>digital data core abstractions clean architecture</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Copyright 2024</Copyright>
@ -17,28 +16,53 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>3.4.3</Version>
<AssemblyVersion>3.4.3</AssemblyVersion>
<FileVersion>3.4.3</FileVersion>
<Version>4.3.0</Version>
<AssemblyVersion>4.3.0</AssemblyVersion>
<FileVersion>4.3.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\nuget-package-icons\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
</ItemGroup>
<!-- disable for net462 -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
<!-- enable for net7 and more -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net7.0' Or '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net9.0'">
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,142 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
#endif
namespace DigitalData.Core.Abstractions
{
public class Factory : IServiceProvider, IServiceCollection
{
public static readonly Factory Shared = new Factory();
private readonly IServiceCollection _serviceCollection = new ServiceCollection();
private readonly Lazy<IServiceProvider> _lazyServiceProvider;
private bool IsBuilt => _lazyServiceProvider.IsValueCreated;
private PostBuildBehavior _pbBehavior = PostBuildBehavior.ThrowException;
public Factory BehaveOnPostBuild(PostBuildBehavior postBuildBehavior)
{
_pbBehavior = postBuildBehavior;
return this;
}
public Factory()
{
_lazyServiceProvider = new Lazy<IServiceProvider>(() => _serviceCollection.BuildServiceProvider());
}
#region Service Provider
public object
#if NET
?
#endif
GetService(Type serviceType)
{
return _lazyServiceProvider.Value.GetService(serviceType);
}
public IServiceScopeFactory ScopeFactory => this.GetRequiredService<IServiceScopeFactory>();
#endregion
#region Service Collection
public int Count => _serviceCollection.Count;
public bool IsReadOnly => _serviceCollection.IsReadOnly;
public ServiceDescriptor this[int index]
{
get => _serviceCollection[index];
set
{
if (EnsureBuilt())
return;
_serviceCollection[index] = value;
}
}
public int IndexOf(ServiceDescriptor item)
{
return _serviceCollection.IndexOf(item);
}
public void Insert(int index, ServiceDescriptor item)
{
if (EnsureBuilt())
return;
_serviceCollection.Insert(index, item);
}
public void RemoveAt(int index)
{
if (EnsureBuilt())
return;
_serviceCollection.RemoveAt(index);
}
public void Add(ServiceDescriptor item)
{
if (EnsureBuilt())
return;
_serviceCollection.Add(item);
}
public void Clear()
{
if (EnsureBuilt())
return;
_serviceCollection.Clear();
}
public bool Contains(ServiceDescriptor item)
{
return _serviceCollection.Contains(item);
}
public void CopyTo(ServiceDescriptor[] array, int arrayIndex)
{
_serviceCollection.CopyTo(array, arrayIndex);
}
public bool Remove(ServiceDescriptor item)
{
if (EnsureBuilt())
return false;
return _serviceCollection.Remove(item);
}
public IEnumerator<ServiceDescriptor> GetEnumerator()
{
return _serviceCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (_serviceCollection as IEnumerable).GetEnumerator();
}
#endregion
#region Helpers
private bool EnsureBuilt()
{
if (IsBuilt)
{
return _pbBehavior == PostBuildBehavior.ThrowException
? throw new InvalidOperationException("Service provider has already been built. No further service modifications are allowed.")
: true;
}
else
return false;
}
#endregion
}
public enum PostBuildBehavior
{
ThrowException,
Ignore
}
}

View File

@ -1,7 +0,0 @@
namespace DigitalData.Core.Abstractions
{
public interface IUnique<T>
{
public T Id { get; }
}
}

View File

@ -1,34 +0,0 @@
using System.Linq.Expressions;
namespace DigitalData.Core.Abstractions.Infrastructure;
public static class Extensions
{
#region Create
public static Task<TEntity> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, TDto dto, CancellationToken ct = default)
{
var entity = repository.Mapper.Map(dto);
return repository.CreateAsync(entity, ct);
}
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, IEnumerable<TDto> dtos, CancellationToken ct = default)
{
var entities = dtos.Select(dto => repository.Mapper.Map(dto));
return repository.CreateAsync(entities, ct);
}
#endregion
#region Read
public static async Task<TEntity?> ReadFirstOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).FirstOrDefault();
public static async Task<TEntity> ReadFirstAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).First();
public static async Task<TEntity?> ReadSingleOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).SingleOrDefault();
public static async Task<TEntity> ReadSingleAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).Single();
#endregion
}

View File

@ -1,24 +0,0 @@
using System.Linq.Expressions;
namespace DigitalData.Core.Abstractions.Infrastructure;
public interface IRepository<TEntity>
{
public IEntityMapper<TEntity> Mapper { get; }
public Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
public Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
public Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
public Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
public Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
public Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
}

View File

@ -0,0 +1,18 @@
namespace DigitalData.Core.Abstractions.Interfaces
#if NET
;
#elif NETFRAMEWORK
{
#endif
/// <summary>
/// Ensures that extension methods are handled securely
/// </summary>
/// <typeparam name="Entity"></typeparam>
public interface IDto<Entity> where Entity : IEntity
{
}
#if NETFRAMEWORK
}
#endif

View File

@ -0,0 +1,17 @@
namespace DigitalData.Core.Abstractions.Interfaces
#if NET
;
#elif NETFRAMEWORK
{
#endif
/// <summary>
/// Ensures that extension methods are handled securely
/// </summary>
public interface IEntity
{
}
#if NETFRAMEWORK
}
#endif

View File

@ -0,0 +1,33 @@
#if NET
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Abstractions;
/// <summary>
/// Extension methods for <see cref="IServiceProvider"/> to retrieve configuration options.
/// </summary>
public static class ServiceProviderExtensions
{
/// <summary>
/// Retrieves an instance of <typeparamref name="TOptions"/> from the <see cref="IServiceProvider"/>.
/// If the options are not registered, returns null.
/// </summary>
/// <typeparam name="TOptions">The type of the options to retrieve.</typeparam>
/// <param name="service">The service provider instance.</param>
/// <returns>An instance of <typeparamref name="TOptions"/> or null if not found.</returns>
public static TOptions? GetOptions<TOptions>(this IServiceProvider service) where TOptions : class
=> service.GetService<IOptions<TOptions>>()?.Value;
/// <summary>
/// Retrieves an instance of <typeparamref name="TOptions"/> from the <see cref="IServiceProvider"/>.
/// Throws an exception if the options are not registered.
/// </summary>
/// <typeparam name="TOptions">The type of the options to retrieve.</typeparam>
/// <param name="service">The service provider instance.</param>
/// <returns>An instance of <typeparamref name="TOptions"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the options are not registered.</exception>
public static TOptions GetRequiredOptions<TOptions>(this IServiceProvider service) where TOptions : class
=> service.GetRequiredService<IOptions<TOptions>>().Value;
}
#endif

View File

@ -1,13 +0,0 @@
namespace DigitalData.Core.Abstractions
{
public static class ServiceResultExtensions
{
public static bool Try<T>(this T? nullableResult, out T result)
{
#pragma warning disable CS8601 // Possible null reference assignment.
result = nullableResult;
#pragma warning restore CS8601 // Possible null reference assignment.
return nullableResult is not null;
}
}
}

View File

@ -1,7 +1,6 @@
using AutoMapper;
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.Repository;
namespace DigitalData.Core.Application
{
@ -18,9 +17,10 @@ namespace DigitalData.Core.Application
/// reducing the need for multiple DTOs and simplifying the data mapping process. It leverages AutoMapper for object mapping
/// and a culture-specific translation service for any necessary text translations, ensuring a versatile and internationalized approach to CRUD operations.
/// </remarks>
[Obsolete("Use MediatR")]
public class BasicCRUDService<TCRUDRepository, TDto, TEntity, TId> :
CRUDService<TCRUDRepository, TDto, TDto, TEntity, TId>, IBasicCRUDService<TDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TDto : class where TEntity : class
{
/// <summary>
/// Initializes a new instance of the BasicCRUDService with the specified repository, translation service, and AutoMapper configuration.

View File

@ -1,9 +1,8 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstractions.Infrastructure;
using AutoMapper;
using DigitalData.Core.DTO;
using DigitalData.Core.Abstractions;
using AutoMapper;
using Microsoft.Extensions.Logging;
using DigitalData.Core.Abstraction.Application.DTO;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstraction.Application;
namespace DigitalData.Core.Application
{
@ -14,8 +13,10 @@ namespace DigitalData.Core.Application
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
/// <typeparam name="TEntity">The entity type.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
///
[Obsolete("Use MediatR")]
public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TEntity, TId> : ReadService<TCRUDRepository, TReadDto, TEntity, TId>, ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TEntity : class
{
/// <summary>
@ -32,11 +33,12 @@ namespace DigitalData.Core.Application
/// </summary>
/// <param name="createDto">The DTO to create an entity from.</param>
/// <returns>A service result indicating success or failure, including the entity DTO.</returns>
public virtual async Task<DataResult<TId>> CreateAsync(TCreateDto createDto)
public virtual async Task<DataResult<TReadDto>> CreateAsync(TCreateDto createDto)
{
var entity = _mapper.Map<TEntity>(createDto);
var createdEntity = await _repository.CreateAsync(entity);
return createdEntity is null ? Result.Fail<TId>() : Result.Success(createdEntity.Id);
var dto = _mapper.Map<TReadDto>(createdEntity);
return createdEntity is null ? Result.Fail<TReadDto>() : Result.Success(dto);
}
/// <summary>
@ -44,12 +46,12 @@ namespace DigitalData.Core.Application
/// </summary>
/// <param name="updateDto">The DTO to update an entity from.</param>
/// <returns>A service message indicating success or failure.</returns>
public virtual async Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>
public virtual async Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto)
{
var currentEntitiy = await _repository.ReadByIdAsync(updateDto.Id);
var currentEntitiy = await _repository.ReadByIdAsync(updateDto.GetId<TId>());
if (currentEntitiy is null)
return Result.Fail().Notice(LogLevel.Warning, Flag.NotFound, $"{updateDto.Id} is not found in update process of {GetType()} entity.");
return Result.Fail().Notice(LogLevel.Warning, Flag.NotFound, $"{updateDto.GetIdOrDefault<TId>()} is not found in update process of {GetType()} entity.");
var entity = _mapper.Map(updateDto, currentEntitiy);

View File

@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstraction.Application;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

View File

@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Description>This package provides implementations for application services within the DigitalData.Core.Abstractions library. It includes generic CRUD operations using Entity Framework Core, AutoMapper integration for object mapping, and additional services such as JWT handling and directory search functionality, adhering to Clean Architecture principles to ensure separation of concerns and maintainability.</Description>
<Description>This package includes generic CRUD operations using Entity Framework Core, AutoMapper integration for object mapping, and additional services such as JWT handling and directory search functionality, adhering to Clean Architecture principles.</Description>
<PackageId>DigitalData.Core.Application</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
@ -14,20 +14,19 @@
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core application clean architecture</PackageTags>
<Version>3.2.1</Version>
<AssemblyVersion>3.2.1</AssemblyVersion>
<FileVersion>3.2.1</FileVersion>
<Version>3.4.0</Version>
<AssemblyVersion>3.4.0</AssemblyVersion>
<FileVersion>3.4.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\nuget-package-icons\core_icon.png">
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
@ -37,20 +36,26 @@
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.Abstraction.Application\DigitalData.Core.Abstraction.Application.csproj" />
</ItemGroup>
</Project>

View File

@ -1,10 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.DirectoryServices;
using Microsoft.Extensions.Caching.Memory;
using System.DirectoryServices.AccountManagement;
using DigitalData.Core.DTO;
using Microsoft.Extensions.Options;
using DigitalData.Core.Abstraction.Application.DTO;
using DigitalData.Core.Abstraction.Application;
namespace DigitalData.Core.Application
{

View File

@ -1,63 +1,63 @@
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstraction.Application;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
namespace DigitalData.Core.Application
namespace DigitalData.Core.Application;
/// <summary>
/// Implements the <see cref="IJWTService{TClaimValue}"/> interface to manage JWT operations for claims of type <typeparamref name="TClaimValue"/>.
/// </summary>
public class JWTService<TClaimValue> : IJWTService<TClaimValue>
{
private readonly Func<TClaimValue, SecurityTokenDescriptor> _factory;
/// <summary>
/// Implements the <see cref="IJWTService{TClaimValue}"/> interface to manage JWT operations for claims of type <typeparamref name="TClaimValue"/>.
/// Initializes a new instance of the <see cref="JWTService{TClaimValue}"/> class.
/// </summary>
public class JWTService<TClaimValue> : IJWTService<TClaimValue>
/// <param name="tokenDescriptorFactory">A factory function to produce <see cref="SecurityTokenDescriptor"/> based on the claim value.</param>
public JWTService(Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory)
{
private readonly Func<TClaimValue, SecurityTokenDescriptor> _factory;
_factory = tokenDescriptorFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="JWTService{TClaimValue}"/> class.
/// </summary>
/// <param name="tokenDescriptorFactory">A factory function to produce <see cref="SecurityTokenDescriptor"/> based on the claim value.</param>
public JWTService(Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory)
{
_factory = tokenDescriptorFactory;
}
/// <summary>
/// Generates a symmetric security key with the specified byte size.
/// </summary>
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
{
using var rng = RandomNumberGenerator.Create();
var randomBytes = new byte[byteSize];
rng.GetBytes(randomBytes);
var securityKey = new SymmetricSecurityKey(randomBytes);
/// <summary>
/// Generates a symmetric security key with the specified byte size.
/// </summary>
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
{
using var rng = RandomNumberGenerator.Create();
var randomBytes = new byte[byteSize];
rng.GetBytes(randomBytes);
var securityKey = new SymmetricSecurityKey(randomBytes);
return securityKey;
}
return securityKey;
}
/// <summary>
/// Generates a JWT for the specified claim value.
/// </summary>
/// <param name="claimValue">The claim value to encode in the JWT.</param>
/// <returns>A JWT as a string.</returns>
public string GenerateToken(TClaimValue claimValue)
{
var tokenDescriptor = _factory(claimValue);
/// <summary>
/// Generates a JWT for the specified claim value.
/// </summary>
/// <param name="claimValue">The claim value to encode in the JWT.</param>
/// <returns>A JWT as a string.</returns>
public string GenerateToken(TClaimValue claimValue)
{
var tokenDescriptor = _factory(claimValue);
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
/// <summary>
/// Reads and validates a security token from a string representation.
/// </summary>
/// <param name="token">The JWT to read.</param>
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
public JwtSecurityToken? ReadSecurityToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.CanReadToken(token) ? tokenHandler.ReadToken(token) as JwtSecurityToken : null;
}
/// <summary>
/// Reads and validates a security token from a string representation.
/// </summary>
/// <param name="token">The JWT to read.</param>
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
public JwtSecurityToken? ReadSecurityToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.CanReadToken(token) ? tokenHandler.ReadToken(token) as JwtSecurityToken : null;
}
}

View File

@ -1,11 +1,11 @@
namespace DigitalData.Core.Application
namespace DigitalData.Core.Application;
[Obsolete("Use MediatR")]
public static class Key
{
public static class Key
{
public static readonly string EntityDoesNotExist = "EntityDoesNotExist";
public static readonly string ReadFailed = "ReadFailed";
public static readonly string UpdateFailed = "UpdateFailed";
public static readonly string DeletionFailed = "DeletionFailed";
public static readonly string DirSearcherDisconnected = "DirSearcherDisconnected";
}
public static readonly string EntityDoesNotExist = "EntityDoesNotExist";
public static readonly string ReadFailed = "ReadFailed";
public static readonly string UpdateFailed = "UpdateFailed";
public static readonly string DeletionFailed = "DeletionFailed";
public static readonly string DirSearcherDisconnected = "DirSearcherDisconnected";
}

View File

@ -1,79 +1,78 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstractions.Infrastructure;
using AutoMapper;
using DigitalData.Core.DTO;
using DigitalData.Core.Abstractions;
using AutoMapper;
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.DTO;
using DigitalData.Core.Abstraction.Application.Repository;
namespace DigitalData.Core.Application
namespace DigitalData.Core.Application;
/// <summary>
/// Provides generic Read (Read and Delete) operations for a specified type of entity.
/// </summary>
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
/// <typeparam name="TEntity">The entity type.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
[Obsolete("Use MediatR")]
public class ReadService<TCRUDRepository, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TReadDto : class where TEntity : class
{
protected readonly TCRUDRepository _repository;
protected readonly IMapper _mapper;
/// <summary>
/// Provides generic Read (Read and Delete) operations for a specified type of entity.
/// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper.
/// </summary>
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
/// <typeparam name="TEntity">The entity type.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
public class ReadService<TCRUDRepository, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TReadDto : class where TEntity : class, IUnique<TId>
/// <param name="repository">The CRUD repository for accessing the database.</param>
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param>
public ReadService(TCRUDRepository repository, IMapper mapper)
{
protected readonly TCRUDRepository _repository;
protected readonly IMapper _mapper;
/// <summary>
/// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper.
/// </summary>
/// <param name="repository">The CRUD repository for accessing the database.</param>
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param>
public ReadService(TCRUDRepository repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
/// <summary>
/// Asynchronously reads an entity by its identifier and maps it to a read DTO.
/// </summary>
/// <param name="id">The identifier of the entity to read.</param>
/// <returns>A service result indicating success or failure, including the read DTO if successful.</returns>
public virtual async Task<DataResult<TReadDto>> ReadByIdAsync(TId id)
{
var entity = await _repository.ReadByIdAsync(id);
return entity is null
? Result.Fail<TReadDto>()
: Result.Success(_mapper.Map<TReadDto>(entity));
}
/// <summary>
/// Asynchronously reads all entities and maps them to read DTOs.
/// </summary>
/// <returns>A service result including a collection of read DTOs.</returns>
public virtual async Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync()
{
var entities = await _repository.ReadAllAsync();
var readDto = _mapper.Map<IEnumerable<TReadDto>>(entities);
return Result.Success(readDto);
}
/// <summary>
/// Asynchronously deletes an entity by its identifier.
/// </summary>
/// <param name="id">The identifier of the entity to delete.</param>
/// <returns>A service message indicating success or failure.</returns>
public virtual async Task<Result> DeleteAsyncById(TId id)
{
TEntity? entity = await _repository.ReadByIdAsync(id);
if (entity is null)
return Result.Fail();
bool isDeleted = await _repository.DeleteAsync(entity);
return isDeleted ? Result.Success() : Result.Fail();
}
/// <summary>
/// Asynchronously checks if an entity with the specified identifier exists.
/// </summary>
/// <param name="id">The identifier of the entity to check.</param>
/// <returns>A Task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
public virtual async Task<bool> HasEntity(TId id) => await _repository.CountAsync(id) > 0;
_repository = repository;
_mapper = mapper;
}
/// <summary>
/// Asynchronously reads an entity by its identifier and maps it to a read DTO.
/// </summary>
/// <param name="id">The identifier of the entity to read.</param>
/// <returns>A service result indicating success or failure, including the read DTO if successful.</returns>
public virtual async Task<DataResult<TReadDto>> ReadByIdAsync(TId id)
{
var entity = await _repository.ReadByIdAsync(id);
return entity is null
? Result.Fail<TReadDto>()
: Result.Success(_mapper.Map<TReadDto>(entity));
}
/// <summary>
/// Asynchronously reads all entities and maps them to read DTOs.
/// </summary>
/// <returns>A service result including a collection of read DTOs.</returns>
public virtual async Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync()
{
var entities = await _repository.ReadAllAsync();
var readDto = _mapper.Map<IEnumerable<TReadDto>>(entities);
return Result.Success(readDto);
}
/// <summary>
/// Asynchronously deletes an entity by its identifier.
/// </summary>
/// <param name="id">The identifier of the entity to delete.</param>
/// <returns>A service message indicating success or failure.</returns>
public virtual async Task<Result> DeleteAsyncById(TId id)
{
TEntity? entity = await _repository.ReadByIdAsync(id);
if (entity is null)
return Result.Fail();
bool isDeleted = await _repository.DeleteAsync(entity);
return isDeleted ? Result.Success() : Result.Fail();
}
/// <summary>
/// Asynchronously checks if an entity with the specified identifier exists.
/// </summary>
/// <param name="id">The identifier of the entity to check.</param>
/// <returns>A Task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
public virtual async Task<bool> HasEntity(TId id) => await _repository.CountAsync(id) > 0;
}

View File

@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;

View File

@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

View File

@ -6,7 +6,6 @@
<Nullable>enable</Nullable>
<Description>This package provides HTTP client extension methods for the DigitalData.Core library, offering simplified and asynchronous methods for fetching and handling HTTP responses. It includes utility methods for sending GET requests, reading response content as text or JSON, and deserializing JSON into dynamic or strongly-typed objects using Newtonsoft.Json. These extensions facilitate efficient and easy-to-read HTTP interactions in client applications.</Description>
<PackageId>DigitalData.Core.Client</PackageId>
<Version>2.0.3</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>Digital Data GmbH</Product>
@ -15,8 +14,9 @@
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core http client json serilization</PackageTags>
<AssemblyVersion>2.0.3</AssemblyVersion>
<FileVersion>2.0.3</FileVersion>
<Version>2.1.0</Version>
<AssemblyVersion>2.1.0</AssemblyVersion>
<FileVersion>2.1.0</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -26,13 +26,27 @@
</None>
</ItemGroup>
<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
</ItemGroup>

View File

@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.Options;
using System.Net;

View File

@ -1,6 +1,6 @@
using System.Net;
namespace DigitalData.Core.Abstractions.Client
namespace DigitalData.Core.Client.Interface
{
public interface IBaseHttpClientService
{

View File

@ -1,4 +1,4 @@
namespace DigitalData.Core.Abstractions.Client
namespace DigitalData.Core.Client.Interface
{
public interface IHttpClientOptions
{

View File

@ -1,4 +1,4 @@
namespace DigitalData.Core.Abstractions.Client
namespace DigitalData.Core.Client.Interface
{
public interface IHttpClientService<TClientOptions> : IBaseHttpClientService where TClientOptions : IHttpClientOptions
{

View File

@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Client

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Exceptions.Middleware;
public static class DependencyInjection
{
public static IServiceCollection ConfigureGlobalExceptionHandler(this IServiceCollection services, Action<GlobalExceptionHandlerOptions>? options = null, bool addDefaultHandlers = true)
{
options ??= opt => { };
if (addDefaultHandlers)
{
options += opt => opt.Add(HttpExceptionHandler.Default, setAsDefault: true);
options += opt => opt.Add(HttpExceptionHandler.DefaultBadRequest);
options += opt => opt.Add(HttpExceptionHandler.DefaultNotFound);
}
return services.Configure(options);
}
public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder app)
{
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
return app;
}
}

View File

@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- NuGet Package Metadata -->
<PackageId>DigitalData.Core.Exceptions.Middleware</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Exceptions.Middleware</Product>
<Description>Provides middleware components for standardized exception handling and error response formatting in ASP.NET Core applications.</Description>
<PackageTags>digital data core exceptions middleware</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Copyright 2025</Copyright>
<PackageProjectUrl></PackageProjectUrl>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>1.0.1</Version>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Exceptions.Middleware;
/// <summary>
/// Middleware for handling exceptions globally in the application.
/// Captures exceptions thrown during the request pipeline execution,
/// logs them, and returns an appropriate HTTP response with a JSON error message.
/// </summary>
public class GlobalExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionHandlerMiddleware>? _logger;
private readonly GlobalExceptionHandlerOptions? _options;
/// <summary>
/// Initializes a new instance of the <see cref="GlobalExceptionHandlerMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="logger">The logger instance for logging exceptions.</param>
public GlobalExceptionHandlerMiddleware(RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware>? logger = null, IOptions<GlobalExceptionHandlerOptions>? options = null)
{
_next = next;
_logger = logger;
_options = options?.Value;
}
/// <summary>
/// Invokes the middleware to handle the HTTP request.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context); // Continue down the pipeline
}
catch (Exception ex)
{
if(ex.GetType() == typeof(Exception) && _options?.DefaultHandler is not null)
await _options.DefaultHandler.HandleExceptionAsync(context, ex, _logger);
if (_options?.Handlers.TryGetValue(ex.GetType(), out var handler) ?? false)
await handler.HandleExceptionAsync(context, ex, _logger);
}
}
}

View File

@ -0,0 +1,17 @@
namespace DigitalData.Core.Exceptions.Middleware;
public class GlobalExceptionHandlerOptions
{
internal readonly Dictionary<Type, HttpExceptionHandler> Handlers = new();
internal HttpExceptionHandler? DefaultHandler { get; private set; }
public GlobalExceptionHandlerOptions Add(HttpExceptionHandler handler, bool setAsDefault = false)
{
if (setAsDefault)
DefaultHandler = handler;
else
Handlers[handler.ExceptionType] = handler;
return this;
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
namespace DigitalData.Core.Exceptions.Middleware;
public record HttpExceptionHandler(Type ExceptionType, Func<HttpContext, Exception, ILogger?, Task> HandleExceptionAsync)
{
#region Alternative generator methods
public static HttpExceptionHandler Create<TException>(Func<HttpContext, Exception, ILogger?, Task> HandleExceptionAsync) where TException : Exception
=> new HttpExceptionHandler(typeof(TException), HandleExceptionAsync);
public static HttpExceptionHandler Create<TException>(HttpStatusCode statusCode, Func<Exception, string> messageFactory) where TException : Exception
=> Create<TException>(
async (context, ex, logger) =>
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
var message = messageFactory(ex);
await context.Response.WriteAsync(JsonSerializer.Serialize(new { message }));
}
);
#endregion
#region Default handlers
public static readonly Func<Exception, string> DefaultMessageFactory = ex => ex.Message;
public static HttpExceptionHandler DefaultBadRequest => Create<BadRequestException>(HttpStatusCode.BadRequest, DefaultMessageFactory);
public static HttpExceptionHandler DefaultNotFound => Create<NotFoundException>(HttpStatusCode.NotFound, DefaultMessageFactory);
public static HttpExceptionHandler Default => Create<Exception>(
async (context, ex, logger) =>
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var message = "An unexpected error occurred.";
await context.Response.WriteAsync(JsonSerializer.Serialize(new { message }));
});
#endregion
};

View File

@ -0,0 +1,22 @@
namespace DigitalData.Core.Exceptions;
/// <summary>
/// Represents an exception that is thrown when a bad request is encountered.
/// </summary>
public class BadRequestException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class.
/// </summary>
public BadRequestException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public BadRequestException(string? message) : base(message)
{
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- NuGet Package Metadata -->
<PackageId>DigitalData.Core.Exceptions</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Exceptions</Product>
<Description>This package contains exceptions for the DigitalData.Core library</Description>
<PackageTags>digital data core exceptions</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Copyright 2025</Copyright>
<PackageProjectUrl></PackageProjectUrl>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>1.1.0</Version>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
namespace DigitalData.Core.Exceptions;
/// <summary>
/// Represents an exception thrown when an operation is forbidden.
/// Typically used to indicate lack of permission or access rights.
/// </summary>
public class ForbiddenException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ForbiddenException"/> class.
/// </summary>
public ForbiddenException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ForbiddenException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ForbiddenException(string? message)
: base(message)
{
}
}

View File

@ -0,0 +1,22 @@
namespace DigitalData.Core.Exceptions;
/// <summary>
/// Represents an exception that is thrown when a requested resource is not found.
/// </summary>
public class NotFoundException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="NotFoundException"/> class.
/// </summary>
public NotFoundException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NotFoundException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public NotFoundException(string? message) : base(message)
{
}
}

View File

@ -6,7 +6,6 @@
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>DigitalData.Core.Infrastructure.AutoMapper</PackageId>
<Version>1.0.2</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Infrastructure.AutoMapper</Product>
@ -16,8 +15,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<RepositoryType>digital data core abstractions clean architecture mapping</RepositoryType>
<PackageTags>digital data core infrastructure clean architecture mapping</PackageTags>
<AssemblyVersion>1.0.2</AssemblyVersion>
<FileVersion>1.0.2</FileVersion>
<Version>1.0.3</Version>
<AssemblyVersion>1.0.3</AssemblyVersion>
<FileVersion>1.0.3</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -28,7 +28,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj" />
</ItemGroup>

View File

@ -1,5 +1,5 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Abstraction.Application.Repository;
namespace DigitalData.Core.Infrastructure.AutoMapper;

View File

@ -1,5 +1,6 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Infrastructure;
#if NET
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.Repository;
using Microsoft.EntityFrameworkCore;
namespace DigitalData.Core.Infrastructure
@ -14,8 +15,9 @@ namespace DigitalData.Core.Infrastructure
/// This repository abstracts the common database operations, offering an asynchronous API to work with the entity's data.
/// It leverages the EF Core's DbContext and DbSet to perform these operations.
/// </remarks>
[Obsolete("Use Repository")]
public class CRUDRepository<TEntity, TId, TDbContext> : ICRUDRepository<TEntity, TId>
where TEntity : class, IUnique<TId>
where TEntity : class
where TDbContext : DbContext
{
protected readonly TDbContext _dbContext;
@ -107,6 +109,7 @@ namespace DigitalData.Core.Infrastructure
/// If there are multiple entities with the same identifier, they will all be counted.
/// The default implementation assumes that the identifier is unique for each entity.
/// </remarks>
public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.Id!.Equals(id)).CountAsync();
public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.GetId().Equals(id)).CountAsync();
}
}
}
#endif

View File

@ -1,79 +1,137 @@
using DigitalData.Core.Abstractions.Infrastructure;
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
using DigitalData.Core.Infrastructure.Factory;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Infrastructure;
#if NETFRAMEWORK
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
#endif
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
{
protected internal readonly TDbContext Context;
protected internal readonly DbSet<TEntity> Entities;
public IEntityMapper<TEntity> Mapper { get; }
public DbRepository(TDbContext context, Func<TDbContext, DbSet<TEntity>> queryFactory, IEntityMapper<TEntity> mapper)
namespace DigitalData.Core.Infrastructure
#if NET
;
#elif NETFRAMEWORK
{
Context = context;
Entities = queryFactory(context);
Mapper = mapper;
}
#endif
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default)
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
{
Entities.Add(entity);
await Context.SaveChangesAsync(ct);
return entity;
}
protected internal readonly TDbContext Context;
public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default)
{
Entities.AddRange(entities);
await Context.SaveChangesAsync(ct);
return entities;
}
protected internal readonly DbSet<TEntity> Entities;
public virtual async Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
=> expression is null
? await Entities.AsNoTracking().ToListAsync(ct)
: await Entities.AsNoTracking().Where(expression).ToListAsync(ct);
public IMapper
#if NET
?
#endif
Mapper { get; }
public virtual async Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
=> single
? await Entities.AsNoTracking().Where(expression).SingleOrDefaultAsync(ct)
: await Entities.AsNoTracking().Where(expression).FirstOrDefaultAsync(ct);
public virtual async Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
=> Mapper.Map<TDto>(await ReadAllAsync(expression, ct));
public virtual async Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
{
var entity = await ReadOrDefaultAsync(expression, single, ct);
return entity is null ? default : Mapper.Map<TDto>(entity);
}
public virtual async Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
{
var entities = await Entities.Where(expression).ToListAsync(ct);
for (int i = entities.Count - 1; i >= 0; i--)
public DbRepository(TDbContext context, DbSetFactory<TDbContext, TEntity> factory, IMapper
#if NET
?
#endif
mapper = null)
{
Mapper.Map(dto, entities[i]);
Entities.Update(entities[i]);
Context = context;
Entities = factory.Create(context);
Mapper = mapper;
}
await Context.SaveChangesAsync(ct);
}
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
{
var entities = await Entities.Where(expression).ToListAsync(ct);
for (int i = entities.Count - 1; i >= 0; i--)
#region Create
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default)
{
Entities.Remove(entities[i]);
Entities.Add(entity);
await Context.SaveChangesAsync(cancel);
return entity;
}
await Context.SaveChangesAsync(ct);
public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default)
{
Entities.AddRange(entities);
await Context.SaveChangesAsync(cancel);
return entities;
}
public virtual Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default)
=> CreateAsync(Mapper
#if NET
!
#endif
.Map<TEntity>(dto), cancel);
public virtual Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default)
=> CreateAsync(Mapper
#if NET
!
#endif
.Map<IEnumerable<TEntity>>(dtos), cancel);
#endregion Create
#region Read
public virtual IQueryable<TEntity> Query => Entities.AsNoTracking();
public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression) => Entities.AsNoTracking().Where(expression);
public virtual IEnumerable<TEntity> GetAll() => Entities.AsNoTracking().ToList();
public virtual async Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default) => await Entities.AsNoTracking().ToListAsync(cancel);
#endregion Read
#region Update
public virtual Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => UpdateAsync(dto, q => q.Where(expression), cancel);
public virtual Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
=> UpdateAsync(entity => Mapper
#if NET
!
#endif
.Map(dto, entity), query, cancel);
public virtual async Task UpdateAsync(Action<TEntity> modification, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
{
var entities = await query(Entities).ToListAsync(cancel);
for (int i = entities.Count - 1; i >= 0; i--)
modification.Invoke(entities[i]);
await Context.SaveChangesAsync(cancel);
}
public virtual Task UpdateAsync(Action<TEntity> modification, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
=> UpdateAsync(modification, q => q.Where(expression), cancel);
#endregion Update
#region Delete
public virtual Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => DeleteAsync(q => q.Where(expression), cancel);
public virtual async Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
{
var entities = await query(Entities).ToListAsync(cancel);
for (int i = entities.Count - 1; i >= 0; i--)
{
Entities.Remove(entities[i]);
}
await Context.SaveChangesAsync(cancel);
}
#endregion Delete
#region Obsolete
[Obsolete("Use IRepository<TEntity>.Where")]
public virtual IQueryable<TEntity> Read() => Entities.AsQueryable();
[Obsolete("Use IRepository<TEntity>.Get")]
public virtual IQueryable<TEntity> ReadOnly() => Entities.AsNoTracking();
#endregion
}
}
#if NETFRAMEWORK
}
#endif

View File

@ -1,19 +1,145 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Infrastructure.Factory;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
#if NETFRAMEWORK
using System.Collections.Generic;
using System;
using System.Linq;
#endif
namespace DigitalData.Core.Infrastructure;
namespace DigitalData.Core.Infrastructure
#if NET
;
#elif NETFRAMEWORK
{
#endif
public static class DependencyInjection
{
public static EntityConfigurationOptions<TEntity> AddDbRepository<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>> queryFactory)
public static IServiceCollection AddDbRepository(this IServiceCollection services, Action<RepositoryConfiguration> options)
{
// register services from configuration
var cfg = new RepositoryConfiguration();
options.Invoke(cfg);
cfg.RegisterAllServices(services);
return services;
}
public class RepositoryConfiguration
{
// 1. register from assembly
private readonly Queue<Action<IServiceCollection>> RegsFromAssembly = new Queue<Action<IServiceCollection>>();
// 2. register entities (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsEntity = new Queue<Action<IServiceCollection>>();
// 3. register db set factories (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsDbSetFactory = new Queue<Action<IServiceCollection>>();
internal void RegisterAllServices(IServiceCollection services)
{
// 1. register from assembly
RegsFromAssembly.InvokeAll(services);
// 2. register entities (can overwrite)
RegsEntity.InvokeAll(services);
// 3. register db set factories (can overwrite)
RegsDbSetFactory.InvokeAll(services);
}
internal RepositoryConfiguration() { }
public void RegisterFromAssembly<TDbContext>(Assembly assembly) where TDbContext : DbContext
{
void reg(IServiceCollection services)
{
// scan all types in the Assembly
var entityTypes = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.GetCustomAttribute<TableAttribute>() != null);
foreach (var entityType in entityTypes)
{
#region Repository
/// register repository
// create generic DbRepository<DbContext, TEntity> type
var repositoryType = typeof(DbRepository<,>).MakeGenericType(typeof(TDbContext), entityType);
var interfaceType = typeof(IRepository<>).MakeGenericType(entityType);
// add into DI container as Scoped
services.AddScoped(interfaceType, repositoryType);
#endregion Repository
#region DbSetFactory
/// register DbSetFactory
var addDbSetFactoryMethod = typeof(DependencyInjection)
.GetMethod(nameof(AddDbSetFactory),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var genericMethod = addDbSetFactoryMethod
#if NET
!
#endif
.MakeGenericMethod(typeof(TDbContext), entityType);
genericMethod.Invoke(null, new [] { services, null });
#endregion DbSetFactory
}
}
RegsFromAssembly.Enqueue(reg);
}
public void RegisterEntity<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>>
#if NET
?
#endif
dbSetFactory = null)
where TDbContext : DbContext
where TEntity : class
{
void reg(IServiceCollection services)
=> services
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>()
.AddDbSetFactory(dbSetFactory);
RegsEntity.Enqueue(reg);
}
public void RegisterDbSetFactory<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>> dbSetFactory)
where TDbContext : DbContext
where TEntity : class
=> RegsDbSetFactory.Enqueue(s => s.AddDbSetFactory(dbSetFactory));
}
private static void InvokeAll<T>(this Queue<Action<T>> queue, T services)
{
while (queue.Count > 0)
queue.Dequeue().Invoke(services);
}
internal static IServiceCollection AddDbSetFactory<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>>
#if NET
?
#endif
create = null)
where TDbContext : DbContext
where TEntity : class
{
services
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>()
.AddSingleton(queryFactory);
return new EntityConfigurationOptions<TEntity>(services);
#if NET
create ??= ctx => ctx.Set<TEntity>();
#elif NETFRAMEWORK
if(create is null)
create = ctx => ctx.Set<TEntity>();
#endif
services.AddSingleton(_ => new DbSetFactory<TDbContext, TEntity>(create));
return services;
}
}
}
#if NETFRAMEWORK
}
#endif

View File

@ -1,32 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TargetFrameworks>net462;net7.0;net8.0;net9.0</TargetFrameworks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>DigitalData.Core.Infrastructure</PackageId>
<Version>2.0.4</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Infrastructure</Product>
<Description>This package provides implementations for data access and other low-level services within the DigitalData.Core.Abstractions library. It includes generic CRUD operations using Entity Framework Core, database context management, and other infrastructure-related functionalities, adhering to Clean Architecture principles to ensure separation of concerns and maintainability.</Description>
<Description>This package provides implementations for data access and other low-level services within the DigitalData.Core.Application library. It includes generic CRUD operations using Entity Framework Core, database context management, and other infrastructure-related functionalities, adhering to Clean Architecture principles.</Description>
<Copyright>Copyright 2024</Copyright>
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<RepositoryType>digital data core abstractions clean architecture</RepositoryType>
<PackageTags>digital data core infrastructure clean architecture</PackageTags>
<AssemblyVersion>2.0.4</AssemblyVersion>
<FileVersion>2.0.4</FileVersion>
<Version>2.5.2</Version>
<AssemblyVersion>2.5.2</AssemblyVersion>
<FileVersion>2.5.2</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\nuget-package-icons\core_icon.png">
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<!-- disable for net462 -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<!-- enable for net7 and more -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net7.0' Or '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net9.0'">
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.32" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
</ItemGroup>
@ -36,11 +52,11 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.Abstraction.Application\DigitalData.Core.Abstraction.Application.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,27 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Infrastructure;
public class EntityConfigurationOptions<TEntity>
{
private readonly IServiceCollection _services;
public EntityConfigurationOptions(IServiceCollection services)
{
_services = services;
}
public EntityConfigurationOptions<TEntity> AddCustomMapper<TEntityMapper>(Action<IServiceCollection> configure, Func<IServiceProvider, TEntityMapper>? factory = null)
where TEntityMapper : class, IEntityMapper<TEntity>
{
configure(_services);
if (factory is null)
_services.AddSingleton<IEntityMapper<TEntity>, TEntityMapper>();
else
_services.AddSingleton(factory);
return this;
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
#if NETFRAMEWORK
using System;
#endif
namespace DigitalData.Core.Infrastructure.Factory
#if NET
;
#elif NETFRAMEWORK
{
#endif
public class DbSetFactory<TDbContext,TEntity> where TDbContext : DbContext where TEntity : class
{
public readonly Func<TDbContext, DbSet<TEntity>> Create;
public DbSetFactory(Func<TDbContext, DbSet<TEntity>> create)
{
Create = create;
}
}
#if NETFRAMEWORK
}
#endif

View File

@ -0,0 +1,173 @@
using Microsoft.Extensions.DependencyInjection;
using DigitalData.Core.Abstractions;
namespace DigitalData.Core.Tests.Abstractions
{
[TestFixture]
public class FactoryTests
{
private Factory _factory;
[SetUp]
public void Setup()
{
_factory = new Factory();
}
[Test]
public void Add_ServiceDescriptor_ShouldIncreaseCount()
{
// Arrange
var descriptor = ServiceDescriptor.Singleton(typeof(string), "test");
// Act
_factory.Add(descriptor);
// Assert
Assert.That(_factory.Count, Is.EqualTo(1));
Assert.That(_factory.Contains(descriptor), Is.True);
}
[Test]
public void Clear_ShouldRemoveAllServices()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "test"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 42));
// Act
_factory.Clear();
// Assert
Assert.That(_factory.Count, Is.EqualTo(0));
}
[Test]
public void GetService_ShouldReturnRegisteredInstance()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "Hello World"));
// Act
var result = _factory.GetService(typeof(string));
// Assert
Assert.That(result, Is.EqualTo("Hello World"));
}
[Test]
public void GetService_UnregisteredType_ShouldReturnNull()
{
// Act
var result = _factory.GetService(typeof(int));
// Assert
Assert.That(result, Is.Null);
}
[Test]
public void ModifyAfterBuild_ShouldThrowInvalidOperationException()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "x"));
var _ = _factory.GetService(typeof(string)); // trigger build
// Act & Assert
Assert.Throws<InvalidOperationException>(() => _factory.Add(ServiceDescriptor.Singleton(typeof(int), 1)));
}
[Test]
public void Remove_ShouldWorkBeforeBuild()
{
// Arrange
var descriptor = ServiceDescriptor.Singleton(typeof(string), "Test");
_factory.Add(descriptor);
// Act
var removed = _factory.Remove(descriptor);
// Assert
Assert.That(removed, Is.True);
Assert.That(_factory.Count, Is.EqualTo(0));
}
[Test]
public void Insert_ShouldInsertAtCorrectIndex()
{
// Arrange
var d1 = ServiceDescriptor.Singleton(typeof(string), "A");
var d2 = ServiceDescriptor.Singleton(typeof(int), 1);
_factory.Add(d1);
_factory.Insert(0, d2);
// Act
var first = _factory[0];
// Assert
Assert.That(first.ServiceType, Is.EqualTo(typeof(int)));
}
[Test]
public void Enumerator_ShouldIterateOverServices()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "a"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 5));
// Act
var list = _factory.ToList();
// Assert
Assert.That(list.Count, Is.EqualTo(2));
Assert.That(list.Any(sd => sd.ServiceType == typeof(string)), Is.True);
Assert.That(list.Any(sd => sd.ServiceType == typeof(int)), Is.True);
}
[Test]
public void CopyTo_ShouldCopyElements()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "x"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 10));
var array = new ServiceDescriptor[2];
// Act
_factory.CopyTo(array, 0);
// Assert
Assert.That(array[0].ServiceType, Is.EqualTo(typeof(string)));
Assert.That(array[1].ServiceType, Is.EqualTo(typeof(int)));
}
[Test]
public void RemoveAt_ShouldRemoveItemAtIndex()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "x"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 5));
// Act
_factory.RemoveAt(0);
// Assert
Assert.That(_factory.Count, Is.EqualTo(1));
Assert.That(_factory[0].ServiceType, Is.EqualTo(typeof(int)));
}
[Test]
public void Indexer_Set_ShouldReplaceItem()
{
// Arrange
var original = ServiceDescriptor.Singleton(typeof(string), "A");
var replacement = ServiceDescriptor.Singleton(typeof(string), "B");
_factory.Add(original);
// Act
_factory[0] = replacement;
// Assert
Assert.That(_factory[0], Is.EqualTo(replacement));
}
}
}

View File

@ -1,5 +1,5 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client;
using DigitalData.Core.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Tests.Client

View File

@ -26,7 +26,6 @@
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
<ProjectReference Include="..\DigitalData.Core.Client\DigitalData.Core.Client.csproj" />
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
<ProjectReference Include="..\DigitalData.Core.Infrastructure.AutoMapper\DigitalData.Core.Infrastructure.AutoMapper.csproj" />
<ProjectReference Include="..\DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj" />
<ProjectReference Include="..\DigitalData.Core.Security\DigitalData.Core.Security.csproj" />
</ItemGroup>
@ -42,7 +41,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
</ItemGroup>

View File

@ -1,113 +0,0 @@
namespace DigitalData.Core.Tests.Infrastructure;
using DigitalData.Core.Infrastructure;
using DigitalData.Core.Tests.Mock;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure.AutoMapper;
public class DbRepositoryTests
{
private IHost _host;
private IRepository<User> _userRepo;
[SetUp]
public void Setup()
{
var builder = Host.CreateApplicationBuilder();
builder.Services.AddDbContext<MockDbContext>(opt => opt.UseInMemoryDatabase("MockDB"));
builder.Services.AddDbRepository<MockDbContext, User>(context => context.Users).UseAutoMapper(typeof(UserCreateDto), typeof(UserReadDto), typeof(UserBase));
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
_host = builder.Build();
_userRepo = _host.Services.GetRequiredService<IRepository<User>>();
}
[TearDown]
public void TearDown()
{
if (_host is IDisposable disposableHost)
disposableHost.Dispose();
}
[TestCase(true, TestName = "WhenGivenMultipleUsers")]
[TestCase(false, TestName = "WhenGivenSingleUser")]
public void CreateAsync_ShouldNotThrow(bool multiple)
{
// Arrange
var faker = Fake.CreateUserFaker();
// Act & Assert
if (multiple)
Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate(Random.Shared.Next(1, 10))));
else
Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate()));
}
[TestCase(true, TestName = "WhenDtoUsed")]
[TestCase(false, TestName = "WhenEntityUsed")]
public async Task ReadAsync_ShouldReturnCreated(bool useDto)
{
// Act
var createdUser = useDto
? await _userRepo.CreateAsync(Fake.UserCreateDto)
: await _userRepo.CreateAsync(Fake.User);
var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(readUser, Is.EqualTo(createdUser));
});
}
[Test]
public async Task ReadAsync_ShouldReturnUpdated()
{
// Arrange
var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
var userUpdateDto = new UserUpdateDto() { Age = 10, Email = "Bar", FirstName = "Foo" };
// Act
await _userRepo.UpdateAsync(userUpdateDto, u => u.Id == createdUser!.Id);
var upToDateUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser!.Id);
// Assert
Assert.Multiple(() =>
{
Assert.That(upToDateUser, Is.Not.Null);
Assert.That(upToDateUser?.FirstName, Is.EqualTo(userUpdateDto.FirstName));
Assert.That(upToDateUser?.Email, Is.EqualTo(userUpdateDto.Email));
Assert.That(upToDateUser?.Age, Is.EqualTo(userUpdateDto.Age));
});
}
[Test]
public async Task ReadAsync_ShouldNotReturnDeleted()
{
// Arrange
var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
// Act
await _userRepo.DeleteAsync(u => u.Id == createdUser.Id);
var deletedUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(deletedUser, Is.Null);
});
}
}

View File

@ -1,6 +1,10 @@
namespace DigitalData.Core.Tests.Mock;
using DigitalData.Core.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations.Schema;
public class User : UserBase
namespace DigitalData.Core.Tests.Mock;
[Table("USER")]
public class User : UserBase, IEntity
{
public required int Id { get; init; }
@ -9,4 +13,4 @@ public class User : UserBase
public override bool Equals(object? obj)
=> (obj is User user && user.GetHashCode() == GetHashCode())
|| (obj is UserBase userBase && userBase.GetHashCode() == base.GetHashCode());
}
}

View File

@ -1,5 +1,7 @@
namespace DigitalData.Core.Tests.Mock;
using DigitalData.Core.Abstractions.Interfaces;
public class UserCreateDto : UserBase
namespace DigitalData.Core.Tests.Mock;
public class UserCreateDto : UserBase, IDto<User>
{
}
}

View File

@ -1,5 +1,7 @@
namespace DigitalData.Core.Tests.Mock;
using DigitalData.Core.Abstractions.Interfaces;
public class UserReadDto : UserBase
namespace DigitalData.Core.Tests.Mock;
public class UserReadDto : UserBase, IDto<User>
{
}
}

View File

@ -1,5 +1,7 @@
namespace DigitalData.Core.Tests.Mock;
using DigitalData.Core.Abstractions.Interfaces;
public class UserUpdateDto : UserBase
namespace DigitalData.Core.Tests.Mock;
public class UserUpdateDto : UserBase, IDto<User>
{
}
}

View File

@ -39,6 +39,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Infrastruc
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Exceptions", "DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj", "{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Exceptions.Middleware", "DigitalData.Core.Exceptions.Middleware\DigitalData.Core.Exceptions.Middleware.csproj", "{2336AE61-A21D-437E-A11B-367D008A64B2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exceptions", "Exceptions", "{8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{0A27EA70-74F4-48FB-881C-D741F2FCD456}"
ProjectSection(SolutionItems) = preProject
Assets\core_icon.png = Assets\core_icon.png
Assets\core_legacy_icon.png = Assets\core_legacy_icon.png
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Abstraction.Application", "DigitalData.Core.Abstraction.Application\DigitalData.Core.Abstraction.Application.csproj", "{420C35A7-0EDE-4E2E-8500-484B57B0367A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{E18417C2-D9F5-437A-9ED5-473DD6260066}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -92,21 +108,33 @@ Global
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Release|Any CPU.Build.0 = Release|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.Build.0 = Release|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.Build.0 = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.Build.0 = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.Build.0 = Release|Any CPU
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.Build.0 = Release|Any CPU
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Release|Any CPU.Build.0 = Release|Any CPU
{2336AE61-A21D-437E-A11B-367D008A64B2}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{2336AE61-A21D-437E-A11B-367D008A64B2}.Debug|Any CPU.Build.0 = Release|Any CPU
{2336AE61-A21D-437E-A11B-367D008A64B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2336AE61-A21D-437E-A11B-367D008A64B2}.Release|Any CPU.Build.0 = Release|Any CPU
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Debug|Any CPU.Build.0 = Release|Any CPU
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC} = {E18417C2-D9F5-437A-9ED5-473DD6260066}
{C57B2480-F632-4691-9C4C-8CC01237203C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{B54DEF90-C30C-44EA-9875-76F1B330CBB7} = {EDF84A84-1E01-484E-B073-383F7139C891}
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
@ -121,6 +149,11 @@ Global
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4} = {8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7}
{2336AE61-A21D-437E-A11B-367D008A64B2} = {8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7}
{8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{420C35A7-0EDE-4E2E-8500-484B57B0367A} = {E18417C2-D9F5-437A-9ED5-473DD6260066}
{E18417C2-D9F5-437A-9ED5-473DD6260066} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}