

.NET Core Web APi类库如何内嵌运行? - Jeffcky
source link: https://www.cnblogs.com/CreateMyself/p/16721269.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们知道在.NET Framework中可以嵌入运行Web APi,那么在.NET Core(.NET 6+称之为.NET)中如何内嵌运行Web Api呢,在实际项目中这种场景非常常见,那么我们本节以.NET 6.0作为演示示例一起来瞅瞅
内嵌运行.NET Core Web APi
接下来我们通过控制台作为主程序来启动Web APi,首先我们创建名为EmbedWebApi的控制台程序,然后创建Embed.WebApi类库运行Web APi,我们在此Web APi中创建如下接口,并实现相关方法来运行Web APi
public class InitTest : IInitTest { public void Init() { var builder = WebApplication.CreateBuilder(); builder.Services.AddControllers(); var app = builder.Build(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); app.Run(); } } public interface IInitTest { void Init(); }
通过写接口并在对应方法中运行Web APi主要是达到在控制中调用该接口进行模拟实现,这里需要注意一点的是,因为我们创建的Web APi是类库,要想使用Web里面的Api等等,直接在项目文件中添加如下一行以表明我们要引用框架,这样一来框架里面所包含的APi等等版本都一致统一,而不是通过NuGet一一下载,这是错误的做法
<ItemGroup> <FrameworkReference Include="Microsoft.AspNetCore.App" /> </ItemGroup>
接下来我们在该类库中按照规范创建Controllers文件夹,并创建测试控制器,如下
using Microsoft.AspNetCore.Mvc; namespace Embed.WebApi.Controllers { [ApiController] [Route("api/[controller]/[action]")] public class TestController : ControllerBase { [HttpGet] public IActionResult Test() { return Ok("Hello World"); } } }
最后我们在控制台程序中注册上述接口并调用初始化方法,如下:
internal class Program { static void Main(string[] args) { var services = new ServiceCollection(); services.AddTransient<IInitTest, InitTest>(); var serviceProvider = services.BuildServiceProvider(); var initTest = serviceProvider.GetRequiredService<IInitTest>(); initTest.Init(); Console.Read(); } }
芜湖,我们通过Postman模拟调用测试接口,结果惊呆了,404了~~~
当我们将类库中的控制器移动到控制台中,此时请求测试接口并成功返回对世界的问候,这是什么原因呢? 不难猜测可知,默认WebAPi控制器的激活以作为入口的主程序集进行查找激活。虽然这样看似解决了问题,假设调用嵌入运行的主程序是底层已经封装好的基础设施,那么岂不是遭到了代码入侵,所以我们就想在运行的Web APi类库里面去激活,此时我们想到将类库作为Web APi应用程序一部分应用手动加载并激活,在初始化方法里面修改为如下即可请求测试接口成功
public class InitTest : IInitTest { private static readonly string AssemblyName = typeof(InitTest).Assembly.GetName().Name; public void Init() { var builder = WebApplication.CreateBuilder(); builder.Services.AddControllers() .AddApplicationPart(Assembly.Load(new AssemblyName(AssemblyName))); var app = builder.Build(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); app.Run(); } }
上述直接在运行Web APi类库中添加控制器激活,这种场景完全限定于底层主入口已封装好,所以只能采用这种方式,若是主入口我们自己可控制,当然还有另外一种方式,来,我们瞧瞧截取的关键性源码
/// <summary> /// Populates the given <paramref name="feature"/> using the list of /// <see cref="IApplicationFeatureProvider{TFeature}"/>s configured on the /// <see cref="ApplicationPartManager"/>. /// </summary> /// <typeparam name="TFeature">The type of the feature.</typeparam> /// <param name="feature">The feature instance to populate.</param> public void PopulateFeature<TFeature>(TFeature feature) { if (feature == null) { throw new ArgumentNullException(nameof(feature)); } foreach (var provider in FeatureProviders.OfType<IApplicationFeatureProvider<TFeature>>()) { provider.PopulateFeature(ApplicationParts, feature); } } internal void PopulateDefaultParts(string entryAssemblyName) { var assemblies = GetApplicationPartAssemblies(entryAssemblyName); var seenAssemblies = new HashSet<Assembly>(); foreach (var assembly in assemblies) { if (!seenAssemblies.Add(assembly)) { // "assemblies" may contain duplicate values, but we want unique ApplicationPart instances. // Note that we prefer using a HashSet over Distinct since the latter isn't // guaranteed to preserve the original ordering. continue; } var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); foreach (var applicationPart in partFactory.GetApplicationParts(assembly)) { ApplicationParts.Add(applicationPart); } } } private static IEnumerable<Assembly> GetApplicationPartAssemblies(string entryAssemblyName) { var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName)); // Use ApplicationPartAttribute to get the closure of direct or transitive dependencies // that reference MVC. var assembliesFromAttributes = entryAssembly.GetCustomAttributes<ApplicationPartAttribute>() .Select(name => Assembly.Load(name.AssemblyName)) .OrderBy(assembly => assembly.FullName, StringComparer.Ordinal) .SelectMany(GetAssemblyClosure); // The SDK will not include the entry assembly as an application part. We'll explicitly list it // and have it appear before all other assemblies \ ApplicationParts. return GetAssemblyClosure(entryAssembly) .Concat(assembliesFromAttributes); } private static IEnumerable<Assembly> GetAssemblyClosure(Assembly assembly) { yield return assembly; var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false) .OrderBy(assembly => assembly.FullName, StringComparer.Ordinal); foreach (var relatedAssembly in relatedAssemblies) { yield return relatedAssembly; } }
从上述源码可知,通过主入口程序集还会加载引用的程序集去查找并激活相关特性(比如控制器),当然前提是实现ApplicationPartAttribute特性,此特性必须在主入口程序集里定义,定义在程序集上,所以我们只需一行代码即可搞定,我们在控制台主入口命名空间顶部添加特性,引入Web APi类库程序集作为应用程序的一部分,如下:
[assembly: ApplicationPart("Embed.WebApi")]
那么接下来问题又来了,要是需要运行多个Web APi我们又当如何呢?按照上述方式一一添加未尝不可,我们也可以通过MSBuild任务来进行构建将相关特性自动添加到主入口程序集描述信息里面去,例如:
<ItemGroup> <AssemblyAttribute Include="Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute"> <_Parameter1>Embed.WebApi</_Parameter1> </AssemblyAttribute> </ItemGroup>
有的童鞋就问了,这不写死了么,那还不如通过添加特性的方式去处理,请注意这里只是使用示例,实际情况下,我们可将多个Web APi放在同一解决方案下,然后在此解决方案下创建可构建任务的.targets文件,并在主项目文件里引入,将程序集名称作为变量引入,剩下事情自行统一处理,若不清楚怎么搞,就在代码中使用特性方式也未尝不可,例如如下:
<ItemGroup> <AssemblyAttribute Include="Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute"> <_Parameter1>$(AssemblyName)</_Parameter1> </AssemblyAttribute> </ItemGroup>
本节我们重点讨论如何内嵌运行.NET Core Web APi类库,同时介绍了两种激活比如控制器特性方案, 希望对您有所帮助,谢谢,我们下节再会

