frist commit

This commit is contained in:
pscgyLancer 2022-04-07 14:00:58 +08:00
commit 0236e96843
62 changed files with 2806 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

341
.gitignore vendored Normal file
View File

@ -0,0 +1,341 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
CQRS_Simple.API/appsettings.Development.json

View File

@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<LangVersion>8</LangVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<!-- Set this to true if you enable server-side prerendering -->
<BuildServerSideRenderer>false</BuildServerSideRenderer>
<RootNamespace>CQRS_Simple.API</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.0.2" />
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="6.0.0" />
<PackageReference Include="Castle.Core" Version="4.4.1" />
<PackageReference Include="AutoMapper" Version="10.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="FluentValidation.AspNetCore" Version="9.2.0" />
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="MediatR.Extensions.FluentValidation.AspNetCore" Version="1.1.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.9" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.2.1" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CQRS_Simple.Domain\CQRS_Simple.Domain.csproj" />
<ProjectReference Include="..\CQRS_Simple.EntityFrameworkCore\CQRS_Simple.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\CQRS_Simple.Infrastructure\CQRS_Simple.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using Autofac;
using AutoMapper;
namespace CQRS_Simple.API.Modules
{
public class AutoMapperModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes(assemblies)
.Where(t => typeof(Profile).IsAssignableFrom(t) && !t.IsAbstract && t.IsPublic)
.As<Profile>();
builder.Register(c => new MapperConfiguration(cfg =>
{
//cfg.ConstructServicesUsing(ServiceConstructor);
foreach (var profile in c.Resolve<IEnumerable<Profile>>())
{
cfg.AddProfile(profile);
}
}))
.AsSelf()
.AutoActivate()
.SingleInstance();
builder.Register(c =>
{
// these are the changed lines
var scope = c.Resolve<ILifetimeScope>();
return new Mapper(c.Resolve<MapperConfiguration>(), scope.Resolve);
})
.As<IMapper>()
.SingleInstance();
}
}
}

View File

@ -0,0 +1,14 @@
using AutoMapper;
using CQRS_Simple.API.Products.Dtos;
using CQRS_Simple.Domain.Products;
namespace CQRS_Simple.Modules
{
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Product, ProductDto>();
}
}
}

View File

@ -0,0 +1,62 @@
using Autofac;
using Autofac.Extras.DynamicProxy;
using CQRS_Simple.EntityFrameworkCore;
using CQRS_Simple.Infrastructure;
using CQRS_Simple.Infrastructure.Dapper;
using CQRS_Simple.Infrastructure.MQ;
using CQRS_Simple.Infrastructure.Uow;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CQRS_Simple.Modules
{
public class InfrastructureModule : Autofac.Module
{
private readonly string _databaseConnectionString;
// private readonly ILoggerFactory _loggerFactory;
public InfrastructureModule(string databaseConnectionString
)
{
this._databaseConnectionString = databaseConnectionString;
// _loggerFactory = loggerFactory;
}
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RabbitMQClient>().SingleInstance();
builder.Register(c => new SqlConnectionFactory(_databaseConnectionString))
.As<ISqlConnectionFactory>()
// .WithParameter("connectionString", _databaseConnectionString)
.InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(DapperRepository<,>)).As(typeof(IDapperRepository<,>))
.InstancePerLifetimeScope();
var dbBuild = new DbContextOptionsBuilder<SimpleDbContext>();
dbBuild.UseMySql(_databaseConnectionString, ServerVersion.Parse("5.7.31-mysql"));
//dbBuild.UseSqlServer(_databaseConnectionString);
// dbBuild.UseLoggerFactory(_loggerFactory);
builder.Register(c => new SimpleDbContext(dbBuild.Options))
.As<DbContext>()
.InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>()
.As<IUnitOfWork>()
.InstancePerLifetimeScope()
.OnRelease(instance => instance.CleanUp())
;
builder.RegisterGeneric(typeof(Repository<,>)).As(typeof(IRepository<,>))
.InstancePerLifetimeScope()
.InterceptedBy(typeof(CallLogger))
.EnableInterfaceInterceptors();
;
}
}
}

View File

@ -0,0 +1,19 @@
using Autofac;
using CQRS_Simple.Infrastructure;
namespace CQRS_Simple.API.Modules
{
public class IocManagerModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c =>
{
var scope = c.Resolve<ILifetimeScope>();
return new IocManager(scope);
})
.As<IIocManager>()
;
}
}
}

View File

