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

286 阅读4分钟

创建服务

  • 使用.NET Core时,需要从ASP.NET Core Web Application开始,并选择Web API模板
  • 如果需要Web页面,还可以使用Web Application(Model-View-Controller)模板
  • ASP.NET Core MVC的Web API和ASP.NET Core MVC使用相同的基础设施。对比之下,ASP.NET的.NET Framework版本不是这样的

示例代码

github.com/Professiona…

BooksServiceSampleHost

namespace BooksServiceSampleHost
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}
namespace BooksServiceSampleHost
{
    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.AddMvc().AddXmlSerializerFormatters();
            services.AddSingleton<IBookChaptersService, BookChaptersService>();
            services.AddSingleton<SampleChapters>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, SampleChapters sampleChapters)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
            sampleChapters.CreateSampleChapters();
        }
    }
}

创建SampleChapters的操作在Configure()方法中完成,留意到,SampleChapters还加到了Configure的参数列表里

appsettings.json

{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "ConnectionStrings": {
    "BookConnection": "server=(localdb)\\mssqllocaldb;database=APIBooksSample;trusted_connection=true"
  }
}

launchsettings.json

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:65141/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "BooksServiceSampleHost": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5000"
    }
  }
}

APIBookServices

选择的是Web API Controller Class

ASP.NET Web API的.NET Framework版,它返回JSON或者XML,具体返回什么,取决于客户端请求的格式而ASP.NET Core MVC中,当返回ObjectResult时,默认返回JSON。如果想返回XML,可以在Startup类的ConfigureServices()里添加对IMVCBuilder的AddXmlSerializerFormatters的调用

Get方法里

  • Get全部,返回IEnumerable<BookChapter>
  • 按ID找到了,返回ObjectResult,它返回状态码200
  • 按ID没找到,返回NotFound,它返回一个404(未找到)响应

Post方法里

  • 如果要添加的BookChapter是null, 则返回BadRequest, 它返回400。
  • 如果创建成功了,就返回CreatedAtRoute, 它返回以下内容:
    1. 它返回的是HTTP 201(已创建)
    2. 序列化的对象
    3. 而且返回的Header里面包含到资源的连接,即到GetBookChapterById这个Get方法的链接,其id设置为新建对象的标识符

Put方法里更新已有条目:

  • 没找到已有条目,返回NotFound,它返回一个404(未找到)响应
  • 找到了已有条目,并更新了它。返回NoContentResult, 它返回204
using BooksServiceSample.Models;
using BooksServiceSample.Services;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

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]
        public IEnumerable<BookChapter> GetBookChapters() => _bookChaptersService.GetAll();

        // GET api/bookchapters/guid
        [HttpGet("{id}", Name = nameof(GetBookChapterById))]
        public IActionResult GetBookChapterById(Guid id)
        {
            BookChapter chapter = _bookChaptersService.Find(id);
            if (chapter == null)
            {
                return NotFound();
            }
            else
            {
                return new ObjectResult(chapter);
            }
        }

        // POST api/bookchapters
        [HttpPost]
        public IActionResult PostBookChapter([FromBody]BookChapter chapter)
        {
            if (chapter == null)
            {
                return BadRequest();
            }
            _bookChaptersService.Add(chapter);
            return CreatedAtRoute(nameof(GetBookChapterById), new { id = chapter.Id }, chapter);
        }

        // PUT api/bookchapters/guid
        [HttpPut("{id}")]
        public IActionResult PutBookChapter(Guid id, [FromBody]BookChapter chapter)
        {
            if (chapter == null || id != chapter.Id)
            {
                return BadRequest();
            }
            if (_bookChaptersService.Find(id) == null)
            {
                return NotFound();
            }
            _bookChaptersService.Update(chapter);
            return new NoContentResult();
        }

        // DELETE api/bookchapters/5
        [HttpDelete("{id}")]
        public void Delete(Guid id) =>
            _bookChaptersService.Remove(id);
    }
}

BookServices

namespace BooksServiceSample.Models
{
    public class BookChapter
    {
        public Guid Id { get; set; }
        public int Number { get; set; }
        public string Title { get; set; }
        public int Pages { get; set; }
    }
}

BookChapterService注册为单例,因此可以同时从多个线程中访问它,所以需要ConcurrentDictionary

using BooksServiceSample.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace BooksServiceSample.Services
{
    public class BookChaptersService : IBookChaptersService
    {
        private readonly ConcurrentDictionary<Guid, BookChapter> _chapters = new ConcurrentDictionary<Guid, BookChapter>();

        public void Add(BookChapter chapter)
        {
            chapter.Id = Guid.NewGuid();
            _chapters[chapter.Id] = chapter;
        }
        public void AddRange(IEnumerable<BookChapter> chapters)
        {
            foreach (var chapter in chapters)
            {
                chapter.Id = Guid.NewGuid();
                _chapters[chapter.Id] = chapter;
            }
        }
        public BookChapter Find(Guid id)
        {
            _chapters.TryGetValue(id, out BookChapter chapter);
            return chapter;
        }
        public IEnumerable<BookChapter> GetAll() => _chapters.Values;
        public BookChapter Remove(Guid id)
        {
            BookChapter removed;
            _chapters.TryRemove(id, out removed);
            return removed;
        }
        public void Update(BookChapter chapter) =>
            _chapters[chapter.Id] = chapter;
    }
}

Remove方法,确保id对应的BookChapter不在字典中。如果字典中已经不包含BookChapter了,那没关系。Remove方法的另一种实现是,找不到id对应的BookChapter时,抛出异常,Controller可以捕获,从而返回HTTP未找到状态码(404)

using BooksServiceSample.Models;
using System;
using System.Collections.Generic;

namespace BooksServiceSample.Services
{
    public interface IBookChaptersService
    {
        void Add(BookChapter bookChapter);
        void AddRange(IEnumerable<BookChapter> chapters);
        IEnumerable<BookChapter> GetAll();
        BookChapter Find(Guid id);
        BookChapter Remove(Guid id);
        void Update(BookChapter bookChapter);
    }
}

using BooksServiceSample.Models;
using System.Collections.Generic;

namespace BooksServiceSample.Services
{
    public class SampleChapters
    {
        private readonly IBookChaptersService _bookChaptersService;
        public SampleChapters(IBookChaptersService bookChapterService)
        {
            _bookChaptersService = bookChapterService;
        }

        private string[] sampleTitles = new[]
        {
            ".NET Application Architectures",
            "Core C#",
            "Objects and Types",
            "Object-Oriented Programming with C#",
            "Generics",
            "Operators and Casts",
            "Arrays",
            "Delegates, Lambdas, and Events",
            "Windows Communication Foundation"
        };
        private int[] chapterNumbers = { 1, 2, 3, 4, 5, 6, 7, 8, 44 };
        private int[] numberPages = { 35, 42, 33, 20, 24, 38, 20, 32, 44 };

        public void CreateSampleChapters()
        {
            var chapters = new List<BookChapter>();
            for (int i = 0; i < 9; i++)
            {
                chapters.Add(new BookChapter
                {
                    Number = chapterNumbers[i],
                    Title = sampleTitles[i],
                    Pages = numberPages[i]
                });
            }
            _bookChaptersService.AddRange(chapters);
        }
    }
}


BookFunctionApp


using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
using System;
using Microsoft.Extensions.DependencyInjection;
using BooksServiceSample.Services;
using BooksServiceSample.Models;

namespace BookFunctionApp
{
    public static class BookFunction
    {
        static BookFunction()
        {
            ConfigureServices();
            FeedSampleChapters();
            GetRequiredServices();
        }

        private static void FeedSampleChapters()
        {
            var sampleChapters = ApplicationServices.GetRequiredService<SampleChapters>();
            sampleChapters.CreateSampleChapters();
        }

        private static void ConfigureServices()
        {
            var services = new ServiceCollection();
            services.AddSingleton<IBookChaptersService, BookChaptersService>();
            services.AddSingleton<SampleChapters>();
            ApplicationServices = services.BuildServiceProvider();
        }

        private static void GetRequiredServices()
        {
            s_bookChaptersService =
              ApplicationServices.GetRequiredService<IBookChaptersService>();
        }

        private static IBookChaptersService s_bookChaptersService;


        public static IServiceProvider ApplicationServices { get; private set; }

        [FunctionName("BookFunction")]
        public static IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", "put", Route = null)]HttpRequest req, TraceWriter log)
        {
            log.Info("C# HTTP trigger function processed a request.");

            IActionResult result = null;
            switch (req.Method)
            {
                case "GET":
                    result = DoGet(req);
                    break;
                case "POST":
                    result = DoPost(req);
                    break;
                case "PUT":
                    result = DoPut(req);
                    break;
                default:
                    break;
            }

            return result;
            //string name = req.Query["name"];

            //string requestBody = new StreamReader(req.Body).ReadToEnd();
            //dynamic data = JsonConvert.DeserializeObject(requestBody);
            //name = name ?? data?.name;

            //return name != null
            //    ? (ActionResult)new OkObjectResult($"Hello, {name}")
            //    : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }

        private static IActionResult DoGet(HttpRequest req)
        {
            var bookChapterService = ApplicationServices.GetRequiredService<IBookChaptersService>();
            var chapters = bookChapterService.GetAll();
            return new OkObjectResult(chapters);
        }

        private static IActionResult DoPost(HttpRequest req)
        {
            string json = new StreamReader(req.Body).ReadToEnd();
            BookChapter chapter = JsonConvert.DeserializeObject<BookChapter>(json);
            s_bookChaptersService.Add(chapter);
            return new OkResult();
        }


        private static IActionResult DoPut(HttpRequest req)
        {
            string json = new StreamReader(req.Body).ReadToEnd();
            BookChapter chapter = JsonConvert.DeserializeObject<BookChapter>(json);
            s_bookChaptersService.Update(chapter);
            return new OkResult();
        }

    }
}