65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Web API 2.2:从头开始创建自托管的基于 OWIN 的 Web API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (33投票s)

2015年1月24日

CPOL

14分钟阅读

viewsIcon

148971

从头开始构建一个精简、最小化的 Web Api 应用程序,是熟悉 Web Api(或任何其他 ASP.NET)项目底层工作原理的绝佳方式。

 
 
 

Network-500从头开始构建一个精简、最小化的 Web Api 应用程序,是熟悉 Web Api(或任何其他 ASP.NET)项目底层工作原理的绝佳方式。

ASP.NET 团队提供了出色的项目模板,让开发人员可以轻松地开始构建 Web 应用程序。这些模板的结构提供了一种基本的样板功能,可以方便快捷地启动和运行。基础的应用程序架构已经就位,所有你可能需要的 Nuget 包和框架引用也都准备好了。

ImagebyIvan Emelianov  |  保留部分权利

这一切都很棒,但也带来了双重问题,特别是对于那些仍在学习 Web 开发,以及如何具体了解 ASP.NET MVC 和 Web Api 应用程序开发内部机制的人来说。

首先,VS 项目模板中展示的通用方法倾向于包含比任何单个应用程序实际需要的多得多的“东西”。为了提供足够的开箱即用功能,以帮助开发人员快速上手,并为各种基本应用程序需求提供起点,Visual Studio 中的模板带来了大量你的特定应用程序并不需要的基础设施和库。

其次,这些模板将完整的、可直接运行的应用程序组合在一起,使得很多事情看起来像是“魔法”般地在幕后发生,这可能让人很难理解这些独立的部件是如何协同工作的。当我们想要自定义应用程序、移除不需要的组件或采用不同的架构方法来构建应用程序时,这个问题就变得重要了。

注意: 在本文中,我们将从零开始构建一个简单的 Web Api 示例。这里的目标不仅仅是“给我代码”,更是要理解像 Web Api 这样的 ASP.NET 组件如何插入到 OWIN/Katana 环境中,以及各个应用程序组件之间是如何关联的。已经有很多示例展示了如何拼凑出一个自托管的 Web Api 应用程序、“Hello World”示例等等。在本文中,我们将寻求理解“为什么”和“怎么样”同等重要。

随着 ASP.NET 5 ("vNext") 越来越接近发布,理解这些组件如何协同工作以及中间件管道的概念将变得日益重要。虽然中间件管道本身的实现会随着即将发布的版本有所变化,但这些概念将更强烈、更普遍地适用于整个 ASP.NET 生态系统。

示例源代码

本文中使用的示例项目的源代码可以在我的 Github 仓库中找到。自托管的 Web Api 应用程序有两个分支,一个包含基本的 API 结构,另一个是在我们添加了 Entity Framework 和数据库之后的分支。

Web Api 与 OWIN 中间件管道

自 ASP.NET 4.5.1 起,Web Api 可以在 OWIN/Katana 环境中作为中间件使用。在之前的一篇文章中,我们探讨了 OWIN/Katana 中间件管道如何构成现代 ASP.NET Web 应用程序的支柱。

OWIN 规范在宿主进程、Web 服务器和 Web 应用程序之间建立了一个区分。IIS 与 ASP.NET 结合,同时扮演宿主进程和服务器的角色。System.WebSystem.Web这个库是一个庞大的、无所不包的库,它与 IIS 紧密耦合。依赖于System.WebSystem.Web组件的 Web 应用程序,例如 MVC(目前如此,直到 MVC 6 "vNext")和 Web Forms,同样也绑定于 IIS。

在标准的 ASP.NET Web Api 项目模板中,Web Api 被配置为 IIS/ASP.NET 处理管道的一部分,MVC 和大多数其他 ASP.NET 项目组件也是如此(Identity 2.0 是一个显著的例外,因为它在所有项目模板中默认使用 OWIN 管道)。然而,从 ASP.NET 4.5.1 开始,Web Api(以及 SignalR)也可以被配置为在 OWIN 管道中运行,从而摆脱了对 IIS 和庞大的System.WebSystem.Web库提供的基础设施的依赖。

