четверг, 16 сентября 2010 г.

Холливарщикам ...

Давно надоели споры на тему что лучше "ASP.NET или PHP". Особенно надоели те люди, которые подобны"куликам в болоте" и не могут дальше своего болота что-либо увидеть...Причем таких бойцов предостаточно с обоих сторон.  На  все аргументы у них один контраргумент  - "мое болото круче, потому что оно мое!" ... Одним словом холливары в топку.
Вот,  замечательная и вдумчивая статья, на тему "ASP.NET или PHP".  Автор этой статьи -  молодец и умница. Побольше бы таких незамутненных взглядов.

PHP vs ASP.NET >>

среда, 15 сентября 2010 г.

Событийная модель страницы в ASP.NET

Четкое понимание событийной модели - это самое главное и основное умение которым вы должны обладать при использовании классической модели ASP.NET ( WebForms). Это скелет, устав, 10 заповедей - назовите это как хотите. Без знания и понимания данной модели создание эффективных веб-приложений невозможно.

Ниже два примера, показывающих  как незнание событийной модели может испортить вам жизнь. Суть примера такова - на странице находится ListView c с данными, и кнопка по счелчку на которую переходим на другую страницу. В обоих случаях - пользователь увидит наполненный ListView и перейдетнастраницу, но есть ньансы...

Main.aspx
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="main.aspx.vb" Inherits="main" %>
<html>
<head runat="server"><title></title></head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <asp:ListView  ID="ListView1" runat="server">
            <LayoutTemplate>
                <ul>
                    <asp:PlaceHolder runat="server" ID="itemPlaceholder" />
                </ul>               
            </LayoutTemplate>
            <ItemTemplate>
                <li>
                    <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
                </li>
            </ItemTemplate>
        </asp:ListView>
    </div>
    </form>
</body>
</html>

Main.aspx.vb (вариант №1)

Partial Class main
    Inherits System.Web.UI.Page

    'биндинг ListView на Page_Load
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Me.Load
        ListView1.DataSource = GetData()
        ListView1.DataBind()
    End Sub

    'биндинг каждого элемента ListView
    Protected Sub ListView1_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewItemEventArgs) Handles ListView1.ItemDataBound
        CType(e.Item.FindControl("Label1"), Label).Text = CType(CType(e.Item, ListViewDataItem).DataItem, String)
    End Sub

    'переход на эту же страницу
    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Button1.Click
        Response.Redirect("states.aspx", True)
    End Sub

    'Функция для создания коллекции необходимой для наполнения ListView
    Public Function GetData() As List(Of String)
        Dim result As New List(Of String)
        For i As Integer = 0 To 100
            Dim labelName As String = "Label #" & i.ToString
            result.Add(labelName)
        Next
        Return result
    End Function

End Class


Main.aspx.vb (вариант №2)
Partial Class main
    Inherits System.Web.UI.Page


    'биндинг ListView на Page_PreRender
   
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Me.PreRender
        ListView1.DataSource = GetData()
        ListView1.DataBind()
    End Sub


    'биндинг каждого элемента ListView
    Protected Sub ListView1_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewItemEventArgs) Handles ListView1.ItemDataBound
        CType(e.Item.FindControl("Label1"), Label).Text = CType(CType(e.Item, ListViewDataItem).DataItem, String)
    End Sub


    'переход на эту же страницу
    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Button1.Click
        Response.Redirect("main.aspx", True)
    End Sub


    'Функция для создания коллекции необходимой для наполнения ListView
    Public Function GetData() As List(Of String)
        Dim result As New List(Of String)
        For i As Integer = 0 To 100
            Dim labelName As String = "Label #" & i.ToString
            result.Add(labelName)
        Next
        Return result
    End Function


End Class


Разбор Полетов

В обоих случаях ListView будет наполнен и пользователь сможет перейти на другую страницу ( в нашем случае - та же самая страница). Теперь важно понимать событийную модель, чтобы увидеть в чем проблема для варианта №1.

В первом варианте биндинг ListView проводится на обработчике события Page_Load.
Переход на страницу осуществляется на событии кнопки Click.

Что происходит по счелчку на кнопку? По счелчку страница уходит на сервер, где начинается ее обработка. Согласно событийной модели,   сначала отработается событие Load ( а вместе с этим выполнится код описанный в обработчике этого события Page_Load). Только после этого отработается отложенное событие, которым является Click  нашей кнопки. И только после этого произойдет переход на другую страницу.
Таким образом биндинг ListView будет происходить все время.

