Pull to refresh

Компиляция страниц ASP.NET: генерация кода

Reading time 10 min
Views 7.4K
Сейчас пришла пора разобраться с тем, что происходит с кодом от момента написания ASPX/ASCX- разметки и CS-кода до момента их физического выполнения в виде некоторого скомпилированного кода на сервере.

Генерация полного исходного кода


Начнём с простой, и, в принципе, известной особенности ASP.NET 2.0: частичной компиляции страниц. Как мы знаем, частичные (partial) классы — это синтаксический сахар для разнесения исходного кода класса в несколько разных файлов. Это особенно удобно, когда одна часть кода класса генерируется автоматически некоторой утилитой, а другая — пишется вручную программистом. Так как модифицировать автогенерённый код — очень плохая практика (ведь последующая перегенерация уничтожит все такие изменения), в таких случаях утилита генерирует классы как частичные, и программист может спокойно добавить нужный функционал «рядом», в соседнем файле, не затрагиваемом перегенерацией.
Именно так и поступает среда ASP.NET, когда сталкивается с парой ASPX (или ASCX,ASHX,ASAX) + CS. Пусть есть пара описаний — ASPX-страница с серверным кодом и элементом управления и её файл отделённого кода:

SomePage.aspx:

  1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SomePage.aspx.cs" Inherits="SomePage" %>
  2. <html>
  3. <head>
  4. <title>SomePage</title>
  5. </head>
  6. <body>
  7. <asp:Label ID="lblHello" runat="server" EnableViewState="false" />
  8.  
  9. <script runat="server" type="text/C#">
  10.  
  11. protected void Page_Load(object sender, EventArgs e)
  12. {
  13. lblHello.ForeColor = System.Drawing.Color.Red;
  14. }
  15.  
  16. </script>
  17. </body>
  18. </html>
  19.  


SomePage.aspx.cs:

  1. using System;
  2. using System.Web;
  3. using System.Web.UI;
  4. using System.Web.UI.WebControls;
  5.  
  6. public partial class SomePage : Page
  7. {
  8. protected void Page_PreRender(object sender, EventArgs e)
  9. {
  10. lblHello.Text = "Hello from code";
  11. }
  12. }
  13.  

Рассмотрим, как данная страница превращается в код.

Здесь ASP.NET генерирует целых два класса. Во-первых, генерируется partial class SomePage, в котором определёны защищённые члены для элементов управления (в нашем случае Label lblHello) и два свойства для доступа к пользовательским типизированным объектам Profile и ApplicationInstance (в данном примере их типы не переопределялись и совпадают со стандартными), а также представлен тот серверный код, что мы видели в теге <script runat=«server»>

App_Web_ogjttwiz.0.cs:

  1. public partial class SomePage : System.Web.SessionState.IRequiresSessionState {
  2.  
  3.  
  4.     protected global::System.Web.UI.WebControls.Label lblHello;
  5.  
  6.  
  7. protected void Page_Load(object sender, EventArgs e)
  8. {
  9. lblHello.ForeColor = System.Drawing.Color.Red;
  10. }
  11.  
  12.  
  13.     protected System.Web.Profile.DefaultProfile Profile {
  14.         get {
  15.             return ((System.Web.Profile.DefaultProfile)(this.Context.Profile));
  16.         }
  17.     }
  18.  
  19.     protected System.Web.HttpApplication ApplicationInstance {
  20.         get {
  21.             return ((System.Web.HttpApplication)(this.Context.ApplicationInstance));
  22.         }
  23.     }
  24. }
  25.  
После этого генерируется собственно класс страницы, который уже будет обрабатывать наш физический запрос. Замечу, что его ASP.NET помещает в тот же временный файл кода, в котором мы видели и предыдущий класс.

