frist commit
This commit is contained in:
commit
0236e96843
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal 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
341
.gitignore
vendored
Normal 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
|
50
CQRS_Simple.API/CQRS_Simple.API.csproj
Normal file
50
CQRS_Simple.API/CQRS_Simple.API.csproj
Normal 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>
|
43
CQRS_Simple.API/Modules/AutoMapperModule.cs
Normal file
43
CQRS_Simple.API/Modules/AutoMapperModule.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
CQRS_Simple.API/Modules/AutoMapping.cs
Normal file
14
CQRS_Simple.API/Modules/AutoMapping.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
CQRS_Simple.API/Modules/InfrastructureModule.cs
Normal file
62
CQRS_Simple.API/Modules/InfrastructureModule.cs
Normal 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();
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
CQRS_Simple.API/Modules/IocManagerModule.cs
Normal file
19
CQRS_Simple.API/Modules/IocManagerModule.cs
Normal 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>()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
CQRS_Simple.API/Modules/LoggerModule.cs
Normal file
18
CQRS_Simple.API/Modules/LoggerModule.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
CQRS_Simple.API/Modules/MediatorModule.cs
Normal file
84
CQRS_Simple.API/Modules/MediatorModule.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
CQRS_Simple.API/MyListener.cs
Normal file
24
CQRS_Simple.API/MyListener.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
CQRS_Simple.API/Pages/Error.cshtml
Normal file
26
CQRS_Simple.API/Pages/Error.cshtml
Normal 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>
|
31
CQRS_Simple.API/Pages/Error.cshtml.cs
Normal file
31
CQRS_Simple.API/Pages/Error.cshtml.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
CQRS_Simple.API/Pages/_ViewImports.cshtml
Normal file
3
CQRS_Simple.API/Pages/_ViewImports.cshtml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@using CQRS_Simple
|
||||||
|
@namespace CQRS_Simple.Pages
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
37
CQRS_Simple.API/PipelineBehaviors/ValidationBehavior.cs
Normal file
37
CQRS_Simple.API/PipelineBehaviors/ValidationBehavior.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
CQRS_Simple.API/Products/Commands/CreateProductCommand.cs
Normal file
53
CQRS_Simple.API/Products/Commands/CreateProductCommand.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
CQRS_Simple.API/Products/Commands/DeleteProductCommand.cs
Normal file
11
CQRS_Simple.API/Products/Commands/DeleteProductCommand.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
11
CQRS_Simple.API/Products/Commands/UpdateProductCommand.cs
Normal file
11
CQRS_Simple.API/Products/Commands/UpdateProductCommand.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
16
CQRS_Simple.API/Products/Dtos/ProductDto.cs
Normal file
16
CQRS_Simple.API/Products/Dtos/ProductDto.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
30
CQRS_Simple.API/Products/Handlers/DeleteProductHandle.cs
Normal file
30
CQRS_Simple.API/Products/Handlers/DeleteProductHandle.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
CQRS_Simple.API/Products/Handlers/GetProductHandle.cs
Normal file
54
CQRS_Simple.API/Products/Handlers/GetProductHandle.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
CQRS_Simple.API/Products/Handlers/UpdateProductHandle.cs
Normal file
30
CQRS_Simple.API/Products/Handlers/UpdateProductHandle.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
CQRS_Simple.API/Products/ProductsController.cs
Normal file
99
CQRS_Simple.API/Products/ProductsController.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
CQRS_Simple.API/Products/Queries/GetProductByIdQuery.cs
Normal file
15
CQRS_Simple.API/Products/Queries/GetProductByIdQuery.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
CQRS_Simple.API/Products/Queries/GetProductsQuery.cs
Normal file
17
CQRS_Simple.API/Products/Queries/GetProductsQuery.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
CQRS_Simple.API/Program.cs
Normal file
54
CQRS_Simple.API/Program.cs
Normal 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>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
28
CQRS_Simple.API/Properties/launchSettings.json
Normal file
28
CQRS_Simple.API/Properties/launchSettings.json
Normal 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
162
CQRS_Simple.API/Startup.cs
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
CQRS_Simple.API/appsettings.json
Normal file
14
CQRS_Simple.API/appsettings.json
Normal 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": "*"
|
||||||
|
}
|
BIN
CQRS_Simple.API/wwwroot/favicon.ico
Normal file
BIN
CQRS_Simple.API/wwwroot/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
20
CQRS_Simple.Domain/CQRS_Simple.Domain.csproj
Normal file
20
CQRS_Simple.Domain/CQRS_Simple.Domain.csproj
Normal 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>
|
29
CQRS_Simple.Domain/Products/Product.cs
Normal file
29
CQRS_Simple.Domain/Products/Product.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
CQRS_Simple.Domain/Products/Request/ProductsRequestInput.cs
Normal file
20
CQRS_Simple.Domain/Products/Request/ProductsRequestInput.cs
Normal 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("忽略行数");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
45
CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.Designer.cs
generated
Normal file
45
CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
CQRS_Simple.EntityFrameworkCore/SimpleDbContext.cs
Normal file
24
CQRS_Simple.EntityFrameworkCore/SimpleDbContext.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
CQRS_Simple.Infrastructure/CQRS_Simple.Infrastructure.csproj
Normal file
24
CQRS_Simple.Infrastructure/CQRS_Simple.Infrastructure.csproj
Normal 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>
|
24
CQRS_Simple.Infrastructure/Dapper/DapperExtensions.cs
Normal file
24
CQRS_Simple.Infrastructure/Dapper/DapperExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
CQRS_Simple.Infrastructure/Dapper/DapperRepository.cs
Normal file
76
CQRS_Simple.Infrastructure/Dapper/DapperRepository.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
201
CQRS_Simple.Infrastructure/Dapper/DynamicQuery.cs
Normal file
201
CQRS_Simple.Infrastructure/Dapper/DynamicQuery.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
CQRS_Simple.Infrastructure/Dapper/IDapperRepository.cs
Normal file
17
CQRS_Simple.Infrastructure/Dapper/IDapperRepository.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace CQRS_Simple.Infrastructure
|
||||||
|
{
|
||||||
|
public interface ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
IDbConnection GetOpenConnection();
|
||||||
|
}
|
||||||
|
}
|
53
CQRS_Simple.Infrastructure/Dapper/QueryResult.cs
Normal file
53
CQRS_Simple.Infrastructure/Dapper/QueryResult.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
CQRS_Simple.Infrastructure/Dapper/SqlConnectionFactory.cs
Normal file
44
CQRS_Simple.Infrastructure/Dapper/SqlConnectionFactory.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
CQRS_Simple.Infrastructure/EntityBase.cs
Normal file
54
CQRS_Simple.Infrastructure/EntityBase.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
5
CQRS_Simple.Infrastructure/IAggregateRoot.cs
Normal file
5
CQRS_Simple.Infrastructure/IAggregateRoot.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
namespace CQRS_Simple.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IAggregateRoot
|
||||||
|
{ }
|
||||||
|
}
|
33
CQRS_Simple.Infrastructure/IDomainEvent.cs
Normal file
33
CQRS_Simple.Infrastructure/IDomainEvent.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
29
CQRS_Simple.Infrastructure/IocManager.cs
Normal file
29
CQRS_Simple.Infrastructure/IocManager.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
CQRS_Simple.Infrastructure/MQ/RabbitData.cs
Normal file
19
CQRS_Simple.Infrastructure/MQ/RabbitData.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
CQRS_Simple.Infrastructure/MQ/RabbitListener.cs
Normal file
102
CQRS_Simple.Infrastructure/MQ/RabbitListener.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
71
CQRS_Simple.Infrastructure/MQ/RabbitPublisher.cs
Normal file
71
CQRS_Simple.Infrastructure/MQ/RabbitPublisher.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
CQRS_Simple.Infrastructure/TimeInterceptor.cs
Normal file
40
CQRS_Simple.Infrastructure/TimeInterceptor.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
CQRS_Simple.Infrastructure/Uow/IRepository.cs
Normal file
21
CQRS_Simple.Infrastructure/Uow/IRepository.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
16
CQRS_Simple.Infrastructure/Uow/IUnitOfWork.cs
Normal file
16
CQRS_Simple.Infrastructure/Uow/IUnitOfWork.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
60
CQRS_Simple.Infrastructure/Uow/Repository.cs
Normal file
60
CQRS_Simple.Infrastructure/Uow/Repository.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
CQRS_Simple.Infrastructure/Uow/UnitOfWork.cs
Normal file
116
CQRS_Simple.Infrastructure/Uow/UnitOfWork.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
CQRS_Simple.Tests/CQRS_Simple.Tests.csproj
Normal file
26
CQRS_Simple.Tests/CQRS_Simple.Tests.csproj
Normal 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>
|
14
CQRS_Simple.Tests/UnitTest1.cs
Normal file
14
CQRS_Simple.Tests/UnitTest1.cs
Normal 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
58
CQRS_Simple.sln
Normal 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
|
Loading…
Reference in New Issue
Block a user