66 Commits

Author SHA1 Message Date
Developer 02
e0737299cf Bump version to 1.1.1 in project file
Updated DigitalData.Core.Exceptions.csproj to set <Version>, <AssemblyVersion>, and <FileVersion> to 1.1.1, replacing the previous 1.1.0 values. No other changes were made.
2026-01-21 22:06:35 +01:00
Developer 02
2db99edcda Add constructor to BadRequestException for inner exceptions
Added a new constructor to BadRequestException that accepts both a message and an inner exception, enabling improved exception chaining and more detailed error reporting.
2026-01-21 22:05:16 +01:00
65186b4f47 chore(Infrastructure): bump to 2.6.1 2026-01-12 10:23:26 +01:00
eb3a5b8991 Apply AsNoTracking to raw/interpolated queries in DbRepository
QueryRaw and QueryInterpolated now always return results with AsNoTracking applied, ensuring entities are not tracked by EF Core. Both methods are now virtual, allowing for easier customization in derived classes. This change improves performance and prevents unintended side effects from entity tracking.
2026-01-12 10:02:56 +01:00
908d85c648 refactor(Core.Abstraction.Application): bump to 1.6.0 2025-12-19 10:23:45 +01:00
d0f055e066 Bump version to 2.6.0 in csproj metadata
Updated <Version>, <AssemblyVersion>, and <FileVersion> fields in DigitalData.Core.Infrastructure.csproj from 2.5.2 to 2.6.0 to reflect the new release.
2025-12-19 10:13:25 +01:00
7f9e6155fe Add repository registration to DI setup
Added RegsRepository.InvokeAll(services) to the dependency injection configuration, ensuring repository types are registered alongside entities and DbSet factories.
2025-12-19 10:11:14 +01:00
1e3cba6fdf Refactor DependencyInjection and RepositoryConfiguration classes
Restructure code for improved readability and maintainability. Move RepositoryConfiguration as a nested public class inside DependencyInjection, group related service registration logic, and clarify method organization. No functional changes.
2025-12-19 10:09:04 +01:00
daa36d767d Refactor repository registration to be context-based
Changed RegisterDefaultRepository to register repositories by DbContext only, removing the entity-specific generic parameter. Added a private InvokeAll<T> helper to process queued service registrations. This streamlines repository registration and improves code maintainability.
2025-12-19 10:03:25 +01:00
3021fd36f6 Implement IRepository in DbRepository<TDbContext>
DbRepository<TDbContext> now implements the IRepository interface, ensuring it conforms to the expected repository contract and interface requirements. This change improves consistency and enforces interface adherence across repository implementations.
2025-12-19 10:01:20 +01:00
2c704c1231 Add raw SQL support to repository interfaces and DI
Extended IRepository interfaces and DbRepository implementation to support raw and interpolated SQL query execution (sync/async) and querying. Updated DependencyInjection to allow registration of default repository implementations. Added Microsoft.EntityFrameworkCore.Abstractions as a dependency. Performed minor refactoring and cleanup.
2025-12-19 09:54:56 +01:00
2bf2bb2276 Refactor namespace and add System.Linq import
Removed conditional compilation around the namespace in DbRepository.cs, making it always use standard braces. Added System.Linq using directive to support LINQ operations.
2025-12-19 09:51:22 +01:00
Developer 02
d2302560f1 Add repository registration to DependencyInjection
Added a `RegsRepository` queue to manage repository registrations.
Introduced `RegisterDefaultRepository<TDbContext, TEntity>()` to
enqueue scoped repository registrations. Updated `RegisterAllServices`
to process the `RegsRepository` queue during service registration.
2025-12-19 09:25:58 +01:00
Developer 02
42c0dc7206 Refactor method names for clarity in IRepository/DbRepository
Renamed methods in `IRepository` and `DbRepository` to replace
`Sql` with `Query` for improved clarity and consistency.

- Updated `IRepository` methods:
  - `ExecuteSqlRawAsync` -> `ExecuteQueryRawAsync`
  - `ExecuteSqlInterpolatedAsync` -> `ExecuteQueryInterpolatedAsync`
  - `ExecuteSqlRaw` -> `ExecuteQueryRaw`
  - `ExecuteSqlInterpolated` -> `ExecuteQueryInterpolated`

- Updated `DbRepository` methods:
  - `ExecuteSqlRawAsync` -> `ExecuteQueryRawAsync`
  - `ExecuteSqlInterpolatedAsync` -> `ExecuteQueryInterpolatedAsync`
  - `ExecuteSqlRaw` -> `ExecuteQueryRaw`
  - `ExecuteSqlInterpolated` -> `ExecuteQueryInterpolated`

