середа, 19 листопада 2008 р.

Інформація для осмислення: DateTime.Now

Якщо передбачається автоматичне тестування проекту, то не можна юзати стандартний DateTime.Now для визначення поточного часу. Бажано перевизначити його як статичну пропертю глобального класу програми. Щось типу:
  1. public static class MyApplicationName  
  2. {  
  3.    public static DateTime Now  
  4.    {  
  5.         get { return DateTime.Now; }  
  6.    }  
  7. }  

Це потрібно для того, щоб у майбутньому можна було зробити детерміновані тести, які зможуть емулювати потрібний час доби.

середа, 5 листопада 2008 р.

Зарубка на носі №1: XPath

Віднині під тегом "зарубки на носі" будуть публікуватися ті істини, які я зрозумів, лише добряче побивши чолом об стінки. Отже, зарубка перша:

XPath in .Net is case-sensetive!
Запит "/root/node[@name='blah']/text()" поверне інший результат, ніж "/root/node[@Name='blah']/text()"

пʼятниця, 31 жовтня 2008 р.

Вчимося викладати Сільверлайт на блогспот

Захотілося зробити одній хорошій людинці приємне і допомогти розібратися з цікавою темою.
Маленький Ліричний Відступ
Як відомо, компанія злих жадібних кровожерних монстрів Майкрософт часто випускає нові продукти. Одним із улюблених дітищ МС є .Net Framework. Остання актуальна версія фреймворку носить номер 3.5 SP1. Серед нововведень цієї версії можна виділити нову XML-based мову для проектування інтерфейсів - XAML. Ця мова лягла в основу WPF - нового слова в інтерфейсі програм на платформі Windows. Однак, дотнет не був би дотнетом, якби не використав її і для іншої мети. Microsoft Silverlight - прямий конкурент Macromedia Adobe Flash на просторах всесвітньої павутини. Такий підхід дозволив дизайнерам користувацьких інтерфейсів вбити двох зайців одночасно - вивчаючи XAML вони автоматично опановували обидві основні парадигми інтерфейсів сучасності, практично не помічаючи різниці між ними. Коротше, WPF-XAML-Silverlight - це круто :)

Отже, що потрібно для того, щоб написати
Silverlight-аплікацію? По-перше, Microsoft Visual Studio 2008 SP1 разом із Microsoft Silverlight Tools for Visual Studio 2008 (для любителів оупен-сурса існує Eclipse Tools for Silverlight).
Качаємо то все щастя з інету (75 метрів) і ставимо. Не забуваєм, що потрібен SP1 до студії і .Net. Якщо качати ще їх, то це майже гігабайт трафіку. Добре, що в мене анлім, та й сервіс паки вже місяць як стоять на домашній машині.
Ага, в студії з'явився новий тип проектів, Silverlight Control. По ходу створення проекту студія цікавиться, чи не потрібно створити для нового контрола цілий асп сайт - гнівно відмітаєм цю пропозицію і просимо генерити тестову сторінку при ребілді проекту. Створюєм проект і після довгих важких родів не без допомоги всезнаючого гугля отримуєм якесь чудо на тему вічної забавки "попади по кнопці" (якщо нічого не видно - треба поставити клієнт сільверлайту, він не важкий, всього два метри):



Тепер коротенько, як це викласти на сторінці блогспота. Коли студія білдить сільверлайт-проект, вона створює в папці bin\release (bin\debug якщо забути перемкнути конфігурацію) декілька файлів. Нас цікавлять два з них, SilverlightTest.xap та TestPage.html. Якщо розмістити їх на нормальному хостінгу (я викладав на Ucoz) то можна вписати в код поста рядочок вигляду
  1. <center>  
  2. <iframe style="WIDTH: 640px; HEIGHT: 480px" src="http://mace.at.ua/TestPage.html" frameborder="0" scrolling="no"></iframe>  
  3. </center>  
