├── .dockerignore ├── .gitattributes ├── .gitignore ├── Application ├── Application.csproj ├── ApplicationService.cs ├── MongoContextService.cs ├── ScopedApplicationService.cs ├── SqlDbContextService.cs └── TransientApplicationService.cs ├── Core.Web ├── ApiController.cs ├── ApiEventFilterAttribute.cs ├── ApiExceptionFilterAttribute.cs ├── Conventions │ ├── ApiExplorerGroupNameConvention.cs │ └── ServiceTemplateConvention.cs ├── Core.Web.csproj ├── Extensions │ ├── ActionContextExtensions.cs │ ├── ApiVersioningExtensions.cs │ ├── ExceptionLessExtensions.cs │ ├── InvalidModelStateExtensions.cs │ ├── JwtParserExtensions.cs │ ├── SwaggerDocEndpointExtensions.cs │ ├── SwaggerGenOptionsExtensions.cs │ └── SwaggerJwtAuthExtensions.cs ├── JsonPatchs │ ├── JsonPatchDocument.cs │ ├── JsonPatchInputFormatter.cs │ ├── JsonPatchOperation.cs │ └── TranscodingReadStream.cs ├── Jwts │ ├── JwtOptions.cs │ └── JwtParserMiddleware.cs ├── Middleware.cs ├── ModelBinding │ └── StringPropertyTrimModelBinderProvider.cs ├── ServiceOptions.cs └── Swaggers │ ├── SwaggerApiVersionHeaderFilter.cs │ ├── SwaggerGenOptionsConfigureOptions.cs │ ├── SwaggerOptionsConfigureOptions.cs │ ├── SwaggerRequiredParameterFilter.cs │ ├── SwaggerRequiredSchemaFilter.cs │ └── SwaggerUIOptionsConfigureOptions.cs ├── Core ├── ApiEvent.cs ├── ApiResult.cs ├── Apk │ ├── ApKReader.cs │ ├── ApkMedia.cs │ └── ManifestReader.cs ├── Code.cs ├── Core.csproj ├── HostedBackgroundService.cs ├── HostedService.cs ├── HttpApis │ ├── GatewayAttribute.cs │ ├── GatewayOptions.cs │ └── ServiceCollectionHttpApiExtensions.cs ├── IApiEventPublisher.cs ├── IConfigureOptions.cs ├── IScopeServiceProvider.cs ├── IScopedDependency.cs ├── ISingletonDependency.cs ├── ITransientDependency.cs ├── Jwt │ ├── JwtClaimsPrincipal.cs │ └── RSJwt.cs ├── Menus │ ├── MenuGroup.cs │ └── MenuItem.cs ├── Page.cs ├── RegisterAttribute.cs ├── ScopeServiceProvider.cs ├── ServiceCollectionDependencyExtensions.cs ├── ServiceCollectionOptionsExtensions.cs └── Xls │ ├── XlsColumnAttribute.cs │ ├── XlsColumnParser.cs │ ├── XlsDoc.cs │ └── XlsSheet.cs ├── Corefx ├── AsyncRoot.cs ├── Buffers │ ├── ArrayPool.cs │ ├── BufferWriter.cs │ ├── IArrayOwner.cs │ └── MemoryOwnerExtensions.cs ├── Collections │ └── Concurrent │ │ └── ConcurrentCache.cs ├── Converter.cs ├── Corefx.csproj ├── Data │ ├── Csv.cs │ └── CsvExtensions.cs ├── Disposable.cs ├── EnumExtensions.cs ├── Guid16.cs ├── Linq │ ├── Expressions │ │ ├── Condition.cs │ │ ├── ConditionItem.cs │ │ ├── Operator.cs │ │ ├── Predicate.cs │ │ └── PredicateExtensions.cs │ ├── IEnumerableExtensions.cs │ └── IQueryableExtensions.cs ├── Net │ ├── Mail │ │ └── EMail.cs │ └── Sockets │ │ └── SocketAwaitableEventArgs.cs ├── NullableExtensions.cs ├── ObjectId.cs ├── Pipelines │ ├── IMiddleware.cs │ ├── IPipelineBuilder.cs │ ├── InvokeDelegate.cs │ ├── PipelineBuilder.cs │ └── PipelineBuilderExtensions.cs ├── Reflection │ ├── Lambda.cs │ ├── Method.cs │ └── Property.cs ├── Security │ ├── DES.cs │ └── MD5.cs ├── StringExtensions.cs ├── Text.Json.Serialization │ ├── JsonLocalDateTimeConverter.cs │ ├── JsonStringToEnumConverter.cs │ └── JsonStringToNumberConverter.cs ├── Threading.Tasks │ ├── ManualTask.cs │ └── TaskPipeScheduler.cs └── TypeExtensions.cs ├── Domain ├── DbSetExtensions.cs ├── Domain.csproj ├── ICreatorable.cs ├── IEntity.cs ├── IEntityView.cs ├── IIntercomable.cs ├── IMongoEntity.cs ├── IStringIdable.cs ├── ITenantable.cs ├── IUser.cs ├── IUserAccessor.cs ├── MongoDbContext.cs ├── MongoDbSet.cs ├── PagingExtensions.cs ├── SqlDbContext.cs └── StringTrimFlagAttribute.cs ├── README.md ├── Web.Benchmark ├── DemoContext.cs ├── Program.cs └── Web.Benchmark.csproj ├── Web.Host ├── Controllers │ ├── v1 │ │ └── ValuesController.cs │ └── v2 │ │ └── ValuesController.cs ├── Dockerfile ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── Web.Host.csproj ├── app.json ├── appsettings.Development.json ├── appsettings.json ├── build.cmd ├── docker.build.cmd ├── docker.build.sh └── docker.run.sh ├── Web.Template.sln └── Web.Test ├── 001.apk ├── 1.xls ├── 1.xlsx ├── Core ├── ApkTest.cs ├── MapTest.cs └── XlsDocTest.cs ├── Corefx └── IEnumerableExtensionsTest.cs └── Web.Test.csproj /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.vs 6 | **/.vscode 7 | **/*.*proj.user 8 | **/azds.yaml 9 | **/charts 10 | **/bin 11 | **/obj 12 | **/Dockerfile 13 | **/Dockerfile.develop 14 | **/docker-compose.yml 15 | **/docker-compose.*.yml 16 | **/*.dbmdl 17 | **/*.jfm 18 | **/secrets.dev.yaml 19 | **/values.dev.yaml 20 | **/.toolstarget -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Application/Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Application/ApplicationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Application 4 | { 5 | /// 6 | /// 表示服务抽象类 7 | /// 8 | public abstract class ApplicationService : Disposable 9 | { 10 | /// 11 | /// 释放资源 12 | /// 13 | /// 是否也释放非托管资源 14 | protected override void Dispose(bool disposing) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Application/MongoContextService.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Domain; 3 | using System; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace Application 8 | { 9 | /// 10 | /// 表示基于Mongodb的抽象服务 11 | /// 12 | public abstract class MongoContextService : ScopedApplicationService where T : class, IMongoEntity 13 | { 14 | /// 15 | /// 获取db名 16 | /// 17 | public string DbName => "Talkback"; 18 | 19 | /// 20 | /// 获取mongodb 21 | /// 22 | protected MongoDbContext MongoDb { get; } 23 | 24 | /// 25 | /// 基于Mongodb的抽象服务 26 | /// 27 | /// 28 | public MongoContextService(MongoDbContext mongoDb) 29 | { 30 | this.MongoDb = mongoDb; 31 | } 32 | 33 | /// 34 | /// 保存日志 35 | /// 36 | /// 日志 37 | /// 38 | public virtual async Task AddAsync(T log) 39 | { 40 | var dbName = this.GetDbName(DateTime.Now); 41 | await this.MongoDb.Set(dbName).AddAsync(log); 42 | } 43 | 44 | /// 45 | /// 条件删除第一条 46 | /// 47 | /// 日志 48 | /// 49 | public virtual async Task RemoveOneAsync(Expression> where) 50 | { 51 | var dbName = this.GetDbName(DateTime.Now); 52 | return await this.MongoDb.Set(dbName).RemoveOneAsync(where); 53 | } 54 | 55 | /// 56 | /// 条件删除 57 | /// 58 | /// 条件 59 | /// 60 | public virtual async Task RemoveAsync(Expression> where) 61 | { 62 | var dbName = this.GetDbName(DateTime.Now); 63 | return await this.MongoDb.Set(dbName).RemoveAsync(where); 64 | } 65 | 66 | /// 67 | /// 返回分页查询 68 | /// 69 | /// 获取哪个月 70 | /// 条件 71 | /// 页面索引 72 | /// 页面大小 73 | /// 74 | public virtual Task> GetPageAsync(DateTime month, Expression> where, int pageIndex, int pageSize) 75 | { 76 | var dbName = this.GetDbName(month); 77 | return this.MongoDb.Set(dbName).ToPageAsync(where, item => item.CreateTime, false, pageIndex, pageSize); 78 | } 79 | 80 | /// 81 | /// 返回dbName 82 | /// 83 | /// 月分 84 | /// 85 | protected virtual string GetDbName(DateTime month) 86 | { 87 | return $"{this.DbName}_{DateTime.Now.ToString("yyMM")}"; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Application/ScopedApplicationService.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | 3 | namespace Application 4 | { 5 | /// 6 | /// 表示Scoped模式注册的应用服务抽象类 7 | /// 每个请求产生一个实例 8 | /// 9 | public abstract class ScopedApplicationService : ApplicationService, IScopedDependency 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Application/SqlDbContextService.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Domain; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace Application 12 | { 13 | /// 14 | /// 表示基于数据库的抽象服务 15 | /// 16 | /// 17 | public abstract class SqlDbContextService : ScopedApplicationService where T : class, IStringIdable 18 | { 19 | /// 20 | /// 获取db上下文 21 | /// 22 | protected SqlDbContext Db { get; } 23 | 24 | /// 25 | /// 基于数据库的抽象服务 26 | /// 27 | /// 28 | public SqlDbContextService(SqlDbContext db) 29 | { 30 | this.Db = db; 31 | } 32 | 33 | /// 34 | /// 检测是否存在 35 | /// 36 | /// 37 | /// 38 | public Task ExistsAsync([NotNull]Expression> where) 39 | { 40 | return this.Db.Set().AnyAsync(where); 41 | } 42 | 43 | /// 44 | /// 根据Id获取记录 45 | /// 46 | /// 47 | /// 48 | public ValueTask FindAsync(string id) 49 | { 50 | return this.Db.Set().FindAsync(id); 51 | } 52 | 53 | /// 54 | /// 根据条件获取一条记录 55 | /// 56 | /// 57 | /// 58 | public async Task FindAsync(Expression> where) 59 | { 60 | return await this.Db.Set().Where(where).FirstOrDefaultAsync(); 61 | } 62 | 63 | /// 64 | /// 根据Id获取记录 65 | /// 66 | /// 67 | /// 68 | /// 69 | public Task FindAsync(string id, [NotNull]Expression> selector) 70 | { 71 | return this.Db.Set().Where(item => item.Id == id).Select(selector).FirstOrDefaultAsync(); 72 | } 73 | 74 | /// 75 | /// 根据Id获取记录 76 | /// 77 | /// 78 | /// 79 | public Task FindManyAsync(IEnumerable id) 80 | { 81 | var where = Predicate.CreateOrEqual(item => item.Id, id); 82 | return this.FindManyAsync(where); 83 | } 84 | 85 | /// 86 | /// 根据条件获取记录 87 | /// 88 | /// 89 | /// 90 | public Task FindManyAsync([NotNull]Expression> where) 91 | { 92 | return this.Db.Set().Where(where).ToArrayAsync(); 93 | } 94 | 95 | /// 96 | /// 根据条件获取记录 97 | /// 98 | /// 99 | /// 100 | /// 101 | public Task FindManyAsync([NotNull]Expression> where, [NotNull]Expression> selector) 102 | { 103 | return this.Db.Set().Where(where).Select(selector).ToArrayAsync(); 104 | } 105 | 106 | /// 107 | /// 获取记录分页数据 108 | /// 109 | /// 排序 110 | /// 当前页码索引,从0开始 111 | /// 每页数据 112 | /// 查询条件 113 | /// 114 | public Task> GetPageAsync([NotNull]string orderBy, int pageIndex, int pageSize, [NotNull]Expression> where) 115 | { 116 | return this.Db.Set().Where(where).ToPageAsync(orderBy, pageIndex, pageSize); 117 | } 118 | 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Application/TransientApplicationService.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | 3 | namespace Application 4 | { 5 | /// 6 | /// 表示Transient模式注册的应用服务抽象类 7 | /// 每次获取产生一个实例 8 | /// 9 | public abstract class TransientApplicationService : ApplicationService, ITransientDependency 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core.Web/ApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace Core.Web 8 | { 9 | /// 10 | /// 表示Api控制器 11 | /// 提供查询条件表达式的获取 12 | /// 13 | [ApiController] 14 | [ApiExceptionFilter] 15 | [Route("api/[service]/[controller]")] 16 | public abstract class ApiController : ControllerBase 17 | { 18 | /// 19 | /// 从请求Query获取查询表达式 20 | /// 21 | /// 22 | /// 23 | /// 24 | protected Expression> GetQueryPredicate(bool trueWhenNull = true) 25 | { 26 | return this.GetQueryCondition().ToAndPredicate(trueWhenNull); 27 | } 28 | 29 | /// 30 | /// 从请求Query获取查询条件 31 | /// 32 | /// 33 | /// 34 | protected virtual Condition GetQueryCondition() 35 | { 36 | var items = this.GetQueryConditionItems(); 37 | return new Condition(items); 38 | } 39 | 40 | /// 41 | /// 从Query获取条件项 42 | /// 43 | /// 44 | /// 45 | private IEnumerable> GetQueryConditionItems() 46 | { 47 | bool TryGetTrimValue(PropertyInfo property, string prefx, out string value) 48 | { 49 | if (this.Request.Query.TryGetValue($"{prefx}{property.Name}", out var stringValues)) 50 | { 51 | value = stringValues.Count == 0 ? null : stringValues[0].NullThenEmpty().Trim(); 52 | return value.IsNullOrEmpty() == false; 53 | } 54 | 55 | value = null; 56 | return false; 57 | } 58 | 59 | foreach (var property in ConditionItem.TypeProperties) 60 | { 61 | if (TryGetTrimValue(property, null, out var value)) 62 | { 63 | yield return new ConditionItem(property, value, null); 64 | } 65 | 66 | if (property.PropertyType.IsValueType) 67 | { 68 | if (TryGetTrimValue(property, "min", out var minValue)) 69 | { 70 | yield return new ConditionItem(property, minValue, Operator.GreaterThanOrEqual); 71 | } 72 | if (TryGetTrimValue(property, "max", out var maxValue)) 73 | { 74 | yield return new ConditionItem(property, maxValue, Operator.LessThanOrEqual); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Core.Web/ApiEventFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Linq; 5 | using System.Security.Claims; 6 | using System.Threading.Tasks; 7 | 8 | namespace Core.Web 9 | { 10 | /// 11 | /// Api事件过滤器 12 | /// 使用IApiEventPublisher发布将请求Api的内容 13 | /// 14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] 15 | public class ApiEventFilterAttribute : Attribute, IAsyncActionFilter, IOrderedFilter 16 | { 17 | /// 18 | /// 获取或设置是否启用 19 | /// 默认为true 20 | /// 21 | public bool Enbale { get; set; } = true; 22 | 23 | /// 24 | /// 获取或设置排序顺序 25 | /// 26 | public int Order { get; set; } = 0; 27 | 28 | /// 29 | /// Action执行 30 | /// 31 | /// 32 | /// 33 | /// 34 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) 35 | { 36 | await next.Invoke(); 37 | 38 | if (this.Enbale == true) 39 | { 40 | await this.OnActionExecutedAsync(context); 41 | } 42 | } 43 | 44 | /// 45 | /// Action执行后 46 | /// 47 | /// 48 | /// 49 | private async Task OnActionExecutedAsync(ActionExecutingContext context) 50 | { 51 | var publisher = context.HttpContext.RequestServices.GetService(); 52 | if (publisher == null) 53 | { 54 | return; 55 | } 56 | 57 | var request = context.HttpContext.Request; 58 | var apiEvent = new ApiEvent 59 | { 60 | Uri = new Uri(request.Path + request.QueryString, UriKind.Relative), 61 | UserId = this.GetUserId(context.HttpContext.User), 62 | CreateTime = DateTime.Now 63 | }; 64 | 65 | var api = context.GetApiDescription(); 66 | var contentParameter = api.ParameterDescriptions 67 | .Where(item => item.Source.Id.EqualsIgnoreCase("BODY") || item.Source.Id.EqualsIgnoreCase("FORM")) 68 | .FirstOrDefault(); 69 | 70 | if (contentParameter != null && context.ActionArguments.TryGetValue(contentParameter.Name, out var content) == true) 71 | { 72 | apiEvent.Content = content; 73 | } 74 | 75 | await publisher.PulishAsync(api.RelativePath, apiEvent); 76 | } 77 | 78 | 79 | /// 80 | /// 返回用户信息Id 81 | /// 82 | /// 83 | /// 84 | protected virtual string GetUserId(ClaimsPrincipal user) 85 | { 86 | return user.FindFirstValue("sub"); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Core.Web/ApiExceptionFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Linq; 7 | 8 | namespace Core.Web 9 | { 10 | /// 11 | /// 表示Api异常处理过滤器 12 | /// 将异常转换为IApiResult返回值 13 | /// 14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] 15 | public class ApiExceptionFilterAttribute : Attribute, IExceptionFilter, IOrderedFilter 16 | { 17 | /// 18 | /// 获取或设置是否启用 19 | /// 默认为true 20 | /// 21 | public bool Enbale { get; set; } = true; 22 | 23 | /// 24 | /// 获取或设置排序顺序 25 | /// 26 | public int Order { get; set; } = 0; 27 | 28 | /// 29 | /// 异常时 30 | /// 31 | /// 32 | public virtual void OnException(ExceptionContext context) 33 | { 34 | if (this.Enbale == false) 35 | { 36 | return; 37 | } 38 | 39 | var apiResultType = context.GetApiDescription() 40 | .SupportedResponseTypes 41 | .Select(item => item.Type) 42 | .Where(item => item.IsInheritFrom()) 43 | .FirstOrDefault(); 44 | 45 | if (apiResultType == null) 46 | { 47 | apiResultType = typeof(ApiResult); 48 | } 49 | 50 | var apiResult = Activator.CreateInstance(apiResultType) as IApiResult; 51 | apiResult.Message = context.Exception.Message; 52 | apiResult.Code = context.Exception is ArgumentException ? Code.ParameterError : Code.ServiceError; 53 | 54 | context.HttpContext 55 | .RequestServices 56 | .GetService>() 57 | .LogError(context.Exception, context.Exception.Message); 58 | 59 | context.ExceptionHandled = true; 60 | context.Result = new ObjectResult(apiResult); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Core.Web/Conventions/ApiExplorerGroupNameConvention.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace Core.Web.Conventions 7 | { 8 | /// 9 | /// 表示ApiExplorer以Controllers下的子文件夹分组约定 10 | /// 11 | public class ApiExplorerGroupNameConvention : IControllerModelConvention 12 | { 13 | private readonly string defaultGroupName; 14 | 15 | /// 16 | /// ApiExplorer以Controllers下的子文件夹分组约定 17 | /// 18 | /// 默认分组名称 19 | /// 20 | public ApiExplorerGroupNameConvention(string defaultGroupName = "default.v1") 21 | { 22 | this.defaultGroupName = defaultGroupName ?? throw new ArgumentNullException(nameof(defaultGroupName)); 23 | } 24 | 25 | /// 26 | /// 应用约定 27 | /// 28 | /// 29 | public void Apply(ControllerModel controller) 30 | { 31 | controller.ApiExplorer.GroupName = GetGroupName(controller.ControllerType, this.defaultGroupName); 32 | } 33 | 34 | /// 35 | /// 获取控制器分组名称 36 | /// 37 | /// 控制器类型 38 | /// 默认分组名称 39 | /// 40 | private static string GetGroupName(Type controllerType, string defaultGroupName) 41 | { 42 | var names = controllerType.Namespace.Split("Controllers."); 43 | var groupName = names.Length > 1 ? names.Last().ToLower() : defaultGroupName; 44 | return string.Join("_", groupName.Split('.').Select(item => FixIfVersion(item))); 45 | } 46 | 47 | /// 48 | /// 修复版本号的显示 49 | /// 50 | /// 51 | /// 52 | private static string FixIfVersion(string segment) 53 | { 54 | if (segment.IsMatch(@"v\d_*\d*") == false) 55 | { 56 | return segment; 57 | } 58 | 59 | var version = segment.Replace("_", ".").TrimStart('v'); 60 | return "v" + ApiVersion.Parse(version).ToString("VV"); 61 | } 62 | 63 | /// 64 | /// 返回版本号 65 | /// 66 | /// 分组名称 67 | /// 68 | public static ApiVersion GetApiVersion(string groupName) 69 | { 70 | var version = groupName.Split('_').FirstOrDefault(item => item.IsMatch(@"v\d+\.*\d*")); 71 | return version == null ? ApiVersion.Default : ApiVersion.Parse(version.TrimStart('v')); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Core.Web/Conventions/ServiceTemplateConvention.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 2 | 3 | namespace Core.Web.Conventions 4 | { 5 | /// 6 | /// 表示控制器路由模板添加[service]的约定 7 | /// api/[service]/[controller] => api/service001/[controller] 8 | /// 9 | public class ServiceTemplateConvention : IControllerModelConvention 10 | { 11 | /// 12 | /// 服务名 13 | /// 14 | private readonly string serviceName; 15 | 16 | /// 17 | /// 控制器路由模板添加[service]的约定 18 | /// 19 | /// 服务名 20 | public ServiceTemplateConvention(string serviceName) 21 | { 22 | this.serviceName = serviceName; 23 | } 24 | 25 | /// 26 | /// 应用约定 27 | /// 28 | /// 29 | public void Apply(ControllerModel controller) 30 | { 31 | foreach (var selector in controller.Selectors) 32 | { 33 | if (selector.AttributeRouteModel == null) 34 | { 35 | continue; 36 | } 37 | 38 | var route = selector.AttributeRouteModel; 39 | route.Template = route.Template.Replace("[service]", this.serviceName); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Core.Web/Core.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 21.1.26 5 | net5.0 6 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 7 | true 8 | Taichuan.Core.Web 9 | asp.net的Api控制器基类、异常过滤器和Startup扩展等 10 | Library 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Core.Web/Extensions/ActionContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Core.Web 8 | { 9 | /// 10 | /// 提供ActionContext扩展 11 | /// 12 | public static class ActionContextExtensions 13 | { 14 | /// 15 | /// 获取对应的Api描述 16 | /// 17 | /// 18 | /// 19 | public static ApiDescription GetApiDescription(this ActionContext context) 20 | { 21 | if (context.ActionDescriptor.Properties.TryGetValue(typeof(ApiDescription), out var value)) 22 | { 23 | if (value is ApiDescription description) 24 | { 25 | return description; 26 | } 27 | } 28 | 29 | var apiExplorer = context.HttpContext.RequestServices.GetService(); 30 | var apiDescription = apiExplorer.ApiDescriptionGroups.Items.SelectMany(item => item.Items).FirstOrDefault(item => item.ActionDescriptor.Id == context.ActionDescriptor.Id); 31 | 32 | context.ActionDescriptor.Properties.TryAdd(typeof(ApiDescription), apiDescription); 33 | return apiDescription; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Core.Web/Extensions/ApiVersioningExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 3 | using Microsoft.AspNetCore.Mvc.Versioning; 4 | using Microsoft.AspNetCore.Mvc.Versioning.Conventions; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Options; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Text.RegularExpressions; 12 | 13 | namespace Core.Web 14 | { 15 | /// 16 | /// Api版本控制扩展 17 | /// 18 | public static class ApiVersioningExtensions 19 | { 20 | /// 21 | /// 添加以namespace来查找Api版本信息 22 | /// 23 | /// 24 | /// api版本请求头名称 25 | /// 26 | public static IServiceCollection AddNamespaceApiVersioning(this IServiceCollection services, string apiVersionHeaderName = "x-api-version") 27 | { 28 | return services.AddApiVersioning(o => 29 | { 30 | o.AssumeDefaultVersionWhenUnspecified = true; 31 | o.Conventions.Add(new VersionByNamespaceConvention()); 32 | o.ApiVersionReader = new HeaderApiVersionReader(apiVersionHeaderName); 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Core.Web/Extensions/ExceptionLessExtensions.cs: -------------------------------------------------------------------------------- 1 | using Exceptionless; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Core.Web 6 | { 7 | /// 8 | /// ExceptionLess扩展 9 | /// 10 | public static class ExceptionLessExtensions 11 | { 12 | /// 13 | /// 初始化ExceptionLess客户端 14 | /// 15 | /// 16 | public static IConfigurationSection BindDefaultExceptionLess(this IConfigurationSection configurationSection) 17 | { 18 | configurationSection.Bind(ExceptionlessClient.Default.Configuration); 19 | ExceptionlessClient.Default.Startup(); 20 | return configurationSection; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Core.Web/Extensions/InvalidModelStateExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Core.Web 9 | { 10 | /// 11 | /// 模型验证熔岩 12 | /// 13 | public static class InvalidModelStateExtensions 14 | { 15 | /// 16 | /// 添加模型验证转换为ApiResult输出 17 | /// 18 | /// 19 | /// 20 | public static IServiceCollection AddApiResultInvalidModelState(this IServiceCollection services) 21 | { 22 | return services.AddApiResultInvalidModelState(null); 23 | } 24 | 25 | /// 26 | /// 添加模型验证转换为ApiResult输出 27 | /// 28 | /// 29 | /// 消息格式化 30 | /// 31 | public static IServiceCollection AddApiResultInvalidModelState(this IServiceCollection services, Func, string> messageFormate) 32 | { 33 | return services.PostConfigure(c => 34 | { 35 | c.InvalidModelStateResponseFactory = context => 36 | { 37 | var keyValue = context.ModelState.FirstOrDefault(item => item.Value.Errors.Count > 0); 38 | var message = messageFormate != null ? messageFormate(keyValue) : $"参数{keyValue.Key}验证失败:{keyValue.Value.Errors[0].ErrorMessage}"; 39 | var apiResult = ApiResult.ParameterError(message); 40 | return new ObjectResult(apiResult); 41 | }; 42 | }); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Core.Web/Extensions/JwtParserExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Web.Jwts; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | namespace Core.Web 8 | { 9 | /// 10 | /// jwt扩展 11 | /// 12 | public static class JwtParserExtensions 13 | { 14 | /// 15 | /// 添加jwt解析器 16 | /// 17 | /// 18 | /// 19 | public static IServiceCollection AddJwtParser(this IServiceCollection services) 20 | { 21 | return services.AddJwtParser(c => { }); 22 | } 23 | 24 | /// 25 | /// 添加jwt解析器 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static IServiceCollection AddJwtParser(this IServiceCollection services,[NotNull] Action config) 31 | { 32 | return services.Configure(config); 33 | } 34 | 35 | /// 36 | /// 添加jwt解析器 37 | /// 38 | /// 39 | /// 40 | public static IApplicationBuilder UseJwtParser(this IApplicationBuilder app) 41 | { 42 | return app.UseMiddleware(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Core.Web/Extensions/SwaggerDocEndpointExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Web.Swaggers; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Options; 4 | using Swashbuckle.AspNetCore.Swagger; 5 | using Swashbuckle.AspNetCore.SwaggerGen; 6 | using Swashbuckle.AspNetCore.SwaggerUI; 7 | 8 | namespace Core.Web 9 | { 10 | /// 11 | /// json文档路径和ui路径扩展 12 | /// 13 | public static class SwaggerDocEndpointExtensions 14 | { 15 | /// 16 | /// 添加json文档路径和ui路径 17 | /// 18 | /// 19 | /// 20 | public static IServiceCollection AddSwaggerDocUIAndEndpoints(this IServiceCollection services) 21 | { 22 | return services 23 | .AddTransient, SwaggerOptionsConfigureOptions>() 24 | .AddTransient, SwaggerGenOptionsConfigureOptions>() 25 | .AddTransient, SwaggerUIOptionsConfigureOptions>(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Core.Web/Extensions/SwaggerGenOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Web.Swaggers; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace Core.Web 6 | { 7 | /// 8 | /// swagger不可空值类型参数或模型属性标记为Required 9 | /// 10 | public static class SwaggerGenOptionsExtensions 11 | { 12 | /// 13 | /// 添加Required标记相关的Filters 14 | /// 15 | /// 16 | /// 17 | public static void AddRequiredFilters(this SwaggerGenOptions options, bool camelCasePropertyNames = true) 18 | { 19 | options.SchemaFilter(camelCasePropertyNames); 20 | options.ParameterFilter(); 21 | } 22 | 23 | /// 24 | /// 添加Api版本请求参数相关Filter 25 | /// 26 | /// 27 | /// 28 | public static void AddApiVersionHeaderFilter(this SwaggerGenOptions options, string headerName = "x-api-version") 29 | { 30 | options.OperationFilter(headerName); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Core.Web/Extensions/SwaggerJwtAuthExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.OpenApi.Models; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | using System.Collections.Generic; 5 | 6 | namespace Core.Web 7 | { 8 | /// 9 | /// swagger的授权信息扩展 10 | /// 11 | public static class SwaggerJwtAuthExtensions 12 | { 13 | /// 14 | /// 添加swagger的Bearer token 15 | /// 16 | /// 17 | /// 18 | public static IServiceCollection AddSwaggerJwtAuth(this IServiceCollection services) 19 | { 20 | return services.AddSwaggerJwtAuth(null); 21 | } 22 | 23 | /// 24 | /// 添加swagger的Bearer token 25 | /// 26 | /// 27 | /// 说明 28 | /// 29 | public static IServiceCollection AddSwaggerJwtAuth(this IServiceCollection services, string description) 30 | { 31 | return services.PostConfigure(c => 32 | { 33 | c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme 34 | { 35 | Description = description ?? "请在下面输入token值", 36 | Name = "Authorization", 37 | In = ParameterLocation.Header, 38 | Scheme = "bearer", 39 | Type = SecuritySchemeType.Http, 40 | BearerFormat = "JWT" 41 | }); 42 | c.AddSecurityRequirement(new OpenApiSecurityRequirement 43 | { 44 | { 45 | new OpenApiSecurityScheme 46 | { 47 | Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } 48 | }, 49 | new List() 50 | } 51 | }); 52 | }); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Core.Web/JsonPatchs/JsonPatchDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Core.Web.JsonPatchs 6 | { 7 | /// 8 | /// 表示JsonPatch文档 9 | /// 10 | public class JsonPatchDocument : IEnumerable 11 | { 12 | /// 13 | /// 操作项 14 | /// 15 | private readonly JsonPatchOperation[] operations; 16 | 17 | /// 18 | /// JsonPatch文档 19 | /// 20 | /// 21 | public JsonPatchDocument(JsonPatchOperation[] operations) 22 | { 23 | this.operations = operations; 24 | } 25 | 26 | /// 27 | /// 应用所有选项到目标 28 | /// 29 | /// 30 | /// 目标 31 | /// 32 | public void ApplyTo(T target) 33 | { 34 | foreach (var op in this) 35 | { 36 | op.ApplyTo(target); 37 | } 38 | } 39 | 40 | /// 41 | /// 返回操作项迭代器 42 | /// 43 | /// 44 | public IEnumerator GetEnumerator() 45 | { 46 | foreach (var item in this.operations) 47 | { 48 | yield return item; 49 | } 50 | } 51 | 52 | /// 53 | /// 返回操作项迭代器 54 | /// 55 | /// 56 | IEnumerator IEnumerable.GetEnumerator() 57 | { 58 | return this.GetEnumerator(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Core.Web/JsonPatchs/JsonPatchInputFormatter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Formatters; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using Microsoft.Net.Http.Headers; 7 | using System; 8 | using System.Text; 9 | using System.Text.Json; 10 | using System.Threading.Tasks; 11 | 12 | namespace Core.Web.JsonPatchs 13 | { 14 | /// 15 | /// JsonPatch输入格式化工具 16 | /// 17 | public class JsonPatchInputFormatter : TextInputFormatter 18 | { 19 | /// 20 | /// JsonPatch输入格式化工具 21 | /// 22 | public JsonPatchInputFormatter() 23 | { 24 | this.SupportedEncodings.Add(UTF8EncodingWithoutBOM); 25 | this.SupportedEncodings.Add(UTF16EncodingLittleEndian); 26 | 27 | this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json-patch+json")); 28 | } 29 | 30 | /// 31 | /// 返回是否可以读取 32 | /// 33 | /// 34 | /// 35 | public override bool CanRead(InputFormatterContext context) 36 | { 37 | return context.ModelType == typeof(JsonPatchDocument); 38 | } 39 | 40 | /// 41 | /// 读取值 42 | /// 43 | /// 44 | /// 45 | /// 46 | public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) 47 | { 48 | var stream = encoding.CodePage == Encoding.UTF8.CodePage ? 49 | context.HttpContext.Request.Body : 50 | new TranscodingReadStream(context.HttpContext.Request.Body, encoding); 51 | 52 | try 53 | { 54 | var options = context.HttpContext.RequestServices.GetService>().Value.JsonSerializerOptions; 55 | var operations = await JsonSerializer.DeserializeAsync(stream, options); 56 | 57 | var model = new JsonPatchDocument(operations); 58 | return InputFormatterResult.Success(model); 59 | } 60 | catch (Exception ex) 61 | { 62 | var logger = context.HttpContext.RequestServices.GetService>(); 63 | logger.LogError(ex, ex.Message); 64 | return InputFormatterResult.Failure(); 65 | } 66 | finally 67 | { 68 | if (stream is TranscodingReadStream transcoding) 69 | { 70 | await transcoding.DisposeAsync(); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Core.Web/JsonPatchs/JsonPatchOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace Core.Web.JsonPatchs 8 | { 9 | /// 10 | /// 表示操作选项 11 | /// 12 | public class JsonPatchOperation 13 | { 14 | /// 15 | /// replace 16 | /// 17 | [JsonPropertyName("op")] 18 | public string Op { get; set; } 19 | 20 | /// 21 | /// /{propertyName} 22 | /// 23 | [JsonPropertyName("path")] 24 | public string Path { get; set; } 25 | 26 | /// 27 | /// 属性值 28 | /// 29 | [JsonPropertyName("value")] 30 | public JsonElement Value { get; set; } 31 | 32 | /// 33 | /// 是否为Replace的op 34 | /// 35 | /// 36 | public bool IsReplace() 37 | { 38 | return string.Equals(this.Op, "replace"); 39 | } 40 | 41 | /// 42 | /// 应用选项到目标 43 | /// 44 | /// 45 | /// 目标 46 | /// 47 | public void ApplyTo(T target) 48 | { 49 | if (this.IsReplace() == false) 50 | { 51 | throw new NotSupportedException($"not supported op: {this.Op}"); 52 | } 53 | 54 | if (target == null) 55 | { 56 | return; 57 | } 58 | 59 | var name = this.Path.NullThenEmpty().TrimStart('/'); 60 | var property = Property.GetProperty(name); 61 | if (property != null) 62 | { 63 | var value = this.GetValue(property.Info.PropertyType); 64 | property.SetValue(target, value); 65 | } 66 | } 67 | 68 | /// 69 | /// 获取值 70 | /// 71 | /// 72 | /// 73 | /// 74 | private object GetValue(Type type) 75 | { 76 | switch (this.Value.ValueKind) 77 | { 78 | case JsonValueKind.False: 79 | return false; 80 | 81 | case JsonValueKind.True: 82 | return true; 83 | 84 | case JsonValueKind.Null: 85 | return null; 86 | 87 | case JsonValueKind.Number: 88 | var number = this.Value.GetRawText(); 89 | return Converter.ConvertToType(number, type); 90 | 91 | case JsonValueKind.String: 92 | var str = this.Value.GetString(); 93 | return Converter.ConvertToType(str, type); 94 | 95 | default: 96 | throw new NotSupportedException(); 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Core.Web/Jwts/JwtOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Web.Jwts 2 | { 3 | /// 4 | /// jwt选项 5 | /// 6 | public class JwtOptions 7 | { 8 | /// 9 | /// jwt的cookie名称 10 | /// 11 | public string JwtCookieName { get; set; } = "jwt"; 12 | 13 | /// 14 | /// jwt的query名称 15 | /// 16 | public string JwtQueryName { get; set; } = "jwt"; 17 | 18 | /// 19 | /// 角色名称 20 | /// 默认是role 21 | /// 22 | public string RoleClaimType { get; set; } = "role"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Core.Web/Jwts/JwtParserMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Core.Jwt; 2 | using Microsoft.AspNetCore.Authentication.JwtBearer; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Options; 5 | using System; 6 | using System.IdentityModel.Tokens.Jwt; 7 | using System.Linq; 8 | using System.Security.Claims; 9 | using System.Threading.Tasks; 10 | 11 | namespace Core.Web.Jwts 12 | { 13 | /// 14 | /// jwt解析器中间件 15 | /// 16 | class JwtParserMiddleware : Middleware 17 | { 18 | /// 19 | /// 选项 20 | /// 21 | private readonly JwtOptions options; 22 | 23 | /// 24 | /// 中间件基类 25 | /// 26 | public JwtParserMiddleware(RequestDelegate next, IOptions options) 27 | : base(next) 28 | { 29 | this.options = options.Value; 30 | } 31 | 32 | /// 33 | /// 执行中间件 34 | /// 35 | /// 36 | /// 37 | public override Task InvokeAsync(HttpContext httpContext) 38 | { 39 | var jwt = this.ReadJwtSecurityToken(httpContext); 40 | if (jwt != null) 41 | { 42 | var json = jwt.Payload.SerializeToJson(); 43 | var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); 44 | identity.AddClaims(jwt.Claims); 45 | httpContext.User = new JwtClaimsPrincipal(identity, this.options.RoleClaimType, json); 46 | } 47 | 48 | return this.Next(httpContext); 49 | } 50 | 51 | 52 | /// 53 | /// 读取token 54 | /// 55 | /// 56 | /// 57 | private JwtSecurityToken ReadJwtSecurityToken(HttpContext httpContext) 58 | { 59 | if (httpContext.Request.Headers.TryGetValue("Authorization", out var bearerToken)) 60 | { 61 | var values = bearerToken.ToString().Split(' '); 62 | if (values.First().EqualsIgnoreCase("Bearer") == true) 63 | { 64 | var token = values.Last(); 65 | return new JwtSecurityToken(token); 66 | } 67 | } 68 | 69 | if (string.IsNullOrEmpty(this.options.JwtCookieName) == false) 70 | { 71 | if (httpContext.Request.Cookies.TryGetValue(this.options.JwtCookieName, out string token) == true) 72 | { 73 | return new JwtSecurityToken(token); 74 | } 75 | } 76 | 77 | if (string.IsNullOrEmpty(this.options.JwtQueryName) == false) 78 | { 79 | if (httpContext.Request.Query.TryGetValue(this.options.JwtQueryName, out var queryToken) == true) 80 | { 81 | var token = queryToken[0]; 82 | return new JwtSecurityToken(token); 83 | } 84 | } 85 | 86 | return null; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Core.Web/Middleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Web 5 | { 6 | /// 7 | /// 中间件基类 8 | /// 所有参数通过DI注册 9 | /// 10 | public abstract class Middleware 11 | { 12 | /// 13 | /// 获取下一个中间件 14 | /// 15 | protected RequestDelegate Next { get; } 16 | 17 | /// 18 | /// 中间件基类 19 | /// 20 | /// 21 | public Middleware(RequestDelegate next) 22 | { 23 | this.Next = next; 24 | } 25 | 26 | /// 27 | /// 执行中间件 28 | /// 29 | /// 30 | /// 31 | public abstract Task InvokeAsync(HttpContext httpContext); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Core.Web/ServiceOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Web 2 | { 3 | /// 4 | /// 表示服务信息 5 | /// 6 | public class ServiceOptions : IConfigureOptions 7 | { 8 | /// 9 | /// 服务名称 10 | /// 11 | public string Name { get; set; } 12 | 13 | /// 14 | /// 健康检查的路由 15 | /// 16 | public string HealthRoute { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Core.Web/Swaggers/SwaggerApiVersionHeaderFilter.cs: -------------------------------------------------------------------------------- 1 | using Core.Web.Conventions; 2 | using Microsoft.OpenApi.Any; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Core.Web.Swaggers 9 | { 10 | /// 11 | /// 为swagger的api增加x-api-version请求头 12 | /// 13 | public class SwaggerApiVersionHeaderFilter : IOperationFilter 14 | { 15 | /// 16 | /// 获取请求头名 17 | /// 18 | public string HeaderName { get; } 19 | 20 | /// 21 | /// swagger的api增加x-api-version请求头 22 | /// 23 | /// 24 | public SwaggerApiVersionHeaderFilter(string headerName = "x-api-version") 25 | { 26 | this.HeaderName = headerName ?? throw new ArgumentNullException(nameof(headerName)); 27 | } 28 | 29 | /// 30 | /// 添加请求头 31 | /// 32 | /// 33 | /// 34 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 35 | { 36 | string GetVersionName() 37 | => ApiExplorerGroupNameConvention.GetApiVersion(context.ApiDescription.GroupName).ToString(); 38 | 39 | if (operation.Parameters == null) 40 | { 41 | operation.Parameters = new List(); 42 | } 43 | 44 | var apiversion = new OpenApiParameter 45 | { 46 | In = ParameterLocation.Header, 47 | Required = false, 48 | Name = this.HeaderName, 49 | Style = ParameterStyle.Simple, 50 | Description = "api版本,不填则对应1.0", 51 | Schema = new OpenApiSchema { Type = "string", Default = new OpenApiString(GetVersionName()) } 52 | }; 53 | operation.Parameters.Add(apiversion); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Core.Web/Swaggers/SwaggerGenOptionsConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Core.Web.Conventions; 2 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Options; 5 | using Microsoft.OpenApi.Models; 6 | using Swashbuckle.AspNetCore.SwaggerGen; 7 | 8 | namespace Core.Web.Swaggers 9 | { 10 | /// 11 | /// 配置SwaggerGenOptions的SwaggerDoc 12 | /// 13 | public class SwaggerGenOptionsConfigureOptions : IConfigureOptions 14 | { 15 | private readonly IApiDescriptionGroupCollectionProvider apiExplorer; 16 | private readonly ServiceOptions thisService; 17 | 18 | /// 19 | /// 配置SwaggerGenOptions的SwaggerDoc 20 | /// 21 | /// 22 | /// 23 | public SwaggerGenOptionsConfigureOptions(IApiDescriptionGroupCollectionProvider apiExplorer, IOptions thisService) 24 | { 25 | this.apiExplorer = apiExplorer; 26 | this.thisService = thisService.Value; 27 | } 28 | 29 | /// 30 | /// 配置 31 | /// 32 | /// 33 | public void Configure(SwaggerGenOptions options) 34 | { 35 | foreach (var group in apiExplorer.ApiDescriptionGroups.Items) 36 | { 37 | options.SwaggerDoc(group.GroupName, new OpenApiInfo 38 | { 39 | Title = $"{thisService.Name}", 40 | Version = "v" + ApiExplorerGroupNameConvention.GetApiVersion(group.GroupName).ToString() 41 | }); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Core.Web/Swaggers/SwaggerOptionsConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Microsoft.OpenApi.Models; 3 | using Swashbuckle.AspNetCore.Swagger; 4 | using System.Collections.Generic; 5 | 6 | namespace Core.Web.Swaggers 7 | { 8 | /// 9 | /// SwaggerOptions的RouteTemplate配置 10 | /// 11 | public class SwaggerOptionsConfigureOptions : IConfigureOptions 12 | { 13 | private readonly ServiceOptions thisService; 14 | 15 | /// 16 | /// SwaggerOptions的RouteTemplate配置 17 | /// 18 | /// 19 | public SwaggerOptionsConfigureOptions(IOptions thisService) 20 | { 21 | this.thisService = thisService.Value; 22 | } 23 | 24 | /// 25 | /// 配置 26 | /// 27 | /// 28 | public void Configure(SwaggerOptions options) 29 | { 30 | options.RouteTemplate = $"/swagger/{thisService.Name}/{{documentName}}/swagger.json"; 31 | options.PreSerializeFilters.Add((swagger, request) => 32 | { 33 | swagger.Servers = new List(); 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Core.Web/Swaggers/SwaggerRequiredParameterFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | using System; 4 | 5 | namespace Core.Web.Swaggers 6 | { 7 | /// 8 | /// 表示swagger的值类型参数自动Required标记过滤器 9 | /// 10 | public class SwaggerRequiredParameterFilter : IParameterFilter 11 | { 12 | /// 13 | /// 应用参数过滤器 14 | /// 15 | /// 16 | /// 17 | public void Apply(OpenApiParameter parameter, ParameterFilterContext context) 18 | { 19 | if (context.ApiParameterDescription.DefaultValue == null) 20 | { 21 | if (context.ApiParameterDescription.Type.CanBeNullValue() == false) 22 | { 23 | parameter.Required = true; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Core.Web/Swaggers/SwaggerRequiredSchemaFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Reflection; 7 | using System.Text.Json; 8 | 9 | namespace Core.Web.Swaggers 10 | { 11 | /// 12 | /// 表示swagger的值类型自动Required标记过滤器 13 | /// 14 | public class SwaggerRequiredSchemaFilter : ISchemaFilter 15 | { 16 | private readonly JsonNamingPolicy camelCaseContractResolver; 17 | 18 | /// 19 | /// swagger的值类型自动Required标记过滤器 20 | /// 21 | /// 22 | public SwaggerRequiredSchemaFilter(bool camelCasePropertyNames) 23 | { 24 | if (camelCasePropertyNames == true) 25 | { 26 | this.camelCaseContractResolver = JsonNamingPolicy.CamelCase; 27 | } 28 | } 29 | 30 | /// 31 | /// 获取属性名 32 | /// 33 | /// 属性 34 | /// 35 | private string GetPropertyName(PropertyInfo property) 36 | { 37 | if (this.camelCaseContractResolver == null) 38 | { 39 | return property.Name; 40 | } 41 | return this.camelCaseContractResolver.ConvertName(property.Name); 42 | } 43 | 44 | /// 45 | /// 应用模型过滤器 46 | /// 47 | /// 48 | /// 49 | public void Apply(OpenApiSchema schema, SchemaFilterContext context) 50 | { 51 | foreach (var property in context.Type.GetProperties()) 52 | { 53 | var schemaPropertyName = this.GetPropertyName(property); 54 | if (schema.Properties?.ContainsKey(schemaPropertyName) == true) 55 | { 56 | var propertyType = property.PropertyType; 57 | if (propertyType.IsValueType == true) 58 | { 59 | var isNullable = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); 60 | if (isNullable == false && property.IsDefined(typeof(RequiredAttribute)) == false) 61 | { 62 | if (schema.Required == null) 63 | { 64 | schema.Required = new SortedSet(); 65 | } 66 | schema.Required.Add(schemaPropertyName); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Core.Web/Swaggers/SwaggerUIOptionsConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 3 | using Microsoft.Extensions.Options; 4 | using Swashbuckle.AspNetCore.SwaggerUI; 5 | using System.Linq; 6 | 7 | namespace Core.Web.Swaggers 8 | { 9 | /// 10 | /// 配置SwaggerUIOptions的SwaggerEndpoint 11 | /// 12 | public class SwaggerUIOptionsConfigureOptions : IConfigureOptions 13 | { 14 | private readonly IApiDescriptionGroupCollectionProvider apiExplorer; 15 | private readonly ServiceOptions thisService; 16 | 17 | /// 18 | /// 配置SwaggerUIOptions的SwaggerEndpoint 19 | /// 20 | /// 21 | /// 22 | public SwaggerUIOptionsConfigureOptions(IApiDescriptionGroupCollectionProvider apiExplorer, IOptions thisService) 23 | { 24 | this.apiExplorer = apiExplorer; 25 | this.thisService = thisService.Value; 26 | } 27 | 28 | /// 29 | /// 配置 30 | /// 31 | /// 32 | public void Configure(SwaggerUIOptions options) 33 | { 34 | options.RoutePrefix = $"swagger/{thisService.Name}"; 35 | options.DocumentTitle = $"{thisService.Name}的openApi文档"; 36 | 37 | foreach (var group in apiExplorer.ApiDescriptionGroups.Items.OrderBy(item => item.GroupName)) 38 | { 39 | options.SwaggerEndpoint($"/swagger/{thisService.Name}/{group.GroupName}/swagger.json", group.GroupName); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Core/ApiEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core 4 | { 5 | /// 6 | /// 定义表示Api事件的接口 7 | /// 8 | public interface IApiEvent 9 | { 10 | /// 11 | /// 请求的uri 12 | /// 13 | Uri Uri { get; set; } 14 | 15 | /// 16 | /// 请求的内容 17 | /// 18 | object Content { get; set; } 19 | 20 | /// 21 | /// 用户Id 22 | /// 23 | string UserId { get; set; } 24 | 25 | /// 26 | /// 创建时间 27 | /// 28 | DateTime CreateTime { get; set; } 29 | } 30 | 31 | /// 32 | /// 表示Api事件 33 | /// 34 | public class ApiEvent : IApiEvent 35 | { 36 | /// 37 | /// 请求的uri 38 | /// 39 | public Uri Uri { get; set; } 40 | 41 | /// 42 | /// 请求的内容 43 | /// 44 | public object Content { get; set; } 45 | 46 | /// 47 | /// 用户Id 48 | /// 49 | public string UserId { get; set; } 50 | 51 | /// 52 | /// 创建时间 53 | /// 54 | public DateTime CreateTime { get; set; } 55 | } 56 | 57 | 58 | /// 59 | /// 表示Api事件 60 | /// 61 | /// 62 | public class ApiEvent : IApiEvent 63 | { 64 | /// 65 | /// 请求的uri 66 | /// 67 | public Uri Uri { get; set; } 68 | 69 | /// 70 | /// 请求的内容 71 | /// 72 | public TContent Content { get; set; } 73 | 74 | /// 75 | /// 用户Id 76 | /// 77 | public string UserId { get; set; } 78 | 79 | /// 80 | /// 创建时间 81 | /// 82 | public DateTime CreateTime { get; set; } 83 | 84 | /// 85 | /// 接口显式实现 86 | /// 87 | object IApiEvent.Content 88 | { 89 | get => this.Content; 90 | set => this.Content = (TContent)value; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Core/ApiResult.cs: -------------------------------------------------------------------------------- 1 | namespace Core 2 | { 3 | /// 4 | /// 提供生成Api响应结果 5 | /// 6 | public static class ApiResult 7 | { 8 | /// 9 | /// 生成无错误的结果 10 | /// 11 | /// 12 | /// 数据 13 | /// 14 | public static ApiResult NoError(T data) 15 | { 16 | return new ApiResult 17 | { 18 | Code = Code.NoError, 19 | Data = data 20 | }; 21 | } 22 | 23 | /// 24 | /// 生成参数错误的结果 25 | /// 26 | /// 27 | /// 提示消息 28 | /// 29 | public static ApiResult ParameterError(string message) 30 | { 31 | return new ApiResult 32 | { 33 | Code = Code.ParameterError, 34 | Message = message 35 | }; 36 | } 37 | } 38 | 39 | /// 40 | /// Api响应结果接口 41 | /// 42 | public interface IApiResult 43 | { 44 | /// 45 | /// 获取或设置状态码 46 | /// 47 | Code Code { get; set; } 48 | 49 | /// 50 | /// 获取或设置提示消息 51 | /// 52 | string Message { get; set; } 53 | 54 | /// 55 | /// 获取或设置数据 56 | /// 57 | object Data { get; set; } 58 | } 59 | 60 | 61 | /// 62 | /// 表示Api响应结果 63 | /// 64 | public class ApiResult : IApiResult 65 | { 66 | /// 67 | /// 获取或设置状态码 68 | /// 69 | public Code Code { get; set; } 70 | 71 | /// 72 | /// 获取或设置提示消息 73 | /// 74 | public string Message { get; set; } 75 | 76 | /// 77 | /// 获取或设置数据 78 | /// 79 | public T Data { get; set; } 80 | 81 | /// 82 | /// 获取或设置数据 83 | /// 84 | object IApiResult.Data 85 | { 86 | get => this.Data; 87 | set => this.Data = (T)value; 88 | } 89 | 90 | /// 91 | /// 从data转换得到 92 | /// 93 | /// 94 | public static implicit operator ApiResult(T data) 95 | { 96 | return ApiResult.NoError(data); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Core/Apk/ApKReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Security; 6 | using System.Xml.Linq; 7 | 8 | namespace Core.Apk 9 | { 10 | /// 11 | /// Apk读取器 12 | /// 13 | public static class ApkReader 14 | { 15 | /// 16 | /// 获取版本信息 17 | /// 18 | /// apk文件路径 19 | /// 20 | /// 21 | /// 22 | public static ApkMedia ReadMedia(string apkFile) 23 | { 24 | if (apkFile == null) 25 | { 26 | throw new ArgumentNullException(nameof(apkFile)); 27 | } 28 | 29 | using var apkStream = new FileStream(apkFile, FileMode.Open, FileAccess.Read); 30 | return ReadMedia(apkStream); 31 | } 32 | 33 | /// 34 | /// 获取版本信息 35 | /// 36 | /// apk流 37 | /// 38 | /// 39 | /// 40 | public static ApkMedia ReadMedia(Stream apkStream) 41 | { 42 | if (apkStream == null) 43 | { 44 | throw new ArgumentNullException(nameof(apkStream)); 45 | } 46 | 47 | var position = apkStream.CanSeek ? apkStream.Position : 0L; 48 | using var zip = new ZipArchive(apkStream, ZipArchiveMode.Read); 49 | const string manifestName = "AndroidManifest.xml"; 50 | var manifest = zip.Entries.FirstOrDefault(item => item.Name.EqualsIgnoreCase(manifestName)); 51 | if (manifest == null) 52 | { 53 | throw new BadImageFormatException($"找不到{manifestName},文件可能不是有效有apk格式"); 54 | } 55 | 56 | var manifestStream = new MemoryStream(); 57 | manifest.Open().CopyTo(manifestStream); 58 | var manifestBytes = manifestStream.ToArray(); 59 | 60 | var xml = ManifestReader.ReadAsXmlString(manifestBytes); 61 | var doc = XDocument.Parse(xml); 62 | 63 | var versionCode = doc.Root.Attribute("versionCode").Value; 64 | var versionName = doc.Root.Attribute("versionName").Value; 65 | var package = doc.Root.Attribute("package").Value; 66 | 67 | var media = new ApkMedia 68 | { 69 | VersionCode = int.Parse(versionCode), 70 | VersionName = versionName, 71 | Package = package 72 | }; 73 | 74 | if (apkStream.CanSeek == true) 75 | { 76 | apkStream.Position = position; 77 | media.FileMd5 = MD5.ComputeHashString(apkStream, false); 78 | } 79 | 80 | return media; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Core/Apk/ApkMedia.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Apk 2 | { 3 | /// 4 | /// Apk信息 5 | /// 6 | public class ApkMedia 7 | { 8 | /// 9 | /// 主版本 10 | /// 11 | public int VersionCode { get; set; } 12 | 13 | /// 14 | /// 版本名 15 | /// 16 | public string VersionName { get; set; } 17 | 18 | /// 19 | /// 包名 20 | /// 21 | public string Package { get; set; } 22 | 23 | /// 24 | /// 文件Md5加密 25 | /// 26 | public string FileMd5 { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Core/Apk/ManifestReader.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Net-Team/Web.Template/be93830a2f7056c4f2189a2e63b77f1b0f67b023/Core/Apk/ManifestReader.cs -------------------------------------------------------------------------------- /Core/Code.cs: -------------------------------------------------------------------------------- 1 | namespace Core 2 | { 3 | /// 4 | /// 表示Api业务状态码 5 | /// 6 | public enum Code 7 | { 8 | /// 9 | /// 无错误 10 | /// 11 | NoError = 0, 12 | 13 | /// 14 | /// 参数错误 15 | /// 参数验证不通过 16 | /// 17 | ParameterError = 1, 18 | 19 | /// 20 | /// 服务器处理异常 21 | /// 22 | ServiceError = 2, 23 | 24 | /// 25 | /// 其它错误 26 | /// 27 | Other = 99 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Core/Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 21.12.6 5 | net5.0 6 | WA2001,NU5104 7 | 8 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 9 | true 10 | Taichuan.Core 11 | 通用应用需要的依赖注册、选项配置、Http客户端、Http接口返回类型和其它工具类 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Core/HostedService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Core 9 | { 10 | /// 11 | /// 表示托管服务抽象类 12 | /// 该对象只适合在应用启动时做等待执行的短时间任务 13 | /// 任务并不会在后台运行 14 | /// 15 | public abstract class HostedService : Disposable, IHostedService 16 | { 17 | /// 18 | /// 服务提供者 19 | /// 20 | private readonly IServiceProvider services; 21 | 22 | /// 23 | /// 托管服务抽象类 24 | /// 该对象只适合在应用启动时做等待执行的短时间任务 25 | /// 任务并不会在后台运行 26 | /// 27 | /// 服务提供者 28 | public HostedService(IServiceProvider services) 29 | { 30 | this.services = services; 31 | } 32 | 33 | /// 34 | /// 服务启动入口 35 | /// 36 | /// 37 | /// 38 | async Task IHostedService.StartAsync(CancellationToken cancellationToken) 39 | { 40 | try 41 | { 42 | await this.StartAsync(cancellationToken); 43 | } 44 | catch (Exception ex) 45 | { 46 | await this.HandleExceptionAsync(ex); 47 | } 48 | } 49 | 50 | /// 51 | /// 启动服务 52 | /// 53 | /// 54 | /// 55 | protected abstract Task StartAsync(CancellationToken cancellationToken); 56 | 57 | 58 | /// 59 | /// 服务停止入口 60 | /// 61 | /// 62 | /// 63 | async Task IHostedService.StopAsync(CancellationToken cancellationToken) 64 | { 65 | try 66 | { 67 | await this.StopAsync(cancellationToken); 68 | } 69 | catch (Exception ex) 70 | { 71 | await this.HandleExceptionAsync(ex); 72 | } 73 | } 74 | 75 | /// 76 | /// 服务停止时 77 | /// 如果应用意外关闭(例如,应用的进程失败),则可能不会调用 StopAsync 78 | /// 79 | /// 80 | /// 81 | protected virtual Task StopAsync(CancellationToken cancellationToken) 82 | { 83 | return Task.CompletedTask; 84 | } 85 | 86 | /// 87 | /// 创建具有指定生命周期范围的服务提供者 88 | /// 89 | /// 90 | protected IScopeServiceProvider CreateScopeServiceProvider() 91 | { 92 | var serviceScope = this.services.CreateScope(); 93 | return new ScopeServiceProvider(serviceScope); 94 | } 95 | 96 | /// 97 | /// 处理异常 98 | /// 默认是输出异常日志 99 | /// 100 | /// 101 | /// 102 | protected virtual Task HandleExceptionAsync(Exception ex) 103 | { 104 | this.services 105 | .GetService()? 106 | .CreateLogger(this.GetType().FullName)? 107 | .LogError(ex, ex.Message); 108 | 109 | return Task.CompletedTask; 110 | } 111 | 112 | /// 113 | /// 释放资源 114 | /// 115 | /// 116 | protected override void Dispose(bool disposing) 117 | { 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Core/HttpApis/GatewayAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Options; 3 | using System.Threading.Tasks; 4 | using WebApiClientCore; 5 | using WebApiClientCore.Attributes; 6 | 7 | namespace Core.HttpApis 8 | { 9 | /// 10 | /// 标记http接口配置网关的域名 11 | /// 12 | public class GatewayAttribute : HttpHostBaseAttribute 13 | { 14 | /// 15 | /// 请求路径 16 | /// 17 | public HttpPath Path { get; } 18 | 19 | /// 20 | /// http接口配置网关的域名 21 | /// 22 | public GatewayAttribute() 23 | : this(null) 24 | { 25 | } 26 | 27 | /// 28 | /// http接口配置网关的域名 29 | /// 30 | /// 路径 31 | public GatewayAttribute(string path) 32 | { 33 | this.Path = HttpPath.Create(path); 34 | } 35 | 36 | /// 37 | /// 请求前 38 | /// 39 | /// 40 | /// 41 | public override Task OnRequestAsync(ApiRequestContext context) 42 | { 43 | var host = context.HttpContext.ServiceProvider.GetService>().CurrentValue.HttpHost; 44 | context.HttpContext.RequestMessage.RequestUri = this.Path.MakeUri(host); 45 | return Task.CompletedTask; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Core/HttpApis/GatewayOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.HttpApis 4 | { 5 | /// 6 | /// 网关代理选项 7 | /// 8 | public class GatewayOptions : IConfigureOptions 9 | { 10 | /// 11 | /// 网关代理地址 12 | /// 13 | public Uri HttpHost { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/HttpApis/ServiceCollectionHttpApiExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Net.Http; 5 | using System.Reflection; 6 | using WebApiClientCore; 7 | 8 | namespace Core.HttpApis 9 | { 10 | /// 11 | /// HttpApi注册 12 | /// 13 | public static class ServiceCollectionHttpApiExtensions 14 | { 15 | /// 16 | /// 注册程序集下所有IHttpApi 17 | /// 18 | /// 19 | /// 20 | /// 21 | public static IServiceCollection AddHttpApis(this IServiceCollection services, Assembly assembly, IConfiguration configuration) 22 | { 23 | foreach (var httpApiType in assembly.GetTypes()) 24 | { 25 | if (httpApiType.IsInterface && httpApiType.IsInheritFrom()) 26 | { 27 | services 28 | .AddHttpApi(httpApiType) 29 | .ConfigureHttpApi(configuration.GetSection(httpApiType.Name)) 30 | .ConfigurePrimaryHttpMessageHandler(() => 31 | { 32 | return new HttpClientHandler { ServerCertificateCustomValidationCallback = (a, b, c, d) => true }; 33 | }); 34 | } 35 | } 36 | 37 | return services; 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Core/IApiEventPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Core 4 | { 5 | /// 6 | /// 定义Api消息发布器的接口 7 | /// 8 | public interface IApiEventPublisher 9 | { 10 | /// 11 | /// 发布Api请求消息 12 | /// 13 | /// 通道 14 | /// Api事件 15 | /// 16 | Task PulishAsync(string channel, IApiEvent apiEvent); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Core/IConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Core 2 | { 3 | /// 4 | /// 定义绑定到配置的选项接口 5 | /// 6 | public interface IConfigureOptions 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Core/IScopeServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core 4 | { 5 | /// 6 | /// 定义具有指定生命周期范围的服务提供者接口 7 | /// 8 | public interface IScopeServiceProvider : IServiceProvider, IDisposable 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/IScopedDependency.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Core 4 | { 5 | /// 6 | /// 定义IScoped模式的对象注入接口 7 | /// 8 | [Register(ServiceLifetime.Scoped)] 9 | public interface IScopedDependency 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/ISingletonDependency.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Core 4 | { 5 | /// 6 | /// 定义Singleton模式的对象注入接口 7 | /// 8 | [Register(ServiceLifetime.Singleton)] 9 | public interface ISingletonDependency 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/ITransientDependency.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Core 4 | { 5 | /// 6 | /// 定义Transient模式的对象注入接口 7 | /// 8 | [Register(ServiceLifetime.Transient)] 9 | public interface ITransientDependency 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/Jwt/JwtClaimsPrincipal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Text.Json; 5 | 6 | namespace Core.Jwt 7 | { 8 | /// 9 | /// jwt凭据信息 10 | /// 11 | public class JwtClaimsPrincipal : ClaimsPrincipal 12 | { 13 | private readonly string roleClaimType; 14 | private readonly string payloadJson; 15 | 16 | /// 17 | /// jwt凭据信息 18 | /// 19 | /// 20 | /// 21 | /// 22 | public JwtClaimsPrincipal(ClaimsIdentity identity, string roleClaimType, string payloadJson) 23 | : base(identity) 24 | { 25 | this.roleClaimType = roleClaimType; 26 | this.payloadJson = payloadJson; 27 | } 28 | 29 | /// 30 | /// 是否在角色里 31 | /// 32 | /// 角色 33 | /// 34 | public override bool IsInRole(string role) 35 | { 36 | return this.HasClaim(this.roleClaimType, role); 37 | } 38 | 39 | /// 40 | /// 是否有Claim 41 | /// 42 | /// 43 | /// 44 | /// 45 | public override bool HasClaim(string type, string value) 46 | { 47 | return this.Claims.Any(item => item.Type.EqualsIgnoreCase(type) && item.Value.EqualsIgnoreCase(value)); 48 | } 49 | 50 | /// 51 | /// 查找第一个claim 52 | /// 53 | /// 54 | /// 55 | public override Claim FindFirst(string type) 56 | { 57 | return this.Claims.FirstOrDefault(item => item.Type.EqualsIgnoreCase(type)); 58 | } 59 | 60 | /// 61 | /// 转换为字符串 62 | /// 63 | /// 64 | public override string ToString() 65 | { 66 | return this.payloadJson; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Core/Jwt/RSJwt.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IdentityModel.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IdentityModel.Tokens.Jwt; 5 | using System.Security.Claims; 6 | using System.Security.Cryptography.X509Certificates; 7 | 8 | namespace Core.Jwt 9 | { 10 | /// 11 | /// 提供RSA-SHA jwt的生成 12 | /// 13 | public class RSJwt 14 | { 15 | /// 16 | /// 证书 17 | /// 18 | private readonly X509Certificate2 certificate; 19 | 20 | /// 21 | /// RSA-SHA jwt 22 | /// 23 | /// 证书 1024位或以上 24 | public RSJwt(X509Certificate2 certificate) 25 | { 26 | this.certificate = certificate; 27 | } 28 | 29 | /// 30 | /// 创建jwt 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | public string CreateToken(string issuer, string audience, IEnumerable claims, DateTime? expires, string alg = SecurityAlgorithms.RsaSha256) 39 | { 40 | var signingCredentials = new SigningCredentials(new X509SecurityKey(this.certificate), alg); 41 | var jwtHandler = new JwtSecurityTokenHandler(); 42 | 43 | var jwt = jwtHandler.CreateJwtSecurityToken( 44 | issuer: issuer, 45 | audience: audience, 46 | expires: expires, 47 | notBefore: DateTime.Now, 48 | signingCredentials: signingCredentials, 49 | subject: new ClaimsIdentity(claims) 50 | ); 51 | return jwtHandler.WriteToken(jwt); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Core/Menus/MenuGroup.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Core.Menus 4 | { 5 | /// 6 | /// 表示菜单项分组 7 | /// 8 | public class MenuGroup 9 | { 10 | /// 11 | /// 获取或设置分组名 12 | /// 13 | [Required] 14 | public string Group { get; set; } 15 | 16 | /// 17 | /// 获取或设置菜单项 18 | /// 19 | [Required] 20 | public MenuItem[] Items { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Core/Menus/MenuItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace Core.Menus 6 | { 7 | /// 8 | /// 表示菜单信息 9 | /// 10 | public class MenuItem : IEquatable 11 | { 12 | private string path; 13 | 14 | /// 15 | /// 获取或设置分组名称 16 | /// 17 | [Required] 18 | public string GroupName { get; set; } 19 | 20 | /// 21 | /// 或取或设置名称 22 | /// 23 | [Required] 24 | public string Name { get; set; } 25 | 26 | /// 27 | /// 获取或设置类名 28 | /// 29 | public string Class { get; set; } 30 | 31 | /// 32 | /// 获取或设置Path名称 33 | /// 34 | public string Path 35 | { 36 | get => path ?? (this.RelativePath == null ? null : System.IO.Path.GetFileName(this.RelativePath)); 37 | set => path = value; 38 | } 39 | 40 | /// 41 | /// 获取或设置相对Uri 42 | /// 43 | [Required] 44 | public string RelativePath { get; set; } 45 | 46 | /// 47 | /// 是否可用 48 | /// 49 | public bool Enable { get; set; } 50 | 51 | /// 52 | /// 与目标是否相等 53 | /// 54 | /// 55 | /// 56 | public bool Equals([AllowNull] MenuItem other) 57 | { 58 | return other != null && other.RelativePath == this.RelativePath; 59 | } 60 | 61 | /// 62 | /// 获取哈希码 63 | /// 64 | /// 65 | public override int GetHashCode() 66 | { 67 | return this.RelativePath == null ? 0 : this.RelativePath.GetHashCode(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Core/Page.cs: -------------------------------------------------------------------------------- 1 | using QMapper; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | 6 | namespace Core 7 | { 8 | /// 9 | /// 表示分页内容 10 | /// 11 | /// 数据 12 | public class Page where T : class 13 | { 14 | /// 15 | /// 页面索引,0开始 16 | /// 17 | public int PageIndex { get; set; } 18 | 19 | /// 20 | /// 页面记录大小 21 | /// 22 | public int PageSize { get; set; } 23 | 24 | /// 25 | /// 全部记录条数 26 | /// 27 | public int TotalCount { get; set; } 28 | 29 | /// 30 | /// 数据集合 31 | /// 32 | [Required] 33 | public T[] DataArray { get; set; } 34 | 35 | /// 36 | /// 将分页数据内容映射为其它类型 37 | /// 38 | /// 39 | /// 40 | public Page MapAs() where TNew : class, new() 41 | { 42 | var mapper = Map.From().Compile(); 43 | return this.MapAs(item => mapper.Map(item)); 44 | } 45 | 46 | /// 47 | /// 将分页数据内容映射为其它类型 48 | /// 49 | /// 50 | /// 51 | /// 52 | public Page MapAs(Func selector) where TNew : class 53 | { 54 | return new Page 55 | { 56 | PageIndex = this.PageIndex, 57 | PageSize = this.PageSize, 58 | TotalCount = this.TotalCount, 59 | DataArray = this.DataArray.Select(selector).ToArray() 60 | }; 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Core/RegisterAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace Core 5 | { 6 | /// 7 | /// 表示将当前实现类型注册为服务的特性 8 | /// 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)] 10 | public sealed class RegisterAttribute : Attribute 11 | { 12 | /// 13 | /// 获取服务的生命周期 14 | /// 15 | public ServiceLifetime Lifetime { get; } 16 | 17 | /// 18 | /// 获取或设置注册的服务类型 19 | /// 为null直接使得当前类型 20 | /// 21 | public Type ServiceType { get; set; } 22 | 23 | /// 24 | /// 将当前实现类型注册为服务的特性 25 | /// 26 | /// 生命周期 27 | public RegisterAttribute(ServiceLifetime lifetime) 28 | { 29 | Lifetime = lifetime; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Core/ScopeServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace Core 5 | { 6 | /// 7 | /// 表示具有指定生命周期范围的服务提供者 8 | /// 9 | class ScopeServiceProvider : Disposable, IScopeServiceProvider 10 | { 11 | /// 12 | /// 服务作用域 13 | /// 14 | private readonly IServiceScope serviceScope; 15 | 16 | /// 17 | /// 具有指定生命周期范围的服务提供者 18 | /// 19 | /// 20 | public ScopeServiceProvider(IServiceScope serviceScope) 21 | { 22 | this.serviceScope = serviceScope; 23 | } 24 | 25 | /// 26 | /// 获取服务 27 | /// 28 | /// 29 | /// 30 | public object GetService(Type serviceType) 31 | { 32 | return this.serviceScope.ServiceProvider.GetService(serviceType); 33 | } 34 | 35 | /// 36 | /// 释放资源 37 | /// 38 | /// 39 | protected override void Dispose(bool disposing) 40 | { 41 | this.serviceScope.Dispose(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Core/ServiceCollectionDependencyExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Core 6 | { 7 | /// 8 | /// 依赖注入注册 9 | /// 10 | public static class ServiceCollectionDependencyExtensions 11 | { 12 | /// 13 | /// 注册程序集下实现依赖注入接口的类型 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static IServiceCollection AddDependencyServices(this IServiceCollection services, Assembly assembly) 19 | { 20 | var types = assembly.GetTypes().Where(item => item.IsClass && item.IsAbstract == false).ToArray(); 21 | foreach (var impType in types) 22 | { 23 | var register = impType.GetCustomAttribute(); 24 | if (register == null) 25 | { 26 | register = impType 27 | .GetInterfaces() 28 | .Select(item => item.GetCustomAttribute()) 29 | .FirstOrDefault(item => item != null); 30 | } 31 | 32 | if (register != null) 33 | { 34 | var descriptor = ServiceDescriptor.Describe(register.ServiceType ?? impType, impType, register.Lifetime); 35 | if (services.Any(item => item.ServiceType == descriptor.ServiceType && item.ImplementationType == descriptor.ImplementationType) == false) 36 | { 37 | services.Add(descriptor); 38 | } 39 | } 40 | } 41 | return services; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Core/ServiceCollectionOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace Core 7 | { 8 | /// 9 | /// IConfigureOptions自动注册扩展 10 | /// 11 | public static class ServiceCollectionOptionsExtensions 12 | { 13 | /// 14 | /// 注册程序集下实现IConfigureOptions接口的Options 15 | /// 并关联到configuration的子项 16 | /// 17 | /// 18 | /// 19 | /// 20 | public static IServiceCollection AddConfigureOptions(this IServiceCollection services, Assembly assembly, IConfiguration configuration) 21 | { 22 | foreach (var optionsType in assembly.GetTypes()) 23 | { 24 | if (optionsType.IsInheritFrom() == false) 25 | { 26 | continue; 27 | } 28 | 29 | var builderType = typeof(BindOptionsBuilder<>).MakeGenericType(optionsType); 30 | var builder = (IBindOptionsBuilder)Activator.CreateInstance(builderType); 31 | builder.Bind(services, configuration.GetSection(optionsType.Name)); 32 | } 33 | return services; 34 | } 35 | 36 | private interface IBindOptionsBuilder 37 | { 38 | void Bind(IServiceCollection services, IConfiguration configuration); 39 | } 40 | 41 | private class BindOptionsBuilder : IBindOptionsBuilder where TOptions : class 42 | { 43 | public void Bind(IServiceCollection services, IConfiguration configuration) 44 | { 45 | services.AddOptions().Bind(configuration); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Core/Xls/XlsColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Xls 4 | { 5 | /// 6 | /// 表示xls的列 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public sealed class XlsColumnAttribute : Attribute 10 | { 11 | /// 12 | /// 获取或设置列表 13 | /// 14 | public string Name { get; } 15 | 16 | /// 17 | /// 获取或设置XlsColumnParser的类型 18 | /// 19 | public Type ParserType { get; set; } 20 | 21 | /// 22 | /// 获取或设置是否忽略这个列 23 | /// 24 | public bool Ignore { get; set; } 25 | 26 | /// 27 | /// xls的列 28 | /// 29 | public XlsColumnAttribute() 30 | : this(null) 31 | { 32 | } 33 | 34 | /// 35 | /// xls的列 36 | /// 37 | /// 命名 38 | public XlsColumnAttribute(string name) 39 | { 40 | this.Name = name; 41 | this.ParserType = typeof(XlsColumnParser); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Core/Xls/XlsColumnParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Xls 4 | { 5 | /// 6 | /// 表示xls字段解析器 7 | /// 8 | public class XlsColumnParser 9 | { 10 | /// 11 | /// 解析字段 12 | /// 13 | /// 从xls读出的原始值 14 | /// 目标属性类型 15 | /// 16 | public virtual object Parse(object value, Type targetType) 17 | { 18 | if (value == null || value == DBNull.Value) 19 | { 20 | return targetType.DefaultValue(); 21 | } 22 | return Converter.ConvertToType(value, targetType); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Corefx/AsyncRoot.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace System 5 | { 6 | /// 7 | /// 提供异步锁 8 | /// 9 | public class AsyncRoot : IDisposable 10 | { 11 | /// 12 | /// 信号量 13 | /// 14 | private readonly SemaphoreSlim semaphoreSlim; 15 | 16 | /// 17 | /// 异步锁 18 | /// 19 | public AsyncRoot() 20 | : this(1) 21 | { 22 | } 23 | 24 | /// 25 | /// 异步锁 26 | /// 27 | /// 允许并行的线程数 28 | public AsyncRoot(int concurrent) 29 | { 30 | this.semaphoreSlim = new SemaphoreSlim(concurrent, concurrent); 31 | } 32 | 33 | /// 34 | /// 锁住代码块 35 | /// using( asyncRoot.Lock() ){ } 36 | /// 37 | /// 38 | public IDisposable Lock() 39 | { 40 | this.semaphoreSlim.Wait(); 41 | return new UnLocker(this.semaphoreSlim); 42 | } 43 | 44 | /// 45 | /// 锁住代码块 46 | /// using( await asyncRoot.LockAsync() ){ } 47 | /// 48 | /// 49 | public async Task LockAsync() 50 | { 51 | await this.semaphoreSlim.WaitAsync(); 52 | return new UnLocker(this.semaphoreSlim); 53 | } 54 | 55 | /// 56 | /// 释放资源 57 | /// 58 | public void Dispose() 59 | { 60 | this.semaphoreSlim.Dispose(); 61 | } 62 | 63 | /// 64 | /// 提供解锁 65 | /// 66 | class UnLocker : IDisposable 67 | { 68 | /// 69 | /// 信号量 70 | /// 71 | private readonly SemaphoreSlim semaphoreSlim; 72 | 73 | /// 74 | /// 解锁 75 | /// 76 | /// 信号量 77 | public UnLocker(SemaphoreSlim semaphoreSlim) 78 | { 79 | this.semaphoreSlim = semaphoreSlim; 80 | } 81 | 82 | /// 83 | /// 释放锁 84 | /// 85 | public void Dispose() 86 | { 87 | this.semaphoreSlim.Release(); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Corefx/Buffers/ArrayPool.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace System.Buffers 4 | { 5 | /// 6 | /// 表示共享的数组池 7 | /// 8 | public static class ArrayPool 9 | { 10 | /// 11 | /// 租赁数组 12 | /// 13 | /// 元素类型 14 | /// 最小长度 15 | /// 16 | public static IArrayOwner Rent(int minLength) 17 | { 18 | return new ArrayOwner(minLength); 19 | } 20 | 21 | /// 22 | /// 表示数组持有者 23 | /// 24 | /// 25 | [DebuggerDisplay("Count = {Count}")] 26 | private sealed class ArrayOwner : Disposable, IArrayOwner 27 | { 28 | /// 29 | /// 获取持有的数组 30 | /// 31 | public T[] Array { get; } 32 | 33 | /// 34 | /// 获取数组的有效长度 35 | /// 36 | public int Count { get; } 37 | 38 | /// 39 | /// 数组持有者 40 | /// 41 | /// 42 | public ArrayOwner(int minLength) 43 | { 44 | this.Count = minLength; 45 | this.Array = ArrayPool.Shared.Rent(minLength); 46 | } 47 | 48 | /// 49 | /// 归还数组 50 | /// 51 | /// 52 | protected sealed override void Dispose(bool disposing) 53 | { 54 | ArrayPool.Shared.Return(this.Array); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Corefx/Buffers/BufferWriter.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace System.Buffers 6 | { 7 | /// 8 | /// 表示字节缓冲区写入对象 9 | /// 10 | public class BufferWriter : Disposable, IBufferWriter 11 | { 12 | private const int defaultSizeHint = 256; 13 | private IArrayOwner byteArrayOwner; 14 | 15 | /// 16 | /// 获取已写入的字节数 17 | /// 18 | public int WrittenCount { get; private set; } 19 | 20 | /// 21 | /// 获取容量 22 | /// 23 | public int Capacity => this.byteArrayOwner.Array.Length; 24 | 25 | 26 | /// 27 | /// 字节缓冲区写入对象 28 | /// 29 | /// 初始容量 30 | /// 31 | public BufferWriter(int initialCapacity = 1024) 32 | { 33 | if (initialCapacity <= 0) 34 | { 35 | throw new ArgumentOutOfRangeException(nameof(initialCapacity)); 36 | } 37 | this.byteArrayOwner = ArrayPool.Rent(initialCapacity); 38 | } 39 | 40 | /// 41 | /// 清除数据 42 | /// 43 | public void Clear() 44 | { 45 | this.byteArrayOwner.Array.AsSpan(0, this.WrittenCount).Clear(); 46 | this.WrittenCount = 0; 47 | } 48 | 49 | /// 50 | /// 设置向前推进 51 | /// 52 | /// 53 | /// 54 | public void Advance(int count) 55 | { 56 | if (count < 0 || this.WrittenCount + count > this.Capacity) 57 | { 58 | throw new ArgumentOutOfRangeException(nameof(count)); 59 | } 60 | this.WrittenCount += count; 61 | } 62 | 63 | /// 64 | /// 获取已数入的数据 65 | /// 66 | /// 67 | public ArraySegment GetWrittenSegment() 68 | { 69 | return new ArraySegment(this.byteArrayOwner.Array, 0, this.WrittenCount); 70 | } 71 | 72 | /// 73 | /// 返回用于写入数据的Memory 74 | /// 75 | /// 意图大小 76 | /// 77 | /// 78 | public Memory GetMemory(int sizeHint = 0) 79 | { 80 | this.CheckAndResizeBuffer(sizeHint); 81 | return this.byteArrayOwner.Array.AsMemory(this.WrittenCount); 82 | } 83 | 84 | /// 85 | /// 返回用于写入数据的Span 86 | /// 87 | /// 意图大小 88 | /// 89 | /// 90 | public Span GetSpan(int sizeHint = 0) 91 | { 92 | this.CheckAndResizeBuffer(sizeHint); 93 | return byteArrayOwner.Array.AsSpan(this.WrittenCount); 94 | } 95 | 96 | /// 97 | /// 写入数据 98 | /// 99 | /// 100 | public void Write(T value) 101 | { 102 | this.GetSpan(1)[0] = value; 103 | this.WrittenCount += 1; 104 | } 105 | 106 | /// 107 | /// 写入数据 108 | /// 109 | /// 值 110 | public void Write(ReadOnlySpan value) 111 | { 112 | if (value.IsEmpty == false) 113 | { 114 | value.CopyTo(this.GetSpan(value.Length)); 115 | this.WrittenCount += value.Length; 116 | } 117 | } 118 | 119 | /// 120 | /// 获取已数入的数据 121 | /// 122 | public ReadOnlySpan GetWrittenSpan() 123 | { 124 | return this.byteArrayOwner.Array.AsSpan(0, this.WrittenCount); 125 | } 126 | 127 | /// 128 | /// 获取已数入的数据 129 | /// 130 | public ReadOnlyMemory GetWrittenMemory() 131 | { 132 | return this.byteArrayOwner.Array.AsMemory(0, this.WrittenCount); 133 | } 134 | 135 | /// 136 | /// 释放资源 137 | /// 138 | /// 139 | protected sealed override void Dispose(bool disposing) 140 | { 141 | this.byteArrayOwner?.Dispose(); 142 | } 143 | 144 | /// 145 | /// 检测和扩容 146 | /// 147 | /// 148 | /// 149 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 150 | private void CheckAndResizeBuffer(int sizeHint) 151 | { 152 | if (sizeHint < 0) 153 | { 154 | throw new ArgumentOutOfRangeException(nameof(sizeHint)); 155 | } 156 | 157 | if (sizeHint == 0) 158 | { 159 | sizeHint = defaultSizeHint; 160 | } 161 | 162 | var freeCapacity = this.Capacity - this.WrittenCount; 163 | if (sizeHint > freeCapacity) 164 | { 165 | var growBy = Math.Max(sizeHint, this.Capacity); 166 | var newSize = checked(this.Capacity + growBy); 167 | 168 | var newOwer = ArrayPool.Rent(newSize); 169 | this.GetWrittenSpan().CopyTo(newOwer.Array); 170 | this.byteArrayOwner.Dispose(); 171 | this.byteArrayOwner = newOwer; 172 | } 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /Corefx/Buffers/IArrayOwner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace System.Buffers 4 | { 5 | /// 6 | /// 定义数组持有者的接口 7 | /// 8 | /// 9 | public interface IArrayOwner : IDisposable 10 | { 11 | /// 12 | /// 获取持有的数组 13 | /// 14 | T[] Array { get; } 15 | 16 | /// 17 | /// 获取数组的有效长度 18 | /// 19 | int Count { get; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Corefx/Buffers/MemoryOwnerExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.Buffers 2 | { 3 | /// 4 | /// IMemoryOwner扩展 5 | /// 6 | public static class MemoryOwnerExtensions 7 | { 8 | /// 9 | /// 切片 10 | /// 11 | /// 12 | /// 13 | /// 开始索引 14 | /// 15 | public static IMemoryOwner Slice(this IMemoryOwner owner, int start) 16 | { 17 | var memory = owner.Memory.Slice(start); 18 | return new MemoryOwner(owner, memory); 19 | } 20 | 21 | /// 22 | /// 切片 23 | /// 24 | /// 25 | /// 26 | /// 开始索引 27 | /// 长度 28 | /// 29 | public static IMemoryOwner Slice(this IMemoryOwner owner, int start, int length) 30 | { 31 | var memory = owner.Memory.Slice(start, length); 32 | return new MemoryOwner(owner, memory); 33 | } 34 | 35 | 36 | /// 37 | /// 表示内存持有者 38 | /// 39 | /// 40 | private class MemoryOwner : Disposable, IMemoryOwner 41 | { 42 | private readonly IDisposable owner; 43 | 44 | /// 45 | /// 获取持有的内存 46 | /// 47 | public Memory Memory { get; } 48 | 49 | /// 50 | /// 内存持有者 51 | /// 52 | /// 内存的实际持有者 53 | /// 内存 54 | public MemoryOwner(IDisposable owner, Memory memory) 55 | { 56 | this.owner = owner; 57 | this.Memory = memory; 58 | } 59 | 60 | /// 61 | /// 归还内存 62 | /// 63 | /// 64 | protected override void Dispose(bool disposing) 65 | { 66 | this.owner.Dispose(); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Corefx/Collections/Concurrent/ConcurrentCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace System.Collections.Concurrent 4 | { 5 | /// 6 | /// 表示线程安全的内存缓存 7 | /// 8 | /// 9 | /// 10 | public class ConcurrentCache 11 | { 12 | /// 13 | /// 线程安全字典 14 | /// 15 | private readonly ConcurrentDictionary> dictionary; 16 | 17 | /// 18 | /// 线程安全的内存缓存 19 | /// 20 | public ConcurrentCache() 21 | { 22 | this.dictionary = new ConcurrentDictionary>(); 23 | } 24 | 25 | /// 26 | /// 线程安全的内存缓存 27 | /// 28 | /// 键的比较器 29 | /// 30 | public ConcurrentCache(IEqualityComparer comparer) 31 | { 32 | this.dictionary = new ConcurrentDictionary>(comparer); 33 | } 34 | 35 | /// 36 | /// 获取或添加缓存 37 | /// 38 | /// 键 39 | /// 生成缓存内容的委托 40 | /// 41 | public TValue GetOrAdd(TKey key, Func valueFactory) 42 | { 43 | return this.dictionary.GetOrAdd(key, k => new Lazy(() => valueFactory(k), true)).Value; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Corefx/Converter.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | /// 4 | /// 扩展类型转换 5 | /// 6 | public static class Converter 7 | { 8 | /// 9 | /// 将value转换为目标类型 10 | /// 11 | /// 要转换的值 12 | /// 转换的目标类型 13 | /// 14 | /// 15 | public static object ConvertToType(object value, Type targetType) 16 | { 17 | if (value == null) 18 | { 19 | return targetType.DefaultValue(); 20 | } 21 | 22 | if (value.GetType() == targetType) 23 | { 24 | return value; 25 | } 26 | 27 | var underlyingType = Nullable.GetUnderlyingType(targetType); 28 | if (underlyingType != null) 29 | { 30 | targetType = underlyingType; 31 | } 32 | 33 | if (targetType.IsEnum == true) 34 | { 35 | return Enum.Parse(targetType, value.ToString(), true); 36 | } 37 | 38 | if (value is IConvertible convertible && targetType.IsInheritFrom()) 39 | { 40 | return convertible.ToType(targetType, null); 41 | } 42 | 43 | if (typeof(Guid) == targetType) 44 | { 45 | return Guid.Parse(value.ToString()); 46 | } 47 | 48 | if (typeof(DateTimeOffset) == targetType) 49 | { 50 | return DateTimeOffset.Parse(value.ToString()); 51 | } 52 | 53 | throw new NotSupportedException($"不支持将对象{value}转换为{targetType}"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Corefx/Corefx.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 21.1.26 5 | net5.0 6 | System 7 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 8 | true 9 | corefx项目的补充,提供诸多基础类型和类型扩展功能 10 | Taichuan.Corefx 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Corefx/Data/Csv.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace System.Data 8 | { 9 | /// 10 | /// 表示Csv文件 11 | /// 12 | /// 13 | public class Csv 14 | { 15 | /// 16 | /// 字段 17 | /// 18 | private readonly List fields = new List(); 19 | 20 | /// 21 | /// 获取数据模型 22 | /// 23 | public IEnumerable Models { get; private set; } 24 | 25 | /// 26 | /// 数据转换为Excel 27 | /// 28 | /// 数据 29 | public Csv(IEnumerable models) 30 | { 31 | this.Models = models ?? throw new ArgumentNullException(nameof(models)); 32 | } 33 | 34 | /// 35 | /// 添加字段 36 | /// 37 | /// 字段类型 38 | /// 字段名 39 | /// 字段值 40 | public Csv AddField(string name, Func value) 41 | { 42 | var field = new Field(name, value); 43 | this.fields.Add(field); 44 | return this; 45 | } 46 | 47 | 48 | /// 49 | /// 保存为csv格式 50 | /// 51 | /// 52 | /// 53 | public async Task SaveAsCsvAsync(string filePath, Encoding encoding = default) 54 | { 55 | var path = Path.GetDirectoryName(filePath); 56 | if (string.IsNullOrEmpty(path) == false) 57 | { 58 | Directory.CreateDirectory(path); 59 | } 60 | 61 | using var stream = new FileStream(filePath, FileMode.Create); 62 | await this.SaveAsCsvAsync(stream, encoding); 63 | } 64 | 65 | /// 66 | /// 保存为csv格式 67 | /// 68 | /// 流 69 | /// 编码 70 | public async Task SaveAsCsvAsync(Stream stream, Encoding encoding = default) 71 | { 72 | if (encoding == default) 73 | { 74 | encoding = Encoding.UTF8; 75 | } 76 | 77 | const int bufferSize = 4 * 1024; 78 | using var writer = new StreamWriter(stream, encoding, bufferSize, leaveOpen: true); 79 | var fieldItem = this.fields.Select(item => item.Name); 80 | var head = string.Join(",", fieldItem); 81 | await writer.WriteLineAsync(head.AsMemory()); 82 | 83 | foreach (var item in this.Models) 84 | { 85 | var lineItems = this.fields.Select(f => f.GetValue(item)); 86 | var line = string.Join(",", lineItems); 87 | await writer.WriteLineAsync(line.AsMemory()); 88 | } 89 | } 90 | 91 | /// 92 | /// 字段接口 93 | /// 94 | private interface IField 95 | { 96 | string Name { get; } 97 | string GetValue(T model); 98 | } 99 | 100 | /// 101 | /// 表示字段信息 102 | /// 103 | /// 104 | private class Field : IField 105 | { 106 | private readonly Func valueFunc; 107 | 108 | public string Name { get; private set; } 109 | 110 | public Field(string name, Func value) 111 | { 112 | this.Name = name; 113 | this.valueFunc = value; 114 | } 115 | 116 | public string GetValue(T model) 117 | { 118 | var value = this.valueFunc(model)?.ToString(); 119 | return value != null && value.Contains(',') 120 | ? @$"""{value}""" 121 | : value; 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Corefx/Data/CsvExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace System.Data 4 | { 5 | /// 6 | /// csv扩展 7 | /// 8 | public static class CsvExtensions 9 | { 10 | /// 11 | /// 转换为Csv 12 | /// 13 | /// 14 | /// 15 | /// 16 | public static Csv ToCsv(this IEnumerable models) 17 | { 18 | return new Csv(models); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Corefx/Disposable.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | /// 4 | /// 表示支持Dispose的抽象基础类 5 | /// 6 | public abstract class Disposable : IDisposable 7 | { 8 | /// 9 | /// 获取对象是否已释放 10 | /// 11 | public bool IsDisposed { get; private set; } 12 | 13 | /// 14 | /// 关闭和释放所有相关资源 15 | /// 16 | public void Dispose() 17 | { 18 | if (this.IsDisposed == false) 19 | { 20 | this.Dispose(true); 21 | GC.SuppressFinalize(this); 22 | } 23 | this.IsDisposed = true; 24 | } 25 | 26 | /// 27 | /// 析构函数 28 | /// 29 | ~Disposable() 30 | { 31 | this.Dispose(false); 32 | } 33 | 34 | /// 35 | /// 释放资源 36 | /// 37 | /// 是否也释放托管资源 38 | protected abstract void Dispose(bool disposing); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Corefx/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Linq; 4 | 5 | namespace System 6 | { 7 | /// 8 | /// 枚举扩展 9 | /// 10 | public static partial class EnumExtensions 11 | { 12 | /// 13 | /// 获取枚举类型所有字段的名称 14 | /// 15 | /// 枚举类型 16 | /// 17 | public static string[] GetNames(this Enum e) 18 | { 19 | return Enum.GetNames(e.GetType()); 20 | } 21 | 22 | 23 | /// 24 | /// 获取枚举类型所有字段的值 25 | /// 26 | /// 值类型 27 | /// 枚举类型 28 | /// 29 | public static T[] GetValues(this Enum e) 30 | { 31 | return Enum.GetValues(e.GetType()).Cast().ToArray(); 32 | } 33 | 34 | /// 35 | /// 获取枚举类型所有字段的值 36 | /// 37 | /// 枚举类型 38 | /// 39 | public static Enum[] GetValues(this Enum e) 40 | { 41 | return e.GetValues(); 42 | } 43 | 44 | /// 45 | /// 获取DisplayAttribute的Name值 46 | /// 47 | /// 48 | /// 49 | public static string GetDisplayName(this Enum e) 50 | { 51 | return e.GetAttribute()?.Name ?? e.ToString(); 52 | } 53 | 54 | /// 55 | /// 获取特性 56 | /// 57 | /// 58 | /// 59 | /// 60 | public static T GetAttribute(this Enum e) where T : class 61 | { 62 | var field = e.GetType().GetField(e.ToString()); 63 | var attribute = Attribute.GetCustomAttribute(field, typeof(T)) as T; 64 | return attribute; 65 | } 66 | 67 | 68 | /// 69 | /// 获取特性 70 | /// 71 | /// 72 | /// 73 | /// 74 | public static T[] GetAttributes(this Enum e) where T : class 75 | { 76 | var field = e.GetType().GetField(e.ToString()); 77 | var attributes = Attribute.GetCustomAttributes(field, typeof(T)) as T[]; 78 | return attributes; 79 | } 80 | 81 | 82 | /// 83 | /// 获取枚举值包含的位域值 84 | /// 85 | /// 86 | /// 87 | public static IEnumerable GetFlagEnums(this Enum e) 88 | { 89 | return e.GetHashCode() == 0 ? (new Enum[0]) : e.GetValues().Where(item => e.HasFlag(item)); 90 | } 91 | 92 | /// 93 | /// 获取枚举值包含的位域值 94 | /// 95 | /// 96 | /// 97 | /// 98 | public static IEnumerable GetFlagEnums(this Enum e) where T : struct 99 | { 100 | return e.GetFlagEnums().Cast(); 101 | } 102 | 103 | 104 | /// 105 | /// 是否声明特性 106 | /// 107 | /// 特性类型 108 | /// 枚举 109 | /// 110 | public static bool IsDefinedAttribute(this Enum e) where T : Attribute 111 | { 112 | var field = e.GetType().GetField(e.ToString()); 113 | return Attribute.IsDefined(field, typeof(T)); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Corefx/Guid16.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace System 4 | { 5 | /// 6 | /// 表示Guid16位 7 | /// 8 | public struct Guid16 : IComparable, IEquatable 9 | { 10 | /// 11 | /// 值 12 | /// 13 | private readonly long val; 14 | 15 | /// 16 | /// 表示空的Guid16 17 | /// 18 | public static readonly Guid16 Empty = new Guid16(0); 19 | 20 | /// 21 | /// Guid16位 22 | /// 23 | /// 值 24 | public Guid16(long val) 25 | { 26 | this.val = val; 27 | } 28 | 29 | /// 30 | /// 转换为字符串 31 | /// 32 | /// 33 | public override string ToString() 34 | { 35 | return this.val.ToString("x16"); 36 | } 37 | 38 | /// 39 | /// 转换为64位整数 40 | /// 41 | /// 42 | public long ToInt64() 43 | { 44 | return this.val; 45 | } 46 | 47 | /// 48 | /// 返回哈希值 49 | /// 50 | /// 51 | public override int GetHashCode() 52 | { 53 | return this.val.GetHashCode(); 54 | } 55 | 56 | /// 57 | /// 返回是否与目标相等 58 | /// 59 | /// 目标 60 | /// 61 | public override bool Equals(object obj) 62 | { 63 | if (obj is Guid16 other) 64 | { 65 | return this.Equals(other); 66 | } 67 | return false; 68 | } 69 | 70 | /// 71 | /// 返回是否与目标相等 72 | /// 73 | /// 目标 74 | /// 75 | public bool Equals(Guid16 other) 76 | { 77 | return this.val.Equals(other.val); 78 | } 79 | 80 | /// 81 | /// 和目标比较大小 82 | /// 83 | /// 目标 84 | /// 85 | public int CompareTo(Guid16 other) 86 | { 87 | return this.val.CompareTo(other.val); 88 | } 89 | 90 | /// 91 | /// 创建新的Guid16 92 | /// 93 | /// 94 | public static Guid16 NewGuid16() 95 | { 96 | var val = Guid.NewGuid().ToByteArray().Aggregate(1, (current, b) => current * (b + 1)); 97 | return new Guid16(val - DateTime.Now.Ticks); 98 | } 99 | 100 | /// 101 | /// 转换为Guid16 102 | /// 103 | /// 16位hex 104 | /// 105 | /// 106 | /// 107 | public static Guid16 Parse(string g) 108 | { 109 | if (string.IsNullOrEmpty(g) == true) 110 | { 111 | throw new ArgumentNullException(nameof(g)); 112 | } 113 | 114 | if (g.Length != 16) 115 | { 116 | throw new ArgumentException(nameof(g)); 117 | } 118 | 119 | var val = Convert.ToInt64(g, 16); 120 | return new Guid16(val); 121 | } 122 | 123 | /// 124 | /// 返回是否相等 125 | /// 126 | /// 127 | /// 128 | /// 129 | public static bool operator ==(Guid16 a, Guid16 b) 130 | { 131 | return a.Equals(b); 132 | } 133 | 134 | /// 135 | /// 返回不相等 136 | /// 137 | /// 138 | /// 139 | /// 140 | public static bool operator !=(Guid16 a, Guid16 b) 141 | { 142 | return a.Equals(b) == false; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Corefx/Linq/Expressions/ConditionItem.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace System.Linq.Expressions 4 | { 5 | /// 6 | /// 表示条件项 7 | /// 8 | public class ConditionItem 9 | { 10 | /// 11 | /// 获取或设置属性名称 12 | /// 13 | public string MemberName { get; set; } 14 | 15 | /// 16 | /// 获取或设置条件值 17 | /// 18 | public object Value { get; set; } 19 | 20 | /// 21 | /// 获取或设置比较操作符 22 | /// 23 | public Operator? Operator { get; set; } 24 | 25 | /// 26 | /// 转换为泛型的ConditionItem 27 | /// 28 | /// 29 | /// 30 | /// 31 | public ConditionItem AsGeneric() 32 | { 33 | var member = ConditionItem.TypeProperties 34 | .FirstOrDefault(item => item.Name.Equals(this.MemberName, StringComparison.OrdinalIgnoreCase)); 35 | 36 | return member == null ? null : new ConditionItem(member, this.Value, this.Operator); 37 | } 38 | 39 | /// 40 | /// 转换为字符串 41 | /// 42 | /// 43 | public override string ToString() 44 | { 45 | return $"{this.MemberName} {this.Operator} {this.Value}"; 46 | } 47 | } 48 | 49 | /// 50 | /// 表示条件项 51 | /// 52 | public class ConditionItem 53 | { 54 | /// 55 | /// 获取T类型的所有属性 56 | /// 57 | public static PropertyInfo[] TypeProperties { get; } = typeof(T).GetProperties(); 58 | 59 | /// 60 | /// 获取属性 61 | /// 62 | public PropertyInfo Member { get; } 63 | 64 | /// 65 | /// 获取条件值 66 | /// 67 | public object Value { get; } 68 | 69 | /// 70 | /// 获取或设置比较操作符 71 | /// 72 | public Operator Operator { get; set; } 73 | 74 | /// 75 | /// 条件项 76 | /// 77 | /// 属性 78 | /// 条件值 79 | /// 比较操作符 80 | /// 81 | /// 82 | public ConditionItem(PropertyInfo member, object value, Operator? @operator) 83 | { 84 | this.Member = member ?? throw new ArgumentNullException(nameof(member)); 85 | this.Value = Converter.ConvertToType(value, member.PropertyType); 86 | if (@operator == null) 87 | { 88 | this.Operator = member.PropertyType == typeof(string) ? Operator.Contains : Operator.Equal; 89 | } 90 | else 91 | { 92 | this.Operator = @operator.Value; 93 | } 94 | } 95 | 96 | 97 | /// 98 | /// 转换为谓词筛选表达式 99 | /// 100 | /// 101 | /// 102 | public Expression> ToPredicate() 103 | { 104 | return Predicate.Create(this.Member, this.Value, this.Operator); 105 | } 106 | 107 | /// 108 | /// 转换为字符串 109 | /// 110 | /// 111 | public override string ToString() 112 | { 113 | return this.ToPredicate().ToString(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Corefx/Linq/Expressions/Operator.cs: -------------------------------------------------------------------------------- 1 | namespace System.Linq.Expressions 2 | { 3 | /// 4 | /// 比较操作符 5 | /// 6 | public enum Operator 7 | { 8 | /// 9 | /// 等于 10 | /// 11 | Equal = ExpressionType.Equal, 12 | 13 | /// 14 | /// 不等于 15 | /// 16 | NotEqual = ExpressionType.NotEqual, 17 | 18 | /// 19 | /// 大于或等于 20 | /// 21 | GreaterThanOrEqual = ExpressionType.GreaterThanOrEqual, 22 | 23 | /// 24 | /// 小于或等于 25 | /// 26 | LessThanOrEqual = ExpressionType.LessThanOrEqual, 27 | 28 | /// 29 | /// 大于 30 | /// 31 | GreaterThan = ExpressionType.GreaterThan, 32 | 33 | /// 34 | /// 小于 35 | /// 36 | LessThan = ExpressionType.LessThan, 37 | 38 | 39 | 40 | /// 41 | /// 包含 42 | /// 只适用于string类型的属性 43 | /// 44 | Contains = 1000, 45 | 46 | /// 47 | /// 结束于 48 | /// 只适用于string类型的属性 49 | /// 50 | EndWith, 51 | 52 | /// 53 | /// 开始于 54 | /// 只适用于string类型的属性 55 | /// 56 | StartsWith 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Corefx/Linq/IEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace System.Linq 4 | { 5 | /// 6 | /// 可迭代类扩展 7 | /// 8 | public static class IEnumerableExtensions 9 | { 10 | /// 11 | /// 去除重复数据 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static IEnumerable Distinct(this IEnumerable source, Func keySelector) 20 | { 21 | return source.Distinct(keySelector, EqualityComparer.Default); 22 | } 23 | 24 | /// 25 | /// 去除重复数据 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | public static IEnumerable Distinct(this IEnumerable source, Func keySelector, IEqualityComparer keyComparer) 35 | { 36 | if (keySelector == null) 37 | { 38 | throw new ArgumentNullException(nameof(keySelector)); 39 | } 40 | if (keyComparer == null) 41 | { 42 | throw new ArgumentNullException(nameof(keyComparer)); 43 | } 44 | var comparer = new KeyEqualityComparer(keySelector, keyComparer); 45 | return source.Distinct(comparer); 46 | } 47 | 48 | /// 49 | /// 为Null则返回0条记录的迭代 50 | /// 51 | /// 52 | /// 53 | /// 54 | public static IEnumerable NullThenEmpty(this IEnumerable source) 55 | { 56 | return source ?? Enumerable.Empty(); 57 | } 58 | 59 | /// 60 | /// 迭代集合 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | public static void ForEach(this IEnumerable source, Action item) 67 | { 68 | if (source == null || item == null) 69 | { 70 | return; 71 | } 72 | 73 | foreach (var value in source) 74 | { 75 | item(value); 76 | } 77 | } 78 | 79 | /// 80 | /// 以数字分段的排序 81 | /// 82 | /// 83 | /// 84 | /// 名称选择器 85 | /// 86 | public static IOrderedEnumerable OrderByNumericSegments(this IEnumerable source, Func nameSelector) 87 | { 88 | if (source == null) 89 | { 90 | return null; 91 | } 92 | 93 | return source.OrderBy(nameSelector, NumericSegmentComparer.Instance); 94 | } 95 | 96 | /// 97 | /// 键选择比较器 98 | /// 99 | /// 100 | /// 101 | private class KeyEqualityComparer : IEqualityComparer 102 | { 103 | private readonly Func keySelector; 104 | private readonly IEqualityComparer comparer; 105 | 106 | public KeyEqualityComparer(Func keySelector, IEqualityComparer comparer) 107 | { 108 | this.keySelector = keySelector; 109 | this.comparer = comparer; 110 | } 111 | 112 | public bool Equals(T x, T y) 113 | { 114 | return comparer.Equals(keySelector(x), keySelector(y)); 115 | } 116 | 117 | public int GetHashCode(T obj) 118 | { 119 | return comparer.GetHashCode(keySelector(obj)); 120 | } 121 | } 122 | 123 | 124 | 125 | /// 126 | /// 比较器 127 | /// 128 | private class NumericSegmentComparer : IComparer 129 | { 130 | private const string Pattern = "\\d+(?=\\D)"; 131 | 132 | public static IComparer Instance { get; } = new NumericSegmentComparer(); 133 | 134 | public int Compare(string x, string y) 135 | { 136 | if (x == null && y == null) 137 | { 138 | return 0; 139 | } 140 | 141 | if (x == null) 142 | { 143 | return -1; 144 | } 145 | 146 | if (y == null) 147 | { 148 | return 1; 149 | } 150 | 151 | var xSegments = x.Matches(Pattern); 152 | var ySegments = y.Matches(Pattern); 153 | 154 | var c = xSegments.Length - ySegments.Length; 155 | if (c != 0) 156 | { 157 | return c; 158 | } 159 | 160 | for (var i = 0; i < xSegments.Length; i++) 161 | { 162 | x = xSegments[i]; 163 | y = ySegments[i]; 164 | 165 | c = int.Parse(x) - int.Parse(y); 166 | if (c != 0) 167 | { 168 | return c; 169 | } 170 | } 171 | 172 | return 0; 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Corefx/Net/Mail/EMail.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace System.Net.Mail 8 | { 9 | /// 10 | /// 表示邮件 11 | /// 12 | public class EMail 13 | { 14 | /// 15 | /// smtp端口 16 | /// 17 | public int Port { get; private set; } 18 | 19 | /// 20 | /// smtp 21 | /// 22 | public string Smtp { get; private set; } 23 | 24 | /// 25 | /// 发送者邮箱 26 | /// 27 | public string From { get; private set; } 28 | /// 29 | /// 密码 30 | /// 31 | public string Password { get; private set; } 32 | 33 | /// 34 | /// 表示邮件 35 | /// 36 | /// 发送者邮箱 37 | /// 密码 38 | /// smtp 39 | public EMail(string from, string password, string smtp) 40 | : this(from, password, smtp, 25) 41 | { 42 | } 43 | 44 | /// 45 | /// 表示邮件 46 | /// 47 | /// 发送者邮箱 48 | /// 密码 49 | /// smtp 50 | /// 端口 51 | public EMail(string from, string password, string smtp, int port) 52 | { 53 | this.From = from; 54 | this.Password = password; 55 | this.Smtp = smtp; 56 | this.Port = port; 57 | } 58 | 59 | /// 60 | /// 发送 61 | /// 62 | /// 接收者邮箱 63 | /// 标题 64 | /// 内容 65 | /// ssl 66 | /// 67 | public async Task SendAsync(string to, string title, string htmlBody, bool ssl = false) 68 | { 69 | await this.SendAsync(new[] { to }, title, htmlBody, ssl); 70 | } 71 | 72 | /// 73 | /// 发送 74 | /// 75 | /// 接收者邮箱 76 | /// 标题 77 | /// 内容 78 | /// ssl 79 | /// 80 | public async Task SendAsync(IEnumerable to, string title, string htmlBody, bool ssl = false) 81 | { 82 | var msg = new MailMessage 83 | { 84 | From = new MailAddress(this.From), 85 | Subject = title, 86 | SubjectEncoding = Encoding.UTF8, 87 | Body = htmlBody, 88 | BodyEncoding = Encoding.UTF8, 89 | IsBodyHtml = true, 90 | }; 91 | 92 | foreach (var item in to.Distinct()) 93 | { 94 | if (item.IsNullOrEmpty() == false && Regex.IsMatch(item, @"^\w+(\.\w*)*@\w+\.\w+$")) 95 | { 96 | msg.To.Add(item); 97 | } 98 | } 99 | 100 | if (msg.To.Count == 0) 101 | { 102 | return; 103 | } 104 | 105 | using (var client = new SmtpClient()) 106 | { 107 | client.Credentials = new NetworkCredential(this.From, this.Password); 108 | client.Port = this.Port; 109 | client.Host = this.Smtp; 110 | client.EnableSsl = ssl; 111 | await client.SendMailAsync(msg); 112 | } 113 | } 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /Corefx/Net/Sockets/SocketAwaitableEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace System.Net.Sockets 6 | { 7 | /// 8 | /// 表示可等待的SocketAsyncEventArgs 9 | /// 10 | public class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion 11 | { 12 | private static readonly Action _callbackCompleted = () => { }; 13 | 14 | private Action _callback; 15 | 16 | /// 17 | /// 获取是否已完成 18 | /// 19 | public bool IsCompleted 20 | { 21 | get => ReferenceEquals(_callback, _callbackCompleted); 22 | } 23 | 24 | /// 25 | /// 获取等待器 26 | /// 27 | /// 28 | public SocketAwaitableEventArgs GetAwaiter() 29 | { 30 | return this; 31 | } 32 | 33 | /// 34 | /// 获取结果 35 | /// 36 | /// 37 | public int GetResult() 38 | { 39 | _callback = null; 40 | if (SocketError != SocketError.Success) 41 | { 42 | throw new SocketException((int)SocketError); 43 | } 44 | return this.BytesTransferred; 45 | } 46 | 47 | /// 48 | /// 完成时 49 | /// 50 | /// 51 | void INotifyCompletion.OnCompleted(Action continuation) 52 | { 53 | if (ReferenceEquals(_callback, _callbackCompleted) || 54 | ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) 55 | { 56 | Task.Run(continuation); 57 | } 58 | } 59 | 60 | /// 61 | /// 完成时 62 | /// 63 | /// 64 | void ICriticalNotifyCompletion.UnsafeOnCompleted(Action continuation) 65 | { 66 | ((INotifyCompletion)this).OnCompleted(continuation); 67 | } 68 | 69 | /// 70 | /// 设置为完成 71 | /// 72 | public void Complete() 73 | { 74 | this.OnCompleted(this); 75 | } 76 | 77 | /// 78 | /// 完成时 79 | /// 80 | /// 81 | protected override void OnCompleted(SocketAsyncEventArgs _) 82 | { 83 | var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); 84 | if (continuation != null) 85 | { 86 | ThreadPool.UnsafeQueueUserWorkItem(state => ((Action)state)(), continuation); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Corefx/NullableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | /// 4 | /// 可空类型扩展 5 | /// 6 | public static class NullableExtensions 7 | { 8 | /// 9 | /// 如果为空则返回类型的初始值 10 | /// 11 | /// 12 | /// 13 | /// 14 | public static T NullThenDefault(this T? source) where T : struct 15 | { 16 | return source ?? default; 17 | } 18 | 19 | /// 20 | /// 如果为空则返回类型的初始值 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static T NullThen(this T? source, T value) where T : struct 27 | { 28 | return source ?? value; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Corefx/Pipelines/IMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace System.Pipelines 4 | { 5 | /// 6 | /// 定义中间件的接口 7 | /// 8 | /// 9 | public interface IMiddleware 10 | { 11 | /// 12 | /// 执行中间件 13 | /// 14 | /// 上下文 15 | /// 下一个中间件 16 | /// 17 | Task InvokeAsync(TContext context, Func next); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Corefx/Pipelines/IPipelineBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace System.Pipelines 2 | { 3 | /// 4 | /// 定义中间件管道创建者的接口 5 | /// 6 | /// 中间件上下文 7 | public interface IPipelineBuilder 8 | { 9 | /// 10 | /// 获取服务提供者 11 | /// 12 | IServiceProvider AppServices { get; } 13 | 14 | /// 15 | /// 使用中间件 16 | /// 17 | /// 中间件 18 | /// 19 | IPipelineBuilder Use(Func, InvokeDelegate> middleware); 20 | 21 | /// 22 | /// 创建所有中间件执行处理者 23 | /// 24 | /// 25 | InvokeDelegate Build(); 26 | 27 | /// 28 | /// 使用默认配制创建新的PipelineBuilder 29 | /// 30 | /// 31 | IPipelineBuilder New(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Corefx/Pipelines/InvokeDelegate.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace System.Pipelines 4 | { 5 | /// 6 | /// 表示所有中间件执行委托 7 | /// 8 | /// 中间件上下文类型 9 | /// 中间件上下文 10 | /// 11 | public delegate Task InvokeDelegate(TContext context); 12 | } 13 | -------------------------------------------------------------------------------- /Corefx/Pipelines/PipelineBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace System.Pipelines 5 | { 6 | /// 7 | /// 表示中间件创建者 8 | /// 9 | public class PipelineBuilder : IPipelineBuilder 10 | { 11 | private readonly InvokeDelegate completedHandler; 12 | private readonly List, InvokeDelegate>> middlewares = new List, InvokeDelegate>>(); 13 | 14 | /// 15 | /// 获取服务提供者 16 | /// 17 | public IServiceProvider AppServices { get; } 18 | 19 | /// 20 | /// 中间件创建者 21 | /// 22 | /// 23 | public PipelineBuilder(IServiceProvider appServices) 24 | : this(appServices, context => Task.CompletedTask) 25 | { 26 | } 27 | 28 | /// 29 | /// 中间件创建者 30 | /// 31 | /// 32 | /// 完成执行内容处理者 33 | public PipelineBuilder(IServiceProvider appServices, InvokeDelegate completedHandler) 34 | { 35 | this.AppServices = appServices; 36 | this.completedHandler = completedHandler; 37 | } 38 | 39 | /// 40 | /// 使用中间件 41 | /// 42 | /// 43 | /// 44 | public IPipelineBuilder Use(Func, InvokeDelegate> middleware) 45 | { 46 | this.middlewares.Add(middleware); 47 | return this; 48 | } 49 | 50 | 51 | /// 52 | /// 创建所有中间件执行处理者 53 | /// 54 | /// 55 | public InvokeDelegate Build() 56 | { 57 | var handler = this.completedHandler; 58 | for (var i = this.middlewares.Count - 1; i >= 0; i--) 59 | { 60 | handler = this.middlewares[i](handler); 61 | } 62 | return handler; 63 | } 64 | 65 | 66 | /// 67 | /// 使用默认配制创建新的PipelineBuilder 68 | /// 69 | /// 70 | public IPipelineBuilder New() 71 | { 72 | return new PipelineBuilder(this.AppServices, this.completedHandler); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Corefx/Pipelines/PipelineBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace System.Pipelines 4 | { 5 | /// 6 | /// 中间件创建者扩展 7 | /// 8 | public static class PipelineBuilderExtensions 9 | { 10 | /// 11 | /// 中断执行中间件 12 | /// 13 | /// 14 | /// 15 | /// 处理者 16 | /// 17 | public static IPipelineBuilder Run(this IPipelineBuilder builder, InvokeDelegate handler) 18 | { 19 | return builder.Use(_ => handler); 20 | } 21 | 22 | /// 23 | /// 条件中间件 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static IPipelineBuilder When(this IPipelineBuilder builder, Func predicate, InvokeDelegate handler) 31 | { 32 | return builder.Use(next => async context => 33 | { 34 | if (predicate.Invoke(context) == true) 35 | { 36 | await handler.Invoke(context); 37 | } 38 | else 39 | { 40 | await next(context); 41 | } 42 | }); 43 | } 44 | 45 | 46 | /// 47 | /// 条件中间件 48 | /// 49 | /// 50 | /// 51 | /// 52 | /// 53 | /// 54 | public static IPipelineBuilder When(this IPipelineBuilder builder, Func predicate, Action> configureAction) 55 | { 56 | return builder.Use(next => async context => 57 | { 58 | if (predicate.Invoke(context) == true) 59 | { 60 | var branchBuilder = builder.New(); 61 | configureAction(branchBuilder); 62 | await branchBuilder.Build().Invoke(context); 63 | } 64 | else 65 | { 66 | await next(context); 67 | } 68 | }); 69 | } 70 | 71 | /// 72 | /// 使用中间件 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | public static IPipelineBuilder Use(this IPipelineBuilder builder) where TMiddleware : class, IMiddleware 79 | { 80 | var middleware = builder.AppServices.GetService(typeof(TMiddleware)) as TMiddleware; 81 | return builder.Use(middleware.InvokeAsync); 82 | } 83 | 84 | /// 85 | /// 使用中间件 86 | /// 87 | /// 88 | /// 89 | /// 90 | /// 91 | public static IPipelineBuilder Use(this IPipelineBuilder builder, Func, Task> middleware) 92 | { 93 | return builder.Use(next => context => middleware(context, () => next(context))); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Corefx/Reflection/Method.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace System.Reflection 5 | { 6 | /// 7 | /// 表示方法 8 | /// 9 | public class Method 10 | { 11 | /// 12 | /// 方法执行委托 13 | /// 14 | private readonly Func invoker; 15 | 16 | /// 17 | /// 获取方法名 18 | /// 19 | public string Name { get; protected set; } 20 | 21 | /// 22 | /// 获取方法信息 23 | /// 24 | public MethodInfo Info { get; private set; } 25 | 26 | /// 27 | /// 方法 28 | /// 29 | /// 方法信息 30 | public Method(MethodInfo method) 31 | { 32 | this.Name = method.Name; 33 | this.Info = method; 34 | this.invoker = Method.CreateInvoker(method); 35 | } 36 | 37 | /// 38 | /// 执行方法 39 | /// 40 | /// 实例 41 | /// 参数 42 | /// 43 | public object Invoke(object instance, params object[] parameters) 44 | { 45 | return this.invoker.Invoke(instance, parameters); 46 | } 47 | 48 | /// 49 | /// 生成方法的调用委托 50 | /// 51 | /// 方法成员信息 52 | /// 53 | /// 54 | private static Func CreateInvoker(MethodInfo method) 55 | { 56 | var instance = Expression.Parameter(typeof(object), "instance"); 57 | var parameters = Expression.Parameter(typeof(object[]), "parameters"); 58 | 59 | var instanceCast = method.IsStatic ? null : Expression.Convert(instance, method.ReflectedType); 60 | var parametersCast = method.GetParameters().Select((p, i) => 61 | { 62 | var parameter = Expression.ArrayIndex(parameters, Expression.Constant(i)); 63 | return Expression.Convert(parameter, p.ParameterType); 64 | }); 65 | 66 | var body = Expression.Call(instanceCast, method, parametersCast); 67 | 68 | if (method.ReturnType == typeof(void)) 69 | { 70 | var action = Expression.Lambda>(body, instance, parameters).Compile(); 71 | return (_instance, _parameters) => 72 | { 73 | action.Invoke(_instance, _parameters); 74 | return null; 75 | }; 76 | } 77 | else 78 | { 79 | var bodyCast = Expression.Convert(body, typeof(object)); 80 | return Expression.Lambda>(bodyCast, instance, parameters).Compile(); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Corefx/Reflection/Property.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Linq; 3 | 4 | namespace System.Reflection 5 | { 6 | /// 7 | /// 表示属性 8 | /// 9 | public class Property : Property 10 | { 11 | /// 12 | /// 类型的属性缓存 13 | /// 14 | private static readonly ConcurrentDictionary cache = new ConcurrentDictionary(); 15 | 16 | /// 17 | /// 属性 18 | /// 19 | /// 属性信息 20 | public Property(PropertyInfo property) 21 | : base(property) 22 | { 23 | } 24 | 25 | /// 26 | /// 从类型获取其所有属性 27 | /// 28 | /// 类型 29 | /// 30 | public static Property[] GetProperties(Type type) 31 | { 32 | return cache.GetOrAdd(type, t => t.GetProperties().Select(p => new Property(p)).ToArray()); 33 | } 34 | } 35 | 36 | /// 37 | /// 表示属性 38 | /// 39 | /// 定义属性的类型 40 | public class Property : Property 41 | { 42 | /// 43 | /// 属性名称与属性缓存 44 | /// 45 | private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); 46 | 47 | /// 48 | /// 获取类型的所有属性 49 | /// 50 | public static Property[] Properties { get; } = typeof(TDeclaring).GetProperties().Select(p => new Property(p)).ToArray(); 51 | 52 | /// 53 | /// 属性 54 | /// 55 | /// 属性信息 56 | public Property(PropertyInfo property) 57 | : base(property) 58 | { 59 | } 60 | 61 | /// 62 | /// 获取属性 63 | /// 64 | /// 属性名称 65 | /// 文本比较器 66 | /// 67 | public static Property GetProperty(string name, StringComparison comparison = StringComparison.OrdinalIgnoreCase) 68 | { 69 | return cache.GetOrAdd(name, n => Properties.FirstOrDefault(item => string.Equals(item.Name, n, comparison))); 70 | } 71 | } 72 | 73 | 74 | /// 75 | /// 表示属性 76 | /// 77 | /// 定义属性的类型 78 | /// 属性类型 79 | public class Property 80 | { 81 | /// 82 | /// 获取器 83 | /// 84 | private readonly Func geter; 85 | 86 | /// 87 | /// 设置器 88 | /// 89 | private readonly Action seter; 90 | 91 | /// 92 | /// 获取属性名称 93 | /// 94 | public string Name { get; protected set; } 95 | 96 | /// 97 | /// 获取属性信息 98 | /// 99 | public PropertyInfo Info { get; } 100 | 101 | /// 102 | /// 属性 103 | /// 104 | /// 属性信息 105 | public Property(PropertyInfo property) 106 | { 107 | this.Name = property.Name; 108 | this.Info = property; 109 | 110 | if (property.CanRead == true) 111 | { 112 | this.geter = Lambda.CreateGetFunc(property); 113 | } 114 | 115 | if (property.CanWrite == true) 116 | { 117 | this.seter = Lambda.CreateSetAction(property); 118 | } 119 | } 120 | 121 | /// 122 | /// 获取属性的值 123 | /// 124 | /// 实例 125 | /// 126 | public TProperty GetValue(TDeclaring instance) 127 | { 128 | return this.geter.Invoke(instance); 129 | } 130 | 131 | /// 132 | /// 设置属性的值 133 | /// 134 | /// 实例 135 | /// 值 136 | public void SetValue(TDeclaring instance, TProperty value) 137 | { 138 | this.seter?.Invoke(instance, value); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /Corefx/Security/DES.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace System.Security 6 | { 7 | /// 8 | /// DES加解密提供者 9 | /// 10 | public class DES 11 | { 12 | /// 13 | /// 默认RgbKey 14 | /// 15 | private static readonly byte[] defaultRgbKey = { 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01 }; 16 | 17 | /// 18 | /// 默认RgbIV 19 | /// 20 | private static readonly byte[] defaultRgbIV = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; 21 | 22 | /// 23 | /// RgbKey 24 | /// 25 | private readonly byte[] rgbKey; 26 | 27 | /// 28 | /// RgbIV 29 | /// 30 | private readonly byte[] rgbIV; 31 | 32 | /// 33 | /// des加解密提供者 34 | /// 35 | private readonly DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider(); 36 | 37 | /// 38 | /// 内置的默认实例 39 | /// 40 | public static DES Default { get; } = new DES(defaultRgbKey, defaultRgbIV); 41 | 42 | 43 | /// 44 | /// DES加解密提供者 45 | /// 46 | /// 47 | /// 48 | public DES(byte[] rgbKey, byte[] rgbIV) 49 | { 50 | if (rgbKey == null || rgbKey.Length != 8) 51 | { 52 | throw new ArgumentException($"{nameof(rgbKey)}必须为8个字节"); 53 | } 54 | if (rgbIV == null || rgbIV.Length != 8) 55 | { 56 | throw new ArgumentException($"{nameof(rgbIV)}必须为8个字节"); 57 | } 58 | 59 | this.rgbKey = rgbKey; 60 | this.rgbIV = rgbIV; 61 | } 62 | 63 | /// 64 | /// DES加密字符串 65 | /// 66 | /// 待加密的字符串 67 | /// 68 | public string Encrypt(string value) 69 | { 70 | if (value.IsNullOrEmpty()) 71 | { 72 | return value; 73 | } 74 | 75 | try 76 | { 77 | DecryptCore(value); 78 | return value; 79 | } 80 | catch (Exception) 81 | { 82 | return EncryptCore(value); 83 | } 84 | } 85 | 86 | /// 87 | /// DES解密字符串 88 | /// 89 | /// 待解密的字符串 90 | /// 91 | public string Decrypt(string value) 92 | { 93 | if (value.IsNullOrEmpty()) 94 | { 95 | return value; 96 | } 97 | 98 | try 99 | { 100 | return DecryptCore(value); 101 | } 102 | catch (Exception) 103 | { 104 | return value; 105 | } 106 | } 107 | 108 | /// 109 | /// DES加密字符串 110 | /// 111 | /// 待加密的字符串 112 | /// 113 | private string EncryptCore(string value) 114 | { 115 | var bytes = Encoding.UTF8.GetBytes(value.NullThenEmpty()); 116 | using var stream = new MemoryStream(); 117 | using var cryptoStream = new CryptoStream(stream, desProvider.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write); 118 | cryptoStream.Write(bytes, 0, bytes.Length); 119 | cryptoStream.FlushFinalBlock(); 120 | return Convert.ToBase64String(stream.ToArray()); 121 | } 122 | 123 | /// 124 | /// DES解密字符串 125 | /// 126 | /// 待解密的字符串 127 | /// 128 | private string DecryptCore(string value) 129 | { 130 | var bytes = Convert.FromBase64String(value); 131 | using var stream = new MemoryStream(); 132 | using var cryptoStream = new CryptoStream(stream, desProvider.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write); 133 | cryptoStream.Write(bytes, 0, bytes.Length); 134 | cryptoStream.FlushFinalBlock(); 135 | return Encoding.UTF8.GetString(stream.ToArray()); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Corefx/Security/MD5.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | 6 | namespace System.Security 7 | { 8 | /// 9 | /// Md5摘要 10 | /// 11 | public static class MD5 12 | { 13 | /// 14 | /// 获取Md5 15 | /// 16 | /// 源字符串 17 | /// 是否使用大写 18 | /// 19 | public static string ComputeHashString(string str, bool upperCase = true) 20 | { 21 | if (str == null) 22 | { 23 | return null; 24 | } 25 | 26 | using var md5 = new MD5CryptoServiceProvider(); 27 | var format = upperCase ? "X2" : "x2"; 28 | var data = md5.ComputeHash(Encoding.UTF8.GetBytes(str)).Select(item => item.ToString(format)).ToArray(); 29 | return string.Join(string.Empty, data); 30 | } 31 | 32 | 33 | /// 34 | /// 获取Md5 35 | /// 36 | /// 流 37 | /// 是否使用大写 38 | /// 39 | public static string ComputeHashString(Stream stream, bool upperCase = true) 40 | { 41 | if (stream == null) 42 | { 43 | return null; 44 | } 45 | 46 | using var md5 = new MD5CryptoServiceProvider(); 47 | var format = upperCase ? "X2" : "x2"; 48 | var data = md5.ComputeHash(stream).Select(item => item.ToString(format)).ToArray(); 49 | return string.Join(string.Empty, data); 50 | } 51 | 52 | /// 53 | /// 获取Md5 54 | /// 55 | /// 数据 56 | /// 57 | /// 58 | /// 是否使用大写 59 | /// 60 | public static string ComputeHashString(byte[] byteArray, int offset, int count, bool upperCase = true) 61 | { 62 | if (byteArray == null) 63 | { 64 | return null; 65 | } 66 | 67 | using var md5 = new MD5CryptoServiceProvider(); 68 | var format = upperCase ? "X2" : "x2"; 69 | var data = md5.ComputeHash(byteArray, offset, count).Select(item => item.ToString(format)).ToArray(); 70 | return string.Join(string.Empty, data); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Corefx/Text.Json.Serialization/JsonLocalDateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace System.Text.Json.Serialization 4 | { 5 | /// 6 | /// 表示DateTime的本地格式化转换器 7 | /// 8 | public class JsonLocalDateTimeConverter : JsonConverter 9 | { 10 | /// 11 | /// 获取默认实例 12 | /// 13 | public static JsonLocalDateTimeConverter Default { get; } = new JsonLocalDateTimeConverter(); 14 | 15 | /// 16 | /// 获取时间格式 17 | /// 18 | public string DateTimeFormat { get; } 19 | 20 | /// 21 | /// DateTime的本地格式化转换器 22 | /// 23 | /// 时间格式 24 | /// 25 | public JsonLocalDateTimeConverter(string dateTimeFormat = "O") 26 | { 27 | this.DateTimeFormat = dateTimeFormat ?? throw new ArgumentNullException(nameof(dateTimeFormat)); 28 | } 29 | 30 | /// 31 | /// 读取时间 32 | /// 统一转换为本地时间 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 39 | { 40 | var value = DateTime.Parse(reader.GetString()); 41 | if (value.Kind == DateTimeKind.Local) 42 | { 43 | return value; 44 | } 45 | 46 | if (value.Kind == DateTimeKind.Utc) 47 | { 48 | return value.ToLocalTime(); 49 | } 50 | 51 | return DateTime.SpecifyKind(value, DateTimeKind.Local); 52 | } 53 | 54 | /// 55 | /// 写入时间 56 | /// 统一转换为本地时间,再格式化 57 | /// 58 | /// 59 | /// 60 | /// 61 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) 62 | { 63 | if (value.Kind == DateTimeKind.Utc) 64 | { 65 | value = value.ToLocalTime(); 66 | } 67 | else if (value.Kind == DateTimeKind.Unspecified) 68 | { 69 | value = DateTime.SpecifyKind(value, DateTimeKind.Local); 70 | } 71 | 72 | var dateTimeString = value.ToString(this.DateTimeFormat, CultureInfo.InvariantCulture); 73 | writer.WriteStringValue(dateTimeString); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Corefx/Threading.Tasks/TaskPipeScheduler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Linq; 3 | 4 | namespace System.Threading.Tasks 5 | { 6 | /// 7 | /// 表示任务调度器 8 | /// 9 | public abstract class TaskPipeScheduler 10 | { 11 | /// 12 | /// 获取非管道调度器 13 | /// 14 | public static TaskPipeScheduler NoPipe { get; } = new ThreadPoolScheduler(); 15 | 16 | /// 17 | /// 创建一个并发限制的调度器 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static TaskPipeScheduler Create(int concurrent) => new ConcurrentScheduler(concurrent); 23 | 24 | /// 25 | /// 调度任务 26 | /// 27 | /// 任务 28 | public void Schedule(Action workItem) 29 | { 30 | if (workItem != null) 31 | { 32 | this.Schedule(() => 33 | { 34 | workItem(); 35 | return Task.CompletedTask; 36 | }); 37 | } 38 | } 39 | 40 | /// 41 | /// 调度任务 42 | /// 43 | /// 任务 44 | public abstract void Schedule(Func workItem); 45 | 46 | 47 | /// 48 | /// 线程池IO队列 49 | /// 50 | private class ThreadPoolScheduler : TaskPipeScheduler 51 | { 52 | /// 53 | /// 调度任务 54 | /// 55 | /// 56 | public override void Schedule(Func workItem) 57 | { 58 | ThreadPool.UnsafeQueueUserWorkItem(state => workItem(), null); 59 | } 60 | } 61 | 62 | 63 | /// 64 | /// 表示并发控制的IO任务调度器 65 | /// 66 | private class ConcurrentScheduler : TaskPipeScheduler 67 | { 68 | private int queueIndex = 0; 69 | private readonly InlineWorkItemQueue[] inlineQueues; 70 | 71 | /// 72 | /// 并发控制的IO任务调度器 73 | /// 74 | /// 任务最大并发数 75 | /// 76 | public ConcurrentScheduler(int concurrent) 77 | { 78 | if (concurrent <= 0) 79 | { 80 | throw new ArgumentOutOfRangeException(nameof(concurrent)); 81 | } 82 | this.inlineQueues = Enumerable.Range(0, concurrent).Select(item => new InlineWorkItemQueue()).ToArray(); 83 | } 84 | 85 | 86 | /// 87 | /// 调度任务 88 | /// 89 | /// 任务工厂 90 | /// 91 | public override void Schedule(Func workItem) 92 | { 93 | if (workItem == null) 94 | { 95 | throw new ArgumentNullException(nameof(workItem)); 96 | } 97 | 98 | this.inlineQueues[queueIndex].Add(workItem); 99 | this.queueIndex = (this.queueIndex + 1) % this.inlineQueues.Length; 100 | } 101 | 102 | /// 103 | /// 表示排队的IO队列 104 | /// 105 | private class InlineWorkItemQueue : IThreadPoolWorkItem 106 | 107 | { 108 | private int doingWork; 109 | private readonly ConcurrentQueue> workItems = new ConcurrentQueue>(); 110 | 111 | /// 112 | /// 添加任务 113 | /// 114 | /// 115 | public void Add(Func workItem) 116 | { 117 | this.workItems.Enqueue(workItem); 118 | if (Interlocked.CompareExchange(ref this.doingWork, 1, 0) == 0) 119 | { 120 | 121 | ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); 122 | 123 | } 124 | } 125 | 126 | 127 | /// 128 | /// 执行 129 | /// 130 | void IThreadPoolWorkItem.Execute() 131 | { 132 | Execute(this); 133 | } 134 | 135 | 136 | /// 137 | /// 执行 138 | /// 139 | /// 140 | private static async void Execute(object state) 141 | { 142 | var _this = (InlineWorkItemQueue)state; 143 | while (true) 144 | { 145 | while (_this.workItems.TryDequeue(out var item)) 146 | { 147 | await item(); 148 | } 149 | 150 | _this.doingWork = 0; 151 | Thread.MemoryBarrier(); 152 | 153 | if (_this.workItems.IsEmpty == true) 154 | { 155 | break; 156 | } 157 | 158 | if (Interlocked.Exchange(ref _this.doingWork, 1) == 1) 159 | { 160 | break; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Corefx/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Linq.Expressions; 3 | 4 | namespace System 5 | { 6 | /// 7 | /// 类型扩展 8 | /// 9 | public static class TypeExtensions 10 | { 11 | /// 12 | /// 类型的默认值缓存 13 | /// 14 | private static readonly ConcurrentCache typeDefaultValueCache = new ConcurrentCache(); 15 | 16 | 17 | /// 18 | /// 返回类型的默认值 19 | /// 20 | /// 21 | /// 22 | public static object DefaultValue(this Type type) 23 | { 24 | if (type == null) 25 | { 26 | return null; 27 | } 28 | 29 | return typeDefaultValueCache.GetOrAdd(type, t => 30 | { 31 | var value = Expression.Convert(Expression.Default(t), typeof(object)); 32 | return Expression.Lambda>(value).Compile().Invoke(); 33 | }); 34 | } 35 | 36 | 37 | /// 38 | /// 是否可以从TBase类型派生 39 | /// 40 | /// 41 | /// 42 | /// 43 | /// 44 | public static bool IsInheritFrom(this Type type) 45 | { 46 | return typeof(TBase).IsAssignableFrom(type); 47 | } 48 | 49 | /// 50 | /// 返回类型的非可空类型 51 | /// 52 | /// 53 | /// 54 | /// 55 | public static Type NonNullableType(this Type type) 56 | { 57 | return Nullable.GetUnderlyingType(type) ?? type; 58 | } 59 | 60 | /// 61 | /// 值是可以为null 62 | /// 63 | /// 64 | /// 65 | /// 66 | public static bool CanBeNullValue(this Type type) 67 | { 68 | if (type == null) 69 | { 70 | throw new ArgumentNullException(nameof(type)); 71 | } 72 | 73 | if (type.IsValueType == false) 74 | { 75 | return true; 76 | } 77 | 78 | return Nullable.GetUnderlyingType(type) != null; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Domain/DbSetExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using QMapper; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Threading.Tasks; 9 | 10 | namespace Domain 11 | { 12 | /// 13 | /// Dbset扩展 14 | /// 15 | public static class DbSetExtensions 16 | { 17 | /// 18 | /// 将Value更新到数据库 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 值 24 | /// 25 | /// 26 | /// 27 | public static Task UpdateFromAsync(this DbSet set, TValue value) 28 | where T : class 29 | where TValue : class, IStringIdable 30 | { 31 | if (value == null) 32 | { 33 | throw new ArgumentNullException(nameof(value)); 34 | } 35 | 36 | if (value.Id.IsNullOrEmpty()) 37 | { 38 | throw new ArgumentException("value的Id值不为能null"); 39 | } 40 | 41 | return set.UpdateFromAsync(value, value.Id); 42 | } 43 | 44 | /// 45 | /// 将Value更新到数据库 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 值 51 | /// id 52 | /// 53 | /// 54 | public static async Task UpdateFromAsync(this DbSet set, TValue value, string id) 55 | where T : class 56 | where TValue : class 57 | { 58 | if (value == null) 59 | { 60 | throw new ArgumentNullException(nameof(value)); 61 | } 62 | if (id.IsNullOrEmpty()) 63 | { 64 | throw new ArgumentNullException(nameof(id)); 65 | } 66 | 67 | var local = await set.FindAsync(id); 68 | if (local != null) 69 | { 70 | value.AsMap().To(local); 71 | } 72 | return local; 73 | } 74 | 75 | /// 76 | /// 将Value更新到数据库 77 | /// 78 | /// 79 | /// 80 | /// 81 | /// 值 82 | /// id 83 | /// 84 | /// 85 | public static async Task UpdateEntityAsync(this DbSet set, TValue value, string id) 86 | where T : class, IEntity 87 | where TValue : class 88 | { 89 | var local = await set.UpdateFromAsync(value, id); 90 | local.LastModifyTime = DateTime.Now; 91 | return local; 92 | } 93 | 94 | /// 95 | /// 通过id标记记录为删除 96 | /// 97 | /// 98 | /// id 99 | /// 100 | public static async Task RemoveAsync(this DbSet set, [NotNull] string id) where T : class 101 | { 102 | var model = await set.FindAsync(id); 103 | if (model != null) 104 | { 105 | set.Remove(model); 106 | } 107 | return model; 108 | } 109 | 110 | /// 111 | /// 通过条件标记第一条记录为删除 112 | /// 113 | /// 114 | /// 115 | /// 116 | public static async Task RemoveAsync(this DbSet set, [NotNull] Expression> where) where T : class 117 | { 118 | var model = await set.Where(where).FirstOrDefaultAsync(); 119 | set.Remove(model); 120 | return model; 121 | } 122 | 123 | 124 | /// 125 | /// 通过条件标记记录为删除 126 | /// 127 | /// 128 | /// 129 | /// 130 | public static Task RemoveRangeAsync(this DbSet set, IEnumerable id) where T : class, IStringIdable 131 | { 132 | var where = System.Linq.Expressions.Predicate.CreateOrEqual(item => item.Id, id); 133 | return set.RemoveRangeAsync(where); 134 | } 135 | 136 | /// 137 | /// 通过条件标记记录为删除 138 | /// 139 | /// 140 | /// 141 | /// 142 | public static async Task RemoveRangeAsync(this DbSet set, [NotNull] Expression> where) where T : class 143 | { 144 | var model = await set.Where(where).ToArrayAsync(); 145 | set.RemoveRange(model); 146 | return model; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Domain/Domain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Domain/ICreatorable.cs: -------------------------------------------------------------------------------- 1 | namespace Domain 2 | { 3 | /// 4 | /// 表示创建者接口 5 | /// 6 | public interface ICreatorable 7 | { 8 | /// 9 | /// 创建者 10 | /// 11 | string Creator { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Domain/IEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Domain 4 | { 5 | /// 6 | /// 定义实体的接口 7 | /// 8 | public interface IEntity : IStringIdable 9 | { 10 | /// 11 | /// 创建时间 12 | /// 13 | DateTime CreateTime { get; set; } 14 | 15 | /// 16 | /// 最近更新时间 17 | /// 18 | DateTime LastModifyTime { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Domain/IEntityView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Domain 4 | { 5 | /// 6 | /// 定义实体视图的接口 7 | /// 8 | public interface IEntityView 9 | { 10 | /// 11 | /// 获取或设置唯一标识 12 | /// 13 | Guid Id { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Domain/IIntercomable.cs: -------------------------------------------------------------------------------- 1 | namespace Domain 2 | { 3 | /// 4 | /// 定义可用于对讲的对象 5 | /// 6 | public interface IIntercomable 7 | { 8 | /// 9 | /// 对讲账号 10 | /// 11 | string IntercomUserId { get; set; } 12 | 13 | /// 14 | /// 对讲token 15 | /// 16 | string IntercomToken { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Domain/IMongoEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Domain 4 | { 5 | /// 6 | /// 定义Mongodb实体接口 7 | /// 8 | public interface IMongoEntity : IStringIdable 9 | { 10 | /// 11 | /// 创建时间 12 | /// 13 | DateTime CreateTime { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Domain/IStringIdable.cs: -------------------------------------------------------------------------------- 1 | namespace Domain 2 | { 3 | /// 4 | /// 定义支持string类型的Id属性 5 | /// 6 | public interface IStringIdable 7 | { 8 | /// 9 | /// 获取或设置唯一标识 10 | /// 11 | string Id { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Domain/ITenantable.cs: -------------------------------------------------------------------------------- 1 | namespace Domain 2 | { 3 | /// 4 | /// 定义支持多租户的接口 5 | /// 6 | public interface ITenantable 7 | { 8 | /// 9 | /// 获取或设置租户Id 10 | /// 11 | string TenantId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Domain/IUser.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace Domain 4 | { 5 | /// 6 | /// 定义当前登录用户信息 7 | /// 8 | public interface IUser 9 | { 10 | /// 11 | /// 获取用户凭据 12 | /// 13 | ClaimsPrincipal Principal { get; } 14 | 15 | /// 16 | /// 获取凭据的sub类型 17 | /// 18 | string Id { get; } 19 | 20 | /// 21 | /// 获取凭据的name类型 22 | /// 23 | string Name { get; } 24 | 25 | /// 26 | /// 获取凭据的tenant_id类型 27 | /// 28 | string TenantId { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Domain/IUserAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace Domain 2 | { 3 | /// 4 | /// 定义用户访问接口 5 | /// 6 | public interface IUserAccesstor 7 | { 8 | /// 9 | /// 获取当前登录用户信息 10 | /// 11 | IUser User { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Domain/MongoDbContext.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Domain 5 | { 6 | /// 7 | /// 表示mongoDb上下文 8 | /// 9 | public class MongoDbContext 10 | { 11 | /// 12 | /// 客户端 13 | /// 14 | private readonly MongoClient client; 15 | 16 | /// 17 | /// mongoDb上下文 18 | /// 19 | /// mongodb://[username:password@]host1[:port1][,host2[:port2],…[,hostN[:portN]]][/[database][?options]] 20 | public MongoDbContext([NotNull]string connectionString) 21 | { 22 | this.client = new MongoClient(connectionString); 23 | } 24 | 25 | /// 26 | /// 获取T类型的集合操作 27 | /// 28 | /// 集合对象类型 29 | /// db名称 30 | /// 31 | public MongoDbSet Set([NotNull]string dbName) where T : class, IStringIdable 32 | { 33 | return this.Set(dbName, typeof(T).Name); 34 | } 35 | 36 | /// 37 | /// 获取集合操作 38 | /// 39 | /// 集合对象类型 40 | /// db名称 41 | /// 集合名称 42 | /// 43 | public MongoDbSet Set([NotNull]string dbName, [NotNull]string collectionName) where T : class, IStringIdable 44 | { 45 | var db = client.GetDatabase(dbName); 46 | var collection = db.GetCollection(collectionName); 47 | return new MongoDbSet(collection); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Domain/PagingExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace Domain 10 | { 11 | /// 12 | /// 分页信息 13 | /// 14 | public static class PagingExtensions 15 | { 16 | /// 17 | /// 执行分页 18 | /// 性能最好 19 | /// 20 | /// 实体类型 21 | /// Id类型 22 | /// 数据源 23 | /// 排序字符串 24 | /// 分页索引 25 | /// 分页大小 26 | /// Id选择器 27 | /// 28 | /// 29 | public static async Task> ToPageAsync([NotNull]this IQueryable source, [NotNull] string orderBy, int pageIndex, int pageSize, [NotNull]Expression> idSelector) 30 | where T : class 31 | where TId : class 32 | { 33 | if (pageSize <= 0) 34 | { 35 | throw new ArgumentOutOfRangeException(nameof(pageSize), "值不能小于1"); 36 | } 37 | 38 | source = source.Where(Predicate.Create(idSelector, null, Operator.NotEqual)); 39 | int total = await source.CountAsync(); 40 | var inc = total % pageSize > 0 ? 0 : -1; 41 | var maxPageIndex = (int)Math.Floor((double)total / pageSize) + inc; 42 | pageIndex = Math.Max(0, Math.Min(pageIndex, maxPageIndex)); 43 | 44 | var idQuery = source.OrderBy(orderBy).Skip(pageIndex * pageSize).Take(pageSize).Select(idSelector); 45 | var datas = await source.Join(idQuery, idSelector, item => item, (item, id) => item).OrderBy(orderBy).ToArrayAsync(); 46 | 47 | return new Page 48 | { 49 | PageIndex = pageIndex, 50 | PageSize = pageSize, 51 | TotalCount = total, 52 | DataArray = datas 53 | }; 54 | } 55 | 56 | /// 57 | /// 执行分页 58 | /// 59 | /// 实体类型 60 | /// 数据源 61 | /// 排序字符串 62 | /// 分页索引 63 | /// 分页大小 64 | /// 65 | /// 66 | public static async Task> ToPageAsync([NotNull]this IQueryable source, [NotNull] string orderBy, int pageIndex, int pageSize) where T : class 67 | { 68 | if (pageSize <= 0) 69 | { 70 | throw new ArgumentOutOfRangeException(nameof(pageSize), "值不能小于1"); 71 | } 72 | 73 | int total = await source.CountAsync(); 74 | var inc = total % pageSize > 0 ? 0 : -1; 75 | var maxPageIndex = (int)Math.Floor((double)total / pageSize) + inc; 76 | pageIndex = Math.Max(0, Math.Min(pageIndex, maxPageIndex)); 77 | 78 | var datas = await source.OrderBy(orderBy).Skip(pageIndex * pageSize).Take(pageSize).AsNoTracking().ToArrayAsync(); 79 | return new Page 80 | { 81 | PageIndex = pageIndex, 82 | PageSize = pageSize, 83 | TotalCount = total, 84 | DataArray = datas 85 | }; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Domain/SqlDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace Domain 4 | { 5 | /// 6 | /// 表示数据库上下文 7 | /// 8 | public class SqlDbContext : DbContext 9 | { 10 | /// 11 | /// 数据库上下文 12 | /// 13 | /// 14 | public SqlDbContext(DbContextOptions options) 15 | : base(options) 16 | { 17 | } 18 | 19 | /// 20 | /// 释放资源 21 | /// 22 | public override void Dispose() 23 | { 24 | base.Dispose(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Domain/StringTrimFlagAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Domain 6 | { 7 | [AttributeUsage(AttributeTargets.Property)] 8 | public class StringTrimFlagAttribute : Attribute 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web.Template 2 | asp.net core 3.x项目模板 3 | 4 | ### 项目介绍 5 | 6 | #### Corefx 7 | 公共基础类,是corefx项目的补充,此部分不依赖于asp.net core 8 | 9 | #### Core 10 | 框架需要的核心接口、类型或者Web相关等工具 11 | 12 | #### Core.Web 13 | asp.net Web层的一些核心公共处理 14 | -------------------------------------------------------------------------------- /Web.Benchmark/DemoContext.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Web.Benchmark 7 | { 8 | public class DemoContext 9 | { 10 | [Benchmark] 11 | public void Method1() 12 | { 13 | } 14 | 15 | [Benchmark] 16 | public void Method2() 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Web.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using System; 3 | 4 | namespace Web.Benchmark 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | var summary = BenchmarkRunner.Run(); 11 | Console.ReadLine(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Web.Benchmark/Web.Benchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | Web.Benchmark 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Web.Host/Controllers/v1/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.Web; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Threading.Tasks; 9 | 10 | namespace Web.Host.Controllers.v1 11 | { 12 | 13 | [Register(ServiceLifetime.Scoped)] 14 | public class AService 15 | { 16 | } 17 | 18 | /// 19 | /// 演示控制器 20 | /// 21 | public class ValuesController : ApiController 22 | { 23 | /// 24 | /// 获取列表 25 | /// 26 | /// 27 | [HttpGet] 28 | public ApiResult Get([FromServices] AService a) 29 | { 30 | return new string[] { "ok" }; 31 | } 32 | 33 | /// 34 | /// 获取列表一条 35 | /// 36 | /// 37 | /// 38 | [HttpGet("{id}")] 39 | public ApiResult Get(int id) 40 | { 41 | return "value"; 42 | } 43 | 44 | /// 45 | /// 添加记录 46 | /// 47 | /// 48 | [HttpPost] 49 | public ApiResult Post([FromBody] string value) 50 | { 51 | return true; 52 | } 53 | 54 | /// 55 | /// 更新记录 56 | /// 57 | /// 58 | /// 59 | [HttpPut("{id}")] 60 | public ApiResult Put(int id, [FromBody] string value) 61 | { 62 | return true; 63 | } 64 | 65 | /// 66 | /// 删除记录 67 | /// 68 | /// 69 | [HttpDelete("{id}")] 70 | public ApiResult Delete(int id) 71 | { 72 | throw new Exception(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Web.Host/Controllers/v2/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.Web; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace Web.Host.Controllers.v2 9 | { 10 | /// 11 | /// 演示控制器 12 | /// 13 | public class ValuesController : ApiController 14 | { 15 | /// 16 | /// 获取列表 17 | /// 18 | /// 19 | [HttpGet] 20 | public ApiResult Get() 21 | { 22 | return new string[] { "ok" }; 23 | } 24 | 25 | /// 26 | /// 获取列表一条 27 | /// 28 | /// 29 | /// 30 | [HttpGet("{id}")] 31 | public ApiResult Get(int id) 32 | { 33 | return "value"; 34 | } 35 | 36 | /// 37 | /// 添加记录 38 | /// 39 | /// 40 | [HttpPost] 41 | public ApiResult Post([FromBody] string value) 42 | { 43 | return true; 44 | } 45 | 46 | /// 47 | /// 更新记录 48 | /// 49 | /// 50 | /// 51 | [HttpPut("{id}")] 52 | public ApiResult Put(int id, [FromBody] string value) 53 | { 54 | return true; 55 | } 56 | 57 | /// 58 | /// 删除记录 59 | /// 60 | /// 61 | [HttpDelete("{id}")] 62 | public ApiResult Delete(int id) 63 | { 64 | throw new Exception(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Web.Host/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview6 2 | COPY app /app 3 | WORKDIR /app 4 | EXPOSE 80 5 | EXPOSE 443 6 | ENTRYPOINT ["dotnet", "Web.Host.dll"] -------------------------------------------------------------------------------- /Web.Host/Program.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Net-Team/Web.Template/be93830a2f7056c4f2189a2e63b77f1b0f67b023/Web.Host/Program.cs -------------------------------------------------------------------------------- /Web.Host/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "Web.Host": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "launchUrl": "swagger/template/index.html", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | }, 11 | "nativeDebugging": true, 12 | "applicationUrl": "http://localhost:5178" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Web.Host/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Net-Team/Web.Template/be93830a2f7056c4f2189a2e63b77f1b0f67b023/Web.Host/Startup.cs -------------------------------------------------------------------------------- /Web.Host/Web.Host.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 2fc22cb2-07c4-4805-9663-bb6f8ce68411 6 | Linux 7 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Web.Host/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "Web.Host", 5 | "script": "dotnet Web.Host.dll", 6 | "env": { 7 | "ASPNETCORE_ENVIRONMENT": "Development", 8 | "ASPNETCORE_URLS": "http://10.0.1.83:6001" 9 | } 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Web.Host/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /Web.Host/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": { 3 | "GatewayOptions": { 4 | "HttpHost": "http://119.27.175.69/" 5 | }, 6 | "ServiceOptions": { 7 | "Name": "template", 8 | "HealthRoute": "/health" 9 | } 10 | }, 11 | "ConnectionStrings": { 12 | "Redis": "localhost:6379,password=", 13 | "Mongodb": "mongodb://username:password@119.27.175.69:27017", 14 | "SqlDbContext": "Data Source=.;Initial Catalog=Web; Persist Security Info=True;User ID=sa;Password=sql2008" 15 | }, 16 | "HttpApi": { 17 | "IBaiduApi": { 18 | "HttpHost": "http://www.baidu.com/", 19 | "FormatOptions": { 20 | "UseCamelCase": true, 21 | "DateTimeFormat": "yyyy'-'MM'-'dd'T'HH':'mm':'ssK" 22 | } 23 | } 24 | }, 25 | "ExceptionLess": { 26 | "ApiKey": "La2TqlbtlFLenEoj6e0Tf5dGgx1czVAZKN5SYOFI", 27 | "ServerUrl": "http://www.ExceptionLess.com:8777/" 28 | }, 29 | "AllowedHosts": "*" 30 | } 31 | -------------------------------------------------------------------------------- /Web.Host/build.cmd: -------------------------------------------------------------------------------- 1 | dotnet build -c Release -v m 2 | dotnet publish -c Release -v m -o ./app -------------------------------------------------------------------------------- /Web.Host/docker.build.cmd: -------------------------------------------------------------------------------- 1 | docker.exe rmi {name}:latest -f 2 | docker.exe build -t {name}:latest . -------------------------------------------------------------------------------- /Web.Host/docker.build.sh: -------------------------------------------------------------------------------- 1 | docker rmi {name}:latest -f 2 | docker build -t {name}:latest . -------------------------------------------------------------------------------- /Web.Host/docker.run.sh: -------------------------------------------------------------------------------- 1 | docker run -d --name {name} \ 2 | -p 127.0.0.1:{port}:80 \ 3 | -e "@ServiceOptions:GatewayProxyUri={gateway}" \ 4 | -e "@ConnectionStrings:Redis={redis}" \ 5 | {name}:latest -------------------------------------------------------------------------------- /Web.Template.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{A75D1A49-515A-4D35-BEAB-81A60A96BCA8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Corefx", "Corefx\Corefx.csproj", "{3EA23D23-5CA7-4949-B131-C77261D76347}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Web", "Core.Web\Core.Web.csproj", "{146A4226-A105-489F-9A4C-8C74B359E644}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Host", "Web.Host\Web.Host.csproj", "{969F9C70-F48C-4A77-9FEB-3DA490B493C7}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Test", "Web.Test\Web.Test.csproj", "{86E5F344-8C11-4B0A-9C13-02EFC85AD4F4}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Benchmark", "Web.Benchmark\Web.Benchmark.csproj", "{9034FECC-65FD-4422-8E90-60CAB80E55DB}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{30EBE09C-A460-4F6A-9C14-4896DA5A6593}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "Domain\Domain.csproj", "{1EBFEE1B-51B1-45B1-98BB-8C47B28AD997}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cores", "Cores", "{1044412A-1370-44BD-842C-9BC5380A7540}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {A75D1A49-515A-4D35-BEAB-81A60A96BCA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {A75D1A49-515A-4D35-BEAB-81A60A96BCA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {A75D1A49-515A-4D35-BEAB-81A60A96BCA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {A75D1A49-515A-4D35-BEAB-81A60A96BCA8}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {3EA23D23-5CA7-4949-B131-C77261D76347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {3EA23D23-5CA7-4949-B131-C77261D76347}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {3EA23D23-5CA7-4949-B131-C77261D76347}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {3EA23D23-5CA7-4949-B131-C77261D76347}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {146A4226-A105-489F-9A4C-8C74B359E644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {146A4226-A105-489F-9A4C-8C74B359E644}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {146A4226-A105-489F-9A4C-8C74B359E644}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {146A4226-A105-489F-9A4C-8C74B359E644}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {969F9C70-F48C-4A77-9FEB-3DA490B493C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {969F9C70-F48C-4A77-9FEB-3DA490B493C7}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {969F9C70-F48C-4A77-9FEB-3DA490B493C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {969F9C70-F48C-4A77-9FEB-3DA490B493C7}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {86E5F344-8C11-4B0A-9C13-02EFC85AD4F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {86E5F344-8C11-4B0A-9C13-02EFC85AD4F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {86E5F344-8C11-4B0A-9C13-02EFC85AD4F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {86E5F344-8C11-4B0A-9C13-02EFC85AD4F4}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {9034FECC-65FD-4422-8E90-60CAB80E55DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {9034FECC-65FD-4422-8E90-60CAB80E55DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {9034FECC-65FD-4422-8E90-60CAB80E55DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {9034FECC-65FD-4422-8E90-60CAB80E55DB}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {30EBE09C-A460-4F6A-9C14-4896DA5A6593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {30EBE09C-A460-4F6A-9C14-4896DA5A6593}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {30EBE09C-A460-4F6A-9C14-4896DA5A6593}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {30EBE09C-A460-4F6A-9C14-4896DA5A6593}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {1EBFEE1B-51B1-45B1-98BB-8C47B28AD997}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {1EBFEE1B-51B1-45B1-98BB-8C47B28AD997}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {1EBFEE1B-51B1-45B1-98BB-8C47B28AD997}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {1EBFEE1B-51B1-45B1-98BB-8C47B28AD997}.Release|Any CPU.Build.0 = Release|Any CPU 62 | EndGlobalSection 63 | GlobalSection(SolutionProperties) = preSolution 64 | HideSolutionNode = FALSE 65 | EndGlobalSection 66 | GlobalSection(NestedProjects) = preSolution 67 | {A75D1A49-515A-4D35-BEAB-81A60A96BCA8} = {1044412A-1370-44BD-842C-9BC5380A7540} 68 | {3EA23D23-5CA7-4949-B131-C77261D76347} = {1044412A-1370-44BD-842C-9BC5380A7540} 69 | {146A4226-A105-489F-9A4C-8C74B359E644} = {1044412A-1370-44BD-842C-9BC5380A7540} 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {BCE391DB-76DE-4076-BAEE-93AD4779F372} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /Web.Test/001.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Net-Team/Web.Template/be93830a2f7056c4f2189a2e63b77f1b0f67b023/Web.Test/001.apk -------------------------------------------------------------------------------- /Web.Test/1.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Net-Team/Web.Template/be93830a2f7056c4f2189a2e63b77f1b0f67b023/Web.Test/1.xls -------------------------------------------------------------------------------- /Web.Test/1.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Net-Team/Web.Template/be93830a2f7056c4f2189a2e63b77f1b0f67b023/Web.Test/1.xlsx -------------------------------------------------------------------------------- /Web.Test/Core/ApkTest.cs: -------------------------------------------------------------------------------- 1 | using Core.Apk; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace Web.Test.Core 8 | { 9 | public class ApkTest 10 | { 11 | [Fact] 12 | public void Test() 13 | { 14 | ApkReader.ReadMedia("001.apk"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Web.Test/Core/MapTest.cs: -------------------------------------------------------------------------------- 1 | using QMapper; 2 | using Xunit; 3 | 4 | namespace Web.Test.Core 5 | { 6 | public class MapTest 7 | { 8 | [Fact] 9 | public void Map() 10 | { 11 | var a = new A(); 12 | var b = new A().AsMap().To(); 13 | 14 | Assert.Equal(a.Age, b.Age); 15 | Assert.Equal(a.Email, b.Email); 16 | Assert.Equal(a.Name, b.Name); 17 | } 18 | 19 | 20 | [Fact] 21 | public void MapIgnore() 22 | { 23 | var a = new A(); 24 | var b = new A().AsMap().Ignore(i => i.Name).To(); 25 | 26 | Assert.Equal(a.Age, b.Age); 27 | Assert.Equal(a.Email, b.Email); 28 | Assert.NotEqual(a.Name, b.Name); 29 | } 30 | 31 | class A 32 | { 33 | public string Name { get; set; } = "A"; 34 | 35 | public int? Age { get; set; } = 9; 36 | 37 | public string Email { get; set; } = "@A"; 38 | } 39 | 40 | class B 41 | { 42 | public string Name { get; set; } = "B"; 43 | 44 | public int Age { get; set; } = 100; 45 | 46 | public string Email { get; set; } = "@B"; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Web.Test/Core/XlsDocTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | using Core.Xls; 6 | using System.Linq; 7 | 8 | namespace Web.Test.Core 9 | { 10 | public class XlsDocTest 11 | { 12 | [Fact] 13 | public void Test() 14 | { 15 | var items = new XlsDoc("1.xls")[0].ToArray(); 16 | } 17 | 18 | class Model 19 | { 20 | public int age { get; set; } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Web.Test/Corefx/IEnumerableExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | 6 | namespace Web.Test.Corefx 7 | { 8 | public class IEnumerableExtensionsTest 9 | { 10 | class A 11 | { 12 | public string Name { get; set; } 13 | } 14 | 15 | [Fact] 16 | public void Distinct() 17 | { 18 | var a = new A { Name = "a" }; 19 | var b = new A { Name = "a" }; 20 | var c = new A { Name = "c" }; 21 | 22 | var v = new[] { a, b, c }.Distinct(item => item.Name).ToArray(); 23 | Assert.True(v.Length == 2); 24 | Assert.True(v.Last().Name == "c"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Web.Test/Web.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------