- Fixed `DbRepository` class declaration by removing the incorrect
  constraint requiring `TDbContext` to implement `IRepository`.
2025-12-19 09:20:18 +01:00
Developer 02
82686db38b Add QueryRaw and QueryInterpolated methods to repository
Added `QueryRaw` and `QueryInterpolated` methods to the
`IRepository` interface for raw and interpolated SQL queries,
conditionally compiled for the `NET` target framework.

Removed the `Sql` method from `DbRepository` and replaced it
with implementations of `QueryRaw` and `QueryInterpolated`
using `Entities.FromSqlRaw` and `Entities.FromSqlInterpolated`.

Updated the `Query` property in `DbRepository` to use
`Entities.AsNoTracking()` for read-only queries.
2025-12-19 09:05:59 +01:00
Developer 02
ce5c59dfc2 Update DbRepository to require IRepository constraint
The `DbRepository` class now enforces that the generic type
parameter `TDbContext` must implement the `IRepository`
interface in addition to inheriting from `DbContext`. This
ensures that `DbRepository` can leverage functionality
provided by the `IRepository` interface, improving type
safety and consistency.
2025-12-19 08:57:18 +01:00
Developer 02
5c3db6886a Add EF Core support and refactor IRepository interface
Updated project dependencies to include `Microsoft.EntityFrameworkCore.Abstractions` for multiple target frameworks (`net462`, `net7.0`, `net8.0`, `net9.0`). Added `Microsoft.Extensions.*` package references to the project file.

Enhanced `IRepository` interface with methods for executing raw and interpolated SQL queries (`ExecuteSqlRawAsync`, `ExecuteSqlInterpolatedAsync`, etc.). Adjusted method declarations to support conditional compilation for `NET` and `NETFRAMEWORK`.

Refactored namespace structure in `IRepository.cs` to simplify and remove unnecessary conditional compilation directives.
2025-12-18 22:09:45 +01:00
Developer 02
144178a504 Add async SQL execution methods to DbRepository
Added `ExecuteSqlRawAsync` and `ExecuteSqlInterpolatedAsync` methods to the `DbRepository` class for asynchronous SQL execution. Updated the `System.Linq` namespace import to support LINQ operations. Simplified conditional compilation directives by consistently enclosing the class declaration in braces `{}` across frameworks. Adjusted closing braces to align with the updated structure.
2025-12-18 21:54:22 +01:00
6717aa37ab Add generic DbRepository for raw SQL execution
Introduced DbRepository<TDbContext> to provide basic database operations, including methods for executing raw SQL commands via ExecuteSqlRaw and ExecuteSqlInterpolated. The class exposes the underlying DbContext for use in derived classes. Existing DbRepository<TDbContext, TEntity> remains unchanged.
2025-12-18 13:44:45 +01:00
bf418e986b Add Sql method to DbRepository and EFCore.Relational refs
Added a Sql method to DbRepository for executing raw SQL queries
via FromSqlRaw. Included Microsoft.EntityFrameworkCore.Relational
package references for all target frameworks to support this
functionality. Cleaned up unused using directives.
2025-12-18 13:21:35 +01:00
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
37 changed files with 1103 additions and 699 deletions

View File

@@ -1,4 +1,5 @@
namespace DigitalData.Core.Abstraction.Application.DTO; #if NET
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary> /// <summary>
/// Represents a base Data Transfer Object (DTO) with an identifier. /// Represents a base Data Transfer Object (DTO) with an identifier.
@@ -14,4 +15,5 @@ public record BaseDTO<TId>(TId Id) where TId : notnull
/// </summary> /// </summary>
/// <returns>A hash code for the current object, derived from the identifier.</returns> /// <returns>A hash code for the current object, derived from the identifier.</returns>
public override int GetHashCode() => Id.GetHashCode(); public override int GetHashCode() => Id.GetHashCode();
} }
#endif