在本文中,我们将把 Web Api 配置为一个轻量级的、基于 OWIN 的应用程序中的中间件组件,从而摆脱对重量级的 System.Web 的依赖。System.Web库提供的基础设施的依赖。

将应用程序组件插入 OWIN/Katana 管道

回顾我们之前的文章,那个描述 Katana 管道中中间件组件交互的简单图示,以及 Katana 对 OWIN 规范的实现如何促进宿主环境、服务器和应用程序之间的交互。

简化的 OWIN 环境

owin-middleware-chain

如果我们回顾一下这是如何工作的,我们记得可以通过多种方式将中间件插入管道,但最常见的机制是为我们的中间件提供一个扩展方法,作为“钩子”或入口点。中间件通常被定义为一个单独的类,如下所示:

简化的中间件组件
public class MiddlewareComponent
{
    AppFunc _next;
    public MiddlewareComponent(AppFunc next)
    {
        _next = next;
 
        // ...Other initialization processing...
    }
 
    public async Task Invoke(IDictionary<string, object> environment)
    {
        // ...Inbound processing on environment or HTTP request...
 
        // Invoke next middleware component:
        await _next.Invoke(environment);
 
        // ...outbound processing on environment or HTTP request...
    }
}

 

然后,为了将一个组件插入到 Katana 的中间件管道中,我们通常按照约定提供一个扩展方法

用于将中间件插入 Katana 管道的扩展方法
public static class AppBuilderExtensions
{
    public static void UseMiddelwareComponent(this IAppBuilder app)
    {
        app.Use<MiddlewareComponent>();
    }
}

 

这使我们能够将我们的 MiddlewareComponent(中间件组件)在调用 Configuration()方法时插入到 Katana 管道中在我们的 OWIN Startup 类中启动

使用扩展方法将中间件插入 Katana
public void Configuration(IAppBuilder app)
{
    app.UseMiddlewareComponent();
}

 

当我们想在基于 OWIN 的应用程序中使用 ASP.NET Web Api 时,我们可以做类似的事情。

将 Web Api 插入到 OWIN/Katana 应用程序中

当我们想在基于 OWIN 的应用程序中使用 Web Api,而不是依赖于System.WebSystem.Web时,我们可以安装 Microsoft.AspNet.WebApi.OwinNuget 包。这个包提供了一个类似于上述的钩子,允许我们将 Web Api 添加到我们的中间件管道中。一旦我们这样做了,我们的图表可能会看起来更像这样:(图表标题)

已插入 Web Api 的 OWIN/Katana 中间件管道

owin-middleware-chain w webapi

Microsot.AspNet.WebApi.Owin包为我们提供了 UseWebApi()这个钩子,我们将用它来将 Web Api 插入一个精简的、最小化的应用程序。首先,我们将看看如何创建一个简单的自托管 Web Api,然后我们将看看如何使用 Katana 管道在一个托管于 IIS 的应用程序中使用 Web Api,同时放弃对 System.Web 的重度依赖。(标题)System.Web.

创建自托管的基于 OWIN 的 Web Api

我们将从创建一个基本的、自托管的 Web Api 开始,使用一个控制台应用程序作为基础。首先,在 Visual Studio 中创建一个新的控制台项目,然后下载 Microsoft.AspNet.WebApi.SelfHostMicrosoft.AspNet.WebApi.OwinSelfHostNuget 包

安装 Web Api 2.2 自托管 Nuget 包
 PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost -Pre

 

Microsoft.AspNet.WebApi.OwinSelfHost这个 Nuget 包在我们的项目中安装了一些新的引用,其中包括:Microsoft.Owin.HostingMicrosoft.Owin.Host.HttpListener。有了这两个库,我们的应用程序现在可以充当自己的宿主,并在应用程序启动时指定的端口上监听 HTTP 请求。

