Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование (VB,C#,ASP,.NET): новости, советы, примеры



Выпуск # 267


http://www.kbyte.ru/

 

Новый выпуск!

Ну что я могу сказать, погода не летная, но на выпуск рассылки это никак не повлияло. Читайте! Если будут вопросы, пишите нам на E-Mail или воспользуйтесь общедоступным форумом (регистрация не требуется).

 

Содержание выпуска

Система ЭКСПЕРТ

    Вопрос #000764: Програмное определение позиции меню в vb6

    Вопрос #000754: HttpWebRequest и HttpWebResponse

    Вопрос #000745: Проблема с компонентом WINSOCK

    Вопрос #000743: C#, forms

    Вопрос #000735: Localization and DataBase

Последнее на форумах

    ASP.NET работа за кадром

    Кодирование -Раскодирование строк

    Как капироват ДБгриде вбронного страку другой ДБгрид

    Перевод с vb на vb.net = проблемка

    zip-файл String'om через POST

Последние статьи

    Как узнать размер базы данных MySQL через php

    Взаимодействие с другими сайтами через HTTP (HttpWebRequest и HttpWebResponse)

    Работа с SQL – Запросы

    Использование XML.Serialization

    Выбор языка программирования для научных работ (2-я редакция)

Интересные коды и решения

Юмор на Kbyte.Ru

 

Система ЭКСПЕРТ

 

Система «Эксперт» - уникальная система, которая поможет решить, если не все, то многие Ваши проблемы с программированием и разработкой! Если у Вас есть какой-либо вопрос по программированию или разработке, то Вы всегда можете задать его нашим Экспертам! При этом каких-либо особых действий от Вас не потребуется! Просто отправьте Ваш вопрос на адрес mailto:expert@kbyte.ru, в теме письма укажите тему вопроса, в тексте – сам вопрос, и все! Эксперты дадут Вам ответ в течение 12-24 часов, на тот адрес, с которого Вы отправите вопрос. Все проще простого! Никакой регистрации и заполнение бесконечных форм, просто спросите и получите ответ ;-)


Вопрос #000764: Програмное определение позиции меню в vb6

Автор вопроса: Иван Денишев // 16.02.2008 / 23:24

Здравствуйте уважаемые эксперты!
Говорят, что вы можете ответить на любой вопрос.
Как программно в vb6 узнать, на каком пункте меню находится курсор.
Для примера, возьмём меню "файл".
Caption | Name
Файл | MnuFile
Создать | MnuFileNew
Открыть | MnuFileOpen
Ну, и пожалуй, хватит. И так будет понятно.
Запускаем программу и жмём: либо f10, либо alt.
Затем стрелку вниз.
Меню "Создать", выделится другим цветом.
Так вот, как программно получить, где находится курсор?
Ведь, в vb нету свойства GotFocus у менюшек.
Очень надеюсь на вашу помощь!
---
С уважением: Иван Денишев.

Отвечает: Алексей Немиро // 17.02.2008 / 10:12

Здравствуйте, Иван.

ВНИМАНИЕ: Данный пример довольно опасный, и может привести к зависанию Visual Basic!

Скачать готовый пример можно тут: http://kbyte.ru/Code/Samples/form1.zip

Создайте новый EXE-проект, на форме разместите Timer и вставьте следующий код:

Код формы:
001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
Option Explicit

Private Sub Form_Load()
 gHW = Me.hwnd
 Hook
End Sub

Private Sub Form_Unload(Cancel As Integer)
 Unhook
End Sub

Private Sub Timer1_Timer()
 Me.Caption = SelectedMenuString
End Sub


Также добавьте несколько элементов меню.
Дополнительно создайте новый модуль и вставьте следующий код:

Код модуля:
001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
016:
017:
018:
019:
020:
021:
022:
023:
024:
025:
026:
027:
028:
029:
030:
031:
032:
033:
034:
035:
036:
037:
038:
039:
040:
041:
042:
043:
044:
045:
046:
047:
048:
049:
050:
051:
052:
053:
054:
055:
056:
057:
058:
059:
060:
061:
062:
063:
064:
065:
066:
067:
068:
069:
070:
071:
072:
073:
074:
075:
076:
077:
078:
079:
080:
081:
082:
083:
084:
085:
086:
087:
088:
089:
090:
091:
092:
093:
094:
095:
096:
097:
098:
099:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
Option Explicit

Public SelectedMenuString As String 'здесь будет заголовок выбранного элемента меню

Private Declare Function CallWindowProc Lib "user32" _
  Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
  ByVal hwnd As Long, ByVal Msg As Long, _
  ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Function SetWindowLong Lib "user32" _
  Alias "SetWindowLongA" (ByVal hwnd As Long, _
  ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Private Declare Function GetMenu Lib "user32" _
 (ByVal hwnd As Long) As Long

Private Declare Function GetSubMenu Lib "user32" _
 (ByVal hMenu As Long, ByVal nPos As Long) As Long

Private Declare Function GetMenuItemCount Lib "user32" _
 (ByVal hMenu As Long) As Long

Private Declare Function GetMenuState Lib "user32" _
 (ByVal hMenu As Long, ByVal wID As Long, _
  ByVal wFlags As Long) As Long

Private Declare Function GetMenuString Lib "user32" _
  Alias "GetMenuStringA" (ByVal hMenu As Long, _
  ByVal wIDItem As Long, ByVal lpString As String, _
  ByVal nMaxCount As Long, ByVal wFlag As Long) As Long

Private Const MF_BYPOSITION = &H400&
Private Const MF_HILITE = &H80&
Private Const WM_MENUSELECT = &H11F
Private Const GWL_WNDPROC = -4

Public lpPrevWndProc As Long
Public gHW As Long

Public Sub Hook()
  'Begin hooking into messages.
  lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
  AddressOf WindowProc)
End Sub

Public Sub Unhook()
  'Cease hooking into messages.
  SetWindowLong gHW, GWL_WNDPROC, lpPrevWndProc
End Sub

Function AnyLit(hSubSubMenu As Long) As Long
  Dim i As Long
  Dim MenuCount As Long

  'Get the number of items in the menu.
  MenuCount = GetMenuItemCount(hSubSubMenu)

  'Loop through the menu items.
  For i = 0 To MenuCount - 1
    'Check whether this item is highlighted.
    If GetMenuState(hSubSubMenu, i, MF_BYPOSITION) And _
        MF_HILITE Then
      AnyLit = True
      Exit Function
    End If
  Next i

  'Return FALSE, no items highlighted.
  AnyLit = False
End Function

Private Sub WalkSubMenu(hSubMenu As Long)
  Dim i As Long
  Dim MenuItems As Long
  Dim hSubSubMenu As Long
  Dim buffer As String
  Dim result As Long

  'Get the count of menu items in this menu.
  MenuItems = GetMenuItemCount(hSubMenu)

  'Loop through all the items on the menu.
  For i = 0 To MenuItems - 1
    'Determine whether this item is highlighted.
    If GetMenuState(hSubMenu, i, MF_BYPOSITION) And _
        MF_HILITE Then
      'Attempt to get a submenu for each menu item.
      hSubSubMenu = GetSubMenu(hSubMenu, i)

      'Check for a submenu with something selected on it.
      If hSubSubMenu And AnyLit(hSubSubMenu) Then
        'There is a submenu with a selection so walk it.
        WalkSubMenu hSubSubMenu
      Else
        buffer = Space(255)
        result = GetMenuString(hSubMenu, i, buffer, Len(buffer), MF_BYPOSITION)
        buffer = Left$(buffer, result)
        SelectedMenuString = GetDescription(buffer)
        Exit Sub
      End If
    End If
  Next i
End Sub

Public Sub FindHilite(TheForm As Form)
  Dim hMenu As Long
  Dim hSubMenu As Long
  Dim i As Long
  Dim MenuCount As Long

  'Clear any previous description.
  SelectedMenuString = ""

  'Get the menu handle.
  hMenu = GetMenu(TheForm.hwnd)

  'Check to see if there is no menu.
  If hMenu <> 0 Then
    'Get the number of top-level menus.
    MenuCount = GetMenuItemCount(hMenu)

    'Enumerate through all top-level menus.
    For i = 0 To MenuCount - 1
      'Ignore top-level menus not currently selected.
      If GetMenuState(hMenu, i, MF_BYPOSITION) And _
          MF_HILITE Then
        'Get a handle to the submenu.
        hSubMenu = GetSubMenu(hMenu, i)

        'Walk the submenu.
        WalkSubMenu hSubMenu
      End If
    Next i
  End If
End Sub

Private Function GetDescription(MenuCaption As String) As String
 GetDescription = MenuCaption
End Function

Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
 If uMsg = WM_MENUSELECT Then
  FindHilite Form1
 End If
 WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)
