├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── custom.md ├── .gitignore ├── App ├── .config │ └── dotnet-tools.json ├── ApiClients │ ├── IUserApi.cs │ ├── UserHostedService.cs │ └── UserService.cs ├── App.csproj ├── Controllers │ ├── TokenFilterAttribute.cs │ ├── TokensController.cs │ └── UsersController.cs ├── Models │ ├── Gender.cs │ └── User.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── README.md ├── Startup.cs ├── TextFile.txt └── appsettings.json ├── AppAot ├── AppAot.csproj ├── AppData.cs ├── AppHostedService.cs ├── AppJsonSerializerContext.cs ├── ICloudflareApi.cs └── Program.cs ├── Directory.Build.props ├── LICENSE ├── README.md ├── README_zh.md ├── WebApiClientCore.Abstractions ├── ApiActionDescriptor.cs ├── ApiActionInvoker.cs ├── ApiDataTypeDescriptor.cs ├── ApiParameterDescriptor.cs ├── ApiReturnDescriptor.cs ├── Attributes │ ├── IApiActionAttribute.cs │ ├── IApiAttribute.cs │ ├── IApiCacheAttribute.cs │ ├── IApiFilterAttribute.cs │ ├── IApiParameterAttribute.cs │ ├── IApiReturnAttribute.cs │ ├── IAttributeEnable.cs │ └── IAttributeMultiplable.cs ├── CachePolicy.cs ├── CodeAnalysis │ ├── DynamicDependencyAttribute.cs │ ├── DynamicallyAccessedMemberTypes.cs │ ├── DynamicallyAccessedMembersAttribute.cs │ ├── RequiresDynamicCodeAttribute.cs │ ├── RequiresUnreferencedCodeAttribute.cs │ └── UnconditionalSuppressMessageAttribute.cs ├── Contexts │ ├── ApiParameterContext.cs │ ├── ApiRequestContext.cs │ ├── ApiResponseContext.cs │ ├── HttpClientContext.cs │ └── HttpContext.cs ├── HttpApiOptions.cs ├── HttpApiRequestMessage.cs ├── IApiActionDescriptorProvider.cs ├── IApiActionInvokerProvider.cs ├── IApiFilter.cs ├── IApiParameter.cs ├── IDataCollection.cs ├── IHttpApiActivator.cs ├── IHttpApiInterceptor.cs ├── IResponseCacheProvider.cs ├── KeyValue.cs ├── ResponseCacheEntry.cs ├── ResponseCacheResult.cs ├── ResultStatus.cs ├── Serialization │ ├── JsonConverters │ │ ├── JsonCompatibleConverter.cs │ │ └── JsonDateTimeConverter.cs │ ├── KeyNamingOptions.cs │ ├── KeyNamingStyle.cs │ └── KeyValueSerializerOptions.cs ├── Sign.snk └── WebApiClientCore.Abstractions.csproj ├── WebApiClientCore.Analyzers ├── Descriptors.cs ├── HttpApiContext.cs ├── HttpApiDiagnosticAnalyzer.cs ├── HttpApiDiagnosticProvider.cs ├── HttpApiSourceGenerator.cs ├── Providers │ ├── CtorAttributeDiagnosticProvider.cs │ ├── GenericInterfaceDiagnosticProvider.cs │ ├── GenericMethodDiagnosticProvider.cs │ ├── ModifierDiagnosticProvider.cs │ ├── NotMethodDefendedDiagnosticProvider.cs │ ├── RefParameterDiagnosticProvider.cs │ ├── ReturnTypeDiagnosticProvider.cs │ └── UriAttributeDiagnosticProvider.cs ├── README.md ├── Resx.Designer.cs ├── Resx.en-US.resx ├── Resx.resx ├── Sign.snk ├── SourceGenerator │ ├── HttpApiProxyClass.cs │ ├── HttpApiProxyClassInitializer.cs │ └── HttpApiSyntaxReceiver.cs └── WebApiClientCore.Analyzers.csproj ├── WebApiClientCore.Benchmarks ├── Program.cs ├── README.md ├── Requests │ ├── Benchmark.cs │ ├── HttpGetBenchmark.cs │ ├── HttpGetJsonBenchmark.cs │ ├── HttpPostJsonBenchmark.cs │ ├── HttpPostXmlBenchmark.cs │ ├── HttpPutFormBenchmark.cs │ ├── IRefitJsonApi.cs │ ├── IRefitXmlApi.cs │ ├── IWebApiClientCoreJsonApi.cs │ └── IWebApiClientCoreXmlApi.cs ├── Sign.snk ├── User.cs ├── WebApiClientCore.Benchmarks.csproj ├── results │ ├── WebApiClientCore.Benchmarks.Requests.HttpGetBenchmark-report-github.md │ ├── WebApiClientCore.Benchmarks.Requests.HttpGetJsonBenchmark-report-github.md │ ├── WebApiClientCore.Benchmarks.Requests.HttpPostJsonBenchmark-report-github.md │ ├── WebApiClientCore.Benchmarks.Requests.HttpPostXmlBenchmark-report-github.md │ └── WebApiClientCore.Benchmarks.Requests.HttpPutFormBenchmark-report-github.md └── user.json ├── WebApiClientCore.Extensions.JsonRpc ├── Attributes │ ├── JsonRpcMethodAttribute.cs │ ├── JsonRpcParamAttribute.cs │ └── JsonRpcParamsStyle.cs ├── Internals │ ├── JsonRpcContent.cs │ ├── JsonRpcParameters.cs │ └── JsonRpcRequest.cs ├── JsonRpcError.cs ├── JsonRpcResult.cs ├── README.md ├── Sign.snk └── WebApiClientCore.Extensions.JsonRpc.csproj ├── WebApiClientCore.Extensions.NewtonsoftJson ├── Attributes │ ├── JsonNetContentAttribute.cs │ └── JsonNetReturnAttribute.cs ├── DependencyInjection │ └── HttpClientBuilderExtensions.cs ├── JsonNetSerializerOptions.cs ├── README.md ├── Sign.snk └── WebApiClientCore.Extensions.NewtonsoftJson.csproj ├── WebApiClientCore.Extensions.OAuths ├── Attributes │ ├── ClientCredentialsTokenAttribute.cs │ ├── OAuthTokenAttribute.cs │ └── PasswordCredentialsTokenAttribute.cs ├── CodeAnalysis │ ├── DynamicDependencyAttribute.cs │ ├── DynamicallyAccessedMemberTypes.cs │ ├── DynamicallyAccessedMembersAttribute.cs │ ├── RequiresDynamicCodeAttribute.cs │ ├── RequiresUnreferencedCodeAttribute.cs │ └── UnconditionalSuppressMessageAttribute.cs ├── DependencyInjection │ ├── ITokenProviderBuilder.cs │ ├── TokenHandlerExtensions.cs │ ├── TokenProviderExtensions.GrantTypeClient.cs │ ├── TokenProviderExtensions.GrantTypePassword.cs │ └── TokenProviderExtensions.cs ├── Exceptions │ ├── TokenEndPointNullException.cs │ ├── TokenException.cs │ └── TokenNullException.cs ├── HttpMessageHandlers │ └── OAuthTokenHandler.cs ├── ITokenProvider.cs ├── ITokenProviderFactory.cs ├── ITokenProviderService.cs ├── README.md ├── Sign.snk ├── TokenProviderFactory.cs ├── TokenProviderFactoryOptions.cs ├── TokenProviderService.cs ├── TokenProviders │ ├── AsyncRoot.cs │ ├── ClientCredentials.cs │ ├── ClientCredentialsOptions.cs │ ├── ClientCredentialsTokenProvider.cs │ ├── Credentials.cs │ ├── DelegateTokenProvider.cs │ ├── OAuth2TokenClient.cs │ ├── PasswordCredentials.cs │ ├── PasswordCredentialsOptions.cs │ ├── PasswordCredentialsTokenProvider.cs │ ├── RefreshTokenCredentials.cs │ └── TokenProvider.cs ├── TokenResult.cs ├── TypeMatchMode.cs └── WebApiClientCore.Extensions.OAuths.csproj ├── WebApiClientCore.Extensions.SourceGenerator ├── Sign.snk └── WebApiClientCore.Extensions.SourceGenerator.csproj ├── WebApiClientCore.OpenApi.SourceGenerator ├── CSharpCode.cs ├── CSharpHtml.cs ├── HtmlTempate.cs ├── HttpApi.cs ├── HttpApiMethod.cs ├── HttpApiSettings.cs ├── HttpModel.cs ├── OpenApiDoc.cs ├── OpenApiDocOptions.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── README.md ├── Views │ ├── HttpApi.cshtml │ └── HttpModel.cshtml ├── WebApiClientCore.OpenApi.SourceGenerator.csproj └── petstore.json ├── WebApiClientCore.Test ├── Abstractions │ └── KeyValueTest.cs ├── Attributes │ ├── ActionAttributes │ │ ├── BasicAuthAttributeTest.cs │ │ ├── HttpDeleteAttributeTest.cs │ │ ├── HttpGetAttributeTest.cs │ │ ├── HttpHeadsAttributeTest.cs │ │ ├── HttpHostAttributeTest.cs │ │ ├── HttpOptionsAttributeTest.cs │ │ ├── HttpPostAttributeTest.cs │ │ └── HttpPutAttributeTest.cs │ ├── FormDataTextAttributeTest.cs │ ├── FormFieldAttributeTest.cs │ ├── HeaderAttributeTest.cs │ ├── ITestApi.cs │ ├── ParameterAttributes │ │ ├── FormContentAttributeTest.cs │ │ ├── HeadersAttributeTest.cs │ │ ├── IMyApi.cs │ │ ├── JsonContentAttributeTest.cs │ │ ├── PathQueryAttributeTest.cs │ │ ├── UriAttributeTest.cs │ │ └── XmlContentAttributeTest.cs │ ├── ReturnAttributes │ │ ├── ITestApi.cs │ │ ├── JsonXmlModelResultAttributeTest.cs │ │ ├── RawTypeResultAttributeTest.cs │ │ └── TestModel.cs │ └── TimeoutAttributeTest.cs ├── BuildinExtensions │ ├── HttpRequestHeaderExtensionsTest.cs │ ├── StringExtensionsTest.cs │ └── TypeExtensionsTest.cs ├── HttpApiTest.cs ├── HttpContents │ └── JsonContentTest.cs ├── HttpPathTest.cs ├── IDescriptorApi.cs ├── Implementations │ ├── ApiActionDescriptorTest.cs │ ├── ApiDataTypeDescriptorTest.cs │ ├── ApiParameterDescriptorTest.cs │ ├── DataValidatorTest.cs │ ├── DefaultDataCollectionTest.cs │ ├── HttpApiRequestMessageTest.cs │ ├── ResponseCacheProviderTest.cs │ ├── Tasks │ │ ├── ActionHandleTaskTest.cs │ │ └── ActionRetryTaskTest.cs │ └── TypeAttributes │ │ └── HttpContentTypeAttributeTest.cs ├── Internals │ ├── HttpUtilTest.cs │ ├── LambdaTest.cs │ ├── MediaTypeUtilTest.cs │ ├── RecyclableBufferWriterTest.cs │ ├── UriValueTest.cs │ └── ValueStringBuilderTest.cs ├── JsonStringTest.cs ├── Microsoft.Extensions │ └── DependencyInjection │ │ └── DependencyInjectionTest.cs ├── Parameters │ ├── ITestApi.cs │ └── JsonPatchDocumentTest.cs ├── Serialization │ ├── FormatModel.cs │ ├── JsonConverters │ │ └── JsonCompatibleConverterTest.cs │ ├── KeyValueSerializerTest.cs │ └── XmlSerializerTest.cs ├── Test.snk ├── TestRequestContext.cs └── WebApiClientCore.Test.csproj ├── WebApiClientCore.sln ├── WebApiClientCore ├── AliasAsAttribute.cs ├── ApiParameterContextExtensions.cs ├── ApiRequestContextExtensions.cs ├── ApiResponseContextExtensions.cs ├── AssemblyInternalsVisible.cs ├── AttributeCtorUsageAttribute.cs ├── Attributes │ ├── ActionAttributes │ │ ├── ApiActionAttribute.cs │ │ ├── BasicAuthAttribute.cs │ │ ├── FormDataTextAttribute.cs │ │ ├── FormFieldAttribute.cs │ │ ├── HeaderAttribute.cs │ │ ├── HttpCompletionOptionAttribute.cs │ │ ├── HttpDeleteAttribute.cs │ │ ├── HttpGetAttribute.cs │ │ ├── HttpHeadAttribute.cs │ │ ├── HttpHostAttribute.cs │ │ ├── HttpHostBaseAttribute.cs │ │ ├── HttpMethodAttribute.cs │ │ ├── HttpOptionsAttribute.cs │ │ ├── HttpPatchAttribute.cs │ │ ├── HttpPostAttribute.cs │ │ ├── HttpPutAttribute.cs │ │ └── TimeoutAttribute.cs │ ├── CacheAttributes │ │ ├── ApiCacheAttribute.cs │ │ └── CacheAttribute.cs │ ├── FilterAttributes │ │ ├── ApiFilterAttribute.cs │ │ ├── LogMessage.cs │ │ └── LoggingFilterAttribute.cs │ ├── ICharSetable.cs │ ├── ICollectionFormatable.cs │ ├── Kind.cs │ ├── ParameterAttributes │ │ ├── ApiParameterAttribute.cs │ │ ├── FormContentAttribute.cs │ │ ├── FormDataContentAttribute.cs │ │ ├── FormDataTextAttribute.cs │ │ ├── FormFieldAttribute.cs │ │ ├── HeaderAttribute.cs │ │ ├── HeadersAttribute.cs │ │ ├── HttpContentAttribute.cs │ │ ├── JsonContentAttribute.cs │ │ ├── JsonFormDataTextAttribute.cs │ │ ├── JsonFormFieldAttribute.cs │ │ ├── ParameterAttribute.cs │ │ ├── PathQueryAttribute.cs │ │ ├── RawFormContentAttribute.cs │ │ ├── RawJsonContentAttribute.cs │ │ ├── RawStringContentAttribute.cs │ │ ├── RawXmlContentAttribute.cs │ │ ├── TimeoutAttribute.cs │ │ ├── UriAttribute.cs │ │ └── XmlContentAttribute.cs │ └── ReturnAttributes │ │ ├── ApiReturnAttribute.cs │ │ ├── CustomValueReturnAttribute.cs │ │ ├── DefaultValueReturnAttribute.cs │ │ ├── JsonReturnAttribute.cs │ │ ├── NoneReturnAttribute.cs │ │ ├── RawReturnAttribute.cs │ │ ├── SpecialReturnAttribute.cs │ │ └── XmlReturnAttribute.cs ├── BuildinExtensions │ ├── CollectionExtensions.cs │ ├── EncodingExtensions.cs │ ├── HttpContentExtensions.cs │ ├── HttpRequestHeaderExtensions.cs │ ├── StringExtensions.cs │ └── TypeExtensions.cs ├── CodeAnalysis │ ├── DynamicDependencyAttribute.cs │ ├── DynamicallyAccessedMemberTypes.cs │ ├── DynamicallyAccessedMembersAttribute.cs │ ├── RequiresDynamicCodeAttribute.cs │ ├── RequiresUnreferencedCodeAttribute.cs │ └── UnconditionalSuppressMessageAttribute.cs ├── CollectionFormat.cs ├── DependencyInjection │ ├── HttpApiConfigureExtensions.cs │ ├── HttpApiExtensions.cs │ ├── HttpClientBuilderExtensions.cs │ ├── IWebApiClientBuilder.cs │ ├── NamedHttpApiExtensions.cs │ └── WebApiClientBuilderExtensions.cs ├── Disposable.cs ├── Exceptions │ ├── ApiException.cs │ ├── ApiInvalidConfigException.cs │ ├── ApiResponseStatusException.cs │ ├── ApiResultNotMatchException.cs │ ├── ApiRetryException.cs │ ├── ApiReturnNotSupportedException.cs │ ├── ApiReturnNotSupportedExteption.cs │ ├── HttpContentBufferedException.cs │ ├── IStatusCodeException.cs │ ├── ProxyTypeCreateException.cs │ ├── ProxyTypeException.cs │ └── TypeInstanceCreateException.cs ├── HttpApi.cs ├── HttpApiProxyClassAttribute.cs ├── HttpApiProxyMethodAttribute.cs ├── HttpContents │ ├── BufferContent.cs │ ├── FormContent.cs │ ├── FormDataContent.cs │ ├── FormDataFileContent.cs │ ├── FormDataTextContent.cs │ ├── ICustomHttpContentConvertable.cs │ ├── JsonContent.cs │ ├── JsonPatchContent.cs │ └── XmlContent.cs ├── HttpMessageHandlers │ ├── AuthorizationHandler.cs │ ├── CookieAuthorizationHandler.cs │ └── SetReason.cs ├── HttpPath.cs ├── HttpRequestHeader.cs ├── IChunkedable.cs ├── IHandleTask.cs ├── IHttpApi.cs ├── IRetryTask.cs ├── ITask.cs ├── Implementations │ ├── ApiRequestExecutor.cs │ ├── ApiRequestSender.cs │ ├── DataValidator.cs │ ├── DefaultApiActionDescriptor.cs │ ├── DefaultApiActionDescriptorProvider.cs │ ├── DefaultApiActionInvoker.cs │ ├── DefaultApiActionInvokerProvider.cs │ ├── DefaultApiDataTypeDescriptor.cs │ ├── DefaultApiParameterDescriptor.cs │ ├── DefaultApiReturnDescriptor.cs │ ├── DefaultDataCollection.cs │ ├── DefaultHttpApiActivator.cs │ ├── DefaultResponseCacheProvider.cs │ ├── HttpApiActivator.cs │ ├── HttpApiInterceptor.cs │ ├── HttpApiRequestMessageImpl.cs │ ├── IITaskReturnConvertable.cs │ ├── ILEmitHttpApiActivator.cs │ ├── JsonFirstApiActionDescriptor.cs │ ├── JsonFirstApiActionDescriptorProvider.cs │ ├── MultiplableComparer.cs │ ├── SourceGeneratorHttpApiActivator.cs │ ├── SourceGeneratorProxyClassFinder.cs │ ├── Tasks │ │ ├── ActionHandleTask.cs │ │ ├── ActionRetryTask.cs │ │ ├── ActionTask.cs │ │ └── TaskBase.cs │ └── TypeAttributes │ │ ├── ApiParameterTypeAttribute.cs │ │ ├── CancellationTokenTypeAttribute.cs │ │ ├── FileInfoTypeAttribute.cs │ │ └── HttpContentTypeAttribute.cs ├── Internals │ ├── HttpUtil.cs │ ├── LambdaUtil.cs │ ├── MediaTypeUtil.cs │ ├── RecyclableBufferWriter.cs │ ├── UriValue.cs │ └── ValueStringBuilder.cs ├── JsonString.cs ├── Parameters │ ├── FormDataFile.cs │ ├── JsonPatchDocument.cs │ └── JsonPatchDocument~.cs ├── Resx.Designer.cs ├── Resx.en-US.resx ├── Resx.resx ├── Serialization │ ├── JsonBufferSerializer.cs │ ├── JsonConverters │ │ ├── JsonDateTimeAttribute.cs │ │ ├── JsonLocalDateTimeConverter.cs │ │ └── JsonStringTypeConverter.cs │ ├── KeyValueSerializer.cs │ ├── Utf8JsonWriterCache.cs │ └── XmlSerializer.cs ├── Sign.snk ├── System.Net.Http │ ├── Headers │ │ └── BasicAuthenticationHeaderValue.cs │ ├── HttpContentExtensions.cs │ ├── HttpProgress.cs │ ├── HttpRequestMessageExtensions.cs │ └── HttpResponseMessageExtensions.cs ├── TaskExtenstions.cs └── WebApiClientCore.csproj ├── docs ├── guide │ ├── 1_getting-started.md │ ├── 2_attributes.md │ ├── 3_special-type.md │ ├── 4_data-validation.md │ ├── 5_advanced.md │ ├── 6_auth-token-extension.md │ ├── 7_json-net-extension.md │ ├── 8_jsonrpc-extension.md │ ├── 9_openapi-to-code.md │ └── readme.md └── old │ ├── advanced │ ├── env-with-di.md │ └── env-without-di.md │ ├── basic │ ├── attribute-scope-features.md │ ├── full-demo.md │ ├── get-head.md │ ├── parameter-attribute.md │ ├── parameter-validation.md │ ├── patch.md │ ├── post-put-delete.md │ └── request-url.md │ ├── getting-started.md │ ├── qa.md │ ├── readme.md │ └── senior │ ├── diy-attribute.md │ ├── exception-retry.md │ ├── filter.md │ └── global-filter.md ├── icon.png ├── icon.psd └── nuget.push.bat /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/.gitignore -------------------------------------------------------------------------------- /App/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "8.0.6", 7 | "commands": [ 8 | "dotnet-ef" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /App/ApiClients/UserHostedService.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 App.Clients 9 | { 10 | public class UserHostedService : BackgroundService 11 | { 12 | private readonly IServiceScopeFactory serviceScopeFactory; 13 | private readonly ILogger logger; 14 | 15 | public UserHostedService(IServiceScopeFactory serviceScopeFactory, ILogger logger) 16 | { 17 | this.serviceScopeFactory = serviceScopeFactory; 18 | this.logger = logger; 19 | } 20 | 21 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 22 | { 23 | try 24 | { 25 | using var scope = this.serviceScopeFactory.CreateScope(); 26 | var userService = scope.ServiceProvider.GetRequiredService(); 27 | await userService.RunRequestsAsync(); 28 | } 29 | catch (Exception ex) 30 | { 31 | this.logger.LogError(ex, ex.Message); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /App/App.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | enable 6 | netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 7 | false 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /App/Controllers/TokenFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace App.Controllers 7 | { 8 | public class TokenFilterAttribute : Attribute, IAsyncActionFilter 9 | { 10 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) 11 | { 12 | if (context.HttpContext.Request.Headers.ContainsKey("Authorization")) 13 | { 14 | await next(); 15 | } 16 | else 17 | { 18 | context.Result = new UnauthorizedResult(); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /App/Controllers/TokensController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using WebApiClientCore.Extensions.OAuths; 3 | using WebApiClientCore.Extensions.OAuths.TokenProviders; 4 | 5 | namespace App.Controllers 6 | { 7 | [ApiController] 8 | [Route("api/[controller]")] 9 | public class TokensController : ControllerBase 10 | { 11 | [HttpPost] 12 | public TokenResult CreateToken([FromForm] ClientCredentials credentials) 13 | { 14 | return new TokenResult 15 | { 16 | Access_token = $"Access_token_{credentials.Client_id}_{credentials.Client_secret}", 17 | Expires_in = 60 * 60, 18 | Id_token = "id", 19 | Token_type = "Bearer" 20 | }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /App/Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using App.Models; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace App.Controllers 6 | { 7 | [TokenFilter] 8 | [ApiController] 9 | [Route("api/[controller]")] 10 | public class UsersController : ControllerBase 11 | { 12 | [HttpGet("{account}")] 13 | public User Get(string account) 14 | { 15 | return new User { Account = account, Password = "password" }; 16 | } 17 | 18 | 19 | [HttpPost("body")] 20 | public User PostByBody([FromBody] User bodyUser) 21 | { 22 | return bodyUser; 23 | } 24 | 25 | [HttpPost("form")] 26 | public User PostByForm([FromForm] User formUser) 27 | { 28 | return formUser; 29 | } 30 | 31 | [HttpPost("formdata")] 32 | public User PostByFormData([FromForm] User formDataUser, IFormFile file) 33 | { 34 | return formDataUser; 35 | } 36 | 37 | [HttpDelete("{account}")] 38 | public void Delete(string account) 39 | { 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /App/Models/Gender.cs: -------------------------------------------------------------------------------- 1 | namespace App.Models 2 | { 3 | /// 4 | /// 性别 5 | /// 6 | public enum Gender 7 | { 8 | Female = 0, 9 | Male = 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /App/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Text.Json.Serialization; 4 | using WebApiClientCore.Serialization.JsonConverters; 5 | 6 | namespace App.Models 7 | { 8 | /// 9 | /// 表示用户模型 10 | /// 11 | public class User 12 | { 13 | [Required] 14 | [StringLength(10, MinimumLength = 1)] 15 | public string Account { get; set; } = string.Empty; 16 | 17 | [Required] 18 | [StringLength(10, MinimumLength = 1)] 19 | public string Password { get; set; } = string.Empty; 20 | 21 | public string? NickName { get; set; } 22 | 23 | [JsonDateTime("yyyy年MM月dd日")] 24 | public DateTime? BirthDay { get; set; } 25 | 26 | public Gender Gender { get; set; } 27 | 28 | [JsonIgnore] 29 | public string? Email { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /App/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace App 5 | { 6 | /// 7 | /// 应用程序 8 | /// 9 | public class Program 10 | { 11 | /// 12 | /// 程序入口 13 | /// 14 | /// 15 | public static void Main(string[] args) 16 | { 17 | CreateHostBuilder(args).Build().Run(); 18 | } 19 | 20 | /// 21 | /// 创建host 22 | /// 23 | /// 24 | /// 25 | public static IHostBuilder CreateHostBuilder(string[] args) 26 | { 27 | return Host 28 | .CreateDefaultBuilder(args) 29 | .ConfigureWebHostDefaults(webBuilder => 30 | { 31 | webBuilder.UseStartup(); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /App/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "App": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "http://localhost:5000" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /App/README.md: -------------------------------------------------------------------------------- 1 | ## App 2 | 这是应用例子,为了方便,服务端和客户端都在同一个程序进程内 3 | 4 | ### 服务端 5 | Controllers 为服务端,TokensController 模拟 token 发放的服务端,UsersController 模拟用户资源服务器 6 | 7 | ### 客户端 8 | * ApiClients.IUserApi为WebApiClientCore的声明式接口 9 | * ApiClients.UserService为包装的服务,注入了IUserApi接口 10 | * ApiClients.UserHostedService为后台服务,启动时获取UserService实例并运行 11 | -------------------------------------------------------------------------------- /App/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/App/Startup.cs -------------------------------------------------------------------------------- /App/TextFile.txt: -------------------------------------------------------------------------------- 1 | 这是上传的文件内容 -------------------------------------------------------------------------------- /App/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "System": "Warning", 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /AppAot/AppAot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | false 7 | enable 8 | true 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppAot/AppData.cs: -------------------------------------------------------------------------------- 1 | namespace AppAot 2 | { 3 | public class AppData 4 | { 5 | public string? WebpackCompilationHash { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /AppAot/AppHostedService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace AppAot 8 | { 9 | class AppHostedService : BackgroundService 10 | { 11 | private readonly IServiceScopeFactory serviceScopeFactory; 12 | private readonly ILogger logger; 13 | 14 | public AppHostedService( 15 | IServiceScopeFactory serviceScopeFactory, 16 | ILogger logger) 17 | { 18 | this.serviceScopeFactory = serviceScopeFactory; 19 | this.logger = logger; 20 | } 21 | 22 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 23 | { 24 | using var scope = this.serviceScopeFactory.CreateScope(); 25 | var api = scope.ServiceProvider.GetRequiredService(); 26 | var appData = await api.GetAppDataAsync(); 27 | this.logger.LogInformation($"WebpackCompilationHash: {appData.WebpackCompilationHash}"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AppAot/AppJsonSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace AppAot 4 | { 5 | [JsonSerializable(typeof(AppData[]))] 6 | partial class AppJsonSerializerContext : JsonSerializerContext 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AppAot/ICloudflareApi.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using WebApiClientCore.Attributes; 3 | 4 | namespace AppAot 5 | { 6 | [LoggingFilter] 7 | [HttpHost("https://www.cloudflare-cn.com")] 8 | public interface ICloudflareApi 9 | { 10 | [HttpGet("/page-data/app-data.json")] 11 | Task GetAppDataAsync(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AppAot/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace AppAot 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Host.CreateDefaultBuilder(args) 11 | .ConfigureServices(services => 12 | { 13 | services 14 | .AddWebApiClient() 15 | .ConfigureHttpApi(options => // json SG生成器配置 16 | { 17 | options.PrependJsonSerializerContext(AppJsonSerializerContext.Default); 18 | }); 19 | 20 | services.AddHttpApi(); 21 | services.AddHostedService(); 22 | }) 23 | .Build() 24 | .Run(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.1.5 4 | Copyright © laojiu 2017-2024 5 | IDE0290;NETSDK1138 6 | 7 | 8 | 9 | laojiu 10 | laojiu 11 | latest 12 | true 13 | true 14 | MIT 15 | https://github.com/dotnetcore/WebApiClient 16 | icon.png 17 | true 18 | 19 | 20 | 21 | True 22 | snupkg 23 | true 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | True 34 | \ 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 .NET Core Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [README](README.md) | [中文文档](README_zh.md) 2 | 3 | ## WebApiClient                  4 | A REST API library with better functionality, performance, and scalability than refit. 5 | 6 | ### Features 7 | #### Semantic Declaration 8 | Client development only requires semantic declaration of C# interfaces. 9 | 10 | #### Diverse serialization 11 | Supports json, xml, form and other custom serialization methods. 12 | 13 | #### Full trimmed and AOT 14 | Supports full trimmed and AOT publishing of .NET8. 15 | 16 | #### Aspect-Oriented Programming 17 | Supports multiple interceptors, filters, logs, retries, custom caches and other aspects. 18 | 19 | #### Code Syntax Analysis 20 | Provides syntax analysis and prompts for interface code declarations to help developers avoid using improper syntax when declaring interfaces. 21 | 22 | #### Quick access 23 | Supports OAuth2 and token management extension packages to facilitate identity authentication and authorization. 24 | 25 | #### Swagger to code 26 | Supports parsing local or remote OpenApi documents to generate WebApiClientCore interface code, which simplifies the workload of interface declaration. 27 | 28 | #### Powerful performance 29 | In [BenchmarkDotNet](WebApiClientCore.Benchmarks/results), the performance is 2.X times ahead of the similar product [refit](https://github.com/reactiveui/refit) under various requests. 30 | 31 | ### Documentation support 32 | [https://webapiclient.github.io/](https://webapiclient.github.io/en/) 33 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | [README](README.md) | [中文文档](README_zh.md) 2 | 3 | ## WebApiClient                  4 | 一个在功能、性能和可扩展性均优于 refit 的 REST API 库 5 | 6 | ### 功能特性 7 | #### 语义化声明 8 | 客户端的开发,只需语义化的声明接口。 9 | 10 | #### 多样序列化 11 | 支持json、xml、form等序列化和其它自定义序列化方式。 12 | 13 | #### 裁剪与AOT 14 | 支持.net8的代码完全裁剪和AOT发布。 15 | 16 | #### 面向切面 17 | 支持多种拦截器、过滤器、日志、重试、缓存自定义等功能。 18 | 19 | #### 语法分析 20 | 提供接口声明的语法分析与提示,帮助开发者声明接口时避免使用不当的语法。 21 | 22 | #### 快速接入 23 | 支持OAuth2与token管理扩展包,方便实现身份认证和授权。 24 | 25 | #### 自动代码 26 | 支持将本地或远程OpenApi文档解析生成WebApiClientCore接口代码的dotnet tool,简化接口声明的工作量 27 | 28 | #### 性能强劲 29 | 在[BenchmarkDotNet](WebApiClientCore.Benchmarks/results)中,各种请求下2.X倍性能领先于同类产品[refit](https://github.com/reactiveui/refit)。 30 | 31 | ### 文档支持 32 | https://webapiclient.github.io/ 33 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/ApiActionInvoker.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 表示Action执行器 5 | /// 6 | public abstract class ApiActionInvoker 7 | { 8 | /// 9 | /// 获取Action描述 10 | /// 11 | public abstract ApiActionDescriptor ActionDescriptor { get; } 12 | 13 | /// 14 | /// 执行Action 15 | /// 16 | /// 服务上下文 17 | /// action参数值 18 | /// 19 | public abstract object Invoke(HttpClientContext context, object?[] arguments); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/ApiDataTypeDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 表示返回的Task(Of T)的T类型描述 7 | /// 8 | public abstract class ApiDataTypeDescriptor 9 | { 10 | /// 11 | /// 获取类型 12 | /// 13 | public abstract Type Type { get; protected set; } 14 | 15 | /// 16 | /// 获取是否为原始类型的String 17 | /// 18 | public abstract bool IsRawString { get; protected set; } 19 | 20 | /// 21 | /// 获取是否为原始类型的Stream 22 | /// 23 | public abstract bool IsRawStream { get; protected set; } 24 | 25 | /// 26 | /// 获取是否为原始类型的 byte[] 27 | /// 28 | public abstract bool IsRawByteArray { get; protected set; } 29 | 30 | /// 31 | /// 获取是否为原始类型的HttpResponseMessage 32 | /// 33 | public abstract bool IsRawHttpResponseMessage { get; protected set; } 34 | 35 | /// 36 | /// 获取是否为原始类型中的一个 37 | /// 38 | public abstract bool IsRawType { get; protected set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/ApiParameterDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Reflection; 5 | 6 | namespace WebApiClientCore 7 | { 8 | /// 9 | /// 表示请求Api的参数描述 10 | /// 11 | public abstract class ApiParameterDescriptor 12 | { 13 | /// 14 | /// 获取参数名称 15 | /// 16 | public abstract string Name { get; protected set; } 17 | 18 | /// 19 | /// 获取关联的参数信息 20 | /// 21 | public abstract ParameterInfo Member { get; protected set; } 22 | 23 | /// 24 | /// 获取参数索引 25 | /// 26 | public abstract int Index { get; protected set; } 27 | 28 | /// 29 | /// 获取参数类型 30 | /// 31 | public abstract Type ParameterType { get; protected set; } 32 | 33 | /// 34 | /// 获取关联的参数特性 35 | /// 36 | public abstract IReadOnlyList Attributes { get; protected set; } 37 | 38 | /// 39 | /// 获取关联的ValidationAttribute特性 40 | /// 41 | public abstract IReadOnlyList ValidationAttributes { get; protected set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/ApiReturnDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WebApiClientCore 5 | { 6 | /// 7 | /// 表示请求Api的返回描述 8 | /// 9 | public abstract class ApiReturnDescriptor 10 | { 11 | /// 12 | /// 获取返回类型 13 | /// 14 | public abstract Type ReturnType { get; protected set; } 15 | 16 | /// 17 | /// 获取ITask(Of T)或Task(Of T)的T类型描述 18 | /// 19 | public abstract ApiDataTypeDescriptor DataType { get; protected set; } 20 | 21 | /// 22 | /// 获取关联的IApiReturnAttribute 23 | /// 24 | public abstract IReadOnlyList Attributes { get; protected set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IApiActionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 定义ApiAction修饰特性的行为 7 | /// 8 | public interface IApiActionAttribute : IAttributeMultiplable, IApiAttribute 9 | { 10 | /// 11 | /// 请求前 12 | /// 13 | /// 上下文 14 | /// 15 | Task OnRequestAsync(ApiRequestContext context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IApiAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// Api特性标记的接口 5 | /// 6 | public interface IApiAttribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IApiCacheAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore 5 | { 6 | /// 7 | /// 定义ApiAction缓存修饰特性的行为 8 | /// 9 | public interface IApiCacheAttribute : IApiAttribute 10 | { 11 | /// 12 | /// 获取缓存的时间戳 13 | /// 14 | TimeSpan Expiration { get; } 15 | 16 | /// 17 | /// 返回读取缓存的策略 18 | /// 19 | /// 上下文 20 | /// 21 | CachePolicy GetReadPolicy(ApiRequestContext context); 22 | 23 | /// 24 | /// 返回写入缓存的策略 25 | /// 26 | /// 27 | /// 28 | CachePolicy GetWritePolicy(ApiRequestContext context); 29 | 30 | /// 31 | /// 获取缓存提供者 32 | /// 33 | /// 34 | /// 35 | IResponseCacheProvider? GetCacheProvider(ApiRequestContext context); 36 | 37 | /// 38 | /// 返回请求对应的缓存的键 39 | /// 该键用于读取或写入缓存到缓存提供者 40 | /// 41 | /// 上下文 42 | /// 43 | Task GetCacheKeyAsync(ApiRequestContext context); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IApiFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 定义Api过滤器修饰特性的行为 5 | /// 6 | public interface IApiFilterAttribute : IApiFilter, IAttributeMultiplable, IAttributeEnable, IApiAttribute 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IApiParameterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 定义Api参数修饰特性的行为 7 | /// 8 | public interface IApiParameterAttribute : IApiAttribute 9 | { 10 | /// 11 | /// 请求前 12 | /// 13 | /// 上下文 14 | /// 15 | Task OnRequestAsync(ApiParameterContext context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IApiReturnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore 5 | { 6 | /// 7 | /// 定义回复内容处理特性的行为 8 | /// 9 | public interface IApiReturnAttribute : IAttributeMultiplable, IAttributeEnable, IApiAttribute 10 | { 11 | /// 12 | /// 获取期望的媒体类型 13 | /// 14 | MediaTypeWithQualityHeaderValue? AcceptContentType { get; } 15 | 16 | /// 17 | /// 请求前 18 | /// 19 | /// 上下文 20 | /// 21 | Task OnRequestAsync(ApiRequestContext context); 22 | 23 | /// 24 | /// 响应后 25 | /// 26 | /// 上下文 27 | /// 28 | Task OnResponseAsync(ApiResponseContext context); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IAttributeEnable.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 定义特性是否启用的接口 5 | /// 6 | public interface IAttributeEnable 7 | { 8 | /// 9 | /// 获取特性是否启用 10 | /// 11 | bool Enable { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Attributes/IAttributeMultiplable.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 定义特性是否允许在接口与方法上重复声明 5 | /// 如果不允许则优先选取方法上的特性 6 | /// 7 | public interface IAttributeMultiplable 8 | { 9 | /// 10 | /// 获取顺序排序的索引 11 | /// 12 | int OrderIndex { get; } 13 | 14 | /// 15 | /// 获取本类型是否允许在接口与方法上重复 16 | /// 17 | bool AllowMultiple { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/CachePolicy.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 缓存策略 5 | /// 6 | public enum CachePolicy : byte 7 | { 8 | /// 9 | /// 尝试使用缓存 10 | /// 11 | Include, 12 | 13 | /// 14 | /// 忽略并跳过缓存 15 | /// 16 | Ignore 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/CodeAnalysis/DynamicDependencyAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// 表示动态依赖属性 6 | /// 7 | [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] 8 | sealed class DynamicDependencyAttribute : Attribute 9 | { 10 | /// 11 | /// 获取或设置动态访问的成员类型 12 | /// 13 | public DynamicallyAccessedMemberTypes MemberTypes { get; } 14 | 15 | /// 16 | /// 获取或设置依赖的类型 17 | /// 18 | public Type? Type { get; } 19 | 20 | /// 21 | /// 初始化 类的新实例 22 | /// 23 | /// 动态访问的成员类型 24 | /// 依赖的类型 25 | public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) 26 | { 27 | this.MemberTypes = memberTypes; 28 | this.Type = type; 29 | } 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// Specifies that the members accessed dynamically at runtime are considered used. 6 | /// 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, Inherited = false)] 8 | sealed class DynamicallyAccessedMembersAttribute : Attribute 9 | { 10 | /// 11 | /// Gets the types of dynamically accessed members. 12 | /// 13 | public DynamicallyAccessedMemberTypes MemberTypes { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with the specified member types. 17 | /// 18 | /// The types of dynamically accessed members. 19 | public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) 20 | { 21 | this.MemberTypes = memberTypes; 22 | } 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/CodeAnalysis/RequiresDynamicCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 || NET5_0 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// Specifies that the attributed class, constructor, or method requires dynamic code. 6 | /// 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] 8 | sealed class RequiresDynamicCodeAttribute : Attribute 9 | { 10 | /// 11 | /// Gets the message associated with the requirement for dynamic code. 12 | /// 13 | public string Message { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with the specified message. 17 | /// 18 | /// The message associated with the requirement for dynamic code. 19 | public RequiresDynamicCodeAttribute(string message) 20 | { 21 | this.Message = message; 22 | } 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/CodeAnalysis/RequiresUnreferencedCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] 5 | sealed class RequiresUnreferencedCodeAttribute : Attribute 6 | { 7 | /// 8 | /// 获取或设置对于未引用代码的要求的消息。 9 | /// 10 | public string Message { get; } 11 | 12 | /// 13 | /// 初始化 类的新实例。 14 | /// 15 | /// 对于未引用代码的要求的消息。 16 | public RequiresUnreferencedCodeAttribute(string message) 17 | { 18 | this.Message = message; 19 | } 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Contexts/HttpContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading; 4 | 5 | namespace WebApiClientCore 6 | { 7 | /// 8 | /// 表示 http 上下文 9 | /// 10 | public class HttpContext : HttpClientContext 11 | { 12 | /// 13 | /// 获取请求消息 14 | /// 15 | public HttpApiRequestMessage RequestMessage { get; } 16 | 17 | /// 18 | /// 获取或设置响应消息 19 | /// 20 | public HttpResponseMessage? ResponseMessage { get; set; } 21 | 22 | /// 23 | /// 获取请求取消令牌集合 24 | /// 25 | public IList CancellationTokens { get; } 26 | 27 | /// 28 | /// 获取或设置指示请求完成选项 29 | /// 30 | public HttpCompletionOption? CompletionOption { get; set; } 31 | 32 | /// 33 | /// http上下文 34 | /// 35 | /// 服务上下文 36 | /// 请求消息 37 | public HttpContext(HttpClientContext context, HttpApiRequestMessage requestMessage) 38 | : base(context.HttpClient, context.ServiceProvider, context.HttpApiOptions, context.OptionsName) 39 | { 40 | this.RequestMessage = requestMessage; 41 | this.CancellationTokens = new List(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IApiActionDescriptorProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | 5 | namespace WebApiClientCore 6 | { 7 | /// 8 | /// 定义ApiActionDescriptor提供者的接口 9 | /// 10 | public interface IApiActionDescriptorProvider 11 | { 12 | /// 13 | /// 创建Action描述 14 | /// 15 | /// 接口的方法 16 | /// 接口类型 17 | ApiActionDescriptor CreateActionDescriptor( 18 | MethodInfo method, 19 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IApiActionInvokerProvider.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 定义ApiActionInvoker提供者的接口 5 | /// 6 | public interface IApiActionInvokerProvider 7 | { 8 | /// 9 | /// 创建Action执行器 10 | /// 11 | /// Action描述 12 | /// 13 | ApiActionInvoker CreateActionInvoker(ApiActionDescriptor actionDescriptor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IApiFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 定义Api过滤器的行为 7 | /// 8 | public interface IApiFilter 9 | { 10 | /// 11 | /// 请求前 12 | /// 13 | /// 上下文 14 | /// 15 | Task OnRequestAsync(ApiRequestContext context); 16 | 17 | /// 18 | /// 响应后 19 | /// 20 | /// 上下文 21 | /// 22 | Task OnResponseAsync(ApiResponseContext context); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IApiParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 定义自身可以做为参数并进行相应处理的对象的行为 7 | /// 此对象作为参数时,不需要特性修饰 8 | /// 9 | public interface IApiParameter 10 | { 11 | /// 12 | /// http请求之前 13 | /// 14 | /// 上下文 15 | /// 16 | Task OnRequestAsync(ApiParameterContext context); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IHttpApiActivator.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 定义THttpApi的实例创建器的接口 7 | /// 8 | /// 9 | public interface IHttpApiActivator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] THttpApi> 10 | { 11 | /// 12 | /// 创建THttpApi的代理实例 13 | /// 14 | /// 接口拦截器 15 | /// 16 | THttpApi CreateInstance(IHttpApiInterceptor apiInterceptor); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IHttpApiInterceptor.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 接口方法的拦截器 5 | /// 6 | public interface IHttpApiInterceptor 7 | { 8 | /// 9 | /// 拦截方法的调用 10 | /// 11 | /// action执行器 12 | /// action的参数集合 13 | /// 14 | object Intercept(ApiActionInvoker actionInvoker, object?[] arguments); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/IResponseCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore 5 | { 6 | /// 7 | /// 定义Api响应结果缓存提供者的接口 8 | /// 9 | public interface IResponseCacheProvider 10 | { 11 | /// 12 | /// 获取提供者的友好名称 13 | /// 14 | string Name { get; } 15 | 16 | /// 17 | /// 从缓存中获取响应实体 18 | /// 19 | /// 键 20 | /// 21 | Task GetAsync(string key); 22 | 23 | /// 24 | /// 设置响应实体到缓存 25 | /// 26 | /// 键 27 | /// 缓存实体 28 | /// 有效时间 29 | /// 30 | Task SetAsync(string key, ResponseCacheEntry entry, TimeSpan expiration); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/KeyValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace WebApiClientCore 6 | { 7 | /// 8 | /// 表示键值对 9 | /// 10 | [DebuggerDisplay("{Key}={Value}")] 11 | public readonly struct KeyValue 12 | { 13 | /// 14 | /// 获取键 15 | /// 16 | public string Key { get; } 17 | 18 | /// 19 | /// 获取值 20 | /// 21 | public string? Value { get; } 22 | 23 | /// 24 | /// 键值对 25 | /// 26 | /// 键 27 | /// 值 28 | /// 29 | public KeyValue(string key, string? value) 30 | { 31 | if (string.IsNullOrEmpty(key)) 32 | { 33 | throw new ArgumentNullException(nameof(key)); 34 | } 35 | this.Key = key; 36 | this.Value = value; 37 | } 38 | 39 | /// 40 | /// 从 KeyValuePair 显式转换 41 | /// 42 | /// 43 | public static explicit operator KeyValue(KeyValuePair keyValue) 44 | { 45 | return new KeyValue(keyValue.Key, keyValue.Value); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/ResponseCacheResult.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 表示响应缓存结果 7 | /// 8 | [DebuggerDisplay("HasValue = {HasValue}")] 9 | public readonly struct ResponseCacheResult 10 | { 11 | /// 12 | /// 表示无Value的缓存结果 13 | /// 14 | public static readonly ResponseCacheResult NoValue = new(null, false); 15 | 16 | /// 17 | /// 获取缓存的值 18 | /// 19 | public ResponseCacheEntry? Value { get; } 20 | 21 | /// 22 | /// 获取是否有缓存的值 23 | /// 24 | public bool HasValue { get; } 25 | 26 | /// 27 | /// 响应缓存结果 28 | /// 29 | /// 缓存的值 30 | /// 是否有缓存的值 31 | public ResponseCacheResult(ResponseCacheEntry? value, bool hasValue) 32 | { 33 | this.Value = value; 34 | this.HasValue = hasValue; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/ResultStatus.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 表示结果状态 5 | /// 6 | public enum ResultStatus : byte 7 | { 8 | /// 9 | /// 无状态 10 | /// 11 | None, 12 | 13 | /// 14 | /// 有结果 15 | /// 16 | HasResult, 17 | 18 | /// 19 | /// 有异常 20 | /// 21 | HasException, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Serialization/JsonConverters/JsonCompatibleConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace WebApiClientCore.Serialization.JsonConverters 5 | { 6 | /// 7 | /// 提供一些类型的兼容性的json转换器 8 | /// 9 | public static class JsonCompatibleConverter 10 | { 11 | private static JsonStringEnumConverter? stringEnumConverter; 12 | 13 | /// 14 | /// 获取Enum类型反序列化兼容的转换器 15 | /// 16 | public static JsonConverter EnumReader 17 | { 18 | [RequiresDynamicCode("JsonStringEnumConverter需要动态代码")] 19 | get 20 | { 21 | stringEnumConverter ??= new JsonStringEnumConverter(); 22 | return stringEnumConverter; 23 | } 24 | } 25 | 26 | /// 27 | /// 获取DateTime类型反序列化兼容的转换器 28 | /// 29 | public static JsonConverter DateTimeReader { get; } = new JsonDateTimeConverter("O"); 30 | } 31 | } -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Serialization/KeyNamingOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Serialization 4 | { 5 | /// 6 | /// 表示键命名选项 7 | /// 8 | public class KeyNamingOptions 9 | { 10 | /// 11 | /// 获取或设置键名的分隔符 12 | /// 默认为. 13 | /// 14 | public string KeyDelimiter { get; set; } = "."; 15 | 16 | /// 17 | /// 获取或设置数组索引格式化 18 | /// 默认为[i] 19 | /// 20 | public Func KeyArrayIndex { get; set; } = i => $"[{i}]"; 21 | 22 | /// 23 | /// 获取或设置键名风格 24 | /// 25 | public KeyNamingStyle KeyNamingStyle { get; set; } = KeyNamingStyle.ShortName; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Serialization/KeyNamingStyle.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Serialization 2 | { 3 | /// 4 | /// 键名风格 5 | /// 6 | public enum KeyNamingStyle : byte 7 | { 8 | /// 9 | /// 只包含最后一级属性名的短键名风格 10 | /// 11 | ShortName, 12 | 13 | /// 14 | /// 包含多级属性名的完整键名风格 15 | /// 16 | FullName, 17 | 18 | /// 19 | /// 同时包含根与多级属性名的完整键名风格 20 | /// 21 | FullNameWithRoot 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Abstractions/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Abstractions/WebApiClientCore.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | netstandard2.1;net5.0;net8.0 6 | true 7 | 8 | WebApiClientCore 9 | WebApiClientCore.Abstractions 10 | True 11 | 12 | WebApiClientCore的接口与抽象类型 13 | WebApiClientCore的接口与抽象类型 14 | 15 | true 16 | Sign.snk 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /WebApiClientCore.Analyzers/HttpApiDiagnosticProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Collections.Generic; 3 | 4 | namespace WebApiClientCore.Analyzers 5 | { 6 | /// 7 | /// 表示HttpApi诊断器提供者抽象类 8 | /// 9 | abstract class HttpApiDiagnosticProvider 10 | { 11 | /// 12 | /// 获取上下文 13 | /// 14 | protected HttpApiContext Context { get; } 15 | 16 | /// 17 | /// 获取诊断描述 18 | /// 19 | public abstract DiagnosticDescriptor Descriptor { get; } 20 | 21 | /// 22 | /// HttpApi诊断器 23 | /// 24 | /// 上下文 25 | public HttpApiDiagnosticProvider(HttpApiContext context) 26 | { 27 | this.Context = context; 28 | } 29 | 30 | /// 31 | /// 创建诊断结果 32 | /// 33 | /// 34 | /// 35 | /// 36 | protected Diagnostic CreateDiagnostic(Location location, params object[] messageArgs) 37 | { 38 | return Diagnostic.Create(this.Descriptor, location, messageArgs); 39 | } 40 | 41 | /// 42 | /// 创建所有的诊断结果 43 | /// 44 | /// 45 | public abstract IEnumerable CreateDiagnostics(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /WebApiClientCore.Analyzers/Providers/GenericInterfaceDiagnosticProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Collections.Generic; 3 | 4 | namespace WebApiClientCore.Analyzers.Providers 5 | { 6 | /// 7 | /// 表示泛型接口诊断器 8 | /// 9 | sealed class GenericInterfaceDiagnosticProvider : HttpApiDiagnosticProvider 10 | { 11 | /// 12 | /// 获取诊断描述 13 | /// 14 | public override DiagnosticDescriptor Descriptor => Descriptors.GenericInterfaceDescriptor; 15 | 16 | /// 17 | /// 泛型接口诊断器 18 | /// 19 | /// 上下文 20 | public GenericInterfaceDiagnosticProvider(HttpApiContext context) 21 | : base(context) 22 | { 23 | } 24 | 25 | /// 26 | /// 返回所有的报告诊断 27 | /// 28 | /// 29 | public override IEnumerable CreateDiagnostics() 30 | { 31 | if (this.Context.Interface.IsGenericType) 32 | { 33 | var location = Context.Syntax.Identifier.GetLocation(); 34 | yield return this.CreateDiagnostic(location); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WebApiClientCore.Analyzers/Providers/ModifierDiagnosticProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace WebApiClientCore.Analyzers.Providers 6 | { 7 | /// 8 | /// public修饰符号诊断 9 | /// 10 | sealed class ModifierDiagnosticProvider : HttpApiDiagnosticProvider 11 | { 12 | public override DiagnosticDescriptor Descriptor => Descriptors.ModifierDescriptor; 13 | 14 | /// 15 | /// public修饰符号诊断 16 | /// 17 | /// 18 | public ModifierDiagnosticProvider(HttpApiContext context) 19 | : base(context) 20 | { 21 | } 22 | 23 | public override IEnumerable CreateDiagnostics() 24 | { 25 | var syntax = this.Context.Syntax; 26 | var isVisible = syntax.Modifiers.Any(item => "public".Equals(item.ValueText)); 27 | if (isVisible == false) 28 | { 29 | var location = syntax.Identifier.GetLocation(); 30 | yield return this.CreateDiagnostic(location); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WebApiClientCore.Analyzers/README.md: -------------------------------------------------------------------------------- 1 | ## WebApiClientCore.Analyzers                   2 | WebApiClientCore声明接口的语法分析,编译前就分析接口是否符合要求,需要接口现实IHttpApi接口才触发分析 3 | 4 | ### 使用方式 5 | ``` 6 | /// 7 | /// 你的接口,记得要实现IHttpApi 8 | /// 9 | public interface IYourApi : IHttpApi 10 | { 11 | ... 12 | } 13 | ``` -------------------------------------------------------------------------------- /WebApiClientCore.Analyzers/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Analyzers/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Analyzers/WebApiClientCore.Analyzers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | enable 5 | netstandard2.0 6 | True 7 | false 8 | false 9 | true 10 | Sign.snk 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | True 21 | True 22 | Resx.resx 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ResXFileCodeGenerator 32 | Resx.Designer.cs 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using WebApiClientCore.Benchmarks.Requests; 3 | 4 | namespace WebApiClientCore.Benchmarks 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | BenchmarkRunner.Run(); 11 | BenchmarkRunner.Run(); 12 | BenchmarkRunner.Run(); 13 | BenchmarkRunner.Run(); 14 | BenchmarkRunner.Run(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/README.md: -------------------------------------------------------------------------------- 1 | ## WebApiClientCore.Benchmarks  2 | 3 | See benchmark [results](results). -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/HttpGetBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Benchmarks.Requests 6 | { 7 | public class HttpGetBenchmark : Benchmark 8 | { 9 | [Benchmark(Baseline = true)] 10 | public async Task WebApiClientCore_GetAsync() 11 | { 12 | using var scope = this.ServiceProvider.CreateScope(); 13 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 14 | await benchmarkApi.GetAsync(id: "id001"); 15 | } 16 | 17 | [Benchmark] 18 | public async Task Refit_GetAsync() 19 | { 20 | using var scope = this.ServiceProvider.CreateScope(); 21 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 22 | await benchmarkApi.GetAsync(id: "id001"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/HttpGetJsonBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Benchmarks.Requests 6 | { 7 | public class HttpGetJsonBenchmark : Benchmark 8 | { 9 | [Benchmark(Baseline = true)] 10 | public async Task WebApiClientCore_GetJsonAsync() 11 | { 12 | using var scope = this.ServiceProvider.CreateScope(); 13 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 14 | return await benchmarkApi.GetJsonAsync(id: "id001"); 15 | } 16 | 17 | [Benchmark] 18 | public async Task Refit_GetJsonAsync() 19 | { 20 | using var scope = this.ServiceProvider.CreateScope(); 21 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 22 | return await benchmarkApi.GetJsonAsync(id: "id001"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/HttpPostJsonBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Benchmarks.Requests 6 | { 7 | public class HttpPostJsonBenchmark : Benchmark 8 | { 9 | [Benchmark(Baseline = true)] 10 | public async Task WebApiClientCore_PostJsonAsync() 11 | { 12 | using var scope = this.ServiceProvider.CreateScope(); 13 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 14 | return await benchmarkApi.PostJsonAsync(User.Instance); 15 | } 16 | 17 | 18 | [Benchmark] 19 | public async Task Refit_PostJsonAsync() 20 | { 21 | using var scope = this.ServiceProvider.CreateScope(); 22 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 23 | return await benchmarkApi.PostJsonAsync(User.Instance); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/HttpPostXmlBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Benchmarks.Requests 6 | { 7 | public class HttpPostXmlBenchmark : Benchmark 8 | { 9 | [Benchmark(Baseline = true)] 10 | public async Task WebApiClientCore_PostXmlAsync() 11 | { 12 | using var scope = this.ServiceProvider.CreateScope(); 13 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 14 | return await benchmarkApi.PostXmlAsync(User.Instance); 15 | } 16 | 17 | [Benchmark] 18 | public async Task Refit_PostXmlAsync() 19 | { 20 | using var scope = this.ServiceProvider.CreateScope(); 21 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 22 | return await benchmarkApi.PostXmlAsync(User.Instance); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/HttpPutFormBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Benchmarks.Requests 6 | { 7 | public class HttpPutFormBenchmark : Benchmark 8 | { 9 | [Benchmark(Baseline = true)] 10 | public async Task WebApiClientCore_PutFormAsync() 11 | { 12 | using var scope = this.ServiceProvider.CreateScope(); 13 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 14 | return await benchmarkApi.PutFormAsync(id: "id001", User.Instance); 15 | } 16 | 17 | [Benchmark] 18 | public async Task Refit_PutFormAsync() 19 | { 20 | using var scope = this.ServiceProvider.CreateScope(); 21 | var benchmarkApi = scope.ServiceProvider.GetRequiredService(); 22 | return await benchmarkApi.PutFormAsync(id: "id001", User.Instance); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/IRefitJsonApi.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Benchmarks.Requests 5 | { 6 | public interface IRefitJsonApi 7 | { 8 | [Get("/benchmarks/{id}")] 9 | Task GetAsync(string id); 10 | 11 | [Get("/benchmarks/{id}")] 12 | Task GetJsonAsync(string id); 13 | 14 | [Post("/benchmarks")] 15 | Task PostJsonAsync([Body(BodySerializationMethod.Serialized)] User model); 16 | 17 | [Put("/benchmarks/{id}")] 18 | Task PutFormAsync(string id, [Body(BodySerializationMethod.UrlEncoded)] User model); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/IRefitXmlApi.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Benchmarks.Requests 5 | { 6 | public interface IRefitXmlApi 7 | { 8 | [Post("/benchmarks")] 9 | Task PostXmlAsync([Body(BodySerializationMethod.Serialized)] User model); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/IWebApiClientCoreJsonApi.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using WebApiClientCore.Attributes; 3 | 4 | namespace WebApiClientCore.Benchmarks.Requests 5 | { 6 | public interface IWebApiClientCoreJsonApi 7 | { 8 | [HttpGet("/benchmarks/{id}")] 9 | Task GetAsync(string id); 10 | 11 | [HttpGet("/benchmarks/{id}")] 12 | Task GetJsonAsync(string id); 13 | 14 | [HttpPost("/benchmarks")] 15 | Task PostJsonAsync([JsonContent] User model); 16 | 17 | [HttpPut("/benchmarks/{id}")] 18 | Task PutFormAsync(string id, [FormContent] User model); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Requests/IWebApiClientCoreXmlApi.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using WebApiClientCore.Attributes; 3 | 4 | namespace WebApiClientCore.Benchmarks.Requests 5 | { 6 | public interface IWebApiClientCoreXmlApi 7 | { 8 | [HttpPost("/benchmarks")] 9 | Task PostXmlAsync([XmlContent] User model); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Benchmarks/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/User.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | using WebApiClientCore.Serialization; 5 | 6 | namespace WebApiClientCore.Benchmarks 7 | { 8 | public class User 9 | { 10 | [JsonPropertyName("id")] 11 | public int Id { get; set; } 12 | 13 | [JsonPropertyName("name")] 14 | public string Name { get; set; } 15 | 16 | [JsonPropertyName("bio")] 17 | public string Bio { get; set; } 18 | 19 | [JsonPropertyName("followers")] 20 | public int Followers { get; set; } 21 | 22 | [JsonPropertyName("following")] 23 | public int Following { get; set; } 24 | 25 | [JsonPropertyName("url")] 26 | public string Url { get; set; } 27 | 28 | 29 | public static User Instance { get; } 30 | public static byte[] Utf8Json { get; } 31 | public static string XmlString { get; set; } 32 | 33 | static User() 34 | { 35 | Utf8Json = File.ReadAllBytes("user.json"); 36 | Instance = JsonSerializer.Deserialize(Utf8Json); 37 | XmlString = XmlSerializer.Serialize(Instance, null); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/WebApiClientCore.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | Exe 6 | true 7 | true 8 | Sign.snk 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/results/WebApiClientCore.Benchmarks.Requests.HttpGetBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | BenchmarkDotNet v0.13.12, CentOS Linux 7 (Core) 4 | Intel Xeon CPU E5-2650 v2 2.60GHz, 2 CPU, 32 logical and 16 physical cores 5 | [Host] : .NET 8.0.4, X64 NativeAOT AVX 6 | 7 | Job=InProcess Toolchain=InProcessEmitToolchain 8 | 9 | ``` 10 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | 11 | |-------------------------- |----------:|----------:|----------:|------:|--------:|-------:|----------:|------------:| 12 | | WebApiClientCore_GetAsync | 5.558 μs | 0.1094 μs | 0.1384 μs | 1.00 | 0.00 | 0.3357 | 3.45 KB | 1.00 | 13 | | Refit_GetAsync | 14.494 μs | 0.2764 μs | 0.3394 μs | 2.61 | 0.10 | 0.4883 | 5.18 KB | 1.50 | 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/results/WebApiClientCore.Benchmarks.Requests.HttpGetJsonBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | BenchmarkDotNet v0.13.12, CentOS Linux 7 (Core) 4 | Intel Xeon CPU E5-2650 v2 2.60GHz, 2 CPU, 32 logical and 16 physical cores 5 | [Host] : .NET 8.0.4, X64 NativeAOT AVX 6 | 7 | Job=InProcess Toolchain=InProcessEmitToolchain 8 | 9 | ``` 10 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | 11 | |------------------------------ |---------:|---------:|---------:|------:|--------:|-------:|----------:|------------:| 12 | | WebApiClientCore_GetJsonAsync | 11.31 μs | 0.225 μs | 0.322 μs | 1.00 | 0.00 | 0.4120 | 4.3 KB | 1.00 | 13 | | Refit_GetJsonAsync | 29.03 μs | 0.575 μs | 0.788 μs | 2.57 | 0.09 | 0.5493 | 5.67 KB | 1.32 | 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/results/WebApiClientCore.Benchmarks.Requests.HttpPostJsonBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | BenchmarkDotNet v0.13.12, CentOS Linux 7 (Core) 4 | Intel Xeon CPU E5-2650 v2 2.60GHz, 2 CPU, 32 logical and 16 physical cores 5 | [Host] : .NET 8.0.4, X64 NativeAOT AVX 6 | 7 | Job=InProcess Toolchain=InProcessEmitToolchain 8 | 9 | ``` 10 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | 11 | |------------------------------- |---------:|---------:|---------:|------:|--------:|-------:|----------:|------------:| 12 | | WebApiClientCore_PostJsonAsync | 11.26 μs | 0.221 μs | 0.331 μs | 1.00 | 0.00 | 0.4120 | 4.23 KB | 1.00 | 13 | | Refit_PostJsonAsync | 26.16 μs | 0.510 μs | 0.663 μs | 2.32 | 0.08 | 0.5798 | 6.08 KB | 1.44 | 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/results/WebApiClientCore.Benchmarks.Requests.HttpPostXmlBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | BenchmarkDotNet v0.13.12, CentOS Linux 7 (Core) 4 | Intel Xeon CPU E5-2650 v2 2.60GHz, 2 CPU, 32 logical and 16 physical cores 5 | [Host] : .NET 8.0.4, X64 NativeAOT AVX 6 | 7 | Job=InProcess Toolchain=InProcessEmitToolchain 8 | 9 | ``` 10 | | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | 11 | |------------------------------ |---------:|---------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:| 12 | | WebApiClientCore_PostXmlAsync | 47.97 μs | 0.943 μs | 2.009 μs | 47.11 μs | 1.00 | 0.00 | 3.4180 | 0.1221 | 35.48 KB | 1.00 | 13 | | Refit_PostXmlAsync | 57.06 μs | 0.948 μs | 0.740 μs | 56.87 μs | 1.21 | 0.02 | 14.0381 | 2.3193 | 144.38 KB | 4.07 | 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/results/WebApiClientCore.Benchmarks.Requests.HttpPutFormBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | BenchmarkDotNet v0.13.12, CentOS Linux 7 (Core) 4 | Intel Xeon CPU E5-2650 v2 2.60GHz, 2 CPU, 32 logical and 16 physical cores 5 | [Host] : .NET 8.0.4, X64 NativeAOT AVX 6 | 7 | Job=InProcess Toolchain=InProcessEmitToolchain 8 | 9 | ``` 10 | | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | 11 | |------------------------------ |---------:|---------:|---------:|---------:|------:|--------:|-------:|----------:|------------:| 12 | | WebApiClientCore_PutFormAsync | 19.94 μs | 0.394 μs | 0.679 μs | 19.62 μs | 1.00 | 0.00 | 0.5493 | 5.7 KB | 1.00 | 13 | | Refit_PutFormAsync | 79.90 μs | 1.551 μs | 2.321 μs | 78.62 μs | 3.98 | 0.17 | 1.0986 | 11.57 KB | 2.03 | 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Benchmarks/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 253, 3 | "name": "Namee3a23814-bfe9-4d4b-96db-8fc95d209ea8", 4 | "bio": "Biof413d158-7ca7-4b1b-9073-565b3621bb83", 5 | "followers": 154, 6 | "following": 136, 7 | "url": "Url70f46596-f86f-4e82-900d-0f07d7dc468c" 8 | } -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/Attributes/JsonRpcParamAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using WebApiClientCore.Exceptions; 3 | using WebApiClientCore.Extensions.JsonRpc; 4 | 5 | namespace WebApiClientCore.Attributes 6 | { 7 | /// 8 | /// 表示Rpc请求的一个参数 9 | /// 10 | public class JsonRpcParamAttribute : ApiParameterAttribute 11 | { 12 | /// 13 | /// 请求前 14 | /// 15 | /// 16 | /// 17 | public override Task OnRequestAsync(ApiParameterContext context) 18 | { 19 | var parameters = context.Properties.Get(typeof(JsonRpcParameters)); 20 | if (parameters == null) 21 | { 22 | throw new ApiInvalidConfigException($"请为接口方法{context.ActionDescriptor.Name}修饰{nameof(JsonRpcMethodAttribute)}"); 23 | } 24 | 25 | parameters.Add(context); 26 | return Task.CompletedTask; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/Attributes/JsonRpcParamsStyle.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Attributes 2 | { 3 | /// 4 | /// 表示JsonRpc的参数风格 5 | /// 6 | public enum JsonRpcParamsStyle 7 | { 8 | /// 9 | /// 数组形式 10 | /// 11 | Array, 12 | 13 | /// 14 | /// 对象形式 15 | /// 16 | Object 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/Internals/JsonRpcContent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using WebApiClientCore.HttpContents; 3 | using WebApiClientCore.Serialization; 4 | 5 | namespace WebApiClientCore.Extensions.JsonRpc 6 | { 7 | /// 8 | /// 表示JsonRpc请求内容 9 | /// 10 | sealed class JsonRpcContent : BufferContent 11 | { 12 | /// 13 | /// 获取对应的ContentType 14 | /// 15 | public static string MediaType => "application/json-rpc"; 16 | 17 | /// 18 | /// uft8 的 json 内容 19 | /// 20 | /// 21 | /// 对象值 22 | /// json序列化选项 23 | public JsonRpcContent(object? value, JsonSerializerOptions? jsonSerializerOptions, string? mediaType) 24 | : base(mediaType ?? MediaType) 25 | { 26 | JsonBufferSerializer.Serialize(this, value, jsonSerializerOptions); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/Internals/JsonRpcParameters.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using WebApiClientCore.Attributes; 4 | 5 | namespace WebApiClientCore.Extensions.JsonRpc 6 | { 7 | /// 8 | /// 表示JsonRpc参数 9 | /// 10 | sealed class JsonRpcParameters : List 11 | { 12 | /// 13 | /// 转换为 jsonRpc 请求参数 14 | /// 15 | /// 16 | /// 17 | public object ToJsonRpcParams(JsonRpcParamsStyle paramsStyle) 18 | { 19 | if (paramsStyle == JsonRpcParamsStyle.Array) 20 | { 21 | return this.Select(item => item.ParameterValue).ToArray(); 22 | } 23 | else 24 | { 25 | return this.ToDictionary(item => item.ParameterName, item => item.ParameterValue); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/Internals/JsonRpcRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | using System.Threading; 4 | 5 | namespace WebApiClientCore.Extensions.JsonRpc 6 | { 7 | /// 8 | /// 表示JsonRpc的请求实体 9 | /// 10 | sealed class JsonRpcRequest 11 | { 12 | /// 13 | /// id值 14 | /// 15 | private static int @id = 0; 16 | 17 | /// 18 | /// json rpc 19 | /// 2.0 20 | /// 21 | [JsonPropertyName("jsonrpc")] 22 | public string JsonRpc { get; set; } = "2.0"; 23 | 24 | /// 25 | /// 请求的方法名 26 | /// 27 | [JsonPropertyName("method")] 28 | public string Method { get; set; } = string.Empty; 29 | 30 | /// 31 | /// 请求的参数数组 32 | /// 33 | [JsonPropertyName("params")] 34 | public object Params { get; set; } = Array.Empty(); 35 | 36 | /// 37 | /// 请求的id 38 | /// 39 | [JsonPropertyName("id")] 40 | public int Id { get; set; } = Interlocked.Increment(ref id); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/JsonRpcError.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace WebApiClientCore.Extensions.JsonRpc 4 | { 5 | /// 6 | /// 表示JsonRpc的错误内容 7 | /// 8 | public class JsonRpcError 9 | { 10 | /// 11 | /// 错误码 12 | /// 13 | [JsonPropertyName("code")] 14 | public int Code { get; set; } 15 | 16 | /// 17 | /// 提示消息 18 | /// 19 | [JsonPropertyName("message")] 20 | public string? Message { get; set; } 21 | 22 | /// 23 | /// 错误内容 24 | /// 25 | [JsonPropertyName("data")] 26 | public object? Data { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/JsonRpcResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace WebApiClientCore.Extensions.JsonRpc 4 | { 5 | /// 6 | /// 表示JsonRpc的请求返回结果 7 | /// 8 | /// 9 | public class JsonRpcResult 10 | { 11 | /// 12 | /// 请求id 13 | /// 14 | [JsonPropertyName("id")] 15 | public int? Id { get; set; } 16 | 17 | /// 18 | /// json rpc版本号 19 | /// 20 | [JsonPropertyName("jsonrpc")] 21 | public string JsonRpc { get; set; } = string.Empty; 22 | 23 | /// 24 | /// 结果值 25 | /// 26 | #nullable disable 27 | [JsonPropertyName("result")] 28 | public TResult Result { get; set; } 29 | #nullable enable 30 | 31 | /// 32 | /// 错误内容 33 | /// 34 | [JsonPropertyName("error")] 35 | public JsonRpcError? Error { get; set; } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/README.md: -------------------------------------------------------------------------------- 1 | ## WebApiClientCore.Extensions.JsonRpc                2 | WebApiClientCore的JsonRpc调用扩展 3 | 4 | ### 使用方式 5 | 使用[JsonRpcMethod]修饰Rpc方法,使用[JsonRpcParam]修饰Rpc参数 6 | 7 | ```c# 8 | [HttpHost("http://localhost:5000/jsonrpc")] 9 | public interface IUserApi 10 | { 11 | [JsonRpcMethod("add")] 12 | ITask> AddAsync([JsonRpcParam] string name, [JsonRpcParam] int age, CancellationToken token = default); 13 | } 14 | ``` 15 | 16 | ``` 17 | POST /jsonrpc HTTP/1.1 18 | Host: localhost:5000 19 | User-Agent: WebApiClientCore/1.0.6.0 20 | Accept: application/json; q=0.01, application/xml; q=0.01 21 | Content-Type: application/json-rpc 22 | 23 | {"jsonrpc":"2.0","method":"add","params":["laojiu",18],"id":1} 24 | ``` -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Extensions.JsonRpc/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.JsonRpc/WebApiClientCore.Extensions.JsonRpc.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | enable 5 | netstandard2.1 6 | True 7 | 8 | true 9 | Sign.snk 10 | 11 | WebApiClientCore的JsonRpc调用扩展 12 | WebApiClientCore的JsonRpc调用扩展 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.NewtonsoftJson/JsonNetSerializerOptions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | 4 | namespace WebApiClientCore.Extensions.NewtonsoftJson 5 | { 6 | /// 7 | /// json.net选项 8 | /// 9 | public class JsonNetSerializerOptions 10 | { 11 | /// 12 | /// camelCaseResolver 13 | /// 不能多次创建,否则影响到反射缓存 14 | /// 15 | private static readonly IContractResolver camelCaseResolver = new CamelCasePropertyNamesContractResolver(); 16 | 17 | /// 18 | /// 序列化设置 19 | /// 20 | public JsonSerializerSettings JsonSerializeOptions { get; set; } = CreateDefaultJsonOptions(); 21 | 22 | /// 23 | /// 反序列化设置 24 | /// 25 | public JsonSerializerSettings JsonDeserializeOptions { get; set; } = CreateDefaultJsonOptions(); 26 | 27 | /// 28 | /// 创建默认JsonSerializerSettings 29 | /// 30 | private static JsonSerializerSettings CreateDefaultJsonOptions() 31 | { 32 | return new JsonSerializerSettings 33 | { 34 | ContractResolver = camelCaseResolver, 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.NewtonsoftJson/README.md: -------------------------------------------------------------------------------- 1 | ## WebApiClientCore.Extensions.NewtonsoftJson                   2 | WebApiClientCore的NewtonsoftJson序列化扩展 3 | 4 | ### 适用场景 5 | * 熟悉NewtonsoftJson,不想使用System.Text.Json的项目; 6 | * 喜欢NewtonsoftJson的动态类型,且性能不敏感的项目; 7 | 8 | ### 使用方式 9 | #### 配置json.net[可选] 10 | ``` 11 | // ConfigureNewtonsoftJson 12 | services.AddHttpApi().ConfigureNewtonsoftJson(o => 13 | { 14 | o.JsonSerializeOptions.NullValueHandling = NullValueHandling.Ignore; 15 | }); 16 | ``` 17 | 18 | #### 声明特性 19 | 使用[JsonNetReturn]替换内置的[JsonReturn],[JsonNetContent]替换内置[JsonContent] 20 | ``` 21 | /// 22 | /// 用户操作接口 23 | /// 24 | [JsonNetReturn] 25 | public interface IUserApi : IHttpApi 26 | { 27 | [HttpPost("/users")] 28 | Task PostAsync([JsonNetContent] User user); 29 | } 30 | ``` -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.NewtonsoftJson/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Extensions.NewtonsoftJson/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.NewtonsoftJson/WebApiClientCore.Extensions.NewtonsoftJson.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | netstandard2.1 6 | True 7 | 8 | true 9 | Sign.snk 10 | 11 | WebApiClientCore的NewtonsoftJson序列化扩展 12 | WebApiClientCore的NewtonsoftJson序列化扩展 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/Attributes/ClientCredentialsTokenAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示 token 应用特性 7 | /// 需要注册 services.AddClientCredentialsTokenProvider 8 | /// 9 | [Obsolete("请使用OAuthTokenAttribute替换")] 10 | public class ClientCredentialsTokenAttribute : OAuthTokenAttribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/Attributes/PasswordCredentialsTokenAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示 token 应用特性 7 | /// 需要注册 services.AddPasswordCredentialsTokenProvider 8 | /// 9 | [Obsolete("请使用OAuthTokenAttribute替换")] 10 | public class PasswordCredentialsTokenAttribute : OAuthTokenAttribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/CodeAnalysis/DynamicDependencyAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// 表示动态依赖属性 6 | /// 7 | [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] 8 | sealed class DynamicDependencyAttribute : Attribute 9 | { 10 | /// 11 | /// 获取或设置动态访问的成员类型 12 | /// 13 | public DynamicallyAccessedMemberTypes MemberTypes { get; } 14 | 15 | /// 16 | /// 获取或设置依赖的类型 17 | /// 18 | public Type? Type { get; } 19 | 20 | /// 21 | /// 初始化 类的新实例 22 | /// 23 | /// 动态访问的成员类型 24 | /// 依赖的类型 25 | public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) 26 | { 27 | this.MemberTypes = memberTypes; 28 | this.Type = type; 29 | } 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// Specifies that the members accessed dynamically at runtime are considered used. 6 | /// 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, Inherited = false)] 8 | sealed class DynamicallyAccessedMembersAttribute : Attribute 9 | { 10 | /// 11 | /// Gets the types of dynamically accessed members. 12 | /// 13 | public DynamicallyAccessedMemberTypes MemberTypes { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with the specified member types. 17 | /// 18 | /// The types of dynamically accessed members. 19 | public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) 20 | { 21 | this.MemberTypes = memberTypes; 22 | } 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/CodeAnalysis/RequiresDynamicCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 || NET5_0 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// Specifies that the attributed class, constructor, or method requires dynamic code. 6 | /// 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] 8 | sealed class RequiresDynamicCodeAttribute : Attribute 9 | { 10 | /// 11 | /// Gets the message associated with the requirement for dynamic code. 12 | /// 13 | public string Message { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with the specified message. 17 | /// 18 | /// The message associated with the requirement for dynamic code. 19 | public RequiresDynamicCodeAttribute(string message) 20 | { 21 | this.Message = message; 22 | } 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/CodeAnalysis/RequiresUnreferencedCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] 5 | sealed class RequiresUnreferencedCodeAttribute : Attribute 6 | { 7 | /// 8 | /// 获取或设置对于未引用代码的要求的消息。 9 | /// 10 | public string Message { get; } 11 | 12 | /// 13 | /// 初始化 类的新实例。 14 | /// 15 | /// 对于未引用代码的要求的消息。 16 | public RequiresUnreferencedCodeAttribute(string message) 17 | { 18 | this.Message = message; 19 | } 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/DependencyInjection/ITokenProviderBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.DependencyInjection 2 | { 3 | /// 4 | /// token提供者创建器接口 5 | /// 6 | public interface ITokenProviderBuilder 7 | { 8 | /// 9 | /// 获取 token 提供者的名称 10 | /// 11 | string Name { get; } 12 | 13 | /// 14 | /// 获取服务提供者 15 | /// 16 | IServiceCollection Services { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/Exceptions/TokenEndPointNullException.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths.Exceptions 2 | { 3 | /// 4 | /// 表示获取Toke的Url节点为 null 的异常 5 | /// 6 | public class TokenEndPointNullException : TokenException 7 | { 8 | /// 9 | /// 获取Toke的Url节点为 null 的异常 10 | /// 11 | public TokenEndPointNullException() 12 | : base("The Endpoint is required") 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/Exceptions/TokenException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Extensions.OAuths.Exceptions 4 | { 5 | /// 6 | /// 表示Token异常 7 | /// 8 | public class TokenException : Exception 9 | { 10 | /// 11 | /// Token异常 12 | /// 13 | /// 消息 14 | public TokenException(string? message) 15 | : base(message) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/Exceptions/TokenNullException.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths.Exceptions 2 | { 3 | /// 4 | /// 表示空 token 异常 5 | /// 6 | public class TokenNullException : TokenException 7 | { 8 | /// 9 | /// 空 token 异常 10 | /// 11 | public TokenNullException() 12 | : base("Unable to get token") 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/ITokenProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WebApiClientCore.Extensions.OAuths 4 | { 5 | /// 6 | /// 定义 token 提供者的接口 7 | /// 8 | public interface ITokenProvider 9 | { 10 | /// 11 | /// 设置名称 12 | /// 13 | string Name { set; } 14 | 15 | /// 16 | /// 强制清除 token 以支持下次获取到新的 token 17 | /// 18 | void ClearToken(); 19 | 20 | /// 21 | /// 获取 token 信息 22 | /// 23 | /// 24 | Task GetTokenAsync(); 25 | } 26 | } -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/ITokenProviderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace WebApiClientCore.Extensions.OAuths 5 | { 6 | /// 7 | /// Token提供者工厂接口 8 | /// 9 | public interface ITokenProviderFactory 10 | { 11 | /// 12 | /// 通过接口类型获取或创建其对应的 token 提供者 13 | /// 14 | /// 接口类型 15 | /// 类型匹配模式 16 | /// 17 | /// 18 | ITokenProvider Create( 19 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type httpApiType, 20 | TypeMatchMode typeMatchMode = TypeMatchMode.TypeOnly); 21 | 22 | /// 23 | /// 通过接口类型获取或创建其对应的 token 提供者 24 | /// 25 | /// 接口类型 26 | /// 类型匹配模式 27 | /// TokenProvider的别名 28 | /// 29 | /// 30 | ITokenProvider Create( 31 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type httpApiType, 32 | TypeMatchMode typeMatchMode, 33 | string alias); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/ITokenProviderService.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths 2 | { 3 | /// 4 | /// 定义 http 接口的 token 提供者服务 5 | /// 6 | interface ITokenProviderService 7 | { 8 | /// 9 | /// 获取 token 提供者 10 | /// 11 | ITokenProvider TokenProvider { get; } 12 | 13 | /// 14 | /// 设置服务提供者的名称 15 | /// 16 | /// 17 | void SetProviderName(string alias); 18 | } 19 | } -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/README.md: -------------------------------------------------------------------------------- 1 | ## WebApiClientCore.Extensions.OAuths 2 | 3 | 使用WebApiClientCore.Extensions.OAuths扩展,轻松支持token的获取、刷新与应用。 4 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Extensions.OAuths/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TokenProviders/ClientCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths.TokenProviders 2 | { 3 | /// 4 | /// 表示Client身份信息 5 | /// 6 | public class ClientCredentials : Credentials 7 | { 8 | /// 9 | /// 访问的范围 10 | /// 11 | public string? Scope { get; set; } 12 | 13 | /// 14 | /// 扩展信息 15 | /// 简单模型或字典 16 | /// 17 | public object? Extra { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TokenProviders/ClientCredentialsOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace WebApiClientCore.Extensions.OAuths.TokenProviders 6 | { 7 | /// 8 | /// 表示Client身份信息选项 9 | /// 10 | public class ClientCredentialsOptions 11 | { 12 | /// 13 | /// 获取或设置提供 token 获取的Url节点 14 | /// 15 | [Required] 16 | [DisallowNull] 17 | public Uri? Endpoint { get; set; } 18 | 19 | /// 20 | /// 是否尝试使用 token 刷新功能 21 | /// 禁用则 token 过期时总是去请求新 token 22 | /// 23 | public bool UseRefreshToken { get; set; } = true; 24 | 25 | /// 26 | /// 获取或设置Client身份信息 27 | /// 28 | [Required] 29 | public ClientCredentials Credentials { get; set; } = new ClientCredentials(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TokenProviders/Credentials.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths.TokenProviders 2 | { 3 | /// 4 | /// 表示身份信息 5 | /// 6 | public class Credentials 7 | { 8 | /// 9 | /// 客户端id 10 | /// 11 | public string? Client_id { get; set; } 12 | 13 | /// 14 | /// 客户端秘钥 15 | /// 16 | public string? Client_secret { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TokenProviders/PasswordCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths.TokenProviders 2 | { 3 | /// 4 | /// 表示用户名密码身份信息 5 | /// 6 | public class PasswordCredentials : ClientCredentials 7 | { 8 | /// 9 | /// 用户名 10 | /// 11 | public string? Username { get; set; } 12 | 13 | /// 14 | /// 密码 15 | /// 16 | public string? Password { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TokenProviders/PasswordCredentialsOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace WebApiClientCore.Extensions.OAuths.TokenProviders 6 | { 7 | /// 8 | /// 表示账号密码身份信息选项 9 | /// 10 | public class PasswordCredentialsOptions 11 | { 12 | /// 13 | /// 获取或设置提供 token 获取的Url节点 14 | /// 15 | [Required] 16 | [DisallowNull] 17 | public Uri? Endpoint { get; set; } 18 | 19 | /// 20 | /// 是否尝试使用 token 刷新功能 21 | /// 禁用则 token 过期时总是去请求新 token 22 | /// 23 | public bool UseRefreshToken { get; set; } = true; 24 | 25 | /// 26 | /// 获取或设置Client身份信息 27 | /// 28 | [Required] 29 | public PasswordCredentials Credentials { get; set; } = new PasswordCredentials(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TokenProviders/RefreshTokenCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths.TokenProviders 2 | { 3 | /// 4 | /// 表示用于刷新 token 的身份信息 5 | /// 6 | public class RefreshTokenCredentials : Credentials 7 | { 8 | /// 9 | /// 刷新 token值 10 | /// 11 | public string? Refresh_token { get; set; } 12 | 13 | /// 14 | /// 扩展信息 15 | /// 简单模型或字典 16 | /// 17 | public object? Extra { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/TypeMatchMode.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Extensions.OAuths 2 | { 3 | /// 4 | /// 类型匹配模式 5 | /// 6 | public enum TypeMatchMode 7 | { 8 | /// 9 | /// 类型精确匹配 10 | /// 11 | TypeOnly, 12 | 13 | /// 14 | /// 类型或其父接口类型匹配 15 | /// 16 | TypeOrBaseTypes 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.OAuths/WebApiClientCore.Extensions.OAuths.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | True 6 | netstandard2.1;net5.0;net8.0 7 | true 8 | 9 | true 10 | Sign.snk 11 | 12 | 基于WebApiClientCore的token提供者 13 | 基于WebApiClientCore的token提供者 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.SourceGenerator/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Extensions.SourceGenerator/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore.Extensions.SourceGenerator/WebApiClientCore.Extensions.SourceGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | false 5 | netstandard2.1 6 | true 7 | Sign.snk 8 | 此扩展包的实现已合并到WebApiClientCore包,无任何功能 9 | 此扩展包的实现已合并到WebApiClientCore包,无任何功能 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/HtmlTempate.cs: -------------------------------------------------------------------------------- 1 | using RazorEngineCore; 2 | 3 | namespace WebApiClientCore.OpenApi.SourceGenerator 4 | { 5 | /// 6 | /// html模板 7 | /// 8 | public class HtmlTempate : RazorEngineTemplateBase 9 | { 10 | /// 11 | /// html标签转换 12 | /// 13 | /// 14 | public override void Write(object? obj = null) 15 | { 16 | var text = obj?.ToString(); 17 | if (text != null) 18 | { 19 | text = text.Replace("<", "<").Replace(">", ">"); 20 | } 21 | base.Write(text); 22 | } 23 | } 24 | 25 | /// 26 | /// html模板 27 | /// 28 | /// 29 | public class HtmlTempate : HtmlTempate 30 | { 31 | public new T? Model { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/HttpModel.cs: -------------------------------------------------------------------------------- 1 | using NJsonSchema.CodeGeneration; 2 | 3 | namespace WebApiClientCore.OpenApi.SourceGenerator 4 | { 5 | /// 6 | /// 表示WebApiClient的模型描述 7 | /// 8 | public class HttpModel : CSharpCode 9 | { 10 | /// 11 | /// 获取使用的命名空间 12 | /// 13 | public string NameSapce { get; } 14 | 15 | /// 16 | /// WebApiClient的模型描述 17 | /// 18 | /// 源代码 19 | /// 命名空间 20 | public HttpModel(CodeArtifact codeArtifact, string nameSpace) 21 | : base(codeArtifact) 22 | { 23 | this.NameSapce = nameSpace; 24 | } 25 | 26 | /// 27 | /// 转换为完整的代码 28 | /// 29 | /// 30 | public override string ToString() 31 | { 32 | var cshtml = CSharpHtml.Views(); 33 | var source = cshtml.RenderText(this); 34 | return new CSharpCode(source, this.TypeName, this.Type).ToString(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/OpenApiDocOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace WebApiClientCore.OpenApi.SourceGenerator 4 | { 5 | /// 6 | /// 表示命令选项 7 | /// 8 | public class OpenApiDocOptions 9 | { 10 | /// 11 | /// openApi的json本地文件路径或远程Uri地址 12 | /// 13 | [Option('o', "openapi", MetaValue = "OpenApi", Required = true, HelpText = "OpenApi的本地文件路径或远程Uri地址")] 14 | public string OpenApi { get; set; } = string.Empty; 15 | 16 | /// 17 | /// 代码的命名空间 18 | /// 19 | [Option('n', "namespace", MetaValue = "Namespace", Required = false, HelpText = "代码的命名空间,如WebApiClientCore")] 20 | public string Namespace { get; set; } = string.Empty; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace WebApiClientCore.OpenApi.SourceGenerator 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | Parser.Default.ParseArguments(args) 10 | .WithParsed(options => 11 | { 12 | var doc = new OpenApiDoc(options); 13 | doc.GenerateFiles(); 14 | }) 15 | .WithNotParsed(errors => 16 | { 17 | errors.Output(); 18 | }); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WebApiClientCore.OpenApi.SourceGenerator": { 4 | "commandName": "Project", 5 | "commandLineArgs": "-o petstore.json" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/README.md: -------------------------------------------------------------------------------- 1 | ## WebApiClientCore.OpenApi.SourceGenerator 2 | 3 | > 将OpenApi的本地或远程文档解析生成WebApiClientCore的接口定义代码文件 4 | 5 | ### 1.1 命令介绍 6 | ``` 7 | -o OpenApi, --openapi=OpenApi Required. openApi的json本地文件路径或远程Uri地址 8 | -n Namespace, --namespace=Namespace 代码的命名空间,如WebApiClientCore 9 | --help Display this help screen. 10 | ``` 11 | ### 1.2 工作流程 12 | 1. 使用NSwag解析OpenApi的json得到OpenApiDocument对象 13 | 2. 使用RazorEngine将OpenApiDocument传入cshtml模板编译得到html 14 | 3. 使用XDocument将html的文本代码提取,得到WebApiClientCore的声明式代码 15 | 4. 代码美化,输出到本地文件 16 | -------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/Views/HttpModel.cshtml: -------------------------------------------------------------------------------- 1 | @inherits HtmlTempate 2 | @using System.Security; 3 | @using WebApiClientCore.OpenApi.SourceGenerator; 4 | 5 | 6 | 7 |
using System;
8 |
using System.Collections.Generic;
9 |
using System.Text.Json.Serialization;
10 |
using System.Runtime.Serialization;
11 |
using System.Collections.ObjectModel;
12 |
using System.ComponentModel.DataAnnotations;
13 |
14 | 15 | 16 |
namespace @(Model.NameSapce)
17 |
{
18 | 19 | @foreach (var line in Model.Lines) 20 | { 21 |
@SecurityElement.Escape(line)
22 | } 23 |
24 |
}
25 |
26 |
-------------------------------------------------------------------------------- /WebApiClientCore.OpenApi.SourceGenerator/WebApiClientCore.OpenApi.SourceGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | enable 6 | netcoreapp3.1;net6.0;net8.0 7 | 8 | 将本地或远程OpenApi文档解析生成WebApiClientCore的接口定义代码文件的工具 9 | zh-Hans 10 | 11 | 将OpenApi的本地或远程文档解析生成WebApiClientCore的接口定义代码文件 12 | 将OpenApi的本地或远程文档解析生成WebApiClientCore的接口定义代码文件 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | PreserveNewest 29 | $(IncludeRazorContentInPack) 30 | Always 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Abstractions/KeyValueTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace WebApiClientCore.Test.Abstractions 5 | { 6 | public class KeyValueTest 7 | { 8 | [Fact] 9 | public void CtorTest() 10 | { 11 | Assert.Throws(() => new KeyValue("", "")); 12 | Assert.Throws(() => new KeyValue(null!, "")); 13 | 14 | var kv = new KeyValue("key", "value"); 15 | Assert.Equal("key", kv.Key); 16 | Assert.Equal("value", kv.Value); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Attributes/ActionAttributes/BasicAuthAttributeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using WebApiClientCore.Attributes; 5 | using WebApiClientCore.Implementations; 6 | using Xunit; 7 | 8 | namespace WebApiClientCore.Test.Attributes.ActionAttributes 9 | { 10 | 11 | public class BasicAuthAttributeTest 12 | { 13 | [Fact] 14 | public async Task OnRequestAsyncTest() 15 | { 16 | var apiAction = new DefaultApiActionDescriptor(typeof(ITestApi).GetMethod("PostAsync")!); 17 | var context = new TestRequestContext(apiAction, string.Empty); 18 | 19 | var attr = new BasicAuthAttribute("laojiu", "123456"); 20 | await attr.OnRequestAsync(context); 21 | 22 | var auth = Convert.ToBase64String(Encoding.ASCII.GetBytes("laojiu:123456")); 23 | Assert.Equal("Basic", context.HttpContext.RequestMessage.Headers.Authorization?.Scheme); 24 | Assert.True(context.HttpContext.RequestMessage.Headers.Authorization?.Parameter == auth); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Attributes/ActionAttributes/HttpHostAttributeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using WebApiClientCore.Attributes; 4 | using WebApiClientCore.Implementations; 5 | using Xunit; 6 | 7 | namespace WebApiClientCore.Test.Attributes.ActionAttributes 8 | { 9 | public class HttpHostAttributeTest 10 | { 11 | [Fact] 12 | public async Task OnRequestAsyncTest() 13 | { 14 | var apiAction = new DefaultApiActionDescriptor(typeof(ITestApi).GetMethod("PostAsync")!); 15 | var context = new TestRequestContext(apiAction, string.Empty); 16 | 17 | Assert.Throws(() => new HttpHostAttribute(null!)); 18 | // Assert.Throws(() => new HttpHostAttribute("/")); 19 | 20 | context.HttpContext.RequestMessage.RequestUri = null; 21 | var attr = new HttpHostAttribute("http://www.webapiclient.com"); 22 | await attr.OnRequestAsync(context); 23 | Assert.True(context.HttpContext.RequestMessage.RequestUri == new Uri("http://www.webapiclient.com")); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Attributes/ITestApi.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Test.Attributes 5 | { 6 | public interface ITestApi : IHttpApi 7 | { 8 | Task PostAsync(object value); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Attributes/ParameterAttributes/IMyApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | 6 | namespace WebApiClientCore.Test.Attributes.ParameterAttributes 7 | { 8 | public interface IMyApi 9 | { 10 | ITask PostAsync(object headers); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Attributes/ReturnAttributes/ITestApi.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Test.Attributes.ReturnAttributes 5 | { 6 | public interface ITestApi 7 | { 8 | Task HttpResponseMessageAsync(); 9 | 10 | Task StringAsync(); 11 | 12 | Task ByteArrayAsync(); 13 | 14 | Task JsonXmlAsync(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Attributes/ReturnAttributes/TestModel.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Test.Attributes.ReturnAttributes 2 | { 3 | public class TestModel 4 | { 5 | public string Name { get; set; } = "laojiu"; 6 | 7 | public int Age { get; set; } = 18; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/BuildinExtensions/HttpRequestHeaderExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Reflection; 4 | using Xunit; 5 | 6 | namespace WebApiClientCore.Test.BuildinExtensions 7 | { 8 | public class HttpRequestHeaderExtensionsTest 9 | { 10 | [Fact] 11 | public void ToHeaderNameTest() 12 | { 13 | Assert.Equal("Accept", HttpRequestHeader.Accept.ToHeaderName()); 14 | Assert.Equal("Accept-Charset", HttpRequestHeader.AcceptCharset.ToHeaderName()); 15 | 16 | foreach (var item in Enum.GetValues()) 17 | { 18 | var name = Enum.GetName(item); 19 | var field = typeof(HttpRequestHeader).GetField(name!); 20 | var headerName = field?.GetCustomAttribute()?.Name; 21 | Assert.Equal(headerName, item.ToHeaderName()); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /WebApiClientCore.Test/BuildinExtensions/StringExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace WebApiClientCore.Test.BuildinExtensions 4 | { 5 | public class StringExtensionsTest 6 | { 7 | [Fact] 8 | public void RepaceIgnoreCaseTest() 9 | { 10 | var str = "WebApiClientCore.Benchmarks.StringReplaces.WebApiClientCore"; 11 | var newStr = str.ReplaceIgnoreCase("core", "CORE", out var replaced); 12 | Assert.True(replaced); 13 | Assert.Equal("WebApiClientCORE.Benchmarks.StringReplaces.WebApiClientCORE", newStr); 14 | 15 | str = "AbccBAd"; 16 | var newStr2 = str.ReplaceIgnoreCase("A", "x", out replaced); 17 | Assert.True(replaced); 18 | Assert.Equal("xbccBxd", newStr2); 19 | 20 | str = "abc"; 21 | var newStr3 = str.ReplaceIgnoreCase("x", "x", out replaced); 22 | Assert.False(replaced); 23 | Assert.Equal(str, newStr3); 24 | 25 | str = "aaa"; 26 | var newStr4 = str.ReplaceIgnoreCase("A", "x", out replaced); 27 | Assert.True(replaced); 28 | Assert.Equal("xxx", newStr4); 29 | 30 | var newStr5 = str.ReplaceIgnoreCase("a", null, out replaced); 31 | Assert.True(replaced); 32 | Assert.Equal("", newStr5); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/HttpContents/JsonContentTest.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using WebApiClientCore.HttpContents; 5 | using Xunit; 6 | 7 | namespace WebApiClientCore.Test.HttpContents 8 | { 9 | public class JsonContentTest 10 | { 11 | [Fact] 12 | public async Task Utf8JsonTest() 13 | { 14 | var options = new WebApiClientCore.HttpApiOptions(); 15 | var content = new JsonContent("at我", options.JsonSerializeOptions); 16 | Assert.Equal(content.GetEncoding(), Encoding.UTF8); 17 | var text = await content.ReadAsStringAsync(); 18 | Assert.Equal("\"at我\"", text); 19 | } 20 | 21 | [Fact] 22 | public async Task Utf16JsonTest() 23 | { 24 | var options = new WebApiClientCore.HttpApiOptions(); 25 | var content = new JsonContent("at我", options.JsonSerializeOptions, Encoding.Unicode); 26 | Assert.Equal(content.GetEncoding(), Encoding.Unicode); 27 | var text = await content.ReadAsStringAsync(); 28 | Assert.Equal("\"at我\"", text); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/IDescriptorApi.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using WebApiClientCore.Attributes; 7 | 8 | namespace WebApiClientCore.Test 9 | { 10 | /// 11 | /// 用户操作接口 12 | /// 13 | [LoggingFilter] 14 | [HttpHost("http://localhost")] 15 | public interface IDescriptorApi : IHttpApi 16 | { 17 | [HttpGet] 18 | [Timeout(10 * 1000)] 19 | Task Get1([Uri] string url, string something); 20 | 21 | [HttpGet] 22 | Task Get2([Required]string id, CancellationToken token); 23 | 24 | [HttpGet] 25 | Task Get3([Required]string account, CancellationToken token); 26 | 27 | [HttpPost] 28 | Task Get4(); 29 | 30 | [HttpGet] 31 | Task Get5(string nickName); 32 | 33 | 34 | [HttpGet] 35 | Task Get6(string nickName); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/ApiActionDescriptorTest.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.Implementations; 2 | using Xunit; 3 | 4 | namespace WebApiClientCore.Test.Implementations 5 | { 6 | public class ApiActionDescriptorTest 7 | { 8 | [Fact] 9 | public void CtorTest() 10 | { 11 | var m = typeof(IDescriptorApi).GetMethod("Get1"); 12 | Assert.NotNull(m); 13 | var d = new DefaultApiActionDescriptor(m); 14 | 15 | Assert.Equal(3, d.Attributes.Count); 16 | Assert.Single(d.FilterAttributes); 17 | Assert.Equal(2, d.Parameters.Count); 18 | Assert.True(d.Name == m.Name); 19 | Assert.True(d.Member == m); 20 | Assert.Single(d.Return.Attributes); 21 | Assert.True(d.Return.ReturnType == m.ReturnType); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/ApiDataTypeDescriptorTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using WebApiClientCore.Implementations; 3 | using Xunit; 4 | 5 | namespace WebApiClientCore.Test.Implementations 6 | { 7 | public class ApiDataTypeDescriptorTest 8 | { 9 | private ApiDataTypeDescriptor Create(string methodName) 10 | { 11 | var method = typeof(IDescriptorApi).GetMethod(methodName); 12 | Debug.Assert(method != null); 13 | return new DefaultApiReturnDescriptor(method).DataType; 14 | } 15 | 16 | [Fact] 17 | public void CtorTest() 18 | { 19 | var m1 = Create("Get1"); 20 | var m2 = Create("Get2"); 21 | var m3 = Create("Get3"); 22 | var m4 = Create("Get4"); 23 | var m5 = Create("Get5"); 24 | var m6 = Create("Get6"); 25 | 26 | Assert.True(m1.IsRawString); 27 | Assert.True(m2.IsRawHttpResponseMessage); 28 | Assert.True(m3.IsRawStream); 29 | Assert.True(m4.IsRawHttpResponseMessage); 30 | Assert.True(m6.IsRawByteArray); 31 | Assert.False(m5.IsRawType); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/ApiParameterDescriptorTest.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.Implementations; 2 | using Xunit; 3 | 4 | namespace WebApiClientCore.Test.Implementations 5 | { 6 | public class ApiParameterDescriptorTest 7 | { 8 | [Fact] 9 | public void CtorTest() 10 | { 11 | var p1 = typeof(IDescriptorApi).GetMethod("Get2")!.GetParameters()[0]; 12 | var m1 = new DefaultApiParameterDescriptor(p1); 13 | Assert.Single(m1.Attributes); 14 | Assert.Equal(0, m1.Index); 15 | Assert.True(m1.Member == p1); 16 | Assert.True(m1.Name == p1.Name); 17 | Assert.True(m1.ParameterType == p1.ParameterType); 18 | Assert.Single(m1.ValidationAttributes); 19 | 20 | 21 | var p2 = typeof(IDescriptorApi).GetMethod("Get2")!.GetParameters()[1]; 22 | var m2 = new DefaultApiParameterDescriptor(p2); 23 | Assert.Single(m2.Attributes); 24 | Assert.Equal(1, m2.Index); 25 | Assert.True(m2.Member == p2); 26 | Assert.True(m2.Name == p2.Name); 27 | Assert.True(m2.ParameterType == p2.ParameterType); 28 | Assert.Empty(m2.ValidationAttributes); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/DefaultDataCollectionTest.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.Implementations; 2 | using Xunit; 3 | 4 | namespace WebApiClientCore.Test.Implementations 5 | { 6 | public class DefaultDataCollectionTest 7 | { 8 | [Fact] 9 | public void ReadWriteTest() 10 | { 11 | var datas = new DefaultDataCollection(); 12 | datas.Set("string", "a"); 13 | datas.Set(2, 1); 14 | datas.Set(typeof(DefaultDataCollectionTest), new DefaultDataCollectionTest()); 15 | 16 | Assert.True(datas.Get("string") == "a"); 17 | Assert.True(datas.Get(2) == 1); 18 | 19 | var state = datas.TryGetValue(typeof(DefaultDataCollectionTest), out var value); 20 | Assert.True(state && value is DefaultDataCollectionTest); 21 | 22 | state = datas.TryGetValue(typeof(DefaultDataCollectionTest), out var tValue); 23 | Assert.True(state && tValue != null); 24 | 25 | state = datas.TryGetValue(typeof(string), out _); 26 | Assert.False(state); 27 | 28 | datas.TryRemove("string", out _); 29 | Assert.True(datas.Get("string") == default); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/ResponseCacheProviderTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using Microsoft.Extensions.Options; 3 | using System; 4 | using System.Threading.Tasks; 5 | using WebApiClientCore.Implementations; 6 | using Xunit; 7 | 8 | namespace WebApiClientCore.Test.Implementations 9 | { 10 | public class ResponseCacheProviderTest 11 | { 12 | [Fact] 13 | public async Task GetSetAsyncTest() 14 | { 15 | var options = Options.Create(new MemoryCacheOptions()); 16 | var memCache = new MemoryCache(options); 17 | var provider = new DefaultResponseCacheProvider(memCache); 18 | var cache = await provider.GetAsync("key"); 19 | Assert.False(cache.HasValue); 20 | 21 | await provider.SetAsync("key", new ResponseCacheEntry(), TimeSpan.FromSeconds(1d)); 22 | cache = await provider.GetAsync("key"); 23 | Assert.True(cache.HasValue); 24 | 25 | await Task.Delay(TimeSpan.FromSeconds(1.1d)); 26 | cache = await provider.GetAsync("key"); 27 | Assert.False(cache.HasValue); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/Tasks/ActionHandleTaskTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using WebApiClientCore.Implementations.Tasks; 4 | using Xunit; 5 | 6 | namespace WebApiClientCore.Test.Implementations.Tasks 7 | { 8 | public class ActionHandleTaskTest 9 | { 10 | class NotImplementedApiTask : TaskBase 11 | { 12 | protected override Task InvokeAsync() 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | } 17 | 18 | 19 | [Fact] 20 | public async Task WhenCatchTest() 21 | { 22 | var apiTask = new NotImplementedApiTask(); 23 | var result = await apiTask.Handle().WhenCatch(() => "abc"); 24 | Assert.True(result == "abc"); 25 | 26 | result = await apiTask.Handle().WhenCatch((ex) => "xyz"); 27 | Assert.True(result == "xyz"); 28 | 29 | result = await apiTask.Handle().WhenCatchAsync((ex) => Task.FromResult("xyz")); 30 | Assert.True(result == "xyz"); 31 | 32 | await Assert.ThrowsAsync(async () => 33 | await apiTask.Handle().WhenCatchAsync((ex) => Task.FromResult("xyz"))); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Implementations/TypeAttributes/HttpContentTypeAttributeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using WebApiClientCore.Implementations; 5 | using WebApiClientCore.Implementations.TypeAttributes; 6 | using Xunit; 7 | 8 | namespace WebApiClientCore.Test.Implementations.TypeAttributes 9 | { 10 | public class HttpContentTypeAttributeTest 11 | { 12 | public interface IMyApi 13 | { 14 | ITask PostAsync(HttpContent content); 15 | } 16 | 17 | [Fact] 18 | public async Task OnRequestAsyncTest() 19 | { 20 | var apiAction = new DefaultApiActionDescriptor(typeof(IMyApi).GetMethod("PostAsync")!); 21 | var context = new TestRequestContext(apiAction, new StringContent("laojiu")); 22 | 23 | context.HttpContext.RequestMessage.RequestUri = new Uri("http://www.mywebapi.com"); 24 | context.HttpContext.RequestMessage.Method = HttpMethod.Post; 25 | 26 | var attr = new HttpContentTypeAttribute(); 27 | await attr.OnRequestAsync(new ApiParameterContext(context, 0)); 28 | 29 | var body = await context.HttpContext.RequestMessage.Content!.ReadAsStringAsync(); 30 | Assert.Equal("laojiu", body); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Internals/MediaTypeUtilTest.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.Internals; 2 | using Xunit; 3 | 4 | namespace WebApiClientCore.Test.Internals 5 | { 6 | public class MediaTypeUtilTest 7 | { 8 | [Fact] 9 | public void IsMatchTest() 10 | { 11 | Assert.True(MediaTypeUtil.IsMatch("a/a", "a/a")); 12 | Assert.True(MediaTypeUtil.IsMatch("a/a", "a/a")); 13 | Assert.True(MediaTypeUtil.IsMatch("a/a", "A/a")); 14 | Assert.True(MediaTypeUtil.IsMatch("a/a", "A/A")); 15 | 16 | Assert.True(MediaTypeUtil.IsMatch("a/*", "A/A")); 17 | Assert.True(MediaTypeUtil.IsMatch("*/*", "A/A")); 18 | 19 | Assert.False(MediaTypeUtil.IsMatch("a/b", "a/a")); 20 | Assert.False(MediaTypeUtil.IsMatch("a/B", "A/a")); 21 | Assert.False(MediaTypeUtil.IsMatch("a/b", "A/A")); 22 | 23 | Assert.False(MediaTypeUtil.IsMatch("a/*", "b/A")); 24 | Assert.False(MediaTypeUtil.IsMatch("a/*", null)); 25 | Assert.False(MediaTypeUtil.IsMatch("a/*", "")); 26 | Assert.False(MediaTypeUtil.IsMatch("a", "a/a")); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Internals/RecyclableBufferWriterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WebApiClientCore.Internals; 3 | using Xunit; 4 | 5 | namespace WebApiClientCore.Test.Internals 6 | { 7 | public class RecyclableBufferWriterTest 8 | { 9 | [Fact] 10 | public void WriteTest() 11 | { 12 | using var writer = new RecyclableBufferWriter(1); 13 | writer.Write('H'); 14 | Assert.Equal(1, writer.WrittenCount); 15 | writer.Write('e'); 16 | Assert.Equal(2, writer.WrittenCount); 17 | writer.Write("llo"); 18 | Assert.True(writer.WrittenSpan.SequenceEqual("Hello")); 19 | 20 | writer.Clear(); 21 | Assert.Equal(0, writer.WrittenCount); 22 | 23 | var span = writer.GetSpan(); 24 | "Word".AsSpan().CopyTo(span); 25 | writer.Advance(4); 26 | Assert.True(writer.WrittenSpan.SequenceEqual("Word")); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Internals/ValueStringBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.Internals; 2 | using Xunit; 3 | 4 | namespace WebApiClientCore.Test.Internals 5 | { 6 | public class ValueStringBuilderTest 7 | { 8 | [Fact] 9 | public void AppendTest() 10 | { 11 | var builder = new ValueStringBuilder(stackalloc char[1]); 12 | builder.Append('a'); 13 | builder.Append("bc"); 14 | builder.Append('d'); 15 | var str = builder.ToString(); 16 | 17 | Assert.Equal("abcd", str); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Parameters/ITestApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Test.Parameters 6 | { 7 | public interface ITestApi 8 | { 9 | Task PostAsync(object value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Serialization/FormatModel.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Test.Serialization 2 | { 3 | public class FormatModel 4 | { 5 | public int Age { get; set; } 6 | 7 | public string? Name { get; set; } 8 | 9 | public override int GetHashCode() 10 | { 11 | return Age.GetHashCode() ^ (Name == null ? 0 : Name.GetHashCode()); 12 | } 13 | 14 | public override bool Equals(object? obj) 15 | { 16 | return obj is FormatModel o && o.Age == this.Age && o.Name == this.Name; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Serialization/XmlSerializerTest.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using WebApiClientCore.Serialization; 3 | using Xunit; 4 | 5 | namespace WebApiClientCore.Test.Serialization 6 | { 7 | public class XmlSerializerTest 8 | { 9 | [Fact] 10 | public void ReadWriteTest() 11 | { 12 | var obj1 = new FormatModel { Age = 18, Name = "老九" }; 13 | var xml = XmlSerializer.Serialize(obj1, null); 14 | var obj2 = XmlSerializer.Deserialize(xml, typeof(FormatModel), null); 15 | Assert.True(obj1.Equals(obj2)); 16 | } 17 | 18 | [Fact] 19 | public void EncodingTest() 20 | { 21 | var obj1 = new FormatModel { Age = 18, Name = "老九" }; 22 | 23 | var opt = new System.Xml.XmlWriterSettings 24 | { 25 | Encoding = Encoding.Unicode 26 | }; 27 | 28 | var xml = XmlSerializer.Serialize(obj1, opt); 29 | Assert.Contains(opt.Encoding.WebName, xml); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WebApiClientCore.Test/Test.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore.Test/Test.snk -------------------------------------------------------------------------------- /WebApiClientCore.Test/WebApiClientCore.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0;net8.0 5 | enable 6 | true 7 | Test.snk 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /WebApiClientCore/AliasAsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 表示将参数别名 7 | /// 8 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] 9 | public sealed class AliasAsAttribute : Attribute 10 | { 11 | /// 12 | /// 获取别名 13 | /// 14 | public string Name { get; } 15 | 16 | /// 17 | /// 指定参数或属性的别名 18 | /// 19 | /// 参数或属性的别名 20 | /// 21 | public AliasAsAttribute(string name) 22 | { 23 | this.Name = name ?? throw new ArgumentNullException(nameof(name)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiClientCore/AssemblyInternalsVisible.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("WebApiClientCore.Test,PublicKey=002400000480000094000000060200000024000052534131000400000100010019109862155d1d31459a723268868f2802134d116288713b711a2ef2e493380d682a6714eee3c7226ee4fee9f2cbc911758ff56823ddd9ec57ed0b82f5b1354e43160593c1982d96f08e94d192966ae23c15562a35bd579d3f381bf0c232c4e795aa650ec892234f04149d0bf31846aa6d57910ca9f7be2de3b438b8b039bae2")] 4 | [assembly: InternalsVisibleTo("WebApiClientCore.Benchmarks,PublicKey=00240000048000009400000006020000002400005253413100040000010001005548d2c9893025d66afe2460cbedc49435f30a4560cfa157c8081bd4d30217f0c88da655b564f70f320cda7b1dad58a40f4ff3417fd3f99e94a5320c6801c4f0da92f5f63389660c6882a7e04fbf5cb1c0f31aa9adff787a21e2474d9399868743fc090158de675e412b8a8024ae12f4b582df72635626a86e329d70021488d1")] 5 | -------------------------------------------------------------------------------- /WebApiClientCore/AttributeCtorUsageAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 表示特性的构造函数使用范围 7 | /// 8 | [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)] 9 | public sealed class AttributeCtorUsageAttribute : Attribute 10 | { 11 | /// 12 | /// 获取使用范围 13 | /// 14 | public AttributeTargets Targets { get; } 15 | 16 | /// 17 | /// 特性的构造函数使用范围 18 | /// 19 | /// 使用范围 20 | public AttributeCtorUsageAttribute(AttributeTargets targets) 21 | { 22 | this.Targets = targets; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/ApiActionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// ApiAction修饰特性抽象 8 | /// 9 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 10 | public abstract class ApiActionAttribute : Attribute, IApiActionAttribute 11 | { 12 | /// 13 | /// 获取执行排序索引 14 | /// 15 | public int OrderIndex { get; protected set; } 16 | 17 | /// 18 | /// 获取本类型是否允许在接口与方法上重复 19 | /// 20 | public bool AllowMultiple => this.GetType().IsAllowMultiple(); 21 | 22 | /// 23 | /// 执行前 24 | /// 25 | /// 上下文 26 | /// 27 | public abstract Task OnRequestAsync(ApiRequestContext context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/BasicAuthAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http.Headers; 4 | using System.Threading.Tasks; 5 | 6 | namespace WebApiClientCore.Attributes 7 | { 8 | /// 9 | /// 表示请求的基本授权 10 | /// 11 | [DebuggerDisplay("{Basic} {parameter}")] 12 | public class BasicAuthAttribute : ApiActionAttribute 13 | { 14 | /// 15 | /// 授权头的值 16 | /// 17 | private readonly BasicAuthenticationHeaderValue authorization; 18 | 19 | /// 20 | /// 基本授权 21 | /// 22 | /// 账号 23 | /// 密码 24 | /// 25 | public BasicAuthAttribute(string username, string? password) 26 | { 27 | this.authorization = new BasicAuthenticationHeaderValue(username, password); 28 | } 29 | 30 | /// 31 | /// 执行前 32 | /// 33 | /// 上下文 34 | /// 35 | public override Task OnRequestAsync(ApiRequestContext context) 36 | { 37 | context.HttpContext.RequestMessage.Headers.Authorization = authorization; 38 | return Task.CompletedTask; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpCompletionOptionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Attributes 6 | { 7 | /// 8 | /// 指示请求完成选项的特性 9 | /// 缺省的情况下,当声明返回类型为Stream或HttpResponseMessage时使用ResponseHeadersRead 10 | /// 11 | public class HttpCompletionOptionAttribute : ApiActionAttribute 12 | { 13 | /// 14 | /// 请求完成选项 15 | /// 16 | private readonly HttpCompletionOption completionOption; 17 | 18 | /// 19 | /// 指示请求完成选项的特性 20 | /// 21 | /// 请求完成选项 22 | public HttpCompletionOptionAttribute(HttpCompletionOption completionOption) 23 | { 24 | this.completionOption = completionOption; 25 | } 26 | 27 | /// 28 | /// 执行前 29 | /// 30 | /// 上下文 31 | /// 32 | public override Task OnRequestAsync(ApiRequestContext context) 33 | { 34 | context.HttpContext.CompletionOption = this.completionOption; 35 | return Task.CompletedTask; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpDeleteAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Delete请求 7 | /// 8 | public class HttpDeleteAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Delete请求 12 | /// 13 | public HttpDeleteAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Delete请求 20 | /// 21 | /// 请求绝对或相对路径 22 | public HttpDeleteAttribute(string? path) 23 | : base(HttpMethod.Delete, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpGetAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Get请求 7 | /// 8 | public class HttpGetAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Get请求 12 | /// 13 | public HttpGetAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Get请求 20 | /// 21 | /// 请求绝对或相对路径 22 | public HttpGetAttribute(string? path) 23 | : base(HttpMethod.Get, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpHeadAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Head请求 7 | /// 8 | public class HttpHeadAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Head请求 12 | /// 13 | public HttpHeadAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Head请求 20 | /// 21 | /// 请求绝对或相对路径 22 | public HttpHeadAttribute(string? path) 23 | : base(HttpMethod.Head, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpHostAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Attributes 6 | { 7 | /// 8 | /// 表示请求服务绝对完整主机域名 9 | /// 例如 http://www.abc.com/ 10 | /// 11 | [DebuggerDisplay("Host = {Host}")] 12 | public class HttpHostAttribute : HttpHostBaseAttribute 13 | { 14 | /// 15 | /// 获取完整主机域名 16 | /// 17 | public Uri Host { get; } 18 | 19 | /// 20 | /// 请求服务绝对完整主机域名 21 | /// 22 | /// 例如 http://www.abc.com 23 | /// 24 | /// 25 | public HttpHostAttribute(string host) 26 | { 27 | this.Host = new Uri(host, UriKind.Absolute); 28 | } 29 | 30 | /// 31 | /// 执行前 32 | /// 33 | /// 上下文 34 | /// 35 | public override Task OnRequestAsync(ApiRequestContext context) 36 | { 37 | if (context.HttpContext.RequestMessage.RequestUri == null) 38 | { 39 | context.HttpContext.RequestMessage.RequestUri = this.Host; 40 | } 41 | return Task.CompletedTask; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpHostBaseAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Attributes 2 | { 3 | /// 4 | /// 表示请求服务主机 5 | /// 6 | public abstract class HttpHostBaseAttribute : ApiActionAttribute 7 | { 8 | /// 9 | /// 请求服务主机 10 | /// 11 | public HttpHostBaseAttribute() 12 | { 13 | this.OrderIndex = int.MinValue; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpOptionsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Options请求 7 | /// 8 | public class HttpOptionsAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Options请求 12 | /// 13 | public HttpOptionsAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Options请求 20 | /// 21 | /// 相对路径 22 | public HttpOptionsAttribute(string? path) 23 | : base(HttpMethod.Options, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpPatchAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Patch请求 7 | /// 8 | public class HttpPatchAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Patch请求 12 | /// 13 | public HttpPatchAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Patch请求 20 | /// 21 | /// 请求绝对或相对路径 22 | public HttpPatchAttribute(string? path) 23 | : base(HttpMethod.Patch, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpPostAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Post请求 7 | /// 8 | public class HttpPostAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Post请求 12 | /// 13 | public HttpPostAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Post请求 20 | /// 21 | /// 请求绝对或相对路径 22 | public HttpPostAttribute(string? path) 23 | : base(HttpMethod.Post, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ActionAttributes/HttpPutAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示Put请求 7 | /// 8 | public class HttpPutAttribute : HttpMethodAttribute 9 | { 10 | /// 11 | /// Put请求 12 | /// 13 | public HttpPutAttribute() 14 | : this(path: null) 15 | { 16 | } 17 | 18 | /// 19 | /// Put请求 20 | /// 21 | /// 请求绝对或相对路径 22 | public HttpPutAttribute(string? path) 23 | : base(HttpMethod.Put, path) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/FilterAttributes/ApiFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// ApiAction的过滤器抽象特性 8 | /// 9 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 10 | public abstract class ApiFilterAttribute : Attribute, IApiFilterAttribute 11 | { 12 | /// 13 | /// 获取或设置是否启用 14 | /// 15 | public bool Enable { get; set; } = true; 16 | 17 | /// 18 | /// 获取或设置执行排序索引 19 | /// 20 | public int OrderIndex { get; set; } 21 | 22 | /// 23 | /// 获取本类型是否允许在接口与方法上重复 24 | /// 25 | public bool AllowMultiple => this.GetType().IsAllowMultiple(); 26 | 27 | /// 28 | /// 请求前 29 | /// 30 | /// 上下文 31 | /// 32 | public abstract Task OnRequestAsync(ApiRequestContext context); 33 | 34 | 35 | /// 36 | /// 响应后 37 | /// 38 | /// 上下文 39 | /// 40 | public abstract Task OnResponseAsync(ApiResponseContext context); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ICharSetable.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Attributes 2 | { 3 | /// 4 | /// 定义支持自定义编码的配置 5 | /// 6 | interface ICharSetable 7 | { 8 | /// 9 | /// 获取或设置编码名称 10 | /// 11 | string CharSet { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ICollectionFormatable.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Attributes 2 | { 3 | /// 4 | /// 定义支持集合序列化方式的接口 5 | /// 6 | interface ICollectionFormatable 7 | { 8 | /// 9 | /// 获取或设置集合格式化方式 10 | /// 11 | CollectionFormat CollectionFormat { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/ApiParameterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示请求参数抽象特性 8 | /// 9 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] 10 | public abstract class ApiParameterAttribute : Attribute, IApiParameterAttribute 11 | { 12 | /// 13 | /// http请求之前 14 | /// 15 | /// 上下文 16 | /// 17 | public abstract Task OnRequestAsync(ApiParameterContext context); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/FormDataTextAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示参数值作为 multipart/form-data 表单的一个文本项 8 | /// 9 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] 10 | public partial class FormDataTextAttribute : IApiParameterAttribute 11 | { 12 | /// 13 | /// 表示参数值作为 multipart/form-data 表单的一个文本项 14 | /// 15 | [AttributeCtorUsage(AttributeTargets.Parameter)] 16 | public FormDataTextAttribute() 17 | { 18 | } 19 | 20 | /// 21 | /// http请求之前 22 | /// 23 | /// 上下文 24 | /// 25 | public Task OnRequestAsync(ApiParameterContext context) 26 | { 27 | var fieldName = context.ParameterName; 28 | var fieldValue = context.ParameterValue?.ToString(); 29 | context.HttpContext.RequestMessage.AddFormDataText(fieldName, fieldValue); 30 | return Task.CompletedTask; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/FormFieldAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示参数值作为 x-www-form-urlencoded 的字段 8 | /// 9 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] 10 | public partial class FormFieldAttribute : IApiParameterAttribute 11 | { 12 | /// 13 | /// 表示参数值作为 x-www-form-urlencoded 的字段 14 | /// 15 | [AttributeCtorUsage(AttributeTargets.Parameter)] 16 | public FormFieldAttribute() 17 | { 18 | } 19 | 20 | /// 21 | /// http请求之前 22 | /// 23 | /// 上下文 24 | /// 25 | public async Task OnRequestAsync(ApiParameterContext context) 26 | { 27 | var fieldName = context.ParameterName; 28 | var fieldValue = context.ParameterValue?.ToString(); 29 | await context.HttpContext.RequestMessage.AddFormFieldAsync(fieldName, fieldValue).ConfigureAwait(false); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/JsonFormDataTextAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示参数值序列化为 json 并作为 multipart/form-data 表单的一个文本项 8 | /// 9 | public class JsonFormDataTextAttribute : ApiParameterAttribute 10 | { 11 | /// 12 | /// 执行前 13 | /// 14 | /// 上下文 15 | /// 16 | [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] 17 | [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] 18 | public override Task OnRequestAsync(ApiParameterContext context) 19 | { 20 | var fieldName = context.ParameterName; 21 | var fieldValue = context.SerializeToJsonString(); 22 | context.HttpContext.RequestMessage.AddFormDataText(fieldName, fieldValue); 23 | return Task.CompletedTask; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/JsonFormFieldAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示参数值序列化为 json 并作为 x-www-form-urlencoded 的字段 8 | /// 9 | public class JsonFormFieldAttribute : ApiParameterAttribute 10 | { 11 | /// 12 | /// 执行前 13 | /// 14 | /// 上下文 15 | /// 16 | [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] 17 | [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] 18 | public override async Task OnRequestAsync(ApiParameterContext context) 19 | { 20 | var fieldName = context.ParameterName; 21 | var fieldValue = context.SerializeToJsonString(); 22 | await context.HttpContext.RequestMessage.AddFormFieldAsync(fieldName, fieldValue).ConfigureAwait(false); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/RawFormContentAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using WebApiClientCore.HttpContents; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示将参数值的已编码原始表单内容作为请求内容 8 | /// 9 | public class RawFormContentAttribute : HttpContentAttribute 10 | { 11 | /// 12 | /// 设置参数到 http 请求内容 13 | /// 14 | /// 上下文 15 | /// 16 | protected override async Task SetHttpContentAsync(ApiParameterContext context) 17 | { 18 | var content = context.HttpContext.RequestMessage.Content; 19 | var formContent = await FormContent.ParseAsync(content).ConfigureAwait(false); 20 | formContent.AddForm(context.ParameterValue?.ToString()); 21 | context.HttpContext.RequestMessage.Content = formContent; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/RawJsonContentAttribute.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.HttpContents; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示将参数的文本内容作为请求 json 内容 7 | /// 8 | public class RawJsonContentAttribute : RawStringContentAttribute 9 | { 10 | /// 11 | /// 将参数的文本内容作为请求 json 内容 12 | /// 13 | public RawJsonContentAttribute() 14 | : base(JsonContent.MediaType) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ParameterAttributes/RawXmlContentAttribute.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.HttpContents; 2 | 3 | namespace WebApiClientCore.Attributes 4 | { 5 | /// 6 | /// 表示将参数的文本内容作为请求 xml 内容 7 | /// 8 | public class RawXmlContentAttribute : RawStringContentAttribute 9 | { 10 | /// 11 | /// 将参数的文本内容作为请求 xml 内容 12 | /// 13 | public RawXmlContentAttribute() 14 | : base(XmlContent.MediaType) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ReturnAttributes/CustomValueReturnAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Attributes 2 | { 3 | /// 4 | /// 表示将结果设置为返回类型的自定义值抽象特性 5 | /// 6 | public abstract class CustomValueReturnAttribute : SpecialReturnAttribute 7 | { 8 | /// 9 | /// 将结果设置为返回类型的自定义值抽象特性 10 | /// 11 | public CustomValueReturnAttribute() 12 | : base() 13 | { 14 | } 15 | 16 | /// 17 | /// 将结果设置为返回类型的自定义值抽象特性 18 | /// 19 | /// accept的质比 20 | public CustomValueReturnAttribute(double acceptQuality) 21 | : base(acceptQuality) 22 | { 23 | } 24 | 25 | /// 26 | /// 指示是否可以设置结果 27 | /// 28 | /// 上下文 29 | /// 30 | protected sealed override bool CanSetResult(ApiResponseContext context) 31 | { 32 | return context.ActionDescriptor.Return.DataType.IsRawType == false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WebApiClientCore/Attributes/ReturnAttributes/NoneReturnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | 4 | namespace WebApiClientCore.Attributes 5 | { 6 | /// 7 | /// 表示响应状态为204时将结果设置为返回类型的默认值特性 8 | /// 9 | public sealed class NoneReturnAttribute : DefaultValueReturnAttribute 10 | { 11 | /// 12 | /// 响应状态为204时将结果设置为返回类型的默认值特性 13 | /// 14 | public NoneReturnAttribute() 15 | : base() 16 | { 17 | } 18 | 19 | /// 20 | /// 响应状态为204时将结果设置为返回类型的默认值特性 21 | /// 22 | /// accept的质比 23 | public NoneReturnAttribute(double acceptQuality) 24 | : base(acceptQuality) 25 | { 26 | } 27 | 28 | /// 29 | /// 指示是否将结果设置为返回类型的默认值 30 | /// 31 | /// 响应消息 32 | /// 33 | protected override bool CanUseDefaultValue(HttpResponseMessage responseMessage) 34 | { 35 | if (responseMessage.StatusCode == HttpStatusCode.NoContent) 36 | { 37 | return true; 38 | } 39 | 40 | return responseMessage.IsSuccessStatusCode && responseMessage.Content.Headers.ContentLength == 0; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /WebApiClientCore/BuildinExtensions/HttpContentExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiClientCore 9 | { 10 | /// 11 | /// HttpContent扩展 12 | /// 13 | static class HttpContentExtensions 14 | { 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static Task ReadAsByteArrayAsync(this HttpContent httpContent, CancellationToken _) 17 | { 18 | return httpContent.ReadAsByteArrayAsync(); 19 | } 20 | 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | public static Task ReadAsStreamAsync(this HttpContent httpContent, CancellationToken _) 23 | { 24 | return httpContent.ReadAsStreamAsync(); 25 | } 26 | 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public static Task ReadAsStringAsync(this HttpContent httpContent, CancellationToken _) 29 | { 30 | return httpContent.ReadAsStringAsync(); 31 | } 32 | } 33 | } 34 | #endif -------------------------------------------------------------------------------- /WebApiClientCore/CodeAnalysis/DynamicDependencyAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// 表示动态依赖属性 6 | /// 7 | [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] 8 | sealed class DynamicDependencyAttribute : Attribute 9 | { 10 | /// 11 | /// 获取或设置动态访问的成员类型 12 | /// 13 | public DynamicallyAccessedMemberTypes MemberTypes { get; } 14 | 15 | /// 16 | /// 获取或设置依赖的类型 17 | /// 18 | public Type? Type { get; } 19 | 20 | /// 21 | /// 初始化 类的新实例 22 | /// 23 | /// 动态访问的成员类型 24 | /// 依赖的类型 25 | public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) 26 | { 27 | this.MemberTypes = memberTypes; 28 | this.Type = type; 29 | } 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /WebApiClientCore/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// Specifies that the members accessed dynamically at runtime are considered used. 6 | /// 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, Inherited = false)] 8 | sealed class DynamicallyAccessedMembersAttribute : Attribute 9 | { 10 | /// 11 | /// Gets the types of dynamically accessed members. 12 | /// 13 | public DynamicallyAccessedMemberTypes MemberTypes { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with the specified member types. 17 | /// 18 | /// The types of dynamically accessed members. 19 | public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) 20 | { 21 | this.MemberTypes = memberTypes; 22 | } 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /WebApiClientCore/CodeAnalysis/RequiresDynamicCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 || NET5_0 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | /// 5 | /// Specifies that the attributed class, constructor, or method requires dynamic code. 6 | /// 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] 8 | sealed class RequiresDynamicCodeAttribute : Attribute 9 | { 10 | /// 11 | /// Gets the message associated with the requirement for dynamic code. 12 | /// 13 | public string Message { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with the specified message. 17 | /// 18 | /// The message associated with the requirement for dynamic code. 19 | public RequiresDynamicCodeAttribute(string message) 20 | { 21 | this.Message = message; 22 | } 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /WebApiClientCore/CodeAnalysis/RequiresUnreferencedCodeAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_1 2 | namespace System.Diagnostics.CodeAnalysis 3 | { 4 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] 5 | sealed class RequiresUnreferencedCodeAttribute : Attribute 6 | { 7 | /// 8 | /// 获取或设置对于未引用代码的要求的消息。 9 | /// 10 | public string Message { get; } 11 | 12 | /// 13 | /// 初始化 类的新实例。 14 | /// 15 | /// 对于未引用代码的要求的消息。 16 | public RequiresUnreferencedCodeAttribute(string message) 17 | { 18 | this.Message = message; 19 | } 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /WebApiClientCore/CollectionFormat.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 集合格式化方式 5 | /// 6 | public enum CollectionFormat : byte 7 | { 8 | /// 9 | /// 逗号分隔 10 | /// value1,value2 11 | /// 12 | Csv, 13 | 14 | /// 15 | /// 空格分隔 16 | /// value1 value2 17 | /// 18 | Ssv, 19 | 20 | /// 21 | /// 反斜杠分隔 22 | /// value1\value2 23 | /// 24 | Tsv, 25 | 26 | /// 27 | /// 竖线分隔 28 | /// value1|value2 29 | /// 30 | Pipes, 31 | 32 | /// 33 | /// 单属性可以取多个值 34 | /// 35 | Multi 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebApiClientCore/DependencyInjection/IWebApiClientBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.DependencyInjection 2 | { 3 | /// 4 | /// WebApiClient全局配置的Builder接口 5 | /// 6 | public interface IWebApiClientBuilder 7 | { 8 | /// 9 | /// 获取服务集合 10 | /// 11 | IServiceCollection Services { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore/Disposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 表示支持Dispose的抽象基础类 7 | /// 8 | public abstract class Disposable : IDisposable 9 | { 10 | /// 11 | /// 获取对象是否已释放 12 | /// 13 | public bool IsDisposed { get; private set; } 14 | 15 | /// 16 | /// 关闭和释放所有相关资源 17 | /// 18 | public void Dispose() 19 | { 20 | if (this.IsDisposed == false) 21 | { 22 | this.Dispose(true); 23 | GC.SuppressFinalize(this); 24 | } 25 | this.IsDisposed = true; 26 | } 27 | 28 | /// 29 | /// 析构函数 30 | /// 31 | ~Disposable() 32 | { 33 | this.Dispose(false); 34 | } 35 | 36 | /// 37 | /// 释放资源 38 | /// 39 | /// 是否也释放托管资源 40 | protected abstract void Dispose(bool disposing); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 表示接口异常基础抽象类 7 | /// 8 | public abstract class ApiException : Exception 9 | { 10 | /// 11 | /// 接口异常基础类 12 | /// 13 | public ApiException() 14 | : base() 15 | { 16 | } 17 | 18 | /// 19 | /// 接口异常基础类 20 | /// 21 | /// 异常消息 22 | public ApiException(string? message) 23 | : base(message) 24 | { 25 | } 26 | 27 | /// 28 | /// 接口异常基础类 29 | /// 30 | /// 异常消息 31 | /// 内部异常 32 | public ApiException(string? message, Exception? inner) 33 | : base(message, inner) 34 | { 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ApiInvalidConfigException.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Exceptions 2 | { 3 | /// 4 | /// 表示接口无效的配置异常 5 | /// 6 | public class ApiInvalidConfigException : ApiException 7 | { 8 | /// 9 | /// 接口无效的配置异常 10 | /// 11 | /// 提示信息 12 | public ApiInvalidConfigException(string? message) : 13 | base(message) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ApiResultNotMatchException.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Exceptions 2 | { 3 | /// 4 | /// 表示接口的结果不匹配异常 5 | /// 6 | public class ApiResultNotMatchException : ApiException 7 | { 8 | /// 9 | /// 获取结果值 10 | /// 11 | public object? Result { get; } 12 | 13 | /// 14 | /// 接口的结果不匹配异常 15 | /// 16 | /// 消息 17 | /// 结果值 18 | public ApiResultNotMatchException(string? message, object? result) 19 | : base(message) 20 | { 21 | this.Result = result; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ApiRetryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 表示接口请求重试异常 7 | /// 8 | public class ApiRetryException : ApiException 9 | { 10 | /// 11 | /// 获取重试的最大次数 12 | /// 13 | public int MaxRetryCount { get; } 14 | 15 | /// 16 | /// 接口请求重试异常 17 | /// 18 | /// 重试的最大次数 19 | /// 内部异常 20 | public ApiRetryException(int maxRetryCount, Exception? inner) 21 | : base(Resx.outof_MaxLimited.Format(maxRetryCount), inner) 22 | { 23 | this.MaxRetryCount = maxRetryCount; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ApiReturnNotSupportedException.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Exceptions 2 | { 3 | /// 4 | /// 5 | /// 6 | public class ApiReturnNotSupportedException : ApiReturnNotSupportedExteption 7 | { 8 | /// 9 | /// 10 | /// 11 | /// 12 | public ApiReturnNotSupportedException(ApiResponseContext context) 13 | : base(context) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/HttpContentBufferedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 表示HttpContent的数据已经缓存的异常 7 | /// 8 | public class HttpContentBufferedException : Exception 9 | { 10 | /// 11 | /// HttpContent的数据已经缓存的异常 12 | /// 13 | public HttpContentBufferedException() 14 | : this(Resx.httpContent_isBuffered) 15 | { 16 | } 17 | 18 | /// 19 | /// HttpContent的数据已经缓存的异常 20 | /// 21 | /// 提示消息 22 | public HttpContentBufferedException(string message) 23 | : base(message) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/IStatusCodeException.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 可获取StatusCode的异常 7 | /// 8 | interface IStatusCodeException 9 | { 10 | /// 11 | /// 获取响应状态码 12 | /// 13 | HttpStatusCode? GetStatusCode(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ProxyTypeCreateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 表示代理类创建异常 7 | /// 8 | public class ProxyTypeCreateException : ProxyTypeException 9 | { 10 | /// 11 | /// 代理类创建异常 12 | /// 13 | /// 接口类型 14 | public ProxyTypeCreateException(Type interfaceType) 15 | : base(interfaceType) 16 | { 17 | } 18 | 19 | /// 20 | /// 代理类创建异常 21 | /// 22 | /// 接口类型 23 | /// 提示消息 24 | public ProxyTypeCreateException(Type interfaceType, string? message) 25 | : base(interfaceType, message) 26 | { 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/ProxyTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 表示代理类异常 7 | /// 8 | public class ProxyTypeException : Exception 9 | { 10 | /// 11 | /// 接口类型 12 | /// 13 | public Type InterfaceType { get; } 14 | 15 | /// 16 | /// 代理类异常 17 | /// 18 | /// 接口类型 19 | public ProxyTypeException(Type interfaceType) 20 | : this(interfaceType, null) 21 | { 22 | } 23 | 24 | /// 25 | /// 代理类创建异常 26 | /// 27 | /// 接口类型 28 | /// 提示消息 29 | public ProxyTypeException(Type interfaceType, string? message) 30 | : base(message) 31 | { 32 | this.InterfaceType = interfaceType; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WebApiClientCore/Exceptions/TypeInstanceCreateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiClientCore.Exceptions 4 | { 5 | /// 6 | /// 表示类型实例创建异常 7 | /// 8 | public class TypeInstanceCreateException : Exception 9 | { 10 | /// 11 | /// 实例类型 12 | /// 13 | public Type InstanceType { get; } 14 | 15 | /// 16 | /// 类型实例创建异常 17 | /// 18 | /// 实例类型 19 | public TypeInstanceCreateException(Type instanceType) 20 | { 21 | this.InstanceType = instanceType; 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore/HttpApiProxyClassAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace WebApiClientCore 5 | { 6 | /// 7 | /// 表示HttpApi代理类的特性 8 | /// 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 11 | public sealed class HttpApiProxyClassAttribute : Attribute 12 | { 13 | /// 14 | /// 获取所代理的目标接口 15 | /// 16 | public Type HttpApiType { get; } 17 | 18 | /// 19 | /// 代理类型的特性 20 | /// 21 | /// 所代理的目标接口 22 | public HttpApiProxyClassAttribute(Type httpApiType) 23 | { 24 | this.HttpApiType = httpApiType; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApiClientCore/HttpContents/FormDataTextContent.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Net.Http.Headers; 3 | 4 | namespace WebApiClientCore.HttpContents 5 | { 6 | /// 7 | /// 表示 multipart/form-data 文本内容 8 | /// 9 | public class FormDataTextContent : StringContent 10 | { 11 | /// 12 | /// multipart/form-data 文本内容 13 | /// 14 | /// 键值对 15 | public FormDataTextContent(KeyValue keyValue) 16 | : this(keyValue.Key, keyValue.Value) 17 | { 18 | } 19 | 20 | /// 21 | /// multipart/form-data 文本内容 22 | /// 23 | /// 名称 24 | /// 文本 25 | public FormDataTextContent(string name, string? value) 26 | : base(value ?? string.Empty) 27 | { 28 | this.Headers.ContentType = null; 29 | if (this.Headers.ContentDisposition == null) 30 | { 31 | var disposition = new ContentDispositionHeaderValue("form-data") 32 | { 33 | Name = $"\"{name}\"" 34 | }; 35 | this.Headers.ContentDisposition = disposition; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /WebApiClientCore/HttpContents/ICustomHttpContentConvertable.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiClientCore.HttpContents 4 | { 5 | /// 6 | /// 定义支持转换为自定义HttpContent的接口 7 | /// 8 | interface ICustomHttpContentConvertable 9 | { 10 | /// 11 | /// 转换为自定义内容的HttpContent 12 | /// 13 | /// 14 | HttpContent ToCustomHttpContext(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiClientCore/HttpContents/XmlContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http.Headers; 3 | using System.Text; 4 | 5 | namespace WebApiClientCore.HttpContents 6 | { 7 | /// 8 | /// 表示 xml 内容 9 | /// 10 | public class XmlContent : BufferContent 11 | { 12 | private static readonly MediaTypeHeaderValue defaultMediaType = new(MediaType) { CharSet = Encoding.UTF8.WebName }; 13 | 14 | /// 15 | /// 获取对应的ContentType 16 | /// 17 | public static string MediaType => "application/xml"; 18 | 19 | /// 20 | /// xml内容 21 | /// 22 | /// xml内容 23 | /// 编码 24 | public XmlContent(ReadOnlySpan xml, Encoding encoding) 25 | { 26 | encoding.GetBytes(xml, this); 27 | this.Headers.ContentType = encoding == Encoding.UTF8 28 | ? defaultMediaType : 29 | new MediaTypeHeaderValue(MediaType) { CharSet = encoding.WebName }; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /WebApiClientCore/HttpMessageHandlers/SetReason.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.HttpMessageHandlers 2 | { 3 | /// 4 | /// 设置授权原因 5 | /// 6 | public enum SetReason 7 | { 8 | /// 9 | /// 用于发送请求 10 | /// 11 | ForSend, 12 | 13 | /// 14 | /// 用于重试发送请求 15 | /// 16 | ForResend 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WebApiClientCore/IChunkedable.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 是否允许chunked传输 5 | /// 6 | interface IChunkedable 7 | { 8 | /// 9 | /// 获取或设置是否允许 chunked 传输 10 | /// 11 | bool AllowChunked { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore/IHandleTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore 5 | { 6 | /// 7 | /// 定义异常处理的行为 8 | /// 9 | /// 10 | public interface IHandleTask : ITask 11 | { 12 | /// 13 | /// 当捕获到异常时返回指定结果 14 | /// 15 | /// 16 | /// 获取结果 17 | /// 18 | IHandleTask WhenCatch(Func func) where TException : Exception; 19 | 20 | /// 21 | /// 当捕获到异常时返回指定结果 22 | /// 23 | /// 24 | /// 获取结果 25 | /// 26 | IHandleTask WhenCatch(Func func) where TException : Exception; 27 | 28 | /// 29 | /// 当捕获到异常时返回指定结果 30 | /// 31 | /// 32 | /// 获取结果 33 | /// 34 | IHandleTask WhenCatchAsync(Func> func) where TException : Exception; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WebApiClientCore/IHttpApi.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore 2 | { 3 | /// 4 | /// 定义HttpApi标识的接口 5 | /// 6 | /// 7 | /// • 1.x版本,继承此接口才获得语法分析提示 8 | /// • 2.0以后的版本,不继承此接口也获得语法分析提示 9 | /// 10 | public interface IHttpApi 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiClientCore/ITask.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace WebApiClientCore 4 | { 5 | /// 6 | /// 定义返回结果的行为 7 | /// 8 | /// 9 | public interface ITask 10 | { 11 | /// 12 | /// 返回新创建的请求任务的等待器 13 | /// 14 | /// 15 | TaskAwaiter GetAwaiter(); 16 | 17 | /// 18 | /// 返回新创建的请求任务的等待器 19 | /// 20 | /// 试图继续回夺取的原始上下文,则为 true;否则为 false 21 | /// 22 | ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/DefaultApiActionDescriptorProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | 5 | namespace WebApiClientCore.Implementations 6 | { 7 | /// 8 | /// ApiActionDescriptor提供者的接口 9 | /// 10 | public class DefaultApiActionDescriptorProvider : IApiActionDescriptorProvider 11 | { 12 | /// 13 | /// 创建Action描述 14 | /// 15 | /// 接口的方法 16 | /// 接口类型 17 | public virtual ApiActionDescriptor CreateActionDescriptor( 18 | MethodInfo method, 19 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) 20 | { 21 | return new DefaultApiActionDescriptor(method, interfaceType); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/HttpApiInterceptor.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Implementations 2 | { 3 | /// 4 | /// 表示接口方法的拦截器 5 | /// 6 | sealed class HttpApiInterceptor : IHttpApiInterceptor 7 | { 8 | /// 9 | /// 服务上下文 10 | /// 11 | private readonly HttpClientContext context; 12 | 13 | /// 14 | /// 接口方法的拦截器 15 | /// 16 | /// httpClient上下文 17 | public HttpApiInterceptor(HttpClientContext context) 18 | { 19 | this.context = context; 20 | } 21 | 22 | /// 23 | /// 拦截方法的调用 24 | /// 25 | /// action执行器 26 | /// 方法的参数集合 27 | /// 28 | public object Intercept(ApiActionInvoker actionInvoker, object?[] arguments) 29 | { 30 | return actionInvoker.Invoke(this.context, arguments); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/IITaskReturnConvertable.cs: -------------------------------------------------------------------------------- 1 | namespace WebApiClientCore.Implementations 2 | { 3 | /// 4 | /// 定义可以转换为ITask返回声明的Action执行器 5 | /// 6 | interface IITaskReturnConvertable 7 | { 8 | /// 9 | /// 转换为ITask返回声明的Action执行器 10 | /// 11 | /// 12 | ApiActionInvoker ToITaskReturnActionInvoker(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/JsonFirstApiActionDescriptorProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | 5 | namespace WebApiClientCore.Implementations 6 | { 7 | /// 8 | /// 当非GET或HEAD请求的缺省参数特性声明时 9 | /// 为复杂参数类型的参数应用JsonContentAttribute 10 | /// 11 | public class JsonFirstApiActionDescriptorProvider : IApiActionDescriptorProvider 12 | { 13 | /// 14 | /// 创建Action描述 15 | /// 16 | /// 接口的方法 17 | /// 接口类型 18 | public ApiActionDescriptor CreateActionDescriptor( 19 | MethodInfo method, 20 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) 21 | { 22 | return new JsonFirstApiActionDescriptor(method, interfaceType); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/Tasks/ActionTask.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WebApiClientCore.Implementations.Tasks 4 | { 5 | /// 6 | /// 表示Api请求的任务 7 | /// 8 | /// 结果类型 9 | sealed class ActionTask : TaskBase 10 | { 11 | private readonly DefaultApiActionInvoker invoker; 12 | private readonly HttpClientContext context; 13 | private readonly object?[] arguments; 14 | 15 | /// 16 | /// Api请求的任务 17 | /// 18 | /// 19 | /// 20 | /// 21 | public ActionTask(DefaultApiActionInvoker invoker, HttpClientContext context, object?[] arguments) 22 | { 23 | this.invoker = invoker; 24 | this.context = context; 25 | this.arguments = arguments; 26 | } 27 | 28 | /// 29 | /// 创建新的请求任务 30 | /// 31 | /// 32 | protected override Task InvokeAsync() 33 | { 34 | return this.invoker.InvokeAsync(this.context, this.arguments); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/Tasks/TaskBase.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Implementations.Tasks 5 | { 6 | /// 7 | /// 表示Task抽象类 8 | /// 9 | /// 10 | abstract class TaskBase : ITask 11 | { 12 | /// 13 | /// 返回新创建的请求任务的等待器 14 | /// 15 | /// 16 | public TaskAwaiter GetAwaiter() 17 | { 18 | return this.InvokeAsync().GetAwaiter(); 19 | } 20 | 21 | /// 22 | /// 返回新创建的请求任务的等待器 23 | /// 24 | /// 试图继续回夺取的原始上下文,则为 true;否则为 false 25 | /// 26 | public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) 27 | { 28 | return this.InvokeAsync().ConfigureAwait(continueOnCapturedContext); 29 | } 30 | 31 | /// 32 | /// 创建新的请求任务 33 | /// 34 | /// 35 | protected abstract Task InvokeAsync(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/TypeAttributes/ApiParameterTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace WebApiClientCore.Implementations.TypeAttributes 5 | { 6 | /// 7 | /// 表示参数类型为IApiParameter的处理特性 8 | /// 9 | sealed class ApiParameterTypeAttribute : IApiParameterAttribute 10 | { 11 | /// 12 | /// http请求之前 13 | /// 14 | /// 上下文 15 | /// 16 | public async Task OnRequestAsync(ApiParameterContext context) 17 | { 18 | if (context.ParameterValue is IApiParameter parameter) 19 | { 20 | await parameter.OnRequestAsync(context).ConfigureAwait(false); 21 | } 22 | else if (context.ParameterValue is IEnumerable parameters) 23 | { 24 | foreach (var item in parameters) 25 | { 26 | await item.OnRequestAsync(context).ConfigureAwait(false); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebApiClientCore/Implementations/TypeAttributes/CancellationTokenTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiClientCore.Implementations.TypeAttributes 6 | { 7 | /// 8 | /// 表示参数类型为CancellationToken处理特性 9 | /// 10 | sealed class CancellationTokenTypeAttribute : IApiParameterAttribute 11 | { 12 | /// 13 | /// http请求之前 14 | /// 15 | /// 上下文 16 | /// 17 | public Task OnRequestAsync(ApiParameterContext context) 18 | { 19 | if (context.ParameterValue is CancellationToken token) 20 | { 21 | if (token.Equals(CancellationToken.None) == false) 22 | { 23 | context.HttpContext.CancellationTokens.Add(token); 24 | } 25 | } 26 | else if (context.ParameterValue is IEnumerable tokens) 27 | { 28 | foreach (var item in tokens) 29 | { 30 | if (item.Equals(CancellationToken.None) == false) 31 | { 32 | context.HttpContext.CancellationTokens.Add(item); 33 | } 34 | } 35 | } 36 | return Task.CompletedTask; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /WebApiClientCore/Serialization/JsonConverters/JsonLocalDateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace WebApiClientCore.Serialization.JsonConverters 5 | { 6 | /// 7 | /// 表示DateTime和DateTimeOffset的Json转换器 8 | /// 9 | [Obsolete("请使用JsonDateTimeConverter替代")] 10 | public class JsonLocalDateTimeConverter : JsonDateTimeConverter 11 | { 12 | /// 13 | /// 获取ISO8601格式的实例 14 | /// 15 | public static JsonLocalDateTimeConverter Default { get; } = new JsonLocalDateTimeConverter(); 16 | 17 | /// 18 | /// 获取本设备的时间格式的实例 19 | /// 20 | public static JsonLocalDateTimeConverter LocalMachine { get; } = new JsonLocalDateTimeConverter($"{DateTimeFormatInfo.CurrentInfo.ShortDatePattern} {DateTimeFormatInfo.CurrentInfo.LongTimePattern}"); 21 | 22 | 23 | /// 24 | /// DateTime和DateTimeOffset的Json转换器 25 | /// 26 | /// 日期时间格式 27 | /// 28 | public JsonLocalDateTimeConverter(string dateTimeFormat = "O") 29 | : base(dateTimeFormat) 30 | { 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /WebApiClientCore/Sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/WebApiClientCore/Sign.snk -------------------------------------------------------------------------------- /WebApiClientCore/System.Net.Http/HttpProgress.cs: -------------------------------------------------------------------------------- 1 | namespace System.Net.Http 2 | { 3 | /// 4 | /// 表示 http 进度 5 | /// 6 | public class HttpProgress 7 | { 8 | /// 9 | /// 获取文件总字节数 10 | /// 11 | public long? FileSize { get; } 12 | 13 | /// 14 | /// 获取当前接收到的字节数 15 | /// 16 | public long RecvSize { get; } 17 | 18 | /// 19 | /// 获取是否已完成 20 | /// 21 | public bool IsCompleted { get; } 22 | 23 | /// 24 | /// http进度 25 | /// 26 | /// 文件总字节数 27 | /// 当前完成的字节数 28 | /// 是否已完成 29 | public HttpProgress(long? fileSize, long recvSize, bool isCompleted) 30 | { 31 | this.FileSize = fileSize; 32 | this.RecvSize = recvSize; 33 | this.IsCompleted = isCompleted; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WebApiClientCore/System.Net.Http/HttpRequestMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using WebApiClientCore.Internals; 2 | 3 | namespace System.Net.Http 4 | { 5 | /// 6 | /// HttpRequestMessage扩展 7 | /// 8 | public static class HttpRequestMessageExtensions 9 | { 10 | /// 11 | /// 读取请求头 12 | /// 13 | /// 14 | /// 15 | public static string GetHeadersString(this HttpRequestMessage request) 16 | { 17 | var uri = request.RequestUri; 18 | Span buffer = stackalloc char[4 * 1024]; 19 | var builder = new ValueStringBuilder(buffer); 20 | 21 | if (uri != null && uri.IsAbsoluteUri) 22 | { 23 | const string host = "Host"; 24 | builder.AppendLine($"{request.Method} {uri.PathAndQuery} HTTP/{request.Version}"); 25 | 26 | if (request.Headers.Contains(host) == false) 27 | { 28 | builder.AppendLine($"{host}: {uri.Authority}"); 29 | } 30 | } 31 | 32 | builder.Append(request.Headers.ToString()); 33 | if (request.Content != null) 34 | { 35 | builder.Append(request.Content.Headers.ToString()); 36 | } 37 | 38 | return builder.ToString(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/guide/4_data-validation.md: -------------------------------------------------------------------------------- 1 | # 数据验证 2 | 3 | 使用 ValidationAttribute 的子类特性来验证请求参数值和响应结果。 4 | 5 | ## 参数值验证 6 | 7 | ```csharp 8 | public interface IUserApi 9 | { 10 | [HttpGet("api/users/{email}")] 11 | Task GetAsync( 12 | [EmailAddress, Required] // 这些验证特性用于请求前验证此参数 13 | string email); 14 | } 15 | ``` 16 | 17 | ## 请求或响应模型验证 18 | 19 | 请求和相应用到的 User 的两个属性值都得到验证。 20 | 21 | ```csharp 22 | public interface IUserApi 23 | { 24 | [HttpPost("api/users")] 25 | Task PostAsync([Required][JsonContent] User user); 26 | } 27 | 28 | public class User 29 | { 30 | [Required] 31 | [StringLength(10, MinimumLength = 1)] 32 | public string Account { get; set; } 33 | 34 | [Required] 35 | [StringLength(10, MinimumLength = 1)] 36 | public string Password { get; set; } 37 | } 38 | ``` 39 | 40 | ## 关闭数据验证功能 41 | 42 | 数据验证功能默认是开启的,可以在接口的 HttpApiOptions 配置关闭数据验证功能。 43 | 44 | ```csharp 45 | public void ConfigureServices(IServiceCollection services) 46 | { 47 | services.AddHttpApi().ConfigureHttpApi(o => 48 | { 49 | // 关闭数据验证功能,即使打了验证特性也不验证。 50 | o.UseParameterPropertyValidate = false; 51 | o.UseReturnValuePropertyValidate = false; 52 | }); 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/guide/7_json-net-extension.md: -------------------------------------------------------------------------------- 1 | # Json.NET 扩展 2 | 3 | 使用 WebApiClientCore.Extensions.NewtonsoftJson 扩展,轻松支持 Newtonsoft 的 `Json.NET` 来序列化和反序列化 json。 4 | 5 | ## 配置[可选] 6 | 7 | ```csharp 8 | // ConfigureNewtonsoftJson 9 | services.AddHttpApi().ConfigureNewtonsoftJson(o => 10 | { 11 | o.JsonSerializeOptions.NullValueHandling = NullValueHandling.Ignore; 12 | }); 13 | ``` 14 | 15 | ## 声明特性 16 | 17 | 使用[JsonNetReturn]替换内置的[JsonReturn],[JsonNetContent]替换内置[JsonContent] 18 | 19 | ```csharp 20 | /// 21 | /// 用户操作接口 22 | /// 23 | [JsonNetReturn] 24 | public interface IUserApi 25 | { 26 | [HttpPost("/users")] 27 | Task PostAsync([JsonNetContent] User user); 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/guide/8_jsonrpc-extension.md: -------------------------------------------------------------------------------- 1 | # JsonRpc 扩展 2 | 3 | 在极少数场景中,开发者可能遇到 JsonRpc 调用的接口,由于该协议不是很流行,WebApiClientCore 将该功能的支持作为 WebApiClientCore.Extensions.JsonRpc 扩展包提供。使用[JsonRpcMethod]修饰 Rpc 方法,使用[JsonRpcParam]修饰 Rpc 参数 4 | 即可。 5 | 6 | ## JsonRpc 声明 7 | 8 | ```csharp 9 | [HttpHost("http://localhost:5000/jsonrpc")] 10 | public interface IUserApi 11 | { 12 | [JsonRpcMethod("add")] 13 | ITask> AddAsync([JsonRpcParam] string name, [JsonRpcParam] int age, CancellationToken token = default); 14 | } 15 | ``` 16 | 17 | ## JsonRpc 数据包 18 | 19 | ```log 20 | 21 | POST /jsonrpc HTTP/1.1 22 | Host: localhost:5000 23 | User-Agent: WebApiClientCore/1.0.6.0 24 | Accept: application/json; q=0.01, application/xml; q=0.01 25 | Content-Type: application/json-rpc 26 | 27 | {"jsonrpc":"2.0","method":"add","params":["laojiu",18],"id":1} 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/guide/9_openapi-to-code.md: -------------------------------------------------------------------------------- 1 | # 将OpenApi(swagger)生成代码 2 | 3 | 使用这个工具可以将 OpenApi 的本地或远程文档解析生成 WebApiClientCore 的接口定义代码文件,`ASP.NET Core` 的 swagger json 文件也适用 4 | 5 | ## 安装工具 6 | 7 | ```shell 8 | dotnet tool install WebApiClientCore.OpenApi.SourceGenerator -g 9 | ``` 10 | 11 | ## 使用工具 12 | 13 | 运行以下命令,会将对应的 WebApiClientCore 的接口定义代码文件输出到当前目录的 output 文件夹下 14 | 15 | ```shell 16 | #举例 17 | WebApiClientCore.OpenApi.SourceGenerator -o https://petstore.swagger.io/v2/swagger.json 18 | ``` 19 | 20 | ### 命令介绍 21 | 22 | ```text 23 | -o OpenApi, --openapi=OpenApi Required. openApi的json本地文件路径或远程Uri地址 24 | -n Namespace, --namespace=Namespace 代码的命名空间,如WebApiClientCore 25 | --help Display this help screen. 26 | ``` 27 | 28 | ### 工具原理 29 | 1. 使用 NSwag 解析 OpenApi 的 json 得到 OpenApiDocument 对象 30 | 2. 使用 RazorEngine 将 OpenApiDocument 传入 cshtml 模板编译得到 html 31 | 3. 使用 XDocument 将 html 的文本代码提取,得到 WebApiClientCore 的声明式代码 32 | 4. 代码美化,输出到本地文件 33 | -------------------------------------------------------------------------------- /docs/guide/readme.md: -------------------------------------------------------------------------------- 1 | # 概览 2 | 3 | [![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) 4 | [![nuget](https://img.shields.io/nuget/v/WebApiClientCore.svg?style=flat-square)](https://www.nuget.org/packages/WebApiClientCore) 5 | [![nuget](https://img.shields.io/nuget/vpre/WebApiClientCore.svg?style=flat-square)](https://www.nuget.org/packages/WebApiClientCore) 6 | [![stats](https://img.shields.io/nuget/dt/WebApiClientCore.svg?style=flat-square)](https://www.nuget.org/stats/packages/WebApiClientCore?groupby=Version) 7 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/dotnetcore/WebApiClient/blob/master/LICENSE) 8 | 9 | [![Stargazers over time](https://starchart.cc/dotnetcore/WebApiClient.svg)](https://starchart.cc/dotnetcore/WebApiClient) 10 | 11 | ## 简介 12 | 13 | WebApiClient 有两个版本 14 | 15 | ### WebApiclientCore 16 | `WebApiclientCore` 基于`.NET Standard2.1`重新设计的新版本,与全新的`依赖注入`、`配置`、`选项`、`日志`等重新设计过的.NET 抽象 Api 完美契合,欢迎您使用、提问、贡献代码、提供创意。 17 | ### WebApiClient 18 | `WebApiClient.JIT`、`WebApiClient.AOT` 基于`.NET Standard2.0`的旧版本(额外支持`.NET Framework 4.5+`),支持`.NET Core 2.0+`,在老版本的.NET 上亦能独当一面,但我们不会继续更新它。 19 | 20 | ## 服务渠道 21 | 1. QQ 群 [825135345](https://shang.qq.com/wpa/qunwpa?idkey=c6df21787c9a774ca7504a954402c9f62b6595d1e63120eabebd6b2b93007410) 进群时请注明 **WebApiClient** 22 | 23 | 2. 反馈问题请前往 [https://github.com/dotnetcore/WebApiClient/issues](https://github.com/dotnetcore/WebApiClient/issues) -------------------------------------------------------------------------------- /docs/old/basic/attribute-scope-features.md: -------------------------------------------------------------------------------- 1 | # 特性的范围和优先级 2 | 3 | ## 特性的范围 4 | 5 | 有些特性比如`[Header]`,可以修饰于接口、方法和参数,使用不同的构造器和修饰于不同的地方产生的含义和结果是有点差别的: 6 | 7 | 修饰接口时,表示接口下的所有方法在请求前都会添加这个请求头; 8 | 修饰方法时,表示此方法在请求前添加这个请求头; 9 | 修饰参数时,表示参数的值将做为请求头的值,由调用者动态传入; 10 | 11 | ## 特性的优先级 12 | 13 | 方法级比接口级优先级高; 14 | `AllowMultiple`为`true`时,方法级和接口级都生效; 15 | `AllowMultiple`为`false`时,方法级的生效,接口级的无效; 16 | -------------------------------------------------------------------------------- /docs/old/basic/parameter-attribute.md: -------------------------------------------------------------------------------- 1 | # 参数及属性注解 2 | 3 | 这些注解特性的命名空间在WebApiClient.DataAnnotations,用于影响参数的序列化行为。 4 | 5 | ## 参数别名 6 | 7 | ```csharp 8 | public interface IMyWebApi : IHttpApi 9 | { 10 | // GET 11 | [HttpGet("http://www.mywebapi.com/webapi/user")] 12 | ITask GetUserByAccountAsync( 13 | [AliasAs("_name")] string account); 14 | } 15 | ``` 16 | 17 | ## 参数模型属性注解 18 | 19 | ```csharp 20 | public class UserInfo 21 | { 22 | public string Account { get; set; } 23 | 24 | // 别名 25 | [AliasAs("a_password")] 26 | public string Password { get; set; } 27 | 28 | // 时间格式,优先级最高 29 | [DateTimeFormat("yyyy-MM-dd")] 30 | [IgnoreWhenNull] // 值为null则忽略序列化 31 | public DateTime? BirthDay { get; set; } 32 | 33 | // 忽略序列化 34 | [IgnoreSerialized] 35 | public string Email { get; set; } 36 | 37 | // 时间格式 38 | [DateTimeFormat("yyyy-MM-dd HH:mm:ss")] 39 | public DateTime CreateTime { get; set; } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/old/basic/parameter-validation.md: -------------------------------------------------------------------------------- 1 | # 参数及参数属性输入验证 2 | 3 | 这些验证特性都有相同的基类ValidationAttribute,命名空间为System.ComponentModel.DataAnnotations,由netfx或corefx提供。 4 | 5 | ## 参数值的验证 6 | 7 | ```csharp 8 | [HttpGet("webapi/user/GetById/{id}")] 9 | ITask GetByIdAsync( 10 | [Required, StringLength(10)] string id); 11 | ``` 12 | 13 | id的参数要求必填且最大长度为10的字符串,否则抛出ValidationException的异常。 14 | 15 | ## 参数的属性值验证 16 | 17 | ```csharp 18 | public class UserInfo 19 | { 20 | [Required] 21 | [StringLength(10, MinimumLength = 1)] 22 | public string Account { get; set; } 23 | 24 | [Required] 25 | [StringLength(10, MinimumLength = 6)] 26 | public string Password { get; set; } 27 | } 28 | 29 | [HttpPut("webapi/user/UpdateWithJson")] 30 | ITask UpdateWithJsonAsync( 31 | [JsonContent] UserInfo user); 32 | ``` 33 | 34 | 当user参数不为null的情况,就会验证它的Account和Password两个属性,HttpApiConfig有个UseParameterPropertyValidate属性,设置为false就禁用验证参数的属性值。 35 | 36 | ## 两者同时验证 37 | 38 | 对于上节的例子,如果我们希望user参数值也不能为null,可以如下声明方法: 39 | 40 | ```csharp 41 | [HttpPut("webapi/user/UpdateWithJson")] 42 | ITask UpdateWithJsonAsync( 43 | [Required, JsonContent] UserInfo user); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/old/basic/patch.md: -------------------------------------------------------------------------------- 1 | # PATCH请求 2 | 3 | json patch是为客户端能够局部更新服务端已存在的资源而设计的一种标准交互,在RFC6902里有详细的介绍json patch,通俗来讲有以下几个要点: 4 | 5 | 使用HTTP PATCH请求方法; 6 | 请求body为描述多个opration的数据json内容; 7 | 请求的Content-Type为application/json-patch+json; 8 | 9 | ## WebApiClient例子 10 | 11 | ```csharp 12 | public interface IMyWebApi : IHttpApi 13 | { 14 | [HttpPatch("webapi/user")] 15 | Task PatchAsync(string id, JsonPatchDocument doc); 16 | } 17 | 18 | var doc = new JsonPatchDocument(); 19 | doc.Replace(item => item.Account, "laojiu"); 20 | doc.Replace(item => item.Email, "laojiu@qq.com"); 21 | var api = HttpApi.Create(); 22 | await api.PatchAsync("id001", doc); 23 | ``` 24 | 25 | ## Asp.net 服务端例子 26 | 27 | ```csharp 28 | [HttpPatch] 29 | public async Task Patch(string id, [FromBody] JsonPatchDocument doc) 30 | { 31 | // 此处user是从db查询获得 32 | var user = await GetUserInfoFromDbAsync(id); 33 | doc.ApplyTo(user); 34 | return user; 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/old/getting-started.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## Nuget包 4 | 5 | | 包名 | 描述 | Nuget | 6 | ---|---|--| 7 | | WebApiClient.JIT | 适用于非AOT编译的所有平台,稳定性好 | [![NuGet](https://buildstats.info/nuget/WebApiClient.JIT)](https://www.nuget.org/packages/WebApiClient.JIT) | 8 | | WebApiClient.AOT | 适用于所有平台,包括IOS和UWP,复杂依赖项目可能编译不通过 | [![NuGet](https://buildstats.info/nuget/WebApiClient.AOT)](https://www.nuget.org/packages/WebApiClient.AOT) | 9 | 10 | ## Http请求 11 | > 12 | > 接口的声明 13 | 14 | ```csharp 15 | public interface IUserApi : IHttpApi 16 | { 17 | // GET api/user?account=laojiu 18 | // Return json或xml内容 19 | [HttpGet("api/user")] 20 | ITask GetAsync(string account); 21 | 22 | // POST api/user 23 | // Body Account=laojiu&password=123456 24 | // Return json或xml内容 25 | [HttpPost("api/user")] 26 | ITask AddAsync([FormContent] UserInfo user); 27 | } 28 | ``` 29 | 30 | > 接口的配置 31 | 32 | ```csharp 33 | HttpApi.Register().ConfigureHttpApiConfig(c => 34 | { 35 | c.HttpHost = new Uri("http://www.webapiclient.com/"); 36 | c.FormatOptions.DateTimeFormat = DateTimeFormats.ISO8601_WithMillisecond; 37 | });; 38 | ``` 39 | 40 | > 接口的调用 41 | 42 | ```csharp 43 | var api = HttpApi.Resolve(); 44 | var user = new UserInfo { Account = "laojiu", Password = "123456" }; 45 | var user1 = await api.GetAsync("laojiu"); 46 | var state = await api.AddAsync(user); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/old/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## 声明的http接口为什么要继承IHttpApi接口? 4 | 5 | 一是为了方便WebApiClient库自动生成接口的代理类,相当用于标记作用;二是继承了`IHttpApi`接口,http接口代理类实例就有Dispose方法。 6 | 7 | ## http接口可以继承其它http接口吗? 8 | 9 | 可以继承,父接口的相关方法也都当作Api方法,需要注意的是,父接口的方法的接口级特性将失效,而是应用了子接口的接口级特性,所以为了方便理解,最好不要这样继承。 10 | 11 | ## 使用`[ProxyAttribute(host,port)]`代理特性前,怎么验证代理的有效性? 12 | 13 | 可以使用ProxyValidator对象的Validate方法来验证代理的有效性。 14 | 15 | ## 为什么不支持将接口方法的返回类型声明为`Task`对象而必须为`Task<>`或`ITask<>`? 16 | 17 | 这个是设计的原则,因为不管开发者关不关注返回值,Http请求要么有响应要么抛出异常,如果你不关注结果的解析,可以声明为`Task`而不去解析`HttpResponseMessage`就可以。 18 | 19 | ## 使用WebApiClient怎么下载文件? 20 | 21 | 你应该将接口返回类型声明为`ITask`。 22 | 23 | ## 接口返回类型除了声明为`ITask`,还可以声明哪些抽象的返回类型? 24 | 25 | 还可以声明为`ITask`、`ITask`和`ITask`,这些都是抽象的返回类型。 26 | 27 | ## 接口声明的参数可以为Object或某些类型的基类吗? 28 | 29 | 可以这样声明,数据还是子类的,但xml序列化会有问题,一般情况下,建议严格按照服务器的具体类型来声明参数。 30 | 31 | ## WebApiClient怎么使用同步请求 32 | 33 | WebApiClient是对HttpClient的封包,HttpClient没有提供相关的同步请求方法,所以WebApiClient也没有同步请求,不正确的阻塞ITask和Task返回值,在一些环境下很容易死锁。 34 | -------------------------------------------------------------------------------- /docs/old/readme.md: -------------------------------------------------------------------------------- 1 | # 概览 2 | 3 | ## WebApiClient.JIT 4 | 5 | 在运行时使用Emit创建Http请求接口的代理类,HttpApiClient.Create()时,在新的程序集创建了TInterface的代理类,类名与TInterface相同,命名空间也相同,由于代理类和TInterface接口不在同一程序集,所以要求TInterface为public。 6 | 7 | + 可以在项目中直接引用WebApiClient.JIT.dll就能使用; 8 | + 不适用于不支持JIT技术的平台(IOS、UWP); 9 | + 接口要求为public; 10 | 11 | ## WebApiClient.AOT 12 | 13 | 在编译过程中使用Mono.Cecil修改编译得到的程序集,向其插入Http请求接口的代理类IL指令,这一步是在AOT编译阶段之前完成。代理类型所在的程序集、模块、命名空间与接口类型的一样,其名称为$前缀的接口类型名称,使用反编译工具查看项目编译后的程序集可以看到这些代理类。 14 | 15 | + 项目必须使用nuget安装WebApiClient.AOT才能正常使用; 16 | + 没有JIT,支持的平台广泛; 17 | + 接口不要求为public,可以嵌套在类里面; 18 | -------------------------------------------------------------------------------- /docs/old/senior/diy-attribute.md: -------------------------------------------------------------------------------- 1 | # 3、自定义特性 2 | 3 | WebApiClient内置很多特性,包含接口级、方法级、参数级的,他们分别是实现了`IApiActionAttribute`接口、`IApiActionFilterAttribute`接口、`IApiParameterAttribute`接口、`IApiParameterable`接口和`IApiReturnAttribute`接口的一个或多个接口。 4 | 5 | ## 3.1 自定义IApiParameterAttribute 6 | 7 | 例如服务端要求使用`x-www-form-urlencoded`提交,由于接口设计不合理,目前要求是提交`:fieldX= {X}`的json文本&fieldY={Y}的json文本 这里`{X}`和`{Y}`都是一个多字段的Model,我们对应的接口是这样设计的: 8 | 9 | ```csharp 10 | [HttpHost("/upload")] 11 | ITask UploadAsync( 12 | [FormField][AliasAs("fieldX")] string xJson, 13 | [FormField][AliasAs("fieldY")] string yJson); 14 | ``` 15 | 16 | 显然,我们接口参数为`string`类型的范围太广,没有约束性,我们希望是这样子 17 | 18 | ```csharp 19 | [HttpHost("/upload")] 20 | ITask UploadAsync([FormFieldJson] X fieldX, [FormFieldJson] Y fieldY); 21 | ``` 22 | 23 | `[FormFieldJson]`将参数值序列化为Json并做为表单的一个字段内容 24 | 25 | ```csharp 26 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 27 | class FormFieldJson: Attribute, IApiParameterAttribute 28 | { 29 | public async Task BeforeRequestAsync(ApiActionContext context, ApiParameterDescriptor parameter) 30 | { 31 | var options = context.HttpApiConfig.FormatOptions; 32 | var json = context.HttpApiConfig.JsonFormatter.Serialize(parameter.Value, options); 33 | var fieldName = parameter.Name; 34 | await context.RequestMessage.AddFormFieldAsync(fieldName, json); 35 | } 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/old/senior/exception-retry.md: -------------------------------------------------------------------------------- 1 | # 4、异常处理和重试策略 2 | 3 | ## 4.1 try catch异常处理 4 | 5 | ```csharp 6 | try 7 | { 8 | var user = await userApi.GetByIdAsync("id001"); 9 | ... 10 | } 11 | catch (HttpStatusFailureException ex) 12 | { 13 | var error = ex.ReadAsAsync(); 14 | ... 15 | } 16 | catch (HttpApiException ex) 17 | { 18 | ... 19 | } 20 | ``` 21 | 22 | ## 4.2 Retry重试策略 23 | 24 | ```csharp 25 | try 26 | { 27 | var user1 = await userApi 28 | .GetByIdAsync("id001") 29 | .Retry(3, i => TimeSpan.FromSeconds(i)) 30 | .WhenCatch(); 31 | ... 32 | } 33 | catch (HttpStatusFailureException ex) 34 | { 35 | var error = ex.ReadAsAsync(); 36 | ... 37 | } 38 | catch (HttpApiException ex) 39 | { 40 | ... 41 | } 42 | catch(Exception ex) 43 | { 44 | ... 45 | } 46 | ``` 47 | 48 | ## 4.3 RX扩展 49 | 50 | 在一些场景中,你可能不需要使用async/await异步编程方式,WebApiClient提供了Task对象转换为IObservable对象的扩展,使用方式如下: 51 | 52 | ```csharp 53 | var unSubscriber = userApi.GetByIdAsync("id001") 54 | .Retry(3, i => TimeSpan.FromSeconds(i)) 55 | .WhenCatch(); 56 | .ToObservable().Subscribe(result => 57 | { 58 | ... 59 | }, ex => 60 | { 61 | ... 62 | }); 63 | ``` 64 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/icon.png -------------------------------------------------------------------------------- /icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetcore/WebApiClient/cfafcbef93aef4e3b2adc96893cc5737dd96e7fa/icon.psd -------------------------------------------------------------------------------- /nuget.push.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set "folder=artifacts\package\release" 4 | 5 | for %%f in ("%folder%\*.nupkg") do ( 6 | echo push %%f 7 | nuget push %%f -source https://api.nuget.org/v3/index.json 8 | ) --------------------------------------------------------------------------------