0 1.6K ru

Пишем свой MediaTypeFormatter в ASP.NET WEB API 2 приложении

Categories: 💻 Programming

В этой статье мы расмотрим подробно как написать свой MediaTypeFormatter в ASP.NET WEB API 2 приложении.

Теория

MediaTypeFormatter - базовый класс, отвечающий за сериализацию и десериализацию строго типизированных объектов с помощью ObjectContent.

MediaTypeFormatter используется в одном из способов model binding'a данных и при формировании ответа с сервера.

IModelBinder - Определяет методы, которые требуются для связывателя модели. Может быть переопределен с помощью аттрибута [ModelBinder(typeof(MyModelBinder))]

Content-Type - Заголовок Content-Type передаваемый клиентом (например application/xml) указывает серверу что данные были отправлены в json формате.

Accept - Заголовок accept нужен для того что б запросить ответ в нужном формате (например JSON или XML)

 

Model binding в ASP.NET Web API 2

Model Bindings owin web api2

Каждый параметр необходимый для Action'a, может быть привязан к его значению одним из путей приведенных на картинке. Какой путь использует система привязки, зависит от того, какой тип и вид передачи этих данных:

  1. Если для Action'а необходимо обработать сложную модель с тела запроса ([FromBody]). Web API считывает тело запроса; экземпляр FormatterParameterBinding вызовет соответствующие MediaTypeFormatter классы, которые связывают значения с  a media type (используя MediaTypeFormatter), и в результате возвращается  новый сложный тип.
  2. Если данные передаються в строке URL или query string, этот URL-адрес передается в экземпляр IModelBinder, который использует IValueProvider для сопоставления значений модели, которая возвращает простой тип.
  3. Если существует кастомный HttpParameterBinding, система использует эту  привязку для создания значения, которое может быть приведено к любому типу к любому виду (простому или сложному) объекта.

 

Создаем свой MediaTypeFormatter

CustomFormatter 

    public class CustomFormatter : MediaTypeFormatter
    {
        public CustomFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/mytype"));
        }
        
        public override bool CanReadType(Type type)
        {
            // Устанавливаем что все типы данных могут проходить через наш форматтер.
            // Если нужно, можем фильтровать только определенные типы  
            // return type == typeof(Test); Такая запись позволит использовать этот форматтер только для типа Тест.
            // Все остальные будут отброшены и будут использовать стандартный (если он позволяет обработать такой content-type)
            return true;
        }

        public override bool CanWriteType(Type type)
        {
            // Устанавливаем что при возврате данных с Action'a можно использовать этот форматтер для переобразования данных всех типов (так же можно установить только для определенных типов.
            return true;
        }

        // Используеться при формировании ответа, когда Action вернул ответ, срабатывает этот метод    
        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
            TransportContext transportContext)
        {

            return Task.Run(() => Write(value, writeStream));
        }
        private static void Write(object value, Stream writeStream)
        {
            if (value == null) return;
            var json = JsonConvert.SerializeObject(value);
            var bytes = Encoding.UTF8.GetBytes("my response: " + json);
            writeStream.Write(bytes, 0, bytes.Length);
        }

        // Используеться при формировании Request модели 
        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            var taskCompletionSource = new TaskCompletionSource<object>();
            try
            {
                var memoryStream = new MemoryStream();
                readStream.CopyTo(memoryStream);
                var requestText = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());

                taskCompletionSource.SetResult(JsonConvert.DeserializeObject<Test>(requestText));
            }
            catch (Exception e)
            {
                taskCompletionSource.SetException(e);
            }
            return taskCompletionSource.Task;
        }
    }

    [Serializable]
    public class Test
    {
        public string Result { get; set; }
    }

Добавляем в WebApiConfig.Register 

config.Formatters.Add(new CustomFormatter());

Таким образом мы зарегистрировали новый Formatter, теперь все запросы сложных типов с заголовком Content-Type и Accept  и значением text/mytype, будут обрабатываться нашим CustomFormatter.

Создаем API controller который будет обрабатывать наши запросы

public class ValuesController : ApiController
    {
        // POST api/values
        public IHttpActionResult Post(Test value)
        {
            return Ok(new Test()
            {
                Result = "result test"
            });
        }
    }

 

Далее в постмене создаем запрос, устанавливаем Content-Type и Accept заголовки с значением text/mytype. Это означает что Read и Write будет обрабатываться нашим Formatter'ом.

postman headers

Делаем POST запрос на метод 

И получаем ответ, уже видоизмененный нашим форматтером.

Итог

MediaTypeFormatter - полезный инструмент для создания своих обработчиков сложных структур данных. Отлично подходят для написания обработчиков своих типов данных (отличающихся от json, xml или дополняющих их функционал). Так же работа со сложными данными, такими как медиа файлы (фото, видео, аудио) и другие.

В следующей статье мы разберем подробно как работать с IModelBinder и как создавать свои ModelBinderы

 

Comments:

Please log in to be able add comments.