And*_*ius 47 c# file-upload .net-core asp.net-core
如何使用分段上传将文件(图像)和json数据列表上载到ASP.NET Core Web API控制器?
我可以成功收到一个文件列表,上传的multipart/form-data
内容类型如下:
public async Task<IActionResult> Upload(IList<IFormFile> files)
Run Code Online (Sandbox Code Playgroud)
当然,我可以使用默认的JSON格式化程序成功接收格式化为我的对象的HTTP请求正文:
public void Post([FromBody]SomeObject value)
Run Code Online (Sandbox Code Playgroud)
但是如何在一个控制器动作中将这两者结合起来呢?如何上传图像和JSON数据并将它们绑定到我的对象?
Bru*_*ell 36
有一个更简单的解决方案,深受Andrius的回答启发.通过使用ModelBinderAttribute
您不必指定模型或活页夹提供程序.这节省了大量代码.您的控制器操作如下所示:
public IActionResult Upload(
[ModelBinder(BinderType = typeof(JsonModelBinder))] SomeObject value,
IList<IFormFile> files)
{
// Use serialized json object 'value'
// Use uploaded 'files'
}
Run Code Online (Sandbox Code Playgroud)
代码背后JsonModelBinder
(或使用完整的NuGet包):
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;
}
}
Run Code Online (Sandbox Code Playgroud)
以下是上述控制器操作接受的原始http请求的示例Upload
.
甲multipart/form-data
请求被分成由指定的分开的多个部分,每个部分boundary=12345
.每个部分都在其Content-Disposition
-header中分配了一个名称.使用这些名称,默认ASP.Net-Core
知道哪个部分绑定到控制器操作中的哪个参数.
绑定的文件IFormFile
还需要filename
在请求的第二部分中指定as.Content-Type
不需要.
需要注意的另一件事是json部分需要反序列化为控制器动作中定义的参数类型.因此,在这种情况下,类型SomeObject
应该有一个属性key
类型string
.
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--
Run Code Online (Sandbox Code Playgroud)
Postman可用于调用操作并测试服务器端代码.这很简单,主要是UI驱动.创建一个新请求并在Body -Tab中选择表单数据.现在,您可以在reqeust的每个部分中选择文本和文件.
and*_*rob 24
我在前端使用 Angular 7,所以我使用了这个FormData
类,它允许您将字符串或 blob 附加到表单中。可以使用[FromForm]
属性将它们从控制器操作中的表单中拉出。我将文件添加到FormData
对象中,然后将希望与文件一起发送的数据字符串化,将其附加到FormData
对象,并在控制器操作中反序列化字符串。
像这样:
//front-end:
let formData: FormData = new FormData();
formData.append('File', fileToUpload);
formData.append('jsonString', JSON.stringify(myObject));
//request using a var of type HttpClient
http.post(url, formData);
//controller action
public Upload([FromForm] IFormFile File, [FromForm] string jsonString)
{
SomeType myObj = JsonConvert.DeserializeObject<SomeType>(jsonString);
//do stuff with 'File'
//do stuff with 'myObj'
}
Run Code Online (Sandbox Code Playgroud)
您现在拥有文件和对象的句柄。请注意,您在控制器操作的 params 列表中提供的名称必须与附加到FormData
前端对象时提供的名称相匹配。
And*_*ius 14
显然,没有内置的方法来做我想要的.所以我最终写了自己ModelBinder
来处理这种情况.我没有找到任何关于自定义模型绑定的官方文档,但我使用这篇文章作为参考.
自定义ModelBinder
将搜索使用属性修饰的FromJson
属性,并将来自多部分请求的字符串反序列化为JSON.我将我的模型包装在另一个具有模型和IFormFile
属性的类(包装器)中.
IJsonAttribute.cs:
public interface IJsonAttribute
{
object TryConvert(string modelValue, Type targertType, out bool success);
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
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;
// Look for FromJson attributes
var attribute = propInfo.GetCustomAttributes(typeof(FromJsonAttribute), false).FirstOrDefault();
if (attribute != null)
return new JsonModelBinder(context.Metadata.ModelType, attribute as IJsonAttribute);
}
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
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)
{
}
// Add custom binder provider in Startup.cs ConfigureServices
services.AddMvc(properties =>
{
properties.ModelBinderProviders.Insert(0, new JsonModelBinderProvider());
});
Run Code Online (Sandbox Code Playgroud)
遵循@ bruno-zell的出色回答,如果您只有一个文件(我没有使用进行测试IList<IFormFile>
),则也可以将控制器声明为:
public async Task<IActionResult> Create([FromForm] CreateParameters parameters, IFormFile file)
{
const string filePath = "./Files/";
if (file.Length > 0)
{
using (var stream = new FileStream($"{filePath}{file.FileName}", FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
// Save CreateParameters properties to database
var myThing = _mapper.Map<Models.Thing>(parameters);
myThing.FileName = file.FileName;
_efContext.Things.Add(myThing);
_efContext.SaveChanges();
return Ok(_mapper.Map<SomeObjectReturnDto>(myThing));
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用Bruno答案中显示的Postman方法来调用您的控制器。
归档时间: |
|
查看次数: |
22799 次 |
最近记录: |