четверг, 18 ноября 2010 г.

XML Parsing Error: no element found

Гугл говорит, что не только я сталкивался с такой проблемой, но причина к сожалению у каждого разная. Но в результате все видят типо:
и пустой html.

Свою ошибку вы можете посмотреть в Application_Error. У меня была "The resource URL cannot be longer than 1024 characters. If using a CompositeScriptReference, reduce the number of ScriptReferences it contains, or combine them into a single static file and set the Path property to the location of it." Потому, что в в ScripMapanger\CompositeScript было много скриптов. До этого я прогнал все файлы в JSLint, но ошибок не нашел. Вот такое г..вно этот CompositeScript, лучше самому скомбинировать и перезжать на продакшен. Как-нибудь напишу решение.

воскресенье, 24 октября 2010 г.

Как заставить браузер принудительно кэшировать картинки

Недавно сталкнулся с проблемой, когда динамически меняешь картинки жаваскриптом, то они мигают при смене первое время. Это из-за того что тратится время на загрузку по требованию. Для решения проблемы надо просто картинки подгрузить раньше на загрузке страницы, выглядит это так:
var preloadImages = {};
preloadImages["image1"] = 'Images/image1.png';
preloadImages["image2"] = 'Images/image2.png';
...
preloadImages["image3"] = 'Images/image3.png';
preloadImages["default"] = 'Images/default.png';

function changeImage(key) {
    if (typeof key != 'undefined') {
        $('#image').attr('src', preloadImages[key]);
    }
    else {
        $('#image').attr('src', preloadImages["default"]);
    }
};

(function loadImages() {
    for (key in preloadImages) {
    var img = new Image();
    img.src = preloadImages[key];
    }
})();

Затем можно использовать типо onmouseover="changeImage('image1');" onmouseout="changeImage();".
По идее все должно работать, но картинки все равно мигают :/ Firebug показывает, что картинки подгружены. Но тогда я увидел следующее

В запросе идет хедер, что кэшировать не надо, а в ответе нифига. Картинка просто не кэшируется и каждый раз когда я меняю src она запрашивается заново. Осталось включить кэширование картинок и вроде бы должно заработать. Настройку кэширования статического контента в IIS6 можно посмотреть здесь. Ниже показано как настроить тоже самое в IIS7.
Сначала выбираем папку с картинками (тоже самое можно сделать и для css папка), затем открываем "Заголовки ответов HTTP".
Последовательность действий сверху вниз. После нажатия OK у вас будет 3 новых вещи:
1. В папке для которой мы настраивали кеш (Img) появится web.config (его надо включить в solution).

2. В заголовке ответа появится хедер Cache-Control, говорящий о том, что картинка кешируется. Даже если следующий раз браузер захочет потянуть картинку, то в firebug вы увидите в ответ на GET запрос не 200 код, а 206. Значит картинка берется из кэша.
3. Ну и 3 это результат, картинки не мигают.

вторник, 18 мая 2010 г.

Как отформатировать сжатый JS файл

Иногда для отладки или для личных нужд вам нужно выровнять сжатый JS файл, который представлен в виде строки, без отступов, пробелов, переносов и тп. Это можно сделать online - вставил, нажал, скопировал обратно (можно выставить размер отступов и тп). И там же есть ссылка на саму утилиту, она open source.

вторник, 11 мая 2010 г.

Как определить к какому пулу относиться w3wp процесс в IIS 7

Если у вас сайт развернут на IIS 7, для того чтоб его отдебажить, надо приэтачиться к процессу w3wp.exe. В одно время у вас может работать несколько сайтов и каждый может принадлежать к своему пулу. Когда вы этачитесь к процессу (рисунок ниже), то начинаем гадать, попал не попал, подсветились брейкпоинты или нет :))


Для того чтоб точно попасть, можно посмотреть, что к чему относится с помощью утилиты для администрирования IIS с командной строки - APPCMD, которая находится по пути '%systemroot%\system32\inetsrv\'. Детально можете разобраться по докам или '/?'. Нам нужно только 'WP администрирование рабочих процессов'. Пример использования и результат ниже. Мы видим ID процесса и пул к которому сайт в этом процессе относится.

