Связывание модели 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"));
        }
    }
}