View File

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

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Configuration; #if NET
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Configuration; using System.Configuration;
@@ -31,4 +32,5 @@ public static class DIExtensions
}); });
return services; return services;
} }
} }
#endif

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging; #if NET
using Microsoft.Extensions.Logging;
using System.Text; using System.Text;
namespace DigitalData.Core.Abstraction.Application.DTO; namespace DigitalData.Core.Abstraction.Application.DTO;
@@ -19,7 +20,7 @@ public static class DTOExtensions
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")] [Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static T Message<T>(this T result, string? message) where T : Result public static T Message<T>(this T result, string? message) where T : Result
{ {
if(message is not null) if (message is not null)
result.Messages.Add(message); result.Messages.Add(message);
return result; return result;
} }
@@ -28,7 +29,7 @@ public static class DTOExtensions
internal static IEnumerable<T> FilterNull<T>(this IEnumerable<T?> list) internal static IEnumerable<T> FilterNull<T>(this IEnumerable<T?> list)
{ {
foreach (var item in list) foreach (var item in list)
if(item is not null) if (item is not null)
yield return item; yield return item;
} }
@@ -328,7 +329,7 @@ public static class DTOExtensions
/// <param name="end">The ending string.</param> /// <param name="end">The ending string.</param>
/// <returns>The joined string.</returns> /// <returns>The joined string.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")] [Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static string Join<T>(this IEnumerable<T> values, string start = "", string seperator = ". ", string end = ".") 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(); => new StringBuilder(start).Append(string.Join(seperator, values)).Append(end).ToString();
/// <summary> /// <summary>
@@ -342,7 +343,7 @@ public static class DTOExtensions
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")] [Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static void LogNotice(this ILogger logger, IEnumerable<Notice> notices, string start = ": ", string seperator = ". ", string end = ".\n") 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))) foreach (LogLevel level in Enum.GetValues(typeof(LogLevel)))
{ {
var logNotices = notices.Where(n => n.Level == level); var logNotices = notices.Where(n => n.Level == level);
@@ -350,7 +351,7 @@ public static class DTOExtensions
continue; continue;
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach(Notice notice in logNotices) foreach (Notice notice in logNotices)
{ {
if (notice.Flag is not null) if (notice.Flag is not null)
sb.Append(notice.Flag); sb.Append(notice.Flag);
@@ -390,4 +391,5 @@ public static class DTOExtensions
/// <returns>True if the data result is false; otherwise, false.</returns> /// <returns>True if the data result is false; otherwise, false.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")] [Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public static bool IsWrong(this DataResult<bool> bResult) => !bResult.Data; public static bool IsWrong(this DataResult<bool> bResult) => !bResult.Data;
} }
#endif

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; #if NET
using System.Text.Json.Serialization;
namespace DigitalData.Core.Abstraction.Application.DTO; namespace DigitalData.Core.Abstraction.Application.DTO;
@@ -25,4 +26,5 @@ public class DataResult<T> : Result
/// <returns>A failed <see cref="DataResult{I}"/> with the current messages and notices.</returns> /// <returns>A failed <see cref="DataResult{I}"/> with the current messages and notices.</returns>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")] [Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public DataResult<I> ToFail<I>() => Fail<I>().Message(Messages).Notice(Notices); public DataResult<I> ToFail<I>() => Fail<I>().Message(Messages).Notice(Notices);
} }
#endif

View File

@@ -1,4 +1,5 @@
namespace DigitalData.Core.Abstraction.Application.DTO; #if NET
namespace DigitalData.Core.Abstraction.Application.DTO;
/// <summary> /// <summary>
/// Defines flags that indicate specific types of status or conditions in a service operation. /// Defines flags that indicate specific types of status or conditions in a service operation.
@@ -47,4 +48,5 @@ public enum Flag
/// This flag is used when the specified item or condition does not exist or is unavailable. /// This flag is used when the specified item or condition does not exist or is unavailable.
/// </summary> /// </summary>
NotFound NotFound
} }
#endif

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging; #if NET
using Microsoft.Extensions.Logging;
namespace DigitalData.Core.Abstraction.Application.DTO; namespace DigitalData.Core.Abstraction.Application.DTO;
@@ -25,4 +26,5 @@ public class Notice
/// </summary> /// </summary>
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")] [Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
public List<string> Messages { get; init; } = new(); public List<string> Messages { get; init; } = new();
} }
#endif

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; #if NET
using System.Text.Json.Serialization;
namespace DigitalData.Core.Abstraction.Application.DTO; namespace DigitalData.Core.Abstraction.Application.DTO;
@@ -105,4 +106,5 @@ public class Result
Data = default Data = default
}; };
#pragma warning restore CS8601 // Possible null reference assignment. #pragma warning restore CS8601 // Possible null reference assignment.
} }
#endif

View File

@@ -1,9 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks> <TargetFrameworks>net462;net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <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> <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> <PackageId>DigitalData.Core.Abstraction.Application</PackageId>
@@ -14,9 +12,9 @@
<PackageIcon>core_icon.png</PackageIcon> <PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core application clean architecture abstraction</PackageTags> <PackageTags>digital data core application clean architecture abstraction</PackageTags>
<Version>1.3.1</Version> <Version>1.6.0</Version>
<AssemblyVersion>1.3.1</AssemblyVersion> <AssemblyVersion>1.6.0</AssemblyVersion>
<FileVersion>1.3.1</FileVersion> <FileVersion>1.6.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -26,6 +24,20 @@
</None> </None>
</ItemGroup> </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> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" 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.Localization.Abstractions" Version="7.0.16" />
@@ -36,22 +48,32 @@
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" /> <PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup> </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" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="3.1.32" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" /> <PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="7.0.20" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" /> <PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.15" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" /> <PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,5 @@
namespace DigitalData.Core.Abstraction.Application; #if NET
namespace DigitalData.Core.Abstraction.Application;
/// <summary> /// <summary>
/// Provides extension methods for retrieving the value of an 'Id' property from objects. /// Provides extension methods for retrieving the value of an 'Id' property from objects.
@@ -91,4 +92,5 @@ public static class EntityExtensions
#pragma warning restore CS8601 #pragma warning restore CS8601
return id is not null; return id is not null;
} }
} }
#endif