@ -0,0 +1,18 @@
using Autofac;
using Microsoft.Extensions.Logging;
namespace CQRS_Simple.API.Modules
{
public class LoggerModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterInstance(new LoggerFactory())
.As<ILoggerFactory>();
builder.RegisterGeneric(typeof(Logger<>))
.As(typeof(ILogger<>))
.SingleInstance();
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Core;
using Autofac.Features.Variance;
using CQRS_Simple.API.PipelineBehaviors;
using FluentValidation;
using MediatR;
using MediatR.Pipeline;
namespace CQRS_Simple.API.Modules
{
public class MediatorModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterSource(new ScopedContravariantRegistrationSource(
typeof(IRequestHandler<,>),
typeof(INotificationHandler<>),
typeof(IValidator<>)
));
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
var mediatrOpenTypes = new[] { typeof(IRequestHandler<,>), typeof(INotificationHandler<>), typeof(IValidator<>), };
foreach (var mediatrOpenType in mediatrOpenTypes)
{
builder
.RegisterAssemblyTypes(typeof(Startup).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
//Mediator validation Pipeline
builder.RegisterGeneric(typeof(ValidationBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.Register<ServiceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
// builder.RegisterGeneric(typeof(CommandValidationBehavior<,>)).As(typeof(IPipelineBehavior<,>));
}
public class ScopedContravariantRegistrationSource : IRegistrationSource
{
private readonly IRegistrationSource _source = new ContravariantRegistrationSource();
private readonly List<Type> _types = new List<Type>();
public ScopedContravariantRegistrationSource(params Type[] types)
{
if (types == null)
throw new ArgumentNullException(nameof(types));
if (!types.All(x => x.IsGenericTypeDefinition))
throw new ArgumentException("Supplied types should be generic type definitions");
_types.AddRange(types);
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
{
var components = _source.RegistrationsFor(service, registrationAccessor);
foreach (var c in components)
{
var defs = c.Target.Services
.OfType<TypedService>()
.Select(x => x.ServiceType.GetGenericTypeDefinition());
if (defs.Any(_types.Contains))
yield return c;
}
}
public bool IsAdapterForIndividualComponents => _source.IsAdapterForIndividualComponents;
}
}
}

View File

@ -0,0 +1,24 @@
using System.Threading.Tasks;
using CQRS_Simple.Infrastructure.MQ;
using Microsoft.Extensions.Options;
namespace CQRS_Simple
{
public class MyListener : RabbitListener
{
private readonly RabbitMQOptions _options;
public MyListener(IOptions<RabbitMQOptions> optionsAccessor)
: base(optionsAccessor)
{
_options = optionsAccessor.Value;
base.QueueName = _options.QueryName;
base.RouteKey = "Test.*";
}
public override async Task<bool> ProcessAsync(string message)
{
return await Task.FromResult(true);
}
}
}

View File

@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace CQRS_Simple.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@ -0,0 +1,3 @@
@using CQRS_Simple
@namespace CQRS_Simple.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using MediatR;
namespace CQRS_Simple.API.PipelineBehaviors
{
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return next();
}
}
}

View File

@ -0,0 +1,53 @@
using System.Threading;
using System.Threading.Tasks;
using CQRS_Simple.API.Products.Handlers;
using CQRS_Simple.Domain.Products;
using CQRS_Simple.Infrastructure.Dapper;
using CQRS_Simple.Infrastructure.MQ;
using CQRS_Simple.Infrastructure.Uow;
using FluentValidation;
using MediatR;
namespace CQRS_Simple.API.Products.Commands
{
public class CreateProductCommandValidate : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidate()
{
RuleFor(x => x.Product.Code).Length(10, 256);
}
}
public class CreateProductCommand : IRequest<int>
{
public Product Product { get; set; }
public CreateProductCommand(Product product)
{
Product = product;
}
public class CreateProductHandle : IRequestHandler<CreateProductCommand, int>
{
//private readonly IDapperRepository<Product, int> _dapperRepository;
private readonly IRepository<Product, int> _repository;
private readonly RabbitMQClient _mq;
public CreateProductHandle(RabbitMQClient mq, IRepository<Product, int> repository)
{
_mq = mq;
_repository = repository;
}
public async Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
_repository.Add(request.Product);
var result = 1;
_mq.PushMessage(new RabbitData(typeof(CreateProductCommand), request, result));
return result;
}
}
}
}

View File

@ -0,0 +1,11 @@
using MediatR;
namespace CQRS_Simple.API.Products.Commands
{
public class DeleteProductCommand : IRequest<int>
{
public int ProductId { get; set; }
public DeleteProductCommand(in int id) { ProductId = id; }
}
}

View File

@ -0,0 +1,11 @@
using CQRS_Simple.Domain.Products;
using MediatR;
namespace CQRS_Simple.API.Products.Commands
{
public class UpdateProductCommand : IRequest<int>
{
public Product Product { get; set; }
public UpdateProductCommand(Product product) { Product = product; }
}
}

View File

