原文:https://msdn.microsoft.com/zh-cn/magazine/dn781361.aspx

身份验证和授权是应用程序安全的基础。身份验证通过验证提供的凭据来确定用户身份,而授权则决定是否允许用户执行请求的操作。安全的 Web API 身份验证基于确定的身份请求和授权用户请求的资源访问。

您可以在 ASP.NET Web API 中使用 ASP.NET Web API 管道中提供的扩展点,以及使用由主机提供的选项来实现身份验证。对于 ASP.NET Web API 的第一个版本,常见的做法是使用授权筛选器或操作筛选器来实现身份验证。ASP.NET Web API 2 引入了一个专门用于此过程的新的身份验证筛选器。这种新的扩展点使身份验证和授权问题被清晰地划分开。在本文中,我会向您介绍这两种安全筛选器,并将身份验证和授权作为 ASP.NET Web API 中独立的两个方面,向您演示如何使用它们来实现将身份验证和授权。

实现安全性方面的选项

通过使用由主机提供的扩展点以及由 ASP.NET Web API 管道自己提供的扩展点能够实现 ASP.NET Web API 中的身份验证和授权。基于主机的选项包括 HTTP 模块和 OWIN 中间件组件,而 ASP.NET Web API 的扩展选项包括消息处理程序、操作筛选器、授权筛选器以及身份验证筛选器。

基于主机的选项很好地集成到主机管道中,并能较早拒绝管道中的无效请求。另一方面,ASP.NET Web API 的扩展选项对身份验证过程提供更精细的控制水平。也就是说,您可以对不同的控制器甚至不同的操作方法设置不同的身份验证机制。权衡与主机更好地集成在一起,并较早对不佳的身份验证粒度请求予以拒绝。除了这些常规特性,每个选项都有自己的优缺点,我将在后面的章节中进行介绍。

HTTP 模块这是在 IIS 上运行的一个 Web API 选项。作为 IIS 管道的一部分,HTTP 模块允许较早地执行安全代码。从 HTTP 模块中建立的主体适用于所有的组件,包括管道中稍后运行的 IIS 组件。例如,如果主体是由响应 AuthenticateRequest 事件的 HTTP 模块构建的,则主体的用户名将被正确地记录在 IIS 日志的 cs-username 字段中。HTTP 模块的最大缺点是缺乏粒度。HTTP 模块对进入应用程序的所有请求做出运行反应。对于具有不同功能(如 HTML 标记生成,Web API 等等)的 Web 应用程序,让一个 HTTP 模块以某种方式强制执行身份验证通常不是一个很灵活的方法。在这种情况下,另一个使用 HTTP 模块的缺点就显现出来了,即依赖主机—IIS。

OWIN 中间件这是另一个与主机相关的选项,适用于 OWIN 主机。ASP.NET Web API 2 完全支持 OWIN。使用 OWIN 中间件确保安全的可能最有说服力的理由是同一中间件可以在不同的框架中工作。这意味着您可以将多个框架(如 ASP.NET Web API、SignalR 等)用在您的应用程序中,却可以使用共同的安全中间件。然而,OWIN 中间件的最小粒度却可能是一个缺点,因为 OWIN 中间件在 OWIN 管道中运行并且通常在处理各个请求时被调用。此外,OWIN 中间件只能用于与 OWIN 兼容的主机,虽然这种依赖比起依赖特定的主机/服务器(如 IIS)相对要好些,但这是 HTTP 模块的实际情况。值得注意的一点是,正是由于 Microsoft.Owin.Host.SystemWeb 包,OWIN 中间件才可以在(集成了 IIS 的)ASP.NET 管道中运行。

消息处理程序由 ASP.NET Web API 提供的扩展选项,将使用消息处理程序确保安全的最大好处就是它作为 ASP.NET Web API 框架的概念可以不依赖底层的主机或服务器。此外,消息处理程序仅对 Web API 请求运行。使用消息处理程序的不足之处在于缺乏更精细的控制。可将消息处理程序配置为对所有请求或对特定路由以全局处理程序来运行。对于给定的路由,您可以有多个控制器。所有这些控制器和它们所包含的操作方法都必须共享相同的由为此路由配置的消息处理程序强制执行的身份验证。换句话说,由消息处理程序执行的身份验证的最低粒度是在路由级别。