View File

@@ -1,4 +1,5 @@
namespace DigitalData.Core.Abstraction.Application #if NET
namespace DigitalData.Core.Abstraction.Application
{ {
/// <summary> /// <summary>
/// Implements a simplified CRUD service interface that uses a single Data Transfer Object (DTO) type for all CRUD operations, /// Implements a simplified CRUD service interface that uses a single Data Transfer Object (DTO) type for all CRUD operations,
@@ -18,4 +19,5 @@
where TDto : class where TEntity : class where TDto : class where TEntity : class
{ {
} }
} }
#endif

View File

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

View File

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

View File

@@ -1,41 +1,42 @@
using Microsoft.IdentityModel.Tokens; #if NET
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace DigitalData.Core.Abstraction.Application 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> /// <summary>
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>. /// Generates a symmetric security key with the specified byte size.
/// </summary> /// </summary>
public interface IJWTService<TClaimValue> /// <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)
{ {
/// <summary> using var rng = RandomNumberGenerator.Create();
/// Generates a symmetric security key with the specified byte size. var randomBytes = new byte[byteSize];
/// </summary> rng.GetBytes(randomBytes);
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param> var securityKey = new SymmetricSecurityKey(randomBytes);
/// <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 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);
} }
}
/// <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

@@ -1,39 +1,40 @@
using DigitalData.Core.Abstraction.Application.DTO; #if NET
using DigitalData.Core.Abstraction.Application.DTO;
namespace DigitalData.Core.Abstraction.Application namespace DigitalData.Core.Abstraction.Application;
[Obsolete("Use MediatR")]
public interface IReadService<TReadDto, TEntity, TId>
where TReadDto : class where TEntity : class
{ {
[Obsolete("Use MediatR")] /// <summary>
public interface IReadService<TReadDto, TEntity, TId> /// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult,
where TReadDto : class where TEntity : class /// including the readDTO on success or null and an error message on failure.
{ /// </summary>
/// <summary> /// <param name="id">The identifier of the entity to retrieve.</param>
/// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult, /// <returns>An DataResult containing the readDTO representing the found entity or null, with an appropriate message.</returns>
/// including the readDTO on success or null and an error message on failure. Task<DataResult<TReadDto>> ReadByIdAsync(TId id);
/// </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> /// <summary>
/// Retrieves all entities and returns their readDTO representations wrapped in an IServiceResult, /// 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. /// including a collection of readDTOs on success or an error message on failure.
/// </summary> /// </summary>
/// <returns>An DataResult containing a collection of readDTOs representing all entities or an error message.</returns> /// <returns>An DataResult containing a collection of readDTOs representing all entities or an error message.</returns>
Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync(); 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> /// <summary>
/// Asynchronously checks if an entity with the specified identifier exists within the data store. /// Deletes an entity by its identifier and returns the result wrapped in an IServiceMessage,
/// </summary> /// indicating the success or failure of the operation, including the error messages on failure.
/// <param name="id">The identifier of the entity to check.</param> /// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns> /// <param name="id">The identifier of the entity to delete.</param>
Task<bool> HasEntity(TId id); /// <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,63 +0,0 @@
using System.Linq.Expressions;
using DigitalData.Core.Abstractions.Interfaces;
namespace DigitalData.Core.Abstraction.Application.Repository;
public static class Extensions
{
#region IRepository
#region Read
public static IEnumerable<TEntity> GetAll<TEntity>(this IRepository repository) where TEntity : IEntity
=> repository.Entity<TEntity>().GetAll();
public static Task<IEnumerable<TEntity>> GetAllAsync<TEntity>(this IRepository repository, CancellationToken cancel = default) where TEntity : IEntity
=> repository.Entity<TEntity>().GetAllAsync(cancel);
public static IQueryable<TEntity> Where<TEntity>(this IRepository repository, Expression<Func<TEntity, bool>> expression)
where TEntity : IEntity
=> repository.Entity<TEntity>().Where(expression);
#endregion
#region Create
public static Task<TEntity> CreateAsync<TEntity>(this IRepository repository, TEntity entity, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().CreateAsync(entity, cancel);
public static Task<TEntity> CreateAsync<TEntity, TDto>(this IRepository repository, TDto dto, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().CreateAsync(dto, cancel);
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity>(this IRepository repository, IEnumerable<TEntity> entities, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().CreateAsync(entities, cancel);
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity, TDto>(this IRepository repository, IEnumerable<TDto> dtos, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().CreateAsync(dtos, cancel);
#endregion Create
#region Update
public static Task UpdateAsync<TEntity, TDto>(this IRepository repository, TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().UpdateAsync(dto, expression, cancel);
public static Task UpdateAsync<TEntity, TDto>(this IRepository repository, TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().UpdateAsync(dto, query, cancel);
#endregion
#region Delete
public static Task DeleteAsync<TEntity>(this IRepository repository, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().DeleteAsync(expression, cancel);
public static Task DeleteAsync<TEntity>(this IRepository repository, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().DeleteAsync(query, cancel);
#endregion
#endregion
}

View File

@@ -1,4 +1,5 @@
namespace DigitalData.Core.Abstraction.Application.Repository #if NET
namespace DigitalData.Core.Abstraction.Application.Repository
{ {
/// <summary> /// <summary>
/// Defines the contract for CRUD operations on a repository for entities of type TEntity. /// Defines the contract for CRUD operations on a repository for entities of type TEntity.
@@ -60,4 +61,5 @@
/// </remarks> /// </remarks>
Task<int> CountAsync(TId id); Task<int> CountAsync(TId id);
} }
} }
#endif

View File

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

View File

@@ -1,50 +1,140 @@
using DigitalData.Core.Abstractions.Interfaces; using System.Linq.Expressions;
using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query;
#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; namespace DigitalData.Core.Abstraction.Application.Repository
public interface IRepository<TEntity>
{ {
#region Create public interface IRepository
public Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default); {
#if NET
public
#endif
Task<int> ExecuteQueryRawAsync([NotParameterized] string sql, IEnumerable<object> parameters, CancellationToken cancel = default);
public Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default); #if NET
public
#endif
Task<int> ExecuteQueryInterpolatedAsync(FormattableString sql, CancellationToken cancel = default);
public Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default); #if NET
public
#endif
int ExecuteQueryRaw([NotParameterized] string sql, params object[] parameters);
public Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default); #if NET
#endregion Create public
#endif
int ExecuteQueryInterpolated(FormattableString sql);
}
#region Read public interface IRepository<TEntity>
public IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression); {
#region Create
#if NET
public
#endif
Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default);
public IEnumerable<TEntity> GetAll(); #if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default);
public Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default); #if NET
#endregion Read public
#endif
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default);
#region Update #if NET
public Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default); public
#endif
Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default);
#endregion Create
public Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default); #region Read
#endregion Update #if NET
public
#endif
IQueryable<TEntity> Query { get; }
#region Delete #if NET
public Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default); public
#endif
IQueryable<TEntity> QueryRaw([NotParameterized] string sql, params object[] parameters);
public Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default); #if NET
#endregion Delete public
#endif
IQueryable<TEntity> QueryInterpolated([NotParameterized] FormattableString sql);
#region Obsolete #if NET
[Obsolete("Use CreateAsync, UpdateAsync or DeleteAsync")] public
public IQueryable<TEntity> Read(); #endif
IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression);
[Obsolete("Use IRepository<TEntity>.Where")] #if NET
public IQueryable<TEntity> ReadOnly(); public
#endregion #endif
} IEnumerable<TEntity> GetAll();
public interface IRepository #if NET
{ public
public IRepository<TEntity> Entity<TEntity>() where TEntity : IEntity; #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
}
} }