@ -0,0 +1,16 @@
using CQRS_Simple.Domain.Products;
namespace CQRS_Simple.API.Products.Dtos
{
/// <summary>
/// <see cref="Product"/>
/// </summary>
public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using System.Threading;
using System.Threading.Tasks;
using CQRS_Simple.API.Products.Commands;
using CQRS_Simple.Domain.Products;
using CQRS_Simple.Infrastructure.Dapper;
using CQRS_Simple.Infrastructure.MQ;
using MediatR;
namespace CQRS_Simple.API.Products.Handlers
{
public class DeleteProductHandle : IRequestHandler<DeleteProductCommand, int>
{
private readonly IDapperRepository<Product, int> _dapperRepository;
private readonly RabbitMQClient _mq;
public DeleteProductHandle(IDapperRepository<Product, int> dapperRepository, RabbitMQClient mq)
{
_dapperRepository = dapperRepository;
_mq = mq;
}
public async Task<int> Handle(DeleteProductCommand request, CancellationToken cancellationToken)
{
var result = await _dapperRepository.RemoveAsync(new Product() { Id = request.ProductId });
if (result > 0)
_mq.PushMessage(new RabbitData(typeof(DeleteProductCommand), request));
return result;
}
}
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using AutoMapper;
using CQRS_Simple.API.Products.Dtos;
using CQRS_Simple.API.Products.Queries;
using CQRS_Simple.Domain.Products;
using CQRS_Simple.Infrastructure.Uow;
using MediatR;
namespace CQRS_Simple.API.Products.Handlers
{
public class GetProductsQueryHandle : IRequestHandler<GetProductByIdQuery, ProductDto>,
IRequestHandler<GetProductsQuery, List<ProductDto>>
{
// private readonly IDapperRepository<Product, int> _dapperRepository;
private readonly IRepository<Product, int> _dapperRepository;
private readonly IMapper _mapper;
private readonly ILifetimeScope _container;
public GetProductsQueryHandle(IRepository<Product, int> dapperRepository,
IMapper mapper,
ILifetimeScope container
)
{
_dapperRepository = dapperRepository;
_mapper = mapper;
_container = container;
}
public virtual async Task<ProductDto> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
{
var result = await _dapperRepository.GetByIdAsync(request.ProductId);
var repository = _container.Resolve<IRepository<Product, int>>();
// var _repository = _iocManager.GetInstance<IRepository<Product, int>>();
repository.UnitOfWork.PrintKey();
var s = await repository.GetByIdAsync(request.ProductId);
s.Name += "2";
return result == null ? null : _mapper.Map<ProductDto>(result);
}
public async Task<List<ProductDto>> Handle(GetProductsQuery request, CancellationToken cancellationToken)
{
var result = await _dapperRepository.GetAllAsync();
return result == null ? new List<ProductDto>() : _mapper.Map<List<ProductDto>>(result);
}
}
}

View File

@ -0,0 +1,30 @@
using System.Threading;
using System.Threading.Tasks;
using CQRS_Simple.API.Products.Commands;
using CQRS_Simple.Domain.Products;
using CQRS_Simple.Infrastructure.Dapper;
using CQRS_Simple.Infrastructure.MQ;
using MediatR;
namespace CQRS_Simple.API.Products.Handlers
{
public class UpdateProductHandle : IRequestHandler<UpdateProductCommand, int>
{
private readonly IDapperRepository<Product, int> _dapperRepository;
private readonly RabbitMQClient _mq;
public UpdateProductHandle(IDapperRepository<Product, int> dapperRepository, RabbitMQClient mq)
{
_dapperRepository = dapperRepository;
_mq = mq;
}
public async Task<int> Handle(UpdateProductCommand request, CancellationToken cancellationToken)
{
var result = await _dapperRepository.UpdateAsync(request.Product);
if (result > 0)
_mq.PushMessage(new RabbitData(typeof(UpdateProductCommand), request));
return result;
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Autofac;
using CQRS_Simple.API.Products.Commands;
using CQRS_Simple.API.Products.Queries;
using CQRS_Simple.Domain.Products;
using CQRS_Simple.Domain.Products.Request;
using CQRS_Simple.Infrastructure;
using CQRS_Simple.Infrastructure.Uow;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Serilog;
namespace CQRS_Simple.API.Products
{
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
private readonly IIocManager _iocManager;
private readonly IUnitOfWork _unitOfWork;
public ProductsController(IMediator mediator, IIocManager iocManager, IUnitOfWork unitOfWork)
{
_mediator = mediator;
_iocManager = iocManager;
_unitOfWork = unitOfWork;
}
[HttpGet]
[Route("GetProduct")]
public async Task<IActionResult> GetProduct(int id)
{
var result = await _mediator.Send(new GetProductByIdQuery(id));
var _r1 = _unitOfWork.GetRepository<Product, int>();
_r1.UnitOfWork.PrintKey();
var _repository = _iocManager.GetInstance<IRepository<Product, int>>();
_repository.UnitOfWork.PrintKey();
var _repository2 = _iocManager.GetInstance<IRepository<Product, int>>();
_repository2.UnitOfWork.PrintKey();
var find = await _repository.GetByIdAsync(id);
if (find != null)
{
find.Name += "1_";
Log.Information(find?.Name);
await _unitOfWork.SaveChangesAsync();
}
// throw new Exception("ss");
return result != null ? (IActionResult)Ok(result) : NotFound();
}
[HttpGet]
[Route("GetAll")]
public async Task<IActionResult> GetAll([FromQuery] ProductsRequestInput input)
{
var list = await _mediator.Send(new GetProductsQuery(input));
Debugger.Break();
return Ok(list);
}
[HttpPost]
[Route("Create")]
public async Task<IActionResult> Create([FromBody]Product input)
{
var result = await _mediator.Send(new CreateProductCommand(input));
return result > 0 ? (IActionResult)Ok(result) : BadRequest();
}
[HttpDelete]
[Route("Delete/{id}")]
public async Task<IActionResult> Delete(int id)
{
var count = await _mediator.Send(new DeleteProductCommand(id));
return count > 0 ? (IActionResult)Ok() : NotFound();
}
[HttpPut]
[Route("Update")]
public async Task<IActionResult> Update([FromBody]Product input)
{
var count = await _mediator.Send(new UpdateProductCommand(input));
return count > 0 ? (IActionResult)Ok() : NotFound();
}
}
}

View File

@ -0,0 +1,15 @@
using CQRS_Simple.API.Products.Dtos;
using MediatR;
namespace CQRS_Simple.API.Products.Queries
{
public class GetProductByIdQuery : IRequest<ProductDto>
{
public int ProductId { get; set; }
public GetProductByIdQuery(int productId)
{
ProductId = productId;
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using CQRS_Simple.API.Products.Dtos;
using CQRS_Simple.Domain.Products.Request;
using MediatR;
namespace CQRS_Simple.API.Products.Queries
{
public class GetProductsQuery : IRequest<List<ProductDto>>
{
public ProductsRequestInput Input { get; set; }
public GetProductsQuery(ProductsRequestInput input)
{
Input = input;
}
}
}

View File

@ -0,0 +1,20 @@
using CQRS_Simple.API.Products.Commands;
using CQRS_Simple.Domain.Products;
using CQRS_Simple.Infrastructure.Dapper;
using FluentValidation;
namespace CQRS_Simple.API.Products.Validation
{
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
private readonly IDapperRepository<Product, int> _productDapperRepository;
public CreateProductCommandValidator(IDapperRepository<Product, int> productDapperRepository)
{
_productDapperRepository = productDapperRepository;
RuleFor(x => x.Product).NotNull();
RuleFor(x => x.Product.Id).Equal(0);
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.IO;
using Autofac.Extensions.DependencyInjection;
using CQRS_Simple.API;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Formatting.Compact;
namespace CQRS_Simple
{
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.Enrich.FromLogContext()
.WriteTo.Console()
// .WriteTo.File(new RenderedCompactJsonFormatter(), "/logs/log.json")
.CreateLogger();
try
{
Log.Information("Starting up");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:21020",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"CQRS_Simple": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger/index.html",
"applicationUrl": "http://localhost:21020",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

162
CQRS_Simple.API/Startup.cs Normal file
View File

@ -0,0 +1,162 @@
using System;
using System.Reflection;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using CQRS_Simple.API.Modules;
using CQRS_Simple.Domain.Products.Request;
using CQRS_Simple.EntityFrameworkCore;
using CQRS_Simple.Infrastructure;
using CQRS_Simple.Infrastructure.MQ;
using CQRS_Simple.Modules;
using FluentValidation.AspNetCore;
using MediatR.Extensions.FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
namespace CQRS_Simple.API
{
public class Startup
{
public IConfigurationRoot _configuration { get; }
public ILifetimeScope AutofacContainer { get; private set; }
public ILoggerFactory _LoggerFactory { get; private set; }
private const string SqlServerConnection = "ConnectionStrings:Default";
private const string MysqlConnection = "ConnectionStrings:Mysql";
public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
// ConfigureServices is where you register dependencies. This gets
// called by the runtime before the ConfigureContainer method, below.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SimpleDbContext>(options =>
options.UseMySql(_configuration[MysqlConnection], ServerVersion.Parse("5.7.31-mysql")));
//options.UseSqlServer(_configuration[SqlServerConnection]));
services.Configure<RabbitMQOptions>(_configuration.GetSection("RabbitMQ"));
//services.AddHostedService<MyListener>();
services.AddControllersWithViews(option =>
{
option.AllowEmptyInputInBodyModelBinding = true; // false as Default
})
.AddNewtonsoftJson(c => { c.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); })
// 注入Api参数的FluentValidation
.AddFluentValidation(c => c.RegisterValidatorsFromAssembly(typeof(ProductValidator).Assembly))
;
// MediatR的FluentValidation 效果和ValidationBehavior一样,取其一
// services.AddFluentValidation(new[]
// {
// typeof(Startup).GetTypeInfo().Assembly,
// typeof(ProductsRequestInput).GetTypeInfo().Assembly
// });
AddSwagger(services);
}
// ConfigureContainer is where you can register things directly
// with Autofac. This runs after ConfigureServices so the things
// here will override registrations made in ConfigureServices.
// Don't build the container; that gets done for you by the factory.
public void ConfigureContainer(ContainerBuilder builder)
{
// 类型注入
builder.Register(c => new CallLogger(Console.Out));
builder.RegisterModule(new IocManagerModule());
builder.RegisterModule(new LoggerModule());
builder.RegisterModule(new InfrastructureModule(_configuration[MysqlConnection]));
builder.RegisterModule(new MediatorModule());
builder.RegisterModule(new AutoMapperModule());
}
// Configure is where you add middleware. This is called after
// ConfigureContainer. You can use IApplicationBuilder.ApplicationServices
// here if you need to resolve things from the container.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
AutofacContainer = app.ApplicationServices.GetAutofacRoot();
//loggerFactory.AddSerilog();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
ConfigureSwagger(app);
}
private static void ConfigureSwagger(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample CQRS API V1"); });
}
private void AddSwagger(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "API",
Version = "v1",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = new Uri("https://somall.top/about")
});
options.DocInclusionPredicate((docName, description) => true);
});
}
}
}

View File

@ -0,0 +1,14 @@
{
"ConnectionStrings": {
"Default": "Server=somall.top; Database=CQRS_Simple;uid=sa;pwd=hiue234Dfdf;Max Pool Size=2000;",
"Mysql": "server=119.91.157.50;port=6606;uid=root;pwd=123456;database=cqrs_simple;CharSet=utf8"
},
"RabbitMQ": {
"UserName": "guest",
"Password": "guest",
"HostName": "127.0.0.1",
"Port": 5672,
"QueryName": "MyQuery"
},
"AllowedHosts": "*"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8</LangVersion>
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="9.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CQRS_Simple.Infrastructure\CQRS_Simple.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,29 @@
using CQRS_Simple.Infrastructure;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using FluentValidation;
namespace CQRS_Simple.Domain.Products
{
[Table("Products")]
public class Product : Entity<int>, IAggregateRoot
{
//[StringLength(256)] [Required]
public string Name { get; set; }
//[StringLength(256)]
public string Code { get; set; }
public string Description { get; set; }
}
public class ProductValidator : AbstractValidator<Product>
{
public ProductValidator()
{
RuleFor(x => x.Name).NotNull();
RuleFor(x => x.Name).Length(5, 256);
RuleFor(x => x.Code).Length(5, 256);
}
}
}

View File

@ -0,0 +1,20 @@
using FluentValidation;
namespace CQRS_Simple.Domain.Products.Request
{
public class ProductsRequestInput
{
public int SkipCount { get; set; }
public int MaxResultCount { get; set; }
}
public class ProductValidator : AbstractValidator<ProductsRequestInput>
{
public ProductValidator()
{
RuleFor(x => x.MaxResultCount).GreaterThanOrEqualTo(1).WithName("每页数量");
RuleFor(x => x.SkipCount).GreaterThanOrEqualTo(0).WithName("忽略行数");
}
}
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8</LangVersion>
<ApplicationIcon />
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CQRS_Simple.Domain\CQRS_Simple.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
// <auto-generated />
using CQRS_Simple.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CQRS_Simple.EntityFrameworkCore.Migrations
{
[DbContext(typeof(SimpleDbContext))]
[Migration("20220406070529_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("CQRS_Simple.Domain.Products.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Code")
.HasColumnType("longtext");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<string>("Name")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Products");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CQRS_Simple.EntityFrameworkCore.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Code = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Description = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Products");
}
}
}

View File

@ -0,0 +1,43 @@
// <auto-generated />
using CQRS_Simple.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CQRS_Simple.EntityFrameworkCore.Migrations
{
[DbContext(typeof(SimpleDbContext))]
partial class SimpleDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("CQRS_Simple.Domain.Products.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Code")
.HasColumnType("longtext");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<string>("Name")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Products");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using CQRS_Simple.Domain.Products;
using Microsoft.EntityFrameworkCore;
using Serilog;
namespace CQRS_Simple.EntityFrameworkCore
{
public class SimpleDbContext : DbContext
{
protected SimpleDbContext()
{
}
public DbSet<Product> Products { get; set; }
public SimpleDbContext(DbContextOptions<SimpleDbContext> options)
: base(options)
{
#if DEBUG
Log.Debug($"SimpleDbContext Init");
#endif
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.0.0" />
<PackageReference Include="Castle.Core" Version="4.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="MySqlConnector" Version="2.1.2" />
<PackageReference Include="RabbitMQ.Client" Version="6.2.1" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
<PackageReference Include="Dapper" Version="2.0.35" />
</ItemGroup>
<ItemGroup>
<Folder Include="MQ\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
namespace CQRS_Simple.Infrastructure.Dapper
{
public static class DapperExtensions
{
public static async Task<T> InsertAsync<T>(this IDbConnection db, string tableName, object param)
{
IEnumerable<T> result = await db.QueryAsync<T>(DynamicQuery.GetInsertQuery(tableName, param), param);
return result.First();
}
public static async Task<int> UpdateAsync(
this IDbConnection db,
string tableName, object param)
{
return await db.ExecuteAsync(DynamicQuery.GetUpdateQuery(tableName, param), param);
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Dapper;
namespace CQRS_Simple.Infrastructure.Dapper
{
public class DapperRepository<T, TC> : IDapperRepository<T, TC> where T : Entity<TC>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
private readonly string _tableName;
public DapperRepository(ISqlConnectionFactory sqlConnectionFactory)
{
_sqlConnectionFactory = sqlConnectionFactory;
var attr = typeof(T).GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault();
_tableName = (attr as TableAttribute)?.Name;
}
public async Task<T> GetByIdAsync(TC id)
{
using var db = _sqlConnectionFactory.GetOpenConnection();
return await db.QueryFirstOrDefaultAsync<T>(
$"SELECT * FROM {_tableName} WHERE Id=@Id",
new { Id = id });
}
public async Task<TC> AddAsync(T item)
{
using var db = _sqlConnectionFactory.GetOpenConnection();
return await db.InsertAsync<TC>(_tableName, item);
}
public async Task<int> RemoveAsync(T item)
{
using var db = _sqlConnectionFactory.GetOpenConnection();
return await db.ExecuteAsync(
$"DELETE FROM {_tableName} WHERE Id=@Id",
new { Id = item.Id });
}
public async Task<int> UpdateAsync(T item)
{
using var db = _sqlConnectionFactory.GetOpenConnection();
return await db.UpdateAsync(_tableName, item);
}
public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate)
{
IEnumerable<T> items;
var result = DynamicQuery.GetDynamicQuery(_tableName, predicate);
using (var db = _sqlConnectionFactory.GetOpenConnection())
{
items = await db.QueryAsync<T>(result.Sql, (object)result.Param);
}
return items;
}
public async Task<IEnumerable<T>> GetAllAsync()
{
IEnumerable<T> items;
using (var db = _sqlConnectionFactory.GetOpenConnection())
{
items = await db.QueryAsync<T>(
$"SELECT * FROM {_tableName}"
);
}
return items;
}
}
}

View File

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace CQRS_Simple.Infrastructure.Dapper
{
/// <summary>
/// Dynamic query class.
/// </summary>
public sealed class DynamicQuery
{
/// <summary>
/// Gets the insert query.
/// </summary>
/// <param name="tableName">Name of the table.</param>
/// <param name="item">The item.</param>
/// <returns>
/// The Sql query based on the item properties.
/// </returns>
public static string GetInsertQuery(string tableName, object item)
{
PropertyInfo[] props = item.GetType().GetProperties(
BindingFlags.Public |
BindingFlags.Instance);
var columns = props.Where(x => x.Name != "Id").Select(p => p.Name).ToArray();
return string.Format("INSERT INTO {0} ({1}) OUTPUT Inserted.Id VALUES (@{2})",
tableName,
string.Join(",", columns),
string.Join(",@", columns));
}
/// <summary>
/// Gets the update query.
/// </summary>
/// <param name="tableName">Name of the table.</param>
/// <param name="item">The item.</param>
/// <returns>
/// The Sql query based on the item properties.
/// </returns>
public static string GetUpdateQuery(string tableName, dynamic item)
{
PropertyInfo[] props = item.GetType().GetProperties(BindingFlags.Public |
BindingFlags.Instance);
string[] columns = props.Where(x => x.Name != "Id").Select(p => p.Name).ToArray();
var parameters = columns.Select(name => name + "=@" + name).ToList();
return string.Format("UPDATE {0} SET {1} WHERE Id=@Id", tableName, string.Join(",", parameters));
}
/// <summary>
/// Gets the dynamic query.
/// </summary>
/// <param name="tableName">Name of the table.</param>
/// <param name="expression">The expression.</param>
/// <returns>A result object with the generated sql and dynamic params.</returns>
public static QueryResult GetDynamicQuery<T>(string tableName, Expression<Func<T, bool>> expression)
{
var queryProperties = new List<QueryParameter>();
var body = (BinaryExpression)expression.Body;
IDictionary<string, Object> expando = new ExpandoObject();
var builder = new StringBuilder();
// walk the tree and build up a list of query parameter objects
// from the left and right branches of the expression tree
WalkTree(body, ExpressionType.Default, ref queryProperties);
// convert the query parms into a SQL string and dynamic property object
builder.Append("SELECT * FROM ");
builder.Append(tableName);
builder.Append(" WHERE ");
for (int i = 0; i < queryProperties.Count(); i++)
{
QueryParameter item = queryProperties[i];
if (!string.IsNullOrEmpty(item.LinkingOperator) && i > 0)
{
builder.Append(string.Format("{0} {1} {2} @{1} ", item.LinkingOperator, item.PropertyName,
item.QueryOperator));
}
else
{
builder.Append(string.Format("{0} {1} @{0} ", item.PropertyName, item.QueryOperator));
}
expando[item.PropertyName] = item.PropertyValue;
}
return new QueryResult(builder.ToString().TrimEnd(), expando);
}
/// <summary>
/// Walks the tree.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="linkingType">Type of the linking.</param>
/// <param name="queryProperties">The query properties.</param>
private static void WalkTree(BinaryExpression body, ExpressionType linkingType,
ref List<QueryParameter> queryProperties)
{
if (body.NodeType != ExpressionType.AndAlso && body.NodeType != ExpressionType.OrElse)
{
string propertyName = GetPropertyName(body);
dynamic propertyValue = body.Right;
string opr = GetOperator(body.NodeType);
string link = GetOperator(linkingType);
queryProperties.Add(new QueryParameter(link, propertyName, propertyValue.Value, opr));
}
else
{
WalkTree((BinaryExpression)body.Left, body.NodeType, ref queryProperties);
WalkTree((BinaryExpression)body.Right, body.NodeType, ref queryProperties);
}
}
/// <summary>
/// Gets the name of the property.
/// </summary>
/// <param name="body">The body.</param>
/// <returns>The property name for the property expression.</returns>
private static string GetPropertyName(BinaryExpression body)
{
string propertyName = body.Left.ToString().Split(new char[] { '.' })[1];
if (body.Left.NodeType == ExpressionType.Convert)
{
// hack to remove the trailing ) when convering.
propertyName = propertyName.Replace(")", string.Empty);
}
return propertyName;
}
/// <summary>
/// Gets the operator.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The expression types SQL server equivalent operator.
/// </returns>
/// <exception cref="System.NotImplementedException"></exception>
private static string GetOperator(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "!=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.AndAlso:
case ExpressionType.And:
return "AND";
case ExpressionType.Or:
case ExpressionType.OrElse:
return "OR";
case ExpressionType.Default:
return string.Empty;
default:
throw new NotImplementedException();
}
}
}
/// <summary>
/// Class that models the data structure in coverting the expression tree into SQL and Params.
/// </summary>
internal class QueryParameter
{
public string LinkingOperator { get; set; }
public string PropertyName { get; set; }
public object PropertyValue { get; set; }
public string QueryOperator { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="QueryParameter" /> class.
/// </summary>
/// <param name="linkingOperator">The linking operator.</param>
/// <param name="propertyName">Name of the property.</param>
/// <param name="propertyValue">The property value.</param>
/// <param name="queryOperator">The query operator.</param>
internal QueryParameter(string linkingOperator, string propertyName, object propertyValue, string queryOperator)
{
this.LinkingOperator = linkingOperator;
this.PropertyName = propertyName;
this.PropertyValue = propertyValue;
this.QueryOperator = queryOperator;
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace CQRS_Simple.Infrastructure.Dapper
{
public interface IDapperRepository<T, TC> where T : Entity<TC>
{
Task<TC> AddAsync(T item);
Task<int> RemoveAsync(T item);
Task<int> UpdateAsync(T item);
Task<T> GetByIdAsync(TC id);
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
Task<IEnumerable<T>> GetAllAsync();
}
}

View File

@ -0,0 +1,9 @@
using System.Data;
namespace CQRS_Simple.Infrastructure
{
public interface ISqlConnectionFactory
{
IDbConnection GetOpenConnection();
}
}

View File

@ -0,0 +1,53 @@
using System;
namespace CQRS_Simple.Infrastructure
{
/// <summary>
/// A result object with the generated sql and dynamic params.
/// </summary>
public class QueryResult
{
/// <summary>
/// The _result
/// </summary>
private readonly Tuple<string, dynamic> _result;
/// <summary>
/// Gets the SQL.
/// </summary>
/// <value>
/// The SQL.
/// </value>
public string Sql
{
get
{
return _result.Item1;
}
}
/// <summary>
/// Gets the param.
/// </summary>
/// <value>
/// The param.
/// </value>
public dynamic Param
{
get
{
return _result.Item2;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="QueryResult" /> class.
/// </summary>
/// <param name="sql">The SQL.</param>
/// <param name="param">The param.</param>
public QueryResult(string sql, dynamic param)
{
_result = new Tuple<string, dynamic>(sql, param);
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Data;
using System.Data.SqlClient;
using MySqlConnector;
namespace CQRS_Simple.Infrastructure.Dapper
{
public class SqlConnectionFactory : ISqlConnectionFactory, IDisposable
{
private readonly string _connectionString;
private IDbConnection _connection;
public SqlConnectionFactory(string connectionString)
{
this._connectionString = connectionString;
}
public IDbConnection GetOpenConnection()
{
if (this._connection == null || this._connection.State != ConnectionState.Open)
{
//this._connection = new SqlConnection(_connectionString);
this._connection = new MySqlConnection(_connectionString)
{
Site = null,
ProvideClientCertificatesCallback = null,
ProvidePasswordCallback = null,
RemoteCertificateValidationCallback = null
};
this._connection.Open();
}
return this._connection;
}
public void Dispose()
{
if (this._connection != null && this._connection.State == ConnectionState.Open)
{
this._connection.Dispose();
}
}
}
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace CQRS_Simple.Infrastructure
{
public class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
{
[Key] public virtual TPrimaryKey Id { get; set; }
private List<IDomainEvent> _domainEvents;
/// <summary>
/// Domain events occurred.
/// </summary>
protected IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
/// <summary>
/// Add domain event.
/// </summary>
/// <param name="domainEvent"></param>
protected void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents ??= new List<IDomainEvent>();
_domainEvents.Add(domainEvent);
}
/// <summary>
/// Clead domain events.
/// </summary>
public void ClearDomainEvents()
{
_domainEvents?.Clear();
}
/// <inheritdoc/>
public override int GetHashCode()
{
return Id == null ? 0 : Id.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
return $"[{GetType().Name} {Id}]";
}
}
public interface IEntity<TPrimaryKey>
{
TPrimaryKey Id { get; set; }
void ClearDomainEvents();
}
}

View File

@ -0,0 +1,5 @@
namespace CQRS_Simple.Infrastructure
{
public interface IAggregateRoot
{ }
}

View File

@ -0,0 +1,33 @@
using System;
namespace CQRS_Simple.Infrastructure
{
public class DomainEvent : IDomainEvent
{
/// <summary>
/// The time when the event occurred.
/// </summary>
public DateTime EventTime { get; set; }
/// <summary>
/// The object which triggers the event (optional).
/// </summary>
public object EventSource { get; set; }
/// <summary>
/// Constructor.
/// </summary>
protected DomainEvent()
{
EventTime = DateTime.Now;
}
}
public interface IDomainEvent
{
DateTime EventTime { get; }
object EventSource { get; set; }
}
}

View File

@ -0,0 +1,29 @@
using Autofac;
namespace CQRS_Simple.Infrastructure
{
public interface IIocManager
{
ILifetimeScope AutofacContainer { get; set; }
TService GetInstance<TService>();
}
public class IocManager : IIocManager
{
public IocManager(ILifetimeScope container)
{
AutofacContainer = container;
}
/// <summary>
/// Autofac容器
/// </summary>
public ILifetimeScope AutofacContainer { get; set; }
public TService GetInstance<TService>()
{
return AutofacContainer.Resolve<TService>();
}
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace CQRS_Simple.API.Products.Handlers
{
public class RabbitData
{
public string Type { get; private set; }
public object Request { get; private set; }
public object Result { get; private set; }
public RabbitData(Type type, object request, object result = null)
{
Type = $"{type}";
Request = request;
Result = result;
}
}
}

View File

@ -0,0 +1,102 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Serilog;
namespace CQRS_Simple.Infrastructure.MQ
{
public class RabbitListener : IHostedService
{
private readonly RabbitMQOptions _options;
private readonly IConnection connection;
private readonly IModel channel;
public RabbitListener(
IOptions<RabbitMQOptions> optionsAccessor
)
{
_options = optionsAccessor.Value;
try
{
var factory = new ConnectionFactory()
{
UserName = _options.UserName,
Password = _options.Password,
HostName = _options.HostName,
Port = _options.Port
};
this.connection = factory.CreateConnection();
this.channel = connection.CreateModel();
Log.Information($"RabbitMQ 连接成功");
}
catch (Exception ex)
{
Log.Error($"RabbitListener init error,ex:{ex.Message}");
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await Register();
await Task.CompletedTask;
}
protected string QueueName= "QueueName";
protected string RouteKey;
// 处理消息的方法
public virtual async Task Register()
{
channel.ExchangeDeclare("message", ExchangeType.Topic, true, false, null);
channel.QueueDeclare(QueueName, true, false, false, null);
channel.QueueBind(queue: QueueName, exchange: "message", routingKey: RouteKey);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
var result = await ProcessAsync(message);
Log.Information($"收到消息: {message} routerKey: { ea.RoutingKey}");
if (result)
{
channel.BasicAck(ea.DeliveryTag, false);
}
await Task.Yield();
};
channel.BasicConsume(queue: QueueName, consumer: consumer);
await Task.CompletedTask;
}
public virtual Task<bool> ProcessAsync(string message)
{
throw new NotImplementedException();
}
public Task StopAsync(CancellationToken cancellationToken)
{
channel?.Dispose();
connection?.Dispose();
return Task.CompletedTask;
}
}
public class RabbitMQOptions
{
public string UserName { get; set; }
public string Password { get; set; }
public string HostName { get; set; }
public int Port { get; set; }
public string QueryName { get; set; }
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.Text;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using RabbitMQ.Client;
using Serilog;
namespace CQRS_Simple.Infrastructure.MQ
{
public class RabbitMQClient : IDisposable
{
private readonly RabbitMQOptions _options;
private IModel _channel;
private IConnection _connection;
public RabbitMQClient(
IOptions<RabbitMQOptions> optionsAccessor
)
{
_options = optionsAccessor.Value;
try
{
var factory = new ConnectionFactory()
{
UserName = _options.UserName,
Password = _options.Password,
HostName = _options.HostName,
Port = _options.Port
};
this._connection = factory.CreateConnection();
this._channel = _connection.CreateModel();
Log.Information($"RabbitMQ Client 连接成功");
}
catch (Exception ex)
{
Console.WriteLine($"RabbitListener init error,ex:{ex.Message}");
}
}
public virtual void PushMessage(object message, string queryName = null, string routerKey = "Test.*")
{
if (queryName == null)
queryName = _options.QueryName;
var exchangeName = "message";
Log.Debug($"PushMessage queryName:{queryName} routingKey:{routerKey}");
//定义一个Direct类型交换机
_channel.ExchangeDeclare(exchangeName, ExchangeType.Topic, true, false, null);
//定义一个队列
_channel.QueueDeclare(queryName, true, false, false, null);
//将队列绑定到交换机
_channel.QueueBind(queryName, exchangeName, routerKey, null);
var sendBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
_channel.BasicPublish(exchangeName, routerKey, null, sendBytes);
}
public void Dispose()
{
_channel?.Dispose();
_connection?.Dispose();
Log.Information($"RabbitMQ Client Dispose");
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Linq;
using Castle.DynamicProxy;
namespace CQRS_Simple.Infrastructure
{
/// <summary>
/// 拦截器 需要实现 IInterceptor接口 Intercept方法
/// </summary>
public class CallLogger : IInterceptor
{
TextWriter _output;
public CallLogger(TextWriter output)
{
_output = output;
}
/// <summary>
/// 拦截方法 打印被拦截的方法执行前的名称、参数和方法执行后的 返回结果
/// </summary>
/// <param name="invocation">包含被拦截方法的信息</param>
public void Intercept(IInvocation invocation)
{
if (invocation.Method.IsPublic)
{
_output.WriteLine("你正在调用方法 \"{0}\" 参数是 {1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
//在被拦截的方法执行完毕后 继续执行
invocation.Proceed();
_output.WriteLine("方法执行完毕,返回结果:{0}", invocation.ReturnValue);
}
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace CQRS_Simple.Infrastructure.Uow
{
public interface IRepository<T, TC> where T : Entity<TC>
{
IUnitOfWork UnitOfWork { get; }
T GetById(TC id);
Task<T> GetByIdAsync(TC id);
IEnumerable<T> GetAll();
IEnumerable<T> Get(Expression<Func<T, bool>> predicate);
void Add(T entity);
void Delete(T entity);
void Update(T entity);
Task<IEnumerable<T>> GetAllAsync();
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;
namespace CQRS_Simple.Infrastructure.Uow
{
public interface IUnitOfWork : IDisposable
{
DbContext Context { get; }
int SaveChanges();
Task<int> SaveChangesAsync();
IRepository<T, TC> GetRepository<T, TC>() where T : Entity<TC>;
void PrintKey();
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace CQRS_Simple.Infrastructure.Uow
{
public class Repository<T, TPrimaryKey> : IRepository<T, TPrimaryKey> where T : Entity<TPrimaryKey>
{
private readonly IUnitOfWork _unitOfWork;
public Repository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public void Add(T entity)
{
_unitOfWork.Context.Set<T>().Add(entity);
}
public void Delete(T entity)
{
T existing = _unitOfWork.Context.Set<T>().Find(entity);
if (existing != null) _unitOfWork.Context.Set<T>().Remove(existing);
}
public IUnitOfWork UnitOfWork => _unitOfWork;
public T GetById(TPrimaryKey id)
{
return _unitOfWork.Context.Set<T>().FirstOrDefault(x => id.Equals(x.Id));
}
public Task<T> GetByIdAsync(TPrimaryKey id)
{
return _unitOfWork.Context.Set<T>().FirstOrDefaultAsync(x => id.Equals(x.Id));
}
public IEnumerable<T> GetAll()
{
return _unitOfWork.Context.Set<T>().AsEnumerable<T>();
}
public IEnumerable<T> Get(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
return _unitOfWork.Context.Set<T>().Where(predicate).AsEnumerable<T>();
}
public void Update(T entity)
{
_unitOfWork.Context.Entry(entity).State = EntityState.Modified;
_unitOfWork.Context.Set<T>().Attach(entity);
}
public async Task<IEnumerable<T>> GetAllAsync()
{
return await _unitOfWork.Context.Set<T>().ToListAsync();
}
}
}

View File

@ -0,0 +1,116 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Serilog;
namespace CQRS_Simple.Infrastructure.Uow
{
public class UnitOfWork : IUnitOfWork
{
private readonly IIocManager _iocManager;
private Guid KEY { get; }
public DbContext Context { get; }
public IDbContextTransaction Transaction;
public UnitOfWork(DbContext context, IIocManager iocManager)
{
_iocManager = iocManager;
Context = context;
Transaction = context.Database.BeginTransaction();
KEY = Guid.NewGuid();
#if DEBUG
Log.Information($"UnitOfWork Init {KEY}");
#endif
}
public int SaveChanges()
{
return Context.SaveChanges();
}
public async Task<int> SaveChangesAsync()
{
return await CommitAsync();
}
public IRepository<T, TC> GetRepository<T, TC>() where T : Entity<TC>
{
return _iocManager.GetInstance<IRepository<T, TC>>();
}
public void PrintKey()
{
Log.Information($"PrintKey:{KEY}");
}
/// <summary>
/// 手动清理
/// </summary>
public void CleanUp()
{
Commit();
Transaction?.Dispose();
Context?.Dispose();
#if DEBUG
Log.Information($"Context CleanUp");
#endif
}
private int Commit()
{
var result = 0;
try
{
result = Context.SaveChanges();
Transaction?.Commit();
return result;
}
catch (Exception e)
{
result = -1;
Transaction?.Rollback();
Log.Error("Context Transaction Error");
Log.Error(e.Message);
}
return result;
}
private async Task<int> CommitAsync()
{
var result = 0;
try
{
result = await Context.SaveChangesAsync();
Transaction?.Commit();
return result;
}
catch (Exception e)
{
result = -1;
if (Transaction != null)
await Transaction.RollbackAsync();
Log.Error("Context Transaction Error");
Log.Error(e.Message);
}
return result;
}
public void Dispose()
{
Context?.Dispose();
#if DEBUG
Log.Information($"Context Dispose");
#endif
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.DotMemoryUnit" Version="3.1.20200127.214830" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="3.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="3.1.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
using System;
using Xunit;
namespace CQRS_Simple.Tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

58
CQRS_Simple.sln Normal file
View File

@ -0,0 +1,58 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29519.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.API", "CQRS_Simple.API\CQRS_Simple.API.csproj", "{0B262E43-DCAD-4FB5-B394-E4DEE455C605}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.Domain", "CQRS_Simple.Domain\CQRS_Simple.Domain.csproj", "{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.Infrastructure", "CQRS_Simple.Infrastructure\CQRS_Simple.Infrastructure.csproj", "{14AB059D-1475-4A91-AAF4-312FCB265E81}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.EntityFrameworkCore", "CQRS_Simple.EntityFrameworkCore\CQRS_Simple.EntityFrameworkCore.csproj", "{F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{31A46403-BFDD-499A-8EC2-2EABDC096A47}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "API", "API", "{439AFE5F-24A5-4F70-9BEC-690E124C5D30}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.Tests", "CQRS_Simple.Tests\CQRS_Simple.Tests.csproj", "{824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Release|Any CPU.Build.0 = Release|Any CPU
{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Release|Any CPU.Build.0 = Release|Any CPU
{14AB059D-1475-4A91-AAF4-312FCB265E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14AB059D-1475-4A91-AAF4-312FCB265E81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14AB059D-1475-4A91-AAF4-312FCB265E81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14AB059D-1475-4A91-AAF4-312FCB265E81}.Release|Any CPU.Build.0 = Release|Any CPU
{F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Release|Any CPU.Build.0 = Release|Any CPU
{824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0B262E43-DCAD-4FB5-B394-E4DEE455C605} = {439AFE5F-24A5-4F70-9BEC-690E124C5D30}
{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5} = {31A46403-BFDD-499A-8EC2-2EABDC096A47}
{14AB059D-1475-4A91-AAF4-312FCB265E81} = {31A46403-BFDD-499A-8EC2-2EABDC096A47}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3BA00842-BEC3-4017-A48A-8EB7D8B1D18A}
EndGlobalSection
EndGlobal

14
README.md Normal file
View File

@ -0,0 +1,14 @@
## 这是一个 DDD CQRS 的项目初始模版项目
### 后端技术栈
- .netcore 3.1
- MediatR
- EntityFrameworkCore 3.1
- Dapper
- RabbitMQ (Producer & Consumer )
- AutoMapper 9
- FluentValidation
- IdentityServer4 (todo)
- Serilog