17 сентября 2007

Membership. Смена пароля без ввода старого

Используя стандартный Membership у меня возникла необходимость смены пароля без ввода старого, но к сожалению метод ChangePassword требует два аргумента - старый и новый пароль.

Пришлось обойти это вот так:

string tempPWD = Membership.GetUser().ResetPassword();
Membership.GetUser().ChangePassword(tempPWD, pwdTB.Text);
 

15 января 2007

Nested Repeater - Вложенныйе Repeater

Очень часто при разработке веб-приложений приходится использовать двууровневую иерархию. Например вам нужно динамически вывести категории товаров и сами товары в категориях. Выглядеть это будет примерно так:

  1. Рыба
    1. Форель
    2. Семга
    3. Окунь
  2. Мясо
    1. Говядина
    2. Свинина

Обычно такие списки делаются с помощью Repeater. И действительно все что надо, что бы отобразить категории - пара строк кода:

<asp:Repeater runat="server" ID="catRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate><li><%# DataBinder.Eval(Container.DataItem, "CategoryName")%></li></ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>

и

protected void Page_Load(object sender, EventArgs e) { SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString); SqlCommand command1 = new SqlCommand("SELECT CategoryID, CategoryName FROM Categories", conn); SqlDataAdapter adapter = new SqlDataAdapter(command); DataTable dt = new DataTable(); adapter.Fill(dt); catRepeater.DataSource = dt; catRepeater.DataBind(); }

Но как же быть с собственно товарами? Очень просто - добавим вложенный Repeater:

<asp:Repeater runat="server" ID="catRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate><li><%# DataBinder.Eval(Container.DataItem, "CategoryName")%><asp:Repeater runat="server" id="itemRepeater" datasource='<%# GetItemDS(DataBinder.Eval(Container.DataItem,"CategoryID").ToString())%>'> <ItemTemplate><%# DataBinder.Eval(Container.DataItem, "ItemName")</ItemTemplate> </asp:Repeater></li></ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>

Как видно из листинга второй Repeater самый обыкновенный за исключением параметра DataSource. За счет него при каждой инициализации вложенный Repeater получает новый DataSource относящийся к текущей категории(CategoryID). Это достигается выполнением метода GetItemDS(string categoryID), возвращающим DataTable. В качестве categoryID передается результат выполнения директивы привязки данных DataBinder.Eval(Container.DataItem,"CategoryID").ToString(). Благодаря тому, что первым параметром методу Eval передается Container.DataItem мы получаем CategoryID текущей категори. Вот его код метода GetItemDS:

protected DataTable GetItemDS(string categoryID) { SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString); SqlCommand command = new SqlCommand("SELECT ItemName FROM Items WHERE CategoryID=@CategoryID", conn); command.Parameter.Add("@CategoryID", SqlDbType.Int).Value = categoryID; SqlDataAdapter adapter = new SqlDataAdapter(command); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; }

Вот собственно и все что нам требуется для достижения результата. Еще раз рассмотрим всю конструкцию. Первоначально мы имеем Repeater который берет данные и выводит категории. В нем мы имеем вложенный Repeater DataSource которого задается динамически с помощью вспомогательного метода GetItemDS и директивы привязки данных с помощью которой в метод передается CategoryID текущей категории. На основании полученной DataTable вложенный Repeater отображает все продукты текущей категории.

Рендеринг веб-контрола по запросу

Иногда в веб-приложении бывает необходимо отослать код, сгенерированный веб-контролом на email пользователю. Чаще всего это встречается в системах со всяческими отчетами. Рассмотрим как это можно реализовать.

Каждый веб-контрол имеет в себе метод , RenderControl(HtmlTextWriter) который при вызове рендерит контрол и возвращает, как результат HtmlTextWriter. Программно это выглядит так:

StringBuilder SB = new StringBuilder(); StringWriter SW = new StringWriter(SB); HtmlTextWriter htmlTW = new HtmlTextWriter(SW); WebControlToRenderID.RenderControl(htmlTW);

Непосредственно к коду вы можете подобраться через метод ToString() объекта HtmlTextWriter.

Однако данный подход прекрасно работающий в ASP.NET 1.1 не подходит для ASP.NET 2.0, так как при выполнении вы получите исключение: RegisterForEventValidation can only be called during Render();

Это происходит потому, что в ASP.NET 2.0 появилась новая возможность - Event Validation. Сделана она для того, чтобы помочь избежать так называемых Injection атак.

К примеру у вас на веб-форме имеется DropDownList контрол:

<asp:DropDownList runat="server" id="MyDropDownList"> <asp:ListItem Value="1">Item 1</asp:ListItem> <asp:ListItem Value="2">Item 2</asp:ListItem> <asp:ListItem Value="3">Item 3</asp:ListItem> </asp:DropDownList>

Если через postback страница передает значение 4, то ASP.NET генерирует исключение, так как ожидает значения 1, 2 или 3. Это идеальное поведение в том случае, если вы действительно хотите получать только 1, 2 или 3, так как если пришло значение 4, это означает, что что-то пошло не так. Но, если вы, к примеру, на клиенте динамически добавляете элемент к полученому select-у, то вас такое поведение не устроит.

В цикле жизни страницы контрол регистрирует себя на подобную проверку через класс ClientScriptManager методом RegisterForEventValidation. Этот метод может быть вызван только на этапе Render, иначе он генерирует исключение. А так как скорее код для програмной генерации контрола вызывается до этого этапа, к примеру, в событии Button1_Click, то вы гарантировано получаете исключение.

Естественно, сказанное выше применимо только к контролам требующим Event Validation. Вот их список:

  1. HtmlAnchor
  2. HtmlButton
  3. HtmlInputButton
  4. HtmlInputCheckBox
  5. HtmlInputHidden
  6. HtmlInputImage
  7. HtmlInputText
  8. HtmlInputPassword
  9. HtmlInputRadioButton
  10. HtmlInputReset
  11. HtmlInputSubmit
  12. HtmlSelect
  13. HtmlTextArea
  14. BulletedList
  15. Button
  16. Calendar
  17. CheckBox
  18. Table
  19. ChildTable
  20. WizardChildTable
  21. DataControlButton
  22. ImageButton
  23. DataControlImageButton
  24. LinkButton
  25. DataControlLinkButton
  26. DataControlPagerLinkButton
  27. DataGridLinkButton
  28. DetailsView
  29. DropDownList
  30. FormView
  31. GridView
  32. HiddenField
  33. ImageMap
  34. LayoutTable
  35. ListBox
  36. Menu
  37. PagerTable
  38. RadioButton
  39. RadioButtonList
  40. TextBox
  41. TreeView
  42. WizardDefaultInnerTable
  43. CatalogZone
  44. ConnectionsZone
  45. EditorZone
  46. WebPartZone
  47. ZoneButton
  48. ZoneLinkButton
Чтобы исправить подобное поведение требуется одно легкое телодвижение. Необходимо вставить, на страницы, рендерящие контрол програмно такой параметр к директиве @Page:

<% @Page EnableEventValidation="False" ... %>

Или же вы можете начинать рендеринг контрола(ов) в событии Page_Render. Перевод статьи Emailing the Rendered Output of an ASP.NET Web Control in ASP.NET 2.0

Url Rewriting в ASP.NET 2

Намедни делая небольшой сайт для знакомой стало ясно что мне понадобится для него модная нынче штука - UrlRewriting. Представляя себе как это делается в общих чертах я воодушевился и полез в Google.

Естественно, первое на что я наткнулся был встроенный в ASP.NET 2 UrlMapping, но почитав про него я понял, что его возможностей для меня будет мало. Сделав еще пару кликов мышью в SERP я набрел на замечательный модуль: UrlRewritingNet.UrlRewrite.

Основными его плюсами можно считать:

  • настройка через web.config с использованием RegEx
  • возможность добавления/изменения Rewrite правил на лету
  • правильная работа с Themes, MasterPages, Postback, Cookieless Sessions
  • необходимость доступа к IIS Manager только для Rewriting урлов без .aspx расширения

Приведу небольшой пример использования, который покажет насколько просто использовать этот модуль. Допустим, у нас есть страница news.apsx, которая выдает новости таким видом: news.aspx?id=13, а мы хотим сделать урл вида news/13.aspx. Для этого добавьте этот код в web.config в секцию <configuration>:

<configSections> <section name="urlrewritingnet" restartOnExternalChanges="true" requirePermission="false" type="UrlRewritingNet.Configuration.UrlRewriteSection, UrlRewritingNet.UrlRewriter" /> </configSections> <urlrewritingnet xmlns="http://www.urlrewriting.net/schemas/config/2006/07" rewriteOnlyVirtualUrls="true" contextItemsPrefix="QueryString" defaultProvider="RegEx"> <rewrites> <add name="News" virtualUrl="~/news/(.*).aspx" rewriteUrlParameter="ExcludeFromClientQueryString" destinationUrl="~/news.aspx?id=$1" ignoreCase="true" /> </rewrites> </urlrewritingnet>

Как видно из данного примера правило Rewrite задается простейшим RegEx выражением. В то же время RegEx дает потенциальную мощность и вы сможете создавать более сложные правила. К примеру такую /news/2007/01/15/url-rewriting.aspx ссылку вы сможете обрабатывать с помщью этого правила:

<rewrites> <add name="News" virtualUrl="~/news/(.*)/(.*)/(.*)/(.*).aspx" rewriteUrlParameter="ExcludeFromClientQueryString" destinationUrl="~/news.aspx?alias=$4" ignoreCase="true" /> </rewrites>

Добро!

Ну что ж начнем. Первый пост комом, как говорится, но все же. Как видно из названия блога - посвящен он будет веб-программированию, а именно ASP.NET 2.0 и все, что с ним связано - Sql Server 2005, AJAX, SEO and more. Так же, если меня сильно заденут, здесь будут пробегать общие новости, вроде выхода iPhone(смайл). Этот блог - моя первая попытка создать нечто не лытдыбровское, а посвященное чему-то доброму, вечному и полезному, так что особо не пинайте. На этом вступление считаю оконченным. Буду рад, если этот блог будет кому-либо полезным.