View File

@@ -1,6 +0,0 @@
namespace DigitalData.Core.Abstraction.Application.Repository;
public interface IRepositoryFactory
{
public IRepository<TEntity> Get<TEntity>();
}

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

@@ -16,11 +16,17 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool> <PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon> <PackageIcon>core_icon.png</PackageIcon>
<Version>4.1.1</Version> <Version>4.3.0</Version>
<AssemblyVersion>4.1.1</AssemblyVersion> <AssemblyVersion>4.3.0</AssemblyVersion>
<FileVersion>4.1.1</FileVersion> <FileVersion>4.3.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="..\Assets\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<!-- disable for net462 --> <!-- disable for net462 -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'"> <PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
@@ -36,26 +42,27 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<None Include="..\..\nuget-package-icons\core_icon.png"> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<Pack>True</Pack> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackagePath>\</PackagePath>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.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.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
</ItemGroup> </ItemGroup>
</Project> </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

@@ -19,4 +19,13 @@ public class BadRequestException : Exception
public BadRequestException(string? message) : base(message) public BadRequestException(string? message) : base(message)
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error message and inner exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that caused the current exception.</param>
public BadRequestException(string? message, Exception? innerException) : base(message, innerException)
{
}
} }

View File

@@ -17,9 +17,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool> <PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon> <PackageIcon>core_icon.png</PackageIcon>
<Version>1.1.0</Version> <Version>1.1.1</Version>
<AssemblyVersion>1.1.0</AssemblyVersion> <AssemblyVersion>1.1.1</AssemblyVersion>
<FileVersion>1.1.0</FileVersion> <FileVersion>1.1.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,5 @@
using DigitalData.Core.Abstraction.Application; #if NET
using DigitalData.Core.Abstraction.Application;
using DigitalData.Core.Abstraction.Application.Repository; using DigitalData.Core.Abstraction.Application.Repository;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -110,4 +111,5 @@ namespace DigitalData.Core.Infrastructure
/// </remarks> /// </remarks>
public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.GetId().Equals(id)).CountAsync(); public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.GetId().Equals(id)).CountAsync();
} }
} }
#endif