А если взять ситуацию, когда наполнение коллекции данных для ListView включает в себя "тяжелые запросы" - это может превратиться для пользователя в тихий ужас. Хочет он перейти на другую страницу и ждет от этого действия мгновенной реакции - а ему вместо этого каждый раз вычитываются данные для ListView, которые ему уже не нужны.

Во втором случае биндинг ListView проводится на обработчике события Page_PreRender
Переход на страницу осуществляется на событии кнопки Click.

Во втором  случае все проиходит нормально. Событие PreRender возникает после обработки отложенных событий ( событие Click  нашей кнопки).  Таким образом,  биндинг ListView не будет происходить лишний раз.  По счелчку на кнопку пользователь сразу перейдет на страницу.

Понимание событийной модели очень важно.

Ссылка на МSDNовский ресурс который замечательно описывает событийную модель страницы. >>

суббота, 11 сентября 2010 г.

ListView vs. Web User Control для отображения списков "Тяжелых" сущностей

Теория

Задача.

Необходимо отображать и редактировать большой  список сущностей. Без пейджинга. Сущности редактируются прямо в списке.   Каждая сущность включает  в себя "тяжелые" ("долгоиграющие") запросы. Под этими запросами я подразумеваю те запросы, которые отрабатываются достаточно долго, ресурсоемки и результаты которых кешировать или не возможно или же не выгодно. Например включает в себя список картинок, которые хранатся в базе в оригинальном размере и при извлечении преобразуюются в меньший размер. Для отображения картинок используется хендлер (тоесть картинки не сохраняются в файловой срситеме). Таким образом, по условию, сущность которая отображается в качестве элемента списка составная и требует определенного количества внутренних запросов для своего формирования. Кешировать даные результаты, будь то ViewState или Session возможно но ресусроемко.

Варианты Реализации.

Вариант Первый.
Самый первый и классический вариант это использование ListView (или GridView. Я предпочитаю первый вариантб так как это позволяет полностью контролировать выходной HTML). Недостатком использования  ListView является то, что при любом событии ListView (будь то перевод элемента в состояние редактирования или отменя редактирования, любой PostBack со сороны ListView) необходимо проводить полный перебинд всего ListView. Тоесть,  при отсутствии возможности кеширования результатов выборки, это опять вычитка из базы всех связанных данных для кажого элемента списка , биндинг каждого элемента и тд.
При отображении скажем трехсот  элементов , простой перевод одного элемента списка  в режим редактирования потребует провести 300 на Х запросов к базе ( где Х - количество внутренних запросов для одного элемента списка). Такой подход будет очень расстравать пользователя, который ожидает мгновенной реакции на такое, как ему кажется, простое действие.

Как вариант, это кешировать данные первоночальной выборки (ViewState или Session), но в любом случае, происходит перебинд всех элементов списка. В случае же, когда на ItemDataBound происходит вычитка всех картинок для сущности и "Тяжелые" операции все равно происходят- это не является оптимальным решением. Все равно медленно... И даже если это исключить, все равно придется при изменении сущности либо перечитывать всю коллекцию, либо изменять закешированную коллекцию. При любых вырантах работы с  ListView - происходит перебинд все эелементов списка.

Решением могло бы стать наличие таких возможностей как перебинд и перерендер только того элемента коллекции с которым идет работа.  Для перерендера только  активного элемента списка ListView решением могло бы быть заключение каждого элемента списка в Update Panel. Но к сожалению ListView имеет различные темплейты для каждого из режимов элемента (отображене, редактирование и тд.) и декларативно обьединить их в одну Update Panel не представляется возможным.  В Update Panel  мы можем положить только весь ListView целиком. Тоесть получаем те же самые проблемы как и при отсутствии Update Panel.

Задача выкристализовывается - "Биндить и рендерить только то, что изменяем".

Вариант Второй.
Для отображения элемента списка мы создаем User Web Controls. Который представляет собой FormView обернутый в Update Panel. И это позволяет рендерить  только тот элемент с которым мы работаем. Так как мы рендерим только один активный элемент, нам нет необходимости биндить другие.  Мы биндим только один!

Производительность.
Опыты показали, что использованние ListView для первично отображения данных (списка сущностей) дает более быстрые результаты, нежели динамическое добавление User Web Controls для тех же целей. Но дальнейшее использование  User Web Controls для редактирования сущности в списке дает лучшую скорость реакции. Я бы скадзал - значительно лучшую ( Так как выборка и биндинг  производится только для одной, текущей сущности). Если же отключить ViewState для контролов отображающие статические данные - то производитьельность увеличится как для первого так и для второго подходов.

