Xây dựng GraphQL API với ASP.NET Core

Xây dựng GraphQL API với ASP.NET Core

Ngày nay, mọi người lướt facebook nhiều hơn là đọc sách. Với mong muốn giúp mọi người dành nhiều thời gian cho đọc sách hơn là lướt facebook. Vì vậy, chúng tôi sẽ tạo một API cung cấp sách.

API trong bài viết này được viết bằng ASP.NET Core 2.2 sử dụng GraphQL, cho phép các nhà phát triển tạo các ứng dụng sử dụng API này một cách an toàn.

ASP.NET Core 2.2 cung cấp khả năng lưu trữ API trên bất kỳ nền tảng nào và GraphQL giúp các nhà phát triển dễ dàng truy vấn API và nhận dữ liệu theo cách họ muốn.

Tất cả những gì bạn cần cho hướng dẫn này là phiên bản 2.2 hoặc mới hơn của .NET Core SDK .

Chúng tôi sử dụng VS Code để viết API này, nhưng bạn hãy sử dụng trình soạn thảo hoặc IDE ưa thích của bạn.

Nào cùng bắt đầu thôi!

Bên trong thư mục gốc của dự án, bạn tạo hai thư mục: ApiClient. Trong đó thư mục Api sẽ chứa mã nguồn của API và thư mục Client sẽ chứa ứng dụng console để kết nối tới API.

Tạo ASP.NET Core API của bạn cho Kiến trúc GraphQL của bạn

Bên trong thư mục Api bạn chạy lệnh sau (không có ký > nhé):

> dotnet new webapi

Vì bạn sẽ chỉ sử dụng ứng dụng của mình cục bộ, bạn có thể tắt chuyển hướng HTTPS. Mở file Startup.cs của bạn và tìm đến phương thức Configure. Cập nhật nội dung của phương thức này để trông như thế này:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
  }
  app.UseMvc();
}

Tạo các lớp mô hình ASP.NET Core API

Trong thư mục Api của bạn, hãy tạo một thư mục mới có tên là Database. Bên trong thư mục đó tạo một lớp Book.cs như sau:

namespace Api.Database
{
  public class Book
  {
    public string Id { get; set; }

    public string Name { get; set; }

    public bool Published { get; set; }

    public string Genre { get; set; }

    public string AuthorId { get; set; }

    public Author Author { get; set; }
  }
}

Bên trong thư mục Database bạn tiếp tục tạo lớp Author.cs với nội dung như sau:

using System.Collections.Generic;

namespace Api.Database
{
  public class Author
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Book> Books { get; set; }
  }
}

Thiết lập cơ sở dữ liệu của bạn

Bây giờ bạn sẽ cần thiết lập kết nối của bạn với cơ sở dữ liệu. Đối với hướng dẫn này, bạn sẽ sử dụng cơ sở dữ liệu InMemory với Entity Framework Core.

Trong thư mục Database bạn tạo một file mới tên là ApplicationDbContext.cs có nội dung như sau:

using Microsoft.EntityFrameworkCore;

namespace Api.Database {

public class ApplicationDbContext : DbContext
{
  public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
      { }

    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }
  }
}

Để thêm DbContext vào ứng dụng của bạn. Bên trong lớp Startup, tìm phương thức ConfigureServices và thêm phần sau vào đầu phương thức:


services.AddDbContext<ApplicationDbContext>(context =>
{
    context.UseInMemoryDatabase("GraphQL");
});

Đảm bảo bạn thêm các chỉ thị using cần thiết sau ở đầu file Startup.cs:


using Microsoft.EntityFrameworkCore;
using Api.Database;

Đoạn mã trên cho phép Entity Framework sử dụng cơ sở dữ liệu trong bộ nhớ có tên GraphQL.

Loại cơ sở dữ liệu này thường được sử dụng cho các thử nghiệm và bạn không nên sử dụng nó trong môi trường production. Tuy nhiên, điều này là quá đủ để đáp ứng nhu cầu của hướng dẫn này.

Tạo dữ liệu mẫu

Vì bạn đang sử dụng cơ sở dữ liệu trong bộ nhớ, dữ liệu sẽ bị mất sau mỗi lần khởi động ứng dụng. Do đó, bạn có thể khởi tạo một vài dữ liệu mẫu khi ứng dụng của chúng tôi khởi chạy. Cập nhật nội dung của file Program.cs với mã sau:

