如何使用适用于 Azure 移动应用的 ASP.NET Framework SDK

本主题说明如何在关键的 Azure 应用服务移动应用方案中使用 .NET 后端服务器 SDK。 借助 Azure 移动应用 SDK 可从 ASP.NET 应用程序使用移动客户端。

警告

本文介绍 v4.2.0 库版本的信息,该版本被 v5.0.0 库取代。 有关最新信息,请参阅最新版本的文章

创建 Azure 移动应用 ASP.NET Framework 后端

可使用 Visual Studio 2019 创建 ASP.NET Framework 应用。

  • 选择“ASP.NET Web 应用程序 (.NET Framework)”模板。 如果定位此模板时遇到问题,请选择“C#”、“所有平台”和“Web”
  • 选择应用程序的名称和位置后,选择“Web API”项目模板。 将为应用程序安装正确的基本服务集合。

下载并初始化 SDK

SDK 提供了入门使用 Azure 移动应用所需的基本功能,可在 NuGet.org 获取。 安装此包:

  1. 右键单击项目,然后选择“管理 NuGet 包...”
  2. 在“浏览”选项卡上,在搜索框中输入 Microsoft.Azure.Mobile.Server,然后按 Enter
  3. 选择 Microsoft.Azure.Mobile.Server.Quickstart 包。
  4. 单击“安装” 。
  5. 按照提示完成安装。

重复此步骤安装 Microsoft.Owin.Host.SystemWeb

注意

请勿更新用作依赖项的包,例如 Newtonsoft.JSONSystem.IdentityModel.Jwt。 在许多情况下,这些包的 API 已更改,现在与适用于 ASP.NET Framework 的 Azure 移动应用不兼容。

初始化服务器项目

Azure 移动应用服务器项目的初始化方式类似其他 ASP.NET Framework 项目,是通过提供 OWIN 启动类来完成的。 添加 OWIN 启动类:

  1. 右键单击项目,然后选择“添加”>“新项”

  2. 选择“Web”>“常规”,然后选择“OWIN 启动类”模板

  3. 输入启动名称 Startup.cs

  4. Startup.cs 文件的内容应类似于以下代码:

    using Microsoft.Azure.Mobile.Server.Config;
    using Microsoft.Owin;
    using Owin;
    using System.Web.Http;
    
    [assembly: OwinStartup(typeof(WebApplication1.Startup))]
    namespace WebApplication1
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                HttpConfiguration config = new HttpConfiguration();
                new MobileAppConfiguration()
                    // no added features
                    .ApplyTo(config);
                app.UseWebApi(config);
            }
        }
    }
    

    OwinStartup 命名空间和类名将有所不同,具体取决于项目。 具体而言,应替换方法 Configuration() 的内容,并相应调整 using 指令。

若要启用单个功能,必须在调用 ApplyTo 之前对 MobileAppConfiguration 对象调用扩展方法。 例如,以下代码在初始化期间,将默认路由添加到具有属性 [MobileAppController] 的所有 API 控制器:

new MobileAppConfiguration()
    .MapApiControllers()
    .ApplyTo(config);

将以下设置视为“常规”用法,使表和 API 控制器使用实体框架访问 SQL 服务。

new MobileAppConfiguration()
    .AddMobileAppHomeController()
    .MapApiControllers()
    .AddTables(
        new MobileAppTableConfiguration()
            .MapTableControllers()
            .AddEntityFramework()
    )
    .MapLegacyCrossDomainController()
    .ApplyTo(config);

使用的扩展方法包括:

  • AddMobileAppHomeController(),用于提供默认的 Azure 移动应用主页。
  • MapApiControllers(),用于为使用 [MobileAppController] 属性修饰的 WebAPI 控制器提供自定义 API 功能。
  • AddTables(),用于提供到表控制器的 /tables 终结点映射。
  • AddTablesWithEntityFramework(),是使用基于实体框架的控制器映射 /tables 终结点的简单方法。
  • MapLegacyCrossDomainController(),提供标准 CORS 标头进行本地开发。

SDK 扩展

以下基于 NuGet 的扩展包提供应用程序可以使用的多种移动功能。 可以使用 MobileAppConfiguration 对象在初始化期间启用扩展。

发布服务器项目