Вывод.
Использование ListView + кеширование данных выборки (при условии отсутствия некешируемых элементов и долгоиграющих запросов для наполнения каждого элемента списка) удобнее, проще и быстрее нежели динамическое добавление User Web Controls.
НО! Если возникает ситуация, когда для наполнения каждой сущности используются некешируеммые элементы и долгоиграющие запросы тот выгоднее и быстрее использовать динамически добавляемые User Web Controls

Практика
Структура проекта

четверг, 9 сентября 2010 г.

Требования к системе для .NET Framework

Тут вот камрад интересную ссылочку подкинул с MSDN. Документик содержит
требования к оборудованию, операционной системе и программному обеспечению для платформы .NET Framework. Очень интересненько... А вы думали NET Framework 4 белый и пушистый, а ему вишь - надо 1 ГГц проца и 512 метров оперативки. и забудте про  Windows XP Tablet PC Edition...

http://msdn.microsoft.com/ru-ru/library/8z6watww.aspx

среда, 8 сентября 2010 г.

IIS6 , II7 и печальные размышления о современниках.

Давеча переносил приложение с одного сервера на другой. Соответственно  с IIS6 на IIS7.  IIS7 - понравился, немножко непривычно после IIS6 - но в целом позитвное впечатление.
А теперь о печалном...
После успешного переноса, решил проверить как все работает. Запустил приложение, проверил определенную фунциональность и тут нате:

"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached."

Ну думаю все понятно - не закрытые соединения к базе. Проверяю соединения к базе - 100 активных соединений!!!  Быстренько вспомнил что делал на сайте и сориентировался в коде что и где... И действительно, в 15(!!!) местах соединение не закрывается. Код не мой, писал подчиненный. И вроде не дурак, может давать хороши и стабильные решения.. НО ТАКОЕ...
Вопрос к  космосу - "ДОКОЛЕ БУДЕТ ПРОЦВЕТАТЬ ТАКОЕ РАЗГИЛЬДЯЙСТВО И БЕЗАЛАБЕРНОСТЬ?!"

А теперь о IIS7 - он поймал эту ошибку. А IIS6 хавал и проглатывал, в результате чего страдала производительность. Такие дела.

среда, 1 сентября 2010 г.

TextBoxWatermarkExtender и UpdatePanel

TextBoxWatermarkExtender - замечательный контрольчик из AjaxControlToolkit  который позоляет вешать на текстовые поле предварительные сообщения, информирующие пользователя о необходимых действиях.

Например.
...
<asp:TextBox   ID="txtLogin" runat="server"></asp:TextBox>
<cc1:TextBoxWatermarkExtender ID="TBWERFALogNumber" runat="server"

TargetControlID="txtLogin"
WatermarkText="Type Login Here" />
....

Теперь при рендере у нас будет текстовое поле внутри которго будет сообщение "Type Login Here". При наведении фокуса на это поле, сообщение пропадает и у нас есть возможность ввести все что нам надо. Если ничего не ввели - то это сообщение появляется опять  при потере фокуса текстовым полем.

После постбека спокойно обращаемся к TextBox не задумываясь о TextBoxWatermarkExtender. Если ничего не было введено, значение TextBox.Text будет пустым. TextBoxWatermarkExtender не окажет никакого влияния на TextBox.

Ситуация координально меняетеся если мы обернем наш  контент в UpdatePanel.
....
<asp:UpdatePanel ID="upSH" runat="server">
<ContentTemplate>
...
<asp:TextBox ID="txtLogin" runat="server"></asp:TextBox>

<cc1:TextBoxWatermarkExtender ID="TBWELogin" runat="server"
TargetControlID="txtLogin"
WatermarkText="Type Login Here" />
...
</ContentTemplate>

</asp:UpdatePanel>
...


Теперь при постбеке, TextBox.Text  всегда будет содержать значение того,  что было написано в нем. Инными словами, если мы ничего не ввели в TextBox, значение TextBox.Text будет равно "Type Login Here" (так как этозначение было установленно туда контролом TextBoxWatermarkExtender ).

Теперь для того чтобы получить корректное значение введенных данных для TextBox , на постбеке необходимо сравнивать значение присутствующее в TextBox со значением  установленным для TextBoxWatermarkExtender.

...
Dim result as String = String.Empty
If txtLogin.Text.Trim <> String.Empty Then
    If Not txtBudgetCost.Text.Trim.Equals(TBWELogin.WatermarkText.Trim) Then
       result = txtLogin.Text.Trim
    End If
End If
....

После этого переменная result будет содержать корректные данные.