пятница, 7 мая 2010 г.

Индикация прогресса при длительных операциях в ASP.NET

Я думаю вы сталкивались с ситуацией, когда нужно что-то импортить, например CSV файл с продуктами, кастомерами, синхронизировать базы или работать с каким то тормознутым веб сервисом. Во всех перечисленных и подобных случаях страница "лочится" пока не закончится операция, но нам хотелось бы видеть какой-то прогресс, о чем сегодня и поговорим.
Недавно мне нужно было импортить продукты из индусского сервиса, не важно как, главное что страница лочилась на 1-2 минуты и непонятно, что там происходило и когда оно завершалось.
Существует несколько решений как можно узнать прогресс на длительной операции:
  1. Сохранять прогресс в сессию, а на клиенте должен быть таймер, который после запуска длительной операции будет опрашивать страницу с какой-то периодичностью, например каждую секунду или тп. Этот метод описан здесь;
  2. Создать вспомогательную страницу в iframe, при запуске которой в OnLoad сразу начинается длительная операция. Во время прогресса надо писать в Response вызов JS функции, которая будет вызываться в родительском окне, с которого был запущен iframe. Этот метод описан здесь;
  3. Сделать страницу асинхронной, через Async или ICallbackEventHandler Interface и опять-таки через таймер опрашивать сервер. Это способ можно отнести как вариация второго.
Из всех перечисленных мне не нравится никакой :)
  1. Потому, что надо делать вспомогательную страницу. Хоть автор и советует использовать только этот метод, но мне он кажется недоделаным.
  2. Потому, что както-то по индусски... опрашивать постоянно сервер? может в каком-то случае это и будет лучший вариант, но не сегодня.
  3. Ограниченные возможности по передаче параметров от клиента к серверу и наоборот. Можно передать, что угодно, но только в одной строке. А дальше думать формат, парсить... ну и + оргументы выше в пункте 2.
Мое решение похожее больше на 1 вариант, но без iframe. В результате получим примерно следующее:



На картинках выше показана индикация прогресса во время импорта продуктов, категорий и брендов из сервиса. Initialization показывает прогресс когда выполняется аутентификация или может создание вспомогательный классов, по вашему желанию. В мое случае я логинился на сервис. А теперь как это делалось. Для этого нам понадобится одна! страница, класс с длительной операцией, интерфейс через который будет уведомляться страница о прогрессе, пару js функций которые будут двигать прогресс бар и немного css для прогресс бара.

public interface IProgressReporter
    {
        void ReportInitializationFinished();
        void ReportCategoriesProgress(int count, int completed);
        void ReportProductsProgress(int count, int completed);
        void ReportBrandsProgress(int count, int completed);
        void ReportException(Exception exception);
    }

Методы интерфейса как я и говорил мы будет дергать из класса, который занимает длительным процессом. Ниже приведен сам класс, он примитивный, просто чтоб показать саму суть.

public class LongOpperations
    {
        public void ProcessCatalog(IProgressReporter progressReporter)
        {
            // some initialization, web service authentication, whatever
            Thread.Sleep(5000);
            progressReporter.ReportInitializationFinished();

            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(100);
                progressReporter.ReportCategoriesProgress(100, i + 1);
            }

            for (int i = 0; i < 20; i++)
            {
                Thread.Sleep(200);
                progressReporter.ReportBrandsProgress(20, i + 1);
            }

            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(200);
                progressReporter.ReportProductsProgress(100, i + 1);
            }
        }
    }
