📜 ⬆️ ⬇️

Compiling ASP.NET pages: code generation

Now it's time to figure out what happens to the code from the time of writing the ASPX / ASCX markup and the CS code to the moment of their physical execution in the form of some compiled code on the server.

Generate full source code


Let's start with a simple, and, in principle, well-known features of ASP.NET 2.0: partial compilation of pages. As we know, partial (partial) classes are syntactic sugar for splitting class source code into several different files. This is especially useful when one part of the class code is automatically generated by some utility, and the other is written manually by the programmer. Since modifying the autogenerated code is a very bad practice (after all, the subsequent regeneration will destroy all such changes), in such cases the utility generates classes as partial, and the programmer can easily add the necessary functionality “side by side” in the adjacent file that is not affected by regeneration.
This is exactly what ASP.NET does when faced with a pair of ASPX (or ASCX, ASHX, ASAX) + CS. Let there be a couple of descriptions - an ASPX page with server code and a control and its separated code file:

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. < script runat = "server" type = "text / C #" >
  9. protected void Page_Load (object sender, EventArgs e)
  10. {
  11. lblHello.ForeColor = System.Drawing.Color.Red;
  12. }
  13. < / script >
  14. < / body >
  15. < / html >

')

SomePage.aspx.cs:

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

Consider how this page turns into code.

Here ASP.NET generates two whole classes. First, partial class SomePage is generated, in which protected members for controls are defined (in our case, Label lblHello) and two properties for accessing custom typed Profile and ApplicationInstance objects (in this example, their types have not been redefined and coincide with standard ones) as well as that server code is presented that we saw in the <script runat = "server"> tag

App_Web_ogjttwiz.0.cs:

  1. public partial class SomePage : System. Web . SessionState . IRequiresSessionState {
  2. protected global :: System. Web . Ui . WebControls . Label lblhello ;
  3. protected void Page_Load ( object sender, EventArgs e )
  4. {
  5. lblHello. ForeColor = System. Drawing . Color . Red ;
  6. }
  7. protected system. Web . Profile . DefaultProfile Profile {
  8. get {
  9. return ( ( System. Web . Profile . DefaultProfile ) ( this . Context . Profile ) ) ;
  10. }
  11. }
  12. protected system. Web . HttpApplication ApplicationInstance {
  13. get {
  14. return ( ( System. Web . HttpApplication ) ( this . Context . ApplicationInstance ) ) ;
  15. }
  16. }
  17. }
After that, the actual page class is generated, which will already process our physical request. I note that its ASP.NET places in the same temporary code file in which we also saw the previous class.