本部分说明如何从 Visual Studio 发布 .NET 后端项目。 可通过其他方法发布应用程序。 有关详细信息,请参阅 Azure 应用服务文档

  1. 在 Visual Studio 中,重新生成项目以还原 NuGet 包。
  2. 在“解决方案资源管理器”中,右键单击该项目并单击“发布”
  3. 如果以前尚未发布此项目,则配置发布。
    • 选择“Azure”作为目标
    • 选择 Azure 应用服务 (Windows) 作为特定目标。
    • 选择要部署到的应用服务实例。 如果没有,请使用 创建一个+
    • 单击“完成” 。
  4. 如果之前尚未链接 SQL 数据库,请单击 SQL 数据库旁边的“配置”
    • 选择“Azure SQL 数据库”
    • 选择数据库。 如果没有数据库或想要使用不同的数据库和服务器,请单击 创建新的数据库和服务器+
    • 输入数据库连接字符串名称 MS_TableConnectionString。 在提供的框中填写用户名和密码。
    • 单击“完成”
  5. 单击“发布”

发布到 Azure 需要一些时间。 有关详细信息,请参阅 Visual Studio 文档

定义表控制器

定义表控制器,使其向移动客户端公开 SQL 表。 配置表控制器需要三个步骤:

  1. 创建数据传输对象 (DTO) 类。
  2. 在 Mobile DbContext 类中配置表引用。
  3. 创建表控制器。

数据传输对象 (DTO) 是继承自 EntityData 的纯 C# 对象。 例如:

public class TodoItem : EntityData
{
    public string Text { get; set; }
    public bool Complete {get; set;}
}

DTO 用于定义 SQL 数据库中的表。 要创建数据库条目,请将 DbSet<> 属性添加到正在使用的 DbContext 中:

public class MobileServiceContext : DbContext
{
    private const string connectionStringName = "Name=MS_TableConnectionString";

    public MobileServiceContext() : base(connectionStringName)
    {

    }

    public DbSet<TodoItem> TodoItems { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add(
            new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
                "ServiceColumnTable", (property, attributes) => attributes.Single().ColumnType.ToString()));
    }
}

最后,创建新的控制器:

  1. 右键单击 Controllers 文件夹。

  2. 选择“Web API”>“Web API 2 控制器 - 空”

  3. 输入控制器名称。

  4. 将控制器的新内容替换为以下代码:

    public class TodoItemController : TableController<TodoItem>
    {
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            ZUMOAPPNAMEContext context = new ZUMOAPPNAMEContext();
            DomainManager = new EntityDomainManager<TodoItem>(context, Request);
        }
    
        // GET tables/TodoItem
        public IQueryable<TodoItem> GetAllTodoItems()
        {
            return Query();
        }
    
        // GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public SingleResult<TodoItem> GetTodoItem(string id)
        {
            return Lookup(id);
        }
    
        // PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
        {
            return UpdateAsync(id, patch);
        }
    
        // POST tables/TodoItem
        public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
        {
            TodoItem current = await InsertAsync(item);
            return CreatedAtRoute("Tables", new { id = current.Id }, current);
        }
    
        // DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task DeleteTodoItem(string id)
        {
            return DeleteAsync(id);
        }
    }
    

调整表分页大小

默认情况下,Azure 移动应用为每个请求返回 50 条记录。 分页可以确保客户端不会长时间占用其 UI 线程或服务器,从而提供良好的用户体验。 若要更改表分页大小,可增大服务器端“允许的查询大小”和客户端页面大小。服务器端“允许的查询大小”可使用 EnableQuery 属性进行调整:

[EnableQuery(PageSize = 500)]

确保 PageSize 等于或大于客户端请求的大小。 有关更改客户端页面大小的详细信息,请参阅具体的客户端操作指南文档。

定义自定义 API 控制器

自定义 API 控制器通过公开终结点,向移动应用后端提供最基本的功能。 可以使用 [MobileAppController] 属性注册移动设备特定的 API 控制器。 MobileAppController 属性将注册路由、设置移动应用 JSON 序列化程序,并打开客户端版本检查。

自定义 API 控制器的内容如下:

[MobileAppController]
public class CustomAPIController : ApiController
{
    // Content here
}

配置 MobileAppController 属性后,可采用与任何其他 Web API 相同的方式定义自定义 API。

使用身份验证

Azure 移动应用使用应用服务身份验证/授权保护移动后端。 本部分说明如何在 .NET 后端服务器项目中执行以下身份验证相关的任务:

将身份验证添加到服务器项目