в якому вказати посилання на свою тестову сторінку і розміри сілверлайт-аплікейшена.
Ага, чуть не забув про проект аплікейшена в студії. Enjoy ;)

UPD: Silverlight

субота, 19 липня 2008 р.

Як писати справді короткі програми =)

Після переустановки Вінди треба було настроїти плагіни до топкодерівської арени. Для того, щоб потестити їх відкрив 250 другого діва з СРМ 409(останнього на даний момент).
Суть задачі полягає аж в тому, щоб порахувати кількість одиничних бітів в числі Х. Перший розвязок мав вигляд:
  1. #include <iostream>  
  2. #include <sstream>  
  3. #include <cstdio>  
  4. #include <cstdlib>  
  5. #include <cmath>  
  6. #include <memory>  
  7. #include <cctype>  
  8. #include <string>  
  9. #include <vector>  
  10. #include <list>  
  11. #include <queue>  
  12. #include <deque>  
  13. #include <stack>  
  14. #include <map>  
  15. #include <set>  
  16. #include <algorithm>  
  17. using namespace std;    
  18.   
  19. #define INF 2000000000  
  20. #define FOR(i, a, b) for(i = a; i < b; i++)  
  21. #define DFOR(i, a, b) for(i = a - 1; i >= b; i--)  
  22. #define ALL(c) (c).begin(),(c).end()   
  23.   
  24. class Stick  
  25. {   
  26. public:   
  27.  int pieces(int x)   
  28.  {   
  29.   int res=0;  
  30.   while(x>0)  
  31.   {  
  32.    if(x&1)  
  33.     res++;  
  34.    x>>=1;  
  35.   }  
  36.   return res;   
  37.  }   
  38. };   

Багато букоф... Шаблон багато займає. А якщо викинути все лишнє? Буде отак:
  1. struct Stick { int pieces(int x){ return x == 0 ? 0 : pieces(x/2)+x%2; } };  

...і все. Навіть інклудів не треба.

понеділок, 14 липня 2008 р.

Редірект з Global.asax

Писав сьогодні на роботі авторизацію на основі ролей для сайту. Так сталося, що мені треба було руцями перевіряти, чи юзер автентифікований і чи має права. Відповідно, якщо не автентифікований - то редіректити його на сторінку, де можна залогуватися, а якщо не має прав, то на сторінку, де йому скажуть "Access denied". Ніби все просто: вішаємо в Global.asax на подію AuthorizeRequest обробник, у якому перевіряємо права юзера і редіректимо його куди треба. Якось от так:
  1. protected void Application_Start(object sender, EventArgs e)  
  2. {  
  3.     AuthorizeRequest += Application_AutorizeRequest;  
  4. }  
  5.   
  6. protected void Application_AutorizeRequest(object sender, EventArgs e)  
  7. {  
  8.     if (Request.IsAuthenticated)  
  9.     {  
  10.         if(...) // user hasn`t rights to use current page  
  11.         {  
  12.             Response.Redirect("AccessDenied.aspx");  
  13.         }  
  14.     }  
  15.     else // must log in  
  16.     {  
  17.         Response.Redirect("Login.aspx");  
  18.     }  
  19. }  


Компілимо код, запускаємо, пробуємо і... Ніфіга не працює! Мало того, після наших редіректів аплікація взагалі вмирає - браузер приречено повідомляє про те, що не може отримати сторінку від сервера. Оживає все тільки по F5.
В чому ж проблема?
По перше, давайте зрозуміємо, що робить Response.Redirect. Він записує кудись адресу, куди треба перенаправити браузер, після чого викидає System.Threading.ThreadAbortException, який перериває виконання поточного потоку. Біда в тому, що на етапі, коли обробляються події в Global.asax ще не існує механізму, який перенаправить браузер за адресою, яку залишив Response.Redirect. Власне тому виклик цього методу просто і тупо завершує роботу аплікації.
ОК. А що ж робити? А все просто.