操作筛选器由 ASP.NET Web API 提供的另一个扩展选项是操作筛选器。然而,从执行身份验证的角度来看,它不是一个可行的选择,仅仅是因为它在授权筛选器在 ASP.NET Web API 管道运行之后才开始运行。为了让身份验证和授权能正常工作,身份验证必须先于授权而运行。

授权筛选器然而,由 ASP.NET Web API 提供的另一个扩展选项是授权筛选器。对于要求比消息处理程序能提供的更高的粒度的情形,执行自定义身份验证最常见的一种方式是使用授权筛选器。将授权筛选器用于身份验证和授权的主要问题是,ASP.NET Web API 并不保证身份验证筛选器的执行顺序。基本上,这意味着在执行身份验证的授权筛选器运行之前,执行授权的授权筛选器就可以正常运行了,从而使得授权筛选器选项如同操作筛选器选项一样不适合身份验证。

身份验证筛选器这是本文的重点所在,它是可用于 ASP.NET Web API 2 的最新扩展选项。身份验证筛选器在消息处理程序之后运行,并且是在其他所有筛选器类型之前运行。因此,它们是实现身份验证相关操作的更好选择。最重要的是,身份验证筛选器是在授权筛选器之前运行的。通过使用专门针对身份验证或授权的筛选器,可以分别处理身份验证和授权相关的问题。

此外,身份验证筛选器提供控制或粒度级别,因此特别有用。以旨在被本机移动应用程序和基于浏览器的 AJAX 应用程序所使用的 Web API 为例。移动应用程序可能会在 HTTP Authorization 标头中显示一个令牌,而 AJAX 应用程序可能将身份验证 Cookie 用作凭据。此外,假设 API 的子集是敏感的,且仅适用于本机移动应用程序,您要确保只能通过提供令牌,而不是提供 Cookie 的方式来访问操作方法(Cookie 很容易受到跨站点请求伪造 [XSRF] 的影响,而在 HTTP Authorization 标头中的令牌则不会)。在这种情况下,身份验证必须以比基于主机的选项,甚至是消息处理程序更精细的粒度级别进行。身份验证筛选器非常适合这个用例。您可以应用基于所有这些控制器的令牌上的身份验证筛选器或必须使用的操作方法,以及基于其他地方的 Cookie 的身份验证筛选器。假设,在这种情况下,您有一些常见的操作方法,想通过令牌或 Cookie 的方式来访问它们。您可以将 Cookie 和令牌身份验证筛选器均应用在这些常见的操作方法上,总会有一个筛选器能够成功进行身份验证。这种控制是能被推上台面的具有最大价值的身份验证筛选器。当需要精确控制身份验证时,正确的做法是,通过身份验证筛选器解决身份验证相关问题以及通过授权筛选器解决授权相关问题。

值得一提的是,开箱即用的身份验证筛选器 (HostAuthenticationFilter) 通过 OWIN 中间件启用了 ASP.NET Web API 身份验证。当 OWIN 身份验证中间件在管道中运行,并试图“主动”身份验证传入的请求时,如需要,也可以将它配置为“被动”身份验证传入的请求。HostAuthenticationFilter 允许依据 Web API 管道中后来的名称运行被动 OWIN 身份验证中间件。这种方法启用了能够在多框架间共享的身份验证代码(包括 Microsoft 提供的 OWIN 身份验证中间件),同时仍允许将每个操作粒度用于身份验证。

