话题

我们知道在.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类库,同时介绍了两种激活比如控制器特性方案, 希望对您有所帮助,谢谢,我们下节再会

.NET Core Web APi类库如何内嵌运行?的更多相关文章

  1. ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现

    很长一段时间以来,我都在思考如何在ASP.NET Core的框架下,实现一套完整的事件驱动型架构.这个问题看上去有点大,其实主要目标是为了实现一个基于ASP.NET Core的微服务,它能够非常简单地 ...

  2. ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理

    在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行代码就展示了一个基本工作原理.然而,要将这样的解决方案运用到实际生产环境,还有很 ...

  3. ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容.下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了. 其 ...

  4. 循序渐进学.Net Core Web Api开发系列【14】:异常处理

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍异 ...

  5. 循序渐进学.Net Core Web Api开发系列【1】:开发环境

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.本篇概述 本篇不 ...

  6. List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac

    List多个字段标识过滤 class Program{  public static void Main(string[] args) { List<T> list = new List& ...

  7. C#设计模式总结 C#设计模式(22)——访问者模式(Vistor Pattern) C#设计模式总结 .NET Core launch.json 简介 利用Bootstrap Paginator插件和knockout.js完成分页功能 图片在线裁剪和图片上传总结 循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi

    C#设计模式总结 一. 设计原则 使用设计模式的根本原因是适应变化,提高代码复用率,使软件更具有可维护性和可扩展性.并且,在进行设计的时候,也需要遵循以下几个原则:单一职责原则.开放封闭原则.里氏代替 ...

  8. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    重温.NET下Assembly的加载过程   最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...

  9. 或许是你应该了解的一些 ASP.NET Core Web API 使用小技巧

    一.前言 在目前的软件开发的潮流中,不管是前后端分离还是服务化改造,后端更多的是通过构建 API 接口服务从而为 web.app.desktop 等各种客户端提供业务支持,如何构建一个符合规范.容易理 ...

随机推荐

  1. Kafka 延时队列&重试队列

    一.延时队列 1. 简介 TimingWheel是kafka时间轮的实现,内部包含了⼀个TimerTaskList数组,每个数组包含了⼀些链表组成的TimerTaskEntry事件,每个TimerTa ...

  2. Linux系列之文本操作命令

    前言 Linux 有八个常用的文本操作命令:cat.head.tail.nl.grep.sed.more.less.本文介绍它们的区别和简单用法. cat命令 显示文本的最基本命令. cat file ...

  3. 写出个灵活的系统竟然可以如此简单!小白也能写出高级的Java业务!

    一 最近正好公司里有个需求,一个短信业务接了多个第三方供应商,某些业务需要查询第三方供应商剩余的短信包数量去选择剩余量最多的渠道去批量发送.有些业务是指定了某个短信供应商,有些场景需要根据业务的值去动 ...

  4. 对 API 平台的再思考【eolink翻译】

    API 是推动现代企业数字化转型的基础.它不但连接了内部应用程序.合作伙伴和客户,同时也快速持续地向市场提供了各种新产品.版本和功能. 但当下还是以集中式的 API 交付为主.一个企业的对外 API ...

  5. Taro框架完美使用Axios

    前言 众所周知,在H5前端开发中,axioshttp库几乎是必选.大部分人都对它的使用非常熟悉.然而在小程序开发中,axios怎么没法用,需要使用对应平台提供的http接口,如微信小程序的wx.req ...

  6. 意向不到的Dubug妙招

    1.直接dubug到想要到达的位置,直接点击旁边的数字即可. 2.debug后不想重新启动,想重新进入再执行一次debug,可以使用drop frame来删除当前栈,跳到之前的栈再一次进入这个栈. 注 ...

  7. Lambda表达式无参数无返回值的练习和Lambda表达式有参数有返回值的练习

    题目: 给定一个厨子Cook接口,内容唯一的抽象方法makeFood,且无参数.无返回值.如下: public interface Cook{ void makeFood(); } 在下面的代碼中,使 ...

  8. 栈Stack

  9. [NCTF2019]True XML cookbook-1|XXE漏洞

    1.打开题目之后和做的上一道:https://www.cnblogs.com/upfine/p/16534940.html题目界面一样,查看源代码等未发现有用信息,界面如下: 2.那就先按原来那道题的 ...

  10. 使用JMeter进行MySQL的压力测试

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. GreatSQL是MySQL的国产分支版本,使用上与MySQL一致. 目录 前言 1. JMeter安装 2. 导入MyS ...