using Api.Database;

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Api
{
  public class Program
  {
    public static void Main(string[] args)
    {
      IWebHost host = CreateWebHostBuilder(args).Build();
      using (IServiceScope scope = host.Services.CreateScope())
      {
        ApplicationDbContext context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

        var authorDbEntry = context.Authors.Add(
          new Author
          {
            Name = "First Author",
          }
        );

        context.SaveChanges();

        context.Books.AddRange(
          new Book
          {
            Name = "First Book",
            Published = true,
            AuthorId = authorDbEntry.Entity.Id,
            Genre = "Mystery"
          },
          new Book
          {
            Name = "Second Book",
            Published = true,
            AuthorId = authorDbEntry.Entity.Id,
            Genre = "Crime"
          }
        );
      }
      host.Run();
  }

  public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
      WebHost.CreateDefaultBuilder(args)
          .UseStartup<Startup>();
  }
}

Thiết lập GraphQL trong ASP.NET Core API

Bây giờ bạn có thể bắt đầu thêm mã liên quan đến GraphQL. Trước tiên, bạn sẽ thiết lập một số middleware giúp việc kiểm tra đầu cuối GraphQL của bạn dễ dàng hơn rất nhiều.

GraphQL Middleware

GraphiQL một IDE trong trình duyệt để khám phá GraphQL. Các tính năng của GraphiQL bao gồm: tô màu cú pháp, nhắc trường và kiểu khi gõ và nhiều tính năng khác. Nó giúp làm nổi bật lỗi thời gian thực và báo cáo. Hoàn thành truy vấn tự động. Chạy và kiểm tra kết quả truy vấn.

GraphiQL.NET giúp bạn không cần thêm bất kỳ sự phụ thuộc nào bằng cách cho phép bạn đưa trình soạn thảo GraphiQL trực tiếp vào ứng dụng ASP.NET Core của bạn thông qua middleware, cho phép bạn khám phá và kiểm tra đầu cuối GraphQL một cách dễ dàng. Hãy chắc chắn rằng bạn đang ở trong thư mục Api của mình và chạy lệnh sau:

dotnet add package GraphQL --version 2.4.0
dotnet add package graphiql --version 1.2.0

Khi đã xong, bạn có thể thêm GraphiQL.NET vào ứng dụng ASP.NET Core của mình bằng cách thêm middleware app.UseGraphiQl() vào phương thức Configure trong file Startup.cs của bạn. Thêm chỉ thị using cho GraphiQL.NET như sau:

using GraphiQl;

Sau đó thêm dòng sau vào trước câu lệnh app.UseMvc();:

app.UseGraphiQl("/graphql");

Đoạn mã này sẽ đảm bảo rằng giao diện GraphiQL.NET chạy tại địa chỉ /graphql.

Truy vấn GraphQL

Vì bạn muốn lấy dữ liệu về sách và tác giả, bạn cần truy vấn các lớp BookAuthor. Tuy nhiên, bạn không thể trực tiếp sử dụng truy vấn GraphQL đối với các lớp này.

Để làm cho GraphQL có thể truy vấn được lớp Book, bạn nên tạo một kiểu dữ liệu mới và mở rộng nó từ kiểu generic là ObjectGraphType<T>và bạn sẽ truyền kiểu Book làm đối số chung của nó.

Bên trong thư mục Api của bạn tạo một thư mục mới có tên GraphQL. Bây giờ bạn có thể tạo một lớp mới BookType.cs bên trong thư mục GraphQL. Dán nội dung sau vào trong file đó:

using Api.Database;
using GraphQL.Types;

namespace Api.GraphQL
{
  public class BookType : ObjectGraphType<Book>
  {
    public BookType()
    {
      Name = "Book";

      Field(x => x.Id, type: typeof(IdGraphType)).Description("The ID of the Book.");
      Field(x => x.Name).Description("The name of the Book");
      Field(x => x.Genre).Description("Book genre");
      Field(x => x.Published).Description("If the book is published or not");
    }
  }
}

Bên trong thư mục này, bạn tiếp tục tạo một lớp khác tên là AuthorType và dán nội dung sau vào bên trong:

using Api.Database;
using GraphQL.Types;

namespace Api.GraphQL
{
  public class AuthorType : ObjectGraphType<Author>
  {
    public AuthorType()
    {
      Name = "Author";

      Field(x => x.Id, type: typeof(IdGraphType)).Description("Author's ID.");
      Field(x => x.Name).Description("The name of the Author");
      Field(x => x.Books, type: typeof(ListGraphType<BookType>)).Description("Author's books");
    }
  }
}

Bây giờ bạn có thể viết truy vấn GraphQL để xử lý tìm kiếm một tác giả và danh sách các tác giả. Tạo một file mới tên là AuthorQuery.cs trong thư mục GraphQL và dán đoạn mã sau vào file:

using System.Linq;
using Api.Database;
using GraphQL.Types;
using Microsoft.EntityFrameworkCore;

namespace Api.GraphQL
{
  public class AuthorQuery : ObjectGraphType
  {
    public AuthorQuery(ApplicationDbContext db)
    {
      Field<AuthorType>(
        "Author",
        arguments: new QueryArguments(
          new QueryArgument<IdGraphType> { Name = "id", Description = "The ID of the Author." }),
        resolve: context =>
        {
          var id = context.GetArgument<int>("id");
          var author = db
            .Authors
            .Include(a => a.Books)
            .FirstOrDefault(i => i.Id == id);
          return author;
        });

      Field<ListGraphType<AuthorType>>(
        "Authors",
        resolve: context =>
        {
          var authors = db.Authors.Include(a => a.Books);
          return authors;
        });
    }
  }
}

Máy khách sẽ luôn gửi yêu cầu POST chứa tên truy vấn, tên thao tác và các biến. Bạn có thể tạo một lớp mới sẽ đóng vai trò là mô hình cho tất cả các truy vấn từ máy khách. Tạo một file mới có tên GraphQLQuery.cs trong thư mục GraphQL và dán đoạn mã sau:

using Newtonsoft.Json.Linq;

namespace Api.GraphQL
{
  public class GraphQLQuery
  {
    public string OperationName { get; set; }
    public string NamedQuery { get; set; }
    public string Query { get; set; }
    public JObject Variables { get; set; }
  }
}

GraphQL Controller

Bây giờ bạn có thể tạo GraphQL Controller sẽ phụ trách tất cả các truy vấn GraphQL từ khách hàng của bạn. Trong thư mục GraphQL, tạo một file mới có tên GraphQLController.cs. Bây giờ bạn có thể dán đoạn mã sau vào controller:

using System.Threading.Tasks;
using Api.Database;
using GraphQL;
using GraphQL.Types;
using Microsoft.AspNetCore.Mvc;

namespace Api.GraphQL
{
  [Route("graphql")]
  [ApiController]
  public class GraphQLController : Controller
  {
    private readonly ApplicationDbContext _db;

    public GraphQLController(ApplicationDbContext db) => _db = db;

    public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
    {
      var inputs = query.Variables.ToInputs();

      var schema = new Schema
      {
        Query = new AuthorQuery(_db)
      };

      var result = await new DocumentExecuter().ExecuteAsync(_ =>
      {
        _.Schema = schema;
        _.Query = query.Query;
        _.OperationName = query.OperationName;
        _.Inputs = inputs;
      });

      if(result.Errors?.Count > 0)
      {
        return BadRequest();
      }

      return Ok(result);
      }
  }
}

Kiểm tra API ASP.NET Core GraphQL của bạn

Bên trong thư mục Client bạn chạy như sau:

> dotnet new console

Bây giờ bạn có thể thêm các gói NuGet cần thiết cho ứng dụng console của mình bằng cách chạy lệnh sau:

> dotnet add package Newtonsoft.Json --version 12.0.1

Bên trong file Program.cs bạn dán đoạn mã sau vào cuối phương thức Main:

var query = @"
{
  author(id: 1) {
    name
  }
}";

var postData = new { Query = query };
var stringContent = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json");

var httpClient = new HttpClient();

var res = await httpClient.PostAsync(postUri, stringContent);
if (res.IsSuccessStatusCode)
{
  var content = await res.Content.ReadAsStringAsync();

  Console.WriteLine(content);
}
else
{
  Console.WriteLine($"Error occurred... Status code:{res.StatusCode}");
}

Console.ReadLine();

Vì bạn đang sử dụng từ khóa await, bạn cần sử dụng từ khóa async cho phương thức Main. Thay đổi chữ ký của phương thức Main của bạn để trông như thế này:

static async Task Main(string[] args)

Bạn cũng sẽ cần phải thêm chỉ thị using như dưới đây cho Task.

using System.Threading.Tasks;

Chạy ứng dụng ASP.NET Core GraphQL của bạn

Mở hai cửa sổ dòng lệnh: một truy cập vào trong thư mục Client và một truy cập vào trong thư mục Api của bạn. Bên trong thư mục Api nhập lệnh bash sau:

> dotnet run

Câu lệnh này sẽ khởi chạy ứng dụng API và đảm bảo ứng dụng của chúng ta lắng nghe mọi yêu cầu đến.

Đã đến lúc kiểm tra ứng dụng console của bạn. Bên trong thư mục Client bạn chạy cùng một lệnh như sau:

> dotnet run

Nếu mọi thứ hoạt động như mong đợi, bạn sẽ nhận được đầu ra sau bên trong cửa sổ dòng lệnh của mình:


{"data":{"author":{"name":"First Author"}}}

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *