这两天遇到一个需求,需要处理动态类型数据,这个数据,既要能在web上传输,页面上展示,又能持久化到数据库。我首先想到的,就是用json字符串来处理,拿到json数据后,再在不同的上下文环境中去解析。
为了简单,我把类型定义为了object,但这种类型不能被EF序列化,所以我们需要做一些额外的转换工作。
思路也很简单,就是利用newtonsoft json来序列化与反序列化。
首先我们看看模型定义
public Class Book
{
[Key]
public string Id { get; set }
public string Name { get; set; }
[NotMapped]
public object Config { get; set; }
}
然后通过 Fluent Api 来实现
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.Property(e => e.Config)
.HasConversion(
v => JsonConvert.SerializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
v => JsonConvert.DeserializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })
);
}
以上,就完成了实体模型中带动态类型数据的持久化。
那么接下来就是如何在页面上显示,以及从页面接收数据了。
微软提供了一套完整的模型绑定库,可以应对绝大多数数据到模型的绑定,一般来说,我们是不用自己处理的,但是由于object没有对应的处理,所以我们需要自己写一个自定义模型绑定库。
首先,实现IModelBinder接口,处理object类型的绑定工作
public class MyBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// 尝试按名称获取数据
// 如果在应用 ModelBinder 的时候,指定了 Name ,那么这个地方的 modelName
// 就是 Name.属性名 这样的形式
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
// 拿到数据后,用 newtonsoft json 处理
var value = valueProviderResult.FirstValue;
try
{
var result = JsonConvert.DeserializeObject(value);
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
// 加上这句,可以将错误的json字符串重新展示到页面上
bindingContext.ModelState.SetModelValue(modelName, result, value);
bindingContext.ModelState.TryAddModelError(modelName, "无效的json数据");
}
return Task.CompletedTask;
}
}
接下来定义一个Provider,并将其添加到DI中
public class MyBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (null == context)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(object))
{
return new BinderTypeModelBinder(typeof(MyBinder));
}
return null;
}
}
// 添加到 DI 中
services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new MyBinderProvider());
})
// 替换System.Text.Json
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
将定义好的 ModelBinder 应用到对应的属性上
public Class Book
{
[Key]
public string Id { get; set }
public string Name { get; set; }
[NotMapped]
[ModelBinder(BinderType = typeof(MyBinder))]
public object Config { get; set; }
}
最后就是如何在页面上显示了,方法很简单,就是直接利用newtonsoft json显示字符串,那种按模板自动生成的方法有点难度,我还搞不定^_^。
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Newtonsoft.Json.JsonConvert.SerializeObject(item.Config)
</td>
</tr>
}
</tbody>
编辑页面直接用模板生成的话,不用什么特殊处理,已经可以正确显示了。