build: 升级目标框架到net10.0并完善模板配置

1. 将Hua.Cli.Tests、项目共享库、模块项目等所有项目的TargetFramework从net8.0/net9.0升级到net10.0
2. 完善dotnet-tools.json、模板配置文件,添加必要的构建和发布配置
3. 新增微服务、网关、应用程序的启动脚本和基础配置文件
4. 补充产品管理模块的领域、应用、Web层基础代码和多语言资源
This commit is contained in:
ShaoHua
2026-07-16 02:43:29 +08:00
parent 2a6f5f81b7
commit 734b2c91f9
264 changed files with 7132 additions and 451 deletions
@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
namespace ProductManagement;
public class CreateProductDto
{
[Required]
[StringLength(ProductConsts.MaxCodeLength)]
public string Code { get; set; } = string.Empty;
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; } = string.Empty;
[StringLength(ProductConsts.MaxImageNameLength)]
public string? ImageName { get; set; }
public float Price { get; set; }
public int StockCount { get; set; }
}
@@ -1,17 +1,21 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace ProductManagement;
public interface IProductAppService :
ICrudAppService<ProductDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateProductDto>
public interface IProductAppService : IApplicationService
{
}
Task<PagedResultDto<ProductDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input);
public class CreateUpdateProductDto
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int StockCount { get; set; }
Task<ListResultDto<ProductDto>> GetListAsync();
Task<ProductDto> GetAsync(Guid id);
Task<ProductDto> CreateAsync(CreateProductDto input);
Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input);
Task DeleteAsync(Guid id);
}
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace ProductManagement;
public interface IPublicProductAppService : IApplicationService
{
Task<ListResultDto<ProductDto>> GetListAsync();
}
@@ -0,0 +1,10 @@
{
"culture": "cs",
"texts": {
"Permission:ProductManagement": "Správa produktů",
"Permission:Products": "Produkty",
"Permission:Edit": "Upravit",
"Permission:Delete": "Smazat",
"Permission:Create": "Vytvořit"
}
}
@@ -0,0 +1,10 @@
{
"culture": "en",
"texts": {
"Permission:ProductManagement": "Product Management",
"Permission:Products": "Products",
"Permission:Edit": "Edit",
"Permission:Delete": "Delete",
"Permission:Create": "Create"
}
}
@@ -0,0 +1,10 @@
{
"culture": "pl-PL",
"texts": {
"Permission:ProductManagement": "Zarządzanie produktami",
"Permission:Products": "Produkty",
"Permission:Edit": "Edytuj",
"Permission:Delete": "Usuń",
"Permission:Create": "Utwórz"
}
}
@@ -0,0 +1,10 @@
{
"culture": "tr",
"texts": {
"Permission:ProductManagement": "Ürün Yönetimi",
"Permission:Products": "Ürünler",
"Permission:Edit": "Güncelle",
"Permission:Delete": "Sil",
"Permission:Create": "Oluştur"
}
}
@@ -0,0 +1,10 @@
{
"culture": "vi",
"texts": {
"Permission:ProductManagement": "Quản lý sản phẩm",
"Permission:Products": "Sản phẩm",
"Permission:Edit": "Sửa",
"Permission:Delete": "Xóa",
"Permission:Create": "Tạo"
}
}
@@ -3,9 +3,11 @@ using Volo.Abp.Application.Dtos;
namespace ProductManagement;
public class ProductDto : FullAuditedEntityDto<Guid>
public class ProductDto : AuditedEntityDto<Guid>
{
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public string? ImageName { get; set; }
public float Price { get; set; }
public int StockCount { get; set; }
}
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>netstandard2.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application" Version="9.0.0" />
<ProjectReference Include="..\ProductManagement.Domain.Shared\ProductManagement.Domain.Shared.csproj" />
@@ -1,6 +1,10 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
namespace ProductManagement;
[DependsOn(typeof(ProductManagementDomainSharedModule))]
[DependsOn(
typeof(ProductManagementDomainSharedModule),
typeof(AbpDddApplicationContractsModule)
)]
public class ProductManagementApplicationContractsModule : AbpModule { }
@@ -0,0 +1,23 @@
using ProductManagement.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace ProductManagement;
public class ProductManagementPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var productManagementGroup = context.AddGroup(ProductManagementPermissions.GroupName, L("Permission:ProductManagement"));
var products = productManagementGroup.AddPermission(ProductManagementPermissions.Products.Default, L("Permission:Products"));
products.AddChild(ProductManagementPermissions.Products.Update, L("Permission:Edit"));
products.AddChild(ProductManagementPermissions.Products.Delete, L("Permission:Delete"));
products.AddChild(ProductManagementPermissions.Products.Create, L("Permission:Create"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<ProductManagementResource>(name);
}
}
@@ -0,0 +1,21 @@
using Volo.Abp.Reflection;
namespace ProductManagement;
public class ProductManagementPermissions
{
public const string GroupName = "ProductManagement";
public static class Products
{
public const string Default = GroupName + ".Product";
public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update";
public const string Create = Default + ".Create";
}
public static string[] GetAll()
{
return ReflectionHelper.GetPublicConstantsRecursively(typeof(ProductManagementPermissions));
}
}
@@ -0,0 +1,17 @@
using Volo.Abp.Settings;
namespace ProductManagement;
public class ProductManagementSettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition(
ProductManagementSettings.MaxPageSize,
"100",
isVisibleToClients: true
)
);
}
}
@@ -0,0 +1,11 @@
namespace ProductManagement;
public static class ProductManagementSettings
{
public const string GroupName = "ProductManagement";
/// <summary>
/// Maximum allowed page size for paged list requests.
/// </summary>
public const string MaxPageSize = GroupName + ".MaxPageSize";
}
@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace ProductManagement;
public class UpdateProductDto
{
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; } = string.Empty;
[StringLength(ProductConsts.MaxImageNameLength)]
public string? ImageName { get; set; }
public float Price { get; set; }
public int StockCount { get; set; }
}
@@ -1,14 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq.Dynamic.Core;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace ProductManagement;
public class ProductAppService :
CrudAppService<Product, ProductDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateProductDto>,
IProductAppService
[Authorize(ProductManagementPermissions.Products.Default)]
public class ProductAppService : ApplicationService, IProductAppService
{
public ProductAppService(IRepository<Product, Guid> repository)
: base(repository) { }
private readonly ProductManager _productManager;
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(ProductManager productManager, IRepository<Product, Guid> productRepository)
{
_productManager = productManager;
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input)
{
await NormalizeMaxResultCountAsync(input);
var queryable = await _productRepository.GetQueryableAsync();
var products = await queryable
.OrderBy(input.Sorting ?? "Name")
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
.ToListAsync();
var totalCount = await _productRepository.GetCountAsync();
var dtos = ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
return new PagedResultDto<ProductDto>(totalCount, dtos);
}
public async Task<ListResultDto<ProductDto>> GetListAsync()
{
var products = await _productRepository.GetListAsync();
var productList = ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
return new ListResultDto<ProductDto>(productList);
}
public async Task<ProductDto> GetAsync(Guid id)
{
var product = await _productRepository.GetAsync(id);
return ObjectMapper.Map<Product, ProductDto>(product);
}
[Authorize(ProductManagementPermissions.Products.Create)]
public async Task<ProductDto> CreateAsync(CreateProductDto input)
{
var product = await _productManager.CreateAsync(
input.Code,
input.Name,
input.Price,
input.StockCount,
input.ImageName
);
return ObjectMapper.Map<Product, ProductDto>(product);
}
[Authorize(ProductManagementPermissions.Products.Update)]
public async Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
{
var product = await _productRepository.GetAsync(id);
product.SetName(input.Name);
product.SetPrice(input.Price);
product.SetStockCount(input.StockCount);
product.SetImageName(input.ImageName);
return ObjectMapper.Map<Product, ProductDto>(product);
}
[Authorize(ProductManagementPermissions.Products.Delete)]
public async Task DeleteAsync(Guid id)
{
await _productRepository.DeleteAsync(id);
}
private async Task NormalizeMaxResultCountAsync(PagedAndSortedResultRequestDto input)
{
var maxPageSize = (await SettingProvider.GetOrNullAsync(ProductManagementSettings.MaxPageSize))?.To<int>();
if (maxPageSize.HasValue && input.MaxResultCount > maxPageSize.Value)
{
input.MaxResultCount = maxPageSize.Value;
}
}
}
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AutoMapper" Version="9.0.0" />
<ProjectReference Include="..\ProductManagement.Application.Contracts\ProductManagement.Application.Contracts.csproj" />
@@ -0,0 +1,11 @@
using AutoMapper;
namespace ProductManagement;
public class ProductManagementApplicationAutoMapperProfile : Profile
{
public ProductManagementApplicationAutoMapperProfile()
{
CreateMap<Product, ProductDto>();
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace ProductManagement;
public class PublicProductAppService : ApplicationService, IPublicProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public PublicProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<ListResultDto<ProductDto>> GetListAsync()
{
return new ListResultDto<ProductDto>(
ObjectMapper.Map<List<Product>, List<ProductDto>>(
await _productRepository.GetListAsync()
)
);
}
}
@@ -0,0 +1,9 @@
using Volo.Abp.Localization;
namespace ProductManagement.Localization;
[LocalizationResourceName("ProductManagement")]
public class ProductManagementResource
{
}
@@ -0,0 +1,8 @@
namespace ProductManagement;
public static class ProductConsts
{
public const int MaxCodeLength = 32;
public const int MaxNameLength = 256;
public const int MaxImageNameLength = 128;
}
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>netstandard2.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Localization" Version="9.0.0" />
</ItemGroup>
@@ -0,0 +1,6 @@
namespace ProductManagement;
public static class ProductManagementDomainErrorCodes
{
//Add your business exception error codes here...
}
@@ -1,7 +1,17 @@
using Volo.Abp.Modularity;
using ProductManagement.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
namespace ProductManagement;
[DependsOn(typeof(AbpLocalizationModule))]
public class ProductManagementDomainSharedModule : AbpModule { }
public class ProductManagementDomainSharedModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpLocalizationOptions>(options =>
{
options.Resources.Add<ProductManagementResource>("en");
});
}
}
@@ -0,0 +1,25 @@
using System;
using Volo.Abp.Domain.Entities.Events.Distributed;
namespace ProductManagement;
[Serializable]
public class ProductStockCountChangedEto : EtoBase
{
public Guid Id { get; }
public int OldCount { get; set; }
public int CurrentCount { get; set; }
private ProductStockCountChangedEto()
{
}
public ProductStockCountChangedEto(Guid id, int oldCount, int currentCount)
{
Id = id;
OldCount = oldCount;
CurrentCount = currentCount;
}
}
@@ -0,0 +1,5 @@
{
"culture": "cs",
"texts": {
}
}
@@ -0,0 +1,6 @@
{
"culture": "en",
"texts": {
}
}
@@ -0,0 +1,6 @@
{
"culture": "pl-PL",
"texts": {
}
}
@@ -0,0 +1 @@
{"culture":"vi","texts":{}}
@@ -0,0 +1,5 @@
{
"culture": "vi",
"texts": {
}
}
@@ -1,11 +1,120 @@
using System;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
namespace ProductManagement;
public class Product : FullAuditedAggregateRoot<Guid>
public class Product : AuditedAggregateRoot<Guid>
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int StockCount { get; set; }
[NotNull]
public string Code { get; private set; }
[NotNull]
public string Name { get; private set; }
public float Price { get; private set; }
public int StockCount { get; private set; }
public string? ImageName { get; private set; }
private Product()
{
}
internal Product(
Guid id,
[NotNull] string code,
[NotNull] string name,
float price = 0.0f,
int stockCount = 0,
string? imageName = null)
{
Check.NotNullOrWhiteSpace(code, nameof(code));
if (code.Length >= ProductConsts.MaxCodeLength)
{
throw new ArgumentException($"Product code can not be longer than {ProductConsts.MaxCodeLength}");
}
Id = id;
Code = code;
SetName(Check.NotNullOrWhiteSpace(name, nameof(name)));
SetPrice(price);
SetImageName(imageName);
SetStockCountInternal(stockCount, triggerEvent: false);
}
public Product SetName([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
if (name.Length >= ProductConsts.MaxNameLength)
{
throw new ArgumentException($"Product name can not be longer than {ProductConsts.MaxNameLength}");
}
Name = name;
return this;
}
public Product SetImageName([CanBeNull] string? imageName)
{
if (imageName == null)
{
return this;
}
if (imageName.Length >= ProductConsts.MaxImageNameLength)
{
throw new ArgumentException($"Product image name can not be longer than {ProductConsts.MaxImageNameLength}");
}
ImageName = imageName;
return this;
}
public Product SetPrice(float price)
{
if (price < 0.0f)
{
throw new ArgumentException($"{nameof(price)} can not be less than 0.0!");
}
Price = price;
return this;
}
public Product SetStockCount(int stockCount)
{
return SetStockCountInternal(stockCount);
}
private Product SetStockCountInternal(int stockCount, bool triggerEvent = true)
{
if (StockCount < 0)
{
throw new ArgumentException($"{nameof(stockCount)} can not be less than 0!");
}
if (StockCount == stockCount)
{
return this;
}
if (triggerEvent)
{
AddDistributedEvent(
new ProductStockCountChangedEto(
Id,
StockCount,
stockCount
)
);
}
StockCount = stockCount;
return this;
}
}
@@ -0,0 +1,12 @@
using Volo.Abp;
namespace ProductManagement;
public class ProductCodeAlreadyExistsException : BusinessException
{
public ProductCodeAlreadyExistsException(string productCode)
: base("PM:000001", $"A product with code {productCode} has already exists!")
{
}
}
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="9.0.0" />
<ProjectReference Include="..\ProductManagement.Domain.Shared\ProductManagement.Domain.Shared.csproj" />
@@ -0,0 +1,8 @@
namespace ProductManagement;
public static class ProductManagementConsts
{
public const string DefaultDbTablePrefix = "Pm";
public const string? DefaultDbSchema = null;
}
@@ -0,0 +1,43 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace ProductManagement;
public class ProductManager : DomainService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductManager(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<Product> CreateAsync(
[NotNull] string code,
[NotNull] string name,
float price = 0.0f,
int stockCount = 0,
string? imageName = null)
{
var existingProduct = await _productRepository.FirstOrDefaultAsync(p => p.Code == code);
if (existingProduct != null)
{
throw new ProductCodeAlreadyExistsException(code);
}
return await _productRepository.InsertAsync(
new Product(
GuidGenerator.Create(),
code,
name,
price,
stockCount,
imageName
)
);
}
}
@@ -0,0 +1,11 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace ProductManagement.EntityFrameworkCore;
[ConnectionStringName("ProductManagement")]
public interface IProductManagementDbContext : IEfCoreDbContext
{
DbSet<Product> Products { get; }
}
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
@@ -1,9 +1,10 @@
using Microsoft.EntityFrameworkCore;
using ProductManagement.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace ProductManagement.EntityFrameworkCore;
public class ProductManagementDbContext : AbpDbContext<ProductManagementDbContext>
public class ProductManagementDbContext : AbpDbContext<ProductManagementDbContext>, IProductManagementDbContext
{
public DbSet<Product> Products { get; set; }
@@ -13,10 +14,6 @@ public class ProductManagementDbContext : AbpDbContext<ProductManagementDbContex
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Product>(b =>
{
b.ToTable("PmProducts");
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
builder.ConfigureProductManagement();
}
}
@@ -0,0 +1,36 @@
using System;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace ProductManagement.EntityFrameworkCore;
public static class ProductManagementDbContextModelCreatingExtensions
{
public static void ConfigureProductManagement(
this ModelBuilder builder,
Action<ProductManagementModelBuilderConfigurationOptions>? optionsAction = null)
{
Check.NotNull(builder, nameof(builder));
var options = new ProductManagementModelBuilderConfigurationOptions();
optionsAction?.Invoke(options);
builder.Entity<Product>(b =>
{
b.ToTable(options.TablePrefix + "Products", options.Schema);
b.ConfigureConcurrencyStamp();
b.ConfigureExtraProperties();
b.ConfigureAudited();
b.Property(x => x.Code).IsRequired().HasMaxLength(ProductConsts.MaxCodeLength);
b.Property(x => x.Name).IsRequired().HasMaxLength(ProductConsts.MaxNameLength);
b.Property(x => x.ImageName).HasMaxLength(ProductConsts.MaxImageNameLength);
b.HasIndex(q => q.Code);
b.HasIndex(q => q.Name);
});
}
}
@@ -0,0 +1,15 @@
using JetBrains.Annotations;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace ProductManagement.EntityFrameworkCore;
public class ProductManagementModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
public ProductManagementModelBuilderConfigurationOptions(
[NotNull] string tablePrefix = ProductManagementConsts.DefaultDbTablePrefix,
[CanBeNull] string? schema = ProductManagementConsts.DefaultDbSchema)
: base(tablePrefix, schema)
{
}
}
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Http.Client" Version="9.0.0" />
<ProjectReference Include="..\ProductManagement.Application.Contracts\ProductManagement.Application.Contracts.csproj" />
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
<PropertyGroup><TargetFramework>net10.0</TargetFramework></PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="9.0.0" />
<ProjectReference Include="..\ProductManagement.Application.Contracts\ProductManagement.Application.Contracts.csproj" />
@@ -1,6 +1,10 @@
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace ProductManagement;
[DependsOn(typeof(ProductManagementApplicationContractsModule))]
[DependsOn(
typeof(ProductManagementApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule)
)]
public class ProductManagementHttpApiModule : AbpModule { }
@@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace ProductManagement;
[RemoteService]
[Area("productManagement")]
[Route("api/productManagement/products")]
public class ProductsController : AbpController, IProductAppService
{
private readonly IProductAppService _productAppService;
public ProductsController(IProductAppService productAppService)
{
_productAppService = productAppService;
}
[HttpGet]
[Route("")]
public Task<PagedResultDto<ProductDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input)
{
return _productAppService.GetListPagedAsync(input);
}
[HttpGet]
[Route("all")]
public Task<ListResultDto<ProductDto>> GetListAsync()
{
return _productAppService.GetListAsync();
}
[HttpGet]
[Route("{id}")]
public Task<ProductDto> GetAsync(Guid id)
{
return _productAppService.GetAsync(id);
}
[HttpPost]
public Task<ProductDto> CreateAsync(CreateProductDto input)
{
return _productAppService.CreateAsync(input);
}
[HttpPut]
[Route("{id}")]
public Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
{
return _productAppService.UpdateAsync(id, input);
}
[HttpDelete]
[Route("{id}")]
public Task DeleteAsync(Guid id)
{
return _productAppService.DeleteAsync(id);
}
}
@@ -0,0 +1,26 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace ProductManagement;
[RemoteService]
[Area("productManagement")]
[Route("api/productManagement/public/products")]
public class PublicProductsController : AbpController, IPublicProductAppService
{
private readonly IPublicProductAppService _publicProductAppService;
public PublicProductsController(IPublicProductAppService publicProductAppService)
{
_publicProductAppService = publicProductAppService;
}
[HttpGet]
public Task<ListResultDto<ProductDto>> GetListAsync()
{
return _publicProductAppService.GetListAsync();
}
}
@@ -0,0 +1,16 @@
{
"culture": "cs",
"texts": {
"Menu:ProductManagement": "Správa produktů",
"Menu:Products": "Produkty",
"ProductManagement": "Správa produktů",
"CreateANewProduct": "Vytvořit nový produkt",
"Products": "Produkty",
"StockCount": "Skladem",
"Code": "Kód",
"Name": "Název",
"Price": "Cena",
"ImageName": "Název obrázku",
"ProductDeletionWarningMessage": "Opravdu chcete smazat tento produkt?"
}
}
@@ -0,0 +1,16 @@
{
"culture": "en",
"texts": {
"Menu:ProductManagement": "Product Management",
"Menu:Products": "Products",
"ProductManagement": "Product Management",
"CreateANewProduct": "Create A New Product",
"Products": "Products",
"StockCount": "Stock Count",
"Code": "Code",
"Name": "Name",
"Price": "Price",
"ImageName": "Image Name",
"ProductDeletionWarningMessage": "Are you sure you want to delete this product?"
}
}
@@ -0,0 +1,16 @@
{
"culture": "pl-PL",
"texts": {
"Menu:ProductManagement": "Zarządzanie produktami",
"Menu:Products": "Produkty",
"ProductManagement": "Zarządzanie produktami",
"CreateANewProduct": "Utwórz nowy produkt",
"Products": "Produkty",
"StockCount": "Ilość na magazynie",
"Code": "Kod",
"Name": "Nazwa",
"Price": "Cena",
"ImageName": "Nazwa obrazka",
"ProductDeletionWarningMessage": "Czy jesteś pewien, że chcesz usunąć ten produkt?"
}
}
@@ -0,0 +1,16 @@
{
"culture": "tr",
"texts": {
"Menu:ProductManagement": "Ürün Yönetimi",
"Menu:Products": "Ürünler",
"ProductManagement": "Ürün Yönetimi",
"CreateANewProduct": "Yeni Bir Ürün Oluştur",
"Products": "Ürünler",
"StockCount": "Stok Sayısı",
"Code": "Kod",
"Name": "İsim",
"Price": "Fiyat",
"ImageName": "Fotoğraf İsmi",
"ProductDeletionWarningMessage": "Bu ürünü silmek istediğinize emin misiniz?"
}
}
@@ -0,0 +1,16 @@
{
"culture": "vi",
"texts": {
"Menu:ProductManagement": "Quản lý sản phẩm",
"Menu:Products": "Sản phẩm",
"ProductManagement": "Quản lý sản phẩm",
"CreateANewProduct": "Tạo một sản phẩm mới",
"Products": "Sản phẩm",
"StockCount": "Số chứng khoán",
"Code": "Mã",
"Name": "Tên",
"Price": "Giá",
"ImageName": "Tên ảnh",
"ProductDeletionWarningMessage": "Bạn có chắc chắn muốn xóa sản phẩm này?"
}
}
@@ -0,0 +1,20 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement.Localization
@model ProductManagement.Pages.ProductManagement.Products.CreateModel
@inject IHtmlLocalizer<ProductManagementResource> L
@{
Layout = null;
}
<abp-dynamic-form submit-button="false" abp-model="Product" asp-page="/ProductManagement/Products/Create">
<abp-modal size="@(AbpModalSize.Large)">
<abp-modal-header title="@L["Create"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)">
</abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace ProductManagement.Pages.ProductManagement.Products;
public class CreateModel : AbpPageModel
{
private readonly IProductAppService _productAppService;
[BindProperty]
public ProductCreateViewModel Product { get; set; } = new ProductCreateViewModel();
public CreateModel(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task<IActionResult> OnPostAsync()
{
var createProductDto = ObjectMapper.Map<ProductCreateViewModel, CreateProductDto>(Product);
await _productAppService.CreateAsync(createProductDto);
return NoContent();
}
public class ProductCreateViewModel
{
[Required]
[StringLength(ProductConsts.MaxCodeLength)]
public string Code { get; set; } = string.Empty;
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; } = string.Empty;
[StringLength(ProductConsts.MaxImageNameLength)]
public string? ImageName { get; set; }
public float Price { get; set; }
public int StockCount { get; set; }
}
}
@@ -0,0 +1,20 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement.Localization
@model ProductManagement.Pages.ProductManagement.Products.EditModel
@inject IHtmlLocalizer<ProductManagementResource> L
@{
Layout = null;
}
<abp-dynamic-form submit-button="false" abp-model="Product" asp-page="/ProductManagement/Products/Edit">
<abp-modal size="@(AbpModalSize.Large)">
<abp-modal-header title="@L["Edit"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)">
</abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
@@ -0,0 +1,55 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace ProductManagement.Pages.ProductManagement.Products;
public class EditModel : AbpPageModel
{
private readonly IProductAppService _productAppService;
[BindProperty]
public ProductEditViewModel Product { get; set; } = new ProductEditViewModel();
public EditModel(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task<ActionResult> OnGetAsync(Guid productId)
{
var productDto = await _productAppService.GetAsync(productId);
Product = ObjectMapper.Map<ProductDto, ProductEditViewModel>(productDto);
return Page();
}
public async Task OnPostAsync()
{
await _productAppService.UpdateAsync(Product.Id, new UpdateProductDto()
{
Name = Product.Name,
Price = Product.Price,
StockCount = Product.StockCount
});
}
public class ProductEditViewModel
{
[HiddenInput]
[Required]
public Guid Id { get; set; }
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; } = string.Empty;
[StringLength(ProductConsts.MaxImageNameLength)]
public string? ImageName { get; set; }
public float Price { get; set; }
public int StockCount { get; set; }
}
}
@@ -0,0 +1,47 @@
@page
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement
@using ProductManagement.Localization
@model ProductManagement.Pages.ProductManagement.Products.IndexModel
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject IThemeManager ThemeManager
@inject IAuthorizationService Authorization
@inject IHtmlLocalizer<ProductManagementResource> L
@{
ViewBag.PageTitle = "Products";
Layout = ThemeManager.CurrentTheme.GetApplicationLayout();
}
@section scripts {
<abp-script src="/Pages/ProductManagement/Products/index.js" />
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<h2>@L["Products"]</h2>
</abp-column>
<abp-column size-md="_6" class="text-right">
@if (await Authorization.IsGrantedAsync(ProductManagementPermissions.Products.Create))
{
<abp-button icon="plus" text="@L["CreateANewProduct"].Value" button-type="Primary" id="CreateNewProductButtonId"></abp-button>
}
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="ProductsTable" class="nowrap">
<thead>
<tr>
<th>@L["Actions"]</th>
<th>@L["Code"]</th>
<th>@L["Name"]</th>
<th>@L["Price"]</th>
<th>@L["StockCount"]</th>
</tr>
</thead>
</abp-table>
</abp-card-body>
</abp-card>
@@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace ProductManagement.Pages.ProductManagement.Products;
public class IndexModel : AbpPageModel
{
public async Task OnGetAsync()
{
}
}
@@ -0,0 +1,76 @@
$(function () {
var l = abp.localization.getResource('ProductManagement');
var _createModal = new abp.ModalManager(abp.appPath + 'ProductManagement/Products/Create');
var _editModal = new abp.ModalManager(abp.appPath + 'ProductManagement/Products/Edit');
var _dataTable = $('#ProductsTable').DataTable(abp.libs.datatables.normalizeConfiguration({
processing: true,
serverSide: true,
paging: true,
searching: false,
autoWidth: false,
scrollCollapse: true,
order: [[1, "desc"]],
ajax: abp.libs.datatables.createAjax(productManagement.products.getListPaged),
columnDefs: [
{
rowAction: {
items:
[
{
text: l('Edit'),
visible: abp.auth.isGranted('ProductManagement.Product.Update'),
action: function (data) {
_editModal.open({
productId: data.record.id
});
}
},
{
text: l('Delete'),
visible: abp.auth.isGranted('ProductManagement.Product.Delete'),
confirmMessage: function (data) { return l('ProductDeletionWarningMessage'); },
action: function (data) {
productManagement.products
.delete(data.record.id)
.then(function () {
_dataTable.ajax.reload();
});
}
}
]
}
},
{
target: 1,
data: "code"
},
{
target: 2,
data: "name"
},
{
target: 3,
data: "price"
},
{
target: 4,
data: "stockCount"
}
]
}));
$("#CreateNewProductButtonId").click(function () {
_createModal.open();
});
_createModal.onClose(function () {
_dataTable.ajax.reload();
});
_editModal.onResult(function () {
_dataTable.ajax.reload();
});
});
@@ -0,0 +1,4 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using ProductManagement.Localization;
using Volo.Abp.UI.Navigation;
namespace ProductManagement;
public class ProductManagementMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
await ConfigureMainMenu(context);
}
}
private async Task ConfigureMainMenu(MenuConfigurationContext context)
{
var l = context.GetLocalizer<ProductManagementResource>();
var rootMenuItem = new ApplicationMenuItem("ProductManagement", l["Menu:ProductManagement"]);
if (await context.IsGrantedAsync(ProductManagementPermissions.Products.Default))
{
rootMenuItem.AddItem(new ApplicationMenuItem("Products", l["Menu:Products"], "/ProductManagement/Products"));
}
context.Menu.AddItem(rootMenuItem);
}
}
@@ -0,0 +1,13 @@
using AutoMapper;
using ProductManagement.Pages.ProductManagement.Products;
namespace ProductManagement;
public class ProductManagementWebAutoMapperProfile : Profile
{
public ProductManagementWebAutoMapperProfile()
{
CreateMap<CreateModel.ProductCreateViewModel, CreateProductDto>();
CreateMap<ProductDto, EditModel.ProductEditViewModel>();
}
}
@@ -1,6 +1,59 @@
using Localization.Resources.AbpUi;
using Microsoft.Extensions.DependencyInjection;
using ProductManagement.Localization;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.UI.Navigation;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
namespace ProductManagement;
[DependsOn(typeof(ProductManagementHttpApiModule))]
public class ProductManagementWebModule : AbpModule { }
[DependsOn(
typeof(ProductManagementHttpApiModule),
typeof(AbpAspNetCoreMvcUiThemeSharedModule),
typeof(AbpAutoMapperModule)
)]
public class ProductManagementWebModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
options.AddAssemblyResource(typeof(ProductManagementResource), typeof(ProductManagementWebModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new ProductManagementMenuContributor());
});
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<ProductManagementWebModule>("ProductManagement");
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<ProductManagementResource>()
.AddBaseTypes(
typeof(AbpValidationResource),
typeof(AbpUiResource)
).AddVirtualJson("/Localization/Resources/ProductManagement");
});
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<ProductManagementWebAutoMapperProfile>(validate: true);
});
}
}
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44301",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"ProductManagement.Web": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:44301",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ProductManagement.Application\ProductManagement.Application.csproj" />
<ProjectReference Include="..\ProductManagement.Domain.Tests\ProductManagement.Domain.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,99 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using Xunit;
namespace ProductManagement
{
public class ProductAppService_Tests : ProductManagementApplicationTestBase
{
private readonly IProductAppService _productAppService;
private readonly IRepository<Product, Guid> _productRepository;
private readonly ProductManagementTestData _testData;
public ProductAppService_Tests()
{
_productAppService = GetRequiredService<IProductAppService>();
_productRepository = GetRequiredService<IRepository<Product, Guid>>();
_testData = GetRequiredService<ProductManagementTestData>();
}
[Fact]
public async Task GetListPagedAsync()
{
var result = await _productAppService.GetListPagedAsync(new PagedAndSortedResultRequestDto());
result.Items.Count.ShouldBeGreaterThan(0);
}
[Fact]
public async Task GetListAsync()
{
var result = await _productAppService.GetListAsync();
result.Items.Count.ShouldBeGreaterThan(0);
}
[Fact]
public async Task GetAsync()
{
var product = (await _productRepository.GetListAsync()).FirstOrDefault();
var result = await _productAppService.GetAsync(product.Id);
result.ShouldNotBeNull();
result.Name.ShouldBe(_testData.ProductName1);
result.Code.ShouldBe(_testData.ProductCode1);
result.Price.ShouldBe(_testData.ProductPrice1);
result.StockCount.ShouldBe(_testData.ProductStockCount1);
}
[Fact]
public async Task CreateAsync()
{
var result = await _productAppService.CreateAsync(new CreateProductDto()
{
Code = "Code",
Name = "Name",
Price = 15,
StockCount = 14
});
result.ShouldNotBeNull();
}
[Fact]
public async Task UpdateAsync()
{
var product = (await _productRepository.GetListAsync()).FirstOrDefault();
var result = await _productAppService.UpdateAsync(product.Id, new UpdateProductDto()
{
Name = nameof(Product.Name),
Price = 15,
StockCount = 14
});
result.ShouldNotBeNull();
result.Name.ShouldBe(nameof(Product.Name));
result.Price.ShouldBe(15);
result.StockCount.ShouldBe(14);
}
[Fact]
public async Task DeleteAsync()
{
var product = (await _productRepository.GetListAsync()).LastOrDefault();
await _productAppService.DeleteAsync(product.Id);
var result = await _productRepository.FindAsync(product.Id);
result.ShouldBeNull();
}
}
}
@@ -0,0 +1,24 @@
using System;
using ProductManagement.EntityFrameworkCore;
namespace ProductManagement
{
public abstract class ProductManagementApplicationTestBase : ProductManagementTestBase<ProductManagementApplicationTestModule>
{
protected virtual void UsingDbContext(Action<IProductManagementDbContext> action)
{
using (var dbContext = GetRequiredService<IProductManagementDbContext>())
{
action.Invoke(dbContext);
}
}
protected virtual T UsingDbContext<T>(Func<IProductManagementDbContext, T> action)
{
using (var dbContext = GetRequiredService<IProductManagementDbContext>())
{
return action.Invoke(dbContext);
}
}
}
}
@@ -0,0 +1,17 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace ProductManagement
{
[DependsOn(
typeof(ProductManagementApplicationModule),
typeof(ProductManagementDomainTestModule)
)]
public class ProductManagementApplicationTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ProductManagement.EntityFrameworkCore.Tests\ProductManagement.EntityFrameworkCore.Tests.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,54 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Uow;
namespace ProductManagement
{
public abstract class ProductManagementDomainTestBase : ProductManagementTestBase<ProductManagementDomainTestModule>
{
#region WithUnitOfWork
protected virtual Task WithUnitOfWorkAsync(Func<Task> func)
{
return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
}
protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func<Task> action)
{
using (var scope = ServiceProvider.CreateScope())
{
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using (var uow = uowManager.Begin(options))
{
await action();
await uow.CompleteAsync();
}
}
}
protected virtual Task<TResult> WithUnitOfWorkAsync<TResult>(Func<Task<TResult>> func)
{
return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
}
protected virtual async Task<TResult> WithUnitOfWorkAsync<TResult>(AbpUnitOfWorkOptions options, Func<Task<TResult>> func)
{
using (var scope = ServiceProvider.CreateScope())
{
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using (var uow = uowManager.Begin(options))
{
var result = await func();
await uow.CompleteAsync();
return result;
}
}
}
#endregion
}
}
@@ -0,0 +1,13 @@
using ProductManagement.EntityFrameworkCore;
using Volo.Abp.Modularity;
namespace ProductManagement
{
[DependsOn(
typeof(ProductManagementEntityFrameworkCoreTestModule)
)]
public class ProductManagementDomainTestModule : AbpModule
{
}
}
@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace ProductManagement
{
public class ProductManager_Tests : ProductManagementDomainTestBase
{
private readonly ProductManager _productManager;
public ProductManager_Tests()
{
_productManager = GetRequiredService<ProductManager>();
}
[Fact]
public async Task Should_Create_A_Valid_Product()
{
//Act
var product = await WithUnitOfWorkAsync(
async () =>
{
return await _productManager.CreateAsync("P000837212", "My Product 837212", stockCount: 42);
}
);
//Assert
product.Code.ShouldBe("P000837212");
product.Name.ShouldBe("My Product 837212");
product.Price.ShouldBe(0.0f);
product.StockCount.ShouldBe(42);
}
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ProductManagement.EntityFrameworkCore\ProductManagement.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\ProductManagement.TestBase\ProductManagement.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,43 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace ProductManagement.EntityFrameworkCore
{
[DependsOn(
typeof(ProductManagementTestBaseModule),
typeof(ProductManagementEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ProductManagementEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var sqliteConnection = CreateDatabaseAndGetConnection();
Configure<AbpDbContextOptions>(options =>
{
options.Configure(abpDbContextConfigurationContext =>
{
abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection);
});
});
}
private static SqliteConnection CreateDatabaseAndGetConnection()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
new ProductManagementDbContext(
new DbContextOptionsBuilder<ProductManagementDbContext>().UseSqlite(connection).Options
).GetService<IRelationalDatabaseCreator>().CreateTables();
return connection;
}
}
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Autofac" Version="9.0.0" />
<PackageReference Include="Volo.Abp.Authorization" Version="9.0.0" />
<PackageReference Include="Volo.Abp.TestBase" Version="9.0.0" />
<ProjectReference Include="..\..\src\ProductManagement.Domain\ProductManagement.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="4.2.2" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.6.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2" />
</ItemGroup>
</Project>
@@ -0,0 +1,15 @@
using Volo.Abp;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
namespace ProductManagement
{
public abstract class ProductManagementTestBase<TStartupModule> : AbpIntegratedTest<TStartupModule>
where TStartupModule : IAbpModule
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}
@@ -0,0 +1,43 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Authorization;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
using Volo.Abp.Uow;
namespace ProductManagement
{
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpTestBaseModule),
typeof(AbpAuthorizationModule),
typeof(ProductManagementDomainModule)
)]
public class ProductManagementTestBaseModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
SeedTestData(context);
}
private static void SeedTestData(ApplicationInitializationContext context)
{
using (var scope = context.ServiceProvider.CreateScope())
{
scope.ServiceProvider
.GetRequiredService<ProductManagementTestDataBuilder>()
.Build();
}
}
}
}
@@ -0,0 +1,23 @@
using Volo.Abp.DependencyInjection;
namespace ProductManagement
{
public class ProductManagementTestData : ISingletonDependency
{
public string ProductCode1 { get; } = "ProductCode1";
public string ProductName1 { get; } = "ProductName1";
public float ProductPrice1 { get; } = 20;
public int ProductStockCount1 { get; } = 100;
public string ProductCode2 { get; } = "ProductCode2";
public string ProductName2 { get; } = "ProductName2";
public float ProductPrice2 { get; } = 30;
public int ProductStockCount2 { get; } = 110;
}
}
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace ProductManagement
{
public class ProductManagementTestDataBuilder : ITransientDependency
{
private ProductManagementTestData _testData;
private readonly ProductManager _productManager;
public ProductManagementTestDataBuilder(
ProductManagementTestData testData,
ProductManager productManager)
{
_testData = testData;
_productManager = productManager;
}
public void Build()
{
AsyncHelper.RunSync(BuildAsync);
}
[UnitOfWork]
public virtual async Task BuildAsync()
{
await _productManager.CreateAsync(_testData.ProductCode1, _testData.ProductName1, _testData.ProductPrice1, _testData.ProductStockCount1);
await _productManager.CreateAsync(_testData.ProductCode2, _testData.ProductName2, _testData.ProductPrice2, _testData.ProductStockCount2);
}
}
}