Вопрос по c#-4.0 – Как правильно реализовать MediaTypeFormatter для обработки запросов типа «multipart / mixed»?

20

Рассмотрим веб-службу, написанную на ASP.NET Web API, для приема любых числовых файлов как «multipart / mixed». запрос. Мат вспомогательного метода выглядит следующим образом (при условии_client является примеромSystem.Net.Http.HttpClient):

<code>public T Post<T>(string requestUri, T value, params Stream[] streams)
{
    var requestMessage = new HttpRequestMessage();
    var objectContent = requestMessage.CreateContent(
        value,
        MediaTypeHeaderValue.Parse("application/json"),
        new MediaTypeFormatter[] {new JsonMediaTypeFormatter()},
        new FormatterSelector());

    var content = new MultipartContent();
    content.Add(objectContent);
    foreach (var stream in streams)
    {
        var streamContent = new StreamContent(stream);
        streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        streamContent.Headers.ContentDisposition =
            new ContentDispositionHeaderValue("form-data")
            {
                Name = "file",
                FileName = "mystream.doc"
            };
        content.Add(streamContent);
    }

    return _httpClient.PostAsync(requestUri, content)
        .ContinueWith(t => t.Result.Content.ReadAsAsync<T>()).Unwrap().Result;
}
</code>

Метод, принимающий запрос в подклассе ApiController, имеет следующую подпись:

<code>public HttpResponseMessage Post(HttpRequestMessage request)
{
    /* parse request using MultipartFormDataStreamProvider */
}
</code>

В идеале я хотел бы определить это следующим образом, где контакт, источник и цель извлекаются из «multipart / mixed». содержание, основанное на «имени»; свойство "Content-Disposition" заголовок.

<code>public HttpResponseMessage Post(Contact contact, Stream source, Stream target)
{
    // process contact, source and target
}
</code>

Однако при моей существующей подписи отправка данных на сервер приводит кInvalidOperationException с сообщением об ошибке:

No 'MediaTypeFormatter' is available to read an object of type 'HttpRequestMessage' with the media type 'multipart/mixed'.

В Интернете есть несколько примеров того, как отправлять и получать файлы с помощью веб-API ASP.NET и HttpClient. Однако я не нашел ни одного, который показал бы, как справиться с этой проблемой.

Я начал смотреть на реализацию кастомаMediaTypeFormatter и зарегистрируйте его в глобальной конфигурации. Тем не менее, пока легко разобраться с сериализацией XML и JSON вMediaTypeFormatter, неясно, как бороться с 'multipart / mixed' запросы, которые в значительной степени могут быть чем угодно.

Ваш Ответ

2   ответа
13

http://forums.asp.net/t/1777847.aspx/1?MVC4+Beta+Web+API+and+multipart+form+data

Вот фрагмент кода (опубликованный imran_ku07), который может помочь вам реализовать собственный форматировщик для обработки multipart / form-data:

public class MultiFormDataMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
{
    public MultiFormDataMediaTypeFormatter() : base()
    {
        this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
    }

    protected override bool CanReadType(Type type)
    {
        return true;
    }

    protected override bool CanWriteType(Type type)
    {
        return false;
    }

    protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
    {
        var contents = formatterContext.Request.Content.ReadAsMultipartAsync().Result;
        return Task.Factory.StartNew<object>(() =>
        {
            return new MultiFormKeyValueModel(contents);
        });
    }

    class MultiFormKeyValueModel : IKeyValueModel
    {
        IEnumerable<HttpContent> _contents;
        public MultiFormKeyValueModel(IEnumerable<HttpContent> contents)
        {
            _contents = contents;
        }


        public IEnumerable<string> Keys
        {
            get
            {
                return _contents.Cast<string>();
            }
        }

        public bool TryGetValue(string key, out object value)
        {
            value = _contents.FirstDispositionNameOrDefault(key).ReadAsStringAsync().Result;
            return true;
        }
    }
}

Затем вам нужно добавить этот форматер в ваше приложение. Если вы ведете себя самостоятельно, вы можете просто добавить его, включив:

config.Formatters.Insert(0, new MultiFormDataMediaTypeFormatter());

