diff --git a/Directory.Packages.props b/Directory.Packages.props index accb58a620..05b99f9dd1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -186,5 +186,6 @@ + \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index abd7184278..7ef0ca75e9 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -1910,6 +1910,7 @@ "DoYouSupportCustomABPArchitectures": "Do you support custom ABP architectures?", "SupportCustomABPArchitecturesExplanation": "No. Support is only provided for standard ABP solution structures. On the other hand, you can always get support for your custom needs with a paid consultancy from the ABP Team.", "DoesABPCollectAnyPersonalOrTechnicalData": "Does ABP collect any personal or technical data?", - "ABPCollectAnyDataExplanation": "The software may collect information about you and your use of the software, and send that to Volosoft. Volosoft as the software and service provider may use this information to provide services and improve its products & services. You may opt-out of these scenarios, as described in the EULA under PRIVACY AND COLLECTION OF PERSONAL DATA topic ." + "ABPCollectAnyDataExplanation": "The software may collect information about you and your use of the software, and send that to Volosoft. Volosoft as the software and service provider may use this information to provide services and improve its products & services. You may opt-out of these scenarios, as described in the EULA under PRIVACY AND COLLECTION OF PERSONAL DATA topic .", + "InThisDocument": "In this document" } } diff --git a/docs/en/Community-Articles/2025-08-25-AutoMapper-Alternatives/AutoMapper-Alternatives.md b/docs/en/Community-Articles/2025-08-25-AutoMapper-Alternatives/AutoMapper-Alternatives.md index fa1e1db495..e9c4dec4ee 100644 --- a/docs/en/Community-Articles/2025-08-25-AutoMapper-Alternatives/AutoMapper-Alternatives.md +++ b/docs/en/Community-Articles/2025-08-25-AutoMapper-Alternatives/AutoMapper-Alternatives.md @@ -35,7 +35,7 @@ Check out the comparison table for key features vs. AutoMapper. | **Maintainability** | Hidden mappings can be hard to debug | Explicit & predictable | Very explicit, compiler-verified mappings | Readable, good balance | Very explicit, most maintainable | | **Best For** | Large teams used to AutoMapper & willing to pay | Teams wanting performance + free tool | Teams prioritizing type-safety & performance | Developers needing flexibility & simplicity | Small/medium projects, performance-critical apps | -There are other libraries such as [**ExpressMapper**](https://github.com/fluentsprings/ExpressMapper) **(308K GitHub stars)**, [**ValueInjecter**](https://github.com/omuleanu/ValueInjecter) **(258K GitHub stars)**, [**AgileMapper**](https://github.com/agileobjects/AgileMapper) **(463K GitHub stars)**. These are not very popular but also free and offer a different balance of simplicity and features. +There are other libraries such as [**ExpressMapper**](https://github.com/fluentsprings/ExpressMapper) **(308 GitHub stars)**, [**ValueInjecter**](https://github.com/omuleanu/ValueInjecter) **(258 GitHub stars)**, [**AgileMapper**](https://github.com/agileobjects/AgileMapper) **(463 GitHub stars)**. These are not very popular but also free and offer a different balance of simplicity and features. @@ -111,35 +111,35 @@ Here are concise, drop-in **side-by-side C# snippets** that map the same model w We'll use these models to show the mapping examples for AutoMapper, Mapster, AgileMapper. ```csharp -public sealed class Order +public class Order { - public int Id { get; init; } - public Customer Customer { get; init; } = default!; - public List Lines { get; init; } = new(); - public DateTime CreatedAt { get; init; } + public int Id { get; set; } + public Customer Customer { get; set; } = default!; + public List Lines { get; set; } = new(); + public DateTime CreatedAt { get; set; } } -public sealed class Customer +public class Customer { - public int Id { get; init; } - public string Name { get; init; } = ""; - public string? Email { get; init; } + public int Id { get; set; } + public string Name { get; set; } = ""; + public string? Email { get; set; } } -public sealed class OrderLine +public class OrderLine { - public int ProductId { get; init; } - public int Quantity { get; init; } - public decimal UnitPrice { get; init; } + public int ProductId { get; set; } + public int Quantity { get; set; } + public decimal UnitPrice { get; set; } } -public sealed class OrderDto +public class OrderDto { - public int Id { get; init; } - public string CustomerName { get; init; } = ""; - public int ItemCount { get; init; } - public decimal Total { get; init; } - public string CreatedAtIso { get; init; } = ""; + public int Id { get; set; } + public string CustomerName { get; set; } = ""; + public int ItemCount { get; set; } + public decimal Total { get; set; } + public string CreatedAtIso { get; set; } = ""; } ``` @@ -189,32 +189,53 @@ public partial class OrderMapper { // Simple property mapping: Customer.Name -> CustomerName [MapProperty(nameof(Order.Customer) + "." + nameof(Customer.Name), nameof(OrderDto.CustomerName))] - public partial OrderDto ToDto(Order s); + public partial OrderDto ToDto(Order source); // Update an existing target (like MapToExisting) [MapProperty(nameof(Order.Customer) + "." + nameof(Customer.Name), nameof(OrderDto.CustomerName))] - public partial void UpdateDto(Order s, OrderDto target); + public partial void UpdateDto(Order source, OrderDto target); - // Post-process calculated fields (ItemCount, Total, CreatedAtIso) - // https://mapperly.riok.app/docs/configuration/before-after-map/ - [UserMapping(Default = true)] - private static void After(Order s, ref OrderDto d) + public OrderDto Map(Order s) { - d = d with - { - ItemCount = s.Lines.Sum(l => l.Quantity), - Total = s.Lines.Sum(l => l.Quantity * l.UnitPrice), - CreatedAtIso = s.CreatedAt.ToString("O") - }; + var d = ToDto(s); + AfterMap(s, d); + return d; + } + + public void Map(Order source, OrderDto d) + { + UpdateDto(source, d); + AfterMap(source, d); + } + + private void AfterMap(Order source, OrderDto d) + { + d.ItemCount = source.Lines.Sum(l => l.Quantity); + d.Total = source.Lines.Sum(l => l.Quantity * l.UnitPrice); + d.CreatedAtIso = source.CreatedAt.ToString("O"); } } + //USAGE var mapper = new OrderMapper(); -var dto = mapper.ToDto(order); - +var order = new Order +{ + Id = 1, + Customer = new Customer { Id = 1, Name = "John Doe", Email = "johndoe@abp.io" }, + Lines = + [ + new OrderLine {ProductId = 1, Quantity = 2, UnitPrice = 10.0m}, + new OrderLine {ProductId = 2, Quantity = 1, UnitPrice = 20.0m} + ] +}; + +// Map to a new object +var dto = mapper.Map(order); + +// Map to an existing object var target = new OrderDto(); -mapper.UpdateDto(order, target); +mapper.Map(order, target); ``` **NuGet Packages:** diff --git a/docs/en/Community-Articles/2025-08-27-backcompat-rest-apis-ms-dotnet/article.md b/docs/en/Community-Articles/2025-08-27-backcompat-rest-apis-ms-dotnet/article.md new file mode 100644 index 0000000000..f88bb0e41c --- /dev/null +++ b/docs/en/Community-Articles/2025-08-27-backcompat-rest-apis-ms-dotnet/article.md @@ -0,0 +1,169 @@ +# Best Practices for Designing Backward‑Compatible REST APIs in a Microservice Solution for .NET Developers + +## Introduction +With microservice architecture, each service develops and ships independently at its own pace, and clients infrequently update in lockstep. **Backward compatibility** means that when you release new versions, current consumers continue to function without changing code. This article provides a practical, 6–7 minute tutorial specific to **.NET developers**. + +--- +## What Counts as “Breaking”? (and what doesn’t) +A change is **breaking** if a client that previously conformed can **fail at compile time or runtime**, or exhibit **different business‑critical behavior**, **without** changing that client in any way. In other words: if an old client needs to be altered in order to continue functioning as it did, your change is breaking. + +### Examples of breaking changes +- **Deleting or renaming an endpoint** or modifying its URL/route. +- **Making an existing field required** (e.g., requiring `address`). +- **Data type or format changes** (e.g., `price: string` → `price: number`, or date format changes). +- **Altering default behavior or ordering** that clients implicitly depend on (hidden contracts). +- **Changing the error model** or HTTP status codes in a manner that breaks pre-existing error handling. +- **Renaming fields** or **making optional fields required** in requests or responses. +- **Reinterpreting semantics** (e.g., `status="closed"` formerly included archived items, but no longer does). + +### Examples of non‑breaking changes +- **Optional fields or query parameters can be added** (clients may disregard them). +- **Adding new enum values** (if the clients default to a safe behavior for unrecognized values). +- **Adding a new endpoint** while leaving the previous one unchanged. +- **Performance enhancements** that leave input/output unchanged. +- **Including metadata** (e.g., pagination links) without changing the current payload shape. + +> Golden rule: **Old clients should continue to work exactly as they did before—without any changes.** + +--- +## Versioning Strategy +Versioning is your master control lever for managing change. Typical methods: + +1) **URI Segment** (simplest) +``` +GET /api/v1/orders +GET /api/v2/orders +``` +Pros: Cache/gateway‑friendly; explicit in docs. Cons: URL noise. + +2) **Header‑Based** +``` +GET /api/orders +x-api-version: 2.0 +``` +Pros: Clean URLs; multiple reader support. Cons: Needs proxy/CDN rules. + +3) **Media Type** + Accept: application/json;v=2 + + Pros: Semantically accurate.
Cons: More complicated to test and implement.
**Recommendation:** For the majority of teams, favor **URI segments**, with an optional **`x-api-version`** header for flexibility. + +### Quick Setup in ASP.NET Core (Asp.Versioning) +```csharp +// Program.cs +using Asp.Versioning; + +builder.Services.AddControllers(); +builder.Services.AddApiVersioning(o => +{ + o.DefaultApiVersion = new ApiVersion(1, 0); + o.AssumeDefaultVersionWhenUnspecified = true; + o.ReportApiVersions = true; // response header: api-supported-versions + o.ApiVersionReader = ApiVersionReader.Combine( + new UrlSegmentApiVersionReader(), + new HeaderApiVersionReader("x-api-version") + ); +}); + +builder.Services.AddVersionedApiExplorer(o => +{ + o.GroupNameFormat = "'v'VVV"; // v1, v2 + o.SubstituteApiVersionInUrl = true; +}); +``` +```csharp +// Controller +using Asp.Versioning; + +[ApiController] +[Route("api/v{version:apiVersion}/orders")] +public class OrdersController : ControllerBase +{ + [HttpGet] + [ApiVersion("1.0", Deprecated = true)] + public IActionResult GetV1() => Ok(new { message = "v1" }); + + [HttpGet] + [MapToApiVersion("2.0")] + public IActionResult GetV2() => Ok(new { message = "v2", includes = new []{"items"} }); +} +``` + +--- +## Schema Evolution Playbook (JSON & DTO) +Obey the following rules for compatibility‑safe evolution: + +- **Add‑only changes**: Favor adding **optional** fields; do not remove/rename fields. +- **Maintain defaults**: When the new field is disregarded, the old functionality must not change. +- **Enum extension**: Clients should handle unknown enum values gracefully (default behavior). +- **Deprecation pipeline**: Mark fields/endpoints as deprecated **at least one version** prior to removal and publicize extensively. - **Stability by contract**: Record any unspoken contracts (ordering, casing, formats) that clients depend on. + +### Example: adding a non‑breaking field +```csharp +public record OrderDto( + Guid Id, + decimal Total, + string Currency, + string? SalesChannel // new, optional +); +``` + +--- +## Compatibility‑Safe API Behaviors +- **Error model**: Use a standard structure (e.g., RFC 7807 `ProblemDetails`). Avoid ad‑hoc error shapes on a per-endpoint basis. +- **Versioning/Deprecation communication** through headers: +- `api-supported-versions: 1.0, 2.0` +- `Deprecation: true` (in deprecated endpoints) +- `Sunset: Wed, 01 Oct 2025 00:00:00 GMT` (planned deprecation date) +- **Idempotency**: Use an `Idempotency-Key` header for retry-safe POSTs. +- **Optimistic concurrency**: Utilize `ETag`/`If-Match` to prevent lost updates. +- **Pagination**: Prefer cursor tokens (`nextPageToken`) to protect clients from sorting/index changes. +- **Time**: Employ ISO‑8601 in UTC; record time‑zone semantics and rounding conventions. + +--- +## Rollout & Deprecation Policy +A good deprecation policy is **announce → coexist → remove**: + +1) **Announce**: Release changelog, docs, and comms (mail/Slack) with v2 information and the sunset date. +2) **Coexist**: Operate v1 and v2 side by side. Employ gateway percentage routing for progressive cutover. +3) **Observability**: Monitor errors/latency/usage **by version**. When v1 traffic falls below ~5%, plan for removal. 4) **Remove**: Post sunset date, return **410 (Gone)** with a link to migration documentation. + +**Canary & Blue‑Green**: Initialize v2 with a small traffic portion and compare error/latency budgets prior to scaling up. + +--- +## Contract & Compatibility Testing +- **Consumer‑Driven Contracts**: Write expectations using Pact.NET; verify at provider CI. +- **Golden files / snapshots**: Freeze representative JSON payloads and automatically detect regressions. +- **Version-specific smoke tests**: Maintain separate, minimal test suites for v1 and v2. +- **SemVer discipline**: Minor = backward‑compatible; Major = breaking (avoid when possible). + +Minimal example (xUnit + snapshot style): +```csharp +[Fact] +public async Task Orders_v1_contract_should_match_snapshot() +{ + var resp = await _client.GetStringAsync("/api/v1/orders"); + Approvals.VerifyJson(resp); // snapshot comparison +} +``` + +--- +## Tooling & Docs (for .NET) +- **Asp.Versioning (NuGet)**: API versioning + ApiExplorer integration. +- **Swashbuckle / NSwag**: Generate an OpenAPI definition **for every version** (`/swagger/v1/swagger.json`, `/swagger/v2/swagger.json`). Display both in Swagger UI. +- **Polly**: Client‑side retries/fallbacks to handle transient failures and ensure resilience. +- **Serilog + OpenTelemetry**: Collect metrics/logs/traces by version for observability and SLOs. + +Swagger UI configuration by group name: +```csharp +app.UseSwagger(); +app.UseSwaggerUI(c => +{ +c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); +c.SwaggerEndpoint("/swagger/v2/swagger.json", "API v2"); +}); +``` +--- + +## Conclusion +Backward compatibility is not a version number—it is **disciplined change management**. When you use add‑only schema evolution, a well‑defined versioning strategy, strict contract testing, and rolling rollout, you maintain microservice independence and safeguard consumer experience. diff --git a/docs/en/Community-Articles/2025-08-27-backcompat-rest-apis-ms-dotnet/cover.png b/docs/en/Community-Articles/2025-08-27-backcompat-rest-apis-ms-dotnet/cover.png new file mode 100644 index 0000000000..07f0782775 Binary files /dev/null and b/docs/en/Community-Articles/2025-08-27-backcompat-rest-apis-ms-dotnet/cover.png differ diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md index 60d4e3ff0b..415cef1878 100644 --- a/docs/en/cli/index.md +++ b/docs/en/cli/index.md @@ -33,44 +33,44 @@ While each command may have a set of options, there are some global options that Here, is the list of all available commands before explaining their details: -* **`help`**: Shows help on the usage of the ABP CLI. -* **`cli`**: Update or remove ABP CLI. -* **`new`**: Generates a new solution based on the ABP [startup templates](../solution-templates/index.md). -* **`new-module`**: Generates a new module based on the given template. -* **`new-package`**: Generates a new package based on the given template. -* **`update`**: Automatically updates all ABP related NuGet and NPM packages in a solution. -* **`clean`**: Deletes all `BIN` and `OBJ` folders in the current folder. -* **`add-package`**: Adds an ABP package to a project. -* **`add-package-ref`**: Adds package to given project. -* **`install-module`**: Adds a [multi-package application module](../modules/index.md) to a given module. -* **`install-local-module`**: Installs a local module to given module. -* **`list-modules`**: Lists names of application modules. -* **`list-templates`**: Lists the names of available templates to create a solution. -* **`get-source`**: Downloads the source code of a module. -* **`add-source-code`**: Downloads the source code and replaces package references with project references. -* **`init-solution`**: Creates ABP Studio configuration files for a given solution. -* **`kube-connect`**: Connects to kubernetes environment. (*Available for* ***Business*** *or higher licenses*) -* **`kube-intercept`**: Intercepts a service running in Kubernetes environment. (*Available for* ***Business*** *or higher licenses*) -* **`list-module-sources`**: Lists the remote module sources. -* **`add-module-source`**: Adds a remote module source. -* **`delete-module-source`**: Deletes a remote module source. -* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints. -* **`remove-proxy`**: Removes previously generated client side proxies. -* **`switch-to-preview`**: Switches to the latest preview version of the ABP. -* **`switch-to-nightly`**: Switches to the latest [nightly builds](../release-info/nightly-builds.md) of the ABP related packages on a solution. -* **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution. -* **`switch-to-local`**: Changes NuGet package references on a solution to local project references. -* **`upgrade`**: It converts the application to use pro modules. -* **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](../framework/fundamentals/localization.md) files in a source control repository. -* **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. -* **`login-info`**: Shows the current user's login information. -* **`logout`**: Logouts from your computer if you've authenticated before. -* **`bundle`**: Generates script and style references for ABP Blazor and MAUI Blazor project. -* **`install-libs`**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. -* **`clear-download-cache`**: Clears the templates download cache. -* **`check-extensions`**: Checks the latest version of the ABP CLI extensions. -* **`install-old-cli`**: Installs old ABP CLI. -* **`generate-razor-page`**: Generates a page class that you can use it in the ASP NET Core pipeline to return an HTML page. +* **[`help`](../cli#help)**: Shows help on the usage of the ABP CLI. +* **[`cli`](../cli#cli)**: Update or remove ABP CLI. +* **[`new`](../cli#new)**: Generates a new solution based on the ABP [startup templates](../solution-templates/index.md). +* **[`new-module`](../cli#new-module)**: Generates a new module based on the given template. +* **[`new-package`](../cli#new-package)**: Generates a new package based on the given template. +* **[`update`](../cli#update)**: Automatically updates all ABP related NuGet and NPM packages in a solution. +* **[`clean`](../cli#clean)**: Deletes all `BIN` and `OBJ` folders in the current folder. +* **[`add-package`](../cli#add-package)**: Adds an ABP package to a project. +* **[`add-package-ref`](../cli#add-package-ref)**: Adds package to given project. +* **[`install-module`](../cli#install-module)**: Adds a [multi-package application module](../modules/index.md) to a given module. +* **[`install-local-module`](../cli#install-local-module)**: Installs a local module to given module. +* **[`list-modules`](../cli#list-modules)**: Lists names of application modules. +* **[`list-templates`](../cli#list-templates)**: Lists the names of available templates to create a solution. +* **[`get-source`](../cli#get-source)**: Downloads the source code of a module. +* **[`add-source-code`](../cli#add-source-code)**: Downloads the source code and replaces package references with project references. +* **[`init-solution`](../cli#init-solution)**: Creates ABP Studio configuration files for a given solution. +* **[`kube-connect`](../cli#kube-connect)**: Connects to kubernetes environment. (*Available for* ***Business*** *or higher licenses*) +* **[`kube-intercept`](../cli#kube-intercept)**: Intercepts a service running in Kubernetes environment. (*Available for* ***Business*** *or higher licenses*) +* **[`list-module-sources`](../cli#list-module-sources)**: Lists the remote module sources. +* **[`add-module-source`](../cli#add-module-source)**: Adds a remote module source. +* **[`delete-module-source`](../cli#delete-module-source)**: Deletes a remote module source. +* **[`generate-proxy`](../cli#generate-proxy)**: Generates client side proxies to use HTTP API endpoints. +* **[`remove-proxy`](../cli#remove-proxy)**: Removes previously generated client side proxies. +* **[`switch-to-preview`](../cli#switch-to-preview)**: Switches to the latest preview version of the ABP. +* **[`switch-to-nightly`](../cli#switch-to-nightly)**: Switches to the latest [nightly builds](../release-info/nightly-builds.md) of the ABP related packages on a solution. +* **[`switch-to-stable`](../cli#switch-to-stable)**: Switches to the latest stable versions of the ABP related packages on a solution. +* **[`switch-to-local`](../cli#switch-to-local)**: Changes NuGet package references on a solution to local project references. +* **[`upgrade`](../cli#upgrade)**: It converts the application to use pro modules. +* **[`translate`](../cli#translate)**: Simplifies to translate localization files when you have multiple JSON [localization](../framework/fundamentals/localization.md) files in a source control repository. +* **[`login`](../cli#login)**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. +* **[`login-info`](../cli#login-info)**: Shows the current user's login information. +* **[`logout`](../cli#logout)**: Logouts from your computer if you've authenticated before. +* **[`bundle`](../cli#bundle)**: Generates script and style references for ABP Blazor and MAUI Blazor project. +* **[`install-libs`](../cli#install-libs)**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. +* **[`clear-download-cache`](../cli#clear-download-cache)**: Clears the templates download cache. +* **[`check-extensions`](../cli#check-extensions)**: Checks the latest version of the ABP CLI extensions. +* **[`install-old-cli`](../cli#install-old-cli)**: Installs old ABP CLI. +* **[`generate-razor-page`](../cli#generate-razor-page)**: Generates a page class that you can use it in the ASP NET Core pipeline to return an HTML page. ### help diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/TelemetryApplicationMetricsEnricher.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/TelemetryApplicationMetricsEnricher.cs new file mode 100644 index 0000000000..fa95f5c298 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/TelemetryApplicationMetricsEnricher.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.DependencyInjection; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Internal.Telemetry.Activity; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Activity.Providers; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))] +public sealed class TelemetryApplicationMetricsEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher +{ + private readonly ITypeFinder _typeFinder; + public TelemetryApplicationMetricsEnricher(ITypeFinder typeFinder, IServiceProvider serviceProvider) : base(serviceProvider) + { + _typeFinder = typeFinder; + } + + protected override Task CanExecuteAsync(ActivityContext context) + { + return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime); + } + + protected override Task ExecuteAsync(ActivityContext context) + { + var appServiceCount = _typeFinder.Types.Count(t => + typeof(IApplicationService).IsAssignableFrom(t) && + t is { IsAbstract: false, IsInterface: false } && + !t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); + + var controllerCount = _typeFinder.Types.Count(t => + typeof(ControllerBase).IsAssignableFrom(t) && + !t.IsAbstract && + !t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); + + + context.Current[ActivityPropertyNames.AppServiceCount] = appServiceCount; + context.Current[ActivityPropertyNames.ControllerCount] = controllerCount; + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Properties/AssemblyInfo.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f9e35084b9 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Authorization.Abstractions")] +[assembly: AssemblyTrademark("")] +[assembly: InternalsVisibleTo("Volo.Abp.Authorization")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("83632bec-2f60-4c2b-b964-30e8b37fa9d8")] diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs index ab49e4aad4..0f5d713e2b 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinition.cs @@ -108,6 +108,8 @@ public class PermissionDefinition : Parent = this }; + child[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName] = this[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName]; + _children.Add(child); return child; diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs index 85d771a6ab..394cdb9d82 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionDefinitionContext.cs @@ -11,6 +11,13 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext public Dictionary Groups { get; } + internal IPermissionDefinitionProvider? CurrentProvider { get; set; } + + public static class KnownPropertyNames + { + public const string CurrentProviderName = "_CurrentProviderName"; + } + public PermissionDefinitionContext(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; @@ -28,7 +35,16 @@ public class PermissionDefinitionContext : IPermissionDefinitionContext throw new AbpException($"There is already an existing permission group with name: {name}"); } - return Groups[name] = new PermissionGroupDefinition(name, displayName); + var group = new PermissionGroupDefinition(name, displayName); + + if (CurrentProvider != null) + { + group[KnownPropertyNames.CurrentProviderName] = CurrentProvider.GetType().FullName; + } + + Groups[name] = group; + + return group; } [NotNull] diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs index bbc6e96cdf..0ad2dca099 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/PermissionGroupDefinition.cs @@ -61,6 +61,8 @@ public class PermissionGroupDefinition : ICanAddChildPermission isEnabled ); + permission[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName] = this[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName]; + _permissions.Add(permission); return permission; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs index 2d0a9d668c..4e6ff0d11c 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs @@ -84,18 +84,23 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, foreach (var provider in providers) { + context.CurrentProvider = provider; provider.PreDefine(context); } foreach (var provider in providers) { + context.CurrentProvider = provider; provider.Define(context); } foreach (var provider in providers) { + context.CurrentProvider = provider; provider.PostDefine(context); } + + context.CurrentProvider = null; return context.Groups; } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/TelemetryPermissionInfoEnricher.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/TelemetryPermissionInfoEnricher.cs new file mode 100644 index 0000000000..4fab06ba8f --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/TelemetryPermissionInfoEnricher.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Activity.Providers; +using Volo.Abp.Internal.Telemetry.Constants; + +namespace Volo.Abp.Authorization.Permissions; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))] +public sealed class TelemetryPermissionInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher +{ + private readonly IPermissionDefinitionManager _permissionDefinitionManager; + + public TelemetryPermissionInfoEnricher(IPermissionDefinitionManager permissionDefinitionManager, + IServiceProvider serviceProvider) : base(serviceProvider) + { + _permissionDefinitionManager = permissionDefinitionManager; + } + + protected override Task CanExecuteAsync(ActivityContext context) + { + return Task.FromResult(context.ProjectId.HasValue); + } + + protected async override Task ExecuteAsync(ActivityContext context) + { + var permissions = await _permissionDefinitionManager.GetPermissionsAsync(); + + var userDefinedPermissionsCount = permissions.Count(IsUserDefinedPermission); + + context.Current[ActivityPropertyNames.PermissionCount] = userDefinedPermissionsCount; + } + + private static bool IsUserDefinedPermission(PermissionDefinition permission) + { + return permission.Properties.TryGetValue(PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName, out var providerName) && + providerName is string && + !providerName.ToString()!.StartsWith(TelemetryConsts.VoloNameSpaceFilter); + } + + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj index caa6b6be94..5afcca9fbe 100644 --- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj +++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs index ec20467ac9..6811e2a9a1 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs @@ -4,13 +4,17 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; using Volo.Abp.Internal; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; using Volo.Abp.Logging; using Volo.Abp.Modularity; +using Volo.Abp.Threading; namespace Volo.Abp; @@ -149,6 +153,56 @@ public abstract class AbpApplicationBase : IAbpApplication options.PlugInSources ); } + protected void SetupTelemetryTracking() + { + if (!ShouldSendTelemetryData()) + { + return; + } + + AsyncHelper.RunSync(InitializeTelemetryTracking); + } + + protected async Task SetupTelemetryTrackingAsync() + { + if (!ShouldSendTelemetryData()) + { + return; + } + + await InitializeTelemetryTracking(); + } + + private async Task InitializeTelemetryTracking() + { + try + { + using var scope = ServiceProvider.CreateScope(); + var telemetryService = scope.ServiceProvider.GetRequiredService(); + await telemetryService.AddActivityAsync(ActivityNameConsts.ApplicationRun); + } + catch (Exception ex) + { + try + { + using var scope = ServiceProvider.CreateScope(); + var logger = scope.ServiceProvider.GetRequiredService>(); + logger.LogException(ex, LogLevel.Trace); + } + catch + { + /* ignored */ + } + } + } + + private bool ShouldSendTelemetryData() + { + using var scope = ServiceProvider.CreateScope(); + var abpHostEnvironment = scope.ServiceProvider.GetRequiredService(); + var configuration = scope.ServiceProvider.GetRequiredService(); + return abpHostEnvironment.IsDevelopment() && configuration.GetValue("Abp:Telemetry:IsEnabled") == true; + } //TODO: We can extract a new class for this public virtual async Task ConfigureServicesAsync() diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs index d80e46c5d7..faf4f7fa1a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs @@ -44,6 +44,8 @@ internal class AbpApplicationWithExternalServiceProvider : AbpApplicationBase, I SetServiceProvider(serviceProvider); await InitializeModulesAsync(); + + await SetupTelemetryTrackingAsync(); } public void Initialize([NotNull] IServiceProvider serviceProvider) @@ -53,6 +55,8 @@ internal class AbpApplicationWithExternalServiceProvider : AbpApplicationBase, I SetServiceProvider(serviceProvider); InitializeModules(); + + SetupTelemetryTracking(); } public override void Dispose() diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs index c5e5eccc83..b652999a7a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs @@ -50,12 +50,15 @@ internal class AbpApplicationWithInternalServiceProvider : AbpApplicationBase, I { CreateServiceProvider(); await InitializeModulesAsync(); + await SetupTelemetryTrackingAsync(); + } public void Initialize() { CreateServiceProvider(); InitializeModules(); + SetupTelemetryTracking(); } public override void Dispose() diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs new file mode 100644 index 0000000000..8a03a66e65 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityContext.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; + +namespace Volo.Abp.Internal.Telemetry.Activity; + +public class ActivityContext +{ + public ActivityEvent Current { get; } + public Dictionary ExtraProperties { get; } = new(); + public bool IsTerminated { get; private set; } + + public Guid? ProjectId => Current.Get(ActivityPropertyNames.ProjectId); + + public Guid? SolutionId => Current.Get(ActivityPropertyNames.SolutionId); + + public SessionType? SessionType => Current.Get(ActivityPropertyNames.SessionType); + + public string? DeviceId => Current.Get(ActivityPropertyNames.DeviceId); + + public string? SolutionPath => ExtraProperties.TryGetValue(ActivityPropertyNames.SolutionPath, out var solutionPath) + ? solutionPath?.ToString() + : null; + + private ActivityContext(ActivityEvent current) + { + Current = current; + } + + public static ActivityContext Create(string activityName, string? details = null, + Action>? additionalProperties = null) + { + var activity = new ActivityEvent(activityName, details); + + if (additionalProperties is not null) + { + var additionalPropertiesDict = new Dictionary(); + activity[ActivityPropertyNames.AdditionalProperties] = additionalPropertiesDict; + additionalProperties.Invoke(additionalPropertiesDict); + } + + return new ActivityContext(activity); + } + + public void Terminate() + { + IsTerminated = true; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityEvent.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityEvent.cs new file mode 100644 index 0000000000..22ad4c0da3 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/ActivityEvent.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Volo.Abp.Internal.Telemetry.Constants; + +namespace Volo.Abp.Internal.Telemetry.Activity; + +public class ActivityEvent : Dictionary +{ + public ActivityEvent() + { + this[ActivityPropertyNames.Id] = Guid.NewGuid(); + this[ActivityPropertyNames.Time] = DateTimeOffset.UtcNow; + } + + public ActivityEvent(string activityName, string? details = null) : this() + { + Check.NotNullOrWhiteSpace(activityName, nameof(activityName)); + + this[ActivityPropertyNames.ActivityName] = activityName; + this[ActivityPropertyNames.ActivityDetails] = details; + } + + public bool HasSolutionInfo() + { + return this.ContainsKey(ActivityPropertyNames.HasSolutionInfo); + } + + public bool HasDeviceInfo() + { + return this.ContainsKey(ActivityPropertyNames.HasDeviceInfo); + } + + public bool HasProjectInfo() + { + return this.ContainsKey(ActivityPropertyNames.HasProjectInfo); + } + + public T Get(string key) + { + return TryConvert(key, out var value) ? value : default!; + } + + public bool TryGetValue(string key, out T value) + { + return TryConvert(key, out value); + } + + private bool TryConvert(string key, out T result) + { + result = default!; + if (!this.TryGetValue(key, out var value) || value is null) + { + return false; + } + + try + { + if (value is T tValue) + { + result = tValue; + return true; + } + + if (value is JsonElement jsonElement) + { + value = ExtractFromJsonElement(jsonElement); + if (value is null) + { + return false; + } + } + + var targetType = typeof(T); + var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + if (underlyingType.IsEnum) + { + if (value is string str) + { + result = (T)Enum.Parse(underlyingType, str, ignoreCase: true); + } + else if (value is int intValue) + { + result = (T)Enum.ToObject(underlyingType, intValue); + } + + return true; + } + + + if (underlyingType == typeof(Dictionary[])) + { + result = (T)value; + return true; + } + + if (underlyingType == typeof(Guid)) + { + result = (T)(object)Guid.Parse(value.ToString()!); + return true; + } + + if (underlyingType == typeof(DateTimeOffset)) + { + result = (T)(object)DateTimeOffset.Parse(value.ToString()!); + return true; + } + + // Nullable types + result = (T)Convert.ChangeType(value, underlyingType); + return true; + } + catch + { + return false; + } + } + + private static object? ExtractFromJsonElement(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.GetInt32(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Null => null, + JsonValueKind.Array => element.EnumerateArray() + .Select(item => + { + if (item.ValueKind == JsonValueKind.Object) + { + return item.EnumerateObject() + .ToDictionary(prop => prop.Name, prop => ExtractFromJsonElement(prop.Value)); + } + + return new Dictionary { { "value", ExtractFromJsonElement(item) } }; + }) + .ToArray(), + + JsonValueKind.Object => element.EnumerateObject() + .ToDictionary(prop => prop.Name, prop => ExtractFromJsonElement(prop.Value)), + _ => element.ToString() + }; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/IHasParentTelemetryActivityEventEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/IHasParentTelemetryActivityEventEnricher.cs new file mode 100644 index 0000000000..5547445d54 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/IHasParentTelemetryActivityEventEnricher.cs @@ -0,0 +1,7 @@ +using Volo.Abp.Internal.Telemetry.Activity.Providers; + +namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; + +public interface IHasParentTelemetryActivityEventEnricher where TParent: TelemetryActivityEventEnricher +{ +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventBuilder.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventBuilder.cs new file mode 100644 index 0000000000..0542b9d676 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventBuilder.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; + +public interface ITelemetryActivityEventBuilder +{ + Task BuildAsync(ActivityContext context); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventEnricher.cs new file mode 100644 index 0000000000..b83741cd88 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityEventEnricher.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; + +public interface ITelemetryActivityEventEnricher +{ + int ExecutionOrder { get; } + + Task EnrichAsync(ActivityContext context); + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs new file mode 100644 index 0000000000..870876ffbd --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Contracts/ITelemetryActivityStorage.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Internal.Telemetry.Activity.Contracts; + +public interface ITelemetryActivityStorage +{ + Guid InitializeOrGetSession(); + void DeleteActivities(ActivityEvent[] activities); + void SaveActivity(ActivityEvent activityEvent); + List GetActivities(); + bool ShouldAddDeviceInfo(); + bool ShouldAddSolutionInformation(Guid solutionId); + bool ShouldAddProjectInfo(Guid projectId); + bool ShouldSendActivities(); + void MarkActivitiesAsFailed(ActivityEvent[] activities); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventBuilder.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventBuilder.cs new file mode 100644 index 0000000000..201ac8c3b6 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventBuilder.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; + +public class TelemetryActivityEventBuilder : ITelemetryActivityEventBuilder, ISingletonDependency +{ + private readonly List _activityEnrichers; + + public TelemetryActivityEventBuilder(IEnumerable activityDataEnrichers) + { + _activityEnrichers = activityDataEnrichers + .Where(FilterEnricher) + .OrderByDescending(x => x.ExecutionOrder) + .ToList(); + } + public virtual async Task BuildAsync(ActivityContext context) + { + foreach (var enricher in _activityEnrichers) + { + try + { + await enricher.EnrichAsync(context); + } + catch + { + //ignored + } + + if (context.IsTerminated) + { + return null; + } + } + + return context.Current; + } + + private static bool FilterEnricher(ITelemetryActivityEventEnricher enricher) + { + return ProxyHelper.GetUnProxiedType(enricher).Assembly.FullName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter) && + enricher is not IHasParentTelemetryActivityEventEnricher; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventEnricher.cs new file mode 100644 index 0000000000..d25e9c58f8 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryActivityEventEnricher.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; + +public abstract class TelemetryActivityEventEnricher : ITelemetryActivityEventEnricher, IScopedDependency +{ + public virtual int ExecutionOrder { get; set; } = 0; + protected bool IgnoreChildren { get; set; } + protected virtual Type? ReplaceParentType { get; set; } + + private readonly IServiceProvider _serviceProvider; + + protected TelemetryActivityEventEnricher(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task EnrichAsync(ActivityContext context) + { + if (!await CanExecuteAsync(context)) + { + return; + } + + await ExecuteAsync(context); + await ExecuteChildrenAsync(context); + } + + protected virtual Task CanExecuteAsync(ActivityContext context) + { + return Task.FromResult(true); + } + + protected abstract Task ExecuteAsync(ActivityContext context); + + protected virtual async Task ExecuteChildrenAsync(ActivityContext context) + { + if (IgnoreChildren) + { + return; + } + + using var scope = _serviceProvider.CreateScope(); + + foreach (var child in GetChildren(scope.ServiceProvider)) + { + await child.EnrichAsync(context); + } + } + + private ITelemetryActivityEventEnricher[] GetChildren(IServiceProvider serviceProvider) + { + var targetType = ReplaceParentType ?? ProxyHelper.GetUnProxiedType(this); + var genericInterfaceType = typeof(IHasParentTelemetryActivityEventEnricher<>).MakeGenericType(targetType); + var enumerableType = typeof(IEnumerable<>).MakeGenericType(genericInterfaceType); + + var childServices = (IEnumerable)serviceProvider.GetRequiredService(enumerableType); + + return childServices + .Cast() + .ToArray(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs new file mode 100644 index 0000000000..6616c62bda --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryApplicationInfoEnricher.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.Helpers; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))] +public sealed class TelemetryApplicationInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher +{ + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + + public TelemetryApplicationInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, IServiceProvider serviceProvider) : base(serviceProvider) + { + _telemetryActivityStorage = telemetryActivityStorage; + } + + protected override Task CanExecuteAsync(ActivityContext context) + { + return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime); + } + + protected override Task ExecuteAsync(ActivityContext context) + { + try + { + var entryAssembly = Assembly.GetEntryAssembly(); + if (entryAssembly is null) + { + context.Terminate(); + return Task.CompletedTask; + } + + var projectMetaData = AbpProjectMetadataReader.ReadProjectMetadata(entryAssembly); + if (projectMetaData?.ProjectId == null || projectMetaData.AbpSlnPath.IsNullOrEmpty()) + { + context.Terminate(); + return Task.CompletedTask; + } + + if (!_telemetryActivityStorage.ShouldAddProjectInfo(projectMetaData.ProjectId.Value)) + { + IgnoreChildren = true; + return Task.CompletedTask; + } + + var solutionId = ReadSolutionIdFromSolutionPath(projectMetaData.AbpSlnPath); + + if (!solutionId.HasValue) + { + IgnoreChildren = true; + context.Terminate(); + return Task.CompletedTask; + } + + context.ExtraProperties[ActivityPropertyNames.SolutionPath] = projectMetaData.AbpSlnPath; + context.Current[ActivityPropertyNames.ProjectType] = projectMetaData.Role ?? string.Empty; + context.Current[ActivityPropertyNames.ProjectId] = projectMetaData.ProjectId.Value; + context.Current[ActivityPropertyNames.SolutionId] = solutionId; + context.Current[ActivityPropertyNames.HasProjectInfo] = true; + } + catch + { + //ignored + } + + return Task.CompletedTask; + } + + + private static Guid? ReadSolutionIdFromSolutionPath(string solutionPath) + { + try + { + if (solutionPath.IsNullOrEmpty()) + { + return null; + } + + using var fs = new FileStream(solutionPath!, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var doc = JsonDocument.Parse(fs, new JsonDocumentOptions + { + AllowTrailingCommas = true + }); + if (doc.RootElement.TryGetProperty("id", out var property) && property.TryGetGuid(out var solutionId)) + { + return solutionId; + } + } + catch + { + // ignored + } + + return null; + } + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs new file mode 100644 index 0000000000..5081519b40 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryDeviceInfoEnricher.cs @@ -0,0 +1,79 @@ +using System; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher))] +internal sealed class TelemetryDeviceInfoEnricher : TelemetryActivityEventEnricher +{ + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + private readonly ISoftwareInfoProvider _softwareInfoProvider; + + public TelemetryDeviceInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, + ISoftwareInfoProvider softwareInfoProvider, IServiceProvider serviceProvider) : base(serviceProvider) + { + _telemetryActivityStorage = telemetryActivityStorage; + _softwareInfoProvider = softwareInfoProvider; + } + protected async override Task ExecuteAsync(ActivityContext context) + { + try + { + var deviceId = DeviceManager.GetUniquePhysicalKey(true); + context.Current[ActivityPropertyNames.DeviceId] = deviceId; + + if (!_telemetryActivityStorage.ShouldAddDeviceInfo()) + { + return; + } + + var softwareList = await _softwareInfoProvider.GetSoftwareInfoAsync(); + + context.Current[ActivityPropertyNames.InstalledSoftwares] = softwareList; + context.Current[ActivityPropertyNames.DeviceLanguage] = CultureInfo.CurrentUICulture.Name; + context.Current[ActivityPropertyNames.OperatingSystem] = GetOperatingSystem(); + context.Current[ActivityPropertyNames.CountryIsoCode] = GetCountry(); + context.Current[ActivityPropertyNames.HasDeviceInfo] = true; + context.Current[ActivityPropertyNames.OperatingSystemArchitecture] = RuntimeInformation.OSArchitecture.ToString(); + } + catch + { + //ignored + } + } + + private static OperationSystem GetOperatingSystem() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperationSystem.Windows; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperationSystem.Linux; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperationSystem.MacOS; + } + + return OperationSystem.Unknown; + } + + + + private static string GetCountry() + { + var region = new RegionInfo(CultureInfo.InstalledUICulture.Name); + return region.TwoLetterISORegionName; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryModuleInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryModuleInfoEnricher.cs new file mode 100644 index 0000000000..d35973b79d --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetryModuleInfoEnricher.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Modularity; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))] +internal sealed class TelemetryModuleInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher +{ + private readonly IModuleContainer _moduleContainer; + private readonly IAssemblyFinder _assemblyFinder; + + public TelemetryModuleInfoEnricher(IModuleContainer moduleContainer, IAssemblyFinder assemblyFinder, + IServiceProvider serviceProvider) : base(serviceProvider) + { + _moduleContainer = moduleContainer; + _assemblyFinder = assemblyFinder; + } + + protected override Task CanExecuteAsync(ActivityContext context) + { + return Task.FromResult(context.SessionType == SessionType.ApplicationRuntime); + } + + protected override Task ExecuteAsync(ActivityContext context) + { + context.Current[ActivityPropertyNames.ModuleCount] = _moduleContainer.Modules.Count; + context.Current[ActivityPropertyNames.ProjectCount] = _assemblyFinder.Assemblies.Count(x => + !x.FullName.IsNullOrEmpty() && + !x.FullName.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySessionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySessionInfoEnricher.cs new file mode 100644 index 0000000000..a015d40733 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySessionInfoEnricher.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; +[ExposeServices(typeof(ITelemetryActivityEventEnricher))] +public class TelemetrySessionInfoEnricher : TelemetryActivityEventEnricher +{ + public override int ExecutionOrder { get; set; } = 10; + + public TelemetrySessionInfoEnricher(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + protected override Task ExecuteAsync(ActivityContext context) + { + context.Current[ActivityPropertyNames.SessionType] = SessionType.ApplicationRuntime; + context.Current[ActivityPropertyNames.SessionId] = Guid.NewGuid().ToString(); + context.Current[ActivityPropertyNames.IsFirstSession] = !File.Exists(TelemetryPaths.ActivityStorage); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs new file mode 100644 index 0000000000..785ca0dfa1 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; + +namespace Volo.Abp.Internal.Telemetry.Activity.Providers; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))] +internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher +{ + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + + public TelemetrySolutionInfoEnricher(ITelemetryActivityStorage telemetryActivityStorage, IServiceProvider serviceProvider) : base(serviceProvider) + { + _telemetryActivityStorage = telemetryActivityStorage; + } + + protected override Task CanExecuteAsync(ActivityContext context) + { + if (context.SolutionId.HasValue && !context.SolutionPath.IsNullOrEmpty()) + { + return Task.FromResult(_telemetryActivityStorage.ShouldAddSolutionInformation(context.SolutionId.Value)); + } + + return Task.FromResult(false); + } + + protected override Task ExecuteAsync(ActivityContext context) + { + try + { + var jsonContent = File.ReadAllText(context.SolutionPath!); + using var doc = JsonDocument.Parse(jsonContent, new JsonDocumentOptions + { + AllowTrailingCommas = true + }); + + var root = doc.RootElement; + + if (root.TryGetProperty("creatingStudioConfiguration", out var creatingStudioConfiguration)) + { + AddSolutionCreationConfiguration(context, creatingStudioConfiguration); + } + + if (root.TryGetProperty("modules", out var modulesElement)) + { + AddModuleInfo(context, modulesElement); + } + + context.Current[ActivityPropertyNames.HasSolutionInfo] = true; + } + catch + { + //ignored + } + + return Task.CompletedTask; + } + + private static void AddSolutionCreationConfiguration(ActivityContext context, JsonElement config) + { + context.Current[ActivityPropertyNames.Template] = TelemetryJsonExtensions.GetStringOrNull(config, "template"); + context.Current[ActivityPropertyNames.CreatedAbpStudioVersion] = TelemetryJsonExtensions.GetStringOrNull(config, "createdAbpStudioVersion"); + context.Current[ActivityPropertyNames.MultiTenancy] = TelemetryJsonExtensions.GetBooleanOrNull(config, "multiTenancy"); + context.Current[ActivityPropertyNames.UiFramework] = TelemetryJsonExtensions.GetStringOrNull(config, "uiFramework"); + context.Current[ActivityPropertyNames.DatabaseProvider] = TelemetryJsonExtensions.GetStringOrNull(config, "databaseProvider"); + context.Current[ActivityPropertyNames.Theme] = TelemetryJsonExtensions.GetStringOrNull(config, "theme"); + context.Current[ActivityPropertyNames.ThemeStyle] = TelemetryJsonExtensions.GetStringOrNull(config, "themeStyle"); + context.Current[ActivityPropertyNames.HasPublicWebsite] = TelemetryJsonExtensions.GetBooleanOrNull(config, "publicWebsite"); + context.Current[ActivityPropertyNames.IsTiered] = TelemetryJsonExtensions.GetBooleanOrNull(config, "tiered"); + context.Current[ActivityPropertyNames.SocialLogins] = TelemetryJsonExtensions.GetBooleanOrNull(config, "socialLogin"); + context.Current[ActivityPropertyNames.DatabaseManagementSystem] = TelemetryJsonExtensions.GetStringOrNull(config, "databaseManagementSystem"); + context.Current[ActivityPropertyNames.IsSeparateTenantSchema] = TelemetryJsonExtensions.GetBooleanOrNull(config, "separateTenantSchema"); + context.Current[ActivityPropertyNames.MobileFramework] = TelemetryJsonExtensions.GetStringOrNull(config, "mobileFramework"); + context.Current[ActivityPropertyNames.IncludeTests] = TelemetryJsonExtensions.GetBooleanOrNull(config, "includeTests"); + context.Current[ActivityPropertyNames.DynamicLocalization] = TelemetryJsonExtensions.GetBooleanOrNull(config, "dynamicLocalization"); + context.Current[ActivityPropertyNames.KubernetesConfiguration] = TelemetryJsonExtensions.GetBooleanOrNull(config, "kubernetesConfiguration"); + context.Current[ActivityPropertyNames.GrafanaDashboard] = TelemetryJsonExtensions.GetBooleanOrNull(config, "grafanaDashboard"); + } + + private static void AddModuleInfo(ActivityContext context, JsonElement modulesElement) + { + var modules = new List>(); + + foreach (var module in modulesElement.EnumerateObject()) + { + var modulePath = GetModuleFilePath(context.SolutionPath!, module); + if (modulePath.IsNullOrEmpty()) + { + continue; + } + + var moduleJsonFileContent = File.ReadAllText(modulePath); + using var moduleDoc = JsonDocument.Parse(moduleJsonFileContent); + + if (!moduleDoc.RootElement.TryGetProperty("imports", out var imports)) + { + continue; + } + + foreach (var import in imports.EnumerateObject()) + { + modules.Add(new Dictionary + { + { ActivityPropertyNames.ModuleName, import.Name }, + { ActivityPropertyNames.ModuleVersion, TelemetryJsonExtensions.GetStringOrNull(import.Value, "version") }, + { ActivityPropertyNames.ModuleInstallationTime, TelemetryJsonExtensions.GetDateTimeOffsetOrNull(import.Value, "creationTime") } + }); + } + } + + context.Current[ActivityPropertyNames.InstalledModules] = modules; + } + + private static string? GetModuleFilePath(string solutionPath, JsonProperty module) + { + var path = TelemetryJsonExtensions.GetStringOrNull(module.Value, "path"); + if (path.IsNullOrEmpty()) + { + return null; + } + + var fullPath = Path.Combine(Path.GetDirectoryName(solutionPath)!, path); + return File.Exists(fullPath) ? fullPath : null; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/FailedActivityInfo.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/FailedActivityInfo.cs new file mode 100644 index 0000000000..d12565d5d2 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/FailedActivityInfo.cs @@ -0,0 +1,18 @@ +using System; + +namespace Volo.Abp.Internal.Telemetry.Activity.Storage; + +internal class FailedActivityInfo +{ + public DateTimeOffset FirstFailTime { get; set; } + public DateTimeOffset LastFailTime { get; set; } + public int RetryCount { get; set; } + + public bool IsExpired() + { + var now = DateTimeOffset.UtcNow; + + return RetryCount >= TelemetryPeriod.MaxActivityRetryCount || + now - FirstFailTime > TelemetryPeriod.MaxFailedActivityAge; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs new file mode 100644 index 0000000000..70455b2d45 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Helpers; + +namespace Volo.Abp.Internal.Telemetry.Activity.Storage; + +public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDependency +{ + private TelemetryActivityStorageState State { get; } + private readonly static JsonSerializerOptions JsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + public TelemetryActivityStorage() + { + CreateDirectoryIfNotExist(); + + State = LoadState(); + } + + public void SaveActivity(ActivityEvent activityEvent) + { + State.Activities.Add(activityEvent); + + var activityName = activityEvent.Get(ActivityPropertyNames.ActivityName); + + if (activityName == ActivityNameConsts.AbpStudioClose) + { + State.SessionId = null; + } + + if (activityEvent.HasDeviceInfo()) + { + State.LastDeviceInfoAddTime = DateTimeOffset.UtcNow; + } + + if (activityEvent.HasSolutionInfo()) + { + var solutionId = activityEvent.Get(ActivityPropertyNames.SolutionId); + State.Solutions[solutionId] = DateTimeOffset.UtcNow; + } + + if (activityEvent.HasProjectInfo()) + { + var projectId = activityEvent.Get(ActivityPropertyNames.ProjectId); + State.Projects[projectId] = DateTimeOffset.UtcNow; + } + + SaveState(); + } + + public List GetActivities() + { + return State.Activities; + } + + public Guid InitializeOrGetSession() + { + if (State.SessionId.HasValue) + { + return State.SessionId.Value; + } + + State.SessionId = Guid.NewGuid(); + SaveState(); + + return State.SessionId.Value; + } + + public void DeleteActivities(ActivityEvent[] activities) + { + var activityIds = new HashSet(activities.Select(x => x.Get(ActivityPropertyNames.Id))); + + State.Activities.RemoveAll(x => activityIds.Contains(x.Get(ActivityPropertyNames.Id))); + + SaveState(); + } + + public void MarkActivitiesAsFailed(ActivityEvent[] activities) + { + var now = DateTimeOffset.UtcNow; + + foreach (var activity in activities) + { + var activityId = activity.Get(ActivityPropertyNames.Id); + + if (State.FailedActivities.TryGetValue(activityId, out var failedActivityInfo)) + { + failedActivityInfo.RetryCount++; + failedActivityInfo.LastFailTime = now; + + if (!failedActivityInfo.IsExpired()) + { + continue; + } + + State.Activities.RemoveAll(x=> x.Get(ActivityPropertyNames.Id) == activityId); + State.FailedActivities.Remove(activityId); + } + else + { + State.FailedActivities[activityId] = new FailedActivityInfo + { + FirstFailTime = now, + LastFailTime = now, + RetryCount = 1 + }; + } + } + + SaveState(); + } + + public bool ShouldAddDeviceInfo() + { + return State.LastDeviceInfoAddTime is null || + DateTimeOffset.UtcNow - State.LastDeviceInfoAddTime > TelemetryPeriod.InformationSendPeriod; + } + + public bool ShouldAddSolutionInformation(Guid solutionId) + { + return !State.Solutions.TryGetValue(solutionId, out var lastSend) || + DateTimeOffset.UtcNow - lastSend > TelemetryPeriod.InformationSendPeriod; + } + + public bool ShouldAddProjectInfo(Guid projectId) + { + return !State.Projects.TryGetValue(projectId, out var lastSend) || + DateTimeOffset.UtcNow - lastSend > TelemetryPeriod.InformationSendPeriod; + } + + public bool ShouldSendActivities() + { + return State.ActivitySendTime is null || + DateTimeOffset.UtcNow - State.ActivitySendTime > TelemetryPeriod.ActivitySendPeriod; + } + + private void SaveState() + { + try + { + var json = JsonSerializer.Serialize(State, JsonSerializerOptions); + var encryptedJson = Cryptography.Encrypt(json); + File.WriteAllText(TelemetryPaths.ActivityStorage, encryptedJson, Encoding.UTF8); + } + catch + { + // Ignored + } + } + + private static TelemetryActivityStorageState LoadState() + { + try + { + if (!File.Exists(TelemetryPaths.ActivityStorage)) + { + return new TelemetryActivityStorageState(); + } + + var fileContent = MutexExecutor.ReadFileSafely(TelemetryPaths.ActivityStorage); + + if (fileContent.IsNullOrEmpty()) + { + return new TelemetryActivityStorageState(); + } + + var json = Cryptography.Decrypt(fileContent); + + var state = JsonSerializer.Deserialize(json, JsonSerializerOptions)!; + state.Activities = state.Activities.Where(x => x != null).ToList(); + return state; + } + catch + { + return new TelemetryActivityStorageState(); + } + } + + private static void CreateDirectoryIfNotExist() + { + try + { + var storageDirectory = Path.GetDirectoryName(TelemetryPaths.ActivityStorage)!; + + if (!Directory.Exists(storageDirectory)) + { + Directory.CreateDirectory(storageDirectory); + } + } + catch + { + // Ignored + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs new file mode 100644 index 0000000000..f0d1dfd4a5 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorageState.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Internal.Telemetry.Activity.Storage; + +internal class TelemetryActivityStorageState +{ + public DateTimeOffset? ActivitySendTime { get; set; } + public DateTimeOffset? LastDeviceInfoAddTime { get; set; } + public Guid? SessionId { get; set; } + public List Activities { get; set; } = new(); + public Dictionary Solutions { get; set; } = new(); + public Dictionary Projects { get; set; } = new(); + public Dictionary FailedActivities { get; set; } = new(); + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryPeriod.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryPeriod.cs new file mode 100644 index 0000000000..131180a3a4 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryPeriod.cs @@ -0,0 +1,34 @@ +using System; + +namespace Volo.Abp.Internal.Telemetry.Activity.Storage; + +static internal class TelemetryPeriod +{ + private const string TestModeEnvironmentVariable = "ABP_TELEMETRY_TEST_MODE"; + + static TelemetryPeriod() + { + var isTestMode = IsTestModeEnabled(); + + InformationSendPeriod = isTestMode + ? TimeSpan.FromSeconds(15) + : TimeSpan.FromDays(7); + + ActivitySendPeriod = isTestMode + ? TimeSpan.FromSeconds(5) + : TimeSpan.FromDays(1); + } + + public static TimeSpan ActivitySendPeriod { get; } + public static TimeSpan InformationSendPeriod { get; } + + public static int MaxActivityRetryCount { get; set; } = 3; + public static TimeSpan MaxFailedActivityAge { get; set; } = TimeSpan.FromDays(30); + + private static bool IsTestModeEnabled() + { + var testModeVariable = + Environment.GetEnvironmentVariable(TestModeEnvironmentVariable, EnvironmentVariableTarget.User); + return bool.TryParse(testModeVariable, out var isTestMode) && isTestMode; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs new file mode 100644 index 0000000000..6f8b5c2a10 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/TelemetryJsonExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.Json; + +namespace Volo.Abp.Internal.Telemetry.Activity; + +static internal class TelemetryJsonExtensions +{ + static internal string? GetStringOrNull(JsonElement element, string propertyName) + { + return element.TryGetProperty(propertyName, out var property) + ? property.GetString() ?? null + : null; + } + + static internal bool? GetBooleanOrNull(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var property) && bool.TryParse(property.GetString(), out var boolValue)) + { + return boolValue; + } + + return null; + } + + static internal DateTimeOffset? GetDateTimeOffsetOrNull(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var date) && DateTimeOffset.TryParse(date.GetString(), out var dateTimeValue)) + { + return dateTimeValue; + } + + return null; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/AbpPlatformUrls.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/AbpPlatformUrls.cs new file mode 100644 index 0000000000..61f2907e8f --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/AbpPlatformUrls.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.Internal.Telemetry.Constants; + +public static class AbpPlatformUrls +{ +#if DEBUG + public const string AbpTelemetryApiUrl = "https://localhost:44393/"; +#else + public const string AbpTelemetryApiUrl = "https://telemetry.abp.io/"; +#endif + + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs new file mode 100644 index 0000000000..64b22ef78f --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs @@ -0,0 +1,76 @@ +namespace Volo.Abp.Internal.Telemetry.Constants; + +public static class ActivityNameConsts +{ + public const string AbpStudioOpen = "AbpStudio.Open"; + public const string AbpStudioOpenFirstTimeForDevice = "AbpStudio.Open.FirstTimeForDevice"; + public const string AbpStudioOpenFirstTimeForUser = "AbpStudio.Open.FirstTimeForUser"; + public const string AbpStudioClose = "AbpStudio.Close"; + public const string AbpStudioCloseWithoutLogin = "AbpStudio.Close.WithoutLogin"; + public const string AbpStudioLogin = "AbpStudio.Login"; + public const string AbpStudioCampaignClick = "AbpStudio.Campaing.Click"; + public const string AbpStudioCommunityPostClick = "AbpStudio.CommunityPost.Click"; + public const string AbpStudioSolutionInitExisting = "AbpStudio.Solution.InitExisting"; + public const string AbpStudioSolutionNew = "AbpStudio.Solution.New"; + public const string AbpStudioSolutionNewMicroservice = "AbpStudio.Solution.New.Microservice"; + public const string AbpStudioSolutionAddModule = "AbpStudio.Solution.Add.Module"; + public const string AbpStudioSolutionAddModuleEmpty = "AbpStudio.Solution.Add.Module.Empty"; + public const string AbpStudioSolutionAddModuleDdd = "AbpStudio.Solution.Add.Module.Ddd"; + public const string AbpStudioSolutionAddModuleStandard = "AbpStudio.Solution.Add.Module.Standard"; + public const string AbpStudioSolutionAddMicroservice = "AbpStudio.Solution.Add.Microservice"; + public const string AbpStudioSolutionAddGateway = "AbpStudio.Solution.Add.Gateway"; + public const string AbpStudioSolutionAddWeb = "AbpStudio.Solution.Add.Web"; + public const string AbpStudioSolutionAddPackage = "AbpStudio.Solution.Add.Package"; + public const string AbpStudioSolutionAddPackageHttpApiLayer = "AbpStudio.Solution.Add.Package.HttpApiLayer"; + public const string AbpStudioAbpCliInstallLibs = "AbpStudio.AbpCli.InstallLibs"; + public const string AbpStudioAbpCliUpgradeAbp = "AbpStudio.AbpCli.UpgradeAbp"; + public const string AbpStudioAbpCliSwitchToStable = "AbpStudio.AbpCli.SwitchToStable"; + public const string AbpStudioAbpCliSwitchToPreview = "AbpStudio.AbpCli.SwitchToPreview"; + public const string AbpStudioAbpCliSwitchToNightly = "AbpStudio.AbpCli.SwitchToNightly"; + public const string AbpStudioAbpCliClean = "AbpStudio.AbpCli.Clean"; + public const string AbpStudioDotnetCliBuild = "AbpStudio.DotnetCli.Build"; + public const string AbpStudioDotnetCliGraphBuild = "AbpStudio.DotnetCli.GraphBuild"; + public const string AbpStudioDotnetCliClean = "AbpStudio.DotnetCli.Clean"; + public const string AbpStudioDotnetCliRestore = "AbpStudio.DotnetCli.Restore"; + public const string AbpStudioSolutionRunnerRunApp = "AbpStudio.SolutionRunner.RunApp"; + public const string AbpStudioSolutionRunnerAddCsharpApp = "AbpStudio.SolutionRunner.Add.CsharpApp"; + public const string AbpStudioSolutionRunnerAddCliApp = "AbpStudio.SolutionRunner.Add.CliApp"; + public const string AbpStudioSolutionRunnerAddProfile = "AbpStudio.SolutionRunner.Add.Profile"; + public const string AbpStudioSolutionRunnerAppManageMetadata = "AbpStudio.SolutionRunner.App.Manage.Metadata"; + public const string AbpStudioSolutionRunnerAppManageSecrets = "AbpStudio.SolutionRunner.App.Manage.Secrets"; + public const string AbpStudioSolutionOpen = "AbpStudio.Solution.Open"; + public const string AbpStudioMonitoringBrowse = "AbpStudio.Monitoring.Browse"; + public const string AbpStudioMonitoringHttpRequests = "AbpStudio.Monitoring.HttpRequests"; + public const string AbpStudioMonitoringHttpRequestsDetail = "AbpStudio.Monitoring.HttpRequests.Detail"; + public const string AbpStudioMonitoringEvents = "AbpStudio.Monitoring.Events"; + public const string AbpStudioMonitoringEventsDetail = "AbpStudio.Monitoring.Events.Detail"; + public const string AbpStudioMonitoringExceptions = "AbpStudio.Monitoring.Exceptions"; + public const string AbpStudioMonitoringExceptionsDetail = "AbpStudio.Monitoring.Exceptions.Detail"; + public const string AbpStudioMonitoringLogs = "AbpStudio.Monitoring.Logs"; + public const string AbpStudioKubernetesAddProfile = "AbpStudio.Kubernetes.Add.Profile"; + public const string AbpStudioKubernetesConnect = "AbpStudio.Kubernetes.Connect"; + public const string AbpStudioKubernetesIntercept = "AbpStudio.Kubernetes.Intercept"; + public const string AbpStudioKubernetesHelmCommandsInstall = "AbpStudio.Kubernetes.Helm.Commands.Install"; + public const string AbpStudioKubernetesHelmCommandsBuildImages = "AbpStudio.Kubernetes.Helm.Commands.BuildImages"; + public const string AbpStudioKubernetesHelmChartsRefreshSubCharts = "AbpStudio.Kubernetes.Helm.Charts.RefreshSubCharts"; + public const string AbpStudioKubernetesHelmChartsManageMetadata = "AbpStudio.Kubernetes.Helm.Charts.Manage.Metadata"; + public const string AbpStudioLogsShow = "AbpStudio.Logs.Show"; + public const string AbpStudioSuiteOpen = "AbpStudio.Suite.Open"; + public const string AbpStudioGlobalSecretsManage = "AbpStudio.GlobalSecrets.Manage"; + public const string AbpStudioGlobalMetadataManage = "AbpStudio.GlobalMetadata.Manage"; + public const string AbpCliCommandsNewSolution = "AbpCli.Comands.NewSolution"; + public const string AbpCliCommandsNewModule = "AbpCli.Comands.NewModule"; + public const string AbpCliCommandsNewPackage = "AbpCli.Comands.NewPackage"; + public const string AbpCliCommandsUpdate = "AbpCli.Comands.Update"; + public const string AbpCliCommandsClean = "AbpCli.Comands.Clean"; + public const string AbpCliCommandsAddPackage = "AbpCli.Comands.AddPackage"; + public const string AbpCliCommandsAddPackageRef = "AbpCli.Comands.AddPackageRef"; + public const string AbpCliCommandsInstallModule = "AbpCli.Comands.InstallModule"; + public const string AbpCliCommandsInstallLocalModule = "AbpCli.Comands.InstallLocalModule"; + public const string AbpCliCommandsListModules = "AbpCli.Comands.ListModules"; + public const string AbpCliRun = "AbpCli.Run"; + public const string AbpCliExit = "AbpCli.Exit"; + public const string ApplicationRun = "Application.Run"; + public const string AbpStudioBrowserOpen = "AbpStudio.Browser.Open"; + public const string Error = "Error"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs new file mode 100644 index 0000000000..68f7e95f16 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs @@ -0,0 +1,77 @@ +namespace Volo.Abp.Internal.Telemetry.Constants; + +public static class ActivityPropertyNames +{ + public const string SessionId = "SessionId"; + public const string ActivityName = "ActivityName"; + public const string Error = "Error"; + public const string ErrorDetail = "ErrorDetail"; + public const string Id = "Id"; + public const string UserId = "UserId"; + public const string OrganizationId = "OrganizationId"; + public const string IpAddress = "IpAddress"; + public const string IsFirstSession = "IsFirstSession"; + public const string DeviceId = "DeviceId"; + public const string DeviceLanguage = "DeviceLanguage"; + public const string OperatingSystem = "OperatingSystem"; + public const string CountryIsoCode = "CountryIsoCode"; + public const string InstalledSoftwares = "InstalledSoftwares"; + public const string ControllerCount = "ControllerCount"; + public const string EntityCount = "EntityCount"; + public const string ProjectCount = "ProjectCount"; + public const string ModuleCount = "ModuleCount"; + public const string PermissionCount = "PermissionCount"; + public const string AppServiceCount = "AppServiceCount"; + public const string ProjectType = "ProjectType"; + public const string ProjectId = "ProjectId"; + public const string SolutionId = "SolutionId"; + public const string Template = "Template"; + public const string CreatedAbpStudioVersion = "CreatedAbpStudioVersion"; + public const string IsTiered = "IsTiered"; + public const string UiFramework = "UiFramework"; + public const string DatabaseProvider = "DatabaseProvider"; + public const string DatabaseManagementSystem = "DatabaseManagementSystem"; + public const string IsSeparateTenantSchema = "IsSeparateTenantSchema"; + public const string Theme = "Theme"; + public const string ThemeStyle = "ThemeStyle"; + public const string MobileFramework = "MobileFramework"; + public const string HasPublicWebsite = "HasPublicWebsite"; + public const string IncludeTests = "IncludeTests"; + public const string MultiTenancy = "MultiTenancy"; + public const string DynamicLocalization = "DynamicLocalization"; + public const string KubernetesConfiguration = "KubernetesConfiguration"; + public const string GrafanaDashboard = "GrafanaDashboard"; + public const string SocialLogins = "SocialLogins"; + public const string InstalledModules = "InstalledModules"; + public const string SolutionPath = "SolutionPath"; + public const string LicenseType = "LicenseType"; + public const string SessionType = "SessionType"; + public const string HasError = "HasError"; + public const string ActivityDuration = "ActivityDuration"; + public const string ActivityDetails = "ActivityDetails"; + public const string Time = "Time"; + public const string SoftwareName = "Name"; + public const string SoftwareVersion = "Version"; + public const string SoftwareUiTheme = "UiTheme"; + public const string SoftwareType = "SoftwareType"; + public const string WebFramework = "WebFramework"; + public const string Dbms = "Dbms"; + public const string UiTheme = "UiTheme"; + public const string UiThemeStyle = "UiThemeStyle"; + public const string MobileApp = "MobileApp"; + public const string SampleCrudPage = "SampleCrudPage"; + public const string FirstAbpVersion = "FirstAbpVersion"; + public const string FirstDotnetVersion = "FirstDotnetVersion"; + public const string CreationTool = "CreationTool"; + public const string ModuleName = "ModuleName"; + public const string ModuleVersion = "ModuleVersion"; + public const string ModuleInstallationTime = "ModuleInstallationTime"; + public const string ExtraProperties = "ExtraProperties"; + public const string HasSolutionInfo = "HasSolutionInfo"; + public const string HasDeviceInfo = "HasDeviceInfo"; + public const string HasProjectInfo = "HasProjectInfo"; + public const string ErrorMessage = "ErrorMessage"; + public const string FailingActivity = "FailingActivity"; + public const string OperatingSystemArchitecture = "OperatingSystemArchitecture"; + public const string AdditionalProperties = "AdditionalProperties"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs new file mode 100644 index 0000000000..e3993886a0 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs @@ -0,0 +1,260 @@ +using System; + +namespace Volo.Abp.Internal.Telemetry.Constants; + +static internal class DeviceManager +{ + public static string GetUniquePhysicalKey(bool shouldHash) + { + char platformId = '?'; + char osArchitecture = '?'; + string operatingSystem = "?"; + + try + { + string osPrefix; + string uniqueKey; + + platformId = GetPlatformIdOrDefault(); + osArchitecture = GetOsArchitectureOrDefault(); + + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform + .Windows)) + { + operatingSystem = "Windows"; + uniqueKey = GetUniqueKeyForWindows(); + osPrefix = "W"; + } + else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices + .OSPlatform.Linux)) + { + operatingSystem = "Linux"; + uniqueKey = GetHarddiskSerialForLinux(); + osPrefix = "L"; + } + else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices + .OSPlatform.OSX)) //MAC + { + operatingSystem = "OSX"; + uniqueKey = GetHarddiskSerialForOsX(); + osPrefix = "O"; + } + else + { + operatingSystem = "Other"; + uniqueKey = GetNetworkAdapterSerial(); + osPrefix = "X"; + } + + if (shouldHash) + { + uniqueKey = ConvertToMd5(uniqueKey).ToUpperInvariant(); + } + + return osPrefix + platformId + osArchitecture + "-" + uniqueKey; + } + catch + { + return Guid.NewGuid().ToString(); + } + } + + private static string GetNetworkAdapterSerial() + { + string macAddress = string.Empty; + + var networkInterfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); + foreach (var networkInterface in networkInterfaces) + { + if (networkInterface.NetworkInterfaceType == System.Net.NetworkInformation.NetworkInterfaceType.Loopback) + { + continue; + } + + var physicalAddress = networkInterface.GetPhysicalAddress().ToString(); + if (string.IsNullOrEmpty(physicalAddress)) + { + continue; + } + + macAddress = physicalAddress; + break; + } + + return macAddress!; + } + + private static char GetPlatformIdOrDefault(char defaultValue = '*') + { + try + { + return ((int)System.Environment.OSVersion.Platform).ToString()[0]; + } + catch + { + return defaultValue; + } + } + + private static string ConvertToMd5(string text) + { + using (var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider()) + { + return EncodeBase64(md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(text))); + } + } + + private static string EncodeBase64(byte[] ba) + { + var hex = new System.Text.StringBuilder(ba.Length * 2); + + foreach (var b in ba) + { + hex.AppendFormat("{0:x2}", b); + } + + return hex.ToString(); + } + + + private static char GetOsArchitectureOrDefault(char defaultValue = '*') + { + try + { + return ((int)System.Runtime.InteropServices.RuntimeInformation.OSArchitecture).ToString()[0]; + } + catch + { + return defaultValue; + } + } + + private static string GetUniqueKeyForWindows() + { + try + { + return GetProcessorIdForWindows(); + } + catch + { + } + + return GetWindowsMachineUniqueId(); + } + + private static string GetProcessorIdForWindows() + { + using (var managementObjectSearcher = + new System.Management.ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor")) + { + using (var searcherObj = managementObjectSearcher.Get()) + { + if (searcherObj.Count == 0) + { + throw new System.Exception("No unique computer ID found for this computer!"); + } + + var managementObjectEnumerator = searcherObj.GetEnumerator(); + managementObjectEnumerator.MoveNext(); + return managementObjectEnumerator.Current.GetPropertyValue("ProcessorId").ToString()!; + } + } + } + + private static string GetWindowsMachineUniqueId() + { + return RunCommandAndGetOutput("powershell (Get-CimInstance -Class Win32_ComputerSystemProduct).UUID"); + } + + + private static string GetHarddiskSerialForLinux() + { + return RunCommandAndGetOutput( + "udevadm info --query=all --name=/dev/sda | grep ID_SERIAL_SHORT | tr -d \"ID_SERIAL_SHORT=:\""); + } + + private static string GetHarddiskSerialForOsX() + { + var command = + "ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ { split($0, line, \"\\\"\"); printf(\"%s\\n\", line[4]); }'"; + + command = System.Text.RegularExpressions.Regex.Replace(command, @"(\\*)" + "\"", @"$1$1\" + "\""); + + return RunCommandAndGetOutput(command); + } + + private static string RunCommandAndGetOutput(string command) + { + var output = ""; + + using (var process = new System.Diagnostics.Process()) + { + process.StartInfo = new System.Diagnostics.ProcessStartInfo(GetFileName()) + { + Arguments = GetArguments(command), + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + process.Start(); + process?.WaitForExit(); + + using (var stdOut = process!.StandardOutput) + { + using (var stdErr = process.StandardError) + { + output = stdOut.ReadToEnd(); + output += stdErr.ReadToEnd(); + } + } + } + + return output.Trim(); + } + + private static string GetFileName() + { + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.OSX) || + System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform + .Linux)) + { + string[] fileNames = { "/bin/bash", "/usr/bin/bash", "/bin/sh", "/usr/bin/sh" }; + foreach (var fileName in fileNames) + { + try + { + if (System.IO.File.Exists(fileName)) + { + return fileName; + } + } + catch + { + //ignore + } + } + + return "/bin/bash"; + } + + //Windows default. + return "cmd.exe"; + } + + private static string GetArguments(string command) + { + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.OSX) || + System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform + .Linux)) + { + return "-c \"" + command + "\""; + } + + //Windows default. + return "/C \"" + command + "\""; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/AbpTool.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/AbpTool.cs new file mode 100644 index 0000000000..f2b20e3a03 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/AbpTool.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Internal.Telemetry.Constants.Enums; + +public enum AbpTool : byte +{ + Unknown = 0, + StudioUI = 1, + StudioCli = 2, + OldCli = 3 +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/OperationSystem.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/OperationSystem.cs new file mode 100644 index 0000000000..312eccec69 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/OperationSystem.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Internal.Telemetry.Constants.Enums; + +public enum OperationSystem +{ + Unknown = 0, + Windows = 1, + MacOS = 2, + Linux = 3, +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/SessionType.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/SessionType.cs new file mode 100644 index 0000000000..568fdb4f4d --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/SessionType.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Internal.Telemetry.Constants.Enums; + +public enum SessionType +{ + Unknown = 0, + AbpStudio = 1, + AbpCli = 2, + ApplicationRuntime = 3 +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/SoftwareType.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/SoftwareType.cs new file mode 100644 index 0000000000..0e334513da --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/Enums/SoftwareType.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Internal.Telemetry.Constants.Enums; + +public enum SoftwareType : byte +{ + Others = 0, + AbpStudio = 1, + DotnetSdk = 2, + OperatingSystem = 3, + Ide = 4, + Browser = 5 +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/TelemetryConsts.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/TelemetryConsts.cs new file mode 100644 index 0000000000..c2797efd4a --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/TelemetryConsts.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Internal.Telemetry.Constants; + +public class TelemetryConsts +{ + public const string VoloNameSpaceFilter = "Volo."; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/TelemetryPaths.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/TelemetryPaths.cs new file mode 100644 index 0000000000..07bc408043 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/TelemetryPaths.cs @@ -0,0 +1,13 @@ +using System; +using System.IO; + +namespace Volo.Abp.Internal.Telemetry.Constants; + +public static class TelemetryPaths +{ + public static string AccessToken => Path.Combine(AbpRootPath, "cli", "access-token.bin"); + public static string ComputerId => Path.Combine(AbpRootPath, "cli", "computer-id.bin"); + public static string ActivityStorage => Path.Combine(AbpRootPath , "telemetry", "activity-storage.bin"); + public static string Studio => Path.Combine(AbpRootPath, "studio"); + private readonly static string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp"); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/ISoftwareDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/ISoftwareDetector.cs new file mode 100644 index 0000000000..8942bf359d --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/ISoftwareDetector.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; + +internal interface ISoftwareDetector +{ + string Name { get; } + Task DetectAsync(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/ISoftwareInfoProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/ISoftwareInfoProvider.cs new file mode 100644 index 0000000000..ae5d75a099 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/ISoftwareInfoProvider.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; + +internal interface ISoftwareInfoProvider +{ + Task> GetSoftwareInfoAsync(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/SoftwareInfo.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/SoftwareInfo.cs new file mode 100644 index 0000000000..bbfa3fe414 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Contracts/SoftwareInfo.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Internal.Telemetry.Constants.Enums; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; + +internal class SoftwareInfo(string name, string? version, string? uiTheme, SoftwareType softwareType) +{ + public string Name { get; set; } = name; + public string? Version { get; set; } = version; + public string? UiTheme { get; set; } = uiTheme; + public SoftwareType SoftwareType { get; set; } = softwareType; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Core/SoftwareDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Core/SoftwareDetector.cs new file mode 100644 index 0000000000..6b02c22c7c --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Core/SoftwareDetector.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +[ExposeServices(typeof(ISoftwareDetector))] +abstract internal class SoftwareDetector: ISoftwareDetector , ISingletonDependency +{ + public abstract string Name { get; } + public abstract Task DetectAsync(); + + protected virtual async Task ExecuteCommandAsync(string command, string? arg) + { + var outputBuilder = new StringBuilder(); + + var processStartInfo = new ProcessStartInfo + { + FileName = command, + Arguments = arg ?? "", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = new Process(); + process.StartInfo = processStartInfo; + process.EnableRaisingEvents = true; + + var tcs = new TaskCompletionSource(); + + process.OutputDataReceived += (sender, e) => + { + if (e.Data != null) + { + outputBuilder.AppendLine(e.Data); + } + }; + + process.ErrorDataReceived += (sender, e) => + { + if (e.Data != null) + { + outputBuilder.AppendLine(e.Data); + } + }; + + process.Exited += (sender, e) => + { + tcs.TrySetResult(true); + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await tcs.Task; + + var output = outputBuilder.ToString().Trim(); + return string.IsNullOrWhiteSpace(output) ? null : output; + } + + protected string? GetFileVersion(string filePath) + { + try + { + var versionInfo = FileVersionInfo.GetVersionInfo(filePath); + return versionInfo.FileVersion; + } + catch + { + return string.Empty; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs new file mode 100644 index 0000000000..3d35c7192a --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs @@ -0,0 +1,76 @@ +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class AbpStudioDetector : SoftwareDetector +{ + public override string Name => "Abp Studio"; + private const string AbpStudioVersionExtensionName = "Volo.Abp.Studio.Extensions.StandardSolutionTemplates"; + + public override Task DetectAsync() + { + try + { + var uiTheme = GetAbpStudioUiTheme(); + var version = GetAbpStudioVersion(); + + return Task.FromResult(new SoftwareInfo(Name, version, uiTheme, SoftwareType.AbpStudio)); + } + catch + { + return Task.FromResult(null); + } + } + + private string? GetAbpStudioUiTheme() + { + var ideStateJsonPath = Path.Combine( + TelemetryPaths.Studio, + "ui", + "ide-state.json" + ); + if (!File.Exists(ideStateJsonPath)) + { + return null; + } + using var fs = new FileStream(ideStateJsonPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var doc = JsonDocument.Parse(fs); + + return doc.RootElement.TryGetProperty("theme", out var themeElement) ? themeElement.GetString() : null; + } + + private string? GetAbpStudioVersion() + { + var extensionsFilePath = Path.Combine(TelemetryPaths.Studio, "extensions.json"); + + if (!File.Exists(extensionsFilePath)) + { + return null; + } + + using var fs = new FileStream(extensionsFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var doc = JsonDocument.Parse(fs); + + if (doc.RootElement.TryGetProperty("Extensions", out var extensionsElement) && + extensionsElement.ValueKind == JsonValueKind.Array) + { + foreach (var extension in extensionsElement.EnumerateArray()) + { + if (extension.TryGetProperty("name", out var nameProp) && + nameProp.GetString() == AbpStudioVersionExtensionName && + extension.TryGetProperty("version", out var versionProp)) + { + return versionProp.GetString(); + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/ChromeDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/ChromeDetector.cs new file mode 100644 index 0000000000..fa7f8cff74 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/ChromeDetector.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class ChromeDetector : SoftwareDetector +{ + public override string Name => "Chrome"; + + public async override Task DetectAsync() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var chromePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google", "Chrome", "Application", "chrome.exe"); + if (File.Exists(chromePath)) + { + return new SoftwareInfo(Name, GetFileVersion(chromePath), null, SoftwareType.Browser); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; + if (File.Exists(chromePath)) + { + var version = await ExecuteCommandAsync(chromePath, "--version"); + return new SoftwareInfo(Name, version, null, SoftwareType.Browser); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var chromePath = "/usr/bin/google-chrome"; + if (File.Exists(chromePath)) + { + var version = await ExecuteCommandAsync(chromePath, "--version"); + return new SoftwareInfo(Name, version, null, SoftwareType.Browser); + } + } + + return null; + } + + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/DotnetSdkDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/DotnetSdkDetector.cs new file mode 100644 index 0000000000..4f03eac248 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/DotnetSdkDetector.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class DotnetSdkDetector : SoftwareDetector +{ + public override string Name => "DotnetSdk"; + + public async override Task DetectAsync() + { + return new SoftwareInfo(Name, Environment.Version.ToString(), null, SoftwareType.DotnetSdk); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/FireFoxDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/FireFoxDetector.cs new file mode 100644 index 0000000000..261e36c7e5 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/FireFoxDetector.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class FireFoxDetector : SoftwareDetector +{ + public override string Name => "Firefox"; + public async override Task DetectAsync() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var firefoxPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Mozilla Firefox", "firefox.exe"); + if (File.Exists(firefoxPath)) + { + return new SoftwareInfo(Name, GetFileVersion(firefoxPath), null, SoftwareType.Browser); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var firefoxPath = "/Applications/Firefox.app/Contents/MacOS/firefox"; + if (File.Exists(firefoxPath)) + { + var version = await ExecuteCommandAsync(firefoxPath, "--version"); + return new SoftwareInfo(Name, version, null, SoftwareType.Browser); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var firefoxPath = "/usr/bin/firefox"; + if (File.Exists(firefoxPath)) + { + var version = await ExecuteCommandAsync(firefoxPath, "--version"); + return new SoftwareInfo(Name, version, null, SoftwareType.Browser); + } + } + + return null; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/MsEdgeDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/MsEdgeDetector.cs new file mode 100644 index 0000000000..e1e67c516e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/MsEdgeDetector.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class MsEdgeDetector : SoftwareDetector +{ + public override string Name => "MsEdge"; + public async override Task DetectAsync() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var firefoxPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), + "Microsoft", "Edge", "Application", "msedge.exe"); + + if (File.Exists(firefoxPath)) + { + return new SoftwareInfo(Name, GetFileVersion(firefoxPath), null, SoftwareType.Browser); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; + if (File.Exists(edgePath)) + { + var version = await ExecuteCommandAsync(edgePath, "--version"); + return new SoftwareInfo(Name, version, null, SoftwareType.Browser); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var edgePath = "/usr/bin/microsoft-edge"; + if (File.Exists(edgePath)) + { + var version = await ExecuteCommandAsync(edgePath, "--version"); + return new SoftwareInfo(Name, version, null, SoftwareType.Browser); + } + } + + return null; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/NodeJsDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/NodeJsDetector.cs new file mode 100644 index 0000000000..85a4e05cce --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/NodeJsDetector.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal class NodeJsDetector : SoftwareDetector +{ + public override string Name => "Node.js"; + + public async override Task DetectAsync() + { + try + { + var output = await ExecuteCommandAsync("node", "-v"); + + if (output.IsNullOrWhiteSpace()) + { + return null; + } + + var version = output.Trim().TrimStart('v'); + + return new SoftwareInfo(Name, version, uiTheme: null, SoftwareType.Others); + } + catch + { + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/OperatingSystemDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/OperatingSystemDetector.cs new file mode 100644 index 0000000000..9dfc77f8f2 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/OperatingSystemDetector.cs @@ -0,0 +1,77 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class OperatingSystemDetector : SoftwareDetector +{ + public override string Name => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "macOS" : "Linux"; + + public async override Task DetectAsync() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return new SoftwareInfo(Name, Environment.OSVersion.Version.ToString(), null, SoftwareType.OperatingSystem); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var version = await ExecuteCommandAsync("sw_vers", "-productVersion"); + return new SoftwareInfo(Name, version, GetMacUiTheme(), SoftwareType.OperatingSystem); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var version = await ExecuteCommandAsync("lsb_release", "-ds") ?? await ExecuteCommandAsync("uname", "-r"); + return new SoftwareInfo(Name, version, await GetLinuxUiTheme(), SoftwareType.OperatingSystem); + } + + return null; + } + + private async Task GetLinuxUiTheme() + { + var output = await ExecuteCommandAsync("gsettings", "get org.gnome.desktop.interface gtk-theme"); + + if (!output.IsNullOrWhiteSpace() && output.ToLowerInvariant().Contains("dark")) + { + return "Dark"; + } + + return "Light"; + } + + + private string? GetMacUiTheme() + { + try + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = "defaults", + Arguments = "read -g AppleInterfaceStyle", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + process.Start(); + var output = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + + return output == "Dark" ? "Dark" : "Light"; + } + catch + { + return "Light"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/RiderDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/RiderDetector.cs new file mode 100644 index 0000000000..a36dd8a0ab --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/RiderDetector.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Xml.Linq; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class RiderDetector : SoftwareDetector +{ + public override string Name => "Rider"; + + public override Task DetectAsync() + { + try + { + string baseConfigDir; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + baseConfigDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "JetBrains"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + baseConfigDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Personal), + "Library", "Application Support", "JetBrains"); + } + else + { + baseConfigDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Personal), + ".config", "JetBrains"); + } + + if (!Directory.Exists(baseConfigDir)) + { + return Task.FromResult(null); + } + + var riderDirs = Directory + .GetDirectories(baseConfigDir, "Rider*") + .Select(dir => + { + var name = Path.GetFileName(dir); + var verStr = name.Substring("Rider".Length); + return Version.TryParse(verStr, out var v) + ? (Path: dir, Version: v) + : (Path: null, Version: null); + }) + .Where(x => x.Path != null) + .ToList(); + + if (!riderDirs.Any()) + { + return Task.FromResult(null); + } + + var latest = riderDirs + .OrderByDescending(x => x.Version) + .First(); + + var theme = string.Empty; + var colorsFile = Path.Combine(latest.Path!, "options", "colors.scheme.xml"); + if (File.Exists(colorsFile)) + { + try + { + var doc = XDocument.Load(colorsFile); + var schemeEl = doc + .Descendants("global_color_scheme") + .FirstOrDefault(); + var schemeName = schemeEl?.Attribute("name")?.Value; + if (!schemeName.IsNullOrEmpty()) + { + theme = schemeName.IndexOf("dark", StringComparison.OrdinalIgnoreCase) >= 0 + ? "Dark" + : "Light"; + } + } + catch + { + //ignored + } + } + + return Task.FromResult(new SoftwareInfo(Name, latest.Version?.ToString(), theme, + SoftwareType.Ide)); + } + catch (Exception e) + { + return Task.FromResult(null); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs new file mode 100644 index 0000000000..72fda0f500 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs @@ -0,0 +1,132 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class VisualStudioCodeDetector : SoftwareDetector +{ + public override string Name => "Visual Studio Code"; + + public async override Task DetectAsync() + { + string? installDir = null; + string? settingsPath = null; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var progFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var candidates = new[] + { + Path.Combine(localAppData, "Programs", "Microsoft VS Code"), + Path.Combine(progFiles, "Microsoft VS Code") + }; + installDir = candidates.FirstOrDefault(Directory.Exists); + + settingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "Code", "User", "globalStorage" ,"storage.json" + ); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var app = "/Applications/Visual Studio Code.app"; + if (Directory.Exists(app)) + { + installDir = app; + } + + settingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Personal), + "Library", "Application Support", "Code", "User", "globalStorage", "storage.json" + ); + } + else + { + var candidate = "/usr/share/code"; + if (Directory.Exists(candidate)) + { + installDir = candidate; + } + + settingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Personal), + ".config", "Code", "User", "globalStorage", "storage.json" + ); + } + + if (installDir == null) + { + return null; + } + + + Version? version = null; + var productJson = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? Path.Combine(installDir, "Contents", "Resources", "app", "product.json") + : Path.Combine(installDir, "resources", "app", "product.json"); + + if (File.Exists(productJson)) + { + try + { + using var jsonDoc = JsonDocument.Parse(File.ReadAllText(productJson)); + var root = jsonDoc.RootElement; + if (root.TryGetProperty("version", out var versionProp)) + { + var versionStr = versionProp.GetString(); + if (Version.TryParse(versionStr, out var v)) + { + version = v; + } + } + } + catch + { + + } + } + + if (version == null) + { + return null; + } + + var theme = "Unknown"; + + if (File.Exists(settingsPath)) + { + try + { + using var json = JsonDocument.Parse( File.ReadAllText(settingsPath)); + var root = json.RootElement; + if (root.TryGetProperty("theme", out var themeProp)) + { + var themeName = themeProp.GetString() ?? ""; + if (themeName.IndexOf("dark", StringComparison.OrdinalIgnoreCase) >= 0) + { + theme = "Dark"; + } + else if (themeName.IndexOf("light", StringComparison.OrdinalIgnoreCase) >= 0) + { + theme = "Light"; + } + } + } + catch + { + // ignored + } + } + + return new SoftwareInfo(Name, version?.ToString(), theme, SoftwareType.Ide); + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioDetector.cs new file mode 100644 index 0000000000..fbbb596157 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioDetector.cs @@ -0,0 +1,106 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Volo.Abp.Internal.Telemetry.Constants.Enums; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Core; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Detectors; + +internal sealed class VisualStudioDetector : SoftwareDetector +{ + public override string Name => "Visual Studio"; + + public override Task DetectAsync() + { + var version = GetVisualStudioVersionViaVsWhere(); + var theme = GetVisualStudioTheme(); + + if (version == null) + { + return Task.FromResult(null); + } + + return Task.FromResult(new SoftwareInfo( + name: Name, + version: version, + uiTheme: theme, + softwareType: SoftwareType.Ide)); + } + + private string? GetVisualStudioVersionViaVsWhere() + { + var vswherePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), + "Microsoft Visual Studio", + "Installer", + "vswhere.exe"); + + if (!File.Exists(vswherePath)) + { + return null; + } + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = vswherePath, + Arguments = "-latest -property catalog_productDisplayVersion", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + process.Start(); + var output = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + + return string.IsNullOrWhiteSpace(output) ? null : output; + } + + private string? GetVisualStudioTheme() + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + var vsSettingsDir = Path.Combine(localAppData, "Microsoft", "VisualStudio"); + + if (!Directory.Exists(vsSettingsDir)) + { + return null; + } + + var settingsPath = Directory.GetFiles(vsSettingsDir, "CurrentSettings*.vssettings", SearchOption.AllDirectories) + .OrderByDescending(File.GetLastWriteTime) + .FirstOrDefault(); + + if (string.IsNullOrEmpty(settingsPath)) + { + return null; + } + + try + { + var doc = XDocument.Load(settingsPath); + + var themeId = doc.Descendants("Theme") + .FirstOrDefault()?.Attribute("Id")?.Value; + + return themeId?.ToUpperInvariant() switch + { + "{1DED0138-47CE-435E-84EF-9EC1F439B749}" => "Dark", + "{DE3DBBCD-F642-433C-8353-8F1DF4370ABA}" => "Light", + "{2DED0138-47CE-435E-84EF-9EC1F439B749}" => "Blue", + _ => "Unknown" + }; + } + catch + { + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Providers/SoftwareInfoProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Providers/SoftwareInfoProvider.cs new file mode 100644 index 0000000000..3a56ed420e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Providers/SoftwareInfoProvider.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.EnvironmentInspection.Contracts; + +namespace Volo.Abp.Internal.Telemetry.EnvironmentInspection.Providers; + +internal class SoftwareInfoProvider : ISoftwareInfoProvider , ISingletonDependency +{ + private readonly IEnumerable _softwareDetectors; + + public SoftwareInfoProvider(IEnumerable softwareDetectors) + { + _softwareDetectors = softwareDetectors; + } + + public async Task> GetSoftwareInfoAsync() + { + var result = new List(); + + foreach (var softwareDetector in _softwareDetectors) + { + try + { + var softwareInfo = await softwareDetector.DetectAsync(); + if (softwareInfo is not null) + { + result.Add(softwareInfo); + } + } + catch + { + //ignored + } + } + return result; + } + + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs new file mode 100644 index 0000000000..5f505324b0 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.Json; + +namespace Volo.Abp.Internal.Telemetry.Helpers; + +static internal class AbpProjectMetadataReader +{ + private const string AbpPackageSearchPattern = "*.abppkg"; + private const string AbpSolutionSearchPattern = "*.abpsln"; + private const int MaxDepth = 5; + public static AbpProjectMetaData? ReadProjectMetadata(Assembly assembly) + { + var assemblyPath = assembly.Location; + try + { + var projectDirectory = Path.GetDirectoryName(assemblyPath); + if (projectDirectory == null) + { + return null; + } + + var abpPackagePath = FindFileUpwards(projectDirectory, AbpPackageSearchPattern); + + if (abpPackagePath.IsNullOrEmpty()) + { + return null; + } + + var projectMetaData = ReadOrCreateMetadata(abpPackagePath); + + var abpSolutionPath = FindFileUpwards(projectDirectory, AbpSolutionSearchPattern); + + if (!abpSolutionPath.IsNullOrEmpty()) + { + projectMetaData.AbpSlnPath = abpSolutionPath; + } + + return projectMetaData; + } + catch + { + return null; + } + } + + private static AbpProjectMetaData ReadOrCreateMetadata(string packagePath) + { + + var fileContent = File.ReadAllText(packagePath); + var metadata = new AbpProjectMetaData(); + + using var document = JsonDocument.Parse(fileContent); + var root = document.RootElement; + + if (TryGetProjectId(root,out var projectId)) + { + metadata.ProjectId = projectId; + } + else + { + metadata.ProjectId = Guid.NewGuid(); + WriteProjectIdToPackageFile(root, packagePath, metadata.ProjectId.Value); + } + + if (root.TryGetProperty("role", out var roleElement) && + roleElement.ValueKind == JsonValueKind.String) + { + metadata.Role = roleElement.GetString()!; + } + + return metadata; + } + + private static void WriteProjectIdToPackageFile(JsonElement root, string packagePath, Guid projectId) + { + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true })) + { + writer.WriteStartObject(); + + if (root.ValueKind == JsonValueKind.Object) + { + foreach (var property in root.EnumerateObject()) + { + if (property.Name != "projectId") + { + property.WriteTo(writer); + } + } + } + + writer.WriteString("projectId", projectId.ToString()); + writer.WriteEndObject(); + } + + var json = Encoding.UTF8.GetString(stream.ToArray()); + File.WriteAllText(packagePath, json); + } + + private static string? FindFileUpwards(string startingDir, string searchPattern) + { + var currentDir = new DirectoryInfo(startingDir); + var currentDepth = 0; + + while (currentDir != null && currentDepth < MaxDepth) + { + var file = currentDir.GetFiles(searchPattern).FirstOrDefault(); + if (file != null) + { + return file.FullName; + } + + currentDir = currentDir.Parent; + currentDepth++; + } + + return null; + } + private static bool TryGetProjectId(JsonElement element, out Guid projectId) + { + if (element.TryGetProperty("projectId", out var projectIdElement) && + projectIdElement.ValueKind == JsonValueKind.String && + Guid.TryParse(projectIdElement.GetString(), out projectId)) + { + return true; + } + + projectId = Guid.Empty; + return false; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpProjectMetaData.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpProjectMetaData.cs new file mode 100644 index 0000000000..d56ecf6106 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpProjectMetaData.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Internal.Telemetry.Helpers; + +internal class AbpProjectMetaData +{ + public Guid? ProjectId { get; set; } + public string? Role { get; set; } + public string? AbpSlnPath { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/Cryptography.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/Cryptography.cs new file mode 100644 index 0000000000..3d402b651b --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/Cryptography.cs @@ -0,0 +1,42 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Volo.Abp.Internal.Telemetry.Helpers; + +static internal class Cryptography +{ + private const string EncryptionKey = "AbpTelemetryStorageKey"; + + public static string Encrypt(string plainText) + { + Check.NotNullOrEmpty(plainText, nameof(plainText)); + using var aes = Aes.Create(); + using var sha256 = SHA256.Create(); + + aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(EncryptionKey)); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.PKCS7; + + var encryptor = aes.CreateEncryptor(); + var inputBytes = Encoding.UTF8.GetBytes(plainText); + var encryptedBytes = encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); + return Convert.ToBase64String(encryptedBytes); + } + + public static string Decrypt(string cipherText) + { + Check.NotNullOrEmpty(cipherText, nameof(cipherText)); + using var aes = Aes.Create(); + using var sha256 = SHA256.Create(); + + aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(EncryptionKey)); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.PKCS7; + + var decryptor = aes.CreateDecryptor(); + var inputBytes = Convert.FromBase64String(cipherText); + var decryptedBytes = decryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); + return Encoding.UTF8.GetString(decryptedBytes); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/MutexExecutor.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/MutexExecutor.cs new file mode 100644 index 0000000000..d8eb28b40e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/MutexExecutor.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Threading; + +namespace Volo.Abp.Internal.Telemetry.Helpers; + +static internal class MutexExecutor +{ + private const string MutexName = "Global\\MyFileReadMutex"; + private const int TimeoutMilliseconds = 3000; + + public static string? ReadFileSafely(string filePath) + { + using var mutex = new Mutex(false, MutexName); + + if (!mutex.WaitOne(TimeoutMilliseconds)) + { + return null; + } + + try + { + if (!File.Exists(filePath)) + { + return null; + } + + return File.ReadAllText(filePath); + } + catch (IOException) + { + return null; + } + finally + { + try + { + mutex.ReleaseMutex(); + } + catch + { + // Already released or abandoned + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/ITelemetryActivitySender.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/ITelemetryActivitySender.cs new file mode 100644 index 0000000000..46171de7ad --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/ITelemetryActivitySender.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Internal.Telemetry; + +public interface ITelemetryActivitySender +{ + Task TrySendQueuedActivitiesAsync(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/ITelemetryService.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/ITelemetryService.cs new file mode 100644 index 0000000000..32ec6f706a --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/ITelemetryService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Internal.Telemetry; +public interface ITelemetryService +{ + IAsyncDisposable TrackActivityAsync(string activityName, Action>? additionalProperties = null); + Task AddActivityAsync(string activityName, Action>? additionalProperties = null); + Task AddErrorActivityAsync(Action> additionalProperties); + Task AddErrorActivityAsync(string errorMessage); + Task AddErrorForActivityAsync(string failingActivity, string errorMessage); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryActivitySender.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryActivitySender.cs new file mode 100644 index 0000000000..d5efc1ccfd --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryActivitySender.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; + +namespace Volo.Abp.Internal.Telemetry; + +public class TelemetryActivitySender : ITelemetryActivitySender, IScopedDependency +{ + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + private readonly ILogger _logger; + + private const int ActivitySendBatchSize = 50; + private const int MaxRetryAttempts = 3; + private const int RetryDelayMilliseconds = 1000; + + public TelemetryActivitySender(ITelemetryActivityStorage telemetryActivityStorage, ILogger logger) + { + _telemetryActivityStorage = telemetryActivityStorage; + _logger = logger; + } + + public async Task TrySendQueuedActivitiesAsync() + { + if (!_telemetryActivityStorage.ShouldSendActivities()) + { + return; + } + + await SendActivitiesAsync(); + } + + + private async Task SendActivitiesAsync() + { + try + { + var activities = _telemetryActivityStorage.GetActivities(); + var batches = CreateActivityBatches(activities); + + using var httpClient = new HttpClient(); + ConfigureHttpClientAuthentication(httpClient); + + foreach (var batch in batches) + { + var isSuccessful = await TrySendBatchWithRetriesAsync(httpClient, batch); + + if (!isSuccessful) + { + break; + } + } + } + catch + { + //ignored + } + } + + + private async Task TrySendBatchWithRetriesAsync(HttpClient httpClient, ActivityEvent[] activities) + { + var currentAttempt = 0; + + while (currentAttempt < MaxRetryAttempts) + { + try + { + var response = await httpClient.PostAsync($"{AbpPlatformUrls.AbpTelemetryApiUrl}api/telemetry/collect", new StringContent(JsonSerializer.Serialize(activities), Encoding.UTF8, "application/json")); + + if (response.IsSuccessStatusCode) + { + _telemetryActivityStorage.DeleteActivities(activities); + } + else + { + _logger.LogWithLevel(LogLevel.Trace, + $"Failed to send telemetry activities. Status code: {response.StatusCode}, Reason: {response.ReasonPhrase}"); + _telemetryActivityStorage.MarkActivitiesAsFailed(activities); + } + + return true; + } + catch (Exception ex) + { + _logger.LogWithLevel(LogLevel.Trace, $"Error sending telemetry activities: {ex.Message}"); + currentAttempt++; + await Task.Delay(currentAttempt * RetryDelayMilliseconds); + } + } + + _logger.LogWithLevel(LogLevel.Trace, "Max retries reached. Failed to send telemetry activities."); + + return false; + } + + private static IEnumerable CreateActivityBatches(List activities) + { + return activities + .Select((x, i) => new { Index = i, Value = x }) + .GroupBy(x => x.Index / ActivitySendBatchSize) + .Select(x => x.Select(v => v.Value).ToArray()); + } + + private static void ConfigureHttpClientAuthentication(HttpClient httpClient) + { + if (!File.Exists(TelemetryPaths.AccessToken)) + { + return; + } + + var accessToken = File.ReadAllText(TelemetryPaths.AccessToken, Encoding.UTF8); + + if (accessToken.IsNullOrEmpty()) + { + return; + } + + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs new file mode 100644 index 0000000000..8a2e3b4382 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Constants; +using ActivityContext = Volo.Abp.Internal.Telemetry.Activity.ActivityContext; + +namespace Volo.Abp.Internal.Telemetry; + +public class TelemetryService : ITelemetryService, IScopedDependency +{ + private readonly ITelemetryActivitySender _telemetryActivitySender; + private readonly ITelemetryActivityEventBuilder _telemetryActivityEventBuilder; + private readonly ITelemetryActivityStorage _telemetryActivityStorage; + + public TelemetryService(ITelemetryActivitySender telemetryActivitySender, + ITelemetryActivityEventBuilder telemetryActivityEventBuilder, + ITelemetryActivityStorage telemetryActivityStorage) + { + _telemetryActivitySender = telemetryActivitySender; + _telemetryActivityEventBuilder = telemetryActivityEventBuilder; + _telemetryActivityStorage = telemetryActivityStorage; + } + + + public IAsyncDisposable TrackActivityAsync(string activityName, + Action>? additionalProperties = null) + { + Check.NotNullOrEmpty(activityName, nameof(activityName)); + var stopwatch = Stopwatch.StartNew(); + var context = ActivityContext.Create(activityName, additionalProperties: additionalProperties); + + return new AsyncDisposeFunc(async () => + { + stopwatch.Stop(); + context.Current[ActivityPropertyNames.ActivityDuration] = stopwatch.ElapsedMilliseconds; + await AddActivityAsync(context); + }); + } + + public async Task AddActivityAsync(string activityName, + Action>? additionalProperties = null) + { + Check.NotNullOrEmpty(activityName, nameof(activityName)); + var context = ActivityContext.Create(activityName, additionalProperties: additionalProperties); + await AddActivityAsync(context); + } + + public async Task AddErrorActivityAsync(Action> additionalProperties) + { + var context = ActivityContext.Create(ActivityNameConsts.Error, additionalProperties: additionalProperties); + await AddActivityAsync(context); + } + + public async Task AddErrorActivityAsync(string errorMessage) + { + var context = ActivityContext.Create(ActivityNameConsts.Error, errorMessage); + await AddActivityAsync(context); + } + + public async Task AddErrorForActivityAsync(string failingActivity, string errorMessage) + { + Check.NotNullOrEmpty(failingActivity, nameof(failingActivity)); + var context = ActivityContext.Create(ActivityNameConsts.Error, errorMessage, configure => + { + configure[ActivityPropertyNames.FailingActivity] = failingActivity; + }); + await AddActivityAsync(context); + } + + private Task AddActivityAsync(ActivityContext context) + { + _ = Task.Run(async () => + { + await BuildAndSendActivityAsync(context); + }); + + return Task.CompletedTask; + } + + private async Task BuildAndSendActivityAsync(ActivityContext context) + { + try + { + var activityEvent = await _telemetryActivityEventBuilder.BuildAsync(context); + if (activityEvent is null) + { + return; + } + + _telemetryActivityStorage.SaveActivity(activityEvent); + await _telemetryActivitySender.TrySendQueuedActivitiesAsync(); + } + catch + { + //ignored + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Telemetry/TelemetryDomainInfoEnricher.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Telemetry/TelemetryDomainInfoEnricher.cs new file mode 100644 index 0000000000..543cf8e406 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Telemetry/TelemetryDomainInfoEnricher.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Internal.Telemetry.Activity; +using Volo.Abp.Internal.Telemetry.Activity.Contracts; +using Volo.Abp.Internal.Telemetry.Activity.Providers; +using Volo.Abp.Internal.Telemetry.Constants; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Domain.Telemetry; + +[ExposeServices(typeof(ITelemetryActivityEventEnricher), typeof(IHasParentTelemetryActivityEventEnricher))] +public class TelemetryDomainInfoEnricher : TelemetryActivityEventEnricher, IHasParentTelemetryActivityEventEnricher +{ + private readonly ITypeFinder _typeFinder; + + public TelemetryDomainInfoEnricher(ITypeFinder typeFinder, IServiceProvider serviceProvider) + : base(serviceProvider) + { + _typeFinder = typeFinder; + } + + protected override Task CanExecuteAsync(ActivityContext context) + { + return Task.FromResult(context.ProjectId.HasValue); + } + + protected override Task ExecuteAsync(ActivityContext context) + { + var entityCount = _typeFinder.Types.Count(t => + typeof(IEntity).IsAssignableFrom(t) && !t.IsAbstract && + !t.AssemblyQualifiedName!.StartsWith(TelemetryConsts.VoloNameSpaceFilter)); + + context.Current[ActivityPropertyNames.EntityCount] = entityCount; + + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs index 28cacd4be0..b2a9179415 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/AuthorizationTestPermissionDefinitionProvider.cs @@ -15,8 +15,12 @@ public class AuthorizationTestPermissionDefinitionProvider : PermissionDefinitio var group = context.AddGroup("TestGroup"); - group.AddPermission("MyAuthorizedService1"); + group[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestPermissionDefinitionProvider).FullName); + + var permission1 = group.AddPermission("MyAuthorizedService1"); + permission1[PermissionDefinitionContext.KnownPropertyNames.CurrentProviderName].ShouldBe(typeof(AuthorizationTestPermissionDefinitionProvider).FullName); + group.AddPermission("MyPermission1").StateCheckers.Add(new TestRequireEditionPermissionSimpleStateChecker()); group.AddPermission("MyPermission2"); group.AddPermission("MyPermission3"); diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Telemetry/ActivityEvent_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Telemetry/ActivityEvent_Tests.cs new file mode 100644 index 0000000000..42083ceaa1 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Telemetry/ActivityEvent_Tests.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using Shouldly; +using Volo.Abp.Internal.Telemetry.Activity; +using Volo.Abp.Internal.Telemetry.Constants; +using Xunit; + +namespace Volo.Abp.Telemetry; + +public class ActivityEvent_Tests +{ + [Fact] + public void Should_Create_ActivityEvent_With_Required_Parameters() + { + // Arrange + var activityName = "TestActivity"; + var details = "Test Details"; + + // Act + var activityEvent = new ActivityEvent(activityName, details); + + // Assert + activityEvent[ActivityPropertyNames.ActivityName].ShouldBe(activityName); + activityEvent[ActivityPropertyNames.ActivityDetails].ShouldBe(details); + activityEvent[ActivityPropertyNames.Id].ShouldNotBe(Guid.Empty); + activityEvent[ActivityPropertyNames.Time].ShouldNotBe(default); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Throw_Exception_When_ActivityName_Is_Invalid(string invalidName) + { + // Act & Assert + Should.Throw(() => + new ActivityEvent(invalidName)); + } + + [Fact] + public void Should_Set_And_Get_AdditionalProperties() + { + // Arrange + var activityEvent = new ActivityEvent("TestActivity"); + var additionalProps = new Dictionary + { + { "key1", "value1" }, + { "key2", 42 } + }; + + // Act + activityEvent[ActivityPropertyNames.AdditionalProperties] = additionalProps; + + // Assert + + var activityAdditionalProps = activityEvent[ActivityPropertyNames.AdditionalProperties] as Dictionary; + activityAdditionalProps.ShouldNotBeNull(); + activityAdditionalProps.Count.ShouldBe(2); + activityAdditionalProps["key1"].ShouldBe("value1"); + activityAdditionalProps["key2"].ShouldBe(42); + } + + [Fact] + public void Should_Return_Default_Values_When_Properties_Not_Set() + { + // Arrange + var activityEvent = new ActivityEvent("TestActivity"); + + // Assert + activityEvent[ActivityPropertyNames.ActivityDetails].ShouldBeNull(); + } + + [Fact] + public void Should_Behave_Like_Dictionary() + { + // Arrange + var activityEvent = new ActivityEvent("TestActivity"); + + // Act + activityEvent["CustomKey"] = "CustomValue"; + + // Assert + activityEvent["CustomKey"].ShouldBe("CustomValue"); + activityEvent.ContainsKey("CustomKey").ShouldBeTrue(); + } + + +} + diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Telemetry/Cryptography_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Telemetry/Cryptography_Tests.cs new file mode 100644 index 0000000000..fd90e1221a --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Telemetry/Cryptography_Tests.cs @@ -0,0 +1,52 @@ +using System; +using Shouldly; +using Volo.Abp.Internal.Telemetry.Helpers; +using Xunit; + +namespace Volo.Abp.Telemetry; + +public class Cryptography_Tests +{ + [Fact] + public void Should_Encrypt_And_Decrypt_Text_Successfully() + { + // Arrange + const string plainText = "Test message 123!"; + + // Act + var encryptedText = Cryptography.Encrypt(plainText); + var decryptedText = Cryptography.Decrypt(encryptedText); + + // Assert + decryptedText.ShouldBe(plainText); + } + + [Fact] + public void Should_Generate_Different_Encrypted_Values_For_Different_Inputs() + { + // Arrange + const string text1 = "Message 1"; + const string text2 = "Message 2"; + + // Act + var encrypted1 = Cryptography.Encrypt(text1); + var encrypted2 = Cryptography.Encrypt(text2); + + // Assert + encrypted1.ShouldNotBe(encrypted2); + } + [Fact] + public void Encrypt_Should_Throw_ArgumentException_When_Input_Is_Null() + { + // Act & Assert + Should.Throw(() => Cryptography.Encrypt(null)); + } + + [Fact] + public void Decrypt_Should_Throw_ArgumentException_When_Input_Is_Null() + { + // Act & Assert + Should.Throw(() => Cryptography.Decrypt(null)); + } + +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs index d54256bd8d..af2236c53c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs @@ -58,31 +58,20 @@ public class EfCoreBlogPostRepository : EfCoreRepository entityIdFilters = null; - if (tagId.HasValue) - { - entityIdFilters = (await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken)).Select(Guid.Parse).ToList(); - } - - var tagFilteredEntityIds = tagId.HasValue - ? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken) - : null; - - var favoriteUserFilteredEntityIds = favoriteUserId.HasValue - ? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType) - : null; + cancellationToken = GetCancellationToken(cancellationToken); + + var tagFilteredEntityIds = await GetEntityIdsByTagId(tagId, cancellationToken); + var favoriteUserFilteredEntityIds = await GetFavoriteEntityIdsByUserId(favoriteUserId, cancellationToken); var queryable = (await GetDbSetAsync()) - .WhereIf(entityIdFilters != null, x => entityIdFilters.Contains(x.Id)) - .WhereIf(tagFilteredEntityIds != null, x => tagFilteredEntityIds.Contains(x.Id.ToString())) - .WhereIf(favoriteUserFilteredEntityIds != null, x => favoriteUserFilteredEntityIds.Contains(x.Id.ToString())) + .WhereIf(tagFilteredEntityIds.Count > 0, x => tagFilteredEntityIds.Contains(x.Id)) + .WhereIf(favoriteUserFilteredEntityIds.Count > 0, x => favoriteUserFilteredEntityIds.Contains(x.Id)) .WhereIf(blogId.HasValue, x => x.BlogId == blogId) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId) .WhereIf(statusFilter.HasValue, x => x.Status == statusFilter) .WhereIf(!string.IsNullOrEmpty(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)); - var count = await queryable.CountAsync(GetCancellationToken(cancellationToken)); - return count; + return await queryable.CountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetListAsync( @@ -99,27 +88,16 @@ public class EfCoreBlogPostRepository : EfCoreRepository(); var usersDbSet = dbContext.Set(); - List entityIdFilters = null; - if (tagId.HasValue) - { - entityIdFilters = (await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken)).Select(Guid.Parse).ToList(); - } - - var tagFilteredEntityIds = tagId.HasValue - ? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken) - : null; - - var favoriteUserFilteredEntityIds = favoriteUserId.HasValue - ? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType) - : null; + cancellationToken = GetCancellationToken(cancellationToken); + + var tagFilteredEntityIds = await GetEntityIdsByTagId(tagId, cancellationToken); + var favoriteUserFilteredEntityIds = await GetFavoriteEntityIdsByUserId(favoriteUserId, cancellationToken); var queryable = (await GetDbSetAsync()) - .WhereIf(entityIdFilters != null, x => entityIdFilters.Contains(x.Id)) - .WhereIf(tagFilteredEntityIds != null, x => tagFilteredEntityIds.Contains(x.Id.ToString())) - .WhereIf(favoriteUserFilteredEntityIds != null, x => favoriteUserFilteredEntityIds.Contains(x.Id.ToString())) + .WhereIf(tagFilteredEntityIds.Count > 0, x => tagFilteredEntityIds.Contains(x.Id)) + .WhereIf(favoriteUserFilteredEntityIds.Count > 0, x => favoriteUserFilteredEntityIds.Contains(x.Id)) .WhereIf(blogId.HasValue, x => x.BlogId == blogId) .WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId) @@ -204,4 +182,64 @@ public class EfCoreBlogPostRepository : EfCoreRepository x.BlogId == blogId, cancellationToken: cancellationToken); } + + private async Task> GetFavoriteEntityIdsByUserId(Guid? userId, CancellationToken cancellationToken = default) + { + var entityIdFilters = new List(); + if (!userId.HasValue) + { + return entityIdFilters; + } + + var entityIds = await _markedItemManager.GetEntityIdsFilteredByUserAsync( + userId.Value, + BlogPostConsts.EntityType, + CurrentTenant.Id, + cancellationToken + ); + + if (entityIds.Count == 0) + { + entityIdFilters.Add(Guid.Empty); + return entityIdFilters; + } + + foreach (var entityId in entityIds) + { + if (Guid.TryParse(entityId, out var parsedEntityId)) + { + entityIdFilters.Add(parsedEntityId); + } + } + + return entityIdFilters; + } + + private async Task> GetEntityIdsByTagId(Guid? tagId, CancellationToken cancellationToken = default) + { + var entityIdFilters = new List(); + if (!tagId.HasValue) + { + return entityIdFilters; + } + + var entityIds = + await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken); + + if (entityIds.Count == 0) + { + entityIdFilters.Add(Guid.Empty); + return entityIdFilters; + } + + foreach (var entityId in entityIds) + { + if (Guid.TryParse(entityId, out var parsedEntityId)) + { + entityIdFilters.Add(parsedEntityId); + } + } + + return entityIdFilters; + } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs index 78afc5a871..f2103c1580 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs @@ -86,8 +86,9 @@ public class MongoBlogPostRepository : MongoDbRepository( - await BlogPostRepository.GetCountAsync(blogId: blog.Id, tagId: input.TagId, - statusFilter: BlogPostStatus.Published, authorId: input.AuthorId), + await BlogPostRepository.GetCountAsync( + blogId: blog.Id, + tagId: input.TagId, + favoriteUserId: favoriteUserId, + statusFilter: BlogPostStatus.Published, + authorId: input.AuthorId + ), ObjectMapper.Map, List>(blogPosts)); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml index 23f214f1e5..b6d0b41f9d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml @@ -14,7 +14,8 @@ @{ const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png"; - var isMarkedItemFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled() && Model.MarkedItemsFeature?.IsEnabled == true; + var isMarkedItemFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled() && + (Model.MarkedItemsFeature?.IsEnabled == true || Model.FilterOnFavorites.HasValue); } @section styles { @@ -68,20 +69,20 @@
} -@if (Model.Blogs.TotalCount > 0) +@if (isMarkedItemFeatureEnabled) { - @if (isMarkedItemFeatureEnabled) - { - var filterOnFavorties = Model.FilterOnFavorites.GetValueOrDefault(); - string icon = filterOnFavorties ? "heart" : "heart-o"; - - + var filterOnFavorites = Model.FilterOnFavorites.GetValueOrDefault(); + string icon = filterOnFavorites ? "heart" : "heart-o"; + + +} - } +@if (Model.Blogs.TotalCount > 0) +{ @foreach (var blog in Model.Blogs.Items) { diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs index 85ffd77924..49e956ad1b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -175,13 +176,15 @@ public class PermissionStore : IPermissionStore, ITransientDependency { using (PermissionGrantRepository.DisableTracking()) { + var permissionNames = new HashSet(notCacheKeys.Select(GetPermissionNameFormCacheKeyOrNull)); var permissions = (await PermissionDefinitionManager.GetPermissionsAsync()) - .Where(x => notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name)).ToList(); + .Where(x => permissionNames.Contains(x.Name)) + .ToList(); Logger.LogDebug($"Getting not cache granted permissions from the repository for this provider name,key: {providerName},{providerKey}"); var grantedPermissionsHashSet = new HashSet( - (await PermissionGrantRepository.GetListAsync(notCacheKeys.Select(GetPermissionNameFormCacheKeyOrNull).ToArray(), providerName, providerKey)).Select(p => p.Name) + (await PermissionGrantRepository.GetListAsync(permissionNames.ToArray(), providerName, providerKey)).Select(p => p.Name) ); Logger.LogDebug($"Setting the cache items. Count: {permissions.Count}"); diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs index 4e892abded..6f11da24e5 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingManagementStore.cs @@ -176,9 +176,10 @@ public class SettingManagementStore : ISettingManagementStore, ITransientDepende string providerKey, List notCacheKeys) { - var settingDefinitions = (await SettingDefinitionManager.GetAllAsync()).Where(x => notCacheKeys.Any(k => GetSettingNameFormCacheKeyOrNull(k) == x.Name)); + var settingNames = new HashSet(notCacheKeys.Select(GetSettingNameFormCacheKeyOrNull)); + var settingDefinitions = (await SettingDefinitionManager.GetAllAsync()).Where(x => settingNames.Contains(x.Name)); - var settingsDictionary = (await SettingRepository.GetListAsync(notCacheKeys.Select(GetSettingNameFormCacheKeyOrNull).ToArray(), providerName, providerKey)) + var settingsDictionary = (await SettingRepository.GetListAsync(settingNames.ToArray(), providerName, providerKey)) .ToDictionary(s => s.Name, s => s.Value); var cacheItems = new List>(); diff --git a/npm/ng-packs/packages/schematics/src/utils/model.ts b/npm/ng-packs/packages/schematics/src/utils/model.ts index eee8b8c441..1ea9130ef2 100644 --- a/npm/ng-packs/packages/schematics/src/utils/model.ts +++ b/npm/ng-packs/packages/schematics/src/utils/model.ts @@ -229,7 +229,7 @@ function renamePropForTenant(interfaces: Interface[]) { const isTenant = prop.name.toLocaleLowerCase().includes(TENANT_KEY); const isSaasDto = prop.refs.filter(f => f.startsWith(SAAS_NAMESPACE)).length > 0; - if (isTenant && isSaasDto) { + if (isTenant && isSaasDto && !prop.type.startsWith('Saas')) { prop.type = 'Saas' + prop.type; } } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts index d19776424f..5d4e745ce9 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts @@ -151,4 +151,15 @@ background-color: rgba(0, 0, 0, 0.6); .abp-md-form { max-width: 540px; } + +.ngx-datatable.material:has(.datatable-body-row) .datatable-footer { + border-top: none; +} + +.ngx-datatable.material:not(:has(.datatable-body-row)) .datatable-footer { + border-top: 1px solid #dee2e6; +} + `; + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index 095e09c07b..1f1593187e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -28,7 +28,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs index 988608d631..86c8803963 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs @@ -24,7 +24,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.MongoDB; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.MongoDB; using Volo.Abp.Emailing; using Volo.Abp.FeatureManagement; @@ -63,7 +63,7 @@ namespace MyCompanyName.MyProjectName; // ABP Framework packages typeof(AbpAspNetCoreMvcModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), @@ -176,7 +176,6 @@ public class MyProjectNameModule : AbpModule ConfigureAuthentication(context); ConfigureUrls(configuration); ConfigureBundles(); - ConfigureAutoMapper(context); ConfigureVirtualFiles(hostingEnvironment); ConfigureLocalizationServices(); ConfigureSwaggerServices(context.Services); @@ -185,6 +184,8 @@ public class MyProjectNameModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMongoDB(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureAuthentication(ServiceConfigurationContext context) @@ -326,15 +327,6 @@ public class MyProjectNameModule : AbpModule }); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureMongoDB(ServiceConfigurationContext context) { context.Services.AddMongoDbContext(options => diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index f7317fa86a..313f51d004 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -28,7 +28,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs index 8923f6189b..762ca3b7b1 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs @@ -25,7 +25,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.EntityFrameworkCore; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.SqlServer; @@ -64,7 +64,7 @@ namespace MyCompanyName.MyProjectName; // ABP Framework packages typeof(AbpAspNetCoreMvcModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpEntityFrameworkCoreSqlServerModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), @@ -179,7 +179,6 @@ public class MyProjectNameModule : AbpModule ConfigureAuthentication(context); ConfigureUrls(configuration); ConfigureBundles(); - ConfigureAutoMapper(context); ConfigureVirtualFiles(hostingEnvironment); ConfigureLocalizationServices(); ConfigureSwaggerServices(context.Services); @@ -188,6 +187,8 @@ public class MyProjectNameModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureEfCore(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureAuthentication(ServiceConfigurationContext context) @@ -329,15 +330,6 @@ public class MyProjectNameModule : AbpModule }); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureEfCore(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorAutoMapperProfile.cs deleted file mode 100644 index 623f6fb33e..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName; - -public class MyProjectNameBlazorAutoMapperProfile : Profile -{ - public MyProjectNameBlazorAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Blazor project. - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorMappers.cs new file mode 100644 index 0000000000..aeb87b3440 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Blazor.WebAssembly.Client; + +[Mapper] +public partial class MyProjectNameBlazorMappers +{ + //Define your Mapperly configuration here for the Blazor project. +} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs index 46afc0d1b0..aeeec93107 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs @@ -10,7 +10,7 @@ using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; using Volo.Abp.Autofac.WebAssembly; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; using Volo.Abp.Identity.Blazor.WebAssembly; @@ -68,8 +68,9 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); - ConfigureAutoMapper(context); ConfigureHttpClientProxies(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureRouter(ServiceConfigurationContext context) @@ -131,12 +132,4 @@ public class MyProjectNameBlazorModule : AbpModule BaseAddress = new Uri(environment.BaseAddress) }); } - - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj index 59edb352e6..8393fda5bc 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj @@ -19,7 +19,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs index 4d8c9f7f51..42ad99abd1 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs @@ -23,7 +23,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.MongoDB; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.FeatureManagement; using Volo.Abp.FeatureManagement.MongoDB; @@ -58,7 +58,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpAspNetCoreMvcModule), typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule), typeof(AbpSwashbuckleModule), @@ -156,7 +156,7 @@ public class MyProjectNameHostModule : AbpModule ConfigureBundles(); ConfigureMultiTenancy(); ConfigureUrls(configuration); - ConfigureAutoMapper(context); + ConfigureMapperly(context); ConfigureSwagger(context.Services, configuration); ConfigureAutoApiControllers(); ConfigureVirtualFiles(hostingEnvironment); @@ -239,17 +239,9 @@ public class MyProjectNameHostModule : AbpModule }); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) + private void ConfigureMapperly(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. - * See AutoMapper's documentation to learn what it is: - * https://docs.automapper.org/en/stable/Configuration-validation.html - */ - options.AddMaps(/* validate: true */); - }); + context.Services.AddMapperlyObjectMapper(); } private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj index ec9700e18b..e15b5ecc30 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj @@ -19,7 +19,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs index 454120e5ef..f77a4069e1 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs @@ -23,7 +23,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.EntityFrameworkCore; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.SqlServer; @@ -60,7 +60,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpAspNetCoreMvcModule), typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpEntityFrameworkCoreSqlServerModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule), @@ -161,7 +161,7 @@ public class MyProjectNameHostModule : AbpModule ConfigureBundles(); ConfigureMultiTenancy(); ConfigureUrls(configuration); - ConfigureAutoMapper(context); + ConfigureMapperly(context); ConfigureSwagger(context.Services, configuration); ConfigureAutoApiControllers(); ConfigureVirtualFiles(hostingEnvironment); @@ -244,17 +244,9 @@ public class MyProjectNameHostModule : AbpModule }); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) + private void ConfigureMapperly(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. - * See AutoMapper's documentation to learn what it is: - * https://docs.automapper.org/en/stable/Configuration-validation.html - */ - options.AddMaps(/* validate: true */); - }); + context.Services.AddMapperlyObjectMapper(); } private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj index 3e124600bb..f645f27eeb 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj @@ -15,7 +15,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs index 0a1942912c..93a9bd89b6 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs @@ -19,7 +19,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.MongoDB; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.FeatureManagement; using Volo.Abp.FeatureManagement.MongoDB; @@ -54,7 +54,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpAspNetCoreMvcModule), typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), @@ -153,7 +153,7 @@ public class MyProjectNameModule : AbpModule ConfigureBundles(); ConfigureMultiTenancy(); ConfigureUrls(configuration); - ConfigureAutoMapper(context); + ConfigureMapperly(context); ConfigureSwagger(context.Services, configuration); ConfigureAutoApiControllers(); ConfigureVirtualFiles(hostingEnvironment); @@ -279,17 +279,9 @@ public class MyProjectNameModule : AbpModule }); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) + private void ConfigureMapperly(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. - * See AutoMapper's documentation to learn what it is: - * https://docs.automapper.org/en/stable/Configuration-validation.html - */ - options.AddMaps(/* validate: true */); - }); + context.Services.AddMapperlyObjectMapper(); } private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj index 551d01521b..67529a7e05 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj @@ -15,7 +15,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs index a4a4410663..d0bc2944dc 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs @@ -20,7 +20,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.EntityFrameworkCore; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.SqlServer; @@ -56,7 +56,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpAspNetCoreMvcModule), typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpEntityFrameworkCoreSqlServerModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpSwashbuckleModule), @@ -157,7 +157,7 @@ public class MyProjectNameModule : AbpModule ConfigureBundles(); ConfigureMultiTenancy(); ConfigureUrls(configuration); - ConfigureAutoMapper(context); + ConfigureMapperly(context); ConfigureSwagger(context.Services, configuration); ConfigureAutoApiControllers(); ConfigureVirtualFiles(hostingEnvironment); @@ -283,17 +283,9 @@ public class MyProjectNameModule : AbpModule }); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) + private void ConfigureMapperly(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. - * See AutoMapper's documentation to learn what it is: - * https://docs.automapper.org/en/stable/Configuration-validation.html - */ - options.AddMaps(/* validate: true */); - }); + context.Services.AddMapperlyObjectMapper(); } private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj index e48f2044fd..383081b517 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj @@ -23,7 +23,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs index 543c60f13f..b0331d6144 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs @@ -17,7 +17,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AuditLogging.MongoDB; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.MongoDB; using Volo.Abp.Emailing; using Volo.Abp.FeatureManagement; @@ -56,7 +56,7 @@ namespace MyCompanyName.MyProjectName; // ABP Framework packages typeof(AbpAspNetCoreMvcModule), typeof(AbpAutofacModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), @@ -159,7 +159,7 @@ public class MyProjectNameModule : AbpModule ConfigureMultiTenancy(); ConfigureUrls(configuration); ConfigureBundles(); - ConfigureAutoMapper(context); + ConfigureMapperly(context); ConfigureSwagger(context.Services); ConfigureNavigationServices(); ConfigureAutoApiControllers(); @@ -287,17 +287,9 @@ public class MyProjectNameModule : AbpModule ); } - private void ConfigureAutoMapper(ServiceConfigurationContext context) + private void ConfigureMapperly(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. - * See AutoMapper's documentation to learn what it is: - * https://docs.automapper.org/en/stable/Configuration-validation.html - */ - options.AddMaps(/* validate: true */); - }); + context.Services.AddMapperlyObjectMapper(); } private void ConfigureMongoDB(ServiceConfigurationContext context) diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs index 39aa8b36c1..4869ab492e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs @@ -293,7 +293,6 @@ public class MyProjectNameModule : AbpModule private void ConfigureMapperly(ServiceConfigurationContext context) { - context.Services.AddMapperlyObjectMapper(); context.Services.AddMapperlyObjectMapper(); } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/ObjectMapping/MyProjectNameAutoMapperProfile.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/ObjectMapping/MyProjectNameAutoMapperProfile.cs deleted file mode 100644 index 6871cd2257..0000000000 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/ObjectMapping/MyProjectNameAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.ObjectMapping; - -public class MyProjectNameAutoMapperProfile : Profile -{ - public MyProjectNameAutoMapperProfile() - { - /* Create your AutoMapper object mappings here */ - } -} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/ObjectMapping/MyProjectNameMappers.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/ObjectMapping/MyProjectNameMappers.cs new file mode 100644 index 0000000000..d857855a80 --- /dev/null +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/ObjectMapping/MyProjectNameMappers.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.ObjectMapping; + +// This file is a placeholder for Mapperly mappers. +// Add your mapper classes here following the pattern: +// [Mapper] +// public partial class SourceToDestinationMapper : MapperBase +// { +// public override partial Destination Map(Source source); +// public override partial void Map(Source source, Destination destination); +// } + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationAutoMapperProfile.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationAutoMapperProfile.cs deleted file mode 100644 index f3f7d14052..0000000000 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationAutoMapperProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName; - -public class MyProjectNameApplicationAutoMapperProfile : Profile -{ - public MyProjectNameApplicationAutoMapperProfile() - { - /* You can configure your AutoMapper mapping configuration here. - * Alternatively, you can split your mapping configurations - * into multiple profile classes for a better organization. */ - } -} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationMappers.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationMappers.cs new file mode 100644 index 0000000000..fa87ca82bd --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationMappers.cs @@ -0,0 +1,12 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName; + +[Mapper] +public partial class MyProjectNameApplicationMappers +{ + /* You can configure your Mapperly mapping configuration here. + * Alternatively, you can split your mapping configurations + * into multiple mapper classes for a better organization. */ +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs index 12ba3bdc25..081d167b6d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs @@ -1,11 +1,12 @@ using Volo.Abp.Account; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.SettingManagement; using Volo.Abp.TenantManagement; +using Microsoft.Extensions.DependencyInjection; namespace MyCompanyName.MyProjectName; @@ -23,9 +24,6 @@ public class MyProjectNameApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.AddMaps(); - }); + context.Services.AddMapperlyObjectMapper(); } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj index 1f70aa544e..add85fe9f0 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj @@ -49,7 +49,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorAutoMapperProfile.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorAutoMapperProfile.cs deleted file mode 100644 index 9e5092bfe7..0000000000 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Blazor.Client; - -public class MyProjectNameBlazorAutoMapperProfile : Profile -{ - public MyProjectNameBlazorAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Blazor project. - } -} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs index 035c9ef835..13a9d2694b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs @@ -10,7 +10,7 @@ using OpenIddict.Abstractions; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; using Volo.Abp.Autofac.WebAssembly; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Identity.Blazor.WebAssembly; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Blazor.WebAssembly; @@ -39,7 +39,8 @@ public class MyProjectNameBlazorClientModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); - ConfigureAutoMapper(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureRouter(ServiceConfigurationContext context) @@ -87,12 +88,4 @@ public class MyProjectNameBlazorClientModule : AbpModule BaseAddress = new Uri(environment.BaseAddress) }); } - - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorMappers.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorMappers.cs new file mode 100644 index 0000000000..81e4271bc8 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Blazor.Client; + +[Mapper] +public partial class MyProjectNameBlazorMappers +{ + //Define your Mapperly configuration here for the Blazor project. +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj index a8494c994e..586ba24a4d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj @@ -26,7 +26,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index 3414c945a3..4d94c3bde9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -38,7 +38,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.DistributedLocking; @@ -108,7 +108,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureBundles(); ConfigureMultiTenancy(); ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureBlazorise(context); ConfigureRouter(context); @@ -116,6 +115,8 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureDataProtection(context, configuration, hostingEnvironment); ConfigureDistributedLocking(context, configuration); ConfigureSwaggerServices(context.Services); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureUrls(IConfiguration configuration) @@ -298,15 +299,6 @@ public class MyProjectNameBlazorModule : AbpModule options.AppAssembly = typeof(MyProjectNameBlazorModule).Assembly; }); } - - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureSwaggerServices(IServiceCollection services) { services.AddAbpSwaggerGen( diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 72835cb34d..a16dfa637a 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -25,7 +25,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs index 6fb111d98d..5d27bb122f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs @@ -32,7 +32,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Identity.Blazor.Server; using Volo.Abp.Modularity; using Volo.Abp.Security.Claims; @@ -121,13 +121,14 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureAuthentication(context); ConfigureUrls(configuration); ConfigureBundles(); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureSwaggerServices(context.Services); ConfigureAutoApiControllers(); ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureAuthentication(ServiceConfigurationContext context) @@ -244,14 +245,6 @@ public class MyProjectNameBlazorModule : AbpModule }); } - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - public override void OnApplicationInitialization(ApplicationInitializationContext context) { var env = context.GetEnvironment(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorAutoMapperProfile.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorAutoMapperProfile.cs deleted file mode 100644 index 4ec4dfb7bb..0000000000 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Blazor.WebApp.Client; - -public class MyProjectNameBlazorAutoMapperProfile : Profile -{ - public MyProjectNameBlazorAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Blazor project. - } -} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs index e2de628db7..d3e28b8e23 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs @@ -11,7 +11,7 @@ using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; using Volo.Abp.Autofac.WebAssembly; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Identity.Blazor.WebAssembly; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Blazor.WebAssembly; @@ -48,7 +48,8 @@ public class MyProjectNameBlazorClientModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); - ConfigureAutoMapper(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureRouter(ServiceConfigurationContext context) @@ -89,12 +90,4 @@ public class MyProjectNameBlazorClientModule : AbpModule BaseAddress = new Uri(environment.BaseAddress) }); } - - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorMappers.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorMappers.cs new file mode 100644 index 0000000000..2edcf5e58f --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Blazor.WebApp.Client; + +[Mapper] +public partial class MyProjectNameBlazorMappers +{ + //Define your Mapperly configuration here for the Blazor project. +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorAutoMapperProfile.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorAutoMapperProfile.cs deleted file mode 100644 index efd9d3cb61..0000000000 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client; - -public class MyProjectNameBlazorAutoMapperProfile : Profile -{ - public MyProjectNameBlazorAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Blazor project. - } -} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs index b0e4e4f67f..7f1f464aac 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs @@ -11,7 +11,7 @@ using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; using Volo.Abp.Autofac.WebAssembly; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Identity.Blazor.WebAssembly; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Blazor.WebAssembly; @@ -48,7 +48,8 @@ public class MyProjectNameBlazorClientModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); - ConfigureAutoMapper(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureRouter(ServiceConfigurationContext context) @@ -88,12 +89,4 @@ public class MyProjectNameBlazorClientModule : AbpModule BaseAddress = new Uri(environment.BaseAddress) }); } - - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorMappers.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorMappers.cs new file mode 100644 index 0000000000..fc64d2f482 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client; + +[Mapper] +public partial class MyProjectNameBlazorMappers +{ + //Define your Mapperly configuration here for the Blazor project. +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj index fc22aa3126..321e09eecf 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj @@ -28,7 +28,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs index aa439fe0ff..0849b09d6d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs @@ -41,7 +41,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.DistributedLocking; @@ -112,7 +112,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureBundles(); ConfigureMultiTenancy(); ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureBlazorise(context); ConfigureRouter(context); @@ -307,14 +306,6 @@ public class MyProjectNameBlazorModule : AbpModule }); } - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureSwaggerServices(IServiceCollection services) { services.AddAbpSwaggerGen( diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj index 76e388e05a..661b04fd57 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj @@ -26,7 +26,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs index a47a16406f..6a75e85ae0 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs @@ -34,7 +34,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Identity.Blazor.Server; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict; @@ -125,13 +125,14 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureAuthentication(context); ConfigureUrls(configuration); ConfigureBundles(); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureSwaggerServices(context.Services); ConfigureAutoApiControllers(); ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureAuthentication(ServiceConfigurationContext context) @@ -252,14 +253,6 @@ public class MyProjectNameBlazorModule : AbpModule }); } - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - public override void OnApplicationInitialization(ApplicationInitializationContext context) { var env = context.GetEnvironment(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj index f1f76659b6..d9c527fe34 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj @@ -24,7 +24,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index 4a6970ac19..c802138f7b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -26,7 +26,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebAutoMapperProfile.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebAutoMapperProfile.cs deleted file mode 100644 index eea8d4cb05..0000000000 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Web; - -public class MyProjectNameWebAutoMapperProfile : Profile -{ - public MyProjectNameWebAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Web project. - } -} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebMappers.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebMappers.cs new file mode 100644 index 0000000000..4ef965f932 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Web.Host; + +[Mapper] +public partial class MyProjectNameWebMappers +{ + //Define your Mapperly configuration here for the Web project. +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs index 9c2db7971c..152076d235 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs @@ -28,7 +28,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.DistributedLocking; @@ -92,11 +92,12 @@ public class MyProjectNameWebModule : AbpModule ConfigureDistributedLocking(context, configuration); ConfigureUrls(configuration); ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureNavigationServices(configuration); ConfigureMultiTenancy(); ConfigureSwaggerServices(context.Services); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureBundles() @@ -217,14 +218,6 @@ public class MyProjectNameWebModule : AbpModule }); } - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment) { if (hostingEnvironment.IsDevelopment()) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index b97fd05460..19dfb6cf70 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -47,7 +47,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebAutoMapperProfile.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebAutoMapperProfile.cs deleted file mode 100644 index eea8d4cb05..0000000000 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Web; - -public class MyProjectNameWebAutoMapperProfile : Profile -{ - public MyProjectNameWebAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Web project. - } -} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebMappers.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebMappers.cs new file mode 100644 index 0000000000..b7b6450bb3 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Web; + +[Mapper] +public partial class MyProjectNameWebMappers +{ + //Define your Mapperly configuration here for the Web project. +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index bec50e72eb..27f8f93367 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -25,7 +25,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity.Web; using Volo.Abp.Localization; @@ -107,11 +107,12 @@ public class MyProjectNameWebModule : AbpModule ConfigureAuthentication(context); ConfigureUrls(configuration); ConfigureBundles(); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureNavigationServices(); ConfigureAutoApiControllers(); ConfigureSwaggerServices(context.Services); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureAuthentication(ServiceConfigurationContext context) @@ -145,14 +146,6 @@ public class MyProjectNameWebModule : AbpModule }); } - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment) { if (hostingEnvironment.IsDevelopment()) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostAutoMapperProfile.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostAutoMapperProfile.cs deleted file mode 100644 index 1552e364ff..0000000000 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Blazor.Host.Client; - -public class MyProjectNameBlazorHostAutoMapperProfile : Profile -{ - public MyProjectNameBlazorHostAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Blazor project. - } -} diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs index 69bfb87723..f8f1805ea1 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs @@ -11,7 +11,7 @@ using Volo.Abp.Account; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme; using Volo.Abp.Autofac.WebAssembly; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Identity.Blazor.WebAssembly; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Blazor.WebAssembly; @@ -41,7 +41,8 @@ public class MyProjectNameBlazorHostClientModule : AbpModule ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); - ConfigureAutoMapper(context); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureRouter(ServiceConfigurationContext context) @@ -83,12 +84,4 @@ public class MyProjectNameBlazorHostClientModule : AbpModule BaseAddress = new Uri(environment.BaseAddress) }); } - - private void ConfigureAutoMapper(ServiceConfigurationContext context) - { - Configure(options => - { - options.AddMaps(); - }); - } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostMappers.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostMappers.cs new file mode 100644 index 0000000000..71b072cd93 --- /dev/null +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Blazor.Host.Client; + +[Mapper] +public partial class MyProjectNameBlazorHostMappers +{ + //Define your Mapperly configuration here for the Blazor project. +} diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebAutoMapperProfile.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebAutoMapperProfile.cs deleted file mode 100644 index c6255b4bd1..0000000000 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebAutoMapperProfile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName; - -public class MyProjectNameWebAutoMapperProfile : Profile -{ - public MyProjectNameWebAutoMapperProfile() - { - //Define your AutoMapper configuration here for the Web project. - } -} diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs index e90eddd122..8d912bcad4 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs @@ -29,7 +29,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.FeatureManagement; @@ -101,11 +101,12 @@ public class MyProjectNameWebHostModule : AbpModule ConfigureCache(configuration); ConfigureUrls(configuration); ConfigureAuthentication(context, configuration); - ConfigureAutoMapper(); ConfigureVirtualFileSystem(hostingEnvironment); ConfigureSwaggerServices(context.Services); ConfigureMultiTenancy(); ConfigureDataProtection(context, configuration, hostingEnvironment); + + context.Services.AddMapperlyObjectMapper(); } private void ConfigureMenu(IConfiguration configuration) @@ -169,14 +170,6 @@ public class MyProjectNameWebHostModule : AbpModule }); } - private void ConfigureAutoMapper() - { - Configure(options => - { - options.AddMaps(); - }); - } - private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment) { if (hostingEnvironment.IsDevelopment()) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebMappers.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebMappers.cs new file mode 100644 index 0000000000..4ef965f932 --- /dev/null +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Web.Host; + +[Mapper] +public partial class MyProjectNameWebMappers +{ + //Define your Mapperly configuration here for the Web project. +} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyCompanyName.MyProjectName.Application.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyCompanyName.MyProjectName.Application.csproj index df92d80ceb..bbb637edb3 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyCompanyName.MyProjectName.Application.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyCompanyName.MyProjectName.Application.csproj @@ -9,7 +9,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationAutoMapperProfile.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationAutoMapperProfile.cs deleted file mode 100644 index f3f7d14052..0000000000 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationAutoMapperProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName; - -public class MyProjectNameApplicationAutoMapperProfile : Profile -{ - public MyProjectNameApplicationAutoMapperProfile() - { - /* You can configure your AutoMapper mapping configuration here. - * Alternatively, you can split your mapping configurations - * into multiple profile classes for a better organization. */ - } -} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationMappers.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationMappers.cs new file mode 100644 index 0000000000..fa87ca82bd --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationMappers.cs @@ -0,0 +1,12 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName; + +[Mapper] +public partial class MyProjectNameApplicationMappers +{ + /* You can configure your Mapperly mapping configuration here. + * Alternatively, you can split your mapping configurations + * into multiple mapper classes for a better organization. */ +} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs index d8fefafb98..93ca04edf6 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Application/MyProjectNameApplicationModule.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.Application; @@ -9,16 +9,12 @@ namespace MyCompanyName.MyProjectName; typeof(MyProjectNameDomainModule), typeof(MyProjectNameApplicationContractsModule), typeof(AbpDddApplicationModule), - typeof(AbpAutoMapperModule) + typeof(AbpMapperlyModule) )] public class MyProjectNameApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddMaps(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); } } diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index 870ca204dd..5ab7cf5e98 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -8,7 +8,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorAutoMapperProfile.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorAutoMapperProfile.cs deleted file mode 100644 index 13deb63b1d..0000000000 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Blazor; - -public class MyProjectNameBlazorAutoMapperProfile : Profile -{ - public MyProjectNameBlazorAutoMapperProfile() - { - /* You can configure your AutoMapper mapping configuration here. - * Alternatively, you can split your mapping configurations - * into multiple profile classes for a better organization. */ - } -} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorMappers.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorMappers.cs new file mode 100644 index 0000000000..a37e5da245 --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorMappers.cs @@ -0,0 +1,12 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Blazor; + +[Mapper] +public partial class MyProjectNameBlazorMappers +{ + /* You can configure your Mapperly mapping configuration here. + * Alternatively, you can split your mapping configurations + * into multiple mapper classes for a better organization. */ +} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index dd7bf654ee..1dcb64b5c5 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -2,7 +2,7 @@ using MyCompanyName.MyProjectName.Blazor.Menus; using Volo.Abp.AspNetCore.Components.Web.Theming; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.UI.Navigation; @@ -11,18 +11,13 @@ namespace MyCompanyName.MyProjectName.Blazor; [DependsOn( typeof(MyProjectNameApplicationContractsModule), typeof(AbpAspNetCoreComponentsWebThemingModule), - typeof(AbpAutoMapperModule) + typeof(AbpMapperlyModule) )] public class MyProjectNameBlazorModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index c11df1a518..7102f1935f 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebAutoMapperProfile.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebAutoMapperProfile.cs deleted file mode 100644 index 08b573de75..0000000000 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebAutoMapperProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; - -namespace MyCompanyName.MyProjectName.Web; - -public class MyProjectNameWebAutoMapperProfile : Profile -{ - public MyProjectNameWebAutoMapperProfile() - { - /* You can configure your AutoMapper mapping configuration here. - * Alternatively, you can split your mapping configurations - * into multiple profile classes for a better organization. */ - } -} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebMappers.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebMappers.cs new file mode 100644 index 0000000000..b7b6450bb3 --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebMappers.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace MyCompanyName.MyProjectName.Web; + +[Mapper] +public partial class MyProjectNameWebMappers +{ + //Define your Mapperly configuration here for the Web project. +} diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index 9e139a0515..4b1876a61d 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -4,7 +4,7 @@ using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.Web.Menus; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; @@ -15,7 +15,7 @@ namespace MyCompanyName.MyProjectName.Web; [DependsOn( typeof(MyProjectNameApplicationContractsModule), typeof(AbpAspNetCoreMvcUiThemeSharedModule), - typeof(AbpAutoMapperModule) + typeof(AbpMapperlyModule) )] public class MyProjectNameWebModule : AbpModule { @@ -44,11 +44,7 @@ public class MyProjectNameWebModule : AbpModule options.FileSets.AddEmbedded(); }); - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddMaps(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => {