当前位置:网站首页 / .NET CORE / 正文

ay的C#8.0和net5高级编程笔记18-构建和消费Web服务

时间:2021年05月12日 | 作者 : aaronyang | 分类 : .NET CORE | 浏览: 1029次 | 评论 0

打开第14章笔记的PracticalApps项目,我们用vs2019新建一个webapi

在vscode命令是 dotnet new webpi

image.png

image.png

image.png

新建后,默认会有个示例WeatherForecastController

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NorthwindService.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

继承了ControllerBase,相比MVC的更简单,因为不会提供View那样的页面,还要Razor。

【Route】特性 用来注册weatherforecast相对URL,以便客户端使用该URL发出HTTP请求,这些HTTP请求将由控制器处理。例如地址栏:https://localhost:5001/weatherforecast/ 的http请求。一些开发人员喜欢前面加个api/,这是在混合项目区分mvc和web",如果输入[controller]就是当前类名,如果输入    [Route("api/forecast")] 就是  https://localhost:5001/api/forecast 

运行后,默认swagger显示webapi的测试页面,方便调试

image.png

ASP.NET CORE2.1引入【ApiController】,支持Rest行为,比如针对无效模型自动HTTP 400响应。

HttpGet就是get请求,现在我们添加一个Get方法

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NorthwindService.Controllers
{
    [ApiController]
    [Route("api/forecast")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        //Get  /api/forecast
        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            return Get(5);
        }

        //Get  /api/forecast/7
        [HttpGet("{days:int}")]
        public IEnumerable<WeatherForecast> Get(int days)
        {
            var rng = new Random();
            return Enumerable.Range(1, days).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }


    }
}

    [HttpGet("{days:int}")]格式模式,days约束为int类型了。

运行后:

image.png

更多路由约束信息-https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-5.0#route-contraint-reference

image.png

image.png


检查Web服务的功能

运行后,浏览器输入 https://localhost:44372/api/forecast/14

image.png



为Northwind数据库创建Web服务

Web最常用的格式json,因为在使用Angular,vue,react,json更合适

NorthwindService项目添加NorthwindContextLib和NorthwindEntitesLib的引用


打开Startup.cs文件,添加AddDbContext

拷贝下数据库到webapi根目录,然后添加以下代码

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "NorthwindService", Version = "v1" });
            });

            string databasePath = System.IO.Path.Combine("Northwind.db");
            services.AddDbContext<Northwind>(options => options.UseSqlite($"Data Source={databasePath}"));

            services.AddControllers(options =>
            {
                Console.WriteLine("默认输出格式");
                foreach (IOutputFormatter formatter in options.OutputFormatters)
                {
                    var mediaFormat = formatter as OutputFormatter;
                    if (mediaFormat == null)
                    {
                        Console.WriteLine($"{formatter.GetType().Name}");
                    }
                    else
                    {
                        Console.WriteLine("{0},MediaType:{1}", mediaFormat.GetType().Name, String.Join(",", mediaFormat.SupportedMediaTypes));
                    }
                }
            }).AddXmlDataContractSerializerFormatters().AddXmlSerializerFormatters().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
        }

设置版本兼容性的好处,https://docs.microsoft.com/zh-cn/aspnet/core/mvc/compatibility-version?view=aspnetcore-3.0


启动web服务,注意四个默认的输出格式化程序,包括将null值转换为204 No Content的格式化程序以 支持纯文本和JSON响应的格式化程序。如下所示

image.png

为实体创建数据存储库

缓存Redis:https://redis.io

现在我们要对客户增删改查

新建一个Repositories文件夹,新建ICustomerRepository

    public interface ICustomerRepository
    {
        Task<Customer> CreateAsync(Customer c);
        Task<IEnumerable<Customer>> RetrieveAllAsync();
        Task<Customer> RetrieveAsync(string id);
        Task<Customer> UpdateAsync(string id, Customer c);

        Task<bool?> DeleteAsync(string id);

    }

实现

    public class CustomerRepository : ICustomerRepository
    {
        /// <summary>
        /// 线程安全的字典,缓存客户
        /// </summary>
        private static ConcurrentDictionary<string, Customer> customersCache;
        private Northwind db;
        public CustomerRepository(Northwind db)
        {
            this.db = db;
            if (customersCache == null)
            {
                customersCache = new ConcurrentDictionary<string, Customer>(db.Customers.ToDictionary(c=>c.CustomerID));
            }
        }

        public async Task<Customer> CreateAsync(Customer c)
        {
            c.CustomerID = c.CustomerID.ToUpper();
            EntityEntry<Customer> added = await db.Customers.AddAsync(c);
            int affected = await db.SaveChangesAsync();
            if (affected == 1)
            {
                return customersCache.AddOrUpdate(c.CustomerID, c, UpdateCache);
            }
            else
            {
                return null;
            }
        }

        public async Task<bool?> DeleteAsync(string id)
        {
            id = id.ToUpper();
            var c = db.Customers.Find(id);
            if (c != null)
            {
                db.Customers.Remove(c);
                int affected = await db.SaveChangesAsync();
                if (affected == 1)
                {
                    return customersCache.TryRemove(id, out c);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        public Task<IEnumerable<Customer>> RetrieveAllAsync()
        {
            return Task.Run<IEnumerable<Customer>>(()=> customersCache.Values);
        }

        public Task<Customer> RetrieveAsync(string id)
        {
            return Task.Run(() =>
            {
                id = id.ToUpper();
                customersCache.TryGetValue(id, out Customer c);
                return c;
            }
            );
        }
        public Customer UpdateCache(string id,Customer c)
        {
            if(customersCache.TryGetValue(id,out Customer old))
            {
                if (customersCache.TryUpdate(id, c, old))
                {
                    return c;
                }
            }
            return null;
        }

        public async  Task<Customer> UpdateAsync(string id, Customer c)
        {
            id = id.ToUpper();
            c.CustomerID = c.CustomerID.ToUpper();

            db.Customers.Update(c);
            int affected = await db.SaveChangesAsync();
            if (affected == 1)
            {
                return UpdateCache(id, c);
            }
            return null;
        }


实现Web API控制器

HttpGet和HttpHead

HttpPost

HttpPut和HttpPatch

HttpDelete

HttpOptions


操作方法返回.NET类型,如果注册了合适的序列化器,那么WebAPI会自动将它们序列化为HTTP请求的Accept标头中设置的请求数据格式。例如JSON。

要对响应进行更多控制,可以使用返回.NET类型的ActionResult包装器。

返回不同类型,返回IActionResult,如果返回单个类型,状态码不同,返回类型设置为ActionResult<T>


建议使用 [ProducesResponseType]特性装饰操作方法,指示客户端应该期望在响应中包含所有已知类型和HTTP状态码。然后可以公开这些信息,以记录客户端应该如何与Web服务交互。后面章节将介绍 安装代码分析器,以便在不像在这样装饰的操作方法时发出警告。


例如

        [HttpGet("{id}")]

        [ProducesResponseType(200,Type=typeof(Product))]

        [ProducesResponseType(404)]

        public IActionResult Get(string id)


Controllerbase类有些方法,可以很容易地返回类型

ok:返回HTTP200,其中包含客户端要的资源,通常Http Get

CreatedAtRoute:返回201,其中包含到新资源的路径。通常响应POST请求

Accepted:返回202,表明请求正在处理但尚未完成。通常用于响应对需要很长时间才能完成的后台进程的请求。

NoContentResult:返回204. 通常响应Http Put请求,以更新现有资源

BadRequest:返回带有可选消息字符串的HTTP 400状态码

NotFound:返回能够自动填充 ProblemDetails主体 HTTP 404状态码


配置存储库和Web API控制器

打开startup.cs,在ConfigureServices方法注册接口和实现

   services.AddScoped<ICustomerRepository, CustomerRepository>();

在Controller文件夹新建 CustomersController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NorthwindEntitesLib;
using NorthwindService.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NorthwindService.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CustomersController : ControllerBase
    {
        private ICustomerRepository repo;
        public CustomersController(ICustomerRepository repo)
        {
            this.repo = repo;
        }

        [HttpGet]
        [ProducesResponseType(200, Type = typeof(IEnumerable<Customer>))]
        public async Task<IEnumerable<Customer>> GetCustomers(string country)
        {
            if (string.IsNullOrWhiteSpace(country))
            {
                return await repo.RetrieveAllAsync();
            }
            else
            {
                return (await repo.RetrieveAllAsync()).Where(customer => customer.Country == country);
            }
        }

        //GET api/customers/{id}
        [HttpGet("{id}", Name = nameof(GetCustomer))]
        [ProducesResponseType(200, Type = typeof(Customer))]
        [ProducesResponseType(404)]
        public async Task<IActionResult> GetCustomer(string id)
        {
            var c = await repo.RetrieveAsync(id);
            if (c == null)
            {
                return NotFound(); //404
            }
            else
            {
                return Ok(c);//200 ok with in body
            }
        }

        //Post api/customers
        //body customer:{JSON,XML}
        [HttpPost]
        [ProducesResponseType(201, Type = typeof(Customer))]
        [ProducesResponseType(400)]
        public async Task<IActionResult> Create([FromBody] Customer c)
        {
            if (c == null)
            {
                return BadRequest();
            }
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            var added = await repo.CreateAsync(c);
            return CreatedAtRoute(nameof(GetCustomer), new { id = added.CustomerID.ToLower() }, added);
        }

        //PUT api/customers/{id}
        //body customer:{JSON,XML}
        [HttpPut("{id}")]
        [ProducesResponseType(204)]
        [ProducesResponseType(400)]
        [ProducesResponseType(404)]
        public async Task<IActionResult> Update(string id,[FromBody] Customer c)
        {
            id = id.ToUpper();
            c.CustomerID = c.CustomerID.ToUpper();

            if(c==null || c.CustomerID != id)
            {
                return BadRequest(); //400 bad request
            }
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState); //400 bad request
            }

            var exist = await repo.RetrieveAsync(id);
            if (exist == null)
            {
                return NotFound(); //404
            }
            await repo.UpdateAsync(id, c);
            return new NoContentResult();//2014 no content
        }


        [HttpDelete("{id}")]
        [ProducesResponseType(204)]
        [ProducesResponseType(400)]
        [ProducesResponseType(404)]
        public async Task<IActionResult> Delete(string id)
        {
            var ext = await repo.RetrieveAsync(id);
            if (ext == null)
            {
                return NotFound();
            }
            var deleted = await repo.DeleteAsync(id);
            if(deleted.HasValue && deleted.Value)
            {
                return new NoContentResult();
            }
            else
            {
                return BadRequest($"CustomerID为{id}存在,但是删除失败");
            }
        }

    }
}

image.png


指定问题的细节

更多信息:HTTP API问题细节的建议标准:https://tools.ietf.org/html/rfc7807

ProblemDetails类实现问题细节的更多信息:https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.mvc.problemdetails

如果想获得控制权,那么可以创建ProblemDetails实例并包含其他信息。

在CustomersController类定义的顶部导入Microsoft.AspNetCore.Http名称

在Delete方法的顶部添加语句,检查id是否与bad匹配,如果匹配,就返回自定义的ProblemDetails对象

    if (id == "bad")
            {
                var problemDetails = new ProblemDetails
                {
                    Status = StatusCodes.Status400BadRequest,
                    Type = "https://localhost:44372/customers/failed-to-delete",
                    Title = $"Customer ID {id} found but failed to delete",
                    Detail = "更多信息,比如公司名,国家名等等",
                    Instance = HttpContext.Request.Path
                };
                return BadRequest(problemDetails);
            }

image.png


解释和测试Web服务

启动服务后,打开浏览器,输入

https://localhost:44372/api/customers

image.png

https://localhost:44372/api/customers/?country=Germany

image.png

https://localhost:44372/api/customers/alfki

image.png


在vscode中,你安装swagger,在vs2019中创建项目就自带了。

在vscode中 安装Rest Client扩展Http请求

vscode中,命令中输入rest client,回车后,多个请求,使用###符号分隔

输入:

###

GET https://localhost:44372/api/customers/?country=Germany Http/1.1

###

GET https://localhost:44372/api/customers/?country=USA Http/1.1

###

GET https://localhost:44372/api/customers/?country=ALFKI Http/1.1

单击Send Request就可以了。


当然还是建议swagger或者 postman自己测试api


启用Swagger

在NorthwindService.csproj中

image.png

在Startup.cs中的ConfigureServices

image.png

可通过以下链接了解Swagger如何支持API的多个版本-

https://stackoverflow.com/questions/30789045/leverage-multipleapiversions-in-swagger-with-attribute-versioning/30789944

using System.Web.Http;

namespace RESTServices.Controllers.v1
{
    [Route("api/v1/Test")]
    public class TestV1Controller : ApiController
    { ... }
.EnableSwagger(c => c.MultipleApiVersions(
        (apiDesc, version) =>
        {
            var path = apiDesc.RelativePath.Split('/');
            var pathVersion = path[1];

            return CultureInfo.InvariantCulture.CompareInfo.IndexOf(pathVersion, version, CompareOptions.IgnoreCase) >= 0;
        },
        vc =>
        {
            vc.Version("v2", "Swashbuckle Dummy API V2"); //add this line when v2 is released

            // ReSharper disable once ConvertToLambdaExpression
            vc.Version("v1", "Swashbuckle Dummy API V1");
        }
        ))

使用Swagger

image.png

输入ALFKI,然后单击蓝色的Execute

image.png

结果可以看见,响应body和响应头信息

我们拷贝这个ResponseBody内容,方便下面的测试


=====www.ayjs.net==============


回到顶部,单击POST/api/Customers,展开端点,单击Try it out

输入下面的json

{
  "customerID": "AY",
  "companyName": "AY的公司",
  "contactName": "杨洋",
  "contactTitle": "开发者",
  "address": "合肥市高新区",
  "city": "合肥",
  "region": null,
  "postalCode": "230000",
  "country": "China",
  "phone": "15255112222",
  "fax": "030-0226545",
  "orders": null
}

单击Execute

image.png

返回201表示创建了客户


移动到顶部,单击 GET /api/Customers

image.png

执行后的结果,能查询到,说明插入成功了

image.png


单击DELETE /api/Customers/{id} 展开节点,单击try it out,输入 AY,单击执行,返回204表示删除成功

image.png

再次单击一次,由于数据不在了,所以返回404

image.png

输入bad,单击execute,返回自定义的详情错误信息

image.png


使用Http客户端消费服务

使用HttpClient类从任何.NET Core应用程序中调用Northwind服务

Net Core2.1引入了HttpClientFactory

如何启动Http请求: https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests


下面示例使用NorthwindMvc网站作为Web服务的客户端。因为两者都需要在Web服务器上托管。先配置不同端口号。

API使用htpps使用5001,MVC的http使用5000,https使用5002

解决方案 添加第16章的NorthwindMvc项目

image.png

打开Program.cs,修改地址

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NorthwindMvc
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                    webBuilder.UseUrls("http://localhost:5000", "https://localhost:5002");

                });
    }
}

打开Startup.cs 在ConfigureServices添加一条语句,使指定了客户端的HttpClientFactory能够使用HTTPS(用于端口5001)调用Northwind Web API服务,并请求JSON作为默认响应格式,如下:

            services.AddHttpClient("NorthwindService",options=> {
                options.BaseAddress = new Uri("https://localhost:5001");
                options.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json",1.0));
            });

image.png

打开Controllers/HomeController.cs

添加IHttpClientFactory相关代码,并且创建新的操作方法Customers

private readonly ILogger<HomeController> _logger;
private readonly IHttpClientFactory clientFactory;

private Northwind db;
public HomeController(ILogger<HomeController> logger, Northwind injectedContext,IHttpClientFactory httpClientFactory)
{
_logger = logger;
db = injectedContext;
clientFactory = httpClientFactory;
}

public async Task<IActionResult> Customers(string country)
{
string uri;
if (string.IsNullOrWhiteSpace(country))
{
ViewData["Title"] = "所有客户";
uri = "api/customers";
}
else
{
ViewData["Title"] = $"{country} 的客户";
uri = $"api/customers/?country={country}";
}
var client = clientFactory.CreateClient("NorthwindService");
var req = new HttpRequestMessage(HttpMethod.Get,uri);
HttpResponseMessage res = await client.SendAsync(req);
string jsonString = await res.Content.ReadAsStringAsync();
IEnumerable<Customer> model = JsonConvert.DeserializeObject<IEnumerable<Customer>>(jsonString);
return View(model);


}

在Views/Home文件夹中创建一个名为Customers.cshtmpl的Razor文件

@using NorthwindEntitesLib;
@model IEnumerable<Customer>
    <h2>@ViewData["Title"]</h2>
    <table class="table">
        <tr>
            <th>公司名字</th>
            <th>联系人名字</th>
            <th>地址</th>
            <th>电话</th>
        </tr>

        @foreach (var item in Model)
        {
            <tr>
                <td>@Html.DisplayFor(m=>item.CompanyName)</td>
                <td>@Html.DisplayFor(m => item.ContactName)</td>
                <td>
                    @Html.DisplayFor(m => item.Address)
                    @Html.DisplayFor(m => item.City)
                    @Html.DisplayFor(m => item.Region)
                    @Html.DisplayFor(m => item.Country)
                    @Html.DisplayFor(m => item.PostalCode)
                </td>
                <td>  @Html.DisplayFor(m => item.Phone)</td>
            </tr>
        }
    </table>

打开Home/Index.cshtml增加一个链接,打开Customer页面

<form asp-action="Customers" method="get">
            <input placeholder="输入国家名" name="country" />
            <input type="submit" />
        </form>

由于mvc和api不是一个端口号,所以存在跨源资源共享的问题。

启用CORS来允许来自指定域的请求

