• 【c#版本Openfeign】Net8 自带OpenFeign实现远程接口调用


    引言

        相信巨硬,我们便一直硬。Net版本到现在已经出了7了,8也已经在预览版了,相信在一个半月就会正式发布,其中也有很多拭目以待的新功能了,不仅仅有Apm和Tap的结合,TaskToAscynResult,以及UnsafeAccessor用来获取私有变量,性能比反射,EMIT更高,还有针对AsyncLocal封装的IAsyncContext,IAsyncState,用来存异步上下文的一些数据,当然了,最让我期待的还是自带了一个OpenFeign,在看新增的东西的时候,其他的都觉得一般般,个人觉得哈,当看到这个AutoClient新增的包的时候,好奇心的驱使下,我点进去看了一下,哇,官网终于出这玩意了,使用简单,根据特性,然后使用Sg来生成我们对应的实现从而我们只需要定义一个接口,打上特性,就可以生成一个对应的代理类,调用远程Api接口,太令人心动,为此特地升级了VS,下载了Net8,体验新功能,接下来,我们就看看他的使用案例。附官网链接:https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.extensions.http.autoclient.autoclientattribute?view=dotnet-plat-ext-8.0

    AutoClient

        在使用自带的OpenFeign的时候,我们还需要下载一个扩展包 Microsoft.Extensions.Http.AutoClient,当然还有 Microsoft.Extensions.Http的扩展包了,接下来我们定义一个接口,IBussiness,打上AutoClient特性,第一个参数是我们在注入Httpclient的时候,给的名字,我这里叫TestApi,这里会根据使用了AutoClient特性自定生成一个BussIness的类,在下图可以看到,自动生成了一个AutoClient.g.cs文件,里面的类就是Bussiness,其中包括了我们的TestPost方法以及路由信息,在上面的代码中,我们使用了Post特性,代表我们这个是Post请求,以及方法参数限制必须有一个CancellationToken,这个Post里面的内容,就是我另外一个项目种的接口地址。

      

    复制代码
    builder.Services.AddHttpClient("TestApi",s=>s.BaseAddress=new Uri(" http://localhost:5062")); 
    [AutoClient("TestApi")] public interface IBussiness { [Post("/Test/TestPost")] public Task<string> TestPost(CancellationToken cancellationToken); }
    复制代码

     

    复制代码
    // 
    #nullable enable
    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
    
    namespace WebApplication1.Api
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Gen.AutoClient", "8.0.0.0")]
        public class Bussiness  : IBussiness
        {
            private static class Statics
            {
                public static readonly global::System.Net.Http.Headers.MediaTypeHeaderValue ApplicationJsonHeader = new("application/json")
                {
                    CharSet = global::System.Text.Encoding.UTF8.WebName
                };
    
                public static readonly global::System.Net.Http.Headers.MediaTypeHeaderValue TextPlainHeader = new("text/plain")
                {
                    CharSet = global::System.Text.Encoding.UTF8.WebName
                };
    
                public static readonly global::System.Uri UriTestPost = new("/Test/TestPost", global::System.UriKind.Relative);
                public static readonly global::Microsoft.Extensions.Http.Telemetry.RequestMetadata RequestMetadataTestPost = new()
                {
                    DependencyName = "Bussiness",
                    RequestName = "TestPost",
                    RequestRoute = "/Test/TestPost"
                };
    
            }
            private readonly global::System.Net.Http.HttpClient _httpClient;
            private readonly global::Microsoft.Extensions.Http.AutoClient.AutoClientOptions _autoClientOptions;
    
            public Bussiness(global::System.Net.Http.HttpClient httpClient, global::Microsoft.Extensions.Http.AutoClient.AutoClientOptions autoClientOptions)
            {
                _httpClient = httpClient;
                _autoClientOptions = autoClientOptions;
            }
    
            public async global::System.Threading.Tasks.Task<string> TestPost(global::System.Threading.CancellationToken cancellationToken)
            {
                var httpRequestMessage = new global::System.Net.Http.HttpRequestMessage()
                {
                    Method = global::System.Net.Http.HttpMethod.Post,
                    RequestUri = Statics.UriTestPost,
                };
    
                try
                {
                    global::Microsoft.Extensions.Telemetry.TelemetryExtensions.SetRequestMetadata(httpRequestMessage, Statics.RequestMetadataTestPost);
    
                    return await SendRequest<string>("Bussiness", Statics.RequestMetadataTestPost.RequestRoute, httpRequestMessage, cancellationToken)
                        .ConfigureAwait(false);
                }
                finally
                {
                    httpRequestMessage.Dispose();
                }
            }
    
            private async global::System.Threading.Tasks.Task SendRequest(
                        string dependencyName,
                        string path,
                        global::System.Net.Http.HttpRequestMessage httpRequestMessage,
                        global::System.Threading.CancellationToken cancellationToken)
                where TResponse : class
            {
    
                var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
    
                if (typeof(TResponse) == typeof(global::System.Net.Http.HttpResponseMessage))
                {
                    return (response as TResponse)!;
                }
    
                try
                {
                    if (!response.IsSuccessStatusCode)
                    {
                        var error = await global::Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.CreateAsync(response, cancellationToken).ConfigureAwait(false);
                        throw new global::Microsoft.Extensions.Http.AutoClient.AutoClientException(global::System.FormattableString.Invariant($"The '{dependencyName}' HTTP client failed with '{response.StatusCode}' status code."), path, error);
                    }
    
                    if (typeof(TResponse) == typeof(string))
                    {
    #if NET5_0_OR_GREATER
                        var rawContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
    #else
                        cancellationToken.ThrowIfCancellationRequested();
                        var rawContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    #endif
    
                        return (rawContent as TResponse)!;
                    }
    
                    var mediaType = response.Content.Headers.ContentType?.MediaType;
                    if (mediaType == "application/json")
                    {
                        var deserializedResponse = await global::System.Net.Http.Json.HttpContentJsonExtensions.ReadFromJsonAsync(response.Content, _autoClientOptions.JsonSerializerOptions, cancellationToken)
                        .ConfigureAwait(false);
                        if (deserializedResponse == null)
                        {
                            var error = await global::Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.CreateAsync(response, cancellationToken).ConfigureAwait(false);
                            throw new global::Microsoft.Extensions.Http.AutoClient.AutoClientException(global::System.FormattableString.Invariant($"The '{dependencyName}' REST API failed to deserialize response."), path, error);
                        }
    
                        return deserializedResponse;
                    }
    
                    var err = await global::Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.CreateAsync(response, cancellationToken).ConfigureAwait(false);
                    throw new global::Microsoft.Extensions.Http.AutoClient.AutoClientException(global::System.FormattableString.Invariant($"The '{dependencyName}' REST API returned an unsupported content type ('{mediaType}')."), path, err);
    
                }
                finally
                {
                    response.Dispose();
                }
            }
        }
    
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Gen.AutoClient", "8.0.0.0")]
        public static class AutoClientsExtensions
        {
            public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddBussiness(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)
            {
                return services.AddBussiness(_ => { });
            }
    
            public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddBussiness(
                    this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services,
                    global::System.Action<global::Microsoft.Extensions.Http.AutoClient.AutoClientOptions> configureOptions)
            {
                global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptionsWithValidateOnStart<global::Microsoft.Extensions.Http.AutoClient.AutoClientOptions, global::Microsoft.Extensions.Http.AutoClient.AutoClientOptionsValidator>(services, "Bussiness").Configure(configureOptions);
                global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddSingleton(services, provider =>
                {
                    var httpClient = global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<global::System.Net.Http.IHttpClientFactory>(provider).CreateClient("TestApi");
                    var autoClientOptions = global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<global::Microsoft.Extensions.Options.IOptionsMonitor<global::Microsoft.Extensions.Http.AutoClient.AutoClientOptions>>(provider).Get("Bussiness");
                    return new Bussiness(httpClient, autoClientOptions);
                });
                return services;
            }
    
        }
    }
    复制代码

     

      下面这段代码,是我另一个项目接口的代码,可以看到,路由是Test,方法的路由是TestPost,返回了一个字符串true,因为,在使用AutoClient的时候,返回类型必须是引用类型,接下来,我们调用一下测试看看,在返回的结果中,我们可以看到返回了我们在另一个项目中返回的结果,true,同时,AutoClient还支持Get,Patch,Delete,Get,Put,Body(标记是在Body中),Header,Query等诸多特性,就是一个c#版本的OpenFeign,简直爽的不要不要的。

    复制代码
    [Route("Test")]
    public class TestController : ControllerBase
    {
        public TestController()
        {
                
        }
        [HttpPost("TestPost")]
        public Task<string> TestPost()
        { 
          return Task.FromResult("true");
        }
    }
    复制代码

     

     结语

        今天就要开始十月一假期了,后续节后来了,会持续带来新增api的一些玩法,包括IAsyncContext,还有其他的在等待探索,欢迎大家关注。

  • 相关阅读:
    13JVM进阶
    Spring Boot Bean 注入的常用方式教程
    java-php-python-客户台账管理计算机毕业设计
    J9数字货币论:什么是区块链节点
    前端研习录(22)——JavaScript字符串及其方法合集
    qt学习之旅--MinGW编译FFmpeg(32bit)
    软件开发、网络空间安全、人工智能三个方向的就业和前景怎么样?哪个方向更值得学习?
    Prometheus Operator 实战 监控 etcd 集群
    记录一次部署Hugo主题lotusdocs到Github Pages实践
    【吴恩达机器学习笔记】十三、异常检测
  • 原文地址:https://www.cnblogs.com/1996-Chinese-Chen/p/17724792.html