准备好之后,添加一个名为 Startup 的新类,并加入以下代码:启动(代码标题)

基于 Katana 的 Web Api 的 Startup 类
// Add the following usings:
using Owin;
using System.Web.Http;
 
namespace MinimalOwinWebApiSelfHost
{
    public class Startup
    {
        // This method is required by Katana:
        public void Configuration(IAppBuilder app)
        {
            var webApiConfiguration = ConfigureWebApi();
 
            // Use the extension method provided by the WebApi.Owin library:
            app.UseWebApi(webApiConfiguration);
        }
 
 
        private HttpConfiguration ConfigureWebApi()
        {
            var config = new HttpConfiguration();
            config.Routes.MapHttpRoute(
                "DefaultApi",
                "api/{controller}/{id}",
                new { id = RouteParameter.Optional });
            return config;
        }
    }
}

 

如我们所见,我们在这里做的仅仅是设置默认的路由配置,这与我们在标准 VS 模板项目中看到的情况类似。然而,我们不是将指定的路由添加到 ASP.NET 管道的路由集合中,而是将 HttpConfiguration对象作为参数传递给 app.UseWebApi()扩展方法。(标题)

接下来,让我们设置熟悉的 ASP.NET Web Api 文件夹结构。添加一个 Models 文件夹和一个 Controllers 文件夹。然后向 Models 文件夹添加一个 CompanyCompany类。

向 Models 文件夹添加一个 Company 类
public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
}

接下来,向 Controllers 文件夹添加一个 CompaniesControllerCompaniesController类。

向 Controllers 文件夹添加一个 CompaniesController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
// Add these usings:
using System.Web.Http;
using System.Net.Http;
using MinimalOwinWebApiSelfHost.Models;
 
namespace MinimalOwinWebApiSelfHost.Controllers
{
    public class CompaniesController : ApiController
    {
        // Mock a data store:
        private static List<Company> _Db = new List<Company>
            {
                new Company { Id = 1, Name = "Microsoft" },
                new Company { Id = 2, Name = "Google" },
                new Company { Id = 3, Name = "Apple" }
            };
 
 
        public IEnumerable<Company> Get()
        {
            return _Db;
        }
 
 
        public Company Get(int id)
        {
            var company = _Db.FirstOrDefault(c => c.Id == id);
            if(company == null)
            {
                throw new HttpResponseException(
                    System.Net.HttpStatusCode.NotFound);
            }
            return company;
        }
 
 
        public IHttpActionResult Post(Company company)
        {
            if(company == null)
            {
                return BadRequest("Argument Null");
            }
            var companyExists = _Db.Any(c => c.Id == company.Id);
 
            if(companyExists)
            {
                return BadRequest("Exists");
            }
 
            _Db.Add(company);
            return Ok();
        }
 
 
        public IHttpActionResult Put(Company company)
        {
            if (company == null)
            {
                return BadRequest("Argument Null");
            }
            var existing = _Db.FirstOrDefault(c => c.Id == company.Id);
 
            if (existing == null)
            {
                return NotFound();
            }
 
            existing.Name = company.Name;
            return Ok();
        }
 
 
        public IHttpActionResult Delete(int id)
        {
            var company = _Db.FirstOrDefault(c => c.Id == id);
            if (company == null)
            {
                return NotFound();
            }
            _Db.Remove(company);
            return Ok();
        }
    }
}

 

在上面的代码中,我们暂时只是用一个 List来模拟数据存储。此外,在一个真实的控制器中,我们可能会实现异步async控制器方法,但目前这样就足够了。

要完成我们自托管 Web Api 应用程序最基本的功能,我们只需要设置 Main()Main()方法来启动由 Microsoft.Owin.Host.HttpListener提供的服务器功能。将以下 usingusing语句和代码添加到 Program.cs 文件中:

