WEB API 之 WEB-HOST


简介

C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies

我们在上面已经提到过了,虽然被命名为ASP.NET Web API,但是其核心的消息处理管道却是独立于ASP.NET平台的,所以我们可以对相同的Web API实施不同的寄宿方式。寄宿的本质就是利用一个具体的应用程序为Web API提供一个运行的环境,并最终解决“请求的接收和响应的回复”问题。作为寄宿的一种主要形式,Web Host就是创建一个ASP.NET Web应用作为Web API的宿主。

采用Web Host方式寄宿Web API的宿主程序WebHost是一个空的ASP.NET应用。除了让它引用定义ContactsController的WebApi项目之外,我们还需要为其添加如下这些必需的程序集引用。所需程序集均可以在目录“%ProgramFiles%\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\”中找到。

  • System.Web.Http.dll
  • System.Net.Formatting.Http.dll
  • System.Web.Http.WebHost.dll
  • System.Net.Http.dll

与ASP.NET MVC一样,如果采用Web Host的方式来寄宿Web API,ASP.NET自身的路由系统会成为接收请求的第一道屏障。在将请求递交给ASP.NET Web API自己的消息处理管道之前,路由系统会解析出当前请求访问的目标HttpController和Action的名称。我们需要做的就是根据需求注册相应的路由,这也是采用Web Host寄宿方式所需的唯一操作。

使用说明

我们在WebHost项目中添加一个Global.asax文件,并按照如下的形式在其Application_Start方法中注册了一个模板为“api/{controller}/{id}”的路由。此模板由3部分组成,静态文本“api”表示其前缀,后面是两个路由参数。前者({controller})表示目标HttpController的名称,后者({id})可以映射为目标Action方法的同名参数(比如ContractsController的Get方法的参数id),这是一个可以缺省的路由参数(RouteParameter.Optional)。

namespace MvcApplication5
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            GlobalConfiguration.Configuration.Routes.MapHttpRoute(
              name: "DefaultApi",
              routeTemplate: "api/{controller}/{id}",
              defaults: new { id = RouteParameter.Optional });
        }


    }
}

控制器(不限定命名空间):

namespace MvcApplication5.Controller
{
    public class ContactsController : ApiController
    {
        static List<Contact> contacts;
        static int counter = 2;

        static ContactsController()
        {
            contacts = new List<Contact>();
            contacts.Add(new Contact
            {
                Id = "001",
                Name = "张三",
                PhoneNo = "0510-12345678",
                EmailAddress = "zhangsan@outlook.com",
                Address = "江苏省无锡市新区"
            });
            contacts.Add(new Contact
            {
                Id = "002",
                Name = "李四",
                PhoneNo = "021-23456789",
                EmailAddress = "lisi@outlook.com",
                Address = "上海市浦东新区"
            });
        }

        public IEnumerable<Contact> Get(string id = null)
        {
            return from contact in contacts
                   where contact.Id == id || string.IsNullOrEmpty(id)
                   select contact;
        }

        public void Post(Contact contact)
        {
            Interlocked.Increment(ref counter);
            contact.Id = counter.ToString("D3");
            contacts.Add(contact);
        }

        public void Put(Contact contact)
        {
            contacts.Remove(contacts.First(c => c.Id == contact.Id));
            contacts.Add(contact);
        }

        public void Delete(string id)
        {
            contacts.Remove(contacts.First(c => c.Id == id));
        }
    }


    public class Contact
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string PhoneNo { get; set; }
        public string EmailAddress { get; set; }
        public string Address { get; set; }
    }
}

如上面的代码片断所示,路由注册是通过调用代表全局路由表的HttpRouteCollection对象的扩展方法MapHttpRoute来完成的。GlobalConfiguration的静态属性Configuration返回一个代表当前配置的HttpConfiguration对象,全局路由表就注册在它的Routes属性上。

如果你了解ASP.NET MVC的路由注册,可能觉得奇怪:注册路由的模板中并没有表示目标Action的路由参数,ASP .NET Web API如何根据请求确定哪个Action方法应该被调用呢?答案其实很简单:它能根据请求采用HTTP方法来确定目标Action方法。当然,在注册路由模板中提供代表Action名称的路由参数({action})也是支持的。

对于ASP.NET Web API来说,它会优先利用请求报头“Accept”携带的媒体类型来确定响应内容采用的表现形式。

IIS拒绝PUT和DELETE请求是由默认注册的一个名为“WebDAVModule”的自定义HttpModule导致的。WebDAV的全称为“Web-based Distributed Authoring and Versioning”,它是一个在多用户之间辅助协同编辑和管理在线文档的HTTP扩展。该扩展使应用程序可以直接将文件写到 Web Server 上,同时支持文件的加锁和版本控制。

微软是推动WebDAV成为一个标准的主导力量,它自己利用自定义的HttpModule实现了IIS针对WebDAV的支持。但是这个默认注册(注册名称为“WebDAVModule”)会拒绝HTTP方法为PUT和DELETE的请求,如果我们的站点不需要提供针对WebDAV的支持,解决这个问题最为直接的方式就是利用如下的配置将注册的HttpModule移除。

<?xml version="1.0" encoding="utf-8"?>

<!--
  有关如何配置 ASP.NET 应用程序的详细信息,请访问
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->

<configuration>
    <system.web>
      <compilation debug="true" targetFramework="4.0" />
    </system.web>

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule"/>
  </modules>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>

HttpClient 调用 API

    HttpClient client;

    public void GetDayEvents()
    {
        System.Threading.Thread.Sleep(5000);
        client = new HttpClient();
        client.BaseAddress = new Uri("http://127.0.0.1:3890/");
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        client.GetAsync("api/dayevents").ContinueWith((t) =>
        {
            if (t.IsFaulted)
            {
                IsLoading = Visibility.Collapsed;
                Console.WriteLine(t.Exception.Message);
            }
            else
            {
                var response = t.Result;
                if (response.IsSuccessStatusCode)
                {
                    response.Content.ReadAsStringAsync().ContinueWith(_ =>
                    {

                        var result = _.Result;
                        System.Diagnostics.Debug.WriteLine(result.ToString());
                        if (!string.IsNullOrWhiteSpace(result))
                        {
                            var jObject = JObject.Parse(result);
                            Console.WriteLine(jObject["data"].First);
                            var temp = from c in jObject["data"].Children()
                                       select JsonConvert.DeserializeObject<DayEvent>(c.ToString());

                            if (temp != null)
                            {
                                temp.ToList().ForEach(d =>
                                {
                                    Console.WriteLine(d.EventID);
                                });
                            }

                            Days = new ObservableCollection<DayModel>(GenerateDays(temp.ToList()));
                            IsLoading = Visibility.Collapsed;
                        }

                    });

                }
                else
                {
                    IsLoading = Visibility.Collapsed;
                    Console.WriteLine(response.StatusCode+response.ToString());
                }
            }
        }, System.Threading.Tasks.TaskScheduler.Current);

    }
站内公告