Refactor and enhance product and order handling

- Updated `DiscountActionDialogBoxViewModel` and `FastProductCreateDialogBoxViewModel` to create command objects directly from properties and improved error handling.
- Added meta tag management UI and logic in `ProductActionDialogBox.razor` and `ProductActionDialogBoxViewModel`.
- Increased max file read stream size to 8 MB in `StorageDialogBoxViewModel`.
- Incremented `AssemblyVersion` and `FileVersion` to `1.7.20.34` in `Netina.AdminPanel.PWA.csproj`.
- Updated `BrandsPage.razor` and `BrandsPageViewModel` for pagination and service injection.
- Updated `CategoriesPageViewModel` to create command objects directly from properties.
- Updated `ProductsPage.razor` for service injection and added a button for product details.
- Updated `ICrudApiRest` and `ICrudDtoApiRest` interfaces to use generic `Create` methods.
- Updated `appsettings.Development.json` for `StorageBaseUrl` and commented out `IsShop`.
- Added new project `AppHost.csproj` targeting .NET 8.0 with Aspire hosting.
- Added new `appsettings.Development.json` and `appsettings.json` for logging.
- Added new `Program.cs` to create and run a distributed application.
- Added new `launchSettings.json` for application launch settings.
- Added `Extensions.cs` for common .NET Aspire services.
- Added new project `ServiceDefaults.csproj` for shared service defaults.
- Introduced `ProductMetaTag` class and related migration for meta tag handling.
- Updated `OrderController.cs` for additional authorization requirements.
- Updated target frameworks to `net8.0` in various projects.
- Enhanced `SiteMapService.cs` to include brand site maps.
- Added new properties to DTOs for customer and meta tag handling.
- Enhanced `Product` class with meta tag management methods.
- Refactored `OrderMapper.g.cs` and `ProductMapper.g.cs` for improved mapping logic.
- Enhanced command handlers to manage meta tags.
- Added `ICurrentUserService` for user permissions in query handlers.
- Refactored `StorageService.cs` for paginated storage file fetching.
subProduct
Amir Hossein Khademi 2024-12-06 17:37:40 +03:30
parent 91d9b602f8
commit 0e7c1d7f81
19 changed files with 418 additions and 40 deletions

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>045801e7-58a1-4ee6-9d28-93c23b5dcd6b</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Netina.AdminPanel.PWA\Netina.AdminPanel.PWA.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
var builder = DistributedApplication.CreateBuilder(args);
builder.Build().Run();

View File

@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17057;http://localhost:15006",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21274",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22232"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15006",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19034",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20044"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}

View File

