创建服务
- 使用.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版本不是这样的
示例代码
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, 它返回以下内容:
- 它返回的是HTTP 201(已创建)
- 序列化的对象
- 而且返回的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();
}
}
}