在 Main() 方法中启动应用程序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
// Add reference to:
using Microsoft.Owin.Hosting;
 
namespace MinimalOwinWebApiSelfHost
{
    class Program
    {
        static void Main(string[] args)
        {
            // Specify the URI to use for the local host:
            string baseUri = "https://:8080";
 
            Console.WriteLine("Starting web Server...");
            WebApp.Start<Startup>(baseUri);
            Console.WriteLine("Server running at {0} - press Enter to quit. ", baseUri);
            Console.ReadLine();
        }
    }
}

 

如果你之前使用过 Web Api 或 MVC 项目,上面的大部分结构应该看起来有些熟悉。

现在我们只需要一个合适的客户端应用程序来消费我们的自托管 Web Api。

创建一个基本的 Web Api 客户端应用程序

我们将创建一个简单的控制台应用程序作为客户端来消费我们的 Web Api。创建一个新的控制台应用程序,然后从 Nuget 添加 Microsoft.AspNet.WebApi.Client库。(图标题)

从 Nuget 添加 Web Api 2.2 客户端库
PM> Install-Package Microsoft.AspNet.WebApi.Client -Pre

 

现在,添加一个名为 CompanyClient的类,并添加以下 using 语句和代码:

在 Web Api 客户端应用程序中定义 CompanyClient 类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
// Add Usings:
using System.Net.Http;
 
namespace MinimalOwinWebApiClient
{
    public class CompanyClient
    {
        string _hostUri;
        public CompanyClient(string hostUri)
        {
            _hostUri = hostUri;
        }
 
 
        public HttpClient CreateClient()
        {
            var client = new HttpClient();
            client.BaseAddress = new Uri(new Uri(_hostUri), "api/companies/");
            return client;
        }
 
 
        public IEnumerable<Company> GetCompanies()
        {
            HttpResponseMessage response;
            using (var client = CreateClient())
            {
                response = client.GetAsync(client.BaseAddress).Result;
            }
            var result = response.Content.ReadAsAsync<IEnumerable<Company>>().Result;
            return result;
        }
 
 
        public Company GetCompany(int id)
        {
            HttpResponseMessage response;
            using (var client = CreateClient())
            {
                response = client.GetAsync(
                    new Uri(client.BaseAddress, id.ToString())).Result;
            }
            var result = response.Content.ReadAsAsync<Company>().Result;
            return result;
        }
 
 
        public System.Net.HttpStatusCode AddCompany(Company company)
        {
            HttpResponseMessage response;
            using (var client = CreateClient())
            {
                response = client.PostAsJsonAsync(client.BaseAddress, company).Result;
            }
            return response.StatusCode;
        }
 
 
        public System.Net.HttpStatusCode UpdateCompany(Company company)
        {
            HttpResponseMessage response;
            using (var client = CreateClient())
            {
                response = client.PutAsJsonAsync(client.BaseAddress, company).Result;
            }
            return response.StatusCode;
        }
 
 
        public System.Net.HttpStatusCode DeleteCompany(int id)
        {
            HttpResponseMessage response;
            using (var client = CreateClient())
            {
                response = client.DeleteAsync(
                    new Uri(client.BaseAddress, id.ToString())).Result;
            }
            return response.StatusCode;
        }
    }
}

 

我们已经(相当仓促地)编写了一个粗糙但简单的客户端类,它将调用我们在 Web Api 应用程序中定义的基本 API 方法。我们这里使用的是模拟数据集,所以我们在 Id 等方面做了一些取舍,以便能够反复运行客户端应用程序而不会遇到键冲突。

在上面,我们创建了一个便捷/工厂方法,用于按需提供一个 HttpClientHttpClient实例,并预先配置了与我们 Web Api 中 ClientController的路由匹配的基础 Uri。然后,我们简单地定义了与每个 API 方法对应的本地方法,这些方法可以在我们的控制台应用程序中使用。

