1、使用自定义包装类MyModelWrapper接收上传的文件及JSON数据
1)IJsonAttribute.cs文件文件代码
public interface IJsonAttribute
{
object TryConvert(string modelValue, Type targertType, out bool success);
}
2)FromJsonAttribute.cs文件代码
using Newtonsoft.Json;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class FromJsonAttribute : Attribute, IJsonAttribute
{
public object TryConvert(string modelValue, Type targetType, out bool success)
{
var value = JsonConvert.DeserializeObject(modelValue, targetType);
success = value != null;
return value;
}
}
3)JsonModelBinderProvider.cs文件代码
public class JsonModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType)
{
var propName = context.Metadata.PropertyName;
var propInfo = context.Metadata.ContainerType?.GetProperty(propName);
if(propName == null || propInfo == null)
return null;
// 查找FromJson标签
var attribute = propInfo.GetCustomAttributes(typeof(FromJsonAttribute), false).FirstOrDefault();
if (attribute != null)
return new JsonModelBinder(context.Metadata.ModelType, attribute as IJsonAttribute);
}
return null;
}
}
4)JsonModelBinder.cs文件代码
public class JsonModelBinder : IModelBinder
{
private IJsonAttribute _attribute;
private Type _targetType;
public JsonModelBinder(Type type, IJsonAttribute attribute)
{
if (type == null) throw new ArgumentNullException(nameof(type));
_attribute = attribute as IJsonAttribute;
_targetType = type;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// Check the value sent in
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Attempt to convert the input value
var valueAsString = valueProviderResult.FirstValue;
bool success;
var result = _attribute.TryConvert(valueAsString, _targetType, out success);
if (success)
{
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
}
使用示例代码:
public class MyModelWrapper
{
public IList<IFormFile> Files { get; set; }
[FromJson]
public MyModel Model { get; set; } // <-- JSON will be deserialized to this object
}
// Controller action:
public async Task<IActionResult> Upload(MyModelWrapper modelWrapper)
{
}
// 在Startup.cs配置服务中添加自定义绑定器提供程序
services.AddMvc(properties =>
{
properties.ModelBinderProviders.Insert(0, new JsonModelBinderProvider());
});
2、使用ModelBinderAttribute标签实现
使用更简单的方法,更少的代码,不使用包装类来实现。
1)实现JsonModelBinder类
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class JsonModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException(nameof(bindingContext));
}
// Check the value sent in
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None) {
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Attempt to convert the input value
var valueAsString = valueProviderResult.FirstValue;
var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
if (result != null) {
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
}
2)Controller中Upload方法代码
public IActionResult Upload(
[ModelBinder(BinderType = typeof(JsonModelBinder))] SomeObject value,
IList<IFormFile> files)
{
// Use serialized json object 'value'
// Use uploaded 'files'
}
下面是控制器action Upload接受的原始http请求的一个示例,
multipart/form-data请求被分成多个部分,每个部分由指定的boundary=12345分隔。每个部分都在其Content-Disposition-header中分配了一个名称。这些名称默认为ASP.Net-Core知道哪个部分绑定到controller action中的哪个参数。
绑定到IFormFile的文件还需要像请求的第二部分那样指定filename。不需要Content-Type。
另一件需要注意的事情是json部分需要反序列化为controller操作中定义的参数类型。在本例中,类型SomeObject应该有一个string类型的属性key:
POST http://localhost:5000/home/upload HTTP/1.1 Host: localhost:5000 Content-Type: multipart/form-data; boundary=12345 Content-Length: 218 --12345 Content-Disposition: form-data; name="value" {"key": "value"} --12345 Content-Disposition: form-data; name="files"; filename="file.txt" Content-Type: text/plain This is a simple text file --12345--
3、用Postman测试
可以使用Postman调用操作并测试服务器端代码。这非常简单,主要是界面操作。创建一个新请求并在body选项卡中选择form-data。可以为reqeust的每个部分选择文本和文件。