https://docs.microsoft.com/zh-cn/aspnet/core/security/cors


在NorthwindService项目中打开Program.cs

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NorthwindService
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                    webBuilder.UseUrls("https://localhost:5001");
                });
    }
}

指定5001端口号,并且在Startup.cs中允许

   services.AddCors();


image.png

然后Configure方法添加一条语句使用cors,并允许来自任何网站(如https://localhost:5002)  HTTP get put post delete请求

            app.UseCors(options =>
            {
                options.WithMethods("GET", "POST", "PUT", "DELETE");
                options.WithOrigins("https://localhost:5002");
            });

image.png


启动项目,测试,先启动NorthwindService

右键NorthwindService项目,选择“在终端打开” 输入 dotnet run

image.png

右键NorthwindMvc项目,选择“在终端打开” 输入 dotnet run

image.png

浏览器地址栏输入:https://localhost:5002/

如果输入http://localhost:5000/ 会自动跳到 https://localhost:5002/这个地址

image.png

输入Germany,点击提交

会出现这个错误,先不管了image.png




实现高级功能

健康检查API

打开NorthwindService 安装以下这个Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore

image.png

安装过后

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <UserSecretsId>0ea0d690-19a9-45a1-b23b-6f3331e0fd72</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.6" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\NorthwindContextLib\NorthwindContextLib.csproj" />
    <ProjectReference Include="..\NorthwindEntitesLib\NorthwindEntitesLib.csproj" />
  </ItemGroup>

  <ItemGroup>
    <None Update="Northwind.db">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

然后我们在Startup.cs的ConfigureServices添加一句代码,支持健康检查。

      services.AddHealthChecks().AddDbContextCheck<Northwind>();
            //默认情况下,数据库上下文检查调用EF Core的CanConnectAsync方法。添加完了,然后在Configure使用


Configure中使用

   app.UseHealthChecks("/ayhealthcheck");

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using NorthwindContextLib;
using NorthwindService.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NorthwindService
{
    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.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "NorthwindService", Version = "v1" });
            });

            string databasePath = System.IO.Path.Combine("Northwind.db");
            services.AddDbContext<Northwind>(options => options.UseSqlite($"Data Source={databasePath}"));

            services.AddControllers(options =>
            {
                Console.WriteLine("默认输出格式");
                foreach (IOutputFormatter formatter in options.OutputFormatters)
                {
                    var mediaFormat = formatter as OutputFormatter;
                    if (mediaFormat == null)
                    {
                        Console.WriteLine($"{formatter.GetType().Name}");
                    }
                    else
                    {
                        Console.WriteLine("{0},MediaType:{1}", mediaFormat.GetType().Name, String.Join(",", mediaFormat.SupportedMediaTypes));
                    }
                }
            }).AddXmlDataContractSerializerFormatters().AddXmlSerializerFormatters().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            services.AddScoped<ICustomerRepository, CustomerRepository>();
            
            services.AddCors();

            services.AddHealthChecks().AddDbContextCheck<Northwind>();
            //默认情况下,数据库上下文检查调用EF Core的CanConnectAsync方法。添加完了,然后在Configure使用
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger(); 
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NorthwindService v1"));
            }
            app.UseCors(options =>
            {
                options.WithMethods("GET", "POST", "PUT", "DELETE");
                options.WithOrigins("https://localhost:5002");
            });

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseHealthChecks("/ayhealthcheck");
        }
    }
}

启动项目,打开浏览器测试,输入https://localhost:5001/ayhealthcheck

image.png

可以根据需要扩展健康检查响应


https://blogs.msdn.microsoft.com/webdev/2018/08/22/asp-net-core-2-2-0-preview1-healthcheck/


实现Open API分析器和约定

本章介绍了如何使用特性手动装饰控制器类,从而使Swagger能够记录Web服务。

在NC2.2以上版本,一些API分析器可以反映控制器类的情况,有些类已经用[ApiController]特性注解以方便记录。分析器往往会采用一些API约定。

安装

image.png


