- สร้างโปรเจ็กส์แบบ Web API
- Add a data store
- Add a controller
- Implement CRUD operations
- Test web API actions
1.สร้างโปรเจ็กส์แบบ Web API
สร้างโปรเจ็กส์แบบ Web API ชื่อ ContosoPets
2.Add a data store
สร้างโฟลเดอร์ Models และไฟล์ Models/Product.cs
ไฟล์ Models/Product.cs
using System.ComponentModel.DataAnnotations; namespace ContosoPets.Models { public class Product { public long Id { get; set; } [Required] public string Name { get; set; } [Required] [Range(minimum: 0.01, maximum: (double)decimal.MaxValue)] public decimal Price { get; set; } } }
สร้างโฟลเดอร์ Data และไฟล์ Data/ContosoPetsContext.cs ไฟล์ Data/SeedData.cs
ไฟล์ Data/ContosoPetsContext.cs
using ContosoPets.Models; using Microsoft.EntityFrameworkCore; namespace ContosoPets.Data { public class ContosoPetsContext : DbContext { public ContosoPetsContext(DbContextOptions<ContosoPetsContext> options) : base(options) { } public DbSet<Product> Products { get; set; } } }
แก้ไขไฟล์ Startup.cs
using ContosoPets.Data; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace ContosoPets { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ContosoPetsContext>(options => options.UseInMemoryDatabase("ContosoPets")); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } } }
ไฟล์ Data/SeedData.cs
using ContosoPets.Models; using System.Linq; namespace ContosoPets.Data { public static class SeedData { public static void Initialize(ContosoPetsContext context) { if (!context.Products.Any()) { context.Products.AddRange( new Product { Name = "Squeaky Bone", Price = 20.99m }, new Product { Name = "Knotted Rope", Price = 12.99m } ); context.SaveChanges(); } } } }
แก้ไขไฟล์ Program.cs
using ContosoPets.Data; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; namespace ContosoPets { public class Program { public static void Main(string[] args) { var host = CreateWebHostBuilder(args).Build(); SeedDatabase(host); host.Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); private static void SeedDatabase(IWebHost host) { var scopeFactory = host.Services.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<ContosoPetsContext>(); if (context.Database.EnsureCreated()) { try { SeedData.Initialize(context); } catch (Exception ex) { var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "A database seeding error occurred."); } } } } } }
3.Add a controller
สร้างไฟล์ Controllers/ProductsController.cs
ถ้าสร้างแบบ API Controller – Empty
ไฟล์ Controllers/ProductsController.cs
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ContosoPets.Controllers { [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase { } }
ถ้าสร้างแบบ API Controller with actions, using Entity Framework
ไฟล์ Controllers/ProductsController.cs
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using ContosoPets.Data; using ContosoPets.Models; namespace ContosoPets.Controllers { [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase { private readonly ContosoPetsContext _context; public ProductsController(ContosoPetsContext context) { _context = context; } // GET: api/Products [HttpGet] public async Task<ActionResult<IEnumerable<Product>>> GetProducts() { return await _context.Products.ToListAsync(); } // GET: api/Products/5 [HttpGet("{id}")] public async Task<ActionResult<Product>> GetProduct(long id) { var product = await _context.Products.FindAsync(id); if (product == null) { return NotFound(); } return product; } // PUT: api/Products/5 [HttpPut("{id}")] public async Task<IActionResult> PutProduct(long id, Product product) { if (id != product.Id) { return BadRequest(); } _context.Entry(product).State = EntityState.Modified; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ProductExists(id)) { return NotFound(); } else { throw; } } return NoContent(); } // POST: api/Products [HttpPost] public async Task<ActionResult<Product>> PostProduct(Product product) { _context.Products.Add(product); await _context.SaveChangesAsync(); return CreatedAtAction("GetProduct", new { id = product.Id }, product); } // DELETE: api/Products/5 [HttpDelete("{id}")] public async Task<ActionResult<Product>> DeleteProduct(long id) { var product = await _context.Products.FindAsync(id); if (product == null) { return NotFound(); } _context.Products.Remove(product); await _context.SaveChangesAsync(); return product; } private bool ProductExists(long id) { return _context.Products.Any(e => e.Id == id); } } }
แก้ไขไฟล์ Controllers/ProductsController.cs
using ContosoPets.Data; using ContosoPets.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; namespace ContosoPets.Controllers { [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase { private readonly ContosoPetsContext _context; public ProductsController(ContosoPetsContext context) { _context = context; } [HttpGet] public ActionResult<List<Product>> GetAll() => _context.Products.ToList(); // GET by ID action // POST action // PUT action // DELETE action } }
ลองรันดู
> curl -k -s https://localhost:44361/api/values ["value1","value2"]
4.Implement CRUD operations
HTTP action verb | CRUD operation | ASP.NET Core attribute |
POST | Create | [HttpPost] |
GET | Read | [HttpGet] |
PUT | Update | [HttpPut] |
DELETE | Delete | [HttpDelete] |
Retrieve a product
[HttpGet("{id}")] public async Task<ActionResult<Product>> GetById(long id) { var product = await _context.Products.FindAsync(id); if (product == null) { return NotFound(); } return product; }
ถ้าไม่เจอโปรดัก (product == null
) จะ return 404 ด้วย NotFound()
แต่ถ้าเจอโปรดักจะ return 200
Add a product
[HttpPost] public async Task<ActionResult<Product>> Create(Product product) { _context.Products.Add(product); await _context.SaveChangesAsync(); // CreatedAtAction uses the action name to generate // a Location HTTP response header with a URL to the newly created product. return CreatedAtAction(nameof(GetById), new { id = product.Id }, product); }
ถ้าเพิ่มโปรดักสำเร็จจะ return 201
แต่ถ้าไม่สำเร็จจะ return 400
Modify a product
[HttpPut("{id}")] public async Task<IActionResult> Update(long id, Product product) { if (id != product.Id) { return BadRequest(); } _context.Entry(product).State = EntityState.Modified; await _context.SaveChangesAsync(); return NoContent(); }
ถ้าไม่เจอโปรดักจะ return 400 ด้วย BadRequest()
ถ้าเจอ แต่อัพเดทโปรดักไม่สำเร็จจะ return 400
แต่ถ้าอัพเดทโปรดักสำเร็จจะ return 204 ด้วย NoContent()
Remove a product
[HttpDelete("{id}")] public async Task<IActionResult> Delete(long id) { var product = await _context.Products.FindAsync(id); if (product == null) { return NotFound(); } _context.Products.Remove(product); await _context.SaveChangesAsync(); return NoContent(); }
ถ้าไม่เจอโปรดัก (product == null
) จะ return 404 ด้วย NotFound()
แต่ถ้าลบโปรดักสำเร็จจะ return 204 ด้วย NoContent()
5.Test web API actions
1.ส่ง invalid HTTP POST request
curl -i -k \ -H "Content-Type: application/json" \ -d "{\"name\":\"Plush Squirrel\",\"price\":0.00}" \ https://localhost:5001/api/Products
-i
displays the HTTP response headers.-d
implies an HTTP POST operation and defines the request body.-H
indicates that the request body is in JSON format. The header’s value overrides the default content type ofapplication/x-www-form-urlencoded
.
พอส่ง request นี้ออกไปจะ Error 400 เพราะ Price
ต่ำกว่า minimum (0.01)
> curl -i -k -H "Content-Type: application/json" -d "{\"name\":\"Plush Squirrel\",\"price\":0.00}" https://localhost:44361/api/Products HTTP/1.1 400 Bad Request Transfer-Encoding: chunked Content-Type: application/problem+json; charset=utf-8 Server: Microsoft-IIS/10.0 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccGhhaXNhcm5zXHNvdXJjZVxyZXBvM1xDb250b3NvUGV0c1xDb250b3NvUGV0c1xhcGlcUHJvZHVjdHM=?= X-Powered-By: ASP.NET Date: Tue, 18 Jun 2019 04:51:38 GMT {"errors":{"Price":["The field Price must be between 0.01 and 7.92281625142643E+28."]},"title":"One or more validation errors occurred.","status":400,"traceId":"80000002-0005-ff00-b63f-84710c7967bb"}
format JSON ให้ดูง่าย
{ "errors":{ "Price":["The field Price must be between 0.01 and 7.92281625142643E+28."] }, "title":"One or more validation errors occurred.", "status":400, "traceId":"80000002-0005-ff00-b63f-84710c7967bb" }
2.ส่ง valid HTTP POST request
curl -i -k \ -H "Content-Type: application/json" \ -d "{\"name\":\"Plush Squirrel\",\"price\":12.99}" \ https://localhost:5001/api/Products
ส่ง request ออกไปจะได้ 201
> curl -i -k -H "Content-Type: application/json" -d "{\"name\":\"Plush Squirrel\",\"price\":12.99}" https://localhost:44361/api/Products HTTP/1.1 201 Created Transfer-Encoding: chunked Content-Type: application/json; charset=utf-8 Location: https://localhost:44361/api/Products/3 Server: Microsoft-IIS/10.0 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccGhhaXNhcm5zXHNvdXJjZVxyZXBvM1xDb250b3NvUGV0c1xDb250b3NvUGV0c1xhcGlcUHJvZHVjdHM=?= X-Powered-By: ASP.NET Date: Tue, 18 Jun 2019 04:59:24 GMT {"id":3,"name":"Plush Squirrel","price":12.99}
บรรทัดที่ 5 Location
แสดง url ในการเรียกดู item ที่เราสร้างขึ้นมา https://localhost:44361/api/Products/3
3.ส่ง HTTP GET request
curl -k -s https://localhost:5001/api/Products/3 | jq
จะได้
> curl -k -s https://localhost:44361/api/Products/3 {"id":3,"name":"Plush Squirrel","price":12.99}
4.ส่ง HTTP PUT request
curl -i -k \ -X PUT \ -H "Content-Type: application/json" \ -d "{\"id\":2,\"name\":\"Knotted Rope\",\"price\":14.99}" \ https://localhost:5001/api/Products/2
> curl -i -k -X PUT -H "Content-Type: application/json" -d "{\"id\":2,\"name\":\"Knotted Rope\",\"price\":14.99}" https://localhost:44361/api/Products/2 HTTP/1.1 204 No Content Server: Microsoft-IIS/10.0 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccGhhaXNhcm5zXHNvdXJjZVxyZXBvM1xDb250b3NvUGV0c1xDb250b3NvUGV0c1xhcGlcUHJvZHVjdHNcMg==?= X-Powered-By: ASP.NET Date: Tue, 18 Jun 2019 05:05:48 GMT
ได้ 204 คืออัพเดทโปรดักสำเร็จ
5.ส่ง HTTP DELETE request
curl -i -k -X DELETE https://localhost:5001/api/Products/1
>curl -i -k -X DELETE https://localhost:44361/api/Products/1 HTTP/1.1 204 No Content Server: Microsoft-IIS/10.0 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccGhhaXNhcm5zXHNvdXJjZVxyZXBvM1xDb250b3NvUGV0c1xDb250b3NvUGV0c1xhcGlcUHJvZHVjdHNcMQ==?= X-Powered-By: ASP.NET Date: Tue, 18 Jun 2019 05:11:17 GMT
ได้ 204 คือลบโปรดักสำเร็จ
ตอนนี้ลบ Product id 1 แล้ว ถ้าเรียก id 1 จะได้ 404
> curl -k -s https://localhost:44361/api/Products/1 { "type":"https://tools.ietf.org/html/rfc7231#section-6.5.4", "title":"Not Found", "status":404, "traceId":"80000002-0004-ff00-b63f-84710c7967bb" }
6.ส่ง HTTP GET request
curl -k -s https://localhost:44361/api/Products | jq
> curl -k -s https://localhost:44361/api/Products [ { "id":2, "name":"Knotted Rope", "price":14.99 }, { "id":3, "name":"Plush Squirrel", "price":12.99 } ]
ตอนนี้เหลือแค่ Product
2 และ 3