我最近有理由将 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
.
这对我来说是个问题,因为我在同一个公共(public) URL 上有许多服务(端口被 API 网关隐藏),所以我需要元数据页面出现在根以外的其他地方。
是否有一种(最好是低影响的)方法来使服务路由的行为与在 .NET Core 1.1 中一样?
更新 - 18/09/17 经过一些研究
我已经找到了两种方法来完成这项工作(都不是理想的...)
第一种方式:
这是我的第一个解决方案的完整存储库。我想出了如何使用 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; }
}
}
}
所以,就像我说的,这个解决方案有效,但会抛出异常
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.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.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.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.Frame
1.d__2.MoveNext()
撇开调查结果不谈,实际问题(不是我在上面更新中显示的内容)可能归结为 ASPNet 托管的以下两个问题:
https://github.com/aspnet/Hosting/issues/815
https://github.com/aspnet/Hosting/issues/1120
对于如何解决我原来的问题,我仍然有点不知所措 - 所以我很感激任何帮助。
最佳答案
不确定您是否找到了解决方案,但 servicestack 论坛上有人遇到了同样的问题(当我开始使用 servicestack 的 CORE 版本时,我自己也遇到了同样的问题)。
如果您可以访问该论坛,您可以关注进度 here
如果您不这样做,总而言之@mythz 指出:
“这是他们在 .NET Core 2.0 中添加的一项重大更改,我们将调查是否有解决方法,否则我会避免与 .NET Core 约定发生冲突,而只是将其托管在一个端口上并使用反向代理以实现所需的基本托管 url。”
关于c# - ServiceStack 和 .NET Core 2 的根 URL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46035720/