虽然您可以混合使用主机级别的身份验证和基于更细粒度 Web API 管道的身份验证,但是也必须仔细考虑主机级别的身份验证会怎样影响 Web API 身份验证。例如,您可以使基于 Cookie 的身份验证中间件处于主机级别,这意味着可以同其他框架配合使用,比如 ASP.NET MVC,但是让 Web API 使用基于 Cookie 的主体会使得它容易受到(比如 XSRF 的)攻击。为了帮助处理这种情况,SuppressDefaultHostAuthentication 扩展方法使 Web API 忽略在主机级别配置的任何身份验证。默认的 Web API Visual Studio 模板在主机级别下启用了 Cookie,并使用在 Web API 级别的承载令牌。因为 Cookie 是在主机级别下启用的,并要求 XSRF 缓解,所以模板还使用 SuppressDefaultHostAuthentication 阻止 Web API 管线使用基于 Cookie 的主体。这样一来,Web API 将只使用基于令牌的主体,您则不需要为 Web API 建立抵御 XSRF 攻击的机制。

使身份验证筛选器和授权筛选器协同工作

在 ASP.NET Web API 管道中,身份验证筛选器第一个运行(紧接着运行的是授权筛选器)的原因很简单,因为授权取决于确定的身份,而这正是身份验证的结果。以下为您介绍如何设计身份验证筛选器和授权筛选器以协同工作来保护 ASP.NET Web API。

设计的基本原则是让身份验证筛选器只负责验证凭据,而不是让其处理其他问题。例如,如果未提供凭据,身份验证筛选器将不会拒绝有 401 未经授权状态代码的请求。它根本没有确定一个经过身份验证的身份,并将如何处理匿名请求的问题留给了授权阶段。身份验证筛选器基本执行三种类型的操作:

  1. 如果感兴趣的凭据不存在于该请求中,则筛选器就不执行任何操作。
  2. 如果存在凭据且该凭据是有效的,则筛选器会以经过身份验证的主体的形式确定一个身份。
  3. 如果存在凭据但该凭据是无效的,筛选器就会通过设置一个错误的结果通知 ASP.NET Web API 框架,这基本上可导致向请求者发回一个“未经授权”的响应。

如果管道中运行的身份验证筛选器都无法检测到无效的凭据,则该管道将继续运行,即使还没有验证未确定的身份。只有根据后来在管道中运行的组件,才能确定如何处理这个匿名请求。

在最基本的层面上,授权筛选器只检查所确定的身份是否是经过身份验证的身份。然而,授权筛选器也可以确保:

  • 经过身份验证的身份的用户名在经过允许的用户列表上。
  • 至少有一个与经过身份验证的身份相关的角色会列在经过允许的角色列表上。

虽然开箱即用的授权筛选器只根据刚才的描述执行基于角色的访问控制,但是来自开箱即用授权筛选器的自定义授权筛选器却可以通过检查属于由身份验证筛选器确定的身份的声明来执行基于声明的访问控制。

如果所有的授权筛选器都运行正常,管道将继续执行,最终 API 控制器的操作方法会生成一个针对请求的响应。如果未确定身份,或者如果在用户名或角色要求方面存在不匹配,则授权筛选器将拒绝存在 401 未授权响应的请求。图 1 说明了两种筛选器在三种情况下所扮演的角色:不存在凭据、提供的凭据无效和存在的凭据有效。

图 1 ASP.NET Web API 管道中的安全筛选器

创建身份验证筛选器

身份验证筛选器是一个实现 IAuthenticationFilter 接口的类。这个接口提供两种方法:AuthenticateAsync 和 ChallengeAsync,如下所示:

public interface IAuthenticationFilter : IFilter
{
  Task AuthenticateAsync(HttpAuthenticationContext context, 
    CancellationToken cancellationToken);
  Task ChallengeAsync(HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken);
}

AuthenticateAsync 方法接受 HttpAuthenticationContext 作为参数。此上下文就是 AuthenticateAsync 方法将身份验证的结果反馈给 ASP.NET Web API 框架的方式。如果请求消息中包含真实的凭据,传入 HttpAuthenticationContext 对象的 Principal 属性将被设置为经过身份验证的主体。如果凭据无效,HttpAuthenticationContext 参数的 ErrorResult 属性将设置为 UnauthorizedResult。如果该请求消息根本不包含凭据,则 AuthenticateAsync 方法不执行任何操作。图 2 中的代码显示了涵盖这三种情况的 AuthenticateAsync 方法的典型实现。在这个示例中使用的经过身份验证的主体是只有名称和角色声明的 ClaimsPrincipal。

