ServiceStack和.NET Core 2

时间:2017-09-04 11:06:56

标签: c# routes servicestack .net-core-2.0 .net-core-1.1

我最近有理由将服务堆栈服务从.NET Core 1.1升级到.NET Core 2.0。

以前,我的根URL在程序类中定义有点像这样......

IWebHost host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup<Startup>() .UseUrls("http://*:44444/api/myservice") .Build(); host.Run();

使用.NET Core 2.0,似乎无法在&#39; UseUrls&#39;中指定根路径。方法 - 我得到一个异常,说System.InvalidOperationException: A path base can only be configured using IApplicationBuilder.UsePathBase()使用合并UsePathBaseMiddleware来设置根路径。

我发现,当我在WebHostBuilder中仅指定根路径+端口号并在apphost文件中设置UsePathBase时(在调用app.UseServiceStack(...)之前或之后),所有内容都可以从根目录中获取(即我认为我对UsePathBase的调用被忽略了?!)。

我的请求使用如下所示的路由属性进行修饰:

[Route("users/{username}", "Get"]

使用.NET Core 1.1,我能够访问此服务 http://[URL]:[PORT]/api/user/users/[USERNAME]

使用.NET Core 2.0,服务出现在
http://[URL]:[PORT]/users/[USERNAME]

现在,我可以对路线进行硬编码以包含&#39; / api / user&#39;每个定义的路由属性上的前缀,或者我认为我可以在AppHost中的GetRouteAttributes()中执行某些操作来覆盖所有发现的路由并根据需要应用前缀 - 但元数据页面始终显示在根地址(即{{1而不是http://[URL]:[PORT]/metadata

这对我来说是一个问题,因为我在同一个公共URL上有很多服务(端口被API网关隐藏),所以我需要将元数据页面显示在除root之外的某个地方。

是否有一种(最好是影响较小的)方式使服务路由的行为与.NET Core 1.1中的一样?

更新 - 经过一些研究后的18/09/17

我已经找到了两种方法来完成这项工作(它们都不理想......)

第一种方式:

这是我第一个解决方案的完整回购。我想出了如何使用http://[URL]:[PORT]/api/[SERVICE]/metadata更改为根网址,但元数据详细信息页面不考虑此路径基础,因此它们只显示从&#39; /&#开始的每个服务方法39;

app.UsePathBase

第二种方式 - 这确实提供了正确的功能 - 包括服务路由和元数据显示但Servicestack在每次调用时解析路径时抛出异常。在这个完整的仓库中,我使用HandlerFactoryPath设置我的基URI,根据servicestack文档应该指定应用程序的基本路由。

using Funq;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ServiceStack;
using System;
using System.Threading.Tasks;

namespace ServiceStackCore1Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "My Service";

            IWebHost host = WebHost.CreateDefaultBuilder()
                .UseStartup<Startup>()
                .UseUrls("http://*:32505/")
                .Build();
            host.Run();

        }
    }

    internal class PathSetupStartupFilter : IStartupFilter
    {
        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
        {
            return app =>
            {
                app.UsePathBase("/api/myservice");
                next(app);
            };
        }
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddLogging();
            services.AddTransient<IStartupFilter, PathSetupStartupFilter>();

        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {

            loggerFactory.AddConsole((x, y) => y > LogLevel.Trace);
            app.UseServiceStack(Activator.CreateInstance<AppHost>());

            app.Run(context => Task.FromResult(0) as Task);
        }
    }

    public class AppHost : AppHostBase
    {
        public AppHost()
            : base("ASM Cloud - My Service", typeof(MyService).GetAssembly())
        {
        }

        /// <summary>
        /// Configure the given container with the
        /// registrations provided by the funqlet.
        /// </summary>
        /// <param name="container">Container to register.</param>
        public override void Configure(Container container)
        {
            this.Plugins.Add(new PostmanFeature());
        }
    }

    public class MyService : Service
    {
        public TestResponse Any(TestRequest request)
        {
            //throw new HttpError(System.Net.HttpStatusCode.NotFound, "SomeErrorCode");
            return new TestResponse { StatusCode = 218, UserName = request.UserName };
        }

        [Route("/test/{UserName}", "GET", Summary = "test")]
        public class TestRequest : IReturn<TestResponse>
        {

            public string UserName { get; set; }
        }

        public class TestResponse : IHasStatusCode
        {
            public int StatusCode { get; set; }
            public string UserName { get; set; }
        }

    }
}

所以,就像我说的,这个解决方案有效,但会引发异常

  

失败:Microsoft.AspNetCore.Server.Kestrel [13]         连接ID&#34; 0HL7UFC5AIAO6&#34;,请求ID&#34; 0HL7UFC5AIAO6:00000004&#34;:应用程序抛出了未处理的异常。   System.ArgumentOutOfRangeException:startIndex不能大于string的长度。   参数名称:startIndex      在System.String.Substring(Int32 startIndex,Int32 length)      at ServiceStack.AppHostBase.d__7.MoveNext()在C:\ BuildAgent \ work \ 799c742886e82e6 \ src \ ServiceStack \ AppHostBase.NetCore.cs:第101行   ---从抛出异常的先前位置开始的堆栈跟踪结束---      在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()      在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)      在Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.d__3.MoveNext()   ---从抛出异常的先前位置开始的堆栈跟踪结束---      在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()      在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)      在Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame using Funq; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ServiceStack; using System; using System.Threading.Tasks; namespace ServiceStackCore1Test { class Program { static void Main(string[] args) { Console.Title = "My Service"; IWebHost host = WebHost.CreateDefaultBuilder() .UseStartup<Startup>() .UseUrls("http://*:32505/") .Build(); host.Run(); } } public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddLogging(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole((x, y) => y > LogLevel.Trace); app.UseServiceStack(Activator.CreateInstance<AppHost>()); app.Run(context => Task.FromResult(0) as Task); } } public class AppHost : AppHostBase { public AppHost() : base("My Service", typeof(MyService).GetAssembly()) { } /// <summary> /// Configure the given container with the /// registrations provided by the funqlet. /// </summary> /// <param name="container">Container to register.</param> public override void Configure(Container container) { Plugins.Add(new PostmanFeature()); SetConfig(new HostConfig { HandlerFactoryPath = "/api/myservice" }); } } public class MyService : Service { public TestResponse Any(TestRequest request) { return new TestResponse { StatusCode = 200, UserName = request.UserName }; } [Route("/test/{UserName}", "GET", Summary = "test")] public class TestRequest : IReturn<TestResponse> { public string UserName { get; set; } } public class TestResponse : IHasStatusCode { public int StatusCode { get; set; } public string UserName { get; set; } } } } 1.d__2.MoveNext()

除了调查结果之外,实际问题(不是我在更新中显示的内容)可能归结为ASPNet托管的以下两个问题:

https://github.com/aspnet/Hosting/issues/815
https://github.com/aspnet/Hosting/issues/1120

我仍然有点担心如何解决我的原始问题 - 所以我很感激任何帮助。

1 个答案:

答案 0 :(得分:2)

不确定您是否找到过这方面的解决方案,但是servicestack论坛上的某些人遇到了同样的问题(就像我开始使用CORE版本的servicestack时那样)。

如果您有权访问该论坛,则可以按照进度here

进行操作

如果你不这样做,总的来说@mythz注意到:

“这是他们在.NET Core 2.0中添加的一个重大变化,我们将调查是否有解决方法但是否则我会避免与.NET Core约定作斗争并将其托管在端口上并使用反向代理实现所需的基本托管网址。“