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

c#,版本,openfeign,net8,自带,实现,远程,接口,调用 · 浏览次数 : 1344

小编点评

**代码摘要** 此代码展示了如何使用AutoClientsExtensions实现C#中OpenFeign的接口。 **主要功能** - 支持Get、Put、Patch、Delete、Get,Put,Body(标记是在Body中),Header,Query等诸多特性 - 接口返回类型必须是引用类型 - 支持IAsyncContext,还有其他的在等待探索 **主要代码** ```csharp 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.Options.IOptionsMonitor>(services, \"Bussiness\").Configure(configureOptions); global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddSingleton<IBussiness>(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>>(provider).Get("Bussiness"); return new Bussiness(httpClient, autoClientOptions); }); return services; } } ``` **使用说明** 可通过添加依赖项`AutoClientsExtensions`来使用该代码。 **示例** ```csharp //添加依赖项 services.AddAutoClientsExtensions(); //使用接口 controller = services.GetRequiredService(); controller.TestPost(); ``` **返回结果** ```csharp true ``` **说明** - `IAsyncContext` 是用于支持异步操作的接口。 - `OpenFeign` 是一个开源的C#客户端,它提供多种功能,包括获取、更新、删除等操作。

正文

引言

    相信巨硬,我们便一直硬。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); }

 

// <auto-generated/>
#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<TResponse> SendRequest<TResponse>(
                    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<TResponse>(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<IBussiness>(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,还有其他的在等待探索,欢迎大家关注。

与【c#版本Openfeign】Net8 自带OpenFeign实现远程接口调用相似的内容:

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

引言 相信巨硬,我们便一直硬。Net版本到现在已经出了7了,8也已经在预览版了,相信在一个半月就会正式发布,其中也有很多拭目以待的新功能了,不仅仅有Apm和Tap的结合,TaskToAscynResult,以及UnsafeAccessor用来获取私有变量,性能比反射,EMIT更高,还有针对Async

极速进化,光速转录,C++版本人工智能实时语音转文字(字幕/语音识别)Whisper.cpp实践

业界良心OpenAI开源的Whisper模型是开源语音转文字领域的执牛耳者,白璧微瑕之处在于无法通过苹果M芯片优化转录效率,Whisper.cpp 则是 Whisper 模型的 C/C++ 移植版本,它具有无依赖项、内存使用量低等特点,重要的是增加了 Core ML 支持,完美适配苹果M系列芯片。

本地推理,单机运行,MacM1芯片系统基于大语言模型C++版本LLaMA部署“本地版”的ChatGPT

OpenAI公司基于GPT模型的ChatGPT风光无两,眼看它起朱楼,眼看它宴宾客,FaceBook终于坐不住了,发布了同样基于LLM的人工智能大语言模型LLaMA,号称包含70亿、130亿、330亿和650亿这4种参数规模的模型,参数是指神经网络中的权重和偏置等可调整的变量,用于训练和优化神经网络

LangChain vs Semantic Kernel

每当向他人介绍 Semantic Kernel, 会得到的第一个问题就是 Semantic Kernel 类似于LangChain吗,或者是c# 版本的LangChain吗? 为了全面而不想重复的回答这个问题,因此我写下这篇文章。在 ChatGPT 之前,构建 集成AI的应用程序的主要分为两个步骤:

[转帖]删除分区如何不让全局索引失效?

记得上次ACOUG年会(《ACOUG年会感想》),请教杨长老问题的时候,谈到分区,如果执行分区删除的操作,就会导致全局索引失效,除了使用12c以上版本能避免这个问题外,指出另外一种解决的方式,表面看很巧妙,实则是对分区原理的深入理解。 我们先从实验,了解这个问题,首先创建分区表,他存在4个分区,每个

[转帖]从生命周期的角度来谈谈Oracle 软件的版本(12c/18c/19c/20c/21c)问题

2022-04-21 20:3720050原创Oracle 19c 本文链接:https://www.cndba.cn/dave/article/107944 在2017年之前,Oracle 的版本路线是非常清晰的,我接触过的几个版本有:9i、10g、11g、12c。 但是到了2018年之后,Ora

Inno setup 脚本判断 Microsoft Visual C++ Redistributable 不同版本区别

有个需要是需要在安装包安装初始化时安装 Microsoft Visual c++ 2013 Redistributable 也就是判断软件安装前需不需要运行 `vcredist_x64.exe` 和 `VC_redist.x64.exe` 这两个程序 第一反应就是可以通过注册表判断是否已经安装过环境

C#实现图片转Base64字符串.并支持markdown文件打开展示

引用1.0.3版本或以上的Wesky.Net.OpenTools 包 1.0.3 版本提供图片转Base64字符串方案,并提供根据后缀名自动识别Mime类型,合成标准URI 开源项目地址: Gitee:https://gitee.com/dreamer_j/open-tools.git Github

8KB的C#贪吃蛇游戏热点答疑和.NET7版本

在之前的一篇文章《看我是如何用C#编写一个小于8KB的贪吃蛇游戏》中,介绍了在.NET Core 3.0的环境下如何将贪吃蛇游戏降低到8KB。不过也有很多小伙伴提出了一些疑问和看法,主要是下面这几个方面: .NET Core 3.0可以做到这么小,那么.NET7表现会不会更好? 不敢在生产中用这样的

.NET Web入门到高级路线(新版本)

.NET Web入门到高级路线 C# 基础语法 .NET Core 基础知识 ASP.NET Core基础知识概述 Blazor ASP.NET Core 官方文档 ORM FreeSql EntityFrameworkCore Dapper SqlSugar 关系型数据库 SQL Server M