图 2 AuthenticateAsync 方法

public Task AuthenticateAsync(HttpAuthenticationContext context,
  CancellationToken cancellationToken)
{
  var req = context.Request;
  // Get credential from the Authorization header
//(if present) and authenticate
  if (req.Headers.Authorization != null &&
    "somescheme".Equals(req.Headers.Authorization.Scheme,
      StringComparison.OrdinalIgnoreCase))
  {
    var creds = req.Headers.Authorization.Parameter;
    if(creds == "opensesame") // Replace with a real check
    {
      var claims = new List<Claim>()
      {
        new Claim(ClaimTypes.Name, "badri"),
        new Claim(ClaimTypes.Role, "admin")
      };
      var id = new ClaimsIdentity(claims, "Token");
      var principal = new ClaimsPrincipal(new[] { id });
      // The request message contains valid credential
      context.Principal = principal;
    }
    else
    {
      // The request message contains invalid credential
      context.ErrorResult = new UnauthorizedResult(
        new AuthenticationHeaderValue[0], context.Request);
    }
  }
  return Task.FromResult(0);
}

您可以使用 AuthenticateAsync 方法来实现验证请求中的凭据的核心身份验证逻辑,并使用 ChallengeAsync 方法添加身份验证质询。当状态代码是 401 未经授权时,身份验证质询被加入到响应中,为了检查状态代码,您需要该响应对象。但 ChallengeAsync 方法不允许您检查响应或直接设置质询。事实上,这种方法是在操作方法之前在 Web API 管道的请求处理部分中执行的。然而,ChallengeAsync 方法的参数 HttpAuthenticationChallengeContext 允许将操作结果对象 (IHttpActionResult) 分配给 Result 属性。操作结果对象的 ExecuteAsync 方法等待任务生成响应,检查响应状态代码,并添加 WWW-Authenticate 响应标头。图 3 中的代码显示了 ChallengeAsync 方法的典型实现。在这个示例中,我只添加一个经过硬编码的质询。ResultWithChallenge 类是我创建用来添加质询的操作结果类。

图 3 ChallengeAsync 方法

public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
  CancellationToken cancellationToken)
{
  context.Result = new ResultWithChallenge(context.Result);
  return Task.FromResult(0);
}
public class ResultWithChallenge : IHttpActionResult
{
  private readonly IHttpActionResult next;
  public ResultWithChallenge(IHttpActionResult next)
  {
    this.next = next;
  }
  public async Task<HttpResponseMessage> ExecuteAsync(
    CancellationToken cancellationToken)
  {
    var response = await next.ExecuteAsync(cancellationToken);
    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
      response.Headers.WwwAuthenticate.Add(
        new AuthenticationHeaderValue("somescheme", "somechallenge"));
    }
    return response;
  }
}

下面的代码显示了完整的筛选器类:

public class TokenAuthenticationAttribute :
Attribute, IAuthenticationFilter
{
  public bool AllowMultiple { get { return false; } }
  // The AuthenticateAsync and ChallengeAsync methods go here
}

除了实现 IAuthenticationFilter 接口,从属性中派生可以将此类用作类(控制器)级或方法(操作方法)级的属性。

因此,您可以创建一个只负责身份验证特定凭据(本例中的虚假令牌)的身份验证筛选器。身份验证筛选器没有授权逻辑;它唯一目的是处理身份验证:(在处理请求消息时如果有的话)确定身份,(在处理响应消息时如果有的话)返回质询。授权筛选器处理授权问题,如检查身份是否是经过身份验证的身份或者已在经过允许的用户或角色列表中列出。

使用授权筛选器

