探索 ASP.NET WebAPI (.NET Framework)

2023-02-23

探索 ASP.NET WebAPI 加入專案,使用 Northwind Database 處理的「不需要資料合約名稱」問題以及常見的 Json 使用 camelCase 回應的設定方式。

logo

說明

Global.asax

protected void Application_Start()
{
    GlobalConfiguration.Configure(WebApiConfig.Register);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
}

Global.asax 加入 WebApi 的註冊,要注意的是註冊必須在 Routes 的註冊之前完成。

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        GlobalConfiguration.Configuration.Formatters.Clear();
        GlobalConfiguration.Configuration.Formatters.Add(new JsonMediaTypeFormatter());
        // GlobalConfiguration.Configuration.Formatters.Add(new XmlMediaTypeFormatter());

        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        var settings = config.Formatters.JsonFormatter.SerializerSettings;
        settings.Formatting = Formatting.Indented;
        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    }
}

使用 Formatters.Clear 可以清除預設的 Formatters (包含 Json 以及 XML),而重新加入 Json 後,就不會因為所收到的 Header 不同,提供不同的回應,固定只會以 Json 進行回應。

此外藉由 JsonFormatter.SerializerSettings 可以設定回應的縮排以及命名方式 (相較於 .NET 的 PascalCase Json 的世界喜歡用 camelCase)。

Trouble Shooting

使用 Northwind Database 的 Customer 當作 WebAPI 的回應內容會發生以下錯誤:

<ExceptionMessage>不需要資料合約名稱為 'Customer_D5F79FA43AD7E38F57F7895655DF9753781705EBD3ED0A7DE5C4FF20C5DE9F7F: http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' 的類型 'System.Data.Entity.DynamicProxies.Customer_D5F79FA43AD7E38F57F7895655DF9753781705EBD3ED0A7DE5C4FF20C5DE9F7F'。如果您使用 DataContractSerializer,請考慮使用 DataContractResolver,或將任何統計上不明的類型加入已知類型清單 - 例如,使用 KnownTypeAttribute 屬性,或將它們加入已傳送給序列化程式的已知類型清單。</ExceptionMessage>

參考 ASP.NET MVC 4 WebApi 使用 Northwind 建置 Controller 所遇到的問題 的說明,只要使用 db.Configuration.ProxyCreationEnabled = false; 即可修正。

反覆在每個 Action 當中使用很麻煩,可以在 DbContext 或者是繼承的 Controller 來達到全域設定。

[ResponseType(typeof(Customer))]
public async Task<IHttpActionResult> GetCustomer(string id)
{
    db.Configuration.ProxyCreationEnabled = false;

    Customer customer = await db.Customers.FindAsync(id);
    if (customer == null)
    {
        return NotFound();
    }

    return Ok(customer);
}

Scaffolding

 public IQueryable<Customer> GetCustomers()
{
    db.Configuration.ProxyCreationEnabled = false;
    return db.Customers;
}

// GET: api/CustomerInfo/BERGS
[ResponseType(typeof(Customer))]
public async Task<IHttpActionResult> GetCustomer(string id)
{
    db.Configuration.ProxyCreationEnabled = false;

    Customer customer = await db.Customers.FindAsync(id);
    if (customer == null)
    {
        return NotFound();
    }

    return Ok(customer);
}

// PUT: api/CustomerInfo/BERGS
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutCustomer(string id, Customer customer)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != customer.CustomerID)
    {
        return BadRequest();
    }

    db.Entry(customer).State = EntityState.Modified;

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!CustomerExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}

// POST: api/CustomerInfo
[ResponseType(typeof(Customer))]
public async Task<IHttpActionResult> PostCustomer(Customer customer)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Customers.Add(customer);

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateException)
    {
        if (CustomerExists(customer.CustomerID))
        {
            return Conflict();
        }
        else
        {
            throw;
        }
    }

    return CreatedAtRoute("DefaultApi", new { id = customer.CustomerID }, customer);
}

// DELETE: api/CustomerInfo/BERGS
[ResponseType(typeof(Customer))]
public async Task<IHttpActionResult> DeleteCustomer(string id)
{
    Customer customer = await db.Customers.FindAsync(id);
    if (customer == null)
    {
        return NotFound();
    }

    db.Customers.Remove(customer);
    await db.SaveChangesAsync();

    return Ok(customer);
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        db.Dispose();
    }
    base.Dispose(disposing);
}

private bool CustomerExists(string id)
{
    return db.Customers.Count(e => e.CustomerID == id) > 0;
}