В качестве эксперимента я занимался идеей создания управляемого модуля IIS для изменения файлов CSS на лету. Предыстория состоит в том, что все наши веб-приложения имеют общий и общий номер версии, который мы добавляем к каждой JS, CSS и ссылке на изображение (в HTML), и я хотел изменить фактическое содержимое CSS, чтобы также добавить номер версии к ссылкам на изображения в CSS. Так, например:
span.warning { background-image: url(warning-icon.png) }
должно стать:
span.warning { background-image: url(warning-icon.png?123) }
Теперь я знаю, что существует много разных подходов к этой проблеме (и некоторые, возможно, лучше), но мне было интересно, сможет ли кто-нибудь ответить на мой вопрос, связанный с тем, с которым я играл.
До сих пор я узнал, что управляемый модуль HTTP не может напрямую изменять поток ответов (я думаю), и что правильный способ - добавить поток выходного фильтра, который выполняет эту работу. Я написал следующий тестовый модуль:
public class HttpCSSModule : IHttpModule
{
public void Init(HttpApplication httpApplication)
{
httpApplication.BeginRequest += new EventHandler(
(s, e) => AttachFilter((HttpApplication)s));
}
private void AttachFilter(HttpApplication httpApplication)
{
HttpRequest httpRequest = httpApplication.Context.Request;
HttpResponse httpResponse = httpApplication.Context.Response;
if (httpRequest.Path.EndsWith(".css", StringComparison.CurrentCultureIgnoreCase))
{
if (!string.IsNullOrEmpty(httpRequest.Url.Query))
{
httpResponse.Filter = new CSSResponseStreamFilter(
httpResponse.Filter, httpRequest.Url.Query);
}
}
}
public void Dispose()
{
}
private class CSSResponseStreamFilter : Stream
{
private Stream inner;
private string version;
private MemoryStream responseBuffer = new MemoryStream();
public CSSResponseStreamFilter(Stream inner, string version)
{
this.inner = inner;
this.version = version;
}
public override void Close()
{
if (responseBuffer.Length != 0)
{
string stylesheet = Encoding.ASCII.GetString(
responseBuffer.GetBuffer(), 0, (int)responseBuffer.Length);
// crude, just testing
string versionedStylesheet = stylesheet.
Replace(".png", ".png" + version).
Replace(".jpg", ".jpg" + version).
Replace(".gif", ".gif" + version);
byte[] outputBytes = Encoding.ASCII.GetBytes(versionedStylesheet);
innerStream.Write(outputBytes, 0, outputBytes.Length);
}
innerStream.Close();
}
public override void Write(byte[] buffer, int offset, int count)
{
responseBuffer.Write(buffer, offset, count);
}
// other Stream members
}
}
Модуль работает, но не всегда, и есть вещи, которые я не понимаю. Самая большая проблема заключается в том, что модуль не работает, когда включено статическое сжатие файлов. Когда статическое сжатие файлов включено, первый запрос к файлам CSS обслуживает файл как обычно, но, вероятно, IIS сохраняет версию gzipped и в любом запросе подпоследовательности мой пользовательский поток передается потоку gzipped. Я не нашел способа обнаружить его, но, возможно, есть более глубокая проблема в том, что я не совсем понимаю, как должны работать модули IIS. Кажется неправильным, что мой модуль должен делать какие-либо предположения о другом модуле, или, возможно, по крайней мере, можно определить порядок, в котором они обрабатывают запрос в конфигурации IIS. Однако это, похоже, не имеет смысла, поскольку каждый модуль может зарегистрироваться для обработки любых событий в жизненном цикле запроса.
Так что, если бы мне пришлось переформулировать свои мысли на более изощренные вопросы, вопросы были бы:
Как я могу убедиться в том, что мой модуль постобработки / модуля вызывается после того, как файл прочитан и отправлен сервером модулем StaticFileModule, но до StaticCompressionModule? И имеет ли этот вопрос смысл? Исходя из наблюдений выше, это кажется немного противоречивым.