ASP.NET Whidbey 中新的代码编译功能

ASP.NET Whidbey 中新的代码编译功能

G. Andrew Duthie
Graymad Enterprises, Inc.

2003 年 10 月

摘要:了解如何利用 ASP.NET Whidbey 更轻松地使用代码。Code 目录会自动为您的站点编译代码,而预编译会使部署工作更容易。

下载本文的源代码。(请注意,在示例文件中,程序员的注释使用的是英文,本文中将其译为中文是为了便于读者理解。)

目录

简介
新的模块化代码模型
/Code 目录
利息计算器
预编译支持
在位预编译
部署预编译
IntelliSense 无处不在!
小结

简介

即将推出的新版 Microsoft® ASP.NET 介绍了大量新功能和改进功能,它的代号为 ASP.NET Whidbey,是根据新版 Microsoft® Visual Studio® .NET 的代号命名的。其中的某些功能利用了基础 Microsoft® .NET Framework 版本(ASP.NET Whidbey 就是基于该版本构建的)中的新功能。在这些功能当中,最有用的功能集之一与代码编译有关。

本文介绍 ASP.NET Whidbey 编译模型的主要更改、这些更改对编写 ASP.NET 应用程序的影响,以及如何利用这些更改。

改进的功能和新的编译功能可以分为以下四个基本方面:

  1. 对模块化代码模型的改进。
  2. 新的 Code 目录。
  3. 新增的对预编译 ASP.NET 应用程序的支持。
  4. Microsoft® IntelliSense® 增强功能。

新的模块化代码模型

默认情况下,使用 Visual Studio .NET 2002 或 2003 开发的站点使用一种称为“模块化代码”的功能将可视元素(HTML 标记、控件等)从与 UI 相关的编程逻辑中分离开来。当开发人员创建一个新 Web 窗体(例如 foo.aspx)时,Visual Studio 会自动创建一个相关的 Codebehind 类文件,该文件名称的前一部分与 Web 窗体相同,后面是 .vb 或 .cs(取决于项目使用的语言)。类文件将通过 @ Page 指令的 CodebehindInherits 属性与 Web 窗体相关联。

类文件包含事件处理代码(包括用于将事件处理程序绑定到相应事件的代码),以及每个控件(通过 Visual Studio Web 窗体编辑器添加到 .aspx 文件中)的分离声明。编译(生成)Web 应用程序项目后,其中的所有 Codebehind 类都将编译到一个 .NET 程序集中,该程序集将放置到 Web 应用程序的 /bin 目录中。Web 窗体页本身会在运行时动态进行编译,并且每个 Web 窗体均继承自与其相关的 Codebehind 类。有关 Visual Studio .NET 2003 和 ASP.NET 1.1 中的模块化代码模型的详细信息,请参阅 MSDN Library 文章 Web Forms Code Model(英文)。

虽然最初的模块化代码模型理论上不错(谁不希望将 UI 元素与编程逻辑相分离呢?),但它还是有一些缺点:

  • 需要重新生成。在 Visual Studio .NET 中,运行时不会自动编译 Codebehind 类,因此对 Codebehind 类的任何更改都需要重新生成整个项目以应用这些更改。(请注意,您可以通过 @ Page 指令的 src 属性指定对模块化代码文件进行动态编译,但默认情况下 Visual Studio .NET 不会执行此操作。)
  • 共享开发问题。由于项目中的所有 Codebehind 类都编译到了一个程序集中,所以很难让多个开发人员同时开发一个项目而不会遇到瓶颈问题。
  • 代码易被破坏。控件同时通过声明(在 .aspx 页面中)和编程(在 Codebehind 类中)的方式存在,如果这两组控件没有正确同步,很容易使代码遭到破坏。
  • 复杂程度增加,而且缺少单文件支持。在 Visual Studio .NET 中,很多用于提高生产率的功能(包括 IntelliSense 语句完成)都需要使用模块化代码。遗憾的是,这些功能通常会在 Codebehind 类中添加大量相对复杂的代码,这就产生了代码易被破坏的问题,因为更改 Visual Studio .NET 插入的代码很容易破坏页面。

