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