@ -194,16 +194,42 @@ public class DiscountActionDialogBoxViewModel : BaseViewModel<DiscountLDto>
if(StartDate != null)
PageDto.StartDate = StartDate.Value;
var request = PageDto.Adapt<CreateDiscountCommand>();
await _restWrapper.CrudApiRest<Discount, Guid>(Address.DiscountController).Create<CreateDiscountCommand>(request, token);
var request = new CreateDiscountCommand(PageDto.Code,
PageDto.Description,
PageDto.DiscountPercent,
PageDto.DiscountAmount,
PageDto.HasCode,
PageDto.AmountType,
PageDto.Type,
PageDto.Count,
PageDto.StartDate,
PageDto.ExpireDate,
PageDto.Immortal,
PageDto.PriceFloor,
PageDto.HasPriceFloor,
PageDto.PriceCeiling,
PageDto.HasPriceCeiling,
PageDto.IsInfinity,
PageDto.UseCount,
PageDto.IsForInvitation,
PageDto.IsSpecialOffer,
PageDto.IsForFirstPurchase,
PageDto.ProductId,
PageDto.CategoryId);
await _restWrapper.CrudDtoApiRest<Discount,DiscountLDto, Guid>(Address.DiscountController).Create<CreateDiscountCommand>(request, token);
_snackbar.Add($"ساخت تخفیف با موفقیت انجام شد", Severity.Success);
_mudDialog.Close(true);
}
catch (ApiException ex)
{
if (ex.StatusCode == HttpStatusCode.BadRequest)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
else
_snackbar.Add(ex.Content, Severity.Error);
_mudDialog.Cancel();
}
catch (Exception e)
@ -248,7 +274,30 @@ public class DiscountActionDialogBoxViewModel : BaseViewModel<DiscountLDto>
if (token == null)
throw new Exception("Token is null");
PageDto.Id = _discountId;
var request = PageDto.Adapt<UpdateDiscountCommand>();
var request = new UpdateDiscountCommand(
PageDto.Id,
PageDto.Code,
PageDto.Description,
PageDto.DiscountPercent,
PageDto.DiscountAmount,
PageDto.HasCode,
PageDto.AmountType,
PageDto.Type,
PageDto.Count,
PageDto.StartDate,
PageDto.ExpireDate,
PageDto.Immortal,
PageDto.PriceFloor,
PageDto.HasPriceFloor,
PageDto.PriceCeiling,
PageDto.HasPriceCeiling,
PageDto.IsInfinity,
PageDto.UseCount,
PageDto.IsForInvitation,
PageDto.IsSpecialOffer,
PageDto.IsForFirstPurchase,
PageDto.ProductId,
PageDto.CategoryId);
await _restWrapper.CrudApiRest<Discount, Guid>(Address.DiscountController).Update<UpdateDiscountCommand>(request, token);
_snackbar.Add($"ویرایش تخفیف با موفقیت انجام شد", Severity.Success);
_mudDialog.Close(true);

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Forms;
using MediatR;
using Microsoft.AspNetCore.Components.Forms;
using Netina.Domain.Entities.Brands;
namespace Netina.AdminPanel.PWA.Dialogs;
@ -141,19 +142,29 @@ public class FastProductCreateDialogBoxViewModel(ISnackbar snackbar, IRestWrappe
}
product.Cost *= 10;
var command = product.Adapt<CreateProductCommand>() with
{
BeDisplayed = true,
BrandId = brand.Id,
CategoryId = SelectedCategory.Id,
Files = files,
Specifications = specifications
};
var id = await restWrapper.CrudApiRest<Product, Guid>(Address.ProductController)
.Create(command, token);
var command = new CreateProductCommand(product.PersianName, product.EnglishName, product.Summery,
product.ExpertCheck,
product.Tags,
product.Warranty,
true,
product.Cost,
product.PackingCost,
product.Stock,
product.HasExpressDelivery,
product.MaxOrderCount,
product.IsSpecialOffer,
brand.Id,
SelectedCategory.Id,
null,
specifications,
files,
new Dictionary<string, string>(),
new Dictionary<string, string>());
await restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Create<CreateProductCommand>(command, token);
}
catch (ApiException ex)
{
if (ex.StatusCode != HttpStatusCode.OK)
snackbar.Add(ex.Message, Severity.Error);
}
catch (Exception e)

View File

