zoukankan      html  css  js  c++  java
  • 笔记-ASP.NET WebApi

    本文是针对ASP.NET WepApi 2 的笔记。

    Web API 可返回的结果:

    1.void

    2.HttpResponseMessage

    3.IHttpActionResult

    4.其他类型

    返回类型 Web API创建响应的方式
    void HTTP204(无内容)
    HttpResponseMessage 转换为HTTP响应消息
    IHttpActionRsult 调用ExecuteAsync来创建HttpResponseMessage,然后转换为HTTP响应消息
    其他类型 将序列化的返回值写到响应正文中,返回HTTP200

    路由

    路由表:Web API的默认路由模板"api/{controller}/{id}",此模板中,"api"是文本路径段,{controller}和{id}是占位符变量。

    路由变体:可以显示指定Action的HTTP方法,如HttpGet,HttpPost。

    public class ProductsController : ApiController
    {
        [HttpGet]
        public Product FindProduct(id) {}
    }

    若要允许多个HTTP方法或GET,POST,PUT,DELETE以外的HTTP方法,可以显示使用AcceptVerbs属性:

    public class ProductsController : ApiController
    {
        [AcceptVerbs("GET", "HEAD")]
        public Product FindProduct(id) { }
    
        // WebDAV method
        [AcceptVerbs("MKCOL")]
        public void MakeCollection() { }
    }

    可以通过ActionName属性重写Action名称:

    public class ProductsController : ApiController
    {
        [HttpGet]
        [ActionName("Thumbnail")]
        public HttpResponseMessage GetThumbnailImage(int id);
    
        [HttpPost]
        [ActionName("Thumbnail")]
        public void AddThumbnailImage(int id);
    }

    也可以使用NonAction属性指定Action不参与路由匹配:

    // Not an action method.
    [NonAction]  
    public string GetPrivateData() { ... }

    Web API路由过程的某些部分提供了扩展点:

    接口 描述
    IHttpControllerSelector 选择控制器
    IHttpControllerTypeResolver 获取控制器类型的列表。DefaultHttpControllerSelector从此列表中选择控制器类型
    IAssembliesResolver 获取项目的程序集的列表。IHttpControllerTypeResolver接口使用此列表找到控制器的类型
    IHttpControllerActivator 创建新的控制器实例
    IHttpActionSelector 选择Action
    IHttpActionInvoker 调用Action

    若要为这些接口提供自己的实现,在HttpConfiguration对象上操作,HttpConfigutation在App_Start文件夹下的WebApiConfig.cs文件下能找到,也可以自己获取:

    var config = GlobalConfiguration.Configuration;
    config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));

    属性路由:Route是API 2支持的一种新类型的路由,顾名思义,它可以使用属性定义路由:

    [Route("customers/{customerId}/orders")]
    public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

    你可能需要先启用,才能使用属性路由:

    using System.Web.Http;
    
    namespace WebApplication
    {
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API routes
                config.MapHttpAttributeRoutes();
    
                // Other Web API configuration not shown.
            }
        }
    }

    路由名称:

    在Web API 中,每个路由都有一个名称。路由名称可用于生成链接,以便可以在HTTP响应中包含一个链接。

    Route可以设置名称属性,下面的代码演示如何设置路由名称,以及如何使用根据路由名称生成的链接:

    public class BooksController : ApiController
    {
        [Route("api/books/{id}", Name="GetBookById")]
        public BookDto GetBook(int id) 
        {
            // Implementation not shown...
        }
    
        [Route("api/books")]
        public HttpResponseMessage Post(Book book)
        {
            // Validate and add book to database (not shown)
    
            var response = Request.CreateResponse(HttpStatusCode.Created);
    
            // Generate a link to the new book and set the Location header in the response.
            string uri = Url.Link("GetBookById", new { id = book.BookId });
            response.Headers.Location = new Uri(uri);
            return response;
        }
    }

    路由顺序:属性路由还可以指定路由匹配时的顺序,RouteOrder,默认顺序是0,首先计算较低的值。

    媒体格式化

    媒体类型,也称MIME类型。在HTTP中,媒体类型描述消息正文的格式。例如:

    • text/html
    • image/png
    • application/json

    当HTTP消息包含实体正文时,Content-type标头指定消息正文的格式。

    例如,如果HTTP响应包含PNG图像,响应可能包含以下标头:

    HTTP/1.1 200 OK
    Content-Length: 95267
    Content-Type: image/png

    当客户端发送请求时,它可以包含Accept标头。Accept标头指示客服端想要从服务器获取的响应媒体类型。例如:

    Accept: text/html,application/xhtml+xml,application/xml

    此标头指示客户端希望获得HTML、XHTML或XML。

    媒体类型可以确定Web API 如何序列化和反序列化HTTP消息正文。可以通过编写自己的媒体类型达到扩展Web API序列化和反序列化的目的。

    若要创建媒体格式化程序,需要实现以下类:

    • MediaTypeFormatter。 此类使用异步的方式读取和写入。
    • BufferedMediaTypeFormatter。此类派生自MediaTypeFormatter,但是使用同步的方式读取和写入。

    下面的实例演示的媒体类型可以序列化以逗号分割值(CSV)格式的产品对象。下面是Product对象的定义:

    namespace ProductStore.Models
    {
        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Category { get; set; }
            public decimal Price { get; set; }
        }
    }

    若要实现CSV格式化程序,定义一个类,派生自BufferedMediaTypeFormatter:

    namespace ProductStore.Formatters
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net.Http;
    using System.Net.Http.Formatting;
    using System.Net.Http.Headers;
    using ProductStore.Models;
    
    namespace ProductStore.Formatters
    {
        public class ProductCsvFormatter : BufferedMediaTypeFormatter
        {
        }
    }

    在构造函数中,添加格式化程序支持的媒体类型。在此示例中,支持类型"text/csv":

    public ProductCsvFormatter()
    {
        // Add the supported media type.
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
    }

    重写CanWriteType方法,以指示该类型格式化程序可以序列化:

    public override bool CanWriteType(System.Type type)
    {
        if (type == typeof(Product))
        {
            return true;
        }
        else
        {
            Type enumerableType = typeof(IEnumerable<Product>);
            return enumerableType.IsAssignableFrom(type);
        }
    }

    在此示例中,格式化程序可以序列化单个Product对象的集合、Product对象。

    同样,重写CanReadType方法,以指示该类型格式化程序可以反序列化。在此示例中,格式化程序不支持反序列化,因此,此方法返回false:

    public override bool CanReadType(Type type)
    {
        return false;
    }

    最后,重写WriteToStream方法。此方法通过向流写入序列化程序。如果支持反序列化,还可以重写ReadFromStream方法。

    public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
    {
        using (var writer = new StreamWriter(writeStream))
        {
            var products = value as IEnumerable<Product>;
            if (products != null)
            {
                foreach (var product in products)
                {
                    WriteItem(product, writer);
                }
            }
            else
            {
                var singleProduct = value as Product;
                if (singleProduct == null)
                {
                    throw new InvalidOperationException("Cannot serialize type");
                }
                WriteItem(singleProduct, writer);
            }
        }
    }
    
    // Helper methods for serializing Products to CSV format. 
    private void WriteItem(Product product, StreamWriter writer)
    {
        writer.WriteLine("{0},{1},{2},{3}", Escape(product.Id),
            Escape(product.Name), Escape(product.Category), Escape(product.Price));
    }
    
    static char[] _specialChars = new char[] { ',', '
    ', '
    ', '"' };
    
    private string Escape(object o)
    {
        if (o == null)
        {
            return "";
        }
        string field = o.ToString();
        if (field.IndexOfAny(_specialChars) != -1)
        {
            // Delimit the entire field with quotes and replace embedded quotes with "".
            return String.Format(""{0}"", field.Replace(""", """"));
        }
        else return field;
    }

    将自定义的媒体格式化程序添加到Web API管道

    public static void ConfigureApis(HttpConfiguration config)
    {
        config.Formatters.Add(new ProductCsvFormatter()); 
    }

    WebAPI 提供了对JSON和XML的媒体类型格式化程序。

    JSON格式由JsonMediaTypeFormatter类处理,默认情况下JsonMediaTypeFormatter使用Json.NET库以执行序列化。

    如果您愿意,可以配置JsonMediaTypeFormatter,以使用DataContractJsonSerializer而不是Json.NET。若要执行此操作,设置UseDataContractJsonSerializer属性true:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.UseDataContractJsonSerializer = true;

    默认情况下,所有公共属性和字段会参与序列化JSON。若要忽略属性或字段,给它装饰JsonIgnore属性。

    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
        [JsonIgnore]
        public int ProductCode { get; set; } // omitted
    }

    你也可以使用DataContract属性装饰你的类,存在此属性,则将忽略所有成员,除非它们具有DataMember(JsonProperty)属性。此外,可以使用DataMember序列化私有成员。

    [DataContract]
    public class Product
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public decimal Price { get; set; }
        public int ProductCode { get; set; }  // omitted by default
    }

    默认情况下,只读属性参与序列化。

    默认情况下,Json.NET将日期将采用ISO 8601格式。使用"Z"后缀编写日期以 UTC (协调世界时)。 以本地时间日期包含时区偏移量。 例如:

    2012-07-27T18:51:45.53403Z         // UTC
    2012-07-27T11:51:45.53403-07:00    // Local

    默认情况下,Json.NET 保留时区。 您可以通过设置 DateTimeZoneHandling 属性覆盖此:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;

    如果想要使用Microsoft JSON 日期格式("/Date(ticks)/") 设置而不是 ISO 8601 DateFormatHandling上序列化程序设置属性:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.DateFormatHandling 
    = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

    编写缩进的JSON,可以如下设置:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

    JSON属性名称使用驼峰式,无需修改数据模型,设置CamelCasePropertyNamesContractResolver序列化程序:

    var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

    可以返回一个匿名对象。

    XML媒体类型格式化程序

    默认XmlMediaTypeFormatter类。默认使用DataContractSerializer来执行序列化。

    如果您愿意,可以配置XmlMediaTypeFormatter若要使用XmlSerializer而不是DataContractSerializer。 若要执行此操作,设置UseXmlSerializer属性设置为true:

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
    xml.UseXmlSerializer = true;

    XmlSerializer类支持一组更窄的类型,而不DataContractSerializer,但提供了更好地控制生成的 XML。 请考虑使用XmlSerializer如果你需要匹配现有 XML 架构。

    介绍下DataContractSerializer的序列化行为:

    • 所有公共的属性和字段进行序列化。若要忽略,使用IgnoreDataMember属性。
    • 不序列私有和受保护成员。
    • 只读属性不会序列化(但是,只读集合属性的内容进行序列化)。
    • 类和成员名称是用XML类声明中显示完成相同的。
    • 使用默认XML命名空间。

    如果需要更好地控制序列化,您可以修饰的类DataContract属性。 当存在此属性时,类被序列,如下所示:

    • "选择加入"方法: 属性和字段,默认情况下不序列化。 要序列化的属性或字段,给它装饰DataMember属性。
    • 要序列化的私有或受保护成员,给它装饰DataMember属性。
    • 只读属性不会序列化。
    • 若要更改类名称在 XML 中的显示方式,请设置名称中的参数DataContract属性。
    • 若要更改成员名称在 XML 中的显示方式,请设置名称中的参数DataMember属性。
    • 若要更改的 XML 命名空间,请设置Namespace中的参数DataContract类。

    删除JSON或XML格式化程序

    如果您不想要使用它们,可以从列表中的格式化程序,删除 JSON 格式化程序或 XML 格式化程序。 若要执行此操作的主要原因是:

    • 若要限制为特定媒体类型将 web API 响应。 例如,你可能决定以支持仅将 JSON 响应,并删除 XML 格式化程序。
    • 若要使用自定义格式化程序替换默认的格式化程序。 例如,可以使用您自己的 JSON 格式化程序的自定义实现来替换 JSON 格式化程序。

    下面的代码演示如何删除默认格式化程序。 调用此成员,从你应用程序_启动Global.asax 中定义的方法。

    void ConfigureApi(HttpConfiguration config)
    {
        // Remove the JSON formatter
        config.Formatters.Remove(config.Formatters.JsonFormatter);
    
        // or
    
        // Remove the XML formatter
        config.Formatters.Remove(config.Formatters.XmlFormatter);
    }

    ASP.NET Web API 中的模型验证

    当客户端将数据发送到 web API 时,通常要执行任何处理之前验证数据。

    数据注释

    在 ASP.NET Web API 中,可以使用中的属性System.ComponentModel.DataAnnotations命名空间来对模型设置属性的验证规则。 请考虑以下模型:

    using System.ComponentModel.DataAnnotations;
    
    namespace MyApi.Models
    {
        public class Product
        {
            public int Id { get; set; }
            [Required]
            public string Name { get; set; }
            public decimal Price { get; set; }
            [Range(0, 999)]
            public double Weight { get; set; }
        }
    }

    如果已在 ASP.NET MVC 中使用模型验证,这应该很熟悉。 Required属性指出Name属性不能为 null。 Range属性指出Weight必须是介于 0 和 999 之间。

    假设客户端发送 POST 请求,其中以下 JSON 表示形式:

    { "Id":4, "Price":2.99, "Weight":5 }

    您可以看到客户端未包括Name属性,它被标记为Required。 当 Web API 将转换为 JSONProduct实例,它会验证Product针对验证特性。 在你的控制器操作,可以检查模型是否有效:

    using MyApi.Models;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    
    namespace MyApi.Controllers
    {
        public class ProductsController : ApiController
        {
            public HttpResponseMessage Post(Product product)
            {
                if (ModelState.IsValid)
                {
                    // Do something with the product (not shown).
    
                    return new HttpResponseMessage(HttpStatusCode.OK);
                }
                else
                {
                    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
                }
            }
        }
    }

    可以创建一个Action筛选器来调用控制器操作前的模型检查:

    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http.Controllers;
    using System.Web.Http.Filters;
    using System.Web.Http.ModelBinding;
    
    namespace MyApi.Filters
    {
        public class ValidateModelAttribute : ActionFilterAttribute
        {
            public override void OnActionExecuting(HttpActionContext actionContext)
            {
                if (actionContext.ModelState.IsValid == false)
                {
                    actionContext.Response = actionContext.Request.CreateErrorResponse(
                        HttpStatusCode.BadRequest, actionContext.ModelState);
                }
            }
        }
    }

    如果模型验证失败,此筛选器将返回 HTTP 响应,其中包含验证错误。 在这种情况下,不会调用控制器操作。

    若要将此筛选器应用于所有的 Web API 控制器,将添加到筛选器的实例HttpConfiguration.Filters在配置期间的集合:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Filters.Add(new ValidateModelAttribute());
    
            // ...
        }
    }

    另一个选项是设置筛选器作为特性上各个控制器或控制器操作:

    public class ProductsController : ApiController
    {
        [ValidateModel]
        public HttpResponseMessage Post(Product product)
        {
            // ...
        }
    }

    ASP.NET Web API 中的参数绑定

    Web API 在控制器上调用方法时,必须设置参数的值,此过程称为“绑定”。

    默认情况下,Web API 使用以下规则进行参数绑定:

    • 如果参数为“简单”类型,Web API 会尝试从 URI 中获取值。 简单类型包括 .NET基元类型(int、 bool、 double等),以及 TimeSpan、DateTime、Guid、decimal 和 string,此外还有**借助类型转换器即可从字符串进行转换的类型。 (稍后介绍有关类型转换器的详细信息。)
    • 对于复杂类型,Web API 尝试使用媒体类型格式化程序从消息正文中读取值。

    例如,下面是典型的 Web API 控制器方法:

    HttpResponseMessage Put(int id, Product item) { ... }

    Id 参数是"简单"类型,因此 Web API 尝试从请求 URI 中获取值。 item 参数是复杂类型,因此 Web API 使用媒体类型格式化程序从请求正文中读取值。

    使用 [FromUri]

     若要强制 Web API 从 URI 读取复杂类型,请向参数添加 [FromUri] 特性。 下面的示例定义 GeoPoint 类型,以及从 URI 获取 GeoPoint 的控制器方法。

    public class GeoPoint
    {
        public double Latitude { get; set; } 
        public double Longitude { get; set; }
    }
    
    public ValuesController : ApiController
    {
        public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
    }

    使用 [FromBody]

    若要强制 Web API 从请求正文读取简单类型,请向参数添加 [FromBody] 特性:

    public HttpResponseMessage Post([FromBody] string name) { ... }

    ASP.NET Web API 中的异常处理

    HttpResponseException

    如果 Web API 控制器引发未捕获的异常,则会发生什么情况? 默认情况下,大多数异常将转换为状态代码 500 内部服务器错误的 HTTP 响应。

    HttpResponseException类型是一种特殊情况。 此异常将返回异常构造函数中指定任何 HTTP 状态代码。 例如,以下方法将返回 404,找不到,如果id参数无效。

    public Product GetProduct(int id)
    {
        Product item = repository.Get(id);
        if (item == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return item;
    }

    更好地响应的控制,还可以构造的整个响应消息,并将其与包含HttpResponseException:

    public Product GetProduct(int id)
    {
        Product item = repository.Get(id);
        if (item == null)
        {
            var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
            {
                Content = new StringContent(string.Format("No product with ID = {0}", id)),
                ReasonPhrase = "Product ID Not Found"
            };
            throw new HttpResponseException(resp);
        }
        return item;
    }

    异常筛选器

    您可以自定义 Web API 通过编写处理异常的方式异常筛选器。 当控制器方法引发任何未处理的异常时执行异常筛选器,除了 HttpResponseException异常。 HttpResponseException类型是一种特殊情况,因为它专为返回的 HTTP 响应。

    异常筛选器实现System.Web.Http.Filters.IExceptionFilter接口。 编写异常筛选器的最简单方法是从派生System.Web.Http.Filters.ExceptionFilterAttribute类并重写OnException方法。

    下面是将转换的筛选器NotImplementedException异常转化为 HTTP 状态代码 501,未实现:

    namespace ProductStore.Filters
    {
        using System;
        using System.Net;
        using System.Net.Http;
        using System.Web.Http.Filters;
    
        public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute 
        {
            public override void OnException(HttpActionExecutedContext context)
            {
                if (context.Exception is NotImplementedException)
                {
                    context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
                }
            }
        }
    }

    响应的属性HttpActionExecutedContext对象包含将发送到客户端的 HTTP 响应消息。

    注册异常筛选器

    有几种方法来注册 Web API 异常筛选器:

    • 由Action
    • 由控制器
    • 全局

    筛选器应用到特定的Action,请将作为属性的筛选器添加到操作:

    public class ProductsController : ApiController
    {
        [NotImplExceptionFilter]
        public Contact GetContact(int id)
        {
            throw new NotImplementedException("This method is not implemented");
        }
    }

    筛选器应用到所有控制器上的操作,将筛选器作为属性添加到控制器类:

    [NotImplExceptionFilter]
    public class ProductsController : ApiController
    {
        // ...
    }

    若要对 Web API 的所有控制器全局应用筛选器,将添加到筛选器的实例GlobalConfiguration.Configuration.Filters集合。 在此集合中的异常筛选器适用于任何 Web API 控制器操作。

    GlobalConfiguration.Configuration.Filters.Add(
        new ProductStore.NotImplExceptionFilterAttribute());

    如果使用"ASP.NET MVC 4 Web 应用程序"项目模板来创建你的项目,将 Web API 配置代码内的放WebApiConfig类,该类在应用程序位于_开始文件夹:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute());
    
            // Other configuration code...
        }
    }

    HttpError

    HttpError对象提供一致的方法来响应正文中返回的错误信息。 下面的示例演示如何返回 HTTP 状态代码 404 (找不到) 与HttpError响应正文中。

    public HttpResponseMessage GetProduct(int id)
    {
        Product item = repository.Get(id);
        if (item == null)
        {
            var message = string.Format("Product with id = {0} not found", id);
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
        }
        else
        {
            return Request.CreateResponse(HttpStatusCode.OK, item);
        }
    }

    CreateErrorResponse是定义在System.Net.Http.HttpRequestMessageExtensions类里的扩展方法。 在内部, CreateErrorResponse创建HttpError实例,然后创建HttpResponseMessage ,其中包含HttpError.

    HttpError 和模型验证

    public HttpResponseMessage PostProduct(Product item)
    {
        if (!ModelState.IsValid)
        {
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }
    
        // Implementation not shown...
    }

    此示例中可能会返回以下响应:

    HTTP/1.1 400 Bad Request
    Content-Type: application/json; charset=utf-8
    Content-Length: 320
    
    {
      "Message": "The request is invalid.",
      "ModelState": {
        "item": [
          "Required property 'Name' not found in JSON. Path '', line 1, position 14."
        ],
        "item.Name": [
          "The Name field is required."
        ],
        "item.Price": [
          "The field Price must be between 0 and 999."
        ]
      }
    }

    使用 HttpResponseException HttpError

    前面的示例返回HttpResponseMessage消息从控制器操作,但您还可以使用HttpResponseException返回HttpError。 这样就可以返回强类型化模型中正常成功的情况下,同时仍返回HttpError如果出现错误:

    public Product GetProduct(int id)
    {
        Product item = repository.Get(id);
        if (item == null)
        {
            var message = string.Format("Product with id = {0} not found", id);
            throw new HttpResponseException(
                Request.CreateErrorResponse(HttpStatusCode.NotFound, message));
        }
        else
        {
            return item;
        }
    }

    全局错误处理 ASP.NET Web API 2 中

    如今,在Web API中没有简单的方式记录或处理全局错误。可以通过异常筛选器过滤某些未经处理的异常,但也有一部分异常筛选器不能处理。例如:

    1. 从控制器的构造函数引发的异常。
    2. 从消息处理程序引发的异常。
    3. 在路由期间引发的异常。
    4. 响应内容序列化期间引发的异常。

    我们可以使用异常筛选器,消息处理程序(后面会提到),但是它们都很难处理上面的问题,所以微软提供了两个类:IExceptionLogger和IExceptionHandler。

    我们该怎么使用:

    • IExceptionLogger用于查看WebAPI捕获到的所有未经过处理的异常。
    • IExceptionHandler用于自定义所有可能的响应未经处理WebAPI捕获的异常。
    • IExceptionFilter是Controller或者Action下捕捉未经处理的异常的最简单的解决方案。
    public interface IExceptionLogger
    {
       Task LogAsync(ExceptionLoggerContext context, 
                     CancellationToken cancellationToken);
    }
    
    public interface IExceptionHandler
    {
       Task HandleAsync(ExceptionHandlerContext context, 
                        CancellationToken cancellationToken);
    }

     

  • 相关阅读:
    这几天的读书心得
    随机接入过程的步骤
    从tlb,ocx,dll类型库中提取com组件的CLSID
    unicode字符集下CStdioFile无法写入中文的解决方案
    多线程CString参数传递问题
    yii2.0 报错Cookievalidationkey Must Be Configured With A Secret Key
    windows下的phpunit安装
    xdebug 一直报错 upstream timed out (110: Connection timed out) while reading response header from upstream
    session与cookie
    【转】LVS负载均衡之session解决方案 持久连接
  • 原文地址:https://www.cnblogs.com/niye/p/9365401.html
Copyright © 2011-2022 走看看