在反序列化期间有选择地转义字符串中的 HTML
Selectively escape HTML in strings during deserialization
我想写一个 JsonConverter
,它在字符串中转义 HTML,除非应用了 [AllowHtml]
属性;
private class ObjectWithStrings
{
// will be HTML-escaped
public string Name { get; set; }
// won't be escaped
[AllowHtml]
public string Unsafe { get; set; }
}
所以我正在尝试使用自定义 ReadJson 编写 JsonConverter 属性;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var s = (string)reader.Value;
if (s == null)
{
return null;
}
// here I need to get a PropertyInfo so I can call GetCustomAttribute<AllowHtmlAttribute>();
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(s, useNamedEntities: true);
return encoded;
}
我的差距是我看不到 Json.Net 是否会让我知道我正在阅读的 属性。因此,我不知道如何获取 属性 的自定义属性。
有没有办法找出 属性 我正在连载的内容,或者针对此类内容推荐的不同模式?
编辑:我没有写清楚问题;我试图编写一个 JsonConverter
来反序列化 字符串, —— 请参见上面 CanConvert()
的实现。我怀疑选择是我问题的开始;我可能需要反序列化具有字符串属性的对象,并执行标准反序列化,除非反序列化特定属性。
在自定义 JsonConverter
中,您可以通过从 Path
属性 中挑选出来找到要反序列化的 JSON 属性 的名称来自 JsonReader
.
string propertyName = reader.Path.Split('.').Last();
但是,这并不能解决你的整体问题。假设 JSON 属性 的名称与您的目标 class 属性 匹配,您仍然需要一种方法来获取父对象类型,以便您可以从中获取自定义属性它。不幸的是,您无法在转换器内部使用此信息。转换器旨在仅负责它表示可以转换的对象类型(在您的情况下为字符串),以及该对象的子属性(在本例中为 none,因为字符串是原始类型)。因此,为了使其工作,需要编写转换器以在 parent class 上运行,然后需要处理 [=55] 的所有字符串属性=].由于您的目标似乎是将 HTML 编码行为应用于所有 classes 中的所有字符串,因此您需要一个通用转换器来处理所有非原始类型,这可能会变得非常混乱,具体取决于关于您要反序列化的内容的广度。
幸运的是,有更好的方法。除了使用 JsonConverter
,您可以结合使用自定义 IContractResolver
和 IValueProvider
来解决这个问题。 ContractResolver
更适合解决此类您希望广泛应用某种行为的问题。
下面是您需要的代码示例。 CustomResolver
class 扩展了 Json.Net 提供的 DefaultContractResolver
。 CreateProperties()
方法检查由基本解析器创建的 JsonProperty
对象,并将内部 HtmlEncodingValueProvider
class 的实例附加到任何没有 [AllowHtml]
属性应用。每个值提供者稍后通过 SetValue()
方法处理其目标字符串 属性 的实际编码。
public class CustomResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
// Find all string properties that do not have an [AllowHtml] attribute applied
// and attach an HtmlEncodingValueProvider instance to them
foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.GetCustomAttribute(typeof(AllowHtmlAttribute), true) == null)
{
prop.ValueProvider = new HtmlEncodingValueProvider(pi);
}
}
return props;
}
protected class HtmlEncodingValueProvider : IValueProvider
{
PropertyInfo targetProperty;
public HtmlEncodingValueProvider(PropertyInfo targetProperty)
{
this.targetProperty = targetProperty;
}
// SetValue gets called by Json.Net during deserialization.
// The value parameter has the original value read from the JSON;
// target is the object on which to set the value.
public void SetValue(object target, object value)
{
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode((string)value, useNamedEntities: true);
targetProperty.SetValue(target, encoded);
}
// GetValue is called by Json.Net during serialization.
// The target parameter has the object from which to read the string;
// the return value is the string that gets written to the JSON
public object GetValue(object target)
{
// if you need special handling for serialization, add it here
return targetProperty.GetValue(target);
}
}
}
要使用解析器,请创建一个新的 JsonSerializerSettings
实例,然后将其 ContractResolver
属性 设置为自定义解析器的新实例,并将设置传递给 JsonConvert.DeserializeObject()
方法。
这是一个简短的演示:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""Name"" : ""<b>Foo Bar</b>"",
""Description"" : ""<p>Bada Boom Bada Bing</p>"",
}";
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json, settings);
Console.WriteLine("Name: " + foo.Name);
Console.WriteLine("Desc: " + foo.Description);
}
}
class Foo
{
public string Name { get; set; }
[AllowHtml]
public string Description { get; set; }
}
class AllowHtmlAttribute : Attribute { }
这是输出。请注意,Name
属性 得到 HTML 编码,而 Description
属性 没有。
Name: <b>Foo Bar</b>
Desc: <p>Bada Boom Bada Bing</p>
Fiddle: https://dotnetfiddle.net/cAg4NC
我想写一个 JsonConverter
,它在字符串中转义 HTML,除非应用了 [AllowHtml]
属性;
private class ObjectWithStrings
{
// will be HTML-escaped
public string Name { get; set; }
// won't be escaped
[AllowHtml]
public string Unsafe { get; set; }
}
所以我正在尝试使用自定义 ReadJson 编写 JsonConverter 属性;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var s = (string)reader.Value;
if (s == null)
{
return null;
}
// here I need to get a PropertyInfo so I can call GetCustomAttribute<AllowHtmlAttribute>();
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(s, useNamedEntities: true);
return encoded;
}
我的差距是我看不到 Json.Net 是否会让我知道我正在阅读的 属性。因此,我不知道如何获取 属性 的自定义属性。
有没有办法找出 属性 我正在连载的内容,或者针对此类内容推荐的不同模式?
编辑:我没有写清楚问题;我试图编写一个 JsonConverter
来反序列化 字符串, —— 请参见上面 CanConvert()
的实现。我怀疑选择是我问题的开始;我可能需要反序列化具有字符串属性的对象,并执行标准反序列化,除非反序列化特定属性。
在自定义 JsonConverter
中,您可以通过从 Path
属性 中挑选出来找到要反序列化的 JSON 属性 的名称来自 JsonReader
.
string propertyName = reader.Path.Split('.').Last();
但是,这并不能解决你的整体问题。假设 JSON 属性 的名称与您的目标 class 属性 匹配,您仍然需要一种方法来获取父对象类型,以便您可以从中获取自定义属性它。不幸的是,您无法在转换器内部使用此信息。转换器旨在仅负责它表示可以转换的对象类型(在您的情况下为字符串),以及该对象的子属性(在本例中为 none,因为字符串是原始类型)。因此,为了使其工作,需要编写转换器以在 parent class 上运行,然后需要处理 [=55] 的所有字符串属性=].由于您的目标似乎是将 HTML 编码行为应用于所有 classes 中的所有字符串,因此您需要一个通用转换器来处理所有非原始类型,这可能会变得非常混乱,具体取决于关于您要反序列化的内容的广度。
幸运的是,有更好的方法。除了使用 JsonConverter
,您可以结合使用自定义 IContractResolver
和 IValueProvider
来解决这个问题。 ContractResolver
更适合解决此类您希望广泛应用某种行为的问题。
下面是您需要的代码示例。 CustomResolver
class 扩展了 Json.Net 提供的 DefaultContractResolver
。 CreateProperties()
方法检查由基本解析器创建的 JsonProperty
对象,并将内部 HtmlEncodingValueProvider
class 的实例附加到任何没有 [AllowHtml]
属性应用。每个值提供者稍后通过 SetValue()
方法处理其目标字符串 属性 的实际编码。
public class CustomResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
// Find all string properties that do not have an [AllowHtml] attribute applied
// and attach an HtmlEncodingValueProvider instance to them
foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.GetCustomAttribute(typeof(AllowHtmlAttribute), true) == null)
{
prop.ValueProvider = new HtmlEncodingValueProvider(pi);
}
}
return props;
}
protected class HtmlEncodingValueProvider : IValueProvider
{
PropertyInfo targetProperty;
public HtmlEncodingValueProvider(PropertyInfo targetProperty)
{
this.targetProperty = targetProperty;
}
// SetValue gets called by Json.Net during deserialization.
// The value parameter has the original value read from the JSON;
// target is the object on which to set the value.
public void SetValue(object target, object value)
{
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode((string)value, useNamedEntities: true);
targetProperty.SetValue(target, encoded);
}
// GetValue is called by Json.Net during serialization.
// The target parameter has the object from which to read the string;
// the return value is the string that gets written to the JSON
public object GetValue(object target)
{
// if you need special handling for serialization, add it here
return targetProperty.GetValue(target);
}
}
}
要使用解析器,请创建一个新的 JsonSerializerSettings
实例,然后将其 ContractResolver
属性 设置为自定义解析器的新实例,并将设置传递给 JsonConvert.DeserializeObject()
方法。
这是一个简短的演示:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""Name"" : ""<b>Foo Bar</b>"",
""Description"" : ""<p>Bada Boom Bada Bing</p>"",
}";
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json, settings);
Console.WriteLine("Name: " + foo.Name);
Console.WriteLine("Desc: " + foo.Description);
}
}
class Foo
{
public string Name { get; set; }
[AllowHtml]
public string Description { get; set; }
}
class AllowHtmlAttribute : Attribute { }
这是输出。请注意,Name
属性 得到 HTML 编码,而 Description
属性 没有。
Name: <b>Foo Bar</b>
Desc: <p>Bada Boom Bada Bing</p>
Fiddle: https://dotnetfiddle.net/cAg4NC