ServiceStack .Net Core流畅验证与完整的.NET 4.6.2不一致

时间:2017-11-08 11:22:39

标签: servicestack fluentvalidation

因此,我们使用.Net 4.6.2在Windows服务中托管了一个有效的ServiceStack服务,它使用了一堆Fluent Validation验证器。

我们希望将其移植到.Net Core。所以我开始使用主应用程序的一些功能创建减少项目,以查看.Net Core的端口是什么样的。

大多数事情都很好,例如

  • IOC
  • 路由
  • 托管
  • 端点正在运行

似乎不正确的是验证。为了说明这一点,我将介绍一些现有的.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工具(我期待的)中得到了这个回应

enter image description here

所以这一切都很好。

.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示例验证器代码

enter image description here

但这一次我得到了一个非常不同的Http响应(一个我根本没想到的)。我明白了

enter image description here

可以看出,我们确实得到了正确的状态代码“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(..)的结果

我做错了什么?

1 个答案:

答案 0 :(得分:0)

latest v5 of ServiceStack现在available on MyGet似乎不再是问题。

发送此请求时:

enter image description here

返回预期的回复:

enter image description here

.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>