View File

@@ -1,106 +1,162 @@
using AutoMapper; using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository; using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
using DigitalData.Core.Infrastructure.Factory; using DigitalData.Core.Infrastructure.Factory;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
#if NETFRAMEWORK
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
#endif
namespace DigitalData.Core.Infrastructure; namespace DigitalData.Core.Infrastructure
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
{ {
protected internal readonly TDbContext Context; public class DbRepository<TDbContext> : IRepository where TDbContext : DbContext
protected internal readonly DbSet<TEntity> Entities;
public IMapper? Mapper { get; }
public DbRepository(TDbContext context, DbSetFactory<TDbContext, TEntity> factory, IMapper? mapper = null)
{ {
Context = context; protected internal readonly TDbContext Context;
Entities = factory.Create(context);
Mapper = mapper;
}
#region Create public DbRepository(TDbContext context)
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default)
{
Entities.Add(entity);
await Context.SaveChangesAsync(cancel);
return entity;
}
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!.Map<TEntity>(dto), cancel);
public virtual Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default)
=> CreateAsync(Mapper!.Map<IEnumerable<TEntity>>(dtos), cancel);
#endregion Create
#region Read
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 async Task UpdateAsync<TDto>(TDto dto, 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--)
{ {
Mapper!.Map(dto, entities[i]); Context = context;
Entities.Update(entities[i]);
} }
await Context.SaveChangesAsync(cancel); public Task<int> ExecuteQueryRawAsync([NotParameterized] string sql, IEnumerable<object> parameters, CancellationToken cancel = default)
}
#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]); return Context.Database.ExecuteSqlRawAsync(sql, parameters, cancel);
} }
await Context.SaveChangesAsync(cancel); public Task<int> ExecuteQueryInterpolatedAsync(FormattableString sql, CancellationToken cancel = default)
{
return Context.Database.ExecuteSqlInterpolatedAsync(sql, cancel);
}
public int ExecuteQueryRaw([NotParameterized] string sql, params object[] parameters)
{
return Context.Database.ExecuteSqlRaw(sql, parameters);
}
public int ExecuteQueryInterpolated(FormattableString sql)
{
return Context.Database.ExecuteSqlInterpolated(sql);
}
} }
#endregion Delete
#region Obsolete public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
[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
}
public class DbRepository : IRepository
{
private readonly IRepositoryFactory _factory;
public DbRepository(IRepositoryFactory factory)
{ {
_factory = factory; protected internal readonly TDbContext Context;
}
public IRepository<TEntity> Entity<TEntity>() where TEntity : IEntity => _factory.Get<TEntity>(); protected internal readonly DbSet<TEntity> Entities;
public IMapper
#if NET
?
#endif
Mapper { get; }
public DbRepository(TDbContext context, DbSetFactory<TDbContext, TEntity> factory, IMapper
#if NET
?
#endif
mapper = null)
{
Context = context;
Entities = factory.Create(context);
Mapper = mapper;
}
#region Create
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default)
{
Entities.Add(entity);
await Context.SaveChangesAsync(cancel);
return entity;
}
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> QueryRaw([NotParameterized] string sql, params object[] parameters) => Entities.FromSqlRaw(sql, parameters).AsNoTracking();
public virtual IQueryable<TEntity> QueryInterpolated([NotParameterized] FormattableString sql) => Entities.FromSqlInterpolated(sql).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
}
} }

View File

