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的每个部分选择文本和文件。