我是伊只雄熊

导航

ASP.NET Web API 框架研究 Self Host模式下的消息处理管道

  Self Host模式下的ASP.NET Web API与WCF非常相似,都可以寄宿在任意类型的托管应用程序中,宿主可以是Windows Form 、WPF、控制台应用以及Windows Service,寄宿的请求的监听、接收 和响应功能,是通过一个类型为HttpBinding的Binding对象创建的ChannelListener管道来完成的。

一、Binding绑定模型

  Binding用来创建处理和传输消息的信道栈,信道栈有一组Channel组成,Binding也由一组BindingElement组成,每个BindingElement会创建一个ChannelListener,ChannelListener再创建对应的Channel,而每个Channel负责处理消息的单独一块功能,Binding启动时候会创建多个Channel组成一个消息处理管道,对请求依次进行处理,对相应按相反方向进行处理,有点类似ASP.NET Web API的消息处理管道,而这是宿主内部的功能,要注意区分。

  对于这种由Binding创建的多个Channel组成的消息处理管道的消息处理能力,是由其包含的Channel决定的,有两种Channel是必不可少的,TransportChannel和MessageEncodingChanneI:

  • TransportChannel  面向传输层用于发送和接收消息
  • MessageEncodingChanneI 负责对接收(请求)的消息实施解码 ,并对发送(响应)的消息实施编码

二、涉及的类及源码分析

  类主要在程序集System.Web.Http SeIfHost.dII中。

   

  

1、HttpBinding

  继承自Binding,其只由两个BindingElement组成,Http(s)TransportBindingElement和HttpMessageEncodingBindingElement

  Http(s)TransportBindingElement  决定最终采用的传输协议,Http或Https

  HttpMessageEncodingBindingElement  创 建一个MessageEncoder对象完成针对消息的编码/解码工作 。

  public class  HttpBinding : Binding, IBindingRuntimePreferences
  {
    internal const string CollectionElementName = "httpBinding";

    //默认传输模式为Buffered
    internal const  TransferMode DefaultTransferMode = System.ServiceModel.TransferMode.Buffered;
    //传输BindingElement 
    private HttpsTransportBindingElement _httpsTransportBindingElement;
    private HttpTransportBindingElement _httpTransportBindingElement;
    private HttpBindingSecurity _security;

    //编码/解码BindingElement
    private HttpMessageEncodingBindingElement _httpMessageEncodingBindingElement;
    private Action<HttpTransportBindingElement> _configureTransportBindingElement;

    //初始化HttpBinding 
    public HttpBinding()
    {
      Initialize();
    }

    public HttpBinding(HttpBindingSecurityMode securityMode)
    : this()
    {
      _security.Mode = securityMode;
    }

    //从URL中确定主机名的比较模式

    public  HostNameComparisonMode HostNameComparisonMode

    {
      get { return _httpTransportBindingElement.HostNameComparisonMode; }

      set
      {
        _httpTransportBindingElement.HostNameComparisonMode = value;
        _httpsTransportBindingElement.HostNameComparisonMode = value;
     }
    }

 
    public long MaxBufferPoolSize
    {
      get { return _httpTransportBindingElement.MaxBufferPoolSize; }

      set
      {
        _httpTransportBindingElement.MaxBufferPoolSize = value;
        _httpsTransportBindingElement.MaxBufferPoolSize = value;
      }
    }

    //Buffered模式下的消息的最大缓冲区大小,Buffered模式,即消息先会保存于内存缓冲区后一并传输
    [DefaultValue(TransportDefaults.MaxBufferSize)]
    public int MaxBufferSize
    {
      get { return _httpTransportBindingElement.MaxBufferSize; }

      set
      {
        _httpTransportBindingElement.MaxBufferSize = value;
        _httpsTransportBindingElement.MaxBufferSize = value;
      }
    }

    //请求消息的最大尺寸,默认为65536
    [DefaultValue(TransportDefaults.MaxReceivedMessageSize)]
    public long MaxReceivedMessageSize
    {
      get { return _httpTransportBindingElement.MaxReceivedMessageSize; }

      set
      {
        _httpTransportBindingElement.MaxReceivedMessageSize = value;
        _httpsTransportBindingElement.MaxReceivedMessageSize = value;
      }
    }


    public Action<HttpTransportBindingElement> ConfigureTransportBindingElement
    {
      get { return _configureTransportBindingElement; }

      set
      {
        if (value == null)
        {
          throw Error.PropertyNull();
        }

        _configureTransportBindingElement = value;
      }
    }

    //传输模式

    [DefaultValue(HttpTransportDefaults.TransferMode)]
    public TransferMode TransferMode
    {
      get { return _httpTransportBindingElement.TransferMode; }

      set
      {
        _httpTransportBindingElement.TransferMode = value;
        _httpsTransportBindingElement.TransferMode = value;
      }
    }

    //创建BindingElements,只包含两种
    public override BindingElementCollection CreateBindingElements()
    {
      BindingElementCollection bindingElements = new BindingElementCollection();

      bindingElements.Add(_httpMessageEncodingBindingElement);
      bindingElements.Add(GetTransport());

      return bindingElements.Clone();
    }

    private TransportBindingElement GetTransport()
    {
      HttpTransportBindingElement result = null;

      if (_security.Mode == HttpBindingSecurityMode.Transport)
      {
        _security.Transport.ConfigureTransportProtectionAndAuthentication(_httpsTransportBindingElement);
        result = _httpsTransportBindingElement;
      }
      else if (_security.Mode == HttpBindingSecurityMode.TransportCredentialOnly)
      {
        _security.Transport.ConfigureTransportAuthentication(_httpTransportBindingElement);
        result = _httpTransportBindingElement;
      }
      else
      {
        _security.Transport.DisableTransportAuthentication(_httpTransportBindingElement);
        result = _httpTransportBindingElement;
      }

      if (_configureTransportBindingElement != null)
      {
        _configureTransportBindingElement(result);
      }

      return result;
    }

    //初始化各种对象

    private void Initialize()
    {
      _security = new  HttpBindingSecurity();

      _httpTransportBindingElement = new HttpTransportBindingElement();
      _httpTransportBindingElement.ManualAddressing = true;

      _httpsTransportBindingElement = new HttpsTransportBindingElement();
      _httpsTransportBindingElement.ManualAddressing = true;

      _httpMessageEncodingBindingElement = new HttpMessageEncodingBindingElement();
    }
  }

