本篇同步發文在個人Blog: 一袋.NET要扛幾樓?打造容器化的ASP.NET Core網站!系列文章 - (5) 建立商品服務的Api - 3
在CatalogApi專案的根目錄,新增CatalogSettings類別,裡面只有一個字串類型的ExternalCatalogBaseUrl Property,是用來從appSettings/環境變數讀取該Catalog Api的Base Url。
在Startup.cs將Configuration注入CatalogSettings參數:
public void ConfigureServices(IServiceCollection services)
{
// other code...
services.Configure<CatalogSettings>(Configuration);
// other code...
}
在appSettings.json,新增ExternalCatalogBaseUrl屬性與值,而值是以launchSettings.json的URL,後續在容器化使用其他的URL:
"ExternalCatalogBaseUrl" : "http://localhost:13914"
在CatalogApi專案的資料夾Controllers,新增CatalogController類別,並繼承ControllerBase,也增加屬性[Route("api/[controller]")]與[ApiController]。
使用Constructor的注入方式,將資料庫的Context與自定義的參數寫入Controller。
private readonly CatalogContext _catalogContext;
private readonly IOptionsSnapshot<CatalogSettings> _settings;
public CatalogController(CatalogContext catalogContext, IOptionsSnapshot<CatalogSettings> settings)
{
_catalogContext = catalogContext;
_settings = settings;
}
此Action回傳所有商品類別。
[HttpGet]
[Route("[action]")]
public async Task<IActionResult> CatalogTypes()
{
var items = await _catalogContext.CatalogTypes.ToArrayAsync();
return Ok(items);
}
此Action接收int Id的參數,並從資料表Catalog找有這Id的商品資訊。但由於資料庫的商品圖片只有存檔,而連結需要被更換,所以在專案新增ViewModels的資料夾,新增一個CatalogItemResponseVM的類別,只定義須回傳到頁面的Property
namespace CatalogApi.ViewModels
{
public class CatalogItemResponseVM
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string PictureUrl { get; set; }
}
}
Action和換照片連結的函式:
private const string pictureUrlTemplate = "/api/picture/{0}";
[HttpGet]
[Route("items/{id:int}")]
public async Task<IActionResult> GetItemById(int id)
{
if (id <= 0)
{
return BadRequest();
}
var item = await _catalogContext.CatalogItems
.Select(x => new CatalogItemResponseVM {
Description = x.Description,
Id = x.Id,
Name = x.Name,
Price = x.Price,
PictureUrl = x.PictureFileName
})
.SingleOrDefaultAsync(c => c.Id == id);
if (item != null)
{
item.PictureUrl = ChangeItemPictureUrl(item.PictureUrl);
return Ok(item);
}
return NotFound();
}
private string ChangeItemPictureUrl(string fileName)
{
return _settings.Value.ExternalCatalogBaseUrl + string.Format(pictureUrlTemplate, fileName);
}
此Action接收int? catalogTypeId 商品類別編號與分頁的參數,在資料表過濾這些條件,並回傳多筆的商品資訊。由於有分頁功能,在ViewModels底下建立一個共通性的分頁PaginatedItemsViewModel類別
using System.Collections.Generic;
namespace CatalogApi.ViewModels
{
public class PaginatedItemsViewModel<TEntity> where TEntity : class
{
public int PageSize { get; set; }
public int PageIndex { get; set; }
public long Count { get; set; }
public IEnumerable<TEntity> Data { get; set; }
public PaginatedItemsViewModel(int pageSize, int pageIndex, long count, IEnumerable<TEntity> data)
{
PageSize = pageSize;
PageIndex = pageIndex;
Count = count;
Data = data;
}
}
}
而Action為預設每頁呈現6筆資料,從第0頁開始查詢:
//Get api/Catalog/items[?catalogTypeId=&pageSize=4&pageIndex=3]
[HttpGet]
[Route("[action]")]
public async Task<IActionResult> Items(int? catalogTypeId, [FromQuery] int pageSize = 6, [FromQuery] int pageIndex = 0)
{
var root = _catalogContext.CatalogItems.AsQueryable();
if (catalogTypeId.HasValue)
{
root = root.Where(c => c.CatalogTypeId == catalogTypeId);
}
var totalItems = await root
.LongCountAsync();
var itemsOnPage = await root
.Select(x => new CatalogItemResponseVM
{
Description = x.Description,
Id = x.Id,
Name = x.Name,
Price = x.Price,
PictureUrl = x.PictureFileName
})
.OrderBy(c => c.Name)
.Skip(pageSize * pageIndex)
.Take(pageSize)
.ToListAsync();
ChangeItemPictureUrls(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItemResponseVM>(pageIndex, pageSize, totalItems, itemsOnPage);
return Ok(model);
}
private void ChangeItemPictureUrls(List<CatalogItemResponseVM> list)
{
list.ForEach(x => x.PictureUrl = ChangeItemPictureUrl(x.PictureUrl));
}
此Action為Http Post,在CatalogItems新增CatalogItem物件,並回傳Status Code 201
[HttpPost]
[Route("items")]
public async Task<IActionResult> CreateProduct([FromBody] CatalogItem product)
{
var item = new CatalogItem
{
CatalogBrandId = product.CatalogBrandId,
CatalogTypeId = product.CatalogTypeId,
Description = product.Description,
Name = product.Name,
PictureFileName = product.PictureFileName,
Price = product.Price
};
_catalogContext.CatalogItems.Add(item);
await _catalogContext.SaveChangesAsync();
return CreatedAtAction(nameof(GetItemById), new { id = item.Id }, item);
}
此Action為Http Put,在CatalogItems更新CatalogItem物件,並回傳Status Code 201
[HttpPut]
[Route("items")]
public async Task<IActionResult> UpdateProduct([FromBody] CatalogItem productToUpdate)
{
var catalogItem = await _catalogContext.CatalogItems
.SingleOrDefaultAsync(c => c.Id == productToUpdate.Id);
if (catalogItem == null)
{
return NotFound(new { Message = $"item with id {productToUpdate.Id} not found." });
}
catalogItem = productToUpdate;
_catalogContext.CatalogItems.Update(catalogItem);
await _catalogContext.SaveChangesAsync();
return CreatedAtAction(nameof(GetItemById), new { id = productToUpdate.Id }, catalogItem);
}
此Action為Http Delete,在CatalogItems根據Id刪除CatalogItem物件,並回傳Status Code 204
[HttpDelete]
[Route("{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
var product = await _catalogContext.CatalogItems.SingleOrDefaultAsync(p => p.Id == id);
if(product == null)
{
return NotFound();
}
_catalogContext.CatalogItems.Remove(product);
await _catalogContext.SaveChangesAsync();
return NoContent();
}
在CatalogApi專案的資料夾Controllers,新增PictureController類別,並繼承ControllerBase,也增加屬性[Route("api/[controller]")]與[ApiController]。
而它主要是讀取商品的照片,並回傳File Result:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.IO;
namespace CatalogApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PictureController : ControllerBase
{
private readonly IWebHostEnvironment _env;
public PictureController(IWebHostEnvironment env)
{
_env = env;
}
[HttpGet]
[Route("{fileName}")]
public IActionResult GetImage(string fileName)
{
var webRoot = _env.WebRootPath;
var path = Path.Combine(webRoot + "/Pictures/", fileName);
var buffer = System.IO.File.ReadAllBytes(path);
return File(buffer, "image/png");
}
}
}
開啟Debug,根據你的Host設定,網址輸入 http://yourhost:yourport/api/catalog/items ,應該要能看到多筆分頁的功能能得到商品資訊:
下一篇將撰寫Swagger UI的配置與測試API CRUD。