使用授权筛选器的基本目标是执行授权,以确定用户是否有权访问所请求的资源。Web API 提供了所谓的 AuthorizeAttribute 授权筛选器的使用。应用该筛选器可确保身份是经过身份验证的身份。您还可以使用允许的特定用户名和角色列表配置授权属性。图 4 中的代码显示了使用不同的身份属性来授权,在不同级别(总的来说,是控制器级别和操作方法级别)应用的授权筛选器。本示例中的筛选器整体上保证了身份是经过身份验证了的。在控制器级别使用的筛选器保证了身份是经过身份验证了的,并且与该身份相关联的角色中至少有一个是“管理员”。在操作方法级别使用的筛选器确保了身份是经过身份验证的,且用户名是“badri”。这里要注意的一点是,在操作方法级别的授权筛选器也继承了控制器级别和全局级别的筛选器。因此,若要成功完成授权,所有筛选器都必须通过:用户名必须是“badri”,其中一个角色必须是“管理员”,且用户必须经过身份验证。

图 4 使用处于三个不同级别的授权筛选器

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other Web API configuration code goes here
    config.Filters.Add(new AuthorizeAttribute()); // Global level
  }
}
[Authorize(Roles="admin")] // Controller level
public class EmployeesController : ApiController
{
  [Authorize(Users="badri")] // Action method level
  public string Get(int id)
  {
    return “Hello World”;
  }
}

开箱即用的 AuthorizeAttribute 非常有用,但是如果需要更多的自定义,您可以对其划分子类,来实现其他的授权行为。下面的代码显示了一个自定义的授权筛选器:

public class RequireAdminClaimAttribute : AuthorizeAttribute
{
  protected override bool IsAuthorized(HttpActionContext context)
  {
    var principal =
      context.Request.GetRequestContext().Principal as ClaimsPrincipal;
    return principal.Claims.Any(c => c.Type ==
      "http://yourschema/identity/claims/admin"
      && c.Value == "true");
  }
}

这个筛选器只检查“管理员”自定义声明,但您可以使用 HttpActionContext 中的主体和其他附加信息在这里进行自定义授权。

在 ASP.NET Web API 的第一个版本中,自定义授权筛选器经常被误用来实现身份验证,但对于 ASP.NET Web API 2,身份验证筛选器现在管道中有自己的地方,这有助于开发干净的模块化代码,便于分开考虑身份验证和授权方面的问题。

筛选器覆盖

正如我刚才解释的,授权筛选器可以应用在操作方法级别、控制器级别或全局级别。通过全局指定授权筛选器,您可以在所有控制器范围内强制执行对所有操作方法调用的授权。如果您想通过一些方法免于执行全局配置检查,那么使用 AllowAnonymous 属性可以轻松做到这一点。

图 5 中的代码显示了在控制器级别对 AllowAnonymous 属性的使用。虽然授权筛选器在全局范围中应用,但与 PublicResourcesController 一起使用的 AllowAnonymous 属性可免于对传入此控制器的请求执行授权。

图 5 使用 AllowAnonymous 属性

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other Web API configuration code goes here
    config.Filters.Add(new AuthorizeAttribute()); // Global level
  }
}
[AllowAnonymous]
public class PublicResourcesController : ApiController
{
  public string Get(int id)
  {
    return “Hello World”;
  }
}

AllowAnonymous 属性提供了一种方式,让特定的操作可以覆盖由更高级别的授权筛选器配置的授权。然而,AllowAnonymous 只允许您覆盖授权。假设您最想要使用 HTTP 基本身份验证对大多数操作进行身份验证,但有一个操作只能用令牌进行身份验证。全局配置令牌身份验证而随后对此操作覆盖身份验证(类似于 AllowAnonymous 覆盖授权的方式)会是不错的方法。

ASP.NET Web API 2 引入了一种新的筛选器类型,以解决这种情况,即覆盖筛选器。不同于 AllowAnonymous,ASP.NET Web API 2 中引入的覆盖筛选器可与任何类型的筛选器一同工作。覆盖筛选器,顾名思义,可以覆盖在更高级别上配置的筛选器。若要覆盖在更高层次上配置的身份验证筛选器,请使用开箱即用的属性 OverrideAuthentication。如果您有一个适合全局使用的身份验证筛选器,并希望阻止它运行特定的操作方法或控制器,您可以仅在所需的级别上应用 OverrideAuthentication。