@@ -4,117 +4,144 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection; using System.Reflection;
#if NETFRAMEWORK
using System.Collections.Generic;
using System;
using System.Linq;
#endif
namespace DigitalData.Core.Infrastructure; namespace DigitalData.Core.Infrastructure
public static class DependencyInjection
{ {
public static IServiceCollection AddDbRepository(this IServiceCollection services, Action<RepositoryConfiguration> options) public static class DependencyInjection
{ {
// register services from configuration public static IServiceCollection AddDbRepository(this IServiceCollection services, Action<RepositoryConfiguration> options)
var cfg = new RepositoryConfiguration(); {
options.Invoke(cfg); // register services from configuration
cfg.RegisterAllServices(services); var cfg = new RepositoryConfiguration();
options.Invoke(cfg);
cfg.RegisterAllServices(services);
// register db repository return services;
services.AddSingleton<IRepository, DbRepository>(); }
// register db repository factory public class RepositoryConfiguration
services.AddSingleton<IRepositoryFactory, DbRepositoryFactory>();
return services;
}
public class RepositoryConfiguration
{
// 1. register from assembly
private readonly Queue<Action<IServiceCollection>> RegsFromAssembly = new();
// 2. register entities (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsEntity = new();
// 3. register db set factories (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsDbSetFactory = new();
internal void RegisterAllServices(IServiceCollection services)
{ {
// 1. register from assembly // 1. register from assembly
RegsFromAssembly.InvokeAll(services); private readonly Queue<Action<IServiceCollection>> RegsFromAssembly = new Queue<Action<IServiceCollection>>();
// 2. register entities (can overwrite) // 2. register entities (can overwrite)
RegsEntity.InvokeAll(services); private readonly Queue<Action<IServiceCollection>> RegsEntity = new Queue<Action<IServiceCollection>>();
// 3. register db set factories (can overwrite) // 3. register db set factories (can overwrite)
RegsDbSetFactory.InvokeAll(services); private readonly Queue<Action<IServiceCollection>> RegsDbSetFactory = new Queue<Action<IServiceCollection>>();
}
internal RepositoryConfiguration() { } // 4. register repository
private readonly Queue<Action<IServiceCollection>> RegsRepository = new Queue<Action<IServiceCollection>>();
public void RegisterFromAssembly<TDbContext>(Assembly assembly) where TDbContext : DbContext internal void RegisterAllServices(IServiceCollection services)
{
void reg(IServiceCollection services)
{ {
// scan all types in the Assembly // 1. register from assembly
var entityTypes = assembly.GetTypes() RegsFromAssembly.InvokeAll(services);
.Where(t => t.IsClass && !t.IsAbstract && t.GetCustomAttribute<TableAttribute>() != null);
foreach (var entityType in entityTypes) // 2. register entities (can overwrite)
{ RegsEntity.InvokeAll(services);
#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 // 3. register db set factories (can overwrite)
services.AddScoped(interfaceType, repositoryType); RegsDbSetFactory.InvokeAll(services);
#endregion Repository
#region DbSetFactory // 4. register repository
/// register DbSetFactory RegsRepository.InvokeAll(services);
var addDbSetFactoryMethod = typeof(DependencyInjection)
.GetMethod(nameof(AddDbSetFactory),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var genericMethod = addDbSetFactoryMethod!.MakeGenericMethod(typeof(TDbContext), entityType);
genericMethod.Invoke(null, new [] { services, null });
#endregion DbSetFactory
}
} }
RegsFromAssembly.Enqueue(reg); 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));
public void RegisterDefaultRepository<TDbContext>()
where TDbContext : DbContext
=> RegsRepository.Enqueue(s => s.AddScoped<IRepository, DbRepository<TDbContext>>());
} }
public void RegisterEntity<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>>? dbSetFactory = null) 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 TDbContext : DbContext
where TEntity : class where TEntity : class
{ {
void reg(IServiceCollection services) #if NET
=> services create ??= ctx => ctx.Set<TEntity>();
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>() #elif NETFRAMEWORK
.AddDbSetFactory(dbSetFactory); if(create is null)
create = ctx => ctx.Set<TEntity>();
RegsEntity.Enqueue(reg); #endif
services.AddSingleton(_ => new DbSetFactory<TDbContext, TEntity>(create));
return services;
} }
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>>? create = null)
where TDbContext : DbContext
where TEntity : class
{
create ??= ctx => ctx.Set<TEntity>();
services.AddSingleton(_ => new DbSetFactory<TDbContext, TEntity>(create));
return services;
} }
} }

View File