/*
Тут маленький відступ: хорошим тоном в ASP.Net є наслідування всіх стандартних контролів своїми, в які в будь-який момент можна дописати все, що завгодно. Так нехай всі наші сторінки наслідують не стандартний System.Web.UI.Page, а свій public class MyPage : System.Web.UI.Page.
*/

Отже, допишемо в Global.asax метод, який запише в кеші адресу сторінки, куди треба редіректитися і заюзаємо його в обробнику Application_AutorizeRequest:
  1. protected void Application_Start(object sender, EventArgs e)  
  2. {  
  3.     AuthorizeRequest += Application_AutorizeRequest;  
  4. }  
  5.   
  6. protected void Application_AutorizeRequest(object sender, EventArgs e)  
  7. {  
  8.     if (Request.IsAuthenticated)  
  9.     {  
  10.         if(...) // user hasn`t rights to use current page  
  11.         {  
  12.             Redirect("AccessDenied.aspx");  
  13.         }  
  14.     }  
  15.     else  
  16.     {  
  17.         Redirect("Login.aspx");  
  18.     }  
  19. }  
  20.   
  21. protected void Redirect(string url)  
  22. {  
  23.     HttpContext.Current.Cache["RedirectUrl"] = url;  
  24. }  

А тепер треба сказати сторінці, що треба редіректитися, якщо в кеші є ця адреса:
  1. public abstract class MyPage : System.Web.UI.Page  
  2. {  
  3.     protected void Page_Load(object sender, EventArgs e)  
  4.     {  
  5.         if(HttpContext.Current.Cache["RedirectUrl"] != null)  
  6.         {  
  7.             string url = (string) HttpContext.Current.Cache["RedirectUrl"];  
  8.             HttpContext.Current.Cache.Remove("RedirectUrl");  
  9.             Response.Redirect(url);  
  10.         }  
  11.         // ...   
  12.     }  
  13.     // ...  
  14. }  

Завдяки тому, що ми унаслідували свій клас для сторінки, не потрібно писати одне й те ж для кожної сторінки - тепер все прекрасно працює.
Отака повчальна історія.
P.S. Нарешті знайшов нормальний syntax highlighter для блогспота. Чим і хвалюся =)

субота, 7 червня 2008 р.

INTSPEI P-Navigator

На днях прийшов спам від фірми INTSPEI, спонсора зимової школи у Харкові. За старою звичкою я забив його читати, але потім все таки вирішив переглянути. Суть спаму полягає у рекламі нового продукта фірми - утиліти "P-Navigator" для "професійного" пошуку в Інтернеті. Ні, вони не написали власну пошукову систему, утиліта юзає Yahoo! Search, але робить це в досить цікавий спосіб.
Як і в будь-якому іншому пошуковику, ви вводите запит, наприклад нам потрібно знайти інформацію про Джорджа Буша старшого. Вводимо запит "Bush". Отримуємо хмаринку тегів, у якій найбільші "president" і "george". Клікаємо по "george" - ага, нова хмаринка тегів, тепер найбільший - "president". Він нас влаштовує - ще один клік. О! Серед великих тегів з'явилися "41st" i "43rd". Не важко здогадатися, що це порядкові номери представників сімейства Бушів у списку президентів США. Клікаємо по "41st", далі додаємо ще підозріло великі теги "walker" i "herbert". Результат: з понад мільярда (!) сторінок, знайдених початковим запитом "Bush", залишилося всього 84000, при чому на всіх міститься інфа саме про старшого Буша. Сторінки проранжовані по релевантності, першою в списку стоїть сторінка з офіційного сайту білого дому, присвячена саме йому. Далі в списку традиційна стаття на Вікіпедії та ще купа різних біографічних статей.
Можна аналізувати як лінки з поточної сторінки на інші, так і ті, які ведуть на поточну; на виході все та ж хмаринка тегів.
Наразі це чудо на стадії фрішної бети, тобто кожен може завантажити його і побавитися вволю.