覆盖筛选器的作用远不止于阻止某些筛选器运行。假设您有两种身份验证筛选器,一个用于验证安全令牌,另一个用于在 HTTP 基本方案中验证用户名/密码。这两种筛选器都在全局范围内应用,使您的 API 足够灵活,可以接受令牌或用户名/密码。下面的代码显示了在全局范围内应用的两种身份验证筛选器:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other Web API configuration code goes here
    config.Filters.Add(new TokenAuthenticator());
    config.Filters.Add(new HttpBasicAuthenticator(realm: "Magical"));
  }
}

现在,也许您想确保只将令牌用作访问特定操作方法的凭据。OverrideAuthentication 如何使您能够满足这种需求,难道是它禁止所有筛选器运行?下面是覆盖筛选器的重要特征,他们清除了在更高级别上指定的所有筛选器,但不删除与其在同一级别上指定的筛选器。这基本上意味着您可以在某个特定级别上添加一个或多个身份验证筛选器,同时清除在更高级别上的所有其他筛选器。回到仅将令牌用作为访问特定操作方法的凭据的要求,您可以简单地在操作方法级别上指定 OverrideAuthentication 属性和 TokenAuthenticator 属性,如下面的代码所示(这可以确保只有 TokenAuthenticator 为操作方法 GetAllowedForTokenOnly 运行):

public class EmployeesController : ApiController
{
  [OverrideAuthentication] // Removes all authentication filters
  [TokenAuthenticator] // Puts back only the token authenticator
  public string GetAllowedForTokenOnly(int id)
  {
    return “Hello World”;
  }
}

因此,引入了 ASP.NET Web API 2 的覆盖筛选器在全局范围内指定筛选器,并在较低级别上选择性地在必须只对全局行为进行覆盖的领域运行筛选器方面提供了更大的灵活性。

除了 OverrideAuthentication 属性,另外还有开箱即用的称为 OverrideAuthorization 的属性,它可以删除在更高级别上指定的授权筛选器。与 AllowAnonymous 相比,不同之处在于 OverrideAuthorization 只删除更高级别上的授权筛选器。但不删除与其在相同级别上的指定的授权筛选器。AllowAnonymous 使 ASP.NET Web API 可以跳过相关的授权过程,即使是与 AllowAnonymous 在相同级别上指定的授权筛选器,都将被忽略。

总结

您可以使用由主机提供的选项,以及由 ASP.NET Web API 管道提供的扩展点在 ASP.NET Web API 中执行身份验证。基于主机的选项很好地集成到主机管道中,并在早期拒绝管道中的无效请求。ASP.NET Web API 扩展点提供对身份验证过程更精细的控制级别。如果您需要对身份验证执行更多的控制,例如,要对不同的控制器,甚至不同的操作方法使用不同的身份验证机制,正确的做法是,通过身份验证筛选器解决身份验证相关问题以及通过授权筛选器解决授权相关问题。

