【问题标题】:Root URL's for ServiceStack and .NET Core 2ServiceStack 和 .NET Core 2 的根 URL
【发布时间】:2017-09-04 11:06:55
【问题描述】:

我最近有理由将 servicestack 服务从 .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,似乎无法在 'UseUrls' 方法中指定根路径 - 我收到一个异常,上面写着 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]

现在,我可以对路由进行硬编码,以在每个已定义的路由属性上包含“/api/user”前缀,或者我认为我可以在 AppHost 的 GetRouteAttributes() 中做一些事情来覆盖所有发现的路由并应用我需要的前缀 - 但元数据页面总是出现在根地址(即http://[URL]:[PORT]/metadata)而不是http://[URL]:[PORT]/api/[SERVICE]/metadata

这对我来说是个问题,因为我在同一个公共 URL 上有几十个服务(端口被 API 网关隐藏),所以我需要元数据页面出现在根目录以外的其他地方。

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


经过一番研究后更新 - 2017 年 9 月 18 日


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

第一种方式:

这是我第一个解决方案的完整回购。我想出了如何使用app.UsePathBase 更改为根 URL,但元数据详细信息页面没有考虑此路径库,因此它们仅显示从“/”开始的每个服务方法

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; }
        }

    }
}

第二种方式 - 这确实提供了正确的功能 - 用于服务路由和元数据显示但 Servicestack 在每次调用解析路径时都会引发异常。在这个完整的 repo 中,我使用 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();
        }
    }

    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; }
        }
    }
}

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

失败:Microsoft.AspNetCore.Server.Kestrel[13] 连接 ID“0HL7UFC5AIAO6”,请求 ID“0HL7UFC5AIAO6:00000004”:应用程序引发了未处理的异常。 System.ArgumentOutOfRangeException:startIndex 不能大于字符串的长度。 参数名称:startIndex 在 System.String.Substring(Int32 startIndex,Int32 长度) 在 C:\BuildAgent\work\799c742886e82e6\src\ServiceStack\AppHostBase.NetCore.cs:line 101 中的 ServiceStack.AppHostBase.d__7.MoveNext() --- 从先前抛出异常的位置结束堆栈跟踪 --- 在 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.Frame1.<ProcessRequestsAsync>d__2.MoveNext() fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "0HL7UFC5AIAO6", Request id "0HL7UFC5AIAO6:00000004": An unhandled exception was thrown by the application. System.ArgumentOutOfRangeException: startIndex cannot be larger than length of string. Parameter name: startIndex at System.String.Substring(Int32 startIndex, Int32 length) at ServiceStack.AppHostBase.<ProcessRequest>d__7.MoveNext() in C:\BuildAgent\work\799c742886e82e6\src\ServiceStack\AppHostBase.NetCore.cs:line 101 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame1.d__2.MoveNext()

除了发现之外,实际问题(不是我在上面更新中显示的问题)可能归结为 ASPNet 托管的以下两个问题:

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

对于如何解决我最初的问题,我仍然有点茫然 - 所以我很感激任何帮助。

【问题讨论】:

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


    【解决方案1】:

    不确定您是否找到了解决方案,但 servicestack 论坛上的某个人遇到了同样的问题(当我开始使用 servicestack 的 CORE 版本时,我自己也遇到了)。

    如果您有权访问该论坛,您可以关注进度here

    如果你不这样做,总而言之,@mythz 指出:

    “这是他们在 .NET Core 2.0 中添加的一项重大更改,我们将调查是否有解决方法,否则我会避免与 .NET Core 约定冲突,而是将其托管在端口上并使用反向代理来实现所需的基本托管 url。"

    【讨论】:

    • 是的,我知道这一点是因为我的一位同事在 SS 论坛上发布了该消息 - 感谢您添加此帖子的链接。这不是问题atm的真正答案。我想如果有足够多的人对解决这个问题感兴趣,那么他们会做一些狡猾的事情来解决这个问题。不幸的是,我不能将此标记为答案 - 但我有一个 +1 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-25
    • 1970-01-01
    • 2019-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多