Плюси:
  • цікавий підхід то пошуку, побудований на покроковому уточненні запитів за допомогою інтуїтивно зрозумілої хмаринки тегів;
  • фічі типу інтеграції з браузерами, поштовим клієнтом;
  • фічі з аналізу пов'язаних сайтів;
Мінуси:
  • бета, нема купи очевидних функцій (наприклад, "очистити історію пошуку") , надіюсь дороблять;
  • платність, я от не впевнений, що в продукту не з'явиться в найближчий час безкоштовний конкурент, та й шукати можна і без нього... не знаю, чи б купив таку прогу ;)
  • чому воно не онлайн? на AJAX така штука би виглядала значно природнішою.
  • неможливість юзати не-Yahooшні сервери, хочу таке ж, але з гуглем =)

Коротше, неоднозначна така штука, хоча сама ідея заслуговує на увагу.

пʼятниця, 30 травня 2008 р.

Юзер контроли на ASP.Net

Сьогодні на роботі написав свій перший повноцінний юзер-контрол на ASP.Net - TabControl (вкладки). Наразі, щоправда, без AJAX - так сказало начальство :) На диво, в АСП версії 2.0 немає такого цінного і потрібного контрола, а значить його треба писати руцями.
Спочатку в двох словах опишу ідею, яку я нарив в гуглі: таб контрол пишеться за допомогою трьох стандартних контролів: Menu, MultiView i View. Перший буде виконувати роль кнопочок вверху вкладок, два інші - все, що внизу.
Почнемо з малого - опишемо клас TabView:
using System.Web.UI.WebControls;

namespace MyControlsLib.TabControl
{
public class TabView : View
{
public string TabName { get; set; }
}
}

Як бачимо, він всього лише розширює функціональність стандартного View, додаючи йому пропертю TabName - заголовок вкладки. Все =)
Тепер трохи складніше - опишемо клас TabMultiView, який і буде контейнером для наших TabView:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyControlsLib.TabControl
{
[ParseChildren(true, "Views")]
public class TabMultiView : CompositeControl
{
public TabMultiView()
{
m_MultiView = new MultiView();
m_Menu = new Menu();
}

protected override void CreateChildControls()
{
base.CreateChildControls();

m_Menu.MenuItemClick += Menu_Click;
int index = 0;
foreach (var tab in Views)
{
MenuItem item = new MenuItem(tab.TabName, index.ToString());
item.Selected = index == ActiveIndex;
m_Menu.Items.Add(item);

m_MultiView.Views.Add(tab);

index++;
}

m_Menu.Orientation = Orientation.Horizontal;

if(!string.IsNullOrEmpty(SelectedTabCssClass))
{
m_Menu.StaticSelectedStyle.CssClass = SelectedTabCssClass;
m_Menu.StaticSelectedStyle.HorizontalPadding = 0;
m_Menu.StaticSelectedStyle.VerticalPadding = 0;
}
else
{
m_Menu.StaticSelectedStyle.BackColor = Color.LightGray;
m_Menu.StaticSelectedStyle.ForeColor = Color.Black;
}

if(!string.IsNullOrEmpty(UnselectedTabCssClass))
{
m_Menu.StaticMenuItemStyle.CssClass = UnselectedTabCssClass;
m_Menu.StaticMenuItemStyle.HorizontalPadding = 0;
m_Menu.StaticMenuItemStyle.VerticalPadding = 0;
}
else
{
m_Menu.StaticMenuItemStyle.BackColor = Color.Black;
m_Menu.StaticMenuItemStyle.ForeColor = Color.White;
}

// multiview

Controls.Add(m_Menu);
Controls.Add(m_MultiView);
}

private void Menu_Click(object sender, MenuEventArgs e)
{
ActiveIndex = int.Parse(e.Item.Value);
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if(ActiveIndex < 0)
{
if(Views.Count > 0)
{
throw new NotSupportedException("Invalid ActiveIndex property. You must set it correct.");
}
}
else
{
m_MultiView.Visible = true;
}
}

public string UnselectedTabCssClass { get; set; }
public string SelectedTabCssClass { get; set; }

public int ActiveIndex
{
get
{
object viewState = ViewState["ActiveIndex"];
if(viewState == null)
{
return -1;
}
return (int) viewState;
}
set
{
ViewState["ActiveIndex"] = value;
m_MultiView.ActiveViewIndex = value;
}
}

public List<TabView> Views
{
get
{
if (m_Tabs == null)
{
m_Tabs = new List<TabView>();
}
return m_Tabs;
}
}

private Menu m_Menu;
private MultiView m_MultiView;
private List<TabView> m_Tabs;
}
}