@@ -1,9 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks> <TargetFrameworks>net462;net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>DigitalData.Core.Infrastructure</PackageId> <PackageId>DigitalData.Core.Infrastructure</PackageId>
<Authors>Digital Data GmbH</Authors> <Authors>Digital Data GmbH</Authors>
@@ -15,9 +13,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<RepositoryType>digital data core abstractions clean architecture</RepositoryType> <RepositoryType>digital data core abstractions clean architecture</RepositoryType>
<PackageTags>digital data core infrastructure clean architecture</PackageTags> <PackageTags>digital data core infrastructure clean architecture</PackageTags>
<Version>2.4.1</Version> <Version>2.6.1</Version>
<AssemblyVersion>2.4.1</AssemblyVersion> <AssemblyVersion>2.6.1</AssemblyVersion>
<FileVersion>2.4.1</FileVersion> <FileVersion>2.6.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -27,16 +25,38 @@
</None> </None>
</ItemGroup> </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" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.32" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.20" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.15" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,16 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Infrastructure.Factory;
public class DbRepositoryFactory : IRepositoryFactory
{
private readonly IServiceProvider _provider;
public DbRepositoryFactory(IServiceProvider provider)
{
_provider = provider;
}
public IRepository<TEntity> Get<TEntity>() => _provider.GetRequiredService<IRepository<TEntity>>();
}

View File

@@ -1,6 +1,14 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
#if NETFRAMEWORK
using System;
#endif
namespace DigitalData.Core.Infrastructure.Factory; namespace DigitalData.Core.Infrastructure.Factory
#if NET
;
#elif NETFRAMEWORK
{
#endif
public class DbSetFactory<TDbContext,TEntity> where TDbContext : DbContext where TEntity : class public class DbSetFactory<TDbContext,TEntity> where TDbContext : DbContext where TEntity : class
{ {
@@ -10,4 +18,8 @@ public class DbSetFactory<TDbContext,TEntity> where TDbContext : DbContext where
{ {
Create = 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,115 +0,0 @@
using DigitalData.Core.Tests.Mock;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Infrastructure;
namespace DigitalData.Core.Tests.Infrastructure;
public class DbRepositoryTests
{
private IHost _host;
private IRepository Repo;
[SetUp]
public void Setup()
{
var builder = Host.CreateApplicationBuilder();
builder.Services.AddDbContext<MockDbContext>(opt => opt.UseInMemoryDatabase("MockDB"));
builder.Services.AddDbRepository(opt =>
{
opt.RegisterFromAssembly<MockDbContext>(typeof(User).Assembly);
});
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
_host = builder.Build();
Repo = _host.Services.GetRequiredService<IRepository>();
}
[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 Repo.CreateAsync(faker.Generate(Random.Shared.Next(1, 10))));
else
Assert.DoesNotThrowAsync(async () => await Repo.CreateAsync(faker.Generate()));
}
[TestCase(true, TestName = "WhenDtoUsed")]
[TestCase(false, TestName = "WhenEntityUsed")]
public async Task ReadAsync_ShouldReturnCreated(bool useDto)
{
// Act
var createdUser = useDto
? await Repo.CreateAsync<User, UserCreateDto>(Fake.UserCreateDto)
: await Repo.CreateAsync(Fake.User);
var readUser = await Repo.Where<User>(u => u.Id == createdUser.Id).FirstOrDefaultAsync();
// 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 Repo.CreateAsync<User, UserCreateDto>(Fake.UserCreateDto);
var userUpdateDto = new UserUpdateDto() { Age = 10, Email = "Bar", FirstName = "Foo" };
// Act
await Repo.UpdateAsync<User, UserUpdateDto>(userUpdateDto, u => u.Id == createdUser!.Id);
var upToDateUser = await Repo.Where<User>(u => u.Id == createdUser!.Id).FirstOrDefaultAsync();
// 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 Repo.CreateAsync<User, UserCreateDto>(Fake.UserCreateDto);
var readUser = await Repo.Where<User>(u => u.Id == createdUser.Id).FirstOrDefaultAsync();
// Act
await Repo.DeleteAsync<User>(u => u.Id == createdUser.Id);
var deletedUser = await Repo.Where<User>(u => u.Id == createdUser.Id).FirstOrDefaultAsync();
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(deletedUser, Is.Null);
});
}
}

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstraction.Application.Repository; using DigitalData.Core.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace DigitalData.Core.Tests.Mock; namespace DigitalData.Core.Tests.Mock;
@@ -13,4 +13,4 @@ public class User : UserBase, IEntity
public override bool Equals(object? obj) public override bool Equals(object? obj)
=> (obj is User user && user.GetHashCode() == GetHashCode()) => (obj is User user && user.GetHashCode() == GetHashCode())
|| (obj is UserBase userBase && userBase.GetHashCode() == base.GetHashCode()); || (obj is UserBase userBase && userBase.GetHashCode() == base.GetHashCode());
} }

View File

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

View File

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

View File

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