附录:修复它示例应用程序 (使用 Azure 生成 Real-World Cloud Apps)

作者 :Rick Anderson,TomDykstra

下载修复项目

使用 Azure 构建真实世界云应用 电子书基于 Scott Guthrie 开发的演示文稿。 其中介绍了 13 种模式和做法,可帮助你成功开发适用于云的 Web 应用。 有关电子书的信息,请参阅 第一章

使用 Azure 构建真实世界云应用电子书的附录包含以下部分,其中提供了有关可以下载的 Fix It 示例应用程序的其他信息:

已知问题

修复它应用最初开发是为了尽可能简单地说明本电子书中介绍的某些模式。 但是,由于该电子书是关于构建实际应用,因此我们对修复它代码进行了审查和测试过程,过程类似于我们对已发布软件所做的操作。 我们发现了许多问题,与任何实际应用程序一样,我们修复了其中一些问题,其中一些问题已推迟到更高版本。

以下列表包括应在生产应用程序中解决的问题,但出于一个或另一个原因,我们决定不在初始版本的 Fix It 示例应用程序中解决。

安全性

  • 确保无法将任务分配给不存在的所有者。
  • 确保只能查看和修改已创建或分配给你的任务。
  • 将 HTTPS 用于登录页和身份验证 Cookie。
  • 指定身份验证 Cookie 的时间限制。

输入验证

通常,生产应用会执行比修复它应用更多的输入验证。 例如,应限制允许上传的图像大小/图像文件大小。

管理员功能

管理员应能够更改现有任务的所有权。 例如,任务的创建者可能会离开公司,除非启用了管理访问权限,否则没有人有权维护该任务。

队列消息处理

Fix It 应用中的队列消息处理设计为简单,以便用最少的代码量演示以队列为中心的工作模式。 对于实际的生产应用程序,此简单代码是不够的。

  • 该代码不保证每个队列消息最多处理一次。 从队列中获取消息时,有一个超时期限,在此期间,该消息对其他队列侦听器不可见。 如果超时在删除消息之前过期,该消息将再次变为可见。 因此,如果辅助角色实例花费很长时间处理消息,理论上同一消息可能会得到两次处理,从而导致数据库中出现重复任务。 有关此问题的详细信息,请参阅 使用 Azure 存储队列
  • 通过批处理消息检索,队列轮询逻辑可能更具成本效益。 每次调用 CloudQueue.GetMessageAsync 时,都会有事务成本。 相反,可以调用 CloudQueue.GetMessagesAsync (记下复数复数 ) ,该复数函数在单个事务中获取多条消息。 Azure 存储队列的事务成本非常低,因此在大多数情况下,对成本的影响并不大。
  • 队列消息处理代码中的紧密循环会导致 CPU 相关性,这不会有效地利用多核 VM。 更好的设计将使用任务并行度并行运行多个异步任务。
  • 队列消息处理仅具有基本的异常处理。 例如,代码不处理 有害消息。 (当消息处理导致异常时,必须记录错误并删除消息,否则辅助角色将尝试再次处理它,循环将无限期地继续。)

SQL 查询是无限的

当前修复它的代码对索引页的查询可能返回的行数没有限制。 如果将大量任务输入到数据库中,则接收的结果列表的大小可能会导致性能问题。 解决方案是实现分页。 有关示例,请参阅在 ASP.NET MVC 应用程序中使用实体框架进行排序、筛选和分页

Fix It 应用使用 FixItTask 实体类在控制器和视图之间传递信息。 最佳做法是使用视图模型。 域模型 (例如,FixItTask 实体类) 围绕数据持久性所需的内容进行设计,而视图模型可以设计用于数据呈现。

“修复它”应用将上传的图像存储为公共图像,这意味着找到该 URL 的任何人都可以访问图像。 可以保护映像,而不是公共映像。

没有用于队列的 PowerShell 自动化脚本

示例 PowerShell 自动化脚本仅为完全在 Azure 应用服务 Web 应用 中运行的 Fix It 的基本版本编写。 我们尚未提供用于设置和部署到 Web 应用以及队列处理所需的云服务环境的脚本。

用户输入中 HTML 代码的特殊处理