@ -182,6 +182,59 @@
<MudGrid>
<MudItem xs="12">
<MudText Typo="Typo.h6">اطلاعات متا تگ</MudText>
<MudText Typo="Typo.caption">می توانید متا تگ های سئو برای صفحه مورد نظر را وارد کنید</MudText>
<MudGrid>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.MetaTagType" T="string" Label="نوع" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="5" md="6">
<MudTextField @bind-Value="@ViewModel.MetaTagValue" T="string" Label="مقدار" Variant="Variant.Outlined"></MudTextField>
</MudItem>
<MudItem lg="2" md="12">
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Color="Color.Info"
class="mt-2 w-full py-3"
OnClick="ViewModel.AddMetaTag"
StartIcon="@Icons.Material.Outlined.Add">افزودن</MudButton>
</MudItem>
<MudItem sm="12">
<MudDataGrid Items="@ViewModel.MetaTags"
T="MetaTagSDto"
Elevation="0"
Outlined="true"
Bordered="true"
Striped="true"
Filterable="false"
SortMode="@SortMode.None"
Groupable="false">
<Columns>
<PropertyColumn T="MetaTagSDto" TProperty="string" Property="x => x.Type" Title="نوع"/>
<PropertyColumn T="MetaTagSDto" TProperty="string" Property="x => x.Value" Title="مقدار"/>
<TemplateColumn T="MetaTagSDto" CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row>
<MudButton DisableElevation="true"
Size="@Size.Small"
Variant="@Variant.Filled"
OnClick="() => ViewModel.MetaTags.Remove(context.Item)"
Color="@Color.Error"
StartIcon="@Icons.Material.Outlined.Delete">حذف</MudButton>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
</MudItem>
</MudGrid>
<MudText Typo="Typo.h6">سوالات متداول</MudText>
<MudText Typo="Typo.caption">می توانید سوالات متداول شهر موردنظر را وارد کنید</MudText>
<MudGrid class="mt-1">
@ -215,7 +268,7 @@
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Error"
OnClick="()=>ViewModel.Faqs.Remove(item.Key)" />
OnClick="() => ViewModel.Faqs.Remove(item.Key)"/>
<MudText>@item.Key</MudText>
</MudStack>
</TitleContent>

View File