配置端点路由

端点路由需要对app.UseRouting()和app.UseEndpoints();

app.UseRouting() 用于标记进行路由决策的管道位置

app.UseEndpoints()用于标记所选端点执行的管道位置

端点路由使用路由模板语法与AspNet MVC使用的相同。并且使用AspNet Mvc5引入的Route特性。

MVC控制器 Razor Pages和SignalR之类的框架过去通常是通过调用UseMvc()或类似的方法启用的,但现在它们已被添加到UseEndpoint()中,他们都与中间件一起被集成到同一路由系统中。


在NorthwindService项目Startup.cs的Configure方法中的  UseEndpoints之前添加一条语句。

引入

using Microsoft.AspNetCore.Http;

using Microsoft.AspNetCore.Routing;

    app.Use(next => (context) =>
            {
                var endpoint = context.GetEndpoint();
                if (endpoint != null)
                {
                    Console.WriteLine("Name:{0};Route:{1};Metadata:{2}",
                        endpoint.DisplayName,
                        (endpoint as RouteEndpoint)?.RoutePattern,
                        string.Join(",",endpoint.Metadata));
                }
                return next(context);
            });

image.png

启动api  ,输入一个api调用

输出

Name:NorthwindService.Controllers.WeatherForecastController.Get (NorthwindService);Route:Microsoft.AspNetCore.Routing.Patterns.RoutePattern;Metadata:Microsoft.AspNetCore.Mvc.ApiControllerAttribute,Microsoft.AspNetCore.Mvc.ControllerAttribute,Microsoft.AspNetCore.Mvc.RouteAttribute,Microsoft.AspNetCore.Mvc.HttpGetAttribute,Microsoft.AspNetCore.Routing.HttpMethodMetadata,Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor,Microsoft.AspNetCore.Routing.RouteNameMetadata,Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter,Microsoft.AspNetCore.Mvc.Infrastructure.ClientErrorResultFilterFactory,Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilterFactory,Microsoft.AspNetCore.Mvc.ApiControllerAttribute,Microsoft.AspNetCore.Mvc.ActionConstraints.HttpMethodActionConstraint

Name:NorthwindService.Controllers.WeatherForecastController.Get (NorthwindService);Route:Microsoft.AspNetCore.Routing.Patterns.RoutePattern;Metadata:Microsoft.AspNetCore.Mvc.ApiControllerAttribute,Microsoft.AspNetCore.Mvc.ControllerAttribute,Microsoft.AspNetCore.Mvc.RouteAttribute,Microsoft.AspNetCore.Mvc.HttpGetAttribute,Microsoft.AspNetCore.Routing.HttpMethodMetadata,Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor,Microsoft.AspNetCore.Routing.RouteNameMetadata,Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter,Microsoft.AspNetCore.Mvc.Infrastructure.ClientErrorResultFilterFactory,Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilterFactory,Microsoft.AspNetCore.Mvc.ApiControllerAttribute,Microsoft.AspNetCore.Mvc.ActionConstraints.HttpMethodActionConstraint


更多端点路由的更多信息:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing

微软已经使用端点路由代替了NC2.1更早版本的使用的基于 IRouter的路由



其他通讯技术

WCF,

gRPC是可以在任何环境中运行的现代开源高性能RPC框架。

二者都是使用一种契约优先的API开发方式支持与语言无关的实现。可以使用.proto文件编写契约,然后使用它们自己的语言语法工具 转换为各种编程语言。

https://grpc.io

微软已经正式支持gRPC与ASP.NET Core

https://docs.microsoft.com/zh-cn/aspnet/core/grpc/aspnetcore

Swagger工具: https://swagger.io/tools

=====www.ayjs.net==============


推荐您阅读更多有关于“C#8.0core3,”的文章

猜你喜欢

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

  查看权限

抖音:wpfui 工作wpf,兴趣学习flutter

目前在合肥市某公司上班,已经厌弃,如果你的公司看的上我,加我QQ私聊

AYUI8全源码 Github地址:前往获取

杨洋(AaronYang简称AY,安徽六安人)AY唯一QQ:875556003和AY交流

高中学历,2010年开始web开发,2015年1月17日开始学习WPF

声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费

不是从我处购买的ayui7源码,我不提供任何技术服务,如果你举报从哪里买的,我可以帮你转正为我的客户,并送demo

查看捐赠

AYUI7.X MVC教程 更新如下:

第一课 第二课 程序加密教程

标签列表