目录
介绍
这种方法有什么好处?
Restful约定
构建API
测试API
一点困惑:
最佳实践:
数据传输对象(DTO)
Automapper
IHttpActionResult
使用Web API
结论
在本文中,我们将构建测试并使用asp.net web api。
让我们快速回顾一下ASP.NET MVC架构。因此,当请求到达我们的应用程序时,MVC Framework将该请求移交给控制器中的操作,此操作大部分时间返回一个视图,然后由razor视图引擎解析,最终将HTML标记返回给客户端。因此,在这种方法中,在服务器上生成html标记,然后返回到客户端。
有另一种生成HTML标记的方法,我们可以在客户端生成它。因此,不是我们的操作返回HTML标记,它们可以返回原始数据。
在客户端上生成标记有很多好处。
这些应用程序简称为端点获取数据并在本地生成视图。我们将这些端点称为数据服务(Web API),因为它们只返回数据,而不是标记。
Web API不仅限于交叉设备,它还广泛用于我们的Web应用程序中,以添加新功能,如youtube,facebook和twitter等许多热门网站,从而公开我们可以在我们的Web应用程序中使用的公共数据服务。我们可以将他们的数据与我们应用程序中的数据合并,并为新用户提供新的体验。这些都是好处。
这些数据服务不仅仅是为了获取数据,我们还提供修改数据的服务,例如添加客户等。我们用来构建这些数据服务的框架称为Web API。这个框架是在ASP.Net MVC之后开发的,但它遵循ASP.NET MVC的相同体系结构和原理,因此它具有路由,控制器,操作,操作结果等。我们在这里看到的细微差别也很少。在.Net Core中,Microsoft已将这两个框架(ASP.NET MVC和ASP.NET Web API)合并到一个框架中。
所以你知道什么是http服务,什么是web api。在这里,我们将开发一个支持几种不同类型请求的应用程序。
GET / api / customers(获取客户列表)
GET / api / customers / 1(获得单个客户)
POST / api / customers(添加客户并在请求正文中添加客户数据)
不要混淆请求数据的GET和POST方式,我们使用get请求来获取资源或数据的列表。我们使用post请求来创建新的数据。
现在更新学生我们使用PUT请求。
PUT / api / customers / 1
因此,客户的ID位于URL中,要更新的实际数据或属性将位于请求正文中。最后删除学生。
Delete / api / customers / 1
我们将HttpDelete请求发送到端点。所以你在这里看到的,就请求类型和端点而言,是一个标准约定,被交付请求REST(Representational State Transfer)
此类派生自ApiController,而不是Controller。如果您正在使用任何现有项目,则只需在controllers文件夹中添加一个新文件夹,并在此处添加api控制器。并添加这些操作,但在apis中定义操作之前,这是我的Customer模型类。
public class Customer
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public bool IsSubscribedToNewsLetter { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? Birthdate { get; set; }
[Display(Name = "Membership Type")]
public byte MembershipTypeId { get; set; }
// it allows us to navigate from 1 type to another
public MembershipType MembershipType { get; set; }
}
这是我的DbContext类
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext()
:base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public DbSet Customers { get; set; }
public DbSet MembershipTypes { get; set; }
}
现在,您可以轻松地为Api编写操作。
public IEnumerable GetCustomers()
{
}
因为我们正在返回一个对象列表。按约定,此操作将作出回应
//Get / api / customers
所以这是ASP.Net Web API中内置的约定。现在,在这个操作中,我们将使用我们的上下文从数据库中获取客户。
namespace MyAPI.Controllers.Api
{
public class CustomersController : ApiController
{
private readonly ApplicationDbContext _context;
public CustomersController()
{
_context = new ApplicationDbContext();
}
// GET /api/customers
public IEnumerable GetCustomers()
{
return _context.Customers.ToList();
}
}
}
如果找不到资源,我们返回找不到httpresponse,否则我们返回该对象。
// POST /api/customers
[HttpPost]
public Customer CreateCustomer(Customer customer)
{
}
所以这是客户参数将在请求体中,ASP.NET Web API框架将自动初始化它。现在我们应该用HttpPost标记这个操作,因为我们正在创建资源。如果我们遵循命名约定,那么我们甚至不需要将操作动词放在操作上。
// POST /api/customers
public Customer PostCustomer(Customer customer)
{
}
但最初它不是那么好的方法,让我们假设您将来重构代码并重命名您的操作,那么您的代码肯定会破坏。所以总是喜欢在操作的顶部使用Http动词。
现在让我们使用api操作的post请求的将客户对象插入到数据库中。
// POST /api/customers
[HttpPost]
public Customer CreateCustomer(Customer customer)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
_context.Customers.Add(customer);
_context.SaveChanges();
return customer;
}
另一个操作让我们假设我们要更新记录。
// PUT /api/customers/1
[HttpPut]
public void UpdateCustomer(int id, Customer customer)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var custmr = _context.Customers.SingleOrDefault(x => x.Id == id);
// Might be user sends invalid id.
if (custmr == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
custmr.Birthdate = customer.Birthdate;
custmr.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
custmr.Name = customer.Name;
custmr.MembershipTypeId = customer.MembershipTypeId;
_context.SaveChanges();
}
在这种情况下,不同的人有不同的意见来返回void或对象。如果我们进行Api的删除操作。
// Delete /api/customers/1
[HttpDelete]
public void DeleteCustomer(int id)
{
var custmr = _context.Customers.SingleOrDefault(x => x.Id == id);
// Might be user sends invalid id.
if (custmr == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
_context.Customers.Remove(custmr);
// Now the object is marked as removed in memory
// Now it is done
_context.SaveChanges();
}
这就是我们如何使用restful约定来构建api。
如果我们运行应用程序并请求api控制器,我们可以看到基于XML语言的客户列表。
"http://localhost:53212/api/customers">http://localhost:53212/api/customers
因此,ASP.NET Web API具有我们所谓的媒体格式化程序。因此,我们从一个动作中返回的内容(在我们的情况下,客户列表将根据客户要求进行格式化)让我解释一下我的意思和我想说的内容。
在上面的屏幕上检查浏览器并刷新页面,在这里您将看到客户请求。
这里查看内容类型,如果您在我们的请求中没有设置内容类型标头,则默认情况下服务器采用application / xml。
注意: General是请求标头,Response Headers是我们的响应标头。正如您在Request Header中看到的,我们没有任何内容类型。现在让我告诉你最好测试api的方式并返回json数据。
在您的计算机中安装Postman Desktop App。并使用localhost端口号复制浏览器链接并将其粘贴到postman。
在这里,我们将请求的url与localhost放在一起,而响应在json中返回。
如果我们点击Header选项卡,这里我们将看到我们的请求标头内容类型是application / json
大多数时候我们将使用json,因为它是javascript代码的原生代码,比xml快得多。XML媒体格式主要用于政府等大型组织,因为它们落后于现代技术。Json格式更轻量级,因为它没有像xml那样的多余的开启和关闭选项卡。
有时当你使用Api或postman时,大多数人都会对Postman的界面感到困惑,因为他们之前从未使用过postman。记住这一点非常简单,
因此,如果您正在处理请求并尝试更改请求的某些信息,请关注请求标头,如果您正在监视响应,请在响应标头中查看结果。因为它们看起来相同,有时当请求标题的向下滚动消失时。所以不要混淆事情。
现在让我们使用Api Post 操作在数据库中插入一个客户。
从下拉列表和请求正文选项卡中选择Post请求。您可以在单击表单数据时插入具有键值对的客户。但大多数时候我们使用json格式。所以点击raw并在这里写json。
不要将Id属性放在json中,因为当我们在数据库中插入数据时,它是硬性规则,在服务器上自动生成id。
现在点击Send按钮,这里我已通过Post api操作成功插入数据并获得响应。
这里上面的块是请求块,下面的块是响应块。你可能会在这里遇到某种错误。
如果您阅读错误消息'此资源不支持请求实体的媒体类型'text / plain'。这是错误消息。
现在来解决这个错误。单击Header选项卡并添加content-type的值('application / json')
这里已经添加了值。查看请求的状态代码是200 OK,我们可以在下面看到响应正文。
现在让我们更新客户实体。
看看它已经更新。
现在让我们删除一条记录,只需在下拉列表中选择Delete,然后在url中指定带正斜杠的id,然后单击Send按钮。它会被自动删除。
最佳实践是在构建api时以及在应用程序中使用api之前。通过Postman测试api会更好。
所以现在我们已经构建了这个API,但这个设计存在一些问题。我们的api接收或返回Customer对象。现在你可能会想到这种方法有什么问题?实际上,Customer对象是我们应用程序的域模型的一部分。当我们在应用程序中实现新功能时,它被认为是可以经常更改的实现细节,并且这些更改可能会抓取依赖于客户对象的现有客户端,即如果我们重命名或删除我们的属性,这会影响依赖于属性的客户端。所以基本上我们使api的协议尽可能稳定。在这里我们使用DTO。
DTO是普通数据结构,用于从服务器上的客户端传输数据,反之亦然,这就是我们称之为数据传输对象的原因。通过创建DTO,我们在重构域模型时减少了对API破坏的可能性。当然,我们应该记住,改变这些DTO可能会产生高昂成本。所以最重要的是我们的api不应该接收或返回Customer模型类对象。
在API中使用域对象的另一个问题是我们在应用程序中打开了安全漏洞。黑客可以轻松地在json中传递其他数据,并将它们映射到我们的域对象。如果不应该更新其中一个属性,黑客可以轻易绕过这个,但如果我们使用DTO,我们可以简单地排除可以更新的属性。因此,在项目中添加名为DTO的新文件夹并添加CustomerDTO类,并将Customer域模型类的所有属性及其数据注释属性复制并粘贴到CustomerDTO中。现在从CustomerDTO中删除导航属性,因为它正在创建与MembershipType域模型类的依赖关系。
namespace MyAPI.DTOs
{
public class CustomerDTO
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public bool IsSubscribedToNewsLetter { get; set; }
public DateTime? Birthdate { get; set; }
public byte MembershipTypeId { get; set; }
}
}
接下来我们要在我们的api中使用CustomerDTO而不是Customer域类对象。因此,为了减少大量代码逐个绑定属性,我们使用Automapper。
从Package Manager控制台安装automapper包。
PM > Install-Package Automapper -version:4.1
现在在App_Start(MappingProfile.cs)中添加新类并继承Profile。
using AutoMapper;
namespace MyAPI.App_Start
{
public class MappingProfile : Profile
{
}
}
现在创建构造函数并在两种类型之间添加映射配置。
public class MappingProfile : Profile
{
public MappingProfile()
{
Mapper.CreateMap();
Mapper.CreateMap();
}
}
CreateMap的第一个参数是Source(源对象),第二个参数是destination(目标对象)。当我们使用CreateMap方法时,automapper会自动使用反射来扫描这些类型。它找到它们的属性并根据它们的名称对它们进行映射。这就是我们调用automapper(基于约定的映射工具)的原因,因为它使用属性名作为映射对象的约定。所以这里是映射配置文件,现在我们需要在应用程序启动时加载它。
现在打开Global.asax.cs文件并编写Application_Start()的代码
protected void Application_Start()
{
Mapper.Initialize(c => c.AddProfile());
GlobalConfiguration.Configure(WebApiConfig.Register);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
现在打开Api的CustomerController。让我们从这里开始改变。
// GET /api/customers
public IEnumerable GetCustomers()
{
return _context.Customers.ToList();
}
现在我们要返回CustomerDTO类型而不是Customer对象。现在我们需要将此Customer对象映射到CustomerDTO。所以我们使用linq扩展方法。
// GET /api/customers
public IEnumerable GetCustomers()
{
return _context.Customers.ToList()
.Select(Mapper.Map);
}
Mapper.Map
这个代表进行映射。我们可以看到我们没有放置函数调用小括号,因为我们没有在这里调用函数。我们在这里引用它。映射函数在执行时自动调用。
// GET /api/customers/1
public Customer GetCustomer(int id)
{
var customer = _context.Customers.SingleOrDefault(x => x.Id == id);
// This is part of the RESTful Convention
if (customer == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return customer;
}
在这个函数中,因为我们返回一个对象所以我们不使用Select扩展方法。这里我们直接使用mapper
// GET /api/customers/1
public CustomerDTO GetCustomer(int id)
{
var customer = _context.Customers.SingleOrDefault(x => x.Id == id);
// This is part of the RESTful Convention
if (customer == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Mapper.Map(customer);
}
现在进行下一个CreateCustomer操作,
// POST /api/customers
[HttpPost]
public CustomerDTO CreateCustomer(CustomerDTO customerDto)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var customer = Mapper.Map(customerDto);
_context.Customers.Add(customer);
_context.SaveChanges();
// Here we make our CustomerDto completely fill, because after
// adding customer to Customer table (id is assigned to it)
// & Now we assigned this id to customerDto
customerDto.Id = customer.Id;
return customerDto;
}
这就是我们如何使用Dto和Automapper。
现在让我们更新UpdateCustomer动作api方法。
// PUT /api/customers/1
[HttpPut]
public void UpdateCustomer(int id, CustomerDTO customerDto)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var custmr = _context.Customers.SingleOrDefault(x => x.Id == id);
// Might be user sends invalid id.
if (custmr == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
Mapper.Map(customerDto, custmr);
//custmr.Birthdate = customerDto.Birthdate;
//custmr.IsSubscribedToNewsLetter = customerDto.IsSubscribedToNewsLetter;
//custmr.Name = customerDto.Name;
//custmr.MembershipTypeId = customerDto.MembershipTypeId;
_context.SaveChanges();
}
这就是我们使用Automapper映射对象的方式。现在,automapper有一些在某些情况下可能会发现有用的功能,即如果您的属性名称不匹配,您可以覆盖默认约定,或者您可以从映射中排除某些属性,或者您可能想要创建自定义映射类。如果您想了解更多信息,可以从Automapper文档中学习。
好吧看看这个CreateCustomer操作方法
// POST /api/customers
[HttpPost]
public CustomerDTO CreateCustomer(CustomerDTO customerDto)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var customer = Mapper.Map(customerDto);
_context.Customers.Add(customer);
_context.SaveChanges();
// Here we make our CustomerDto completely fill, because after
// adding customerDto to Customer table (id is assigned to it)
// & Now we assigned this id to customerDto
customerDto.Id = customer.Id;
return customerDto;
}
在这里,我们只是返回CustomerDto,最终会产生这样的响应
但是在restful约定中当我们创建资源时,状态代码应为201或created。所以我们需要更多地控制操作的响应返回并实现这一点,而不是返回CustomerDto,我们返回IHttpActionResult。这个接口类似于我们在MVC框架中的ActionResult所以它由几个不同的类实现,这里在ApiController中,我们有很多方法来创建一个实现IHttpActionResult接口的类的实例。
现在,如果模型无效而不是抛出异常,请使用辅助方法BadRequest()
// POST /api/customers
[HttpPost]
public IHttpActionResult CreateCustomer(CustomerDTO customerDto)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var customer = Mapper.Map(customerDto);
_context.Customers.Add(customer);
_context.SaveChanges();
// Here we make our CustomerDto completely fill, because after
// adding customerDto to Customer table (id is assigned to it)
// & Now we assigned this id to customerDto
customerDto.Id = customer.Id;
return Created(new Uri(Request.RequestUri + "/" + customer.Id),
customerDto);
}
我们可以看到如果ModelState是无效的,它返回BadRequest,如果已经添加了customer,那么我们在Created()中使用我们最终创建新对象的对象返回具有此资源ID的Uri 。
看这里我们创建了另一个资源,现在状态为201 Created。如果我们查看位置选项卡
这就是新创造的客户的uri。这是restful约定的一部分。所以在Web Api中,我们更倾向于使用IHttpActionResult作为操作的返回类型。
现在让我们对此Web Api中的其余操作进行更改。这是我们的Api的完整代码
public class CustomersController : ApiController
{
private readonly ApplicationDbContext _context;
public CustomersController()
{
_context = new ApplicationDbContext();
}
// GET /api/customers
public IHttpActionResult GetCustomers()
{
return Ok(_context.Customers.ToList()
.Select(Mapper.Map));
}
// GET /api/customers/1
public IHttpActionResult GetCustomer(int id)
{
var customer = _context.Customers.SingleOrDefault(x => x.Id == id);
// This is part of the RESTful Convention
if (customer == null)
{
return NotFound();
}
return Ok(Mapper.Map(customer));
}
// POST /api/customers
[HttpPost]
public IHttpActionResult CreateCustomer(CustomerDTO customerDto)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var customer = Mapper.Map(customerDto);
_context.Customers.Add(customer);
_context.SaveChanges();
// Here we make our CustomerDto completely fill, because after
// adding customerDto to Customer table (id is assigned to it)
// & Now we assigned this id to customerDto
customerDto.Id = customer.Id;
return Created(new Uri(Request.RequestUri + "/" + customer.Id),
customerDto);
}
// PUT /api/customers/1
[HttpPut]
public IHttpActionResult UpdateCustomer(int id, CustomerDTO customerDto)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var custmr = _context.Customers.SingleOrDefault(x => x.Id == id);
// Might be user sends invalid id.
if (custmr == null)
{
return NotFound();
}
Mapper.Map(customerDto, custmr);
_context.SaveChanges();
return Ok(custmr);
}
// Delete /api/customers/1
[HttpDelete]
public IHttpActionResult DeleteCustomer(int id)
{
var custmr = _context.Customers.SingleOrDefault(x => x.Id == id);
// Might be user sends invalid id.
if (custmr == null)
{
return NotFound();
}
_context.Customers.Remove(custmr);
// Now the object is marked as removed in memory
// Now it is done
_context.SaveChanges();
return Ok(custmr);
}
}
我在这里提一下这一点,因为你可以看到我们在UpdateCustomer中有2个参数。如果参数是原始类型,就像我们是int id那样我们将把这个参数放在路由url或查询字符串中。如果我们想要像我们这样初始化我们的复杂类型我们有CustomerDTO,那么我们将始终从postman中的请求体初始化它。所以不要在这里困扰这件事。
现在让我们通过post更新和删除json对象。如果您专注于UpdateCustomer操作参数,这里我们有第一个参数是记录ID,第二个参数是Customer领域模型类对象。
看它在请求头中使用Id,因为我们的实体在这里完成了。
但是如果我们不在请求标头中提供id,我们将得到错误。
而exceptionMessage是“属性'Id'是对象的关键信息的一部分,无法修改。”实际上这个异常发生在这一行,
Mapper.Map
因为customerDto不包含Id,但custmr(Customer模型类的对象变量)具有Id属性。在这里,我们需要告诉Automapper在映射从customerDto到custmr时忽略Id。所以,来看映射配置文件
public class MappingProfile : Profile
{
public MappingProfile()
{
Mapper.CreateMap();
Mapper.CreateMap()
.ForMember(c => c.Id, opt => opt.Ignore());
}
}
看它现在正在工作,
经过适当的api测试后,现在是使用api的时候了。最重要的是我想在这里提一下。现在我们的api准备好了,你可以在任何客户端使用这个api。在这里,我们向您展示使用Visual Studio应用程序进行使用的示例。如果你用我们构建这个api,那么你可以在jquery ajax的帮助下在php,python,任何框架应用程序中使用它。现在我们将使用jquery来调用我们的api。看这个屏幕,这里我展示了一些客户。
现在我们想要的是单击删除按钮删除行。因此,如果您了解我们如何在屏幕上呈现记录项,显然使用foreach循环。所以在删除锚点标签上点击我们还要记录id以将此id传递给web api删除操作并在成功时删除该行。
@foreach (var customer in Model)
{
@Html.ActionLink(customer.Name, "Edit", "Customers", new { id = customer.Id }, null)
@if (customer.Birthdate != null)
{
@customer.Birthdate
}
else
{
Not Available
}
}
这是html。现在我想通过ajax调用我的api。
@section scripts{
}
这就是我们使用ajax和api的方式。现在你可能在想这里我只是将id传递给客户api并定位Delete操作,在 success事件中我直接删除了行。您可能会以不同的方式思考这种情况,因为每个开发人员都有自己的品味。
您可能会想到,首先我们使用Delete方法删除记录,然后从Web Api的GetCustomers()方法获取所有记录,然后通过jquery的每个循环渲染所有这些项。但这种情况需要花费太多时间和效率。当我点击删除锚标签并在检查浏览器中显示结果时,状态为200 ok。这意味着一切正常,我们的代码(删除操作)正如我们所期望的那样工作。因此,我们不需要再次验证数据库中有多少项并通过每个循环呈现它们。
只需简化您的情况,并像我一样删除记录。
因此,结论是当您使用Web Api时,始终遵循Restful约定。Web Api比基于SOAP的Web服务更加的轻量级。他们是跨平台的。Restful Http动词有助于在应用程序中插入,删除,更新,获取记录。在这里,我们看到我们如何使用postman,并有2个不同的窗格,如请求标头和响应标头。大多数时候,开发人员对如何使用jquery ajax使用Web Api操作感到困惑。在这里我们也使用了这个动作。
原文地址:https://www.codeproject.com/Articles/1252477/Building-a-Restful-API-With-ASP-NET-Web-API