2、HttpMessage

  Binding处理管道中处理的消息是HttpMessage,其继承自Message,它是对ASP.NET Web API处理的消息HttpRequestMessage和HttpResponseMessage的封装;HttpMessageEncoder对请求消息解码后得到HttpMessage对象,其会转成—个HttpRequestMessage对象并传入ASP.NET WebAPI消息处理管道进行处理,处理完后返回HttpResponseMessage对象,其被封装成HttpMessage对象,在通过传输层将响应返回给客户端之前,需要利用HttpMessageEncoder对其进行编码,然后进行传输。

  internal sealed class  HttpMessage : Message
  {
    private HttpRequestMessage _request;
    private HttpResponseMessage _response;
    private MessageHeaders _headers;
    private MessageProperties _properties;

    //请求对象为参数,进行封装

    public HttpMessage(HttpRequestMessage request)
    {
      Contract.Assert(request != null, "The 'request' parameter should not be null.");
      _request = request;
      Headers.To = request.RequestUri;
      IsRequest = true;
    }

    //响应对象为参数,进行封装  

    public HttpMessage(HttpResponseMessage response)
    {
      Contract.Assert(response != null, "The 'response' parameter should not be null.");
      _response = response;
      IsRequest = false;
    }

    public override MessageVersion Version
    {
      get
      {
        EnsureNotDisposed();
        return MessageVersion.None;
      }
    }

    public override MessageHeaders Headers
    {
      get
      {
        EnsureNotDisposed();
        if (_headers == null)
        {
          _headers = new MessageHeaders(MessageVersion.None);
        }

        return _headers;
      }
    }

    public override MessageProperties Properties
    {
      get
      {
        EnsureNotDisposed();
        if (_properties == null)
        {
          _properties = new MessageProperties();
          _properties.AllowOutputBatching = false;
        }

        return _properties;
      }
    }

    public override bool IsEmpty
    {
      get
      {
        long? contentLength = GetHttpContentLength();
        return contentLength.HasValue && contentLength.Value == 0;
      }
    }

    public override bool IsFault
    {
      get { return false; }
    }

    public bool IsRequest { get; private set; }

    //从HttpMessage中获取HttpRequestMessage,参数extract是否是抽取,抽取即访问一次后第二次访问会返回null

    public HttpRequestMessage GetHttpRequestMessage(bool extract)
    {
      EnsureNotDisposed();
      Contract.Assert(IsRequest, "This method should only be called when IsRequest is true.");
      if (extract)
      {
        HttpRequestMessage req = _request;

        //设置为null,第二次访问为null
        _request = null;
        return req;
      }

      return _request;
    }

    //从HttpMessage中获取HttpResponseMessage ,参数extract是否是抽取,抽取即访问一次后第二次访问会返回null

    public HttpResponseMessage GetHttpResponseMessage(bool extract)
    {
      EnsureNotDisposed();
      Contract.Assert(!IsRequest, "This method should only be called when IsRequest is false.");
      if (extract)
      {
        HttpResponseMessage res = _response;

        //设置为null,第二次访问为null
        _response = null;
        return res;
      }

      return _response;
    }

    protected override void OnClose()
    {
      base.OnClose();
      if (_request != null)
      {
        _request.DisposeRequestResources();
        _request.Dispose();
        _request = null;
      }

      if (_response != null)
      {
        _response.Dispose();
        _response = null;
      }
    }

    private static string GetNotSupportedMessage()
    {
      return Error.Format(
      SRResources.MessageReadWriteCopyNotSupported,
      HttpMessageExtensions.ToHttpRequestMessageMethodName,
      HttpMessageExtensions.ToHttpResponseMessageMethodName,
      typeof(HttpMessage).Name);
    }

    private void EnsureNotDisposed()
    {
      if (IsDisposed)
      {
        throw Error.ObjectDisposed(SRResources.MessageClosed, typeof(Message).Name);
      }
    }

    private long? GetHttpContentLength()
    {
      HttpContent content = IsRequest
      ? GetHttpRequestMessage(false).Content
      : GetHttpResponseMessage(false).Content;

      if (content == null)
      {
        return 0;
      }

      return content.Headers.ContentLength;
    }
  }

3、HttpSelfHostServer

  继承自HttpServer,是WebAPI 消息处理管道的第一个处理器,类似Web Host模式,管道的配置是通过HttpConfiguration完成,其对应由HttpSelfHostConfiguration来完成,也是在构造函数里指定。

   重要逻辑都在代码注释里,代码太多,只是拿出重要的代码,以下是注意点:

  • 虽然继承HttpServer,但是没有重写SendAsync方法,只是重用了HttpServer的SendAsync,所以这部分逻辑是一致的
  • HttpSelfHostServer本身会打开和开启channel来监听,监听到消息后,会创建HttpRequestMessage,并调用基类HttpServer的SendAsync,进行后续的消息处理,返回响应HttpResponseMessage后,转换成Message返回给客户端。

  public sealed class  HttpSelfHostServer : HttpServer
  {
    private ConcurrentBag<IReplyChannel> _channels = new ConcurrentBag<IReplyChannel>();
    private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

    private bool _disposed;
    private HttpSelfHostConfiguration _configuration;
    private IChannelListener<IReplyChannel> _listener;

    private readonly object _windowSizeLock = new object();

    //调用基类HttpServer构造函数,所以最有一个处理器默认也是HttpRoutingDispatcher
    public HttpSelfHostServer(HttpSelfHostConfiguration configuration)
    : base(configuration)
    {
      if (configuration == null)
      {
        throw Error.ArgumentNull("configuration");
      }

      _configuration = configuration;
      InitializeCallbacks();
    }

    //初始化回调,接受到消息后,通过回调触发
    private void InitializeCallbacks()
    {
      //...
      _onReceiveRequestContextComplete = new AsyncCallback(OnReceiveRequestContextComplete);
      _onReplyComplete = new AsyncCallback(OnReplyComplete);

    }

    //打开服务
    public Task OpenAsync()
    {
      if (Interlocked.CompareExchange(ref _state, 1, 0) == 1)
      {
        throw Error.InvalidOperation(SRResources.HttpServerAlreadyRunning, typeof(HttpSelfHostServer).Name);
      }

      _openTaskCompletionSource = new TaskCompletionSource<bool>();
      BeginOpenListener(this);
      return _openTaskCompletionSource.Task;
    }

    private void BeginOpenListener(HttpSelfHostServer server)
    {
      Contract.Assert(server != null);

      try
      {
        // 创建 WCF HTTP transport channel
        HttpBinding binding = new HttpBinding();

        // 获取配置(从HttpSelfHostConfiguration ),并设置到HttpBinding 
        BindingParameterCollection bindingParameters = server._configuration.ConfigureBinding(binding);
        if (bindingParameters == null)
        {
          bindingParameters = new BindingParameterCollection();
        }

        // 创建channel listener
        server._listener = binding.BuildChannelListener<IReplyChannel>(server._configuration.BaseAddress, bindingParameters);
        if (server._listener == null)
        {
          throw Error.InvalidOperation(SRResources.InvalidChannelListener, typeof(IChannelListener).Name, typeof(IReplyChannel).Name);
        }

        //开始监听

        IAsyncResult result = server._listener.BeginOpen(_onOpenListenerComplete, server);
        if (result.CompletedSynchronously)
        {

          //监听到请求消息,触发回调函数
          OpenListenerComplete(result);
        }
      }
      catch (Exception e)
      {
        FaultTask(server._openTaskCompletionSource, e);
      }
    }

    //..省略各种回调

    //Channel接收到消息后,通过回调最后会调用此方法
    private async void ProcessRequestContext(ChannelContext channelContext, RequestContext requestContext)
    {
      Contract.Assert(channelContext != null);
      Contract.Assert(requestContext != null);

      //调用下边的核心方法SendAsync

      HttpResponseMessage response = await SendAsync(channelContext, requestContext);

      //异步调用处理完后返回响应消息,把响应消息转换成Message 
      Message reply = response.ToMessage();

      //传回给客户端
      BeginReply(new ReplyContext(channelContext, requestContext, reply));
    }

    //核心方法,注意没有重写(override)基类SendAsync

    private async  Task<HttpResponseMessage> SendAsync(ChannelContext channelContext, RequestContext requestContext)
    {
      HttpRequestMessage request = null;
      try
      {
        request = CreateHttpRequestMessage(requestContext);
      }
      catch
      {
        return new HttpResponseMessage(HttpStatusCode.BadRequest);
      }

      try
      {

        //SendAsync方法是HttpServer的SendAsync,因为本类HttpSelfHostServer没有override该方法,

        //所以其他逻辑,比如建立Web API消息处理管道等,都和HttpServer一样
        HttpResponseMessage response = await channelContext.Server.SendAsync(request, channelContext.Server._cancellationTokenSource.Token);

        if (response == null)
        {
          response = request.CreateResponse(HttpStatusCode.InternalServerError);
        }

        return response;
      }
      catch (OperationCanceledException operationCanceledException)
      {
        return request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, SRResources.RequestCancelled, operationCanceledException);
      }
    }

    //构建HttpRequestMessage 

    private  HttpRequestMessage CreateHttpRequestMessage(RequestContext requestContext)
    {
      // 从HTTP请求中获取 WCF Message
      HttpRequestMessage request = requestContext.RequestMessage.ToHttpRequestMessage();
      if (request == null)
      {
        throw Error.InvalidOperation(SRResources.HttpMessageHandlerInvalidMessage, requestContext.RequestMessage.GetType());
      }

      // 创建windows授权的 principal 信息并添加进请求HttpRequestMessage 
      SetCurrentPrincipal(request);

      HttpRequestContext httpRequestContext = new SelfHostHttpRequestContext(requestContext, _configuration,
      request);
      request.SetRequestContext(httpRequestContext);

      // 添加查询客户端证书委托到属性字典中,以便以后可以查询
      request.Properties.Add(HttpPropertyKeys.RetrieveClientCertificateDelegateKey, _retrieveClientCertificate);

      // 添加表示是否是本地请求信息到请求的属性字典中
      request.Properties.Add(HttpPropertyKeys.IsLocalKey, new Lazy<bool>(() => IsLocal(requestContext.RequestMessage)));
      return request;
    }

    protected override void Dispose(bool disposing)
    {
      if (!_disposed)
      {
        _disposed = true;
        if (_cancellationTokenSource != null)
        {
          _cancellationTokenSource.Dispose();
          _cancellationTokenSource = null;
        }
      }

      base.Dispose(disposing);
    }

    private static void SetCurrentPrincipal(HttpRequestMessage request)
    {
      SecurityMessageProperty property = request.GetSecurityMessageProperty();
      if (property != null)
      {
        ServiceSecurityContext context = property.ServiceSecurityContext;
        if (context != null && context.PrimaryIdentity != null)
        {
          WindowsIdentity windowsIdentity = context.PrimaryIdentity as WindowsIdentity;

          if (windowsIdentity != null)
          {

            //设置Thread.CurrentPrincipal 为WindowsPrincipal
            Thread.CurrentPrincipal = new WindowsPrincipal(windowsIdentity);
          }
        }
      }

    }
  }

4、HttpSelfHostConfiguration

  继承自HttpConfiguration,构造函数中指定一个Uri作为监听基地址,Self Host模式下,请求的监听、接收、响应基本都是通过HttpBinding完成的,HttpSelfHostConfiguration的大部分属性都是用于对创建HttpBinding进行配置,所以它们的属性基本相同。

  public class  HttpSelfHostConfiguration : HttpConfiguration
  {
    public HttpSelfHostConfiguration(string baseAddress)
    : this(CreateBaseAddress(baseAddress))
    {
    }

    //基地址Uri
    public Uri BaseAddress
    {
      get { return _baseAddress; }
    }

    //最大请求并发量,默认值是100,若是多处理器,要cheny乘以处理器个数
    public int MaxConcurrentRequests
    {
      get { return _maxConcurrentRequests; }

      set
      {
        if (value < MinConcurrentRequests)
        {
          throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinConcurrentRequests);
        }
        _maxConcurrentRequests = value;
      }
    }

    //消息传输模式,分Streamed和Buffered(默认)
    public TransferMode TransferMode
    {
      get { return _transferMode; }

      set
      {
        TransferModeHelper.Validate(value, "value");
        _transferMode = value;
      }
    }

    //从URI中获取主机名的匹配比较模式
    public HostNameComparisonMode HostNameComparisonMode
    {
      get { return _hostNameComparisonMode; }

      set
      {
        HostNameComparisonModeHelper.Validate(value, "value");
        _hostNameComparisonMode = value;
      }
    }

    //Buffered模式的最大缓冲池大小,默认值65536

    public int MaxBufferSize
    {
      get
      {
        if (_maxBufferSizeIsInitialized || TransferMode != TransferMode.Buffered)
        {
          return _maxBufferSize;
        }

        long maxReceivedMessageSize = MaxReceivedMessageSize;
        if (maxReceivedMessageSize > Int32.MaxValue)
        {
          return Int32.MaxValue;
        }
        return (int)maxReceivedMessageSize;
      }

      set
      {
        if (value < MinBufferSize)
        {
          throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinBufferSize);
        }
        _maxBufferSizeIsInitialized = true;
        _maxBufferSize = value;
      }
    }

    //允许请求消息的最大大小,默认为65536

    public long MaxReceivedMessageSize
    {
      get { return _maxReceivedMessageSize; }

      set
      {
        if (value < MinReceivedMessageSize)
        {
          throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinReceivedMessageSize);
        }
        _maxReceivedMessageSize = value;
      }
    }

    //接收请求消息的超时时间,默认为10分钟  

    public TimeSpan ReceiveTimeout
    {
      get { return _receiveTimeout; }

      set
      {
        if (value < TimeSpan.Zero)
        {
          throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, TimeSpan.Zero);
        }

        _receiveTimeout = value;
      }
    }

    //发送响应消息的超时时间,默认为1分钟  

    public TimeSpan SendTimeout
    {
      get { return _sendTimeout; }

      set
      {
        if (value < TimeSpan.Zero)
        {
          throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, TimeSpan.Zero);
        }

        _sendTimeout = value;
      }
    }

    //客户端采用的用户凭证类型
    public HttpClientCredentialType ClientCredentialType
    {
      get { return _clientCredentialType; }
      set { _clientCredentialType = value; }
    }

    //将配置应用到httpBinding
    internal BindingParameterCollection ConfigureBinding(HttpBinding httpBinding)
    {
      return OnConfigureBinding(httpBinding);
    }

    
    protected virtual BindingParameterCollection OnConfigureBinding(HttpBinding httpBinding)
    {
      if (httpBinding == null)
      {
        throw Error.ArgumentNull("httpBinding");
      }

      if (_clientCredentialType != HttpClientCredentialType.Basic && _credentials.UserNameAuthentication.CustomUserNamePasswordValidator != null)
      {
        throw Error.InvalidOperation(SRResources.CannotUseOtherClientCredentialTypeWithUserNamePasswordValidator);
      }

      if (_clientCredentialType != HttpClientCredentialType.Certificate && _credentials.ClientCertificate.Authentication.CustomCertificateValidator != null)
      {
        throw Error.InvalidOperation(SRResources.CannotUseOtherClientCredentialTypeWithX509CertificateValidator);
      }

      httpBinding.MaxBufferSize = MaxBufferSize;
      httpBinding.MaxReceivedMessageSize = MaxReceivedMessageSize;
      httpBinding.TransferMode = TransferMode;
      httpBinding.HostNameComparisonMode = HostNameComparisonMode;
      httpBinding.ReceiveTimeout = ReceiveTimeout;
      httpBinding.SendTimeout = SendTimeout;

      if (_baseAddress.Scheme == Uri.UriSchemeHttps)
      {

        httpBinding.Security = new HttpBindingSecurity()
        {
          Mode = HttpBindingSecurityMode.Transport,
        };
      }

      if (_clientCredentialType != HttpClientCredentialType.None)
      {
        if (httpBinding.Security == null || httpBinding.Security.Mode == HttpBindingSecurityMode.None)
        {
          // Basic over HTTP case
          httpBinding.Security = new HttpBindingSecurity()
          {
            Mode = HttpBindingSecurityMode.TransportCredentialOnly,
          };
        }

        httpBinding.Security.Transport.ClientCredentialType = _clientCredentialType;
      }

      if (UserNamePasswordValidator != null || X509CertificateValidator != null)
      {
        // those are the only two things that affect service credentials
        return AddCredentialsToBindingParameters();
      }
      else
      {
        return null;
      }
    }

    private BindingParameterCollection AddCredentialsToBindingParameters()
    {
      BindingParameterCollection bindingParameters = new BindingParameterCollection();
      bindingParameters.Add(_credentials);
      return bindingParameters;
    }

    //根据基地址创建对应Uri

    private static Uri CreateBaseAddress(string baseAddress)
    {
      if (baseAddress == null)
      {
        throw Error.ArgumentNull("baseAddress");
      }

      return new Uri(baseAddress, UriKind.RelativeOrAbsolute);
    }
  }

三、HttpBiding、HttpSelfHostServer和消息处理管道的衔接

  先根据指定的监听基地址创建一个HttpSelftHostConfiguration对象,然后,根据它创建HttpSelfHostServer,调用OpenAsync方法开启时候,HttpSelfHostServer会创建一个HttpBinding,并用指定的HttpSelfHostConfiguration对HttpBinding进行配置,然后,HttpBinding会根据监听基地址创建一个ChannelListener管道,对请求进行监听,请求到达时候,接收的二进制数据会经过解码后生成HttpMessage,其是对HttpRequestMessage的封装,然后,HttpSelfHostServer会从该HttpMessage提取出HttpRequestMessage,传递给WebAPI消息处理管道的其他处理器依次处理,处理完返回一个HttpResponseMessage对象,把其封装成HttpMessage,接着对其进行编码,通过传输层进行传输,返回给客户端。

  另外,特别注意的是,Web API消息处理管道的最后一个消息处理器还是HttpRoutingDispatcher,其在HttpSelfHostServer创建时候,调用基类HttpServer的构造函数时候指定,而且在HttpRoutingDispatcher路由时候,由于路由数据没在HttpRequestMessage的属性字典中,所以要直接进行路由解析,获得的路由数据也会放在HttpRequestMessage的属性字典中,所以,后续的Controller创建等操作需要的路由数据都是从HttpRequestMessage的属性字典中获取。

  

posted on 2017-12-17 16:40  我是伊只雄熊  阅读(677)  评论(0编辑  收藏  举报