@ -1,4 +1,6 @@
namespace Netina.AdminPanel.PWA.Dialogs;
using Netina.Domain.Entities.Seo;
namespace Netina.AdminPanel.PWA.Dialogs;
public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
{
@ -89,6 +91,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
PageDto = productLDto;
productLDto.Specifications.ForEach(s => Specifications.Add(s));
productLDto.Files.ForEach(f => Files.Add(f));
productLDto.MetaTags.ForEach(m => MetaTags.Add(m));
SelectedCategory = new ProductCategorySDto { Id = productLDto.CategoryId, Name = productLDto.CategoryName };
SelectedBrand = new BrandSDto { Id = productLDto.BrandId, PersianName = productLDto.BrandName };
PageDto.IsSpecialOffer = productLDto.IsSpecialOffer;
@ -193,7 +196,7 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
PageDto.Specifications,
PageDto.Files,
Faqs,
new Dictionary<string, string>());
MetaTags.ToDictionary(x => x.Type, x => x.Value));
await _restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Update<UpdateProductCommand>(request, token);
_snackbar.Add($"ویرایش محصول {PageDto.PersianName} با موفقیت انجام شد", Severity.Success);
@ -258,8 +261,8 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
PageDto.Specifications,
PageDto.Files,
Faqs,
new Dictionary<string, string>());
await _restWrapper.CrudApiRest<Product, Guid>(Address.ProductController).Create<CreateProductCommand>(request, token);
MetaTags.ToDictionary(x => x.Type, x => x.Value));
await _restWrapper.CrudDtoApiRest<Product, ProductLDto, Guid>(Address.ProductController).Create<CreateProductCommand>(request, token);
_snackbar.Add($"ساخت محصول {PageDto.PersianName} با موفقیت انجام شد", Severity.Success);
_mudDialog.Close(DialogResult.Ok(true));
@ -376,6 +379,25 @@ public class ProductActionDialogBoxViewModel : BaseViewModel<ProductLDto>
}
}
public readonly ObservableCollection<MetaTagSDto> MetaTags = new();
public string MetaTagType { get; set; } = string.Empty;
public string MetaTagValue { get; set; } = string.Empty;
public void AddMetaTag()
{
try
{
if (MetaTagType.IsNullOrEmpty())
throw new Exception("لطفا نوع متا مورد نظر را وارد کنید");
if (MetaTagValue.IsNullOrEmpty())
throw new Exception("لطفا مقدار متا مورد نظر را وارد کنید");
MetaTags.Add(new MetaTagSDto() { Type = MetaTagType, Value = MetaTagValue });
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
}
public void AddSpecification()
{

View File

@ -115,7 +115,7 @@ public class StorageDialogBoxViewModel : BaseViewModel
IsProcessing = true;
using var memoryStream = new MemoryStream();
var file = obj.File;
var stream = file.OpenReadStream();
var stream = file.OpenReadStream(8000000);
await stream.CopyToAsync(memoryStream);
var fileUpload = new FileUploadRequest

View File

@ -5,8 +5,8 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<AssemblyVersion>1.6.18.28</AssemblyVersion>
<FileVersion>1.6.18.28</FileVersion>
<AssemblyVersion>1.7.20.34</AssemblyVersion>
<FileVersion>1.7.20.34</FileVersion>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
</PropertyGroup>

View File

@ -1,10 +1,13 @@
@page "/product/brands"
@using StringExtensions = Netina.Common.Extensions.StringExtensions
@inject IDialogService DialogService
@inject NavigationManager NavigationManager
@inject IRestWrapper RestWrapper
@inject ISnackbar Snackbar
@inject IUserUtility UserUtility
@inject IConfiguration Configuration
@inject IJSRuntime JsRuntime
<MudStack class="h-full w-full p-8">
@ -25,7 +28,7 @@
<MudPaper>
<MudDataGrid Striped="true" T="BrandSDto" Items="@ViewModel.PageDto" Filterable="false" Loading="@ViewModel.IsProcessing"
CurrentPage="@ViewModel.CurrentPage"
RowsPerPage="15"
RowsPerPage="10"
SortMode="@SortMode.None" Groupable="false">
<ToolBarContent>
<MudTextField T="string" Placeholder="جست جو بر اساس نام" Adornment="Adornment.Start" Immediate="true"
@ -37,7 +40,6 @@
<Columns>
<PropertyColumn Title="نام برند" Property="arg => arg.PersianName" />
<PropertyColumn Title="نام انگلیسی برند" Property="arg => arg.EnglishName" />
<PropertyColumn Title="توضیحاتــ" Property="arg => arg.Description" />
<TemplateColumn Title="صفحه اختصاصی دارد" T="BrandSDto">
<CellTemplate>
@if (@context.Item.HasSpecialPage)
@ -54,15 +56,22 @@
<TemplateColumn CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Filled.Link"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Surface"
OnClick="async()=>await ShowBrand(context.Item)" />
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Info"
OnClick="async()=>await ViewModel.EditBrandAsync(context.Item)"></MudIconButton>
OnClick="async () => await ViewModel.EditBrandAsync(context.Item)"></MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
OnClick="async() => await ViewModel.DeleteBrandAsync(context.Item.Id)"
OnClick="async () => await ViewModel.DeleteBrandAsync(context.Item.Id)"
Color="@Color.Error"></MudIconButton>
</MudStack>
</CellTemplate>
@ -92,8 +101,13 @@
await base.OnInitializedAsync();
}
}
@code {
private async Task ShowBrand(BrandSDto item)
{
var webUrl = Configuration.GetValue<string>("WebSiteUrl") ?? string.Empty;
var slug = WebUtility.UrlEncode(item.Slug.Replace(' ', '-'));
var url = $"{webUrl}/brands/{item.Id}/{slug}";
await JsRuntime.InvokeVoidAsync("open", url, "_blank");
}
}

View File

@ -23,6 +23,8 @@ public class BrandsPageViewModel(
var dto = await restWrapper.CrudDtoApiRest<Brand, BrandSDto, Guid>(Address.BrandController)
.ReadAll(0);
PageDto = dto;
if (PageDto.Count == 10)
PageCount = 2;
}
catch (ApiException ex)
{
@ -108,7 +110,7 @@ public class BrandsPageViewModel(
PageDto.Clear();
var dto = await restWrapper.BrandRestApi.ReadAll(CurrentPage, Search);
dto.ForEach(d => PageDto.Add(d));
if (PageDto.Count == 20)
if (PageDto.Count == 10)
PageCount = 2;
}
catch (ApiException ex)
@ -149,7 +151,7 @@ public class BrandsPageViewModel(
}
dto.ForEach(d => PageDto.Add(d));
if (PageDto.Count % 20 == 0)
if (PageDto.Count % 10 == 0)
PageCount = CurrentPage + 2;
}

