抛出HttpResponseException或返回Request.CreateErrorResponse吗?


172

在阅读了ASP.NET Web API中的异常处理文章之后我对何时引发异常与返回错误响应感到困惑。我还想知道当您的方法返回特定于域的模型而不是HttpResponseMessage... 时是否可以修改响应...

因此,回顾一下我的问题,后面是一些带有案例编号的代码:

问题

有关案例1的问题

  1. 我是否应该始终使用HttpResponseMessage而不是具体的域模型,以便可以自定义消息?
  2. 如果要返回具体域模型,可以自定义消息吗?

有关案例#2、3、4的问题

  1. 我应该抛出异常还是返回错误响应?如果答案是“视情况而定”,那么您能否举例说明何时使用另一种方法。
  2. HttpResponseExceptionvs与thing有Request.CreateErrorResponse什么区别?输出到客户端似乎相同...
  3. 我是否应该总是使用HttpError“包装”错误中的响应消息(是否抛出异常或返回错误响应)?

案例样本

// CASE #1
public Customer Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    //var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    //response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return customer;
}        

// CASE #2
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #3
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
        throw new HttpResponseException(errorResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #4
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var httpError = new HttpError(message);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

更新资料

为了进一步说明案例#2、3、4,以下代码段突出显示了在找不到客户时“可能发生”的几个选项...

if (customer == null)
{
    // which of these 4 options is the best strategy for Web API?

    // option 1 (throw)
    var notFoundMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
    throw new HttpResponseException(notFoundMessage);

    // option 2 (throw w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    throw new HttpResponseException(errorResponse);

    // option 3 (return)
    var message = String.Format("Customer with id: {0} was not found", id);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    // option 4 (return w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
}

6
@Mike Wasson作为链接文章的作者,您将采用哪种方法?
zam6ak 2012年

Answers:


102

我采用的方法是仅从api控制器操作中引发异常,并注册一个异常过滤器来处理异常并在操作执行上下文上设置适当的响应。

该过滤器公开了一个流畅的接口,该接口提供了一种在使用全局配置注册过滤器之前为特定类型的异常注册处理程序的方法。

使用此过滤器可实现集中式异常处理,而不是将其分散在控制器操作中。但是,在某些情况下,如果集中处理特定异常没有意义,我将在控制器动作中捕获异常并返回特定响应。

过滤器注册示例:

GlobalConfiguration.Configuration.Filters.Add(
    new UnhandledExceptionFilterAttribute()
    .Register<KeyNotFoundException>(HttpStatusCode.NotFound)

    .Register<SecurityException>(HttpStatusCode.Forbidden)

    .Register<SqlException>(
        (exception, request) =>
        {
            var sqlException = exception as SqlException;

            if (sqlException.Number > 50000)
            {
                var response            = request.CreateResponse(HttpStatusCode.BadRequest);
                response.ReasonPhrase   = sqlException.Message.Replace(Environment.NewLine, String.Empty);

                return response;
            }
            else
            {
                return request.CreateResponse(HttpStatusCode.InternalServerError);
            }
        }
    )
);

UnhandledExceptionFilterAttribute类:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http.Filters;

namespace Sample
{
    /// <summary>
    /// Represents the an attribute that provides a filter for unhandled exceptions.
    /// </summary>
    public class UnhandledExceptionFilterAttribute : ExceptionFilterAttribute
    {
        #region UnhandledExceptionFilterAttribute()
        /// <summary>
        /// Initializes a new instance of the <see cref="UnhandledExceptionFilterAttribute"/> class.
        /// </summary>
        public UnhandledExceptionFilterAttribute() : base()
        {

        }
        #endregion

        #region DefaultHandler
        /// <summary>
        /// Gets a delegate method that returns an <see cref="HttpResponseMessage"/> 
        /// that describes the supplied exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, HttpRequestMessage, HttpResponseMessage}"/> delegate method that returns 
        /// an <see cref="HttpResponseMessage"/> that describes the supplied exception.
        /// </value>
        private static Func<Exception, HttpRequestMessage, HttpResponseMessage> DefaultHandler = (exception, request) =>
        {
            if(exception == null)
            {
                return null;
            }

            var response            = request.CreateResponse<string>(
                HttpStatusCode.InternalServerError, GetContentOf(exception)
            );
            response.ReasonPhrase   = exception.Message.Replace(Environment.NewLine, String.Empty);

            return response;
        };
        #endregion

        #region GetContentOf
        /// <summary>
        /// Gets a delegate method that extracts information from the specified exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, String}"/> delegate method that extracts information 
        /// from the specified exception.
        /// </value>
        private static Func<Exception, string> GetContentOf = (exception) =>
        {
            if (exception == null)
            {
                return String.Empty;
            }

            var result  = new StringBuilder();

            result.AppendLine(exception.Message);
            result.AppendLine();

            Exception innerException = exception.InnerException;
            while (innerException != null)
            {
                result.AppendLine(innerException.Message);
                result.AppendLine();
                innerException = innerException.InnerException;
            }

            #if DEBUG
            result.AppendLine(exception.StackTrace);
            #endif

            return result.ToString();
        };
        #endregion

        #region Handlers
        /// <summary>
        /// Gets the exception handlers registered with this filter.
        /// </summary>
        /// <value>
        /// A <see cref="ConcurrentDictionary{Type, Tuple}"/> collection that contains 
        /// the exception handlers registered with this filter.
        /// </value>
        protected ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> Handlers
        {
            get
            {
                return _filterHandlers;
            }
        }
        private readonly ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> _filterHandlers = new ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>();
        #endregion

        #region OnException(HttpActionExecutedContext actionExecutedContext)
        /// <summary>
        /// Raises the exception event.
        /// </summary>
        /// <param name="actionExecutedContext">The context for the action.</param>
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if(actionExecutedContext == null || actionExecutedContext.Exception == null)
            {
                return;
            }

            var type    = actionExecutedContext.Exception.GetType();

            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

            if (this.Handlers.TryGetValue(type, out registration))
            {
                var statusCode  = registration.Item1;
                var handler     = registration.Item2;

                var response    = handler(
                    actionExecutedContext.Exception.GetBaseException(), 
                    actionExecutedContext.Request
                );

                // Use registered status code if available
                if (statusCode.HasValue)
                {
                    response.StatusCode = statusCode.Value;
                }

                actionExecutedContext.Response  = response;
            }
            else
            {
                // If no exception handler registered for the exception type, fallback to default handler
                actionExecutedContext.Response  = DefaultHandler(
                    actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
                );
            }
        }
        #endregion

        #region Register<TException>(HttpStatusCode statusCode)
        /// <summary>
        /// Registers an exception handler that returns the specified status code for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register a handler for.</typeparam>
        /// <param name="statusCode">The HTTP status code to return for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler has been added.
        /// </returns>
        public UnhandledExceptionFilterAttribute Register<TException>(HttpStatusCode statusCode) 
            where TException : Exception
        {

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                statusCode, DefaultHandler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
        /// <summary>
        /// Registers the specified exception <paramref name="handler"/> for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register the <paramref name="handler"/> for.</typeparam>
        /// <param name="handler">The exception handler responsible for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception <paramref name="handler"/> 
        /// has been added.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="handler"/> is <see langword="null"/>.</exception>
        public UnhandledExceptionFilterAttribute Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler) 
            where TException : Exception
        {
            if(handler == null)
            {
              throw new ArgumentNullException("handler");
            }

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                null, handler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Unregister<TException>()
        /// <summary>
        /// Unregisters the exception handler for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to unregister handlers for.</typeparam>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler 
        /// for exceptions of type <typeparamref name="TException"/> has been removed.
        /// </returns>
        public UnhandledExceptionFilterAttribute Unregister<TException>()
            where TException : Exception
        {
            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> item = null;

            this.Handlers.TryRemove(typeof(TException), out item);

            return this;
        }
        #endregion
    }
}

源代码也可以在这里找到。


哇!:)对于较小的项目来说可能有点多,但是仍然非常好...顺便说一句,为什么在DefaultHandler中使用CreateResponse而不是CreateErrorResponse?
zam6ak 2012年

我试图将错误详细信息(在正文中序列化)与原因短语分开;但是您可以肯定地使用CreateErrorResponse(如果在模型绑定的情况下更有意义)。
反对党

1
由于您可以只用一行代码注册过滤器,因此我认为它几乎适用于任何项目类型。我们在内部NuGet提要上发布的类库中有过滤器,因此开发人员易于使用。
反对党2012年

您正在使用什么作为警卫人员(本地或第三方)?
zam6ak 2012年

横生,我在上面的示例中删除了它的使用。Guard类提供了一组静态方法,可以防止或验证是否已声明。如果要实现,请参见codepaste.net/5oc1if(卫队)和codepaste.net/nsrsei(DelegateInfo)。
反对党2012年

23

如果您不返回HttpResponseMessage而是直接返回实体/模型类,那么我发现有用的一种方法是将以下实用程序功能添加到控制器中

private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
    var errorResponse = Request.CreateErrorResponse(statusCode, message);
    throw new HttpResponseException(errorResponse);
}

并使用适当的状态代码和消息进行调用


4
这是正确的答案,它带有“消息”格式作为正文中的键值对。我通常会这样看待其他框架和语言来做到这一点
MobileMon 2015年

我对此方法有一个小问题。我正在angularJS页面中使用{{}}语法使用消息。如果我留下回车符,它们在消息中为n \ r \。保存它们的正确方法是什么?
Naomi

我尝试过这种方法。我做了throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")),但是在Fiddler中,它显示的状态为500(而不是400)。知道为什么吗?
山姆

区别在于错误的父函数。这是应用程序中任何异常的ThrowResponseException。但是应该是抛出异常的真实函数……
Serge

15

情况1

  1. 不一定,管道中还有其他地方可以修改响应(动作过滤器,消息处理程序)。
  2. 参见上文-但是,如果操作返回一个域模型,则无法在操作内部修改响应。

案例2-4

  1. 引发HttpResponseException的主要原因是:
    • 如果您要返回域模型,但需要处理错误情况,
    • 通过将错误视为异常来简化控制器逻辑
  2. 这些应该是等效的;HttpResponseException封装了HttpResponseMessage,该消息作为HTTP响应返回。

    例如,案例2可以改写为

    public HttpResponseMessage Get(string id)
    {
        HttpResponseMessage response;
        var customer = _customerService.GetById(id);
        if (customer == null)
        {
            response = new HttpResponseMessage(HttpStatusCode.NotFound);
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.OK, customer);
            response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
        }
        return response;
    }

    ...但是如果您的控制器逻辑更加复杂,则抛出异常可能会简化代码流。

  3. HttpError为响应主体提供一致的格式,并且可以序列化为JSON / XML / etc,但这不是必需的。例如,您可能不想在响应中包含实体,或者您可能需要其他格式。


我采用的方法是仅从api控制器操作中引发异常,并且我注册了一个异常过滤器,该过滤器处理异常并在操作执行上下文上设置适当的响应。过滤器是“可插入的”,这样我可以在使用全局配置注册过滤器之前为特定类型的异常注册处理程序。这使我可以进行集中式异常处理,而不是将其分散在各个控制器上。
反对党2012年

@Oppositional您是否愿意分享异常过滤器?可能是Gist还是在CodePaste之类的代码共享网站上?
Paige Cook

@Mike Wasson您会说“返回错误响应”是比“抛出异常”更常见的方法吗?我从功能上理解最终结果可能是相同的,但是我想知道为什么不只在try / catch中包含整个控制器逻辑,并在适当时返回错误响应?
zam6ak 2012年

15

不要抛出HttpResponseException或返回HttpResponesMessage错误- 除非,如果意图是结束请求那个确切的结果

HttpResponseException的处理方式与其他异常不同。它们没有被异常过滤器捕获。它们没有被捕获在Exception Handler中。它们是在终止当前代码的执行流时插入HttpResponseMessage的巧妙方法。

除非代码是依赖于此特殊处理的基础结构代码,否则请避免使用HttpResponseException类型!

HttpResponseMessage也不例外。它们不会终止当前代码的执行流程。它们不能被过滤为例外。它们不能作为例外记录。它们代表一个有效的结果-即使500响应也是“有效的非异常响应”!


让生活更简单:

当有一个特殊的/错误的情况下,继续并抛出一个正常的.NET异常-或定制的应用程序异常类型(从HttpResponseException导出)与期望的'http错误/响应的属性,诸如状态码-按照正常的异常处理

使用异常过滤器/异常处理程序/异常记录器对以下异常情况进行适当的处​​理:更改/添加状态代码?添加跟踪标识符?包括堆栈跟踪?日志?

通过避免HttpResponseException ,“例外情况”处理变得统一,可以作为公开管道的一部分来处理!例如,可以轻松统一地将“ NotFound”转换为404,将“ ArgumentException”转换为400,将“ NullReference”转换为500,并具有应用程序级异常-同时允许可扩展性提供错误日志等“基础”。


2
我了解为什么ArgumentException逻辑上控制器中的s为400,但是ArgumentException堆栈中更深的s呢?将它们转换为400不一定正确,但是如果您有一个将全部ArgumentExceptions 都转换为400 的过滤器,避免这种情况的唯一方法就是在控制器中捕获异常并重新抛出其他东西,这似乎从而无法在过滤器或类似程序中实现统一异常处理的目的。
cmeeren '17

@cmeeren在我处理的代码中,大多数代码都捕获了异常,并将其转换为每个Web方法中的HttpResponse [Exception / Message]。两种情况都是相同的,如果关注点是对内部异常执行不同的操作,即对捕获的内部异常执行“某些操作”:我建议结果是抛出一个适当的包装异常,该异常仍在处理上堆栈。
user2864740

@cmeeren更新后,我们的大多数Web入口点都会向使用错误抛出一个特殊的派生类(非HttpResponseException,它具有和或映射到适当的响应代码)。统一处理程序可以进行一些堆栈检查(棘手,但要谨慎工作),以确定异常来自哪个级别,即。涵盖了99%没有更精细处理的案例-或仅对内部错误回答500。HttpResponseException的症结在于它绕过了有用的管道处理。
user2864740

9

何时使用HttpResponseException代替Response.CreateResponse(HttpStatusCode.NotFound)或其他错误状态代码的另一种情况是,如果动作过滤器中有事务,并且您希望在将错误响应返回给客户端时回滚事务。

使用Response.CreateResponse不会回滚事务,而抛出异常会回滚。


3

我想指出的是,根据我的经验,如果在Webapi 2方法中抛出HttpResponseException而不是返回HttpResponseMessage,则如果立即调用IIS Express,它将超时或返回200,但在HTML中出现html错误。响应。测试此问题的最简单方法是对抛出HttpResponseException的方法进行$ .ajax调用,并在ajax中的errorCallBack中立即调用另一个方法,甚至是简单的http页面。您会注意到即时呼叫将失败。如果您在错误回调中添加断点或settimeout()来延迟第二次调用一到两秒,从而使服务器有时间恢复,则它可以正常工作。

更新:奇怪的Ajax连接超时的根本原因是,如果使用相同的tcp连接进行ajax调用的速度足够快。我通过返回HttpResonseMessage或抛出HTTPResponseException(引发返回到浏览器ajax调用)来引发401错误。但是,伴随该调用,MS返回了“未找到对象”错误,因为在Startup.Auth.vb应用程序中启用了UserCookieAuthentication,因此它试图返回拦截响应并添加重定向,但由于“对象不是对象实例”而出错。该错误是html,但事后添加到了响应中,因此仅当ajax调用足够快并且使用了相同的tcp连接时,它才会返回到浏览器,然后才添加到下一个调用的前面。由于某些原因,Chrome刚刚超时,提琴手因为json和htm的混合而变了,但是firefox取消了真正的错误。因此,怪异却是数据包嗅探器或Firefox是唯一跟踪此消息的方法。

还应注意,如果使用Web API帮助生成自动帮助,并且返回HttpResponseMessage,则应添加一个

[System.Web.Http.Description.ResponseType(typeof(CustomReturnedType))] 

属性的值,以便帮助能够正确生成。然后

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

或错误

return Request.CreateErrorResponse( System.Net.HttpStatusCode.InternalServerError, new Exception("An Error Ocurred"));

希望这对抛出HttpResponseException之后可能会随机超时或服务器不可用的其他用户有所帮助。

另外,返回HttpResponseException的另一个好处是,当返回的错误是需要在单页应用程序中刷新AuthToken时,不会导致Visual Studio在未处理的异常有用时中断。

更新:我正在撤回有关IIS Express超时的声明,这恰好是我的客户端ajax回调中的一个错误,事实证明这是因为Ajax 1.8返回$ .ajax()并返回$ .ajax。()。then()两者都返回promise,但不是相同的链式promise,那么then()返回一个新的promise,从而导致执行顺序错误。因此,当then()承诺完成时,这就是脚本超时。奇怪但不是IIS Express,在键盘和椅子之间出现了问题。



0

在错误情况下,我想返回特定的错误详细信息类,以客户端要求的任何格式而不是快乐路径对象的格式。

我想让我的控制器方法返回特定于域的快乐路径对象,否则抛出异常。

我遇到的问题是HttpResponseException构造函数不允许域对象。

这就是我最终想出的

public ProviderCollection GetProviders(string providerName)
{
   try
   {
      return _providerPresenter.GetProviders(providerName);
   }
   catch (BadInputValidationException badInputValidationException)
   {
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest,
                                          badInputValidationException.Result));
   }
}

Result是一个包含错误详细信息的类,而这ProviderCollection是我满意的结果。


0

我喜欢反对的答案

无论如何,我需要一种方法来捕获继承的Exception,并且该解决方案无法满足我的所有需求。

所以我最终改变了他处理OnException的方式,这是我的版本

public override void OnException(HttpActionExecutedContext actionExecutedContext) {
   if (actionExecutedContext == null || actionExecutedContext.Exception == null) {
      return;
   }

   var type = actionExecutedContext.Exception.GetType();

   Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

   if (!this.Handlers.TryGetValue(type, out registration)) {
      //tento di vedere se ho registrato qualche eccezione che eredita dal tipo di eccezione sollevata (in ordine di registrazione)
      foreach (var item in this.Handlers.Keys) {
         if (type.IsSubclassOf(item)) {
            registration = this.Handlers[item];
            break;
         }
      }
   }

   //se ho trovato un tipo compatibile, uso la sua gestione
   if (registration != null) {
      var statusCode = registration.Item1;
      var handler = registration.Item2;

      var response = handler(
         actionExecutedContext.Exception.GetBaseException(),
         actionExecutedContext.Request
      );

      // Use registered status code if available
      if (statusCode.HasValue) {
         response.StatusCode = statusCode.Value;
      }

      actionExecutedContext.Response = response;
   }
   else {
      // If no exception handler registered for the exception type, fallback to default handler
      actionExecutedContext.Response = DefaultHandler(actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
      );
   }
}

核心是此循环,在该循环中,我检查异常类型是否为已注册类型的子类。

foreach (var item in this.Handlers.Keys) {
    if (type.IsSubclassOf(item)) {
        registration = this.Handlers[item];
        break;
    }
}

my2cents

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.