【发布时间】: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.Frame
1.<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