ASP.NET 自动防止恶意用户通过在用户输入文本框中输入脚本来尝试跨站点脚本攻击。 用于显示任务标题和备注的 MVC DisplayFor 帮助程序会自动对发送到浏览器的值进行 HTML 编码。 但在生产应用中,你可能想要采取其他措施。 有关详细信息,请参阅 ASP.NET 中的请求验证

最佳做法

以下是在代码评审和测试中发现修复应用的原始版本后修复的一些问题。 有些是由于原始编码者没有意识到特定的最佳做法,有些仅仅是因为代码编写得很快,不适合用于发布的软件。 我们在此处列出问题,以防我们从此审查和测试中学到的东西可能对开发 Web 应用的其他人员有所帮助。

释放数据库存储库

FixItTaskRepository 必须释放实体框架 DbContext 实例。 为此,我们在 类中FixItTaskRepository实现IDisposable

public class FixItTaskRepository : IFixItTaskRepository, IDisposable
{
    private MyFixItContext db = new MyFixItContext();

    // other code not shown

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Free managed resources.
            if (db != null)
            {
                db.Dispose();
                db = null;
            }
        }
    }
}

请注意,AutoFac 将自动释放 FixItTaskRepository 实例,因此我们不需要显式释放它。

另一个选项是从 中删除DbContext成员变量FixItTaskRepository,并改为在 语句中的每个存储库方法中创建一个using局部DbContext变量。 例如:

// Alternate way to dispose the DbContext
using (var db = new MyFixItContext())
{
    fixItTask = await db.FixItTasks.FindAsync(id);
}

向 DI 注册此类单一实例

由于只需要 类和 Logger 类的PhotoService一个实例,因此应在 DependenciesConfig.cs将这些类注册为依赖项注入的单个实例

builder.RegisterType<Logger>().As<ILogger>().SingleInstance();
builder.RegisterType<FixItTaskRepository>().As<IFixItTaskRepository>();
builder.RegisterType<PhotoService>().As<IPhotoService>().SingleInstance();

安全性:不向用户显示错误详细信息

原始的“修复它”应用没有通用错误页,只是让所有异常浮升到 UI 中,因此某些异常(如数据库连接错误)可能会导致向浏览器显示完整的堆栈跟踪。 详细的错误信息有时会促进恶意用户的攻击。 解决方法是记录异常详细信息,并向用户显示不包含错误详细信息的错误页。 Fix It 应用已记录,为了显示错误页,我们在 Web.config 文件中添加 <customErrors mode=On> 了 。

<system.web>
  <customErrors mode="On"/>
  <authentication mode="None" />
  <compilation debug="true" targetFramework="4.5" />
  <httpRuntime targetFramework="4.5" />
</system.web>

默认情况下,这会导致 显示 Views\Shared\Error.cshtml 以查找错误。 可以自定义 Error.cshtml 或创建自己的错误页面视图并添加 defaultRedirect 属性。 还可以为特定错误指定不同的错误页。

安全性:仅允许任务创建者编辑任务

“仪表板索引”页仅显示登录用户创建的任务,但恶意用户可能会创建具有另一个用户任务的 ID 的 URL。 我们在 DashboardController.cs 中添加了代码,以便在这种情况下返回 404:

public async Task<ActionResult> Edit(int id)
{
    FixItTask fixittask = await fixItRepository.FindTaskByIdAsync(id);
    if (fixittask == null)
    {
        return HttpNotFound();
    }

    // Verify logged in user owns this FixIt task.
    if (User.Identity.Name != fixittask.Owner)
    {
       return HttpNotFound();
    }

    return View(fixittask);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int id, [Bind(Include = "CreatedBy,Owner,Title,Notes,PhotoUrl,IsDone")]FormCollection form)
{
    FixItTask fixittask = await fixItRepository.FindTaskByIdAsync(id);

    // Verify logged in user owns this FixIt task.
    if (User.Identity.Name != fixittask.Owner)
    {
       return HttpNotFound();
    }

    if (TryUpdateModel(fixittask, form))
    {
        await fixItRepository.UpdateAsync(fixittask);
        return RedirectToAction("Index");
    }

    return View(fixittask);
}

不要吞咽异常