App_Web_ogjttwiz.0.cs (continued):

  1. namespace ASP {
  2. ...
  3. [ System . Runtime CompilerServices . CompilerGlobalScopeAttribute ( ) ]
  4. public class somepage_aspx : global :: SomePage , System. Web . IHttpHandler {
  5. private static bool @__initialized ;
  6. private static object @__fileDependencies ;
  7. [ System. Diagnostics . DebuggerNonUserCodeAttribute ( ) ]
  8. public somepage_aspx ( ) {
  9. string [ ] dependencies ;
  10. ( this ) . AppRelativeVirtualPath = "~ / SomePage.aspx" ;
  11. if ( ( global :: ASP . somepage_aspx . @ __ initialized == false ) ) {
  12. dependencies = new string [ 2 ] ;
  13. dependencies [ 0 ] = "~ / SomePage.aspx" ;
  14. dependencies [ 1 ] = "~ / SomePage.aspx.cs" ;
  15. global :: ASP . somepage_aspx . @ __ fileDependencies = this . GetWrappedFileDependencies ( dependencies ) ;
  16. global :: ASP . somepage_aspx . @ __ initialized = true ;
  17. }
  18. this . Server . ScriptTimeout = 30,000,000 ;
  19. }
  20. [ System. Diagnostics . DebuggerNonUserCodeAttribute ( ) ]
  21. protected override void FrameworkInitialize ( ) {
  22. base . FrameworkInitialize ( ) ;
  23. this . @__BuildControlTree ( this ) ;
  24. this . AddWrappedFileDependencies ( global :: ASP . Somepage_aspx . @ __ fileDependencies ) ;
  25. }
  26. [ System. Diagnostics . DebuggerNonUserCodeAttribute ( ) ]
  27. private void @__BuildControlTree ( somepage_aspx @__ctrl ) {
  28. this . InitializeCulture ( ) ;
  29. System. Web . Ui . IParserAccessor @__parser = ( ( System. Web . UI . IParserAccessor ) ( @__ctrl ) ) ;
  30. @__parser. AddParsedSubObject ( new System. Web . UI . LiteralControl ( @ "<html>
  31. <head>
  32. <title> SomePage </ title>
  33. </ head>
  34. <body> " ) ) ;
  35. global :: System. Web . Ui . WebControls . Label @__ ctrl2 ;
  36. @__ ctrl2 = this . @__BuildControllblHello ( ) ;
  37. @__parser. AddParsedSubObject ( @__ ctrl2 ) ;
  38. @__parser. AddParsedSubObject ( new System. Web . UI . LiteralControl ( "</ body> </ html>" ) ) ;
  39. }
  40. [ System. Diagnostics . DebuggerNonUserCodeAttribute ( ) ]
  41. private global :: System. Web . Ui . WebControls . Label @__BuildControllblHello ( ) {
  42. global :: System. Web . Ui . WebControls . Label @__ctrl ;
  43. @__ctrl = new global :: System. Web . Ui . WebControls . Label ( ) ;
  44. this . lblError = @__ctrl ;
  45. @__ctrl. ApplyStyleSheetSkin ( this ) ;
  46. @__ctrl. Id = "lblHello" ;
  47. @__ctrl. EnableViewState = false ;
  48. return @__ctrl ;
  49. }
  50. ...
  51. }
  52. }
This class is by default named somepage_aspx, but this can be changed by setting the ClassName attribute on the Page directive, like this:
  1. <% @ Page Language = "C #" AutoEventWireup = "true" CodeFile = "SomePage.aspx.cs" Inherits = "SomePage" ClassName = "MyPageClass" %>
As you can see here, to generate the first of the classes (public partial class SomePage), the CodeFile and Inherits attributes are used, and they must be matched (that is, the file specified in the CodeFile must contain a class with the name, as in the Inherits attribute). After that, a second class is created with the name taken from the ClassName attribute (or by default - <Inits_lowercase value> _aspx)
In the second class (it is the final class of the page), we see the code responsible for generating the page's control tree, and the code responsible for initializing the compilation and caching infrastructure of the compiled pages as assemblies .dll.
We analyze it in parts.
  1. public somepage_aspx ( ) {
  2. string [ ] dependencies ;
  3. ( this ) . AppRelativeVirtualPath = "~ / SomePage.aspx" ;
  4. if ( ( global :: ASP . somepage_aspx . @ __ initialized == false ) ) {
  5. dependencies = new string [ 2 ] ;
  6. dependencies [ 0 ] = "~ / SomePage.aspx" ;
  7. dependencies [ 1 ] = "~ / SomePage.aspx.cs" ;
  8. global :: ASP . somepage_aspx . @ __ fileDependencies = this . GetWrappedFileDependencies ( dependencies ) ;
  9. global :: ASP . somepage_aspx . @ __ initialized = true ;
  10. }
  11. this . Server . ScriptTimeout = 30,000,000 ;
  12. }
The constructor somepage_aspx initializes the current virtual path for the page, since the ASP.NET infrastructure itself is not responsible for this (you need to keep this fact in mind when developing your own custom classes that inherit the page, bypassing the standard compilation path).
After that, it is stated that the compiled version of this class (in the form of a dll) must be recompiled when the following source files are changed: ~ / SomePage.aspx and ~ / SomePage.aspx.cs. This information will be transmitted below to the infrastructure (namely, to the standard provider of the path), which, in turn, will provide it to the compilation provider if necessary.

The generation of the control tree is handled by the @__BuildControlTree method, in which a sequential invocation of methods of the type @__ BuildControl <control_id> () is made:
  1. [ System. Diagnostics . DebuggerNonUserCodeAttribute ( ) ]
  2. private void @__BuildControlTree ( somepage_aspx @__ctrl ) {
  3. this . InitializeCulture ( ) ;
  4. System. Web . Ui . IParserAccessor @__parser = ( ( System. Web . UI . IParserAccessor ) ( @__ctrl ) ) ;
  5. @__parser. AddParsedSubObject ( new System. Web . UI . LiteralControl ( @ "<html>
  6. <head>
  7. <title> SomePage </ title>
  8. </ head>
  9. <body> " ) ) ;
  10. global :: System. Web . Ui . WebControls . Label @__ ctrl2 ;
  11. @__ ctrl2 = this . @__BuildControllblHello ( ) ;
  12. @__parser. AddParsedSubObject ( @__ ctrl2 ) ;
  13. @__parser. AddParsedSubObject ( new System. Web . UI . LiteralControl ( "</ body> </ html>" ) ) ;
  14. }
In our case, there is only one control - this is lblHello. In addition to creating an object for it, text fragments are added to the page object tree, framing our control. As a result, each line of markup is added to the page object tree, either as LiteralControl, or as a typed server control.

Total


The resulting class implements the IHttpHandler interface, and after working through the page factory, the instance of this class it creates handles a request of the form http: //url/SomePage.aspx — after which the well-known ASP.NET life cycle begins.

In the future, I will probably write more about the providers mentioned here: compilations and virtual paths.

UPD: Transferred to ".NET"

Source: https://habr.com/ru/post/61074/


All Articles