了解到这些缺点后,负责开发 ASP.NET 和 Visual Studio .NET Whidbey 的小组决定重新考虑模块化代码模型。新的模块化代码模型利用了 Microsoft® Visual Basic® .NET 和 C# 中称为局部类(在 C# 中称为局部类型)的新功能。局部类使您能够在多个文件中定义一个类的不同部分。编译时,由编译器将这些部分再组合到一起。ASP.NET Whidbey 使用 @ Page 指令中新的 CompileWithClassname 属性来标识要与 .aspx 页面结合的 Codebehind 局部类。通过利用局部类,再进行一些其他更改,ASP.NET 小组可以实现以下目的:

  • 无需在 Codebehind 类中编写控件声明和事件绑定代码(在控件声明中通过声明的方式绑定事件)。
  • 允许运行时同时对 Web 窗体页和 Codebehind 类进行动态编译,无需再为细微的更改而重新生成整个项目。
  • 减少共享开发中的文件争用现象。
  • 对于使用模块化代码文件的开发人员以及喜欢单文件开发(所有代码和标记均包含在 .aspx 文件中)的开发人员,均可获得相同的 IDE 体验。

下面给出了模块化代码模型更改前后的不同视图。以下代码只是在使用模块化代码添加新的 Web 窗体(在 Visual Studio .NET Whidbey 中称之为具有代码分隔的 Web 窗体)时,由 Visual Studio 创建的默认代码:

Visual Studio .NET 2002/2003

WebForm1.aspx:

<%@ Page Language="vb" AutoEventWireup="false"
  Codebehind="WebForm1.aspx.vb" Inherits="TestWebApp_121602.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <title>WebForm1</title>
    <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">
    <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1">
    <meta name=vs_defaultClientScript content="JavaScript">
    <meta name=vs_targetSchema
      content="http://schemas.microsoft.com/intellisense/ie5">
  </head>
  <body MS_POSITIONING="GridLayout">    <form id="Form1" method="post" runat="server">
   </form>
  </body>
</html>

WebForm1.aspx.vb:

Public Class WebForm1
    Inherits System.Web.UI.Page