为了方便大家在移动端也能看到我分享的博文,现已注册个人公众号,扫描上方左边二维码即可,欢迎大家关注,有时间会及时分享相关技术博文。
感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让楼主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!
本文版权归作者和博客园共有,来源网址:http://www.cnblogs.com/CreateMyself)/欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
Recommend
-
76
上一篇我们讲完SQL动态查询,本节我们继续来讲解SQL动态查询中存在的问题。 SQL动态查询条件筛选过滤 当我们创建存储过程调用存储过程时,若筛选条件有值则过滤,没有值则返回所行记录,类似如下查询:
-
186
-
75
-
108
-
69
-
32
前言 这两天主要是公司同事用到了RSA加密,事后也看了下,以为很简单,最终利用RSACryptoServiceProvider来实现RSA加密,然后大致了解到RSACryptoServiceProvi
-
60
最近沉寂了一段,主要是上半年相当于休息和调整了一段时间,接下来我将开始陆续学习一些新的技术,比如Docker、Jenkins等,都会以生活实例从零开始讲解起,到时一并和大家分享和交流。接下来几节课的内容将会讲解JWT,关于JWT的原理解析等等...
-
35
如题,本节我们进入JWT最后一节内容,JWT本质上就是从身份认证服务器获取访问令牌,继而对于用户后续可访问受保护资源,但是关键问题是:访问令牌的生命周期到底设置成多久呢?见过一些使用JWT的童鞋会将JWT过期时间设置成很长,有的几个小...
-
8
.Net 5的控制台程序中使用桌面类库 今天试了一下在.net 5控制台程序中使用桌面类库, 发现遇到了一些障碍。虽然在.n...
-
4
Redis ,是一个高性能(NOSQL)的key-value数据库,Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis是完全开源免费的,遵守BSD协议、Redis特点性能极高 – Redis能读...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK