c# 高级编程 32章826页 【Web API】【创建异步服务】

200 阅读3分钟

创建异步服务

  • 有些时候服务的实现里需要调用一些异步方法,比如异步访问网络(e.g. 使用HttpClient类)或数据库
  • 这时需要将服务实现成异步的,例如下面的服务,包含的都是些异步方法
namespace BooksServiceSample.Services
{
    public interface IBookChaptersService
    {
        Task AddAsync(BookChapter chapter);
        Task AddRangeAsync(IEnumerable<BookChapter> chapters);
        Task<BookChapter> RemoveAsync(Guid id);
        Task<IEnumerable<BookChapter>> GetAllAsync();
        Task<BookChapter> FindAsync(Guid id);
        Task UpdateAsync(BookChapter chapter);
    }
}

示例代码:

  • 读写字典时,虽然此处定义为异步方法,但是实际上不需要异步,所以返回的Task使用FromResult方法创建,此有点“假异步”的意思,为一个简单实例
  • 原本是void的,此时返回Task.CompletedTask
  • 原本有返回值的,此时返回Task.FromResult(返回值)
namespace BooksServiceSample.Services
{
    public class BookChaptersService : IBookChaptersService
    {
        private readonly ConcurrentDictionary<Guid, BookChapter> _chapters = new ConcurrentDictionary<Guid, BookChapter>();

        public Task AddAsync(BookChapter chapter)
        {
            chapter.Id = Guid.NewGuid();
            _chapters[chapter.Id] = chapter;
            return Task.CompletedTask;
        }

        public Task AddRangeAsync(IEnumerable<BookChapter> chapters)
        {
            foreach (var chapter in chapters)
            {
                chapter.Id = Guid.NewGuid();
                _chapters[chapter.Id] = chapter;
            }
            return Task.CompletedTask;
        }

        public Task<BookChapter> RemoveAsync(Guid id)
        {
            _chapters.TryRemove(id, out BookChapter removed);
            return Task.FromResult(removed);
        }

        public Task<IEnumerable<BookChapter>> GetAllAsync() =>
          Task.FromResult<IEnumerable<BookChapter>>(_chapters.Values);

        public Task<BookChapter> FindAsync(Guid id)
        {
            _chapters.TryGetValue(id, out BookChapter chapter);
            return Task.FromResult(chapter);
        }

        public Task UpdateAsync(BookChapter chapter)
        {
            _chapters[chapter.Id] = chapter;
            return Task.CompletedTask;
        }
    }
}
  • Controller只需要一点变化,就可以实现为异步版本
  • Controller方法也返回一个Task, 这样就会很容易调用IBookChaptersService的异步方法
  • 对于客户端来说,Controller实现为同步还是异步,并不重要。API的调用方式是相同的,客户端会为两种情形创建相同个Http请求
namespace BooksServiceSample.Controllers
{
    [Produces("application/json", "application/xml")]
    [Route("api/[controller]")]
    public class BookChaptersController : Controller
    {
        private readonly IBookChaptersService _bookChaptersService;
        public BookChaptersController(IBookChaptersService bookChaptersService)
        {
            _bookChaptersService = bookChaptersService;
        }

        // GET: api/bookchapters
        [HttpGet()]
        [ProducesResponseType(typeof(IEnumerable<BookChapter>), 200)]
        public Task<IEnumerable<BookChapter>> GetBookChaptersAsync() =>
          _bookChaptersService.GetAllAsync();

        // GET api/bookchapters/guid
        [HttpGet("{id}", Name = nameof(GetBookChapterByIdAsync))]
        [ProducesResponseType(typeof(BookChapter), 200)]
        [ProducesResponseType(404)]
        public async Task<IActionResult> GetBookChapterByIdAsync(Guid id)
        {
            BookChapter chapter = await _bookChaptersService.FindAsync(id);
            if (chapter == null)
            {
                return NotFound();
            }
            else
            {
                return new ObjectResult(chapter);
            }
        }

        // POST api/bookchapters
        /// <summary>
        /// Creates a BookChapter
        /// </summary>
        /// <remarks>
        /// Sample request:
        ///     POST api/bookchapters
        ///     {
        ///       Number: 42,
        ///       Title: "Sample Title",
        ///       Pages: 98
        ///     }
        /// </remarks>
        /// <param name="chapter"></param>
        /// <returns>A newly created book chapter</returns>
        /// <response code="201">Returns the newly created book chapter</response>
        /// <response code="400">If the chapter is null</response>
        [HttpPost]
        [ProducesResponseType(typeof(BookChapter), 201)]
        [ProducesResponseType(400)]
        public async Task<IActionResult> PostBookChapterAsync(
          [FromBody]BookChapter chapter)
        {
            if (chapter == null)
            {
                return BadRequest();
            }
            await _bookChaptersService.AddAsync(chapter);
            return CreatedAtRoute(nameof(GetBookChapterByIdAsync),
              new { id = chapter.Id }, chapter);
        }

        // PUT api/bookchapters/guid
        [HttpPut("{id}")]
        [ProducesResponseType(204)]
        [ProducesResponseType(400)]
        [ProducesResponseType(404)]
        public async Task<IActionResult> PutBookChapterAsync(
          Guid id, [FromBody]BookChapter chapter)
        {
            if (chapter == null || id != chapter.Id)
            {
                return BadRequest();
            }
            if (await _bookChaptersService.FindAsync(id) == null)
            {
                return NotFound();
            }
            await _bookChaptersService.UpdateAsync(chapter);
            return new NoContentResult();
        }

        // DELETE api/bookchapters/guid
        [HttpDelete("{id}")]
        public async Task DeleteAsync(Guid id) =>
            await _bookChaptersService.RemoveAsync(id);
    }
}
  • 如下这个IBookChaptersService的实现,不再是从字典里“假异步真同步”读写值,而是从数据库里“真异步”读写值
  • 从数据库里读写值,使用了Entity Framework Core, 它支持异步读写值
namespace BooksServiceSample.Services
{
    public class DBBookChaptersService : IBookChaptersService
    {
        private readonly BooksContext _booksContext;
        public DBBookChaptersService(BooksContext booksContext)
        {
            _booksContext = booksContext;
        }
        public async Task AddAsync(BookChapter chapter)
        {
            await _booksContext.Chapters.AddAsync(chapter);
            await _booksContext.SaveChangesAsync();
        }

        public async Task AddRangeAsync(IEnumerable<BookChapter> chapters)
        {
            await _booksContext.Chapters.AddRangeAsync(chapters);
            await _booksContext.SaveChangesAsync();
        }

        public Task<BookChapter> FindAsync(Guid id) =>
            _booksContext.Chapters.FindAsync(id);

        public async Task<IEnumerable<BookChapter>> GetAllAsync() =>
            await _booksContext.Chapters.ToListAsync();

        public async Task<BookChapter> RemoveAsync(Guid id)
        {
            BookChapter chapter = await _booksContext.Chapters.SingleOrDefaultAsync(c => c.Id == id);
            if (chapter == null) return null;

            _booksContext.Chapters.Remove(chapter);
            await _booksContext.SaveChangesAsync();
            return chapter;
        }

        public async Task UpdateAsync(BookChapter chapter)
        {
            _booksContext.Chapters.Update(chapter);
            await _booksContext.SaveChangesAsync();
        }
    }
}
namespace BooksServiceSample.Services
{
    public class SampleChapters
    {
        private readonly IBookChaptersService _bookChaptersService;
        public SampleChapters(IBookChaptersService bookChapterService)
        {
            _bookChaptersService = bookChapterService;
        }

        private string[] sampleTitles = new[]
        {
            ".NET Applications and Tools",
            "Core C#",
            "Objects and Types",
            "Object-Oriented Programming with C#",
            "Generics",
            "Operators and Casts",
            "Arrays",
            "Delegates, Lambdas, and Events"
        };
        private int[] numberPages = { 35, 42, 33, 20, 24, 38, 20, 32 };


        public async Task CreateSampleChaptersAsync()
        {
            var chapters = new List<BookChapter>();
            for (int i = 0; i < 8; i++)
            {
                chapters.Add(new BookChapter
                {
                    Number = i,
                    Title = sampleTitles[i],
                    Pages = numberPages[i]
                });
            }
           await _bookChaptersService.AddRangeAsync(chapters);
        }
    }
}
  • 使用了Entity Framework Core
namespace BooksServiceSample.Models
{
    public class BooksContext : DbContext
    {
        public BooksContext(DbContextOptions<BooksContext> options)
            : base(options)
        {
        }

        public DbSet<BookChapter> Chapters { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BookChapter>()
                .ToTable("Chapters")
                .HasKey(c => c.Id);
            modelBuilder.Entity<BookChapter>()
                .Property(c => c.Id)
                .HasColumnType("UniqueIdentifier")
                .HasDefaultValueSql("newid()");
            modelBuilder.Entity<BookChapter>()
                .Property(c => c.Title)
                .HasMaxLength(120);
                
        }
    }
}
namespace BooksServiceSample.Models
{
    public class BookChapter
    {
        public Guid Id { get; set; }
        [Required]
        public int Number { get; set; }
        [Required]
        [MaxLength(40)]
        public string Title { get; set; }
        [DefaultValue(0)]
        public int Pages { get; set; }
    }
}