因此,我们使用.Net 4.6.2在Windows服务中托管了一个有效的ServiceStack服务,它使用了一堆Fluent Validation验证器。
我们希望将其移植到.Net Core。所以我开始使用主应用程序的一些功能创建减少项目,以查看.Net Core的端口是什么样的。
大多数事情都很好,例如
似乎不正确的是验证。为了说明这一点,我将介绍一些现有的.Net 4.6.2代码,然后是.Net Core代码。我在哪里列出了两者的结果
.NET 4.6.2示例
使用完整的.Net 4.6.2框架和各种ServiceStack Nuget软件包时,这一切都很好。
例如我有这个基本的Dto(请忽略这个奇怪的名字,不是我选择的长篇故事)
using ServiceStack;
namespace .RiskStore.ApiModel.Analysis
{
[Route("/analysis/run", "POST")]
public class AnalysisRunRequest : BaseRequest, IReturn<AnalysisRunResponse>
{
public AnalysisPart Analysis { get; set; }
}
}
我们有这个基类的地方
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace .RiskStore.ApiModel
{
public abstract class BaseRequest
{
public string CreatedBy { get; set;}
}
}
我们有这个验证器(我们有更多的工作在.Net 4.6.2应用程序,这只是为了显示完整的.Net和.Net Core之间的差异,我们将在一分钟内看到)
using .RiskStore.ApiModel.Analysis;
using ServiceStack.FluentValidation;
namespace .RiskStore.ApiServer.Validators.Analysis
{
public class AnalysisRunRequestValidator : AbstractValidator<AnalysisRunRequest>
{
public AnalysisRunRequestValidator(AnalysisPartValidator analysisPartValidator)
{
RuleFor(analysis => analysis.CreatedBy)
.Must(HaveGoodCreatedBy)
.WithMessage("CreatedBy MUST be 'sbarber'")
.WithErrorCode(ErrorCodes.ValidationErrorCode);
}
private bool HaveGoodCreatedBy(AnalysisRunRequest analysisRunRequest, string createdBy)
{
return createdBy == "sbarber";
}
}
}
这是此服务的主机文件
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Funq;
using .RiskStore.ApiModel.Analysis;
using .RiskStore.ApiModel.Analysis.Results;
using .RiskStore.ApiServer.Api.Analysis;
using .RiskStore.ApiServer.Exceptions;
using .RiskStore.ApiServer.IOC;
using .RiskStore.ApiServer.Services;
using .RiskStore.ApiServer.Services.Results;
using .RiskStore.ApiServer.Validators.Analysis;
using .RiskStore.ApiServer.Validators.Analysis.Results;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Results;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Search;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Validation;
using .RiskStore.DataAccess.Configuration;
using .RiskStore.DataAccess.Connectivity;
using .RiskStore.DataAccess.Ingestion.Repositories.EventSet;
using .RiskStore.DataAccess.JobLog.Repositories;
using .RiskStore.DataAccess.StaticData.Repositories;
using .RiskStore.DataAccess.UnitOfWork;
using .RiskStore.Toolkit.Configuration;
using .RiskStore.Toolkit.Jobs.Repositories;
using .RiskStore.Toolkit.Storage;
using .RiskStore.Toolkit.Utils;
using .Toolkit;
using .Toolkit.Configuration;
using .Toolkit.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ServiceStack;
using ServiceStack.Text;
using ServiceStack.Validation;
namespace .RiskStore.ApiServer.Api
{
public class ApiServerHttpHost : AppHostHttpListenerBase
{
private readonly ILogger _log = Log.ForContext<ApiServerHttpHost>();
public static string RoutePrefix => "analysisapi";
/// <summary>
/// Base constructor requires a Name and Assembly where web service implementation is located
/// </summary>
public ApiServerHttpHost()
: base(typeof(ApiServerHttpHost).FullName, typeof(AnalysisServiceStackService).Assembly)
{
_log.Debug("ApiServerHttpHost constructed");
}
public override void SetConfig(HostConfig config)
{
base.SetConfig(config);
JsConfig.TreatEnumAsInteger = true;
JsConfig.EmitCamelCaseNames = true;
JsConfig.IncludeNullValues = true;
JsConfig.AlwaysUseUtc = true;
JsConfig<Guid>.SerializeFn = guid => guid.ToString();
JsConfig<Guid>.DeSerializeFn = Guid.Parse;
config.HandlerFactoryPath = RoutePrefix;
var exceptionMappings = new Dictionary<Type, int>
{
{typeof(JobServiceException), 400},
{typeof(NullReferenceException), 400},
};
config.MapExceptionToStatusCode = exceptionMappings;
_log.Debug("ApiServerHttpHost SetConfig ok");
}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
public override void Configure(Container container)
{
//Config examples
//this.Plugins.Add(new PostmanFeature());
//this.Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(AnalysisRunRequestValidator).Assembly);
.......
.......
.......
.......
container.Register<AnalysisPartValidator>(c => new AnalysisPartValidator(
c.Resolve<AnalysisDealPartLinkingModelEventSetValidator>(),
c.Resolve<AnalysisOutputSettingsPartValidMetaRisksValidator>(),
c.Resolve<AnalysisOutputSettingsGroupPartValidMetaRisksValidator>(),
c.Resolve<AnalysisDealPartCollectionValidator>(),
c.Resolve<AnalysisPortfolioPartCollectionValidator>(),
c.Resolve<UniqueCombinedOutputSettingsPropertiesValidator>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisRunRequestValidator>(c => new AnalysisRunRequestValidator(c.Resolve<AnalysisPartValidator>()))
.ReusedWithin(ReuseScope.None);
_log.Debug("ApiServerHttpHost Configure ok");
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json
});
}}
}
然后我用这个JSON
命中了这个端点{
"Analysis": {
//Not important for discussion
//Not important for discussion
//Not important for discussion
//Not important for discussion
},
"CreatedBy": "frank"
}
我在PostMan工具(我期待的)中得到了这个回应
所以这一切都很好。
.NET核心示例
现在让我们看看.Net核心示例中的内容。
让我们从请求dto开始
using System;
namespace ServiceStack.Demo.Model.Core
{
[Route("/analysis/run", "POST")]
public class AnalysisRunRequest : BaseRequest, IReturn<AnalysisRunResponse>
{
public AnalysisDto Analysis { get; set; }
}
}
使用此基本请求对象
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public abstract class BaseRequest
{
public string CreatedBy { get; set; }
}
}
这是我们在.NET 4.6.2示例中使用的验证器,但在我的.Net核心代码中
using System;
using System.Collections.Generic;
using System.Text;
using ServiceStack.Demo.Model.Core;
using ServiceStack.FluentValidation;
namespace ServiceStack.Demo.Core.Validators
{
public class AnalysisRunRequestValidator : AbstractValidator<AnalysisRunRequest>
{
public AnalysisRunRequestValidator(AnalysisDtoValidator analysisDtoValidator)
{
RuleFor(analysis => analysis.CreatedBy)
.Must(HaveGoodCreatedBy)
.WithMessage("CreatedBy MUST be 'sbarber'")
.WithErrorCode(ErrorCodes.ValidationErrorCode);
}
private bool HaveGoodCreatedBy(AnalysisRunRequest analysisRunRequest, string createdBy)
{
return createdBy == "sbarber";
}
}
}
这是.Net核心示例的主机代码
using System;
using System.Collections.Generic;
using System.Text;
using Funq;
using ServiceStack.Demo.Core.Api.Analysis;
using ServiceStack.Demo.Core.IOC;
using ServiceStack.Demo.Core.Services;
using ServiceStack.Demo.Core.Validators;
using ServiceStack.Text;
using ServiceStack.Validation;
namespace ServiceStack.Demo.Core.Api
{
public class ApiServerHttpHost : AppHostBase
{
public static string RoutePrefix => "analysisapi";
public ApiServerHttpHost()
: base(typeof(ApiServerHttpHost).FullName, typeof(AnalysisServiceStackService).GetAssembly())
{
Console.WriteLine("ApiServerHttpHost constructed");
}
public override void SetConfig(HostConfig config)
{
base.SetConfig(config);
JsConfig.TreatEnumAsInteger = true;
JsConfig.EmitCamelCaseNames = true;
JsConfig.IncludeNullValues = true;
JsConfig.AlwaysUseUtc = true;
JsConfig<Guid>.SerializeFn = guid => guid.ToString();
JsConfig<Guid>.DeSerializeFn = Guid.Parse;
config.HandlerFactoryPath = RoutePrefix;
var exceptionMappings = new Dictionary<Type, int>
{
{typeof(NullReferenceException), 400},
};
config.MapExceptionToStatusCode = exceptionMappings;
Console.WriteLine("ApiServerHttpHost SetConfig ok");
}
public override void Configure(Container container)
{
//Config examples
//this.Plugins.Add(new PostmanFeature());
//this.Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(ApiServerHttpHost).GetAssembly());
container.RegisterAutoWiredAs<DateProvider, IDateProvider>()
.ReusedWithin(ReuseScope.Container);
container.RegisterAutoWiredAs<FakeRepository, IFakeRepository>()
.ReusedWithin(ReuseScope.Container);
container.Register<LifetimeScopeManager>(cont => new LifetimeScopeManager(cont))
.ReusedWithin(ReuseScope.Hierarchy);
container.Register<DummySettingsPropertiesValidator>(c => new DummySettingsPropertiesValidator(c.Resolve<LifetimeScopeManager>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisDtoValidator>(c => new AnalysisDtoValidator(
c.Resolve<DummySettingsPropertiesValidator>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisRunRequestValidator>(c => new AnalysisRunRequestValidator(c.Resolve<AnalysisDtoValidator>()))
.ReusedWithin(ReuseScope.None);
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json
});
}
}
}
这就是我现在尝试使用相同的错误JSON有效负载来攻击.Net Core端点,如上面的.Net 4.6.2示例所示,它提供了正确的Http响应(即包含我期待的响应错误)
无论如何,此处有效负载被发送到.Net Core端点
{
"Analysis": {
//Not important for discussion
//Not important for discussion
//Not important for discussion
//Not important for discussion
},
"CreatedBy": "frank"
}
我们可以看到我们正在进入.Net Core示例验证器代码
但这一次我得到了一个非常不同的Http响应(一个我根本没想到的)。我明白了
可以看出,我们确实得到了正确的状态代码“400”(失败),这是好的。但我们完全没有获得有关验证失败的任何信息。
我希望这能给我与上面的原始.Net 4.6.2示例相同的http响应。
但我似乎回过头来的是代表 AnalysisRunResponse 的JSON。看起来像这样
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public class AnalysisRunResponse : BaseResponse
{
public Guid AnalysisUid { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public abstract class BaseResponse
{
public ResponseStatus ResponseStatus { get; set; }
}
}
我认为ServiceStack的工作方式(事实上它对我们所有现有的.Net 4.6.2代码的工作方式)是首先完成验证,如果通过验证则为AND ONLY是实际路由代码运行
但是这个.Net核心示例似乎不能那样工作。
我在Visual Studio中设置了一个断点,用于实际路由和Console.WriteLine(..)但是从未命中,我从未看到Console.WriteLine(..)的结果
我做错了什么?
答案 0 :(得分:0)
latest v5 of ServiceStack现在available on MyGet似乎不再是问题。
发送此请求时:
返回预期的回复:
.NET Core软件包为merged with the main packages in ServiceStack v5,因此您需要删除.Core
前缀才能下载它们,即:
<PackageReference Include="ServiceStack" Version="5.*" />
<PackageReference Include="ServiceStack.Server" Version="5.*" />
您还需要add ServiceStack's MyGet feed来获取最新的ServiceStack v5 NuGet软件包,您可以将此NuGet.config添加到与.sln
相同的文件夹中来执行此操作:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="ServiceStack MyGet feed" value="https://www.myget.org/F/servicestack" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>