Связывание модели Web API с многостраничными формами
Есть ли способ получить привязку модели (или что-то еще), чтобы выдать модель из запроса данных с несколькими данными в ASP.NET MVC Web API?
Я вижу различные записи в блогах, но все изменилось между публикацией и фактическим выпуском или они не показывают работу привязки модели.
Это устаревшее сообщение: Отправка данных HTML-формы
и так далее: Асинхронная загрузка файлов с использованием веб-API ASP.NET
Я нашел этот код (и немного изменил) где-то, который считывает значения вручную:
Модель:
public class TestModel
{
[Required]
public byte[] Stream { get; set; }
[Required]
public string MimeType { get; set; }
}
Контроллер:
public HttpResponseMessage Post()
{
if (!Request.Content.IsMimeMultipartContent("form-data"))
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
IEnumerable<HttpContent> parts = Request.Content.ReadAsMultipartAsync().Result.Contents;
string mimeType;
if (!parts.TryGetFormFieldValue("mimeType", out mimeType))
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
var media = parts.ToArray()[1].ReadAsByteArrayAsync().Result;
// create the model here
var model = new TestModel()
{
MimeType = mimeType,
Stream = media
};
// save the model or do something with it
// repository.Save(model)
return Request.CreateResponse(HttpStatusCode.OK);
}
Тест:
[DeploymentItem("test_sound.aac")]
[TestMethod]
public void CanPostMultiPartData()
{
var content = new MultipartFormDataContent { { new StringContent("audio/aac"), "mimeType"}, new ByteArrayContent(File.ReadAllBytes("test_sound.aac")) };
this.controller.Request = new HttpRequestMessage {Content = content};
var response = this.controller.Post();
Assert.AreEqual(response.StatusCode, HttpStatusCode.OK);
}
Этот код в основном хрупкий, не ремонтируемый и, кроме того, не обеспечивает привязки привязки модели или ограничений аннотации данных.
Есть ли лучший способ сделать это?
Обновление: Я видел этот пост, и это заставляет меня думать - мне нужно написать новый форматтер для каждой отдельной модели что я хочу поддержать?
Ответы
Ответ 1
@Mark Jones связался с моим сообщением в блоге http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/, который привел меня сюда. Я должен думать о том, как делать то, что вы хотите.
Я считаю, что если вы объедините мой метод вместе с TryValidateProperty(), вы сможете выполнить то, что вам нужно. Мой метод получит объект десериализован, однако он не обрабатывает никакой проверки. Вам нужно будет использовать рефлексию для прокрутки свойств объекта, а затем вручную вызвать TryValidateProperty() на каждом из них. Этот метод немного больше, но я не уверен, как это сделать.
http://msdn.microsoft.com/en-us/library/dd382181.aspx
http://www.codeproject.com/Questions/310997/TryValidateProperty-not-work-with-generic-function
Изменить: кто-то еще задал этот вопрос, и я решил закодировать его, чтобы убедиться, что он сработает. Вот мой обновленный код из моего блога с проверками проверки.
public class FileUpload<T>
{
private readonly string _RawValue;
public T Value { get; set; }
public string FileName { get; set; }
public string MediaType { get; set; }
public byte[] Buffer { get; set; }
public List<ValidationResult> ValidationResults = new List<ValidationResult>();
public FileUpload(byte[] buffer, string mediaType,
string fileName, string value)
{
Buffer = buffer;
MediaType = mediaType;
FileName = fileName.Replace("\"","");
_RawValue = value;
Value = JsonConvert.DeserializeObject<T>(_RawValue);
foreach (PropertyInfo Property in Value.GetType().GetProperties())
{
var Results = new List<ValidationResult>();
Validator.TryValidateProperty(Property.GetValue(Value),
new ValidationContext(Value)
{MemberName = Property.Name}, Results);
ValidationResults.AddRange(Results);
}
}
public void Save(string path, int userId)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
var SafeFileName = Md5Hash.GetSaltedFileName(userId,FileName);
var NewPath = Path.Combine(path, SafeFileName);
if (File.Exists(NewPath))
{
File.Delete(NewPath);
}
File.WriteAllBytes(NewPath, Buffer);
var Property = Value.GetType().GetProperty("FileName");
Property.SetValue(Value, SafeFileName, null);
}
}
Ответ 2
Существует хороший пример родоформата для загрузки файлов здесь http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/. Если бы я собирался иметь несколько контроллеров, принимающих загрузку файлов, тогда это был бы подход, который я бы принял.
P.S. Оглядываясь вокруг, это похоже на лучший пример для загрузки в контроллер http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web-api-rtm/
Обновление
Re: Полезность подхода Multipart, здесь описано , но это, в сущности, сводится к тому, что многопользовательский подход хорошо строится для значительно размерные бинарные данные и т.д.
Будет ли работать привязка модели DEFAULT?
Стандартное/стандартное связующее устройство для WebApi не построено, чтобы справиться с моделью, которую вы указали, то есть, которая смешивает простые типы и потоки и массивы байтов (не так просто)... Это цитата из статья, которая вдохновила lonetechie's:
"Простые типы" использует привязку модели. В сложных типах используются форматирующие элементы. "Простой тип" включает в себя: примитивы, TimeSpan, DateTime, Guid, Decimal, String или что-то с TypeConverter, который преобразует из строки
Использование байтового массива в вашей модели и необходимость создания этого из потока/содержимого запроса будет направлять вас на использование форматов вместо.
Отправить модель и файлы отдельно?
Лично я хотел бы отделить загрузку файла с модели... возможно, не вариант для вас... таким образом, вы бы отправили POST на тот же контроллер и маршрут, когда используете тип содержимого данных MultiPart, это вызовет файл, загружающий форматировщик, и когда вы используете application/json или x-www-form-urlencoded, тогда он будет делать привязку модели простого типа... Два POST могут быть не в этом вам, но это вариант...
Пользовательское связующее устройство?
У меня был небольшой успех с настраиваемое связующее устройство, вы можете что-то сделать с этим, возможно... это можно сделать родовым ( с некоторыми умеренными усилиями) и может быть зарегистрирован глобально в поставщике связующего для повторного использования...
Это может стоить игры?
public class Foo
{
public byte[] Stream { get; set; }
public string Bar { get; set; }
}
public class FoosController : ApiController
{
public void Post([ModelBinder(typeof(FileModelBinder))] Foo foo)
{
//
}
}
Пользовательское связующее устройство:
public class FileModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
public FileModelBinder()
{
}
public bool BindModel(
System.Web.Http.Controllers.HttpActionContext actionContext,
System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
if (actionContext.Request.Content.IsMimeMultipartContent())
{
var inputModel = new Foo();
inputModel.Bar = ""; //From the actionContext.Request etc
inputModel.Stream = actionContext.Request.Content.ReadAsByteArrayAsync()
.Result;
bindingContext.Model = inputModel;
return true;
}
else
{
throw new HttpResponseException(actionContext.Request.CreateResponse(
HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}