原始的 Fix It 应用刚刚在记录 SQL 查询导致的异常后返回 null:

catch (Exception e)
{
    log.Error(e, "Error in FixItTaskRepository.FindTasksByOwnerAsync(userName={0})", userName);
    return null;
}

这将使用户看起来好像查询成功,但未返回任何行。 解决方法是在捕获和日志记录后重新引发异常:

catch (Exception e)
{
    log.Error(e, "Error in FixItTaskRepository.FindTasksByCreatorAsync(creater={0})", creator);
    throw;
}

捕获辅助角色中的所有异常

辅助角色中任何未经处理的异常都会导致 VM 被回收,因此你需要将所有操作包装在 try-catch 块中并处理所有异常。

指定实体类中字符串属性的长度

为了显示简单的代码,修复它应用的原始版本没有指定 FixItTask 实体字段的长度,因此它们被定义为 varchar (数据库中的最大) 。 因此,UI 将接受几乎任意数量的输入。 指定长度将设置限制,这些限制同时应用于网页中的用户输入和数据库中的列大小:

public class FixItTask
{
    public int FixItTaskId  { get; set; }
    [StringLength(80)]
    public string CreatedBy { get; set; }
    [Required]
    [StringLength(80)]
    public string Owner { get; set; }
    [Required]
    [StringLength(80)]
    public string Title { get; set; }
    [StringLength(1000)]
    public string Notes { get; set; }
    [StringLength(200)]
    public string PhotoUrl { get; set; }
    public bool IsDone      { get; set; }  
}

如果预期私有成员不会更改,则将私有成员标记为只读

例如,在 类中 DashboardController 创建了 的 FixItTaskRepository 实例,并且预期不会更改,因此我们将其定义为 只读

public class DashboardController : Controller
    {
        private readonly IFixItTaskRepository fixItRepository = null;

使用列表。Any () 而不是 list。Count () > 0

如果你只关心列表中的一个或多个项是否符合指定条件,请使用 Any 方法,因为它在找到符合条件的项后立即返回,而 Count 该方法始终必须循环访问每个项。 仪表板 Index.cshtml 文件最初具有以下代码:

@if (Model.Count() == 0) {
    <br />
    <div>You don't have anything currently assigned to you!!!</div>
}

将其更改为:

@if (!Model.Any()) {
    <br />
    <div>You don't have anything currently assigned to you!!!</div>
}

使用 MVC 帮助程序在 MVC 视图中生成 URL

对于主页上的 “创建修复 它”按钮,“修复它”应用对定位点元素进行了硬编码:

<a href="/Tasks/Create" class="btn btn-primary btn-large">Create a New FixIt &raquo;</a>

对于如下所示的视图/操作链接,最好使用 Url.Action HTML 帮助程序,例如:

@Url.Action("Create","Tasks")

在辅助角色中使用 Task.Delay 而不是 Thread.Sleep

new-project 模板将辅助角色的示例代码放入 Thread.Sleep ,但导致线程进入睡眠状态可能会导致线程池生成其他不必要的线程。 可以改用 Task.Delay 来避免这种情况。

while (true)
{
    try
    {
        await queueManager.ProcessMessagesAsync();
    }
    catch (Exception ex)
    {
        logger.Error(ex, "Exception in worker role Run loop.");
    }
    await Task.Delay(1000);
}

避免异步 void

如果异步方法不需要返回值,则返回 Task 类型而不是 void

此示例来自 FixItQueueManager 类:

// Correct
public async Task SendMessageAsync(FixItTask fixIt) { ... }

// Incorrect
public async void SendMessageAsync(FixItTask fixIt) { ... }

应仅将 用于 async void 顶级事件处理程序。 如果将方法 async void定义为 ,则调用方无法 等待 该方法或捕获该方法引发的任何异常。 有关详细信息,请参阅 异步编程中的最佳做法

使用取消令牌中断辅助角色循环

通常,辅助角色上的 Run 方法包含无限循环。 辅助角色停止时,将调用 RoleEntryPoint.OnStop 方法。 应使用此方法取消 Run 方法中正在完成的工作,并正常退出。 否则,进程可能会在操作过程中终止。

选择退出自动 MIME 探查过程

在某些情况下,Internet Explorer 报告的 MIME 类型不同于 Web 服务器指定的类型。 例如,如果 Internet Explorer 在通过 HTTP 响应标头 Content-Type: text/plain 传递的文件中找到 HTML 内容,则 Internet Explorer 将确定内容应呈现为 HTML。 遗憾的是,这种“MIME 探查”也可能导致托管不受信任内容的服务器的安全问题。 为了解决此问题,Internet Explorer 8 对 MIME 类型确定代码进行了多次更改,并允许应用程序开发人员 选择退出 MIME 探查。 以下代码已添加到 Web.config 文件中。

<system.webServer>
     <httpProtocol>
        <customHeaders>
           <add name="X-Content-Type-Options" value="nosniff"/>
        </customHeaders>
     </httpProtocol>
     <modules>
      <remove name="FormsAuthenticationModule" />
    </modules>
  </system.webServer>

启用捆绑和缩小

Visual Studio 创建新的 Web 项目时,默认情况下不会启用 JavaScript 文件的捆绑和缩小。 我们在 BundleConfig.cs 中添加了一行代码:

// For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862
public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                "~/Scripts/jquery-{version}.js"));
 
   // Code removed for brevity/
 
   BundleTable.EnableOptimizations = true;
}

为身份验证 Cookie 设置过期超时

默认情况下,身份验证 Cookie 会在两周内过期。 时间越短越安全。 可以在 StartupAuth.cs 中更改此设置:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    ExpireTimeSpan = System.TimeSpan.FromMinutes(20)
});

如何在本地计算机上从 Visual Studio 运行应用

有两种方法可以运行 Fix It 应用:

  • 运行将新任务直接写入 SQL 数据库的基本应用程序。
  • 使用队列和后端服务运行应用程序以创建任务。 队列模式在 以队列为中心的工作模式一章中介绍。

运行基本应用程序

  1. 安装 Visual Studio 2017
  2. 安装 用于 .NET 的 Azure SDK for Visual Studio
  3. MSDN 代码库下载 .zip 文件。
  4. 在文件资源管理器中,右键单击 .zip 文件并单击“属性”,然后在属性窗口单击“取消阻止”。
  5. 解压缩文件。
  6. 双击 .sln 文件以启动 Visual Studio。
  7. “工具 ”菜单中,依次单击“ NuGet 包管理器”和 “包管理器控制台”。
  8. 在包管理器控制台 (PMC) 中,单击“还原”。
  9. 退出 Visual Studio。
  10. 启动 Azure 存储模拟器
  11. 重启 Visual Studio,打开在上一步中关闭的解决方案文件。
  12. 确保将 FixIt 项目设置为启动项目,然后按 Ctrl+F5 运行该项目。

使用队列处理运行应用程序

  1. 按照 运行基本应用程序的说明操作,然后关闭浏览器并关闭 Visual Studio。

  2. 使用管理员权限启动 Visual Studio。 (你将使用 Azure 计算模拟器,这需要管理员特权。)

  3. MyFixIt 项目 (Web 项目 ) 的应用程序Web.config文件中,将 的值appSettings/UseQueues更改为“true”:

    <appSettings>
        <!-- Other settings not shown -->
        <add key="UseQueues" value="true"/>
    </appSettings>
    
  4. 如果 Azure 存储模拟器 尚未运行,请再次启动它。

  5. 同时运行 FixIt Web 项目和 MyFixItCloudService 项目。

    使用 Visual Studio:

    1. F5 运行 FixIt 项目。
    2. 解决方案资源管理器中,右键单击 MyFixItCloudService 项目,然后单击“调试>启动新实例”。

    使用 Visual Studio 2013 Express for Web:

    1. 在“解决方案资源管理器”中,右键单击“FixIt”解决方案,然后选择“属性”。

    2. 选择 “多个启动项目”。

    3. 在“MyFixIt”和“MyFixItCloudService ”下的“操作 ”下拉列表中,选择“ 启动”。

    4. 单击 “确定”

    5. F5 运行这两个项目。

      运行 MyFixItCloudService 项目时,Visual Studio 会启动 Azure 计算模拟器。 根据防火墙配置,可能需要允许模拟器通过防火墙。

如何使用Windows PowerShell脚本将基本应用部署到Azure 应用服务 Web 应用

为了说明 “自动执行所有内容” 模式,向 Fix It 应用提供了脚本,这些脚本在 Azure 中设置环境并将项目部署到新环境。 以下说明说明了如何使用脚本。

如果要在 Azure 中运行而不使用队列,并且进行了更改以使用队列在本地运行,请确保先将 UseQueues appSetting 值设置回 false,然后再继续执行以下说明。

这些说明假定你已下载并在本地运行 Fix It 解决方案,并且你拥有一个 Azure 帐户或有权管理的 Azure 订阅。

  1. 安装 Azure PowerShell 控制台。 有关说明,请参阅如何安装和配置 Azure PowerShell

    此自定义控制台配置为使用 Azure 订阅。 Azure 模块安装在 Program Files 目录中,并在每次使用 Azure PowerShell 控制台时自动导入。

    如果希望使用不同的主机程序(例如Windows PowerShell ISE),请务必使用 Import-Module cmdlet 导入 Azure 模块,或使用 Azure 模块中的命令触发模块的自动导入。

  2. 使用“以管理员身份运行”选项启动Azure PowerShell。

  3. 运行 Set-ExecutionPolicy cmdlet,将Azure PowerShell执行策略设置为 RemoteSigned。 ) 输入 “Y (”为“是”以完成策略更改。

    PS C:\> Set-ExecutionPolicy RemoteSigned
    

    此设置使你能够运行未进行数字签名的本地脚本。 (还可以将执行策略设置为 Unrestricted,这样以后就不需要取消阻止步骤,但出于安全原因,不建议这样做。)

  4. Add-AzureAccount运行 cmdlet,使用帐户的凭据设置 PowerShell。

    PS C:\> Add-AzureAccount
    

    这些凭据在一段时间后过期,必须重新运行 cmdlet Add-AzureAccount 。 由于正在编写此电子书,因此凭据过期前的时间限制为 12 小时。

  5. 如果有多个订阅,请使用 Select-AzureSubscription cmdlet 指定要在其中创建测试环境的订阅。

  6. 使用 Get-AzurePublishSettingsFileImport-AzurePublishSettingsFile cmdlet 为同一 Azure 订阅导入管理证书。 其中第一个 cmdlet 下载证书文件,并在第二个 cmdlet 中指定该文件的位置以导入该文件。 > [!重要提示]

    将下载的文件保存在安全的位置,或者在使用完成后将其删除,因为它包含可用于管理 Azure 服务的证书。

    PS C:\Users\username\Documents\Visual Studio 2013\Projects\MyFixIt\Automation> Get-AzurePublishSettingsFile
    PS C:\Users\username\Documents\Visual Studio 2013\Projects\MyFixIt\Automation> Import-AzurePublishSettingsFile "C:\Users
    \username\Downloads\Azure MSDN - Visual Studio Ultimate-12-14-2013-credentials.publishsettings"
    

    该证书用于 REST API 调用,该调用检测开发计算机的 IP 地址,以便在SQL 数据库服务器上设置防火墙规则。

  7. 运行 Set-Location cmdlet (别名为 cdchdirsl) 以导航到包含脚本的目录。 (它们位于“修复它”解决方案文件夹的 “自动化 ”文件夹中。) 如果任何目录名称包含空格,请将路径括在引号中。 例如,若要导航到目录, c:\Sample Apps\FixIt\Automation 可以输入以下命令:

    PS C:\> cd "c:\Sample Apps\MyFixIt\Automation"
    
  8. 若要允许Windows PowerShell运行这些脚本,请使用 Unblock-File cmdlet。 (脚本被阻止,因为它们是从 Internet 下载的。)

    警告

    安全性 - 在任何脚本或可执行文件上运行 Unblock-File 之前,请在记事本中打开该文件,检查命令,并验证它们是否不包含任何恶意代码。

    例如,以下命令 Unblock-File 在当前目录中的所有脚本上运行 cmdlet。

    PS C:\Sample Apps\FixIt\Automation> Unblock-File -Path .\*.ps1
    
  9. 若要为基本应用创建 Web 应用 (没有队列处理) 修复它应用,请运行环境创建脚本。

    必需的 Name 参数指定数据库的名称,还用于脚本创建的存储帐户。 该名称在 azurewebsites.net 域中必须全局唯一。 如果指定的名称不唯一(如 Fixit 或 Test (,甚至如示例中的 fixitdemo) ),则 New-AzureWebsite cmdlet 将失败,并显示报告冲突的内部错误。 该脚本将名称转换为所有小写,以符合 Web 应用、存储帐户和数据库的名称要求。

    必需的 SqlDatabasePassword 参数指定将为SQL 数据库创建的管理员帐户的密码。 请勿在密码 (& <> 中包含特殊 XML 字符;) 。 这是脚本编写方式的限制,而不是 Azure 的限制。

    例如,如果要创建名为“fixitdemo”的 Web 应用并使用SQL Server管理员密码“Passw0rd1”,则可以输入以下命令:

    PS C:\Sample Apps\FixIt\Automation> .\New-AzureWebsiteEnv.ps1 -Name 
    fixitdemo <required params here>
    

    该名称在 azurewebsites.net 域中必须唯一,并且密码必须满足密码复杂性SQL 数据库要求。 (示例 Passw0rd1 满足要求。)

    请注意,该命令以“.”开头。 为了帮助防止恶意执行脚本,Windows PowerShell要求在运行脚本时提供脚本文件的完全限定路径。 可以使用点来指示当前目录 (”。) 或提供完全限定的路径,例如:

    PS C:\Temp\FixIt\Automation> C:\Temp\FixIt\Automation\New-AzureWebsiteEnv.ps1 -Name fixitdemo -SqlDatabasePassword Pas$w0rd
    

    有关脚本的详细信息,请使用 Get-Help cmdlet。

    PS C:\Sample Apps\FixIt\Automation> Get-Help -Full .\New-AzureWebsiteEnv.ps1
    

    可以使用 DetailedGet-Help cmdlet 的 、 FullParametersExamples 参数筛选返回的帮助。

    如果脚本失败或生成错误,例如“New-AzureWebsite:先调用 Set-AzureSubscription 并 Select-AzureSubscription”,则可能尚未完成Azure PowerShell的配置。

    脚本完成后,可以使用 Azure 管理门户查看已创建的资源,如 自动化所有内容 一章所示。

  10. 若要将 FixIt 项目部署到新的 Azure 环境,请使用 AzureWebsite.ps1 脚本。 例如:

    PS C:\Sample Apps\FixIt\Automation> .\Publish-AzureWebsite.ps1 ..\MyFixIt\MyFixIt.csproj -Launch
    

    部署完成后,浏览器将打开,并在 Azure 中运行修复它。

排查Windows PowerShell脚本问题

运行这些脚本时遇到的最常见错误与权限相关。 确保 Add-AzureAccountImport-AzurePublishSettingsFile 成功,并且已将其用于同一 Azure 订阅。 即使 Add-AzureAccount 成功,也可能需要再次运行它。 添加 Add-AzureAccount 的权限将在 12 小时后过期。

对象引用未设置为某个对象的实例。

如果脚本返回错误,例如“对象引用未设置为对象的实例”,这意味着Windows PowerShell找不到要处理的对象 (这是) 的空引用异常,请运行 Add-AzureAccount cmdlet 并重试脚本。

New-AzureSqlDatabaseServer : Object reference not set to an instance of an object.
At C:\ps-test\azure-powershell-samples-master\WebSite\create-azure-sql.ps1:80 char:19
+ $databaseServer = New-AzureSqlDatabaseServer -AdministratorLogin $UserName -Admi ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [New-AzureSqlDatabaseServer], NullReferenceException
    + FullyQualifiedErrorId : 
Microsoft.WindowsAzure.Commands.SqlDatabase.Server.Cmdlet.NewAzureSqlDatabaseServer

InternalError:服务器遇到内部错误。

New-AzureWebsite当名称在 azurewebsites.net 域中不唯一时,cmdlet 将返回内部错误。 若要解决此错误,请对名称使用不同的值,该值位于 New-AzureWebsiteEnv.ps1的 Name 参数中。

New-AzureWebsite : InternalError: The server encountered an internal error. 
Please retry the request.
At line:1 char:1
+ New-AzureWebsite -Name fixitdemo
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          
: CloseError: (:) [New-AzureWebsite], Exception
+ FullyQualifiedErrorId : 
Microsoft.WindowsAzure.Commands.Websites.NewAzureWebsiteCommand

重启脚本

如果需要重启 New-AzureWebsiteEnv.ps1 脚本,因为它在打印“脚本已完成”消息之前失败,可能需要删除脚本在停止之前创建的资源。 例如,如果脚本已创建 ContosoFixItDemo Web 应用,并且你再次使用相同的名称运行该脚本,则脚本将失败,因为该名称正在使用中。

若要确定脚本在停止之前创建的资源,请使用以下 cmdlet:

  • Get-AzureWebsite
  • Get-AzureSqlDatabaseServer
  • Get-AzureSqlDatabase:若要运行此 cmdlet,请通过管道将数据库服务器名称传递给 Get-AzureSqlDatabaseGet-AzureSqlDatabaseServer | Get-AzureSqlDatabase.

若要删除这些资源,请使用以下命令。 请注意,如果删除数据库服务器,则会自动删除与服务器关联的数据库。

  • Get-AzureWebsite -Name <WebsiteName> | Remove-AzureWebsite
  • Get-AzureSqlDatabase -Name <DatabaseName> -ServerName <DatabaseServerName> | Remove-SqlAzureDatabase
  • Get-AzureSqlDatabaseServer | Remove-AzureSqlDatabaseServer

如何通过队列处理将应用部署到Azure 应用服务 Web 应用和 Azure 云服务

若要启用队列,请在 MyFixIt\Web.config 文件中进行以下更改。 在 下 appSettings,将 的值 UseQueues 更改为“true”:

<appSettings>
    <!-- Other settings not shown -->
    <add key="UseQueues" value="true"/>
</appSettings>

然后,将 MVC 应用程序部署到 Azure 应用服务 中的 Web 应用,如所述。

接下来,创建新的 Azure 云服务。 Fix It 应用附带的脚本不会创建或部署云服务,因此必须使用Azure 门户。 在门户中,单击“ 新建 -- 计算 - 云服务 -- 快速创建”,然后输入 URL 和数据中心位置。 使用部署 Web 应用的同一数据中心。

显示用于创建新的 Azure 云服务项目的 Azure 云服务门户和多个选项卡及其可用选项的示意图

在部署云服务之前,需要更新一些配置文件。

在 MyFixIt.WorkerRole\app.config 的 下connectionStrings,将 连接字符串 的值appdb替换为SQL 数据库的实际连接字符串。 可以从门户获取连接字符串。 在门户中,单击“SQL 数据库 - appdb - 视图”SQL 数据库 ADO .Net、ODBC、PHP 和 JDBC 的连接字符串。 复制 ADO.NET 连接字符串并将该值粘贴到 app.config 文件中。 将“{your_password_here}”替换为数据库密码。 (假设使用脚本部署 MVC 应用,请在 script 参数中 SqlDatabasePassword 指定数据库密码。)

结果应如下所示:

<add name="appdb" connectionString="Server=tcp:####.database.windows.net,1433;Database=appdb;User ID=####;Password=####;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" providerName="System.Data.SqlClient" />

在同一 MyFixIt.WorkerRole\app.config 文件中,替换 appSettingsAzure 存储帐户的两个占位符值。

<appSettings>
  <add key="StorageAccountName" value="{StorageAccountName}" />
  <add key="StorageAccountAccessKey" value="{StorageAccountAccessKey}" />
</appSettings>

可以从门户获取访问密钥。 请参阅 如何管理存储帐户

在 MyFixItCloudService\ServiceConfiguration.Cloud.cscfg 中,替换 Azure 存储帐户的两个占位符值。

<ConfigurationSettings>
    <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 
             value="DefaultEndpointsProtocol=https;AccountName={StorageAccountName};AccountKey={StorageAccountAccessKey}" />
  </ConfigurationSettings>

现在,你已准备好部署云服务。 在“解决方案浏览”中,右键单击“MyFixItCloudService”项目,然后选择“ 发布”。 有关详细信息,请参阅本教程第 2 部分中的“将应用程序部署到 Azure”。