App_Web_ogjttwiz.0.cs (продолжение):

  1. namespace ASP {
  2.  
  3. ...
  4.     [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
  5.     public class somepage_aspx : global::SomePageSystem.Web.IHttpHandler {
  6.  
  7.         private static bool @__initialized;
  8.  
  9.         private static object @__fileDependencies;
  10.  
  11.         [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  12.         public somepage_aspx() {
  13.             string[] dependencies;
  14.  
  15.             (this).AppRelativeVirtualPath = "~/SomePage.aspx";
  16.  
  17.             if ((global::ASP.somepage_aspx.@__initialized == false)) {
  18.                 dependencies = new string[2];
  19.                 dependencies[0] = "~/SomePage.aspx";
  20.                 dependencies[1] = "~/SomePage.aspx.cs";
  21.                 global::ASP.somepage_aspx.@__fileDependencies = this.GetWrappedFileDependencies(dependencies);
  22.                 global::ASP.somepage_aspx.@__initialized = true;
  23.             }
  24.             this.Server.ScriptTimeout = 30000000;
  25.         }
  26.  
  27.         [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  28.         protected override void FrameworkInitialize() {
  29.             base.FrameworkInitialize();
  30.             this. @__BuildControlTree(this);
  31.             this.AddWrappedFileDependencies(global::ASP.somepage_aspx.@__fileDependencies);
  32.         }
  33.  
  34.         [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  35.         private void  @__BuildControlTree(somepage_aspx @__ctrl) {
  36.  
  37.             this.InitializeCulture();
  38.  
  39.             System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
  40.  
  41.             @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(@"<html>
  42. <head>
  43. <title>SomePage</title>
  44. </head>
  45. <body>"));
  46.  
  47.     global::System.Web.UI.WebControls.Label @__ctrl2;
  48.  
  49.             @__ctrl2 = this. @__BuildControllblHello();
  50.  
  51.             @__parser.AddParsedSubObject(@__ctrl2);
  52.  
  53.             @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("</body></html>"));
  54.         }
  55.  
  56. [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  57.         private global::System.Web.UI.WebControls.Label  @__BuildControllblHello() {
  58.             global::System.Web.UI.WebControls.Label @__ctrl;
  59.  
  60.             @__ctrl = new global::System.Web.UI.WebControls.Label();
  61.  
  62.             this.lblError = @__ctrl;
  63.             @__ctrl.ApplyStyleSheetSkin(this);
  64.  
  65.             @__ctrl.ID = "lblHello";
  66.  
  67.  
  68.             @__ctrl.EnableViewState = false;
  69.  
  70.             return @__ctrl;
  71.         }
  72. ...
  73.     }
  74. }
  75.  
Этот класс по умолчанию имеет имя somepage_aspx, но это можно изменить установкой атрибута ClassName у директивы Page, вот так:
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SomePage.aspx.cs" Inherits="SomePage" ClassName="MyPageClass" %>
Как видно здесь, для генерации первого из классов (public partial class SomePage) используется атрибут CodeFile и Inherits, причём они должны быть согласованы (то есть, в файле, указанном в CodeFile, должен находиться класс с именем, как в атрибуте Inherits). После этого создаётся второй класс с именем, взятом из атрибута ClassName (или по умолчанию — <Значение_Inherits_lowercase>_aspx)
Во втором классе (именно он является окончательным классом страницы) мы видим код, отвечающий за генерацию дерева элементов управления страницы, и код, отвечающий за инициализацию инфраструктуры компиляции и кэширования скомпилированных страниц в виде сборок .dll.
Разберем его по частям.
  1.        public somepage_aspx() {
  2.             string[] dependencies;
  3.  
  4.             (this).AppRelativeVirtualPath = "~/SomePage.aspx";
  5.  
  6.             if ((global::ASP.somepage_aspx.@__initialized == false)) {
  7.                 dependencies = new string[2];
  8.                 dependencies[0] = "~/SomePage.aspx";
  9.                 dependencies[1] = "~/SomePage.aspx.cs";
  10.                 global::ASP.somepage_aspx.@__fileDependencies = this.GetWrappedFileDependencies(dependencies);
  11.                 global::ASP.somepage_aspx.@__initialized = true;
  12.             }
  13.             this.Server.ScriptTimeout = 30000000;
  14.         }
  15.  
Конструктор somepage_aspx инициализирует текущий виртуальный путь для страницы, так как сама инфраструктура ASP.NET не отвечает за это (надо иметь в виду этот факт при разработке собственных пользовательских классов-наследников страницы, обходящий стандартный путь компиляции).
После этого заявляется, что скомпилированная версия данного класса (в виде dll) должна быть перекомпилирована при изменении следующих исходных файлов: ~/SomePage.aspx и ~/SomePage.aspx.cs. Данная информация будет ниже передана инфраструктуре (а именно — стандартному провайдеру пути), который, в свою очередь, будет предоставлять её провайдеру компиляции при необходимости.

Генерацией дерева элементов управления занимается метод @__BuildControlTree, в котором производится поочёредный вызов методов вида @__BuildControl<control_id>():
  1.         [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  2.         private void  @__BuildControlTree(somepage_aspx @__ctrl) {
  3.  
  4.             this.InitializeCulture();
  5.  
  6.             System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
  7.  
  8.             @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(@"<html>
  9. <head>
  10. <title>SomePage</title>
  11. </head>
  12. <body>"));
  13.  
  14.     global::System.Web.UI.WebControls.Label @__ctrl2;
  15.  
  16.             @__ctrl2 = this. @__BuildControllblHello();
  17.  
  18.             @__parser.AddParsedSubObject(@__ctrl2);
  19.  
  20.             @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("</body></html>"));
  21.         }
  22.  
В нашем случае контрол всего один — это lblHello. Помимо создания объекта для него, в объектное дерево страницы добавляются текстовые фрагменты, обрамляющие наш контрол. В итоге, каждая строчка разметки оказывается добавленной в объектное дерево страницы, либо как LiteralControl, либо как типизированный серверный элемент управления.

Итого


Полученный класс реализует интерфейс IHttpHandler, и после отработки фабрики страниц созданный ею экземпляр этого класса обрабатывает запрос вида http://url/SomePage.aspx — после чего начинается всем хорошо известный жизненный цикл ASP.NET.

В дальнейшем, вероятно, напишу подробнее про провайдеры, упоминавшиеся здесь: компиляции и виртуального пути.

UPD: Перенёс в «.NET»
Tags:
Hubs:
+4
Comments 4
Comments Comments 4

Articles