End Function


При выборе элемента меню в переменной SelectedMenuString будет текст заголовка выбранного элемента меню.

 

Вопрос #000754: HttpWebRequest и HttpWebResponse

Автор вопроса: Сергей // 13.02.2008 / 23:01

Здравствуйте.
Алексей Сергеевич в статье "Взаимодействие с другими сайтами через HTTP" хорошо описал, как можно программно заполнить и отправить Web-форму и получить результат. Спасибо. А как быть, если есть защита от автоматов? Т.е. как отобразить имиджи с цифрами, получить и отправить выбранный вручную ответ? Спасибо.

--------------------------------------
Категория: --- C# (Си шарп)

Отвечает: Алексей Немиро // 14.02.2008 / 03:35

Здравствуйте, Сергей.

Для этого придется икать уязвимости в самой CAPTCHA, «проще всего» разобраться с картинкой, которая в url содержит код, по которому она была сгенерирована.

http://www.captcha.ru/breakings/

Также можно попробовать написать распозновалку картинок, но это уже зависит от ее кривизны и окраски, чем меньше кривизны и меньше цветов, тем больше вероятность написать нормальную распозновалку.

Либо придется подключать человека, отдавать ему картинки и чтобы он сам вручную вводил номера.


--
С наилучшими пожеланиями,
Немиро Алексей http://aleksey.nemiro.ru/

 

Вопрос #000745: Проблема с компонентом WINSOCK

Автор вопроса: Дмитрий // 10.02.2008 / 15:57

Уважаемые Эксперты!
Помогите решить вот такую проблему: создал приложение с использованием сокетов но на других компьютерах оно не запускается из-за того что якобы отсутствует компонент MSWINSCK.OCX. Как можно решить эту проблему?
Заранее спасибо


--------------------------------------
Категория: --- Visual Basic 5.0/6.0

Отвечает: Алексей Немиро // 10.02.2008 / 16:09

Здравствуйте, Дмитрий!

Файл MSWINSCK.OCX можно найти в папке WINDOWS\system32 Вашего
компьютера. Чтобы Ваша программа работала на другом компьютере, Вам
необходимо перенести этот файл, и возможно зарегистрировать его.
Регистрация компонентов производится программой regsvr32.exe,
которую можно найти в той же папке (system32). Для регистрации достаточно
перетащить файл компонента (mswinsck.ocx) на файл regsvr32.exe в проводнике Windows.


--
С наилучшими пожеланиями,
Немиро Алексей http://aleksey.nemiro.ru/

 

Вопрос #000743: C#, forms

Автор вопроса: "Slonov K." // 09.02.2008 / 14:05



Добрый день.
C# .
Есть форма(Form1) с кнопкой, при нажатии на неё создаётся другая
форма(Form2).

private void button1_Click(object sender, EventArgs e)
{
Form2 Fr = new Form2();
Fr.Show();
}

Вопрос : как добиться чтобы чтобы при повторном нажатии на эту же
кнопку не создавалось ещё одно окно(типа Form2) , а просто
становилось становилось активным уже существующее Fr.
Заранее спасибо.
Костя

З.Ы. Может я что то принципиально не так делаю?

Отвечает: EROS // 10.02.2008 / 18:36

Здравствуйте
Подобного поведения можно добиться различными способами, вот один из
них:

001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
private void button1_Click(object sender, EventArgs e)
    {
      foreach (Form item in Application.OpenForms)
      {
        if (item is Form2)
        {
          item.Focus();
          return;
        }
      }

      Form2 frm = new Form2();
      frm.Show();
    }


--
С уважением,
EROS mailto:eros@fromru.com

 

Вопрос #000735: Localization and DataBase

Автор вопроса: Taleh Mikailov // 05.02.2008 / 15:36

Здравствуйте уважаемые эксперты!
На свой вопрос, что написал прошлый раз не получил ответа. В связи с этим решил более подробнее обьяснить ситуацию и более точно сформулировать свой вопрос...
Мне нужно включить в Веб Приложение поддержку локализации из Баз Данных.
Долго искал, по этому вопросу, какой-нибудь материал в сети.
Мое внимание привлекла вот эта статья: www.microsoft.com/rus/msdn/publish/articles/asap.mspx
После того, как прочитал статью, для примера создал не большое приложение и одну таблицу в Базе Данных()
Таблица(StringResources) из Базы Данных(CustomResourceProviderSample)

resourceType | cultureCode |resourceKey | resourceValue
Default.aspx en lbHelloLocalResource1.Text Hello(en)
Default.aspx en lbLanguageLocalResource1.Text English(en)
Default.aspx en lbWelcomeLocalResource1.Text Welcome(en)
Default.aspx en hlAboutCompanyLocalResource1.Text About Company(en)
Default.aspx en hlContactUsLocalResource1.Text Contact Us(en)
Default.aspx en hlLearnMoreLocalResource1.Text Learn More(en)
Default.aspx en btnSendDemoMailLocalResource1.Text Send Demo Mail(en)
AboutCompany.aspx en lbCompanyNameLocalResource1.Text MatrixSoftware(en)
AboutCompany.aspx en Literal1LocalResource1.Text "Текст с описанием фирмы(en)"
AboutCompany.aspx en hlContactUsLocalResource1.Text Contact Us(en)
AboutCompany.aspx en btnBackLocalResource1.Text Main Page(en)


Обращение к ресурсам осуществляю следующим образом:

this.lbHello.Text = HttpContext.GetLocalResourceObject("Default.aspx", "lbHelloLocalResource1.Text") as string;

Одним словом строго следую указаниям из статьи...
Проблема в том, что при компиляции приложения получаю 8 ошибок:

Error 1 The type or namespace name 'ResourceProviderFactory' could not be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceProviderFactory.cs 11 46 CustomResourceProviders


using System;

using System.Web;

using System.Diagnostics;

using System.Globalization;

using System.Resources;

using System.Threading;

namespace CustomResourceProviders

{

public class DBResourceProviderFactory : ResourceProviderFactory

{

public override IResourceProvider CreateGlobalResourceProvider(string classKey)

{

Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProviderFactory.CreateGlobalResourceProvider({0})", classKey));

return new DBResourceProvider(classKey);

}

public override IResourceProvider CreateLocalResourceProvider(string virtualPath)

{

Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "DBResourceProviderFactory.CreateLocalResourceProvider({0}", virtualPath));

// we should always get a path from the runtime

string classKey = virtualPath;

if (!string.IsNullOrEmpty(virtualPath))

{

virtualPath = virtualPath.Remove(0, 1);

classKey = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1);

}

return new DBResourceProvider(classKey);

}

}

}

-----------------------------------

Error 2 The type or namespace name 'Compilation' does not exist in the namespace 'System.Web' (are you missing an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceProvider.cs 2 18 CustomResourceProviders


using System.Web.Compilation; <-????

------------------------------------------

Error 3 The type or namespace name 'DisposableBaseType' could not be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceProvider.cs 17 36 CustomResourceProviders


namespace CustomResourceProviders

{

/// <summary>

/// Resource provider accessing resources from the database.

/// This type is thread safe.

/// </summary>

public class DBResourceProvider : DisposableBaseType, IResourceProvider <- ????????

{

private string m_classKey;

private StringResourcesDALC m_dalc;

// resource cache

private Dictionary<string, Dictionary<string, string>> m_resourceCache = new Dictionary<string, Dictionary<string, string>>();

/// <summary>

/// Constructs this instance of the provider

/// supplying a resource type for the instance.

/// </summary>

--------------------------------------------

Error 4 The type or namespace name 'IResourceProvider' could not be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceProvider.cs 17 56 CustomResourceProviders


------------------------------------

Error 5 The type or namespace name 'Security' does not exist in the namespace 'System.Web' (are you missing an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceReader.cs 5 18 CustomResourceProviders
С этой разобрался!

Error 6 The type or namespace name 'DisposableBaseType' could not be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceReader.cs 23 34 CustomResourceProviders


Error 7 The type or namespace name 'IResourceProvider' could not be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceProviderFactory.cs 14 25 CustomResourceProviders


Error 8 The type or namespace name 'IResourceProvider' could not be found (are you missing a using directive or an assembly reference?) C:\Documents and Settings\talekh\My Documents\Visual Studio 2005\Projects\DBResourceProviderWeb\CustomResourceProviders\DBResourceProviderFactory.cs 20 25 CustomResourceProviders


Совсем НЕ уверен, что делаю все как надобно, поэтому обращаюсь к вам !!!
Очень прошу помочь !!!

Буду рад получить от вас замечания и исправления !!!
Заранее благодарю!!!

Отвечает: EROS // 08.02.2008 / 03:47

Здравствуйте.
Судя по описанию ошибок вы не подключили необходимые библиотеки либо
не указали нужную директиву using


--
С уважением,
EROS

 


Чтобы задать Ваш вопрос, отправьте его на адрес expert@kbyte.ru

Раздел Эксперт на сайте Kbyte.Ru

Наверх

 

 

Последнее на форумах

ASP.NET работа за кадром | Автор: ave_maria
Добавлено: 20.02.2008 / 21:58 | ответов: 3 | просмотров: 70

Zdrasti :) iy nadeus' menia ne zakidaut pomodorami esli iy sproshy
dostatochno premetivniy vopros no dymau ne luboi programmist smojet otvetit'

dopystim aplikazia napisannaia na ASP.NET (daje ne ochen' vajno kakaia versia :) )
iy kompiliryu kod, zapuskaiu, a vot s etogo momenta esli mojno popodrobnee chto i kak proishodit ,
i esli mojno po ocherdnost'.

ili dopystim na forme est' mnogo vsiakih controls i naprimer button pri najatii kotorogo proishodit event etogo buttona
a do etogo momenta kak vse rabotaet? esli y kogo est' interesnie stat'i pochitat' bila bi ochen' priznatel'na
Spasibo masha

Ответить на это сообщение | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 1 | Автор: Алексей
Добавлено:21.02.2008 / 08:23
Подобную информацию обычно пишут во всех книжках по программированию на ASP .NET.

wiki :
Хотя ASP.NET берёт своё название от старой технологии Microsoft ASP, она значительно от нее отличается. Microsoft полностью перестроила ASP.NET, основываясь на Common Language Runtime (CLR), который является основой всех приложений Microsoft .NET. Разработчики могут писать код для ASP.NET, используя практически любые языки программирования, в том числе, и входящие в комплект .NET Framework(C#, Visual Basic.NET, и JScript .NET). ASP.NET имеет преимущество в скорости по сравнению с скриптовыми технологиями, так как при первом обращении код компилируется и помещается в специальный кеш, и впоследствии только исполняется, не требуя затрат времени на парсинг, оптимизацию, и т. д.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 2 | Автор: Алексей
Добавлено:21.02.2008 / 08:27

Внутри ASP.NET

Автор: Mika Soukhov
The RSDN Group

Источник: RSDN Magazine #6-2003
Опубликовано: 07.08.2004
Исправлено: 05.03.2006
Версия текста: 1.0
Вступление
От неуправляемого к управляемому. От IIS к ASP.NET
HTTP-конвейеры
Заключение
<script language="javascript"> function ToggleCode(id) { el=document.getElementById(id); img=document.getElementById("img"+id); if(el.style.display=="none") { img.src="http://www.rsdn.ru/images/ls2.gif"; el.style.display=""; } else { img.src="http://www.rsdn.ru/images/ls1.gif"; el.style.display="none"; } return false; } </script>

Вступление

Что такое ASP.NET? Одни скажут, что это среда для разработки крупных порталов, другие – средство для быстрого создания Web-сервисов. Для третьих ASP.NET – это инструмент разработки сайтов, адаптированных для работы на мобильных устройствах. И все они окажутся правы.

Более того, хотя ASP.NET в первую очередь и создана для решения упомянутых выше задач, возможности её этим отнюдь не ограничиваются. Благодаря развитой, расширяемой архитектуре, новые возможности в ASP.NET можно добавлять почти до бесконечности. Но для того, чтобы эти добавления не производились бездумно и не превращались в почти шаманские камлания, желательно представлять, каким же образом устроена ASP.NET, и что у неё внутри.

От неуправляемого к управляемому. От IIS к ASP.NET

ISAPI расширения

Во всех новых версиях Windows (XP, 2000 и 2003) есть компонент Internet Information Services (IIS). Получив запрос, IIS “смотрит”, к какому типу этот запрос относится (*.asp, *.shtml или *.aspx). Если IIS не способен самостоятельно обработать запрос (например, *.html или *.htm он может обрабатывать самостоятельно), то он передает его соответствующему ISAPI-расширению. Чтобы увидеть все доступные расширения, нужно:

  • запустить оснастку IIS Manager;
  • в контекстном меню узла Web Sites выбрать пункт меню “Properties”;
  • в появившемся окне свойств выбрать закладку “Home Directory”;
  • в группе “Application Settings” нажать кнопку “Configurations…”;
  • перейти на закладку “Mappings”.

В списке “Application Extensions” (в этой статье приводятся названия, используемые в Windows Server 2003) будут указаны все ISAPI-расширения IIS, установленные на текущий момент. Среди них наверняка будет и aspnet_isapi.dll (предполагается, что на компьютере читателя установлен IIS, а после него – .NET Framework, при другой последовательности установки ISAPI-расширение не будет зарегистрировано).

ПРИМЕЧАНИЕ

Иногда необходимо вручную зарегистрировать ASP.NET в IIS, к примеру, IIS установлен позднее .NET Framework, или, скажем, после обновления .NET Framework IIS не “увидел” изменений. Самый простой способ решить эту проблему – воспользоваться утилитой aspnet_regiis.exe. Она сможет самостоятельно зарегистрировать расширение aspnet_isapi.dll и сконфигурировать IIS.

Так что же это за расширение?

ASP.NET Hosting Runtime

Это – ISAPI-расширение (aspnet_isapi.dll), и предназначено оно для получения запросов, адресованных ASP.NET-приложениям (*.aspx *.asmx и т.д.), а также создания рабочих процессов aspnet_wp.exe, обратывающих запросы (aspnet_wp.exe – процесс, в котором, собственно, и работает ASP.NET, в Windows 2003 этот процесс носит более звучное имя – w3wp.exe).

Получив такой запрос, расширение проверяет настройки в секции <processModel> конфигурационного файла machine.config, находящегося в каталоге “%SystemRoot%\Microsoft.NET\Framework\v%CLRVersion%\CONFIG. Если уже существующий процесс aspnet_wp.exe не удовлетворяет хотя бы одному требованию, указанному в настройках, то aspnet_isapi.dll создает новый процесс с “правильными” настройками. При этом старый рабочий процесс продолжает существовать до окончания обработки выполняемых запросов, после чего удаляется.

ПРИМЕЧАНИЕ

В ASP.NET появилась модель рабочих процессов «Web Garden», которая позволяет на мультипроцессорных серверах создавать несколько экземпляров рабочих процессов. Чтобы разрешить поддержку этой модели, нужно установить атрибут webGarden секции <processModel> в true. При этом также рекомендуется задать значение атрибута cpuMask. Это битовая маска, значение которой задается в шестнадцатеричном формате. Маска указывает, какие из доступных процессоров будут участвовать в работе. Например, если маска установлена в 0x0D (в двоичном виде – 1101), то процессор номер 1 (нумерация идет справа и начинается с нуля) не будет участвовать в работе. Если webGarden установлен в false, то значение cpuMask игнорируется. Следует отметить, что Windows 2003 позволяет создавать несколько экземпляров w3wp.exe и для однопроцессорной машины.

При создании этого процесса через командную строку ему можно передать следующие параметры:

ПараметрОписание
IIS-Process-IDИдентификатор процесса IIS.
This-Process-Unique-IDИдентификатор процесса, использующийся при включенном режиме «Web Garden».
Number-of-Sync-PipesЧисло каналов (pipes), по которым может поступать информация.
RPC_C_AUTHN_LEVEL_XXXУровень аутентификации для модели безопасности COM, по умолчанию - CONNECT.
RPC_C_IMP_LEVEL_XXXУровень имперсонации для модели безопасности COM, по умолчанию - IMPERSONATE.
CPU-MaskБитовая маска доступных процессоров.
Max-Worker-ThreadsМаксимальное число рабочих потоков в пуле на каждый процессор.
Max-IO-ThreadsМаксимальное число I/O потоков в пуле на каждый процессор.
Таблица 1

Aspnet_isapi.dll общается с процессом aspnet_wp.exe через асинхронные именованные каналы (pipes). Через них aspnet_isapi.dll производит мониторинг рабочего процесса. Параметры сервера, получаемые как значение свойства HttpRequest.ServerVariables, также передаются через эти каналы. Имена каналов формируются автоматически, при их формировании используются количество каналов (Number-of-Sync-Pipes) и идентификатор процесса (IIS-Process-ID).

ПРИМЕЧАНИЕ

При использовании модели «Web Garden» в работе принимает участие параметр This-Process-Unique-ID. Но тут есть один нюанс. Дело в том, что aspnet_isapi.dll не знает, какой ID будет у процесса aspnet_wp.exe, так как данная информация становится известна только после создания этого процесса (она возвращается в параметре lpProcessInformation функции CreateProcess). Поэтому вместо идентификатора передается только что сгенерированный GUID.

В поставку ASP.NET входит aspnet_filter.dll. Этот фильтр предназначен для работы с “cookieless”-сессиями. В таких сессиях их идентификаторы передаются не в теле сообщения (http-заголовок “Cookie”), а в конце URL. Существование таких сессий обусловлено тем, что некоторые HTTP-клиенты в целях безопасности не работают с Cookie-файлами. Фильтр aspnet_filter.dll получает и разбирает строку запроса, после чего дописывает идентификатор сессии в тело сообщения под новым http-заголовком “AspFilterSessionId”.

Aspnet_state.exe – это сервис, предназначенный для хранения состояния сессий ASP.NET. Чтобы сохранять состояние сессии с помощью этого сервиса, нужно установить в конфигурационном файле значение атрибута mode секции <sessionState> в значение StateServer. Сохранение данных сессии в отдельном процессе обеспечивает большую надежность системы. Это позволяет не заботиться о процессе сбора мусора (не путать со сборкой мусора в .NET), который проводит aspnet_isapi.dll. Ведь aspnet_isapi.dll может с легкостью удалить процесс aspnet_wp.exe, в котором находятся все ваши приложения и их данные. Это может произойти по ряду причин, таких как: истечение времени ожидания ответа от aspnet_wp.exe, превышение порогового значения объёма используемой памяти и т.п.

Если предполагаются длительные и ресурсоемкие операции на сервере, то желательно увеличить некоторые значения, находящиеся в секции <processModel> файла machine.config, чтобы aspnet_isapi.dll не удалила по ошибке исправно работающее приложение. Стоит заметить, что значения по умолчанию, выставленные в этой секции, в большинстве случаев оказываются оптимальными. Поэтому, если возникает необходимость изменения пороговых значений, самое время задуматься о рефакторинге приложения.


Рисунок 1. Процесс активации ASP.NET-среды IIS-ом.

ПРИМЕЧАНИЕ

В Windows Server 2003 слушающий сокет (HTTP Listener), ранее интегрированный в IIS, реализован на уровне ядра (см. рисунок 1, модуль HTTP.SYS). Получая запросы, он не сразу передает их сервису, а сначала проверяет расширение запроса. Этим занимается модуль Web Administration Service (WAS). Если запрос предназначается ASP.NET-приложению, WAS создает рабочий процес w3wp.exe и передает в него запрос, в ином случае – в IIS. Благодаря этому обработка данных происходит значительно быстрее, нежели это происходило бы при лишнем обращении через границы процесса IIS.

Интересный факт – можно выставить для IIS 6.0 уровень изоляции, при котором ASP.NET будет работать через IIS. Для этого нужно:

  • запустить оснастку IIS Manager;
  • в контекстном меню узла Web Sites выбрать пункт меню “Properties”;
  • в появившемся окне свойств выбрать закладку “Service”;
  • установить флаг “Run WWW service in IIS 5.0 isolation mode” во включенное состояние.

Теперь данные, находящиеся в секции <processModel> файла machine.config, не будут учитываться, и настройки нужно будет производить только через оснастку IIS Manager. Для этого в контекстном меню секции Application Pool нужно выбрать пункт меню “Properties”. В появившемся окне свойств можно увидеть настройки, в том числе и те, которые выставляются в <processModel>.

HTTP-конвейеры

Инициализация HttpApplication

Хостинг ASP.NET

Что же представляет собою aspnet_wp.exe? Это unmanaged-приложение, являющееся хостом CLR. aspnet_wp.exe активирует CLR через CorBindToRuntimeEx, после чего создается домен для CLR. Далее происходит процесс инициализации среды ASP.NET. Для этого создается еще один отдельный домен с помощью метода ApplicationHost.CreateApplicationHost. В него передается тип класса, через который будет происходить процесс передачи и получения данных из обычного .NET-приложения в среду ASP.NET. В данном случае это класс ISAPIRuntime. Так же при создании домена передаются физический и виртуальный пути к ASP.NET-приложению.

Новый домен будет создан при первом обращении к какому-нибудь виртуальному пути. Количество создаваемых доменов не ограничено ничем, кроме объёма доступной памяти. Это настраивается в атрибуте memoryLimit секции <processModel>, и по умолчанию равно 60% от общего объема памяти.

При создании домена для ASP.NET среды проверяются настройки из файла machine.config. Значения настроек выбираются и из локального файла конфигурации web.config, который находится в папке приложения. Если одинаковые настройки встретились в обоих файлах, то, как правило, используются настройки из файла web.config.

Настройки из web.config могут заместить многие, хотя и не все, настройки из machine.config. Так, например, значения из секции <processModel>, используются в процессе создания нового рабочего процесса, который, в свою очередь, создает домен, конфигурируемый файлом web.config. Другими словами, домен с конкретным конфигурационным файлом web.config будет создан много позже считывания значений из <processModel> конфигурационного файла machine.config. Поэтому значения секции <processModel>, записанные в файле web.config, будут проигнорированы.

Некоторые настройки создаются путем объединения всех конфигурационных файлов, лежащих в виртуальных папках, которые сервер находит, просматривая виртуальный путь, к которому осуществляется запрос. Соответственно, machine.config – конфигурирующий файл корневого каталога всех виртуальных папок. Поэтому все, что можно разрешить на верхнем уровне, можно запретить на нижнем. Например, мое приложение называется MyWebApp. В его директории есть вложенная папка с названием MyWebAppSecurity. В обеих папках лежат файлы web.config. И если теперь разрешить всем пользователям обращаться анонимно к файлам каталога MyWebApp, то для MyWebApp/MyWebAppSecurity это можно запретить. Настройки авторизации прописываются в секции <authorization>.

После создания домена вызыватся метод ISAPIRuntime.ProcessRequest. Внутри этого метода (мы уже находимся в домене запрашиваемого ASP.NET-приложения) создается объект, класс которого является наследником HttpWorkerRequest. Этот объект передается в метод HttpRuntime.ProcessRequest. На данный момент существуют три класса, реализующие HttpWorkerRequest и используемые ISAPIRuntime.

КлассОписание
ISAPIWorkerRequestOutOfProcASP.NET работает с IIS 5.0
ISAPIWorkerRequestInProcForIIS6ASP.NET работает через IIS 6.0 (под Windows 2003)
ISAPIWorkerRequestInProcASP.NET работает без IIS 6.0 (под Windows 2003)
Таблица 2. Классы, реализующие HttpWorkerRequest

Вот отсюда, по большому счету, и начинается ASP.NET. Давайте разберем поподробнее, что именно происходит в недрах HttpRuntime. Для этого я привел UML-диаграмму последовательности вызовов для более наглядного представления происходящего внутри классов из пространства имен System.Web.


Рисунок 2. Процесс активации и инициализации объекта HttpApplication.

При первом вызове статического метода HttpRuntime.ProcessRequest производится инициализация очередей запросов, мониторинга состояния домена, создания профайлера (Profiler) для вывода трассировочной информации, настройки которого хранятся в секции <trace> и т.д. Также в этот момент производится загрузка всех сборок с расширением dll, находящихся в папке bin. После этого HttpRuntime подписывается на событие выгрузки домена, в обработчике которого будет вызван метод Application_End. Домен может быть выгружен в нескольких случаях. Первый – это когда произошла ошибка на ранних этапах инициализации HttpRuntime или при завершении обработки запроса. Второй – если произошли изменения в конфигурационных файлах (web.config, machine.config или файлы конфигурации, устанавливающие уровни политики безопасности в секции <trustLevel>). Третий – когда была удалена или переименована директория bin. И, наконец, четвертый – принудительно был вызван метод HttpRuntime.UnloadAppDomain.

Закончив процесс инициализации, HttpRuntime создает обработчик события, извещающего об окончании отсылки ответа, и передает его клиентской программе через вызов метода HttpWorkerRequest.SetEndOfSendNotification. В этом обработчике производится освобождение ресурсов, занятых объектами HttpRequest и HttpResponse. Так что программистам, работающим с HttpWorkerRequest, настоятельно рекомендуется вызывать этот метод, чтобы предотвратить утечку ресурсов.

HttpRuntime создает экземпляр HttpContext, который, в свою очередь, инициализирует HttpRequest и HttpResponse. Затем через фабрику HttpApplicationFactory создается новый экземпляр HttpApplication. В него загружается его состояние, то есть объект класса HttpApplicationState. После этого происходит процесс инициализации созданного экземпляра HttpApplication.

Так для чего нужен HttpApplication? Фактически этот объект является олицетворением всех ASP.NET-сущностей, таких, как: Web-страница (*.aspx), User control (*.ascx), Web-сервис (*.asmx) или HTTP-обработчик (*.ashx). При обращении к ним создается отдельный экземпляр HttpApplication. Все события, вся обработка информации происходят в нем, и все методы конечного объекта (Page, WebService и т.д.) вызываются прямо или косвенно из этого класса. Иными словами, HttpApplication – это мозг всего ASP.NET.

Парсинг и компиляция ASP.NET-файлов

При первом вызове метода HttpApplicationFactory.GetApplicationInstance происходит процесс парсинга и компиляции ASP.NET-файлов проекта. Основную работу выполняет класс BuildManager из пространства имен System.Web.Compilation.


Рисунок 3. Процесс парсинга и компиляции ASP.NET-файлов.

Экземпляр HttpApplication вызывает статический метод BuildManager.GetGlobalAsaxType. В этом методе проверяется, нужна ли перекомпиляция. Чтобы разобраться, как среда ASP.NET решает, нужна ли перекомпиляция сборок, давайте зайдем в каталог “%SystemRoot%\Microsoft.NET\Framework\v%CLRVersion%\Temporary ASP.NET Files”. Здесь находятся подкаталоги с названиями Web-приложений. Вот примерная структура дерева каталогов, которую можно увидеть, зайдя в любую папку ASP.NET-приложения:

\Microsoft .NET  \Framework   \v%CLRVersion%    \Temporary ASP.NET Files     \WebService      \a45ede38      
\336d7fb3 

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

ПРИМЕЧАНИЕ

Временная папка “Temporary ASP.NET Files” может и не находиться в каталоге “%SystemRoot%\. Чтобы изменить путь к временным файлам ASP.NET-приложений, нужно изменить в реестре значение ключа Path (в HTLM\Software\Microsoft\ASP.NET\v%CLRVersion%\).

В таких папках находятся XML-файлы вида [filename].[hashcode].compiled следующего содержания:

<preserve resultType="2" assembly="Code"  hash="a433b8acd5ef24" usesExistingAssembly="true" type="Service_asmx">  <filedep name="/WebService/Service.asmx"/> </preserve>

Именно по хеш-значениям ASP.NET определяет, изменился ли файл, и требуется ли перекомпиляция.

Если необходима перекомпиляция, то BuildManaged создает экземпляр класса BuildProviderHost и передает ему коллекцию объектов, реализующих IBuildProvider. Список классов этих объектов регистрируется в секции <buildProviders>. Провайдеры классифицируются по расширению обрабатываемых ими файлов. Эти расширения определяются значением атрибута extension в секции <buildProviders>. После этого экземпляр класса BuildProviderHost выбирает нужный IBuildProvider и вызывает его метод GenerateCode, передавая в него ссылку на самого себя. С помощью IBuildProvider создается экземпляр производного класса BaseParser, который занимается парсингом *.as*x-файлов.

После окончания парсинга класс, реализующий IBuildProvider, вызывает методы класса BuildProviderHost, осуществляющие компиляцию. Этим методам передаются результаты работы парсера и другие данные, которые могут потребоваться позже. Это могут быть название файла, название типа и т.д. Какой именно компилятор нужно использовать, задается в секции <compilers>.

ПРИМЕЧАНИЕ

Каждый домен позволяет перекомпилировать себя определенное количество раз. Этот предел задается в секции <compilation> атрибутом numRecompilesBeforeAppRestart. По умолчанию он равен 15. При этом происходит выгрузка всего домена, так как CLR позволяет выгружать только домены, а не отдельные сборки.

Global.asax

После разбора и компиляции файла фабрика классов HttpApplicationFactory получила описание класса (ссылку на объект System.Type), производного от HttpApplication, и начинает искать в нем с помощью технологии “отражения” (reflection) специальные методы Application_OnStart, Application_OnEnd, Session_OnStart, Session_OnEnd (или эквивалентные им Application_Start, Application_End, Session_Start, Session_End). Они будут вызываться самой фабрикой HttpApplicationFactory. Затем производится поиск оставшихся методов вида Application_Error, Application_BeginRequest, Application_EndRequest и их привязка к событиям объекта HttpApplication.

Заметьте, что класс Global из файла global.asax не содержит явной подписки на события. Эта подписка производится неявно. Не зная этого, можно произвести подписку вручную, а потом удивляться тому, что события приходят дважды. Что ж, еще одним секретом меньше в нашем мире.

ПРИМЕЧАНИЕ

Если вы хотите «привязать» вручную класс Global к соответствующим событиям, не пользуясь при этом дизайнером Visual Studio, нужно давать имена методам, обрабатывающим события, в соответствии со следующим шаблоном: Application_[On]%EventName%.

Пару слов о событии Application_Error. Если в момент обработки произошло исключение, то оно передается в метод HttpContext.AddError, где сохраняется до окончания обработки запроса. Для этого в HttpContext находится динамический список, где сохраняются объекты типа Exception. Чтобы получить все такие исключения, можно, вызвать свойство HttpContext.AllErrors.

Создав экземпляр HttpApplication, фабрика HttpApplicationFactory вызывает событие Application_Start, что сигнализирует о начале работы приложения.

Http-модули

После запуска приложения происходит инициализация созданного экземпляра класса HttpApplication. В начале создаются Http-модули (классы, реализующие интерфейс IHttpModule), перечень которых находится в секции <httpModules>. В файле machine.config зарегистрированы такие модули, как OutputCacheModule, SessionStateModule, SessionIDModule, FormsAuthenticationModule и т.д. Наиболее интересным является модуль, отвечающий за состояние данных сессии. Без сессий не могло быть и речи о безопасности. Все известные типы аутентификаций (Form, Windows) основаны на механизме сессий. Созданием сессий и занимается модуль SessionStateModule.

SessionStateModule хранит свои настройки в секции <sessionState>. Атрибут mode этой секции указывает на то, как будет храниться сессия. Он может принимать одно из следующих значений:

ЗначениеОписание
OffСессия не поддерживается.
InProcХранить данные локально в памяти рабочего процесса.
StateServerХранить данные в процессе aspnet_state.exe.
SQLServerХранить данные в SQL-сервере.
CustomХранить данные в другом хранилище.

Соответственно, в .NET Framework есть классы, реализующие приведенные выше модели хранения. Это InProcSessionStateStore, OutOfProcSessionStateStore, SqlSessionStateStore. Но, как и многое другое, эти классы объявлены как private.

Чтобы реализовать собственное хранилище, нужно создать класс, реализующий интерфейс ISessionStateStore, а в атрибуте mode выставить значение Custom, а в дополнительном атрибуте type указать полное имя типа вашего класса.

SessionStateModule подписывается на три события HttpApplication: асинхронное событие изменения состояния сессии (AcquireRequestStateAsync), событие сохранения состояния сессии (ReleaseRequestState), событие окончания запроса (EndRequest).

В обработчике AcquireRequestStateAsync модуль сохраняет состояние сессии в экземпляр класса HttpContext. Для этого он создает экземпляр класса HttpSessionState и добавляет его со значением ключа “AspSession” в коллекцию HttpContext.Items. Но в программе для получения состояния сессии в целях удобства лучше использовать свойство HttpContext.Session. При создании сессии вызывается событие Session_Start.

В обработчике ReleaseRequestState проверяется, был ли вызван метод HttpSessionState.Abandon. Если да, то вызывается метод HttpApplication.EndSession (который передает управление в метод Global.Session_End).

ПРИМЕЧАНИЕ

Время жизни сессии по умолчанию равно 20 минутам. Это значение можно изменить, отредактировав значение атрибута timeout секции <sessionState> конфигурационного файла.

Далее SessionStateModule сохраняет состояние сессии в хранилище и удаляет ее из HttpContext вместе с идентификатором сессии. Если придет новый запрос, SessionStateModule восстановит состояние сессии в HttpContext. С первого взгляда это может показаться лишним, зачем, мол, что-то удалять и снова восстанавливать? Но не забывайте, что экземпляры HttpApplication (и, соответственно, HttpContext, экземпляр которого содержится в HttpApplication) хранятся в пуле. И поэтому, если придет запрос от другого клиента, нам не нужно будет сначала удалять состояние старой сессии, а затем восстанавливать.

ПРИМЕЧАНИЕ

Если использовать в качестве хранилища сессий SQL Server или State Server, то никакого события об окончании сессии не придет. Это логично, ведь если даже сессия вашего приложения будет уничтожена или будет выгружен сам рабочей процесс, данные все равно останутся не тронутыми. Это функциональность очень полезна, когда Web Services объединяются в так называемые “Фермы” (Web Farm), когда несколько машин, нагрузка по которым распределяется через технологию Load Balansing, образуют один глобальный Web-сервис. Но в этой, с первого взгляда, очень полезной функциональности кроется довольно существенная опасность. Дело в том, что как уже описывалось выше, рабочий процесс может быть удален, как только у него случится внутренний сбой. Соответственно, если у вас используется всего одно Web-приложение, работающее с SQL Server, то никто не сможет послать ему команду об осбовождении данных и удалении записей из таблиц. Именно для решения этой проблемы в поставку .NET входят *.sql файлы (они находятся в корне каталога, где установлен Framework). Эти файлы создают базу, в которой будут храниться состояния сессий. Но вместе с базой устанавливается и SQL-задание (JOB – действие, которое активизируется по расписанию). Это задание смотрит время последнего подключения и, если был привышен временной лимит, задание считает, что данная сессия более не нужна, и удаляет данные из базы. На самом деле, вам в любом случае нужно будет выполнить эти скрипты, ибо они создают базу, с которой будет работать SqlSessionStateStore (если вы, конечно, не хотите устанавливать все вручную). Так что стоит упомянуть одну важную особенность. Дело в том, что существуют два установочных сценария, InstallSqlState.sql и InstallPersistSqlState.sql. Разница между ними в том, что первый устанавливает TempDB, базу, которая будет унижтожена при выключении компьютера. Второй сценарий, InstallPersistSqlState.sql, устанавливает полноценную БД ASPState.

Еще один достойный рассмотрения модуль – SessionIDModule. Он отвечает за процесс создания идентификатора сессии для выполняющегося за ним модуля SessionStateModule. Если используется “cookieless” сессия (значение атрибута cookieless секции <sessionState> равно true), то этот модуль подписывается на перехват события BeginRequest. В обработчике из http-заголовка “AspFilterSessionId”, созданного, как было сказано выше, фильтром aspnet_filter.dll, извлекается номер сессии. Обработчик записывает полученное значение в HttpContext.Items с ключом “AspCookielessSession”. Если же значение cookieless было равно false (это означает, что номер сессии будет записан в cookie, и будет передаваться в специальном http-заголовке “Cookie”), данный модуль ничего перехватывать не будет, а идентификатор сессии будет выбираться непосредственно из модуля SessionStateModule.

ПРИМЕЧАНИЕ

Как вы могли догадаться, порядок регистрации модулей в конфигурационном файле тоже имеет значение. Ведь если бы модуль SessionIDModule стоял после SessionStateModule, то он и подписался бы на событие BeginRequest позднее, чем SessionStateModule. В таком случае ни о каких cookieless-сессиях не могло бы быть и речи.

Модуль SessionIDModule появится только в ASP.NET 2.0 (на момент написания этой статьи мне была доступна бета-версия Visual Studio codename Whidbey). До этого он был интегрирован с модулем SessionStateModule. Хорошо это или плохо (особенно, если принимать предыдущее замечание), решать вам, но знать заранее не помешает.

Завершающим модулем в цепочке всегда ставится DefaultAuthenticationModule. Он не указывается ни в каких конфигурационных файлах, так как автоматически добавляется классом конфигурации HttpModulesConfiguration. Предназначен этот модуль для создания IPrincipal (используемого по умолчанию). Такой принципал не имеет ни имени, ни типа аутентификации, ни ролей, и используется только потому, что в ASP.NET-приложениях свойство HttpContext.Principal не может возвращать null (подробнее про безопасность можно почитать в статье Тимофея Казакова "Механизмы безопасности в .NET", опубликованной в нашем журнале, #4’2003.

Формирование конвейера

Следующий этап инициализации объекта HttpApplication – формирование массива действий, которые будут им последовательно выполнены.

На рисунке 4 представлены события HttpApplication в том порядке, в котором они будут вызваны.


Рисунок 4. Порядок исполнения “шагов” HttpApplication.

В ASP.NET 1.1 некоторых показанных на рисунке событий нет, так как они появятся только в ASP.NET 2.0. Так что вы можете не удивляться, если пока не найдете о них ничего в документации. Описание событий для версий 1.1 и ниже доступно в MSDN, поэтому я привел пояснения только к тем событиям, которые будут доступны в новой версии.

На этом процесс инициализации объекта HttpApplication заканчивается. После этого инициализированный объект начинает обработку запросов.

Обработка запроса


Рисунок 5. Вызов метода HttpApplication.BeginProcessMessage.

HttpRuntime вызывает метод HttpApplication.BeginProcessMessage. Этот метод является реализацией интерфейса IHttpAsyncHandler, и используется для инициализации асинхронного вызова. После этого экземпляр HttpApplication начинает поочередно выполнять действия, список которых составлен на предыдущем этапе. Именно в этот момент вызываются обработчики событий, приведенные на рисунке 4, в модулях (Http Modules). Вы наверняка заметили неописанные действия MapHandlerExecutionStep, CallHandlerExecutionStep, CallFilterExecutionStep? Я опишу их подробнее.

MapHandlerExecutionStep – на этом шаге создается объект-обработчик полученной информации, то есть класс, реализующий IHttpHandler. На рисунке 6 показана UML-диаграмма создания этого обработчика.


Рисунок 6. Создание обработчика.

По запросу, посланному пользователем (*.asmx, *.ashx, *.aspx и т.д.), создается фабрика обработчиков (IHttpHandlerFactory). Все фабрики записаны в секции <httpHandlers>. Далее создается фабрика протоколов, используемых при запросе. В случае Web-сервиса этой фабрикой является SoapServerProtocolFactory. С помощью созданного экземпляра ServerProtocolFactory создается объект ServerProtocol, который передается конструктору обработчика. После создания ссылка на обработчик помещается в свойство HttpContext.Handler.

Для регистрации обработчиков используется секция <httpHandlers> конфигурационного файла. Чтобы добавить обработчик, нужно создать подсекцию <add> с атрибутами, приведёнными ниже:

  • Verb -тип запроса (PUT, GET или POST);
  • Path - путь или маска (например: *.asmx, service1.asmx);
  • Type - полное имя типа обработчика.

Имеется также возможность управлять созданием обработчиков. Для этого вместо обработчиков в секции <httpHandlers> регистрируются их фабрики (классы, реализующие интерфейс IHttpHandlerFactory). Так как фабрика создает нужный впоследствии обработчик, то, реализовав свою собственную фабрику, можно контролировать и регулировать процесс создания обработчиков. Обычно в machine.config указываются именно фабрики обработчиков (WebServiceHandlerFactory, HttpRemotingHandlerFactory, PageHandlerFactory и т.д.).

ПРИМЕЧАНИЕ

Если вы регистрируете обработчик, а не фабрику, то в HttpApplication для него создается HandlerFactoryWrapper. Вероятно, это сделано разработчиками ASP.NET из соображений единообразия и не несет дополнительной функциональности.

CallHandlerExecutionStep – это вызов обработчика, созданного на предыдущем шаге. На рисунке 7 показана диаграмма, демонстрирующая этот вызов (на примере вызова Web-сервиса, WebServiceHandler). Экземпляр этого класса занимается сериализацией и десериализацией SOAP-сообщений и, конечно же, вызовом Web-метода.


Рисунок 7. Вызов Web-метода.

В процессе выполнения обработчик обращается к объекту HttpRequest для получения входящих данных и заголовков HTTP-запроса. На рисунке 8 показано, что происходит при этих вызовах внутри класса HttpRequest.


Рисунок 8. Загрузка данных запроса в классе HttpRequest.

HttpRequest в цикле вызывает метод HttpWorkerRequest.ReadEntityBody, дочитывая входящую информацию, посланную в теле POST-запроса. После этого он фильтрует входящий поток. Этот фильтр может быть установлен в свойстве HttpRequest.Filter. Затем полученные данные десериализуются и вызывается Web-метод. На рисунке 7 отмечено, что на данном этапе может быть задействована COM+-транзакция. Это зависит от свойства TransactionOption атрибута WebMethodAttribute Web-метода. Далее в выходной поток сериализуется ответ Web-сервиса.

Тут я хочу упомянуть о еще одной маленькой, но очень важной детали. Часто в форумах, посвященных ASP.NET, можно услышать вопрос “А почему в моей реализации IHttpHandler значение сессии равно null?”. Дело в том, что если вашему обработчику нужно работать с состоянием сессии, он должен реализовать интерфейс IRequiresSessionState. Данный интерфейс не имеет ни методов, ни свойств, ни событий, и является своеобразным маркером. Модуль SessionStateModule, проверив, что текущий обработчик реализует этот интерфейс, немедленно сообщает HttpApplication о прекращении асинхронной обработки события AcquireRequestState, и HttpApplication, в свою очередь, начинает исполнять его последовательно.

CallFilterExecutionStep – вызов фильтров исходящего потока (HttpResponse.Filter). На рисунке 9 представлено, что происходит при этом.


Рисунок 9. Окончание обработки запроса в классе HttpResponse.

Как видно из диаграммы (см. рисунок 9), именно здесь вызываются последние события HttpApplication PreSendRequestHeaders, PreSendRequestContent в цепочке делегатов.

Завершение работы HttpApplication

Вот мы и добрались до этапа завершения работы HttpApplication и освобождения ресурсов. Отработавший экземпляр HttpApplication помещается в пул таких же объектов для того, чтобы в следующий раз не создавать его заново, повторяя достаточно большой процесс инициализации. Но если максимальный размер пула уже достигнут, а он, кстати, “как показывают опыты советских ученых”, имеет жестко определённое значение, равное 100 (это можно увидеть в методе RecycleNormalApplicationInstance класса HttpApplicationFactory при помощи любого дизассемблера или декомпилятора), то объект в пул не помещается и уничтожается. При уничтожении объекта вызывается метод IHttpModule.Dispose для всех модулей, используемых приложением.

Заключение

Надеюсь, что этот материал поможет вам разобраться в механизмах работы ASP.NET и более осмысленно использовать эту технологию в своей работе. Хочу выразить особую благодарность следующим людям: моему брату, Игорю Сухову, за неоценимую помощь в написании статьи, и Тимофею Казакову - за предоставление академической информации и конструктивную критику.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 3 | Автор: ave_maria
Добавлено:21.02.2008 / 11:12
Sypper, spasibo za stol' bistriy otvet!!!! i iy ochen im dovol'na!!!!!!!!!!

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Кодирование -Раскодирование строк | Автор: Игорь Голов
Добавлено: 20.02.2008 / 18:08 | ответов: 9 | просмотров: 124

Нужно кодировать.раскодировать строки (это требуется для сохранения особых настроек)

Алгоритм Rinjdael для работы со строками подойдёт?
И подскажите, пожайлуста, в какую сторону дальше копать...
Спасибо!

Ответить на это сообщение | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 1 | Автор: Игорь Голов
Добавлено:20.02.2008 / 18:15
Да, и если нетрудно, подскажите, пожайлуста, как реализовать Rinjdael средствами Студии.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 2 | Автор: 5acred Phoenix
Добавлено:20.02.2008 / 18:33
Смотрите в сторону пространства имён System.Security.Cryptography

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 3 | Автор: Игорь Голов
Добавлено:20.02.2008 / 18:53
Шифрование строки по алгоритму DES
Алгоритм выложен на абстрактном языке.
Когда разберусь с переводом в код - выложу текст программы здесь. :)

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 4 | Автор: 5acred Phoenix
Добавлено:20.02.2008 / 21:55
Алгоритм выложен на абстрактном языке.

Гы-гы))) Это си-шарп

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 5 | Автор: EROS
Добавлено:21.02.2008 / 00:18
Дожили... C# уже стал абстракным... куда мир катится?

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 6 | Автор: Алексей
Добавлено:21.02.2008 / 03:20
http://kbyte.ru/Expert/Show.aspx?id=27

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 7 | Автор: Игорь Голов
Добавлено:21.02.2008 / 11:12
Однакось вот обещанная реализация в [urlhttp://kbyte.ru/code/Show.aspx?id=116&lang=ru]нашем Кодообменнике[/url]
Сорри, если кого - то обидел - я не со Зла.... :)
Если это С# - то это вообще здорово!

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 8 | Автор: Игорь Голов
Добавлено:21.02.2008 / 11:15
Упс!
Ссылка на обещанный код

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 9 | Автор: Игорь Голов
Добавлено:21.02.2008 / 17:42
Дожили... C# уже стал абстракным... куда мир катится?


Не хочу давать долгосрочных прогнозов, но судя по всему мир катится к тому, что VB .NET - наше всё! (с) :)

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Как капироват ДБгриде вбронного страку другой ДБгрид | Автор: agakamran
Добавлено: 20.02.2008 / 15:41 | ответов: 1 | просмотров: 25

Добрый день уважаимы експерт у меня такой задача нада с одного ДБ_грида по щелчку мышку
копировалса выбронного строка изаписалса другой ДБ_грид заране спосиба

Ответить на это сообщение | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 1 | Автор: Алексей
Добавлено:20.02.2008 / 16:25
Можно добавить LinkButton или Button в GridView, из которого требуется скопировать данные, у кнопки указать параметр CommandArgument, в который передать уникальный идентификатор данных, а в свойство CommandName можно написать что-нибудь типа "CopyRow". Затем в коде написать обработчик события RowCommand для GridView, в котором будут копироваться данные с указанным в параметре CommandArgument идентификатором в другую БД.

зы: что-то с русским языком совсем плоховато :-)

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Перевод с vb на vb.net = проблемка | Автор: DANger
Добавлено: 19.02.2008 / 11:29 | ответов: 3 | просмотров: 75

Привет всем ..
Пытаюсь переписать по vb под нет ..
Студия выдаёт ошибку .. MSDN говорит что то про инициализацию .. хотя в vb всё работает !
'UPGRADE_WARNING: Event txtSend.TextChanged may fire when form is initialized. Click for more: 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="vbup2075"'
Private Sub txtSend_TextChanged(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles txtSend.TextChanged
tcpClient.SendData(txtSend.Text)
End Sub
Помогите плизз кто сталкивался ..
Как это должно выглядеть правильно под vb.net ..

Ответить на это сообщение | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 1 | Автор: 5acred Phoenix
Добавлено:19.02.2008 / 20:52
Сам-то обработчик события верный.. Покажи, что у тебя в Form_Load()

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 2 | Автор: Алексей
Добавлено:20.02.2008 / 11:49
Скорей всего в InitializeComponent косяк, см. в Form.Designer, либо покажи, что там у тебя написано.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 3 | Автор: EROS
Добавлено:20.02.2008 / 12:42
Этот WARNING говорит о том, что TextChanged может сработать при инициализации формы, и как следствие в этой процедуре он может получить исключение т.к. на этот момент txtSend.Text само собой будет пустым. Тебе туда надо просто вставит дополнительную проверку чтобы убедиться что поле не пустое. Да и собственно говоря TextChanged далеко не лучшая процедура для отправки данных. Более того, настоятельно рекомендую воздержаться от автоматического перевода кода и VB6 в NET. Ни к чему хорошему это никогда не приводило. Наверняка ты использовал Winsocks в своем проекте, а для НЕТ это далеко не лучшее решение..

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


zip-файл String'om через POST | Автор: bobsapp
Добавлено: 19.02.2008 / 10:38 | ответов: 5 | просмотров: 100

народ, доброго времечка!
по ходу работы отправляю список значений на сервер, но потом глянул, что можно отправлять просто зазипованный файл стрингом, т.к. в виде значений текстовый файл весит примерно 450кб, а запакованный в зип всего 25кб, так вот вопрос, как на сервере можно принять этот стринг-зип чтоб распаковать в памяти и уже работать непосредственно со значениями?
библиотеку думаю подключать Ionic.Utils.Zip

Ответить на это сообщение | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 1 | Автор: Edlin
Добавлено:20.02.2008 / 09:59
Нужно использовать элемент управления FileUpload. Он имеет кнопку Browse, позволяющую на клиенте выбрать файл, который ты будешь загружать на сервер. Далее создается еще одна кнопка, по нажатию на которую файл отправляется на сервер. Это обычная кнопка, никак не связанная с FileUpload. В ее обработчике события Click ты проверяешь свойства объекта FileUpload. Среди них есть и Stream, содержащий посылаемый файл.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 2 | Автор: bobsapp
Добавлено:20.02.2008 / 11:05
клиент вообще левый, в смысле, что написан на совершенно другом языке в корне отличном от платформы .NET
однако, там формируется HTTPResponse и значение строки. Дальше, в ВебМетод на сервак приходит строка:
<WebMethod>_
001:
002:
003:
004:
005:
006:
Public Function TestArchiveString (ByVal strZipData As String) As Boolean
' так вот здесь strZipData это строка ни что иное как запакованный файл переданный строкой
' вопрос, как её(строку) извлечь в какой массив значений прямо в этом коде?
End Function

Colorized by: Eros.CodeSyntaxColor Engine


П.С. а куда делся Алексей Немиров? Он никогда не отвечает на мои посты почему-то :(

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 3 | Автор: Алексей
Добавлено:20.02.2008 / 11:56
П.С. а куда делся Алексей Немиров?

одна лишняя буква :)

я здесь, никуда не делся, если меня нет, я все равно здесь, но времени пока нет вступать в дискуссии и отвечать на вопросы..

Он никогда не отвечает на мои посты почему-то :(

видимо, ты всегда задаешь вопросы, когда я занят :)

в .Net есть пространство имен System.IO.Compression:
001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
'compress
Dim mem As New IO.MemoryStream
Dim gz As New System.IO.Compression.GZipStream(mem, IO.Compression.CompressionMode.Compress)
Dim sw As New IO.StreamWriter(gz)
sw.WriteLine("hello compression")
sw.Close()

'decompress
Dim mem2 As New IO.MemoryStream(mem.ToArray)
gz = New System.IO.Compression.GZipStream(mem2, IO.Compression.CompressionMode.Decompress)
Dim sr As New IO.StreamReader(gz)
MsgBox(sr.ReadLine)
sr.Close()

Colorized by: Eros.CodeSyntaxColor Engine

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 4 | Автор: bobsapp
Добавлено:20.02.2008 / 15:41
oh, ура, наконец-то Алексей ответил, спасибо, но... я же писал не о компрессии, а о сжатии...

я принимаю в функцию стринг, но этот стринг не что иное, как zip-файл, т.е. если в функции сохранить этот стринг как файл string.zip, то получится обычный зазипованный файл, который можно открыть.
но! я не хочу его сохранять на диск, я хочу этот поток разжимать прямо в функции(например, каким-то образом в память) и считать данные с уже распакованного в памяти файла в переменные.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Ответ # 5 | Автор: Алексей
Добавлено:20.02.2008 / 16:32
см. аттач.

Добавить ответ | Посмотреть эту тему на сайте | Форум Kbyte.Ru


Наверх

 

Последние статьи

Как узнать размер базы данных MySQL через php
Автор: Николай Рудченко | добавлено: 14.02.2008 / 13:01 | просмотров: 53

В этой небольшой статье вы узнаете, как получить размер вашей базы данных (БД) MySQL используя язык программирования PHP.

Взаимодействие с другими сайтами через HTTP (HttpWebRequest и HttpWebResponse)
Автор: Немиро Алексей | добавлено: 27.07.2007 / 14:01 | просмотров: 2842

В данной статье речь пойдет о том, как можно программно заполнить и отправить Web-форму, а также получить результат с использованием довольно простых классов - HttpWebRequest и HttpWebResponse.

Работа с SQL – Запросы
Автор: Хабибулин А. (aka Bille Gates) | добавлено: 22.07.2007 / 04:43 | просмотров: 5305

В данной статье рассмотрены часто используемые SQL-запросы и приведены примеры их использования.

Использование XML.Serialization
Автор: Немиро Алексей | добавлено: 19.07.2007 / 15:38 | просмотров: 1480

В данном обзоре речь пойдет об использование XML.Serialization в .NET и ее применении на примере сохранения и считывания настроек программы с использованием синтаксиса языка Visual Basic .NET.

Выбор языка программирования для научных работ (2-я редакция)
Автор: Юдин С. Ю. | добавлено: 15.03.2007 / 02:49 | просмотров: 4129

Идея написания этой статьи возникла после того, как на одном из сайтов по физике http://physics.nad.ru/aniboard/messages/291.html я обнаружил интересный алгоритм моделирования движения тела в поле постоянной напряженности по различным направляющим. Вернее, меня заинтересовал не весь алгоритм, т.к. основа их всех одна (принцип Даламбера, если не считать квазиалгоритма с применением уравнений Лагранжа 2-го рода) и различаются они только способом определения реакций в месте соприкосновения тел. ...

 

Статьи на Kbyte.Ru

Наверх

 

Интересные коды и решения

Получение многоразрядного числа
Дата добавления: 31.07.2006 / 12:32 | просмотров: 1146 | платформа: VB

Как определить, какие кнопки мыши нажаты?
Дата добавления: 16.08.2006 / 19:45 | просмотров: 1279 | платформа: VB

Как запретить пользователю закрыть форму?
Дата добавления: 31.07.2006 / 12:10 | просмотров: 1115 | платформа: VB

Как можно скрыть курсор мышки?
Дата добавления: 16.08.2006 / 19:48 | просмотров: 1198 | платформа: VB

Как распечатать Web-страницу?
Дата добавления: 17.08.2006 / 17:58 | просмотров: 1492 | платформа: VB

Как узнать, в какой папке находится приложение?
Дата добавления: 01.08.2006 / 20:29 | просмотров: 1103 | платформа: VB

Как получить случайное число?
Дата добавления: 16.08.2006 / 19:52 | просмотров: 1029 | платформа: VB

Как загрузить изображение в PictureBox/Image?
Дата добавления: 16.08.2006 / 19:55 | просмотров: 1454 | платформа: VB

Как изменить фон в каждом элементе списка?
Дата добавления: 31.07.2006 / 14:13 | просмотров: 2095 | платформа: ASP .NET (VB)

Как сделать полупрозрачную форму?
Дата добавления: 01.08.2006 / 17:25 | просмотров: 1623 | платформа: VB .NET

 

Универсальные коды на Kbyte.Ru

Наверх

 

Юмор на Kbyte.Ru

Мужик с работы приходит пьяный. Заходит на кухню холодильник открывает. Постоял посмотрел, разворачивается, хрясь жене подзатыльник. Жена думает: видно еды мало. На следующий день она забивает холодильник едой. Мужик пришёл с работы, опять дал жене подзатыльник. Жена думает: видно пива нет. На следующий день муж опять дал жене подзатыльник. Жена думает: еда есть, пиво есть, значит нету водки. Утром забила холодильник водкой, пивом, едой. Мужик, как обычно, пришёл пьяный, заглянул в холодильник, развернулся и дал жене ещё сильнее подзатыльник. Жена не выдержала:
- За что?
Мужик:
- Ты почему уже третий день в холодильнике свет не выключаешь!!!

 

Рейтинг: 203
Всего голосов: 331
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Сидят два хакера за компьютером. Вдруг заходит кот. Один говорит:
- Твой кот?
- Да.
- Как зовут?
- Zyxel.
- А почему так?
- Смотри.
Берёт веник, подходит к коту и говорит:
- Zyxel Конект!
Кот:
- Пшшшшшшшшшш

 

Рейтинг: 147
Всего голосов: 246
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Звонок в компьютерную контору:
- С моим компьютером проблемы, он не включается...
- Как не включается?
- Да пишет все время: "Вставьте дискету, вставьте дискету..."
- А вы вставляли?
- Hет... Как-то не догадался

 

Рейтинг: 165
Всего голосов: 271
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

- А вы поменяете пять пачек обычного порошка на один грамм необычного?

 

Рейтинг: 116
Всего голосов: 223
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Смотрю в выходной новости по Первому в 21.00. Весь выпуск - одни катастрофы: идет опознание жертв родственниками, в Питере горит Троицкий собор, какой-то взрыв, какое-то убийство, теракт... От сюжета к сюжету становится все страшнее.
В конце выпуска диктор объявляет: "Вот и все новости дня, которые мы успели вам сообщить. А сейчас на нашем канале смотрите художественный фильм "Это могло случиться с тобой".

 

Рейтинг: 249
Всего голосов: 385
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Ехали как-то на джипе четыре братка, вдруг в них врезается сзади старая шестерка. Братки злые выходят из машины и видят из шестерки выходит старый дед. Братки ему:
Ну все дед ты попал!!!
Дед: Не злитесь ребятки, у меня сын на птицефабрике работает, я ему позвоню, он все уладит.
Братки думают ладно раз на птицефабрике работает, значит деньги есть.
Дед позвонил, приезжают через десять минут СОБРовцы, всех братков на землю повалили.
Подходид СОБРовец к деду и говорит:
Батя, сколько раз повторять можно, не на птицефабрике, а в отряде ОРЕЛ!!!

 

Рейтинг: 183
Всего голосов: 270
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Земля
2050 год
Генетический программер разбирает очерендной кусок генной последовательности и видит следующий комментарий:
/* A eti geny nado by ubrat nahren. Archangel Gavriil */

 

Рейтинг: 190
Всего голосов: 324
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Следом за акцией "Кликни Деда Мороза!" компания Coca-Cola
планирует с Нового года запустить акции "Бэкапни Деда Мороза" и
"Форматни Снегурочку"

 

Рейтинг: 271
Всего голосов: 413
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

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

 

Рейтинг: 271
Всего голосов: 424
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Спрашивает учитель на уроке математики у Гоги:
- Гоги, а чему равна сумма квадратов катета?
- Квадрату гипотенузы!
- A чем докажешь?
Гоги:
- Мамой клянусь!!!

 

Рейтинг: 352
Всего голосов: 513
Оцените этот анекдот через E-Mail:
Выберите оценку: 5 | 4 | 3 | 2 | 1

 

Весь юмор на Kbyte.Ru

Наверх


Выпуск подготовил: R-KDR-19826

Редактор: Немиро Алексей

Copyright (c) Nemiro AS, 2005-2007


В избранное