我们可以通过向客户端应用程序的 Program.cs 文件添加以下代码来让它运行起来:

API 客户端应用程序的 Program.cs 文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
// Add Usings:
using System.Net.Http;
 
 
namespace MinimalOwinWebApiClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Read all the companies...");
            var companyClient = new CompanyClient("https://:8080");
            var companies = companyClient.GetCompanies();
            WriteCompaniesList(companies);
 
            int nextId  = (from c in companies select c.Id).Max() + 1;
 
            Console.WriteLine("Add a new company...");
            var result = companyClient.AddCompany(
                new Company 
                { 
                    Id = nextId, 
                    Name = string.Format("New Company #{0}", nextId) 
                });
            WriteStatusCodeResult(result);
 
            Console.WriteLine("Updated List after Add:");
            companies = companyClient.GetCompanies();
            WriteCompaniesList(companies);
 
            Console.WriteLine("Update a company...");
            var updateMe = companyClient.GetCompany(nextId);
            updateMe.Name = string.Format("Updated company #{0}", updateMe.Id);
            result = companyClient.UpdateCompany(updateMe);
            WriteStatusCodeResult(result);
 
            Console.WriteLine("Updated List after Update:");
            companies = companyClient.GetCompanies();
            WriteCompaniesList(companies);
 
            Console.WriteLine("Delete a company...");
            result = companyClient.DeleteCompany(nextId -1);
            WriteStatusCodeResult(result);
 
            Console.WriteLine("Updated List after Delete:");
            companies = companyClient.GetCompanies();
            WriteCompaniesList(companies);
 
            Console.Read();
        }
 
 
        static void WriteCompaniesList(IEnumerable<Company> companies)
        {
            foreach(var company in companies)
            {
                Console.WriteLine("Id: {0} Name: {1}", company.Id, company.Name);
            }
            Console.WriteLine("");
        }
 
 
        static void WriteStatusCodeResult(System.Net.HttpStatusCode statusCode)
        {
            if(statusCode == System.Net.HttpStatusCode.OK)
            {
                Console.WriteLine("Opreation Succeeded - status code {0}", statusCode);
            }
            else
            {
                Console.WriteLine("Opreation Failed - status code {0}", statusCode);
            }
            Console.WriteLine("");
        }
    }
}

 

现在,如果我们运行自托管的 Web Api,启动后应该会看到以下控制台输出:

自托管 Web Api 启动时的控制台输出

console-output-web-api-startup

然后,当我们运行客户端应用程序时,我们应该会看到以下内容:

Web Api 客户端应用程序的控制台输出

console-output-client-startup

我们看到的结果与我们编写的代码所预期的基本一致。我们向 Web Api 查询公司列表。然后我们添加一个新公司,并刷新列表。接着我们更新刚刚添加的公司,并再次查看列表。最后,我们在列表中新公司之前移除了一个公司,并最后一次查看列表。

为自托管的 Web Api 添加数据库和 Entity Framework

到目前为止一切顺利。然而,一个没有持久化和检索数据机制的 Web Api(即使是一个小型的自托管 Api)用处不大。我们可以在自托管的 Web Api 中添加数据库并使用 Entity Framework。

由于我们是自托管的,我们可能(取决于应用程序的需求)也想使用一个本地的、进程内的数据库(而不是客户端/服务器解决方案),以保持我们的 Web Api 完全自包含。通常我会选择 SQLite,但为了简单起见,我们将使用 SQL CE。虽然有适用于 SQLite 的 Entity Framework 提供程序,但它与 EF Code-First 的协作不太好。

如果你不介意手动创建数据库(或者采用一些变通方法使其与 code first 协同工作),你可以在 Entity Framework 中使用 SQLite,但就我们的目的而言,SQL CE 就足够了。

当然,我们并非必须使用本地数据库。根据你的应用程序,你很可能想要连接到 SQL Server 或其他外部数据库。如果是这样,如果你下载标准的 Entity Framework 包并针对 SQL Server 工作,以下大部分内容同样适用。

要添加一个 SQL Server Compact Edition 数据库,我们只需再次访问 Nuget,并引入 EntityFramework.SqlServerCompact包。Nuget 包

将 Entity Framework SQL CE Nuget 包添加到 Web Api 应用程序
PM> Install-Package EntityFramework.SqlServerCompact

 

完成之后,让我们做一些整理工作,为我们的新数据库铺平道路。

为 Entity Framework 添加 ApplicationDbContext 和初始化器

首先,我们需要添加一个数据上下文类。此外,我们希望使用一个数据库初始化器,在应用程序运行时调用它来应用任何更改。另外,对于这个特定情况,我们将设置成每次都重新创建数据库并重新填充数据。

如果我们不想每次都删除并重新创建,我们会继承自 DropCreateDatabaseIfModelChanges而不是DropCreateDatabaseAlways

向 Models 文件夹添加 ApplicationDbContext 和 Initializer 类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
// Add using:
using System.Data.Entity;
 
namespace MinimalOwinWebApiSelfHost.Models
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext() : base("MyDatabase")
        {
        }
        public IDbSet<Company> Companies { get; set; }
    }
 
 
    public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
    {
        protected override void Seed(ApplicationDbContext context)
        {
            base.Seed(context);
            context.Companies.Add(new Company { Name = "Microsoft" });
            context.Companies.Add(new Company { Name = "Google" });
            context.Companies.Add(new Company { Name = "Apple" });
        }
    }
}

 

现在我们需要设置,以便数据库初始化器在每次应用程序启动时运行(至少在“开发”期间)。

按如下方式更新 Program.cs 文件。注意,你需要在 usingSystem.Data.Entity语句中添加对 System.Data.Entityusing以及你的 Models 命名空间的引用。

更新 Program.cs 以运行数据库初始化器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin.Hosting;
 
// Add reference to:
using System.Data.Entity;
using MinimalOwinWebApiSelfHost.Models;
 
namespace MinimalOwinWebApiSelfHost
{
    class Program
    {
        static void Main(string[] args)
        {
            // Set up and seed the database:
            Console.WriteLine("Initializing and seeding database...");
            Database.SetInitializer(new ApplicationDbInitializer());
            var db = new ApplicationDbContext();
            int count = db.Companies.Count();
            Console.WriteLine("Initializing and seeding database with {0} company records...", count);
 
            // Specify the URI to use for the local host:
            string baseUri = "https://:8080";
 
            Console.WriteLine("Starting web Server...");
            WebApp.Start<Startup>(baseUri);
            Console.WriteLine("Server running at {0} - press Enter to quit. ", baseUri);
            Console.ReadLine();
        }
    }
}

 

最后,让我们给我们的 Company类中的 CompanyId属性添加一个 [Key]ID特性,这样 EF 就会知道我们希望它成为一个自增的主Company键。请注意,你需要在 using 语句中添加对 System.ComponentModel.DataAnnotations.Schemaint的引用。System.ComponentModel.DataAnnotations(代码标题)

用 [Key] 特性更新 Company 类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
// Add using:
using System.ComponentModel.DataAnnotations;
 
namespace MinimalOwinWebApiSelfHost.Models
{
    public class Company
    {
        // Add Key Attribute:
        [Key]
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

 

更新控制器以使用数据库和异步方法

现在我们需要对我们的 CompaniesController 做一些更改。之前,我们使用的是一个列表作为模拟数据存储。现在让我们更新控制器方法来使用实际的数据库。同时,我们现在将使用异步方法。CompaniesController请注意,我们需要在 using 语句中添加对 System.Threading.Tasks

System.Data.EntitySystem.Data.Entity的引用。

更新控制器方法以使用数据库并使用 Async/Await
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Net.Http;
using MinimalOwinWebApiSelfHost.Models;
 
// Add these usings:
using System.Data.Entity;
 
namespace MinimalOwinWebApiSelfHost.Controllers
{
    public class CompaniesController : ApiController
    {
        ApplicationDbContext _Db = new ApplicationDbContext();
        public IEnumerable<Company> Get()
        {
            return _Db.Companies;
        }
 
 
        public async Task<Company> Get(int id)
        {
            var company = await _Db.Companies.FirstOrDefaultAsync(c => c.Id == id);
            if (company == null)
            {
                throw new HttpResponseException(
                    System.Net.HttpStatusCode.NotFound);
            }
            return company;
        }
 
 
        public async Task<IHttpActionResult> Post(Company company)
        {
            if (company == null)
            {
                return BadRequest("Argument Null");
            }
            var companyExists = await _Db.Companies.AnyAsync(c => c.Id == company.Id);
 
            if (companyExists)
            {
                return BadRequest("Exists");
            }
 
            _Db.Companies.Add(company);
            await _Db.SaveChangesAsync();
            return Ok();
        }
 
 
        public async Task<IHttpActionResult> Put(Company company)
        {
            if (company == null)
            {
                return BadRequest("Argument Null");
            }
            var existing = await _Db.Companies.FirstOrDefaultAsync(c => c.Id == company.Id);
 
            if (existing == null)
            {
                return NotFound();
            }
 
            existing.Name = company.Name;
            await _Db.SaveChangesAsync();
            return Ok();
        }
 
 
        public async Task<IHttpActionResult> Delete(int id)
        {
            var company = await _Db.Companies.FirstOrDefaultAsync(c => c.Id == id);
            if (company == null)
            {
                return NotFound();
            }
            _Db.Companies.Remove(company);
            await _Db.SaveChangesAsync();
            return Ok();
        }
    }
}

 

最后,我们需要对客户端应用程序做几个小改动,因为我们现在使用的是一个会插入自增整数 ID 的数据库。

更新 Api 客户端应用程序

我们只需要在这里更改一行代码,即之前我们在添加新公司时提供了一个新的 Id 值。将高亮显示的行更改如下:

添加记录时不要为新 Id 传值
Console.WriteLine("Add a new company...");
var result = companyClient.AddCompany(new Company 
    { 
        Name = string.Format("New Company #{0}", nextId) 
    });
WriteStatusCodeResult(result);

 

现在我们所做的只是将下一个 Id 作为拼凑的命名方案的一部分(而且,这也不是从数据库获取下一个 Id 的好方法...)。

运行带数据库的自托管 Web Api

如果我们一切都做对了,我们就可以启动 Web Api 应用程序,然后运行客户端应用程序,看看会发生什么。如果一切顺利,我们的控制台输出应该和之前基本相同。

启动 Web Api 应用程序时的控制台输出

console-output-web-api-startup-with-database

同样,当我们运行客户端应用程序时,我们的控制台输出应该与之前基本相同,只是这次 Web Api 是从 SQL CE 数据库中获取和保存数据,而不是从内存中的列表。

Web Api 客户端应用程序启动时的控制台输出

console-output-client-startup-with-database

后续步骤

在本文中,我们看到了如何在自托管场景中组建一个非常简单且最小化的 ASP.NET Web Api 应用程序,无需 IIS,也无需依赖重量级的 System.WebSystem.Web库。我们利用了 OWIN/Katana 中间件管道,并看到了如何将 Web Api 组件“挂钩”到宿主/服务器的交互中。

接下来,我们将研究如何应用这些相同的概念,在仍然托管于 IIS 环境中的同时构建一个最小化的 Web Api,并且我们将看到如何引入 ASP.NET Identity 来为整个系统添加一些身份验证和授权功能。

下一篇:ASP.NET Web Api:理解 OWIN/Katana 身份验证/授权 第一部分:概念

其他资源和感兴趣的项目

 
 
 

© . All rights reserved.