#Region " Web 窗体设计器生成的代码 "

    '此调用是 Web 窗体设计器所必需的。
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()

    End Sub

    '注意:以下占位符声明是 
    'Web 窗体设计器所必需的。
    '请勿删除或改变其位置。
    Private designerPlaceholderDeclaration As System.Object

    Private Sub Page_Init(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Init
        'CODEGEN:此方法调用是 Web 窗体设计器所必需的。
        '请勿使用代码编辑器修改它。
        InitializeComponent()
    End Sub

#End Region

    Private Sub Page_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
        '此处放置用于初始化该页面的用户代码
    End Sub

End Class

Visual Studio .NET Whidbey

Default.aspx:

<%@ page language="VB" compilewith="Default.aspx.vb"
  classname="ASP.Default_aspx" %>

<html>
<head runat="server">
    <title>无标题页</title>
</head>
<body>
    <form runat="server">

    </form>
</body>
</html>

Default.aspx.vb:

Imports Microsoft.VisualBasic

Namespace ASP

Expands Class Default_aspx

End Class

End Namespace

从上面的示例可以清楚地看到,由 Visual Studio .NET Whidbey 生成的代码更加清晰、易读。无需牺牲拖放功能或 IntelliSense 来实现此目的。

/Code 目录

ASP.NET Whidbey 中的另一个非常酷且实用的新功能是增加了 /Code 目录。/Code 目录与 /bin 目录类似,是 ASP.NET 使用的一个特殊目录,但它与 /bin 目录又有所不同:/bin 目录用于存储由应用程序使用的预编译程序集,而 /Code 目录用于存储要在运行时进行动态编译的类文件。这使您可以将业务逻辑组件、数据访问组件以及其他组件的类存储在应用程序中的某个位置,并从任意页面使用这些类。因为这些类是在运行时动态编译的,而且会被包含 /Code 目录的应用程序自动引用,所以在部署项目之前不需要生成项目,也不需要明确添加类引用。您可以放心地对组件进行更改,然后使用简单的 XCOPY 或拖放操作进行部署。除了简化组件的部署和引用外,/Code 目录还极大地简化了本地化过程中使用的资源文件 (.resx) 的创建和访问过程,以及为 WSDL 文件 (.wsdl) 自动生成和编译代理类的过程。

为了更好地说明上述操作的实现过程,让我们先来看几个示例。在第一个示例中,我们将看到如何创建一个简单的业务组件,以及如何从 Web 窗体页对其进行访问。

利息计算器

首先,我们打开 Visual Studio .NET Whidbey,创建一个名为 Compilation 的新 Web 站点。创建 Web 站点之后,IDE 应与图 1 类似。

图 1:Visual Studio .NET Whidbey Web 站点

然后,我们将 /Code 文件夹添加到该 Web 站点,添加方法为右击该项目,然后选择 New Folder(新建文件夹)。此文件夹必须命名为 Code,但名称不区分大小写。添加文件夹后,我们可以添加一个新的类文件:右击 /Code 文件夹,单击 Add New Item...(添加新项...),然后在 Add New Item(添加新项)对话框的 Templates(模板)窗格中选择 Class(类)项。将类命名为 CalculateInterest.vb。然后添加用于计算利息的代码(将其添加到 ClassEnd Class 语句之间):

Public Function CalcBalance(ByVal Prncpl As Integer, _
                        ByVal Rate As Double, _
                        ByVal Years As Integer, _
                        ByVal Period As Integer) As String
    Dim BaseNum As Double = (1 + Rate / Period)
    CalcBalance = _
        Format(Prncpl * System.Math.Pow(BaseNum, _
        (Years * Period)), "#,###,##0.00").ToString
End Function

创建组件类后,我们需要修改 Default.aspx 页面以提供用来输入数据的字段,还需要调用组件的 CalcBalance 方法。为了简单起见,Default.aspx 的完整列表显示如下(请注意,Default.aspx 使用单文件代码模型)。

Default.aspx:

<%@ page language="VB" %>

<script runat="server">    
    Sub Button1_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs)
        Dim Calc As New CalculateInterest
        Label6.Text = "$" & _
            Calc.CalcBalance(Convert.ToInt32(TextBox1.Text), _
                (Convert.ToInt32(TextBox2.Text) / 100), _
                Convert.ToInt32(TextBox3.Text), _
                Convert.ToInt16(Dropdownlist1.SelectedValue))
        Label6.Visible = True
    End Sub
</script>

<html>
<head runat="server">
    <title> 利息计算器</title>
</head>
<body>
    <form runat="server">
        <asp:label id="Label1" 
            runat="server">P本金 ($):</asp:label>
        <asp:textbox id="TextBox1" runat="server">
        </asp:textbox>
        <br />
        <asp:label id="Label2" 
            runat="server">利率 (%):</asp:label>
        <asp:textbox id="TextBox2" runat="server">
        </asp:textbox>
        <br />
        <asp:label id="Label3" runat="server">年数:</asp:label>
        <asp:textbox id="TextBox3" runat="server">
        </asp:textbox>
        <br />
        <asp:label id="Label4" 
            runat="server">复利频率:</asp:label>
        <asp:dropdownlist id="Dropdownlist1" runat="server">
            <asp:ListItem Value="1">每年</asp:ListItem>
            <asp:ListItem Value="4">每季</asp:ListItem>
            <asp:ListItem Value="12">每月</asp:ListItem>
            <asp:ListItem Value="365">每日</asp:ListItem>
        </asp:dropdownlist>
        <br />
        <asp:label id="Label5" 
            runat="server">结余: </asp:label>
        <asp:label id="Label6" 
            visible="false" runat="server"></asp:label>
        <br />
        <asp:button id="Button1" 
            runat="server" text="计算" οnclick="Button1_Click" />
    </form>
</body>
</html>

在设计视图中,修改后的 Default.aspx 应与图 2 类似。

图 2:设计视图中的 Default.aspx

需要注意是,当您在用于调用组件类的 <脚本> 块中键入代码时,将获得完整的 IntelliSense 语句完成(包括组件类),如图 3 所示。这是在 Visual Studio .NET 2003 基础上的一大改进,Visual Studio .NET 2003 不支持服务器端 <脚本> 块中的 IntelliSense。

图 3:源视图中的 IntelliSense

浏览 Default.aspx 会生成图 4 中所示的输出。填入本金、利率和年数,然后单击 Calculate(计算),输出结果应与图 5 类似。

图 4:Default.aspx 的初始输出

图 5:计算后的输出

资源文件

如果您以前在 Visual Studio .NET 2002 或 2003 中使用过 Web 应用程序,您一定已经注意到每次创建新 Web 窗体页时,除了 .aspx 页以及 .vb 或 .cs 模块化文件之外,Visual Studio 还会创建一个具有 .resx 扩展名的匹配文件(即 WebForm1.aspx.resx)。与大多数 Web 开发人员一样,您可能也会忽略或试图删除这些文件,因为它们的用途和/或用法并不是很直观。简言之,这些 .resx 文件称为“资源文件”,主要用于存储各个版本的资源,例如用于本地化的不同语言的文本字符串。

在 Visual Studio .NET 2002 和 2003 中,资源文件需要作为生成项目进程的一部分添加到项目程序集中,并且需要导入两个命名空间,创建一个 ResourceManager 对象,并调用其 GetString 方法以访问资源字符串。

在 /Code 目录的帮助下,Visual Studio .NET Whidbey 中的资源访问过程变得非常简单,如下面的示例所示。

我们先从创建资源文件开始,还是使用上一个示例中的项目。首先,右击刚才创建的 Compilation Web 站点,然后单击 Add New Item...(添加新项...)。在 Add New Item(添加新项)对话框中,选择 Assembly Resource File(程序集资源文件)模板,将资源文件命名为 strings.resx,然后单击 Open(打开)。strings.resx 文件的默认视图应与图 6 类似。

图 6:在 XML 编辑器中编辑资源文件

将以下项添加到数据表中(可以将“comment”(注释)、“type”(类型)和“mimetype”(MIME 类型)列保留为空):

名称
txtColorPrompt请选择一种颜色:
txtColorResponseRed您选择了红色!
txtColorResponseGreen您选择了绿色!
txtColorResponseBlue您选择了蓝色!

现在重复上述过程,添加一个名为 strings.en-GB.resx 的新资源文件,并将以下项添加到其数据表中,然后保存文件(因为我们没有添加 txtColorResponse* 的项,所以所有客户机都将使用 strings.resx 中的这些项的值):

名称
txtColorPrompt请选择一种颜色:

现在,为了充分利用 Code 目录的神奇作用,我们需要将这两个 .resx 文件从 Web 站点的根目录拖到 Code 目录中。完成上述操作后,将得到类似图 7 的结果。

图 7:Code 目录中的 .resx 文件

为了说明现在使用创建的资源文件是多么简单,我们将一个 Web 窗体添加到项目中,方法如下:右击 Web 站点节点,然后单击 Add New Item....(添加新项...)。在 Add New Item(添加新项)对话框中,选择 Web 窗体,将页面命名为 ColorPicker.aspx,然后单击 Open(打开)。修改此页面,使其与以下列表匹配。

ColorPicker.aspx:

<%@ page UICulture="en-GB" language="VB" %>

<script runat="server">

    
    Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Label1.Text = Resources.strings.txtColorPrompt
    End Sub
    
    Sub Submit_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs)
        Label1.ForeColor = _
            System.Drawing.Color.FromName(Dropdownlist1.SelectedValue)
        Select Case Dropdownlist1.SelectedValue
            Case "红色"
                Label1.Text = Resources.strings.txtColorResponseRed
            Case "绿色"
                Label1.Text = Resources.strings.txtColorResponseGreen
            Case "蓝色"
                Label1.Text = Resources.strings.txtColorResponseBlue
        End Select
        Dropdownlist1.Visible = False
        Submit.Visible = False
    End Sub
</script>

<html>
<head runat="server">
    <title>颜色选取器</title>
</head>
<body>
    <form runat="server">
        <asp:label id="Label1" runat="server">Label</asp:label>
        <asp:dropdownlist id="Dropdownlist1" runat="server">
            <asp:listitem value="Red">红色</asp:listitem>
            <asp:listitem value="Green">绿色</asp:listitem>
            <asp:listitem value="Blue">蓝色</asp:listitem>
        </asp:dropdownlist>
        <asp:button id="Submit" 
            text="提交" runat="Server" οnclick="Submit_Click" />
    </form>
</body>
</html>

当从浏览器中浏览 ColorPicker.aspx 时,默认输出与图 8 类似。如果从为英国用户设置的系统中浏览此页面(您可以通过将页面的 UICulture 属性设置为“en-GB”并保存页面来模拟此情形),输出将与图 9 类似(请注意,我们已经在“colour”中添加了 u)。

图 8:ColorPicker.aspx 的默认输出

图 9:英国系统的 ColorPicker.aspx 输出

请注意,在 ASP.NET Whidbey 中访问资源文件只需要一行代码。因为将资源文件放到 Code 目录中后,即可自动嵌入和引用该资源文件,所以不需要引用任何命名空间或程序集,也不需要为访问资源字符串创建对象。而且 ASP.NET 还可以确定应该使用哪个资源文件(基于用户浏览器的设置),因此我们不需要在运行时对此进行判断并作出相应的响应。ASP.NET 可以帮助我们完成这一切。

预编译支持

ASP.NET Web 窗体的优势之一就是增加动态编译后,您可以很轻松地更改 .aspx 页,保存更改时页面将动态更新,而不需要重新编译(只要不使用模块化代码)。但动态编译并不是对每个应用程序都适合,而且第一次访问某个应用程序时,动态编译会导致浏览器的初始性能降低。另外,很多时候您可能希望部署一个没有源代码的应用程序。

如果您遇到上述情况,您会更高兴地了解到 ASP.NET Whidbey 具有支持预编译 Web 站点的功能。ASP.NET Whidbey 支持两种预编译模式:在位预编译和部署预编译。

在位预编译

在位预编译使您可以对 Web 站点中的所有页面进行手动批编译。这也是用户在您的应用程序中首次单击某个页面后发生的操作(前文提到的后一种情况除外),用户只需坐下来等待批编译完成。

使用在位预编译有两个主要原因:首先,它可以避免第一次请求页面时批编译的性能降低;其次,它使您可以“先于”用户发现编译错误。

在位预编译也很容易实现,只需浏览到 Web 站点的根目录,添加特定的处理程序名称 precompile.axd(熟悉 ASP.NET 跟踪功能的用户会发现该名称与 trace.axd 处理程序的名称类似):

http://localhost/mywebsitename/precompile.axd

其中 mywebsitename 是您 Web 站点的名称。预编译站点之后,对站点内页面的请求也应随即完成,而不会有任何编译滞后。

部署预编译

第二种预编译模式使您可以创建整个 Web 站点的可执行版本,部署这种版本不需要任何源代码(包括 HTML 和其他静态文件)。因此,部署预编译可以防止别人随意访问由代码表示的知识产权信息。生成的程序集和 Stub 文件集可以通过 XCOPY、FTP、Windows 资源管理器等部署到生产服务器上。

为了预编译站点以进行部署,ASP.NET Whidbey 提供了一个名为 aspnet_compiler.exe 的命令行实用程序。要在文件系统 Web 站点上调用 ASP.NET 预编译器,需要打开一个命令窗口,浏览到 .NET Framework 的安装位置(<Windows>/Microsoft.NET/Framework/<版本>),然后输入以下命令:

aspnet_compiler /v /<websitename> -p <source> <destination>

其中 <websitename> 为 Web 站点的名称(即在浏览器中输入的名称),<source> 和 <destination> 为两个文件系统路径,分别指向要编译站点的位置以及编译后的版本应输出至的位置。以我们的 Web 站点为例,输入的命令如下所示(请注意下面是一条命令):

aspnet_compiler /v /Compilation 
-p c:/WebSites/Compilation c:/WebSites/Compilation_Compiled

如果要查看 ASP.NET 预编译器的所有可用选项,只需输入以下命令:

aspnet_compiler /?

请注意,某些命令行选项要求 Web 站点必须为有效的 Microsoft® Internet 信息服务 (IIS) 应用程序才能正常工作。

如果在 Microsoft® Windows 资源管理器中浏览至目标目录,您会看到预编译 Web 站点后将生成一个包含 bin 目录的站点,bin 目录中包含一些程序集和描述性文件,以及大量与原始页面同名的 Stub 文件,但不包含代码(包括 HTML 和可执行代码)。如果浏览此站点,输出与原始站点的输出相同。请注意,不能在已进行部署预编译的站点上使用在位预编译,原因很简单,因为它已经被预编译过了。

IntelliSense 无处不在!

对于 Visual Studio .NET 2002 和 2003,最令我头痛的问题之一(相信很多开发人员也有同感)就是对 IntelliSense 和其他用于提高生产率的功能的支持不一致。希望在 HTML 视图中将控件从工具箱中拖到页面上吗?还做不到。事实上,在 HTML 视图中根本看不到工具箱的 Web 窗体面板!要在 .aspx 页面上写入内嵌代码而不是使用模块化代码?可以做到,但必须放弃 IntelliSense、拖放功能以及其他更多功能。最后,正如我最近在 MSDN ASP.NET Developer Center 上发表的文章中提到的,在 Visual Studio .NET 2002 或 2003 中获得自定义控件的设计时支持需要跨越层层障碍,包括有点粗糙但奏效的 XSL 修改。

令人高兴的是,ASP.NET Whidbey 中实现了编译模型的统一,所有这些问题也都迎刃而解。在 Visual Studio .NET Whidbey 中,可以写入内嵌代码或使用新的模块化代码模型,还能获得控件拖放、IntelliSense 语句完成以及所有以前您希望使用却因编码方式局限而无法使用的那些可以提高生产率的功能。另外,对自定义服务器控件和 Web 控件的设计时支持有了很大的改进,包括为源视图(HTML 视图在 Visual Studio .NET Whidbey 中的等效视图)中的自定义控件增加了 IntelliSense 语句完成。

小结

ASP.NET Whidbey 中对编译模型的更改,以及 Visual Studio .NET Whidbey 中相应功能的改进无疑是一个巨大的飞跃,不仅为开发人员提供了所需的灵活性,还使他们可以充分利用 IDE 提供的可以提高生产率的功能。大大简化的模块化代码模型将使该功能更有用、更简捷,而新增的对内嵌代码的完全支持显然会受到那些希望所有代码都位于一个 .aspx 文件中的开发人员的欢迎。

相信 /Code 目录会大大提高生产率,对于那些从事发展迅速的中小型项目的开发人员,以及那些因为编译过程过于复杂而无法完成工作的开发人员来说尤其如此。它还为访问业务逻辑组件、资源文件、WSDL 文件以及其他资源提供了一种更为直接、简单的方法:通过自动编译、嵌入或创建这些资源的代理并自动引用它们,只需很少的代码即可访问这些资源。

预编译功能使开发人员可以轻松地提高其站点的初始性能,如果需要,还可以通过提供功能完备的 Web 应用程序(不包含源代码或 HTML)为重要的知识产权信息添加保护措施。

最后,集所有功能于一身的 Visual Studio .NET Whidbey 无疑会为开发人员带来非凡的体验,他们不仅能从内嵌代码模型和模块化代码模型中获得完全的 IntelliSense 支持,还能查看给定页面的所有视图,开发工作不会再因工具限制而局限于某一种样式。

作者简介

G. Andrew Duthie 是 Graymad Enterprises, Inc.(英文)的创始人和负责人,该公司提供 Microsoft Web 开发技术的培训和咨询服务。自从 Active Server Pages 问世以来,Andrew 一直从事多层 Web 应用程序的开发工作。他编写了大量有关 ASP.NET 的书籍,包括《Microsoft ASP.NET Programming with Microsoft Visual Basic》、《Microsoft ASP.NET Programming with Microsoft Visual C#》和《ASP.NET in a Nutshell》(第二版)。Andrew 经常在一些重大活动中发表演讲,这些活动包括“Software Development”、“Dev-Connections family of conferences”、“Microsoft Developer Days”以及“VSLive!”。他还作为 International .NET Association (INETA)(英文)Speaker's Bureau 成员在 .NET 用户组上发表了演讲。您可以从其公司的 Web 站点 http://www.graymad.com/(英文)上了解到有关 Andrew 的更多信息。



回到顶部

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值