可以通过扩展 MobileAppConfiguration 对象并配置 OWIN 中间件,将身份验证添加到服务器项目。

  1. 在 Visual Studio 中,安装 Microsoft.Azure.Mobile.Server.Authentication 包。

  2. Startup.cs 项目文件中,在“配置”方法的开头添加以下代码行

    app.UseAppServiceAuthentication(config);
    

    此 OWIN 中间件组件验证由关联的应用服务网关颁发的令牌。

  3. [Authorize] 属性添加到任何要求身份验证的控制器或方法。

对应用程序使用自定义身份验证

重要

若要启用自定义身份验证,必须先启用应用服务身份验证,而不必在 Azure 门户中为应用服务选择提供程序。 这会使得在托管时启用 WEBSITE_AUTH_SIGNING_KEY 环境变量。

如果不想使用某个应用服务身份验证/授权提供程序,可以实现自己的登录系统。 安装 Microsoft.Azure.Mobile.Server.Login 包以协助生成身份验证令牌。 提供自己的代码验证用户凭据。 例如,可以针对数据库中的加盐密码和哈希密码进行检查。 在以下示例中,isValidAssertion() 方法(在其他位置定义)负责这些检查。

通过创建 ApiController 并公开 registerlogin 操作,可以公开自定义身份验证。 客户端应使用自定义 UI 从用户处收集信息。 随后再通过标准 HTTP POST 调用将信息提交到 API。 服务器验证断言后,便可以使用 AppServiceLoginHandler.CreateToken() 方法颁发令牌。 ApiController 不可使用 [MobileAppController] 属性。

示例 login 操作:

public IHttpActionResult Post([FromBody] JObject assertion)
{
    if (isValidAssertion(assertion)) // user-defined function, checks against a database
    {
        JwtSecurityToken token = AppServiceLoginHandler.CreateToken(new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, assertion["username"]) },
            mySigningKey,
            myAppURL,
            myAppURL,
            TimeSpan.FromHours(24) );
        return Ok(new LoginResult()
        {
            AuthenticationToken = token.RawData,
            User = new LoginResultUser() { UserId = userName.ToString() }
        });
    }
    else // user assertion was not valid
    {
        return this.Request.CreateUnauthorizedResponse();
    }
}

在前面的示例中,LoginResultLoginResultUser 是可序列化的对象,用于公开所需属性。 客户端预期收到采用以下格式的 JSON 对象的登录响应:

{
    "authenticationToken": "<token>",
    "user": {
        "userId": "<userId>"
    }
}

AppServiceLoginHandler.CreateToken() 方法包含 audienceissuer 参数。 这两个参数使用 HTTPS 方案设置为应用程序根目录的 URL。 同样,应将 secretKey 设置为应用程序的签名密钥值。 请勿分发客户端中的签名密钥,因为该密钥可用于伪造密钥和模拟用户。 在应用服务中托管时,可通过引用 WEBSITE_AUTH_SIGNING_KEY 环境变量获取签名密钥。 如果在本地调试上下文中有需要,可根据使用身份验证进行本地调试部分中的说明检索密钥,并将它存储为应用程序设置。

颁发的令牌还可以包括其他声明和到期日期。 颁发的令牌至少应包含一个使用者 (sub) 声明。

可以通过重载身份验证路由支持标准客户端 loginAsync() 方法。 如果客户端通过调用 client.loginAsync('custom'); 进行登录,则路由必须是 /.auth/login/custom。 可以使用 MapHttpRoute() 设置自定义身份验证控制器的路由:

config.Routes.MapHttpRoute("custom", ".auth/login/custom", new { controller = "CustomAuth" });

提示

使用 loginAsync() 方法可确保将身份验证令牌附加到后续对服务的所有调用。

检索经过身份验证的用户信息

当应用服务对用户进行身份验证时,可以访问分配的用户 ID 和 .NET 后端代码中的其他信息。 用户信息可用于在后端作出授权决策。 可使用以下代码获取与某个请求关联的用户 ID:

// Get the SID of the current user.
var claimsPrincipal = this.User as ClaimsPrincipal;
string sid = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier).Value;

SID 派生自提供程序特定的用户 ID,对于给定的用户和登录提供程序而言是静态的。 SID 为 null 即表示无效的身份验证令牌。

应用服务还允许向登录提供程序请求特定声明。 每个标识提供者都可以使用标识提供者 SDK 提供更多信息。 例如,可以使用 Facebook Graph API 提供好友信息。 可以在 Azure 门户上的提供程序边栏选项卡中指定请求的声明。 某些声明需要标识提供者更多的配置。