Отчетом о прогрессе выступает наша страница, для этого нужно реализовать наш интерфейс. Потом, когда вы будем стартовать длительную процедуру, мы передадим себя, страницу, в наш обработчик. В итоге получаем подобие шаблона Visitor. Самое интересное находится в методе Render. Начало ясное, если мы не нажали кнопку, то просто рендерим страницу как есть. Дальше мы должны отключить буферизацию страницы, в другом случае IE например закеширует первые 256 байт ответа и мы получим не то, что ожидали. Поэтому отключаем. Если мы после этого начнем репортить прогресс, то мы перетрем то, что существует на странице, поэтому ренедрим её как есть, а потом будет дописывать в Response наши ответы. Основная идея заключается в том, чтоб в Response записать вызов клиентской функции и сразу вывести его на страницу через Flush. Как только на странице появится script, он сразу выполнится, тем самым подвинет прогресс бары.

public partial class _Default : Page, IProgressReporter
    {
        private bool started;
        protected override void Render(HtmlTextWriter writer)
        {
            if (!started)
            {
                base.Render(writer);
                return;
            }

            Response.BufferOutput = false;
            base.Render(writer);
            Response.Flush();

            ReportInitializationStarted();
            new LongOpperations().ProcessCatalog(this);
        }

        protected void OnStartClick(object sender, EventArgs e)
        {
            started = true;
        }

        public void ReportInitializationStarted()
        {
            Response.Write("<script>ReportInitializationStarted();</script>");
            Response.Flush();
        }

        public void ReportInitializationFinished()
        {
            Response.Write("<script>ReportInitializationFinished();</script>");
            Response.Flush();
        }

        public void ReportCategoriesProgress(int count, int completed)
        {
            ReportProgress(pnlCategoryProgress.ClientID, lblCategory.ClientID, count, completed);
        }

        public void ReportProductsProgress(int count, int completed)
        {
            ReportProgress(pnlProductProgress.ClientID, lblProduct.ClientID, count, completed);
        }

        public void ReportBrandsProgress(int count, int completed)
        {
            ReportProgress(pnlBrandProgress.ClientID, lblBrand.ClientID, count, completed);
        }

        public void ReportException(Exception exception)
        {
            string progressCall = string.Format("<script>ReportException('{0}','{1}');</script>", exception.Message, exception.StackTrace);

            Response.Write(progressCall);
            Response.Flush();
        }

        private void ReportProgress(string progressBar, string percentLabel, int count, int completed)
        {
            double progress = (count == 0) ? 100 : (100.0 / count) * completed;

            string progressCall = string.Format("<script>UpdateProgress('{0}','{1}',{2},{3},{4});</script>", progressBar, percentLabel, progress, count, completed);

            Response.Write(progressCall);
            Response.Flush();
        }

Клиентский функции, которые двигают прогресс бары и тп.

function UpdateProgress(progressBarId, percentLabelId, progress, count, completed) {
            var progressBar = document.getElementById(progressBarId);
            var progressLabel = document.getElementById(percentLabelId);
            
            progressLabel.innerHTML = (progress == -1) ? '0' : completed + '/' + count;
            progressBar.style.width = progress + '%';            
        };

        function ReportException(message, stack) {
            var p = document.createElement('p');
            p.innerHTML = 'Message: ' + message + '
 Stack: ' + stack;
            document.getElementById('error').appendChild(p);
        };

        function ReportInitializationFinished() {
            var pnl = document.getElementById('pnlInitializing');
            pnl.className = null;
        };

        function ReportInitializationStarted() {
            var pnl = document.getElementById('pnlInitializing');
            pnl.className = 'progress';
            pnl.style.width = '100%'            
        };

Вот еще кусок разметки для ясности. Весь приводить не буду, т.к. там тоже самое и немного css.


Вот и все. Основная идея в том, чтоб запустив длительный процесс сразу отдавать ответ в виде js сриптовой инъекции, которая будет обновлять прогресс. Вы можете наделать любых методов в интерфейсе. Они могут быть настолько детальными на сколько вам необходимо. В моем случае я хотел еще справа от прогресс баров добавить лог ошибок и добавил соответсвующий метод в инерфейсе ReportException, оставил для примера.
Буду рад услышать ваши идеи и отзывы.