Тепер коротенько, що, де, коли і навіщо:
1. Наслідуємося від CompositeControl - це такий контрол, який дозволяє тримати всередині інші контроли - у нас же всередині будуть лежати TabView та менюшка.
2. Вписуємо в нього пропертю List<TabView> Views - отут якраз вони і будуть лежати. Вішаємо на клас атрибутик [ParseChildren(true, "Views")] - цим самим ми кажемо компілятору, що всі внутрішні контроли треба пхати в ліст "Views".
3. Пропертя int ActiveIndex - індекс активної вкладки. Значення тримаємо в ViewState - його потрібно зберігати між перезавантаженнями сторінки.
4. Перевантажуємо метод CreateChildControls() - в ньому потрібно створити контроли, з яких і буде складатися наш мега-контрол. Тут пробігаємося по Views - там уже сидять TabView, які ми вписали всередину контрола і витягуємо з них імена для елементів меню. Для кожного елемента потрібно запам'ятати в проперті Value його індекс - щоб знати, який TabView активувати при кліку на елементі. Крім того додаємо в m_MultiView таби. В кінці не забуваємо додати меню і мультів'ю в список контролів і обробити клік по пункту меню: m_Menu.MenuItemClick += Menu_Click;.
5. Додаємо купу фігні, яка відповідає за красивості з CSS-стилями.
Дописуємо в Web.config пару рядочків для зручності користування контролами:
<pages>
<controls>
<add assembly="MyControlsLib" namespace="MyControlsLib.TabControl" tagPrefix="my"/>
</controls>
</pages>

Все. Тепер можна користуватися:
<my:TabMultiView runat="server" ActiveIndex="0" SelectedTabCssClass="SelectedTab" UnselectedTabCssClass="UnselectedTab">
<my:TabView runat="server" TabName="Tab1">
<asp:Panel runat="server" CssClass="TabPanel">
Blah-Blah-Blah
</asp:Panel>
</my:TabView>
<my:TabView runat="server" TabName="Second tab">
<asp:Panel runat="server" CssClass="TabPanel">
Мне бы джина, я б тогда не путался в проводах<br />
Прям из Крыма по Днепру бы подтянул Карадаг<br />
До зарплаты за неделю не был бы на мели<br />
Королевы и принцессы in love with me, yeah.<br />
Тру бутылки, банки, кружки, баночки тоже тру<br />
Спрячу джина под подушку, достану поутру<br />
Вот тогда бы мы дел наделали, вот тогда б зажгли<br />
Все дела поотменять, потому что у меня...<br /><br />
На восьмом этаже - пати в неглиже<br />
На седьмом этаже - соседи спят уже<br />
На девятом этаже - медведи и Фаберже<br />
Этажом выше - спецназ на крыше.<br />

</asp:Panel>
</my:TabView>
<my:TabView runat="server" TabName="Yet another tab...">
<asp:Panel runat="server" CssClass="TabPanel">
Hello world!
</asp:Panel>
</my:TabView>
</my:TabMultiView>

Після легкої настройки CSS можна отримати таке:

Я думаю, будь-кому зрозуміло, що прикрашати контрол можна й далі - але це вже виходить за рамки цього меседжа.