перед созданием экземпляра класса HttpSelfHostServer.

-- EDIT --

Для разбора двоичных потоков вам понадобится другой форматер. Вот то, что я использую для анализа изображений в одном из моих рабочих проектов.

class JpegFormatter : MediaTypeFormatter
{
    protected override bool CanReadType(Type type)
    {
        return (type == typeof(Binary));
    }

    protected override bool CanWriteType(Type type)
    {
        return false;
    }

    public JpegFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
    }

    protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
    {
        return Task.Factory.StartNew(() =>
            {
                byte[] fileBytes = new byte[stream.Length];
                stream.Read(fileBytes, 0, (int)fileBytes.Length);

               return (object)new Binary(fileBytes);
            }); 
    }

    protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext)
    {
        throw new NotImplementedException();
    }
}

В вашем контроллере / действии вы захотите сделать что-то вроде:

public HttpResponseMessage UploadImage(Binary File) {
 //do something with your file
}
Error: User Rate Limit Exceededbyterot.blogspot.com/2012/04/aspnet-web-api-series-part-5.html
Error: User Rate Limit ExceededhereError: User Rate Limit Exceeded bloudraak
2

https://stackoverflow.com/a/17073113/1944993 ответ Киран Чалла действительно хорош!

Основная часть:

Пользовательский в памяти MultiaprtFormDataStreamProvider:

    public class InMemoryMultipartFormDataStreamProvider : MultipartStreamProvider
{
    private NameValueCollection _formData = new NameValueCollection();
    private List<HttpContent> _fileContents = new List<HttpContent>();

    // Set of indexes of which HttpContents we designate as form data
    private Collection<bool> _isFormData = new Collection<bool>();

    /// <summary>
    /// Gets a <see cref="NameValueCollection"/> of form data passed as part of the multipart form data.
    /// </summary>
    public NameValueCollection FormData
    {
        get { return _formData; }
    }

    /// <summary>
    /// Gets list of <see cref="HttpContent"/>s which contain uploaded files as in-memory representation.
    /// </summary>
    public List<HttpContent> Files
    {
        get { return _fileContents; }
    }

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
    {
        // For form data, Content-Disposition header is a requirement
        ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
        if (contentDisposition != null)
        {
            // We will post process this as form data
            _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));

            return new MemoryStream();
        }

        // If no Content-Disposition header was present.
        throw new InvalidOperationException(string.Format("Did not find required '{0}' header field in MIME multipart body part..", "Content-Disposition"));
    }

    /// <summary>
    /// Read the non-file contents as form data.
    /// </summary>
    /// <returns></returns>
    public override async Task ExecutePostProcessingAsync()
    {
        // Find instances of non-file HttpContents and read them asynchronously
        // to get the string content and then add that as form data
        for (int index = 0; index < Contents.Count; index++)
        {
            if (_isFormData[index])
            {
                HttpContent formContent = Contents[index];
                // Extract name from Content-Disposition header. We know from earlier that the header is present.
                ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;
                string formFieldName = UnquoteToken(contentDisposition.Name) ?? String.Empty;

                // Read the contents as string data and add to form data
                string formFieldValue = await formContent.ReadAsStringAsync();
                FormData.Add(formFieldName, formFieldValue);
            }
            else
            {
                _fileContents.Add(Contents[index]);
            }
        }
    }

    /// <summary>
    /// Remove bounding quotes on a token if present
    /// </summary>
    /// <param name="token">Token to unquote.</param>
    /// <returns>Unquoted token.</returns>
    private static string UnquoteToken(string token)
    {
        if (String.IsNullOrWhiteSpace(token))
        {
            return token;
        }

        if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
        {
            return token.Substring(1, token.Length - 2);
        }

        return token;
    }}

Затем вы можете & quot; MemoryMultiPartDataStreamProvider & quot; у вас в webapi вот так:

var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataStreamProvider>(new InMemoryMultipartFormDataStreamProvider());

    //access form data
    NameValueCollection formData = provider.FormData;

    //access files
    IList<HttpContent> files = provider.Files;

Похожие вопросы