View File

@ -152,15 +152,17 @@ public class CategoriesPageViewModel(
public bool ChangeOriginalCategoryVisibility() => OriginalCategoryVisibility = !OriginalCategoryVisibility;
public void AddFastProductCategory(string categoryName, Guid? parentId = null)
{
if(categoryName.IsNullOrEmpty())
if (categoryName.IsNullOrEmpty())
return;
ProductCategorySDto category = new ProductCategorySDto { Name = categoryName, IsMain = true};
ProductCategorySDto category = new ProductCategorySDto { Name = categoryName, IsMain = true };
if (parentId != null)
{
category.IsMain = false;
category.ParentId = parentId.Value;
}
var command = category.Adapt<CreateProductCategoryCommand>() with{Files = new()};
var command = new CreateProductCategoryCommand(category.Name, category.Description, category.IsMain, category.ParentId, new(), new Dictionary<string, string>(), new Dictionary<string, string>());
Task.Run(async () =>
{

View File

@ -7,6 +7,8 @@
@inject IUserUtility UserUtility
@inject IRestWrapper RestWrapper
@inject IBrowserViewportService BrowserViewportService
@inject IConfiguration Configuration
@inject IJSRuntime JsRuntime
<MudStack class="h-full w-full p-8">
<MudGrid>
@ -180,6 +182,12 @@
<TemplateColumn CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Filled.Link"
Size="@Size.Small"
Variant="@Variant.Outlined"
Color="@Color.Surface"
OnClick="async()=>await ShowProduct(context.Item)" />
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="@Size.Small"
Variant="@Variant.Outlined"
@ -217,4 +225,12 @@
await ViewModel.InitializeAsync();
await base.OnInitializedAsync();
}
private async Task ShowProduct(ProductSDto item)
{
var webUrl = Configuration.GetValue<string>("WebSiteUrl") ?? string.Empty;
var slug = WebUtility.UrlEncode(item.Slug.Replace(' ', '-'));
var url = $"{webUrl}/products/{item.Id}/{slug}";
await JsRuntime.InvokeVoidAsync("open", url, "_blank");
}
}

View File

@ -24,7 +24,7 @@ public interface ICrudApiRest<T, TKey> where T : class
public interface ICrudDtoApiRest<T, TDto, in TKey> where T : class where TDto : class
{
[Post("")]
Task Create([Body] T payload, [Header("Authorization")] string authorization);
Task Create<TCreateCommand>([Body] TCreateCommand payload, [Header("Authorization")] string authorization);
[Post("")]
Task Create([Body] TDto payload, [Header("Authorization")] string authorization);

View File

@ -16,9 +16,9 @@
//"WebSiteUrl": "https://bonsaigallery.shop",
//"AdminPanelBaseUrl": "https://admin.bonsaigallery.shop",
//"StorageBaseUrl": "https://storage.bonsaigallery.shop",
//"StorageBaseUrl": "https://storage.bonsaigallery.shop/",
//"ApiUrl": "https://api.bonsaigallery.shop/api",
"IsShop": true
//"IsShop": true
//"WebSiteUrl": "https://hamyanedalat.com",
//"AdminPanelBaseUrl": "https://admin.hamyanedalat.com",

View File

@ -0,0 +1,118 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ServiceDiscovery;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace Microsoft.Extensions.Hosting;
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
// This project should be referenced by each service project in your solution.
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.AddServiceDiscovery();
});
// Uncomment the following to restrict the allowed schemes for service discovery.
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
// {
// options.AllowedSchemes = ["https"];
// });
return builder;
}
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation();
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
//{
// builder.Services.AddOpenTelemetry()
// .UseAzureMonitor();
//}
return builder;
}
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder;
}
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (app.Environment.IsDevelopment())
{
// All health checks must pass for app to be considered ready to accept traffic after starting
app.MapHealthChecks("/health");
// Only health checks tagged with the "live" tag must pass for app to be considered alive
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.10.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.2.2" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
</ItemGroup>
</Project>