ASP.NET Web API 安全筛选器的更多相关文章

  1. Web API 授权筛选器

    方式一.全局认证 public static class WebApiConfig { public static void Register(HttpConfiguration config) { ...

  2. ASP.NET Web API 接口执行时间监控

    软件产品常常会出现这样的情况:产品性能因某些无法预料的瓶颈而受到干扰,导致程序的处理效率降低,性能得不到充分的发挥.如何快速有效地找到软件产品的性能瓶颈,则是我们感兴趣的内容之一. 在本文中,我将解释 ...

  3. ASP.NET Web API中的依赖注入

    什么是依赖注入 依赖,就是一个对象需要的另一个对象,比如说,这是我们通常定义的一个用来处理数据访问的存储,让我们用一个例子来解释,首先,定义一个领域模型如下: namespace Pattern.DI ...

  4. 剖析Asp.Net Web API中HttpController的激活

    在Asp.Net Web API中,请求的目标是定义在某个HttpController中的某个Action方法.当请求经过Asp.Net Web API消息处理管道到达管道"龙尾" ...

  5. Asp.Net Web API 2第十二课——Media Formatters媒体格式化器

    前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html 本教程演示如何在ASP.N ...

  6. 【ASP.NET Web API教程】6.1 媒体格式化器

    http://www.cnblogs.com/r01cn/archive/2013/05/17/3083400.html 6.1 Media Formatters6.1 媒体格式化器 本文引自:htt ...

  7. ASP.NET Web API 文件產生器 - 使用 Swagger

    转帖:http://kevintsengtw.blogspot.hk/2015/12/aspnet-web-api-swagger.html Swagger 是一套 API 互動文件產生器,使用 HT ...

  8. ASP.NET Web API Model-ModelBinder

    ASP.NET Web API Model-ModelBinder 前言 本篇中会为大家介绍在ASP.NET Web API中ModelBinder的绑定原理以及涉及到的一些对象模型,还有简单的Mod ...

  9. ASP.NET Web API 过滤器创建、执行过程(一)

    ASP.NET Web API 过滤器创建.执行过程(一) 前言 在上一篇中我们讲到控制器的执行过程系列,这个系列要搁置一段时间了,因为在控制器执行的过程中包含的信息都是要单独的用一个系列来描述的,就 ...

随机推荐

  1. 为你的网页图标(Favicon)添加炫丽的动画和图片

    Favico.js 在让你的网页图标显示徽章,图像或视频.你设置可以轻松地在网页图标中使用动画,可以自定义类型的动画,背景颜色和文字颜色.它支持的动画,像幻灯片,渐变,弹出等等. 您可能感兴趣的相关文 ...

  2. Aristochart – 灵活的 HTML5 Canvas 折线图

    Aristochart 是基于 HTML5 Canvas 的折线图功能库,具有高定制性和灵活性的特点.Aristochart 会帮助你处理图形显示,让你能够专注于业务逻辑处理. 您可能感兴趣的相关文章 ...

  3. javascript笔记图

    1.this 2.对象 3.继承 4.跨域 5.事件 6.基础

  4. 盒图(boxplot)

    最近在摆弄数据离散度的时候遇到一种图形,叫做盒图(boxplot).它对于显示数据的离散的分布情况效果不错. 盒图是在1977年由美国的统计学家约翰·图基(John Tukey)发明的.它由五个数值点 ...

  5. .Net关闭数据库连接时判断ConnectionState为Open还是Closed?

    两种写法: if (conn.State == System.Data.ConnectionState.Open)            {                conn.Close();  ...

  6. Android jni开发中的常见错误

    错误1:java.lang.UnsatisfiedLinkError: Native method not found: 本地方法没有找到 1.本地函数名写错 2.忘记加载.so文件 没有调用Syst ...

  7. UIAlertView' is deprecated: first deprecated in iOS 9.0 - UIAlertView is deprecated. Use UIAlert

    UIAlertController * cancleAlertController = [UIAlertController alertControllerWithTitle:nil message: ...

  8. Android touch mode和focusableInTouchMode分析

    首先我们来看看touch mode的定义.它是用户和手机进行交互时view层次结构的一个状态.它本身是很容易理解的, 代表了最近一次的交互是否是通过触摸屏发生的,因为在Android设备上还存在别的交 ...

  9. iOS做新浪微博sso授权登录遇到的一些坑

    新浪微博sso授权第三方登录,这里没有借助第三方框架,如shareSKD和友盟等,直接参考新浪官方SDK和文档. 过程中遇到几个坑,找了很久,好歹最后解决了,记录如下 问题1: _NSInlineDa ...

  10. 基于ruby的watir自动化测试 笔记二

    基于ruby的watir自动化测试 笔记一的补充版,新增加了些特殊的控件捕获方法.还在更新中.... attribute_value 获取当前控件的属性 Value = ie.link(:id=> ...