Сейчас пришла пора разобраться с тем, что происходит с кодом от момента написания ASPX/ASCX- разметки и CS-кода до момента их физического выполнения в виде некоторого скомпилированного кода на сервере.
Начнём с простой, и, в принципе, известной особенности ASP.NET 2.0: частичной компиляции страниц. Как мы знаем, частичные (partial) классы — это синтаксический сахар для разнесения исходного кода класса в несколько разных файлов. Это особенно удобно, когда одна часть кода класса генерируется автоматически некоторой утилитой, а другая — пишется вручную программистом. Так как модифицировать автогенерённый код — очень плохая практика (ведь последующая перегенерация уничтожит все такие изменения), в таких случаях утилита генерирует классы как частичные, и программист может спокойно добавить нужный функционал «рядом», в соседнем файле, не затрагиваемом перегенерацией.
Именно так и поступает среда ASP.NET, когда сталкивается с парой ASPX (или ASCX,ASHX,ASAX) + CS. Пусть есть пара описаний — ASPX-страница с серверным кодом и элементом управления и её файл отделённого кода:
Рассмотрим, как данная страница превращается в код.
Здесь ASP.NET генерирует целых два класса. Во-первых, генерируется partial class SomePage, в котором определёны защищённые члены для элементов управления (в нашем случае Label lblHello) и два свойства для доступа к пользовательским типизированным объектам Profile и ApplicationInstance (в данном примере их типы не переопределялись и совпадают со стандартными), а также представлен тот серверный код, что мы видели в теге <script runat=«server»>
Во втором классе (именно он является окончательным классом страницы) мы видим код, отвечающий за генерацию дерева элементов управления страницы, и код, отвечающий за инициализацию инфраструктуры компиляции и кэширования скомпилированных страниц в виде сборок .dll.
Разберем его по частям.
После этого заявляется, что скомпилированная версия данного класса (в виде dll) должна быть перекомпилирована при изменении следующих исходных файлов: ~/SomePage.aspx и ~/SomePage.aspx.cs. Данная информация будет ниже передана инфраструктуре (а именно — стандартному провайдеру пути), который, в свою очередь, будет предоставлять её провайдеру компиляции при необходимости.
Генерацией дерева элементов управления занимается метод @__BuildControlTree, в котором производится поочёредный вызов методов вида @__BuildControl<control_id>():
Полученный класс реализует интерфейс IHttpHandler, и после отработки фабрики страниц созданный ею экземпляр этого класса обрабатывает запрос вида http://url/SomePage.aspx — после чего начинается всем хорошо известный жизненный цикл ASP.NET.
В дальнейшем, вероятно, напишу подробнее про провайдеры, упоминавшиеся здесь: компиляции и виртуального пути.
UPD: Перенёс в «.NET»
Генерация полного исходного кода
Начнём с простой, и, в принципе, известной особенности ASP.NET 2.0: частичной компиляции страниц. Как мы знаем, частичные (partial) классы — это синтаксический сахар для разнесения исходного кода класса в несколько разных файлов. Это особенно удобно, когда одна часть кода класса генерируется автоматически некоторой утилитой, а другая — пишется вручную программистом. Так как модифицировать автогенерённый код — очень плохая практика (ведь последующая перегенерация уничтожит все такие изменения), в таких случаях утилита генерирует классы как частичные, и программист может спокойно добавить нужный функционал «рядом», в соседнем файле, не затрагиваемом перегенерацией.
Именно так и поступает среда ASP.NET, когда сталкивается с парой ASPX (или ASCX,ASHX,ASAX) + CS. Пусть есть пара описаний — ASPX-страница с серверным кодом и элементом управления и её файл отделённого кода:
SomePage.aspx:
- <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SomePage.aspx.cs" Inherits="SomePage" %>
- <html>
- <head>
- <title>SomePage</title>
- </head>
- <body>
- <asp:Label ID="lblHello" runat="server" EnableViewState="false" />
- <script runat="server" type="text/C#">
- protected void Page_Load(object sender, EventArgs e)
- {
- lblHello.ForeColor = System.Drawing.Color.Red;
- }
- </script>
- </body>
- </html>
SomePage.aspx.cs:
- using System;
- using System.Web;
- using System.Web.UI;
- using System.Web.UI.WebControls;
- public partial class SomePage : Page
- {
- protected void Page_PreRender(object sender, EventArgs e)
- {
- lblHello.Text = "Hello from code";
- }
- }
Рассмотрим, как данная страница превращается в код.
Здесь ASP.NET генерирует целых два класса. Во-первых, генерируется partial class SomePage, в котором определёны защищённые члены для элементов управления (в нашем случае Label lblHello) и два свойства для доступа к пользовательским типизированным объектам Profile и ApplicationInstance (в данном примере их типы не переопределялись и совпадают со стандартными), а также представлен тот серверный код, что мы видели в теге <script runat=«server»>
App_Web_ogjttwiz.0.cs:
После этого генерируется собственно класс страницы, который уже будет обрабатывать наш физический запрос. Замечу, что его ASP.NET помещает в тот же временный файл кода, в котором мы видели и предыдущий класс.
- public partial class SomePage : System.Web.SessionState.IRequiresSessionState {
- protected global::System.Web.UI.WebControls.Label lblHello;
- protected void Page_Load(object sender, EventArgs e)
- {
- lblHello.ForeColor = System.Drawing.Color.Red;
- }
- protected System.Web.Profile.DefaultProfile Profile {
- get {
- return ((System.Web.Profile.DefaultProfile)(this.Context.Profile));
- }
- }
- protected System.Web.HttpApplication ApplicationInstance {
- get {
- return ((System.Web.HttpApplication)(this.Context.ApplicationInstance));
- }
- }
- }
App_Web_ogjttwiz.0.cs (продолжение):
Этот класс по умолчанию имеет имя somepage_aspx, но это можно изменить установкой атрибута ClassName у директивы Page, вот так:
- namespace ASP {
- ...
- [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
- public class somepage_aspx : global::SomePage, System.Web.IHttpHandler {
- private static bool @__initialized;
- private static object @__fileDependencies;
- [System.Diagnostics.DebuggerNonUserCodeAttribute()]
- public somepage_aspx() {
- string[] dependencies;
- (this).AppRelativeVirtualPath = "~/SomePage.aspx";
- if ((global::ASP.somepage_aspx.@__initialized == false)) {
- dependencies = new string[2];
- dependencies[0] = "~/SomePage.aspx";
- dependencies[1] = "~/SomePage.aspx.cs";
- global::ASP.somepage_aspx.@__fileDependencies = this.GetWrappedFileDependencies(dependencies);
- global::ASP.somepage_aspx.@__initialized = true;
- }
- this.Server.ScriptTimeout = 30000000;
- }
- [System.Diagnostics.DebuggerNonUserCodeAttribute()]
- protected override void FrameworkInitialize() {
- base.FrameworkInitialize();
- this. @__BuildControlTree(this);
- this.AddWrappedFileDependencies(global::ASP.somepage_aspx.@__fileDependencies);
- }
- [System.Diagnostics.DebuggerNonUserCodeAttribute()]
- private void @__BuildControlTree(somepage_aspx @__ctrl) {
- this.InitializeCulture();
- System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
- @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(@"<html>
- <head>
- <title>SomePage</title>
- </head>
- <body>"));
- global::System.Web.UI.WebControls.Label @__ctrl2;
- @__ctrl2 = this. @__BuildControllblHello();
- @__parser.AddParsedSubObject(@__ctrl2);
- @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("</body></html>"));
- }
- [System.Diagnostics.DebuggerNonUserCodeAttribute()]
- private global::System.Web.UI.WebControls.Label @__BuildControllblHello() {
- global::System.Web.UI.WebControls.Label @__ctrl;
- @__ctrl = new global::System.Web.UI.WebControls.Label();
- this.lblError = @__ctrl;
- @__ctrl.ApplyStyleSheetSkin(this);
- @__ctrl.ID = "lblHello";
- @__ctrl.EnableViewState = false;
- return @__ctrl;
- }
- ...
- }
- }
Как видно здесь, для генерации первого из классов (public partial class SomePage) используется атрибут CodeFile и Inherits, причём они должны быть согласованы (то есть, в файле, указанном в CodeFile, должен находиться класс с именем, как в атрибуте Inherits). После этого создаётся второй класс с именем, взятом из атрибута ClassName (или по умолчанию — <Значение_Inherits_lowercase>_aspx)
- <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SomePage.aspx.cs" Inherits="SomePage" ClassName="MyPageClass" %>
Во втором классе (именно он является окончательным классом страницы) мы видим код, отвечающий за генерацию дерева элементов управления страницы, и код, отвечающий за инициализацию инфраструктуры компиляции и кэширования скомпилированных страниц в виде сборок .dll.
Разберем его по частям.
Конструктор somepage_aspx инициализирует текущий виртуальный путь для страницы, так как сама инфраструктура ASP.NET не отвечает за это (надо иметь в виду этот факт при разработке собственных пользовательских классов-наследников страницы, обходящий стандартный путь компиляции).
- public somepage_aspx() {
- string[] dependencies;
- (this).AppRelativeVirtualPath = "~/SomePage.aspx";
- if ((global::ASP.somepage_aspx.@__initialized == false)) {
- dependencies = new string[2];
- dependencies[0] = "~/SomePage.aspx";
- dependencies[1] = "~/SomePage.aspx.cs";
- global::ASP.somepage_aspx.@__fileDependencies = this.GetWrappedFileDependencies(dependencies);
- global::ASP.somepage_aspx.@__initialized = true;
- }
- this.Server.ScriptTimeout = 30000000;
- }
После этого заявляется, что скомпилированная версия данного класса (в виде dll) должна быть перекомпилирована при изменении следующих исходных файлов: ~/SomePage.aspx и ~/SomePage.aspx.cs. Данная информация будет ниже передана инфраструктуре (а именно — стандартному провайдеру пути), который, в свою очередь, будет предоставлять её провайдеру компиляции при необходимости.
Генерацией дерева элементов управления занимается метод @__BuildControlTree, в котором производится поочёредный вызов методов вида @__BuildControl<control_id>():
В нашем случае контрол всего один — это lblHello. Помимо создания объекта для него, в объектное дерево страницы добавляются текстовые фрагменты, обрамляющие наш контрол. В итоге, каждая строчка разметки оказывается добавленной в объектное дерево страницы, либо как LiteralControl, либо как типизированный серверный элемент управления.
- [System.Diagnostics.DebuggerNonUserCodeAttribute()]
- private void @__BuildControlTree(somepage_aspx @__ctrl) {
- this.InitializeCulture();
- System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
- @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(@"<html>
- <head>
- <title>SomePage</title>
- </head>
- <body>"));
- global::System.Web.UI.WebControls.Label @__ctrl2;
- @__ctrl2 = this. @__BuildControllblHello();
- @__parser.AddParsedSubObject(@__ctrl2);
- @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("</body></html>"));
- }
Итого
Полученный класс реализует интерфейс IHttpHandler, и после отработки фабрики страниц созданный ею экземпляр этого класса обрабатывает запрос вида http://url/SomePage.aspx — после чего начинается всем хорошо известный жизненный цикл ASP.NET.
В дальнейшем, вероятно, напишу подробнее про провайдеры, упоминавшиеся здесь: компиляции и виртуального пути.
UPD: Перенёс в «.NET»