Как упоминалось в нескольких других сообщениях (см. Ссылки ниже), я пытаюсь создать фильтры ответов, чтобы изменить контент, создаваемый другим веб-приложением.
У меня есть базовая логика преобразования строк, работающая и инкапсулированная в фильтры, производные от общего FilterBase. Однако логика должна работать с полным содержимым, а не с его фрагментами. Поэтому мне нужно кэшировать фрагменты по мере их записи и выполнять фильтрацию, когда все записи завершены.
Как показано ниже, я создал новый ResponseFilter, производный от MemoryStream. При записи содержимое кэшируется в другой MemoryStream. На Flush полное содержимое, теперь находящееся в MemoryStream, преобразуется в строку, и срабатывает логика фильтра. Затем измененное содержимое записывается обратно в исходный поток.
Однако при каждом втором запросе (в основном, когда новый фильтр создается поверх предыдущего) выполняется метод Flush предыдущего фильтра. В этот момент приложение падает в методе _outputStream.Write(), поскольку _cachedStream пуст.
Порядок мероприятия следующий:
- Первый запрос
- Метод записи называется
- Метод слива называется
- Метод закрытия называется
- Метод закрытия называется
- В этот момент приложение возвращается и отображается правильный контент.
- Второй запрос
- Метод слива называется
- Сбой приложения на _outputStream.Write. ArgumentOutOfRangeException (смещение).
- Продолжить после сбоя (в Visual Studio)
- Метод закрытия называется
У меня есть пара вопросов:
- Почему Close вызывается дважды?
- Почему вызывается Flush после вызова Closed?
- По словам Джея ниже, Flush может быть вызван до того, как поток будет полностью прочитан, где должна находиться логика фильтра? В закрытом? Во флеше, но с "при закрытии"?
- Какова правильная реализация фильтра ответов, который работает сразу со всем содержимым?
Примечание. Я получаю точно такое же поведение (за исключением событий Close), если не переопределяю метод Close.
public class ResponseFilter : MemoryStream
{
private readonly Stream _outputStream;
private MemoryStream _cachedStream = new MemoryStream(1024);
private readonly FilterBase _filter;
public ResponseFilter (Stream outputStream, FilterBase filter)
{
_outputStream = outputStream;
_filter = filter;
}
// Flush is called on the second, fourth, and so on, page request (second request) with empty content.
public override void Flush()
{
Encoding encoding = HttpContext.Current.Response.ContentEncoding;
string cachedContent = encoding.GetString(_cachedStream.ToArray());
// Filter the cached content
cachedContent = _filter.Filter(cachedContent);
byte[] buffer = encoding.GetBytes(cachedContent);
_cachedStream = new MemoryStream();
_cachedStream.Write(buffer, 0, buffer.Length);
// Write new content to stream
_outputStream.Write(_cachedStream.ToArray(), 0, (int)_cachedStream.Length);
_cachedStream.SetLength(0);
_outputStream.Flush();
}
// Write is called on the first, third, and so on, page request.
public override void Write(byte[] buffer, int offset, int count)
{
// Cache the content.
_cachedStream.Write(buffer, 0, count);
}
public override void Close()
{
_outputStream.Close();
}
}
// Example usage in a custom HTTP Module on the BeginRequest event.
FilterBase transformFilter = new MapServiceJsonResponseFilter();
response.Filter = new ResponseFilter(response.Filter, transformFilter);
Использованная литература: