带有点网核心的自托管进程内 Web API

Posted

技术标签:

【中文标题】带有点网核心的自托管进程内 Web API【英文标题】:Self-hosted In Process Web API with Dot net core 【发布时间】:2020-01-25 00:18:55 【问题描述】:

我正在尝试调查迁移到 dot net core 的合理性,现在 3.0 已经发布。我们的关键组件之一允许我们的(私有)nugets 创建自己的 WebAPI,为消费者提供事件和方法。支持远程服务控制、远程服务配置等功能,允许api提供远程配置设置/检索等功能。

此功能是我们微服务架构当前工作方式的关键。

我正在尝试使用 dotnet core 复制它,但是,我正在努力寻找直接等效的教程/场景。我们基本上遵循了这里详述的过程:

https://docs.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api

但是,在检查了 nuget 包的兼容性(一切看起来都还不错..)之后,我现在在调用 WebApp.Start<Startup>(baseaddress); 时得到空引用异常

null 引用异常显然是由 nuget 包与 .net 核心的不兼容调用的,请参见此处:

NullReferenceException experienced with Owin on Startup .Net Core 2.0 - Settings?

链接中提供的解决方案是一种方式,但它使用了第三方应用程序 - NancyFx。有没有办法以当前形式实现与 dotnet core 相同的功能?之前有大量关于自托管的文档,但不幸的是,由于 aspnet 核心在自己的进程中运行,因此很难找到解决方案!

任何人都可以在这里指出正确的方向吗?

代码如下所示

//the external library would contain all this code. I.e. this could present the configuration endpoints as mentioned above.

public class Startup

    // This code configures Web API. The Startup class is specified as a type
    // parameter in the WebApp.Start method.
    public void Configuration(IAppBuilder appBuilder)
    
        // Configure Web API for self-host. 
        HttpConfiguration config = new HttpConfiguration();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/controller/id",
            defaults: new  id = RouteParameter.Optional 
        );
        appBuilder.UseWebApi(config);
    


public class WebAPI:IDisposable

    private IDisposable _webApp;
    public WebAPI()
    
        string baseAddress = "http://localhost:8800/";
        _webApp = WebApp.Start<Startup>(baseAddress); // << This line throws null reference exception 
    
    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    
        if (!disposedValue)
        
            if (disposing)
            
                _webApp.Dispose();
                _webApp = null;
                            
            disposedValue = true;
        
    

    public void Dispose()
    
        Dispose(true);
    
    #endregion


public class ValuesController:ApiController

    // GET api/values 
    public IEnumerable<string> Get()
    
        return new string[]  "value1", "value2" ;
    

    // GET api/values/5 
    public string Get(int id)
    
        return "value";
    

主应用,上面库的宿主/消费者。

class Program

    static void Main()
    
        var webapi = new WebApiTest.WebAPI();
        Console.WriteLine("Running...");
        Console.ReadLine();
        webapi.Dispose();
    

【问题讨论】:

你看过dotnet new webapi提供的模板了吗?这默认情况下自托管,但也可以部署到 IIS。 谢谢一个好主意,我会看看他们是怎么做的!谢谢.. @ChrisWatts 我知道这是一个老问题 - 但你有没有想过这个问题?我们正在将 .NET Framework 项目移植到 .NET 5,我也遇到了这个空引用问题。 NancyFX 项目已停止,如果可以的话,我宁愿避免它。 @tmwoods,见下文.. 应该是您需要的唯一一点.. 它可以帮助您入门,也可以解决您的问题.. 【参考方案1】:

我最终还是弄清楚了。基本上,我查看了使用新的 dotnet core webapi 项目创建的项目存根,然后重写了启动类,使其不那么固定。这意味着我可以在主应用程序中使用我自己的依赖注入容器,并且一切都可以按预期连接。我必须补充一点,这没有经过实战测试,或者根本没有经过真正的测试,该项目退居二线,但原理有效,只需要改进/定制以适应。

唯一感兴趣的代码是新的“Api”类,在我的例子中,它可以按需启动/停止 API:(可能可以删除配置,但正在尝试尽可能多地自定义 API)

public class Api:IDisposable
    
        private readonly IHost _host;
        private readonly IConfigurationManager _configManager;

        public Api(DryIoc.IContainer container, IConfigurationManager configManager)
        
            _host = CreateBuilder(Array.Empty<string>(), container).Build();
            _configManager = configManager;
            _configManager.Build($"nameof(Api)_iosource");
        

        public async void Start()
        
            await _host.StartAsync();
        

        public async void Stop()
        
            await _host.StopAsync();
        

        private IHostBuilder CreateBuilder(string[] args, DryIoc.IContainer container = null)
        
            return new HostBuilder()
                //CreateDefaultBuilder, taken from source, to allow custom useserviceproviderfactory
                .UseContentRoot(Directory.GetCurrentDirectory())
                .ConfigureHostConfiguration(config =>
                
                    config.AddEnvironmentVariables(prefix: "DOTNET_");
                    if (args != null)
                    
                        config.AddCommandLine(args);
                    
                )
                .ConfigureAppConfiguration((hostingContext, config) =>
                
                    var env = hostingContext.HostingEnvironment;

                    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.env.EnvironmentName.json", optional: true, reloadOnChange: true);

                    if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
                    
                        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                        if (appAssembly != null)
                        
                            config.AddUserSecrets(appAssembly, optional: true);
                        
                    

                    config.AddEnvironmentVariables();

                    if (args != null)
                    
                        config.AddCommandLine(args);
                    
                )
                .UseServiceProviderFactory(new DryIocServiceProviderFactory(container))
                .ConfigureLogging((hostingContext, logging) =>
                
                    var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

                    // IMPORTANT: This needs to be added *before* configuration is loaded, this lets
                    // the defaults be overridden by the configuration.
                    if (isWindows)
                    
                        // Default the EventLogLoggerProvider to warning or above
                        logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
                    

                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEventSourceLogger();

                    if (isWindows)
                    
                        // Add the EventLogLoggerProvider on windows machines
                        logging.AddEventLog();
                    
                )
                .ConfigureWebHostDefaults(webBuilder =>
                
                    webBuilder.UseStartup<Startup>();
                    webBuilder.UseUrls("http://localhost:12000/");
                );
        

        //This is the original code thats generated by dotnet for new webapi projects
        //private IHostBuilder CreateHostBuilder(string[] args) =>
        //    //new WebHostBuilder().Build();
        //    Host.CreateDefaultBuilder(args)
        //        .ConfigureWebHostDefaults(webBuilder =>
        //        
        //            webBuilder.UseStartup<Startup>();
        //            webBuilder.UseUrls("http://localhost:12000/");
        //        );

        public void Dispose()
        
            _host?.Dispose();
        
    

【讨论】:

以上是关于带有点网核心的自托管进程内 Web API的主要内容,如果未能解决你的问题,请参考以下文章

Azure 不显示托管 .net 核心 API 的自定义错误消息

带有 EF Core 更新实体的 ASP.Net 核心 Web Api 如何

在 docker 容器内使用带有 ssl 的自托管(Jetty)元数据库时出错

在列表中添加表单数据(点网核心)

基于 Matlab 的点网应用程序的托管服务提供商

使用外部 WCF 服务时,在 docker 内运行的 Dotnet 核心 Web api 无法进行身份验证