话题

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

标签智能推荐:

精通 ASP.NET Core MVC (第 7 版) 源码下载

将使用的.NET版本更新到5.0版本。GitHub地址:https://github.com/haoguanjun/pro-asp.net-core-mvc-2

ASP.NET Core基于Socket实现消息推送实战演练

于ASP.NET&nbsp;Core中如何实现消息推送,我们已经学过了《ASP.NET&nbsp;Core基于SignalR实现消息推送实战演练》、《ASP.NET&nbsp;Core基于WebSocket实现消息推送实战演练》,《ASP.NET&nbsp;Core基于SuperWebSocket实现消息推送实战演练》今天我们要学习的是如何通过微软原生的System.Net.Sockets来实现消

如何在K8s中调式.net core

o-debug-asp-net-core-in-kubernetes-from-visual-studio-2019-1e9d16099d99https://www.okteto.com/blog/how-to-develop-aspnetcore-apps-in-kubernetes/https://developers.redhat.com/articles/2022/01/07/debug-

.NET 6 Preview 功能预览

-core-6-0-preview-4.NET6Preview3微软官方文档:https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-3参考博客:.NET6Preview3中的ASP.NETCore更新微软官方博客:https://devblogs.microsoft.com/aspnet/asp-net-core-update

ASP.NET Core - 开篇

&nbsp;由来ASP.NETCore是一个跨平台的高性能开源框架,ASP.NET&nbsp;Core第一次出现在我们眼前是以ASP.NETvNext命名的,然后又重新命名为ASP.NET5,为了表明它并不是ASP.NET的简单迭代升级,最终命名为ASP.NETCore。这是一个重新设计的Web开源框架,它最重要的特性是不再依赖IIS(依然可以部署在IIS上),支持跨平台,可以在任何平台上部署,这

ASP.NET Core 入门教程 2、使用ASP.NET Core MVC框架构建Web应用

依赖注入(DI)ASP.NETCore默认集成了DI。所有官方模块的引入都要使用DI的方式引入。https://baike.baidu.com/item/IOCvscode-solution-explorer创建、删除、重命名或移动解决方案、解决方案文件夹和项目。管理项目引用。VSCode扩展管理页直接搜索扩展名安装即可,本次安装的版本是:0.2.33https://github.com/ken-

asp.net Core依赖注入汇总

icrosoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0最佳实践https://www.cnblogs.com/runningsmallguo/p/10234307.html全面理解https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.

Asp.Net Core-----简介与安装

录Asp.NetCore-----简介与安装Asp.NetCore简介ASP.NETCore是一个全新的开源、跨平台框架,可以用它来构建基于网络连接的现代云应用程序,比如:Web应用,IoT(InternetOfThings,物联网)应用和移动后端等。ASP.NETCore可以运行在.NETCore或完整的.NETFramework之上,其架构为发布到云端或本地运行的应用提供了一个最佳的开发框架,

备忘录:VS中使用Git报错

it-core/git-askpass.exe:NosuchfileordirectorycouldnotreadUsernamefor'https://github.com':terminalpromptsdisabledPushingtohttps://github.com/shanzm/ASP.NET-Core.git2.解决方案修改仓库的.git文件夹中的config文件,将文件中的:ur

ASP.NET Core 入门教程 5、ASP.NET Core MVC 视图传值入门

@model&nbsp;语法指定对应的类型,这样我们可以在视图文件(.cshtml)中使用Model关键字来使用传输到视图的该类型的实例。https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-05本文参考https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/ov