UPDATE:为tl; dr版本跳到底部
我有一个非常简单的JsonConverter子类,我正在使用Web API:
public class DbGeographyJsonConverter : JsonConverter
{
public override bool CanConvert(Type type)
{
return typeof(DbGeography).IsAssignableFrom(type);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = (string)reader.Value;
if (value.StartsWith("POINT", StringComparison.OrdinalIgnoreCase))
{
return DbGeography.PointFromText(value, DbGeography.DefaultCoordinateSystemId);
}
else if (value.StartsWith("POLYGON", StringComparison.OrdinalIgnoreCase))
{
return DbGeography.FromText(value, DbGeography.DefaultCoordinateSystemId);
}
else //We don't want to support anything else right now.
{
throw new ArgumentException();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((DbGeography)value).AsText());
}
}
问题是,在ReadJson
返回后,应用程序永远不会将绑定对象返回到操作方法,因为它似乎停留在无限的验证循环中。
当我暂停执行时,这是调用堆栈的顶部:
System.Web.Http.dll!System.Web.Http.Metadata.Providers.AssociatedMetadataProvider.GetMetadataForPropertiesImpl.AnonymousMethod__0()第40行C# System.Web.Http.dll!System.Web.Http.Metadata.ModelMetadata.Model.get()第85行C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(System.Web.Http.Metadata.ModelMetadata metadata,System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext,object container)第94行C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(System.Web.Http.Metadata.ModelMetadata metadata,System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext)第156行C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(System.Web.Http.Metadata.ModelMetadata metadata,System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext,object container)第130行C# System.Web.Http.dll!System.Web.Http.Validation.DefaultBodyModelValidator.ValidateElements(System.Collections.IEnumerable model,System.Web.Http.Validation.DefaultBodyModelValidator.ValidationContext validationContext)第176行C#
之后,DefaultBodyModelValidator.Validation *调用模式一遍又一遍地重复。每次我暂停执行时,它似乎都处于相同的深度,所以它似乎没有递归更深。
如果我强制JsonConverter返回null
,控件将返回API控制器操作方法,我假设因为没有任何内容可以验证。
我没有留下脑汁来解决这个问题。我做错了什么?
更新:脑汁有点补充,我已经介绍了大部分代码,看来在验证模型时,DefaultBodyModelValidator
正在钻进{{1}并陷入一个循环读取属性的地方。我真的不在乎确切地找到确切位置,因为我不希望SqlTypesAssembly
钻取DefaultBodyModelValidator
类型实例。
模型验证没有理由深入到DbGeography
类。我需要弄清楚如何让DbGeography
方法为MediaTypeFormatterCollection.IsTypeExcludedFromValidation
返回true,这将导致typeof(DbGeography)
对任何DefaultBodyModelValidator
实例执行浅层验证。那么现在的问题是 - 如何从模型验证中排除类型? DbGeography
ShouldValidateType
方法被标记为虚拟,但是在启动时是否有一种简单的方法可以添加排除类型?
答案 0 :(得分:32)
这个问题是错误还是Web API的限制,我不知道,但这是我的解决方法:
首先,我们需要对DefaultBodyModelValidator
进行子类化并覆盖ShouldValidateType
方法。
public class CustomBodyModelValidator : DefaultBodyModelValidator
{
public override bool ShouldValidateType(Type type)
{
return type!= typeof(DbGeography) && base.ShouldValidateType(type);
}
}
现在在global.asax的Application_Start
方法中,添加
GlobalConfiguration.Configuration.Services.Replace(typeof(IBodyModelValidator), new CustomBodyModelValidator());
就是这样。现在将对DbGeography
类型实例执行浅层验证,并且所有内容都能很好地绑定。
答案 1 :(得分:1)
只是有完全相同的问题,但随后是自定义类型。经过相当多的研究,结果证明这个线程的知识非常符合逻辑。自定义类具有公共只读属性,该属性返回同一类的另一个实例。验证器会查看类的所有属性(即使您根本不进行任何验证)并获取值。如果你的类返回同一个类的新实例,这会一次又一次地发生...... 看起来Geography类中的StartPoint属性具有同样的问题。 https://msdn.microsoft.com/en-us/library/system.data.spatial.dbgeography.startpoint(v=vs.110).aspx
答案 2 :(得分:1)
joelmdev的回答引导我朝着正确的方向前进,但是在MVC和WebApi 5.2.3中使用我的WebApi配置时,放置在Global.asax中时不会调用新的验证器。
解决方案是将其与其他WebApi路由一起放在我的WebApiConfig.Register方法中:
import java.io.IOException;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ListObjectsV2Request;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3ObjectSummary;
public class ListKeys {
private static String bucketName = "***bucket name***";
public static void main(String[] args) throws IOException {
AmazonS3 s3client = new AmazonS3Client(new ProfileCredentialsProvider());
try {
System.out.println("Listing objects");
final ListObjectsV2Request req = new ListObjectsV2Request().withBucketName(bucketName);
ListObjectsV2Result result;
do {
result = s3client.listObjectsV2(req);
for (S3ObjectSummary objectSummary :
result.getObjectSummaries()) {
System.out.println(" - " + objectSummary.getKey() + " " +
"(size = " + objectSummary.getSize() +
")");
}
System.out.println("Next Continuation Token : " + result.getNextContinuationToken());
req.setContinuationToken(result.getNextContinuationToken());
} while(result.isTruncated() == true );
} catch (AmazonServiceException ase) {
System.out.println("Caught an AmazonServiceException, " +
"which means your request made it " +
"to Amazon S3, but was rejected with an error response " +
"for some reason.");
System.out.println("Error Message: " + ase.getMessage());
System.out.println("HTTP Status Code: " + ase.getStatusCode());
System.out.println("AWS Error Code: " + ase.getErrorCode());
System.out.println("Error Type: " + ase.getErrorType());
System.out.println("Request ID: " + ase.getRequestId());
} catch (AmazonClientException ace) {
System.out.println("Caught an AmazonClientException, " +
"which means the client encountered " +
"an internal error while trying to communicate" +
" with S3, " +
"such as not being able to access the network.");
System.out.println("Error Message: " + ace.getMessage());
}
}
}
答案 3 :(得分:0)
如果您在Mvc应用程序中遇到同样的问题,则可能对此答案感兴趣。它并不适合每个人,但是由于我对纬度和经度非常感兴趣,因此我最终解决此问题的方法不是忽略DbGeography属性,而是指示模型绑定程序如何正确验证它们,或者至少我要它们如何已验证。这种方法允许标准验证属性属性起作用,并允许我对纬度和经度使用常规的HTML控件。我将它们作为一对进行验证,因为最终它们将被绑定到模型类上的单个DbGepgraphy属性,因此仅靠其中一个是不足以被视为有效的。另外,我认为这需要引用function getStyles() {
cy.wait(15000); // The page has definitely loaded and applied all styles by now
cy.get('.el-on-the-page *').then((elements) => {
...
Nuget程序包,该程序包与所使用的SQL Server的目标版本相对应,但是您可能已经在引用它。
Microsoft.SqlServer.Types
然后使用它的自定义提供程序:
using System;
using System.Data.Entity.Spatial;
using System.Web.Mvc;
...
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var latitudePropertyValue = bindingContext.ValueProvider.GetValue(string.Concat(bindingContext.ModelName, ".", nameof(DbGeography.Latitude)));
var longitudePropertyValue = bindingContext.ValueProvider.GetValue(string.Concat(bindingContext.ModelName, ".", nameof(DbGeography.Longitude)));
if (!string.IsNullOrEmpty(latitudePropertyValue?.AttemptedValue)
&& !string.IsNullOrEmpty(longitudePropertyValue?.AttemptedValue))
{
if (decimal.TryParse(latitudePropertyValue.AttemptedValue, out decimal latitude)
&& decimal.TryParse(longitudePropertyValue.AttemptedValue, out decimal longitude))
{
// This is not a typo - longitude does come before latitude here
return DbGeography.FromText($"POINT ({longitude} {latitude})", DbGeography.DefaultCoordinateSystemId);
}
}
return null;
}
}
然后在Global.asax.cs的Application_Start()中:
using System;
using System.Data.Entity.Spatial;
using System.Web.Mvc;
...
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
if (modelType == typeof(DbGeography))
{
return new CustomModelBinder();
}
return null;
}
}
然后在我的模型中,这使我可以只使用常规的RequiredAttribute:
ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
最后,要在视图中使用它,我创建了显示和编辑器模板。 在〜/ Views / Shared / DisplayTemplates / DbGeography.cshtml中:
[Display(Name = "Location")]
[Required(ErrorMessage = "You must specify a location")]
public DbGeography Location { get; set; }
在〜/ Views / Shared / EditorTemplates / DbGeography.cshtml中:
@model System.Data.Entity.Spatial.DbGeography
@Html.LabelFor(m => m.Latitude),
@Html.LabelFor(m => m.Longitude)
然后我可以在任何其他常规视图中使用此编辑器模板,如下所示:
@model System.Data.Entity.Spatial.DbGeography
@Html.TextBoxFor(m => m.Latitude),
@Html.TextBoxFor(m => m.Longitude)
您也可以使用隐藏字段在编辑器视图,并添加一些JavaScript到您的模板,以建立一个谷歌地图,例如,提供的地图脚本还会选择当一个坐标重新设置隐藏字段的值。>
答案 4 :(得分:0)
如果您在WebAPI 5.2.3中遇到此问题,那么解决此问题的唯一方法对我有用,是使用一种我在此描述的技术的变体:https://stackoverflow.com/a/40534310/2001934
基本上,我在模型中的DbGeography属性上使用了一个自定义的required属性,在MVC中,该属性完全可以像普通的Required属性一样工作,但是在WebApi中,我添加了一个附加的属性适配器,该属性适配器总是替换列表中的 带有新的空列表的客户端验证规则,因此在WebApi模型绑定中不对那些相同的属性执行验证。
此答案唯一缺少的是如何替换WebApi中的现有ModelValidatorProvider:
DataAnnotationsModelValidationFactory factory = (p, a) => new DataAnnotationsModelValidator(
new List<ModelValidatorProvider>(), new CustomRequiredAttribute()
);
DataAnnotationsModelValidatorProvider provider = new DataAnnotationsModelValidatorProvider();
provider.RegisterAdapterFactory(typeof(CustomRequiredAttribute), factory);
GlobalConfiguration.Configuration.Services.Replace(typeof(ModelValidatorProvider), provider);