可以使用以下代码调用 GetAppServiceIdentityAsync 扩展方法以获取登录凭据,包括对 Facebook Graph API 发出请求所需的访问令牌:

// Get the credentials for the logged-in user.
var credentials = await this.User.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);

if (credentials.Provider == "Facebook")
{
    // Create a query string with the Facebook access token.
    var fbRequestUrl = "https://graph.facebook.com/me/feed?access_token="
        + credentials.AccessToken;

    // Create an HttpClient request.
    var client = new System.Net.Http.HttpClient();

    // Request the current user info from Facebook.
    var resp = await client.GetAsync(fbRequestUrl);
    resp.EnsureSuccessStatusCode();

    // Do something here with the Facebook user information.
    var fbInfo = await resp.Content.ReadAsStringAsync();
}

可以通过向 System.Security.Principal 添加 using 语句提供 GetAppServiceIdentityAsync 扩展方法。

对已获授权的用户限制数据访问

上一部分已说明如何检索经过身份验证的用户的用户 ID。 可以根据此值来限制对数据和其他资源的访问。 例如,将 userId 列添加到表以及根据用户 ID 筛选查询结果,是将返回的数据局限于已获授权用户的简单方式。 以下代码只会在 SID 与 TodoItem 表上 UserId 列中的值匹配时才返回数据行:

// Get the SID of the current user.
var claimsPrincipal = this.User as ClaimsPrincipal;
string sid = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier).Value;

// Only return data rows that belong to the current user.
return Query().Where(t => t.UserId == sid);

Query() 方法返回的 IQueryable 可通过 LINQ 操作来处理筛选。

对 .NET 服务器 SDK 进行调试和故障排除

Azure 应用服务提供多种适用于 ASP.NET 应用程序的调试和故障排除方法:

日志记录

可以使用标准的 ASP.NET 跟踪写入来写入应用服务诊断日志。 必须在 Azure 移动应用后端启用诊断后,才能写入日志。

启用诊断并写入日志:

  1. 按照启用应用程序日志记录(Windows)中的步骤操作。

  2. 在代码文件中添加以下 using 语句:

    using System.Web.Http.Tracing;
    
  3. 创建跟踪写入器以便从 .NET 后端写入诊断日志,如下所示:

    ITraceWriter traceWriter = this.Configuration.Services.GetTraceWriter();
    traceWriter.Info("Hello, World");
    
  4. 重新发布服务器项目,并访问 Azure 移动应用后端,结合日志记录执行代码路径。

  5. 下载并评估日志,如 Access 日志文件中所述

使用身份验证进行本地调试

可以先在本地运行应用程序以测试更改,然后将更改发布到云中。 对于大多数 Azure 移动应用后端,请在 Visual Studio 中按 F5。 但是,使用身份验证时需考虑其他一些事项。

必须有基于云的移动应用,已配置应用服务身份验证/授权,并且客户端必须有指定为备用登录主机的云终结点。 请参阅相关的客户端平台文档获取所需的具体步骤。

确保移动后端已安装 Microsoft.Azure.Mobile.Server.Authentication。 然后,在将 MobileAppConfiguration 应用到 HttpConfiguration 之后,在应用程序的 OWIN 启动类中添加以下代码:

app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions()
{
    SigningKey = ConfigurationManager.AppSettings["authSigningKey"],
    ValidAudiences = new[] { ConfigurationManager.AppSettings["authAudience"] },
    ValidIssuers = new[] { ConfigurationManager.AppSettings["authIssuer"] },
    TokenHandler = config.GetAppServiceTokenHandler()
});

在上例中,应使用 HTTPS 方案将 Web.config 文件中的 authAudienceauthIssuer 应用程序设置配置为每个应用程序根目录的 URL。 同样,应将 authSigningKey 设置为应用程序的签名密钥值。

获取签名密钥:

  1. Azure 门户 中导航到应用
  2. 单击“工具”>“Kudu”>“转到”
  3. 在 Kudu 管理站点中,单击“环境”
  4. 查找 WEBSITE_AUTH_SIGNING_KEY 的值。

在本地应用程序配置中使用 authSigningKey 参数的签名密钥。移动后端现在可在本地运行时验证令牌,而客户端从基于云的终结点获取令牌