我前面几篇随笔介绍了关于几篇关于SqlSugar的基础封装,已经可以直接应用在Winform项目开发上,并且基础接口也通过了单元测试,同时测试通过了一些Winform功能页面;本篇随笔继续深化应用开发,着手在在.net6框架的Web API上开发应用,也就是基于.net core的Web API应用开发,这样可以应用在不同的前端接入上。本篇随笔主要介绍基于.net6框架的Web API的相关整合开发内容,内容涉及到Swagger的整合支持、SeriLog的支持、JWT鉴权和用户身份信息缓存、基类控制器封装、自动注入接口对象、统一结果封装、统一异常处理等方面。

1、创建.netcore WebApi项目并添加相关支持

本篇随笔主要从基础框架开发创建,因此使用VS2022添加一个基于.net core6的WebAPI项目,如下所示。

我们在生成的项目中,看到有一个Program.cs的代码文件,里面代码比较简洁,我们逐步调整并添加自己的相关代码即可。

在其中可以看到

  1. builder.Services.AddSwaggerGen();

这个是简单的Swagger注释支持,我们如果需要定义更多的信息,可以采用下面的代码。

  1. #region 添加swagger注释
  2.  
  3. //builder.Services.AddSwaggerGen();
  4.  
  5. builder.Services.AddSwaggerGen(c =>
  6. {
  7. c.SwaggerDoc("v1", new OpenApiInfo
  8. {
  9. Version = "v1",
  10. Title = "Api"
  11. });
  12. c.IncludeXmlComments(Path.Combine(basePath, "SugarWebApi.xml"), true);//WebAPI项目XML文件
  13. c.IncludeXmlComments(Path.Combine(basePath, "SugarProjectCore.xml"), true);//其他项目所需的XML文件
  14.  
  15. c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
  16. {
  17. Description = "Value: Bearer {token}",
  18. Name = "Authorization",
  19. In = ParameterLocation.Header,
  20. Type = SecuritySchemeType.ApiKey,
  21. Scheme = "Bearer"
  22. });
  23. c.AddSecurityRequirement(new OpenApiSecurityRequirement()
  24. {
  25. {
  26. new OpenApiSecurityScheme
  27. {
  28. Reference = new OpenApiReference
  29. {
  30. Type = ReferenceType.SecurityScheme,
  31. Id = "Bearer"
  32. },Scheme = "oauth2",Name = "Bearer",In = ParameterLocation.Header,
  33. },new List<string>()
  34. }
  35. });
  36. });
  37. #endregion

上面的代码除了添加对应控制器的接口信息外,还增加了一个相关服务类的接口定义,便于我们查看详细的xml信息,如下所示得到很详细的接口注释。

然后调整Swagger UI支持的代码如下所示。

  1. // Configure the HTTP request pipeline.
  2. if (app.Environment.IsDevelopment())
  3. {
  4. app.UseSwagger();
  5. app.UseSwaggerUI();
  6. }

另外,我们的Web API控制器,需要集成JWT Bear 认证的处理的,添加认证代码如下所示。

  1. //JWT Bear 认证
  2. builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
  3. {
  4. options.TokenValidationParameters = new TokenValidationParameters
  5. {
  6. //非固定可选可加
  7. ValidateIssuer = true,
  8. ValidIssuer = builder.Configuration["Jwt:Issuer"],
  9. ValidateAudience = true,
  10. ValidAudience = builder.Configuration["Jwt:Audience"],
  11.  
  12. ValidateLifetime = true,//时间
  13. ClockSkew = TimeSpan.Zero,
  14. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]))
  15. };
  16. });

这里面的代码读取配置信息的,我们可以在appSettings.json中配置JWT的一些键值。

  1. "Jwt": {
  2. "Secret": "your-256-bit-secret",
  3. "Issuer": "iqidi.com",
  4. "Audience": "api"
  5. }

另外,为了在.net core中输出日志,可以使用SeriLog组件进行处理。

添加相关的nuget类库,如下所示。

然后在Program.cs中添加初始化日志代码即可。

  1. //初始化日志
  2. Log.Logger = new LoggerConfiguration()
  3. .MinimumLevel.Debug() //最小记录级别
  4. //对其他日志进行重写,除此之外,目前框架只有微软自带的日志组件
  5. .MinimumLevel.Override(source: "Microsoft", minimumLevel: Serilog.Events.LogEventLevel.Error)
  6.  
  7. .Enrich.FromLogContext() //记录相关上下文信息
  8. .WriteTo.Console() //输出到控制台
  9. .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day) //输出到本地文件
  10. .CreateLogger();

2、统一结果封装和异常处理

在Web API的控制器返回信息中,我们为了方便使用JSON信息,往往需要对返回结果进行封装,让它们返回指定格式的数据,如下所示。

关于接口数据格式的统一封装,我们定义一个WrapResultFilter,以及需要一个不封装的属性标识DontWrapResultAttribute,默认是统一封装返回的结果。

  1. /// <summary>
  2. /// 禁用封装结果
  3. /// </summary>
  4. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  5. public class DontWrapResultAttribute : Attribute
  6. {
  7. }

而统一封装的处理,需要继承ActionFilterAttribute并重写OnResultExecuting处理操作。

里面的主要逻辑就是对结果内容进行统一的再次封装,如下所示主要的逻辑代码。

  1. if (context.Result is ObjectResult objRst)
  2. {
  3. if (objRst.Value is AjaxResponse)
  4. return;
  5.  
  6. context.Result = new ObjectResult(new AjaxResponse
  7. {
  8. Success = true,
  9. Error = null,
  10. TargetUrl = string.Empty,
  11. UnAuthorizedRequest = false,
  12. Result = objRst.Value
  13. });
  14. }

除了常规的正常返回内容进行封装,也需要对异常进行拦截,并对结果进行封装,因此需要继承ExceptionFilterAttribute并添加一个异常处理的过滤器进行处理,并重写OnException的操作即可。

  1. /// <summary>
  2. /// 自定义异常处理
  3. /// </summary>
  4. public class GlobalExceptionFilter : ExceptionFilterAttribute
  5. {
  6. /// <summary>
  7. /// 异常处理封装
  8. /// </summary>
  9. /// <param name="context"></param>
  10. public override void OnException(ExceptionContext context)
  11. {
  12. if (!context.ExceptionHandled)
  13. {
  14. //异常返回结果包装
  15. var content = new AjaxResponse()
  16. {
  17. Success = false,
  18. Error = new ErrorInfo(0, context.Exception.Message, context.Exception.StackTrace),
  19. TargetUrl = string.Empty,
  20. UnAuthorizedRequest = false,
  21. Result = null
  22. };
  23.  
  24. //日志记录
  25. Log.Error(context.Exception, context.Exception.Message);
  26.  
  27. context.ExceptionHandled = true;
  28. context.Result = new ApplicationErrorResult(content);
  29. context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
  30. }
  31. }

因此为了拦截相关的处理,我们在Program.cs中添加以下代码进行拦截。

  1. //控制器添加自定义过滤器
  2. builder.Services.AddControllers(options=>
  3. {
  4. options.Filters.Add<WrapResultFilter>(); //统一结果封装处理
  5. options.Filters.Add<GlobalExceptionFilter>();//自定义异常处理
  6. });
  7. //所有控制器启动身份验证
  8. builder.Services.AddMvc(options =>
  9. {
  10. options.Filters.Add(new AuthorizeFilter());//所有MVC服务默认添加授权标签
  11. });

并调整代码,添加认证和授权验证的代码处理。

  1. app.UseAuthentication();
  2.  
  3. app.UseAuthorization();

对于一些系统异常的处理(如401未授权、400未找到接口、500系统错误)等,默认是没有进行处理的

我们如果要拦截,就另外需要添加一个中间件的方式来处理信息流,如下所示。

其中在Invoke的函数处理中,统一处理不同的异常即可。

  1. public async Task Invoke(HttpContext context)
  2. {
  3. try
  4. {
  5. await next(context);
  6. }
  7. catch (Exception ex)
  8. {
  9. var statusCode = context.Response.StatusCode;
  10. if (ex is ArgumentException)
  11. {
  12. statusCode = 200;
  13. }
  14. await HandleExceptionAsync(context, statusCode, ex.Message);
  15. }
  16. finally
  17. {
  18. var statusCode = context.Response.StatusCode;
  19. var msg = "";
  20. if (statusCode == 401)
  21. {
  22. msg = "未授权 " + context.Response.Headers["WWW-Authenticate"];
  23.  
  24. }
  25. else if (statusCode == 404)
  26. {
  27. msg = "未找到服务";
  28. }
  29. else if(statusCode == 500)
  30. {
  31. msg = "系统错误";
  32. }
  33. else if (statusCode == 502)
  34. {
  35. msg = "请求错误";
  36. }
  37. else if (statusCode != 200)
  38. {
  39. msg = "未知错误";
  40. }
  41.  
  42. if (!string.IsNullOrWhiteSpace(msg))
  43. {
  44. await HandleExceptionAsync(context, statusCode, msg);
  45. }
  46. }
  47. }

并添加一个扩展类方法,用于快速使用中间件方式调用。

  1. /// <summary>
  2. /// 自定义错误处理的扩展方法
  3. /// </summary>
  4. public static class ErrorHandlingExtensions
  5. {
  6. public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder builder)
  7. {
  8. return builder.UseMiddleware<ErrorHandlingMiddleware>();
  9. }
  10. }

最后在program.cs代码中添加使用代码即可,注意添加位置。

另外,为了把用户身份信息缓存起来,我们可以使用Redis进行缓存处理,因此在项目中使用CRedis的封装类库进行操作Redis

通过连接字符串(读取配置信息)初始化Redis的代码如下所示。

  1. //初始化Redis
  2. RedisHelper.Initialization(new CSRedisClient(builder.Configuration["CSRedis:ConnectString"]));

其中appSettings.json信息如下所示。

  1. {
  2. "ConnectionStrings": {
  3. "Default": "Server=.; Database=WeixinBootstrap2; Trusted_Connection=True;",
  4. "Oracle": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl)));User ID=C##ABP;Password=abp",
  5. "MySql": "Server=localhost;Database=myprojectdb;Uid=root;Pwd=123456;",
  6. "PostgreSQL": "Server=localhost;Port=5432;Database=myprojectdb;User Id=postgres;Password=123456"
  7. },
  8. "DbSetting": {
  9. "DefaultDb": "Default",
  10. "ComponentDbType": "sqlserver"
  11. },
  12. "CSRedis": {
  13. "ConnectString": "127.0.0.1:6379"
  14. },
  15. "Jwt": {
  16. "Secret": "your-256-bit-secret",
  17. "Issuer": "iqidi.com",
  18. "Audience": "api"
  19. },
  20. "Logging": {
  21. "LogLevel": {
  22. "Default": "Information",
  23. "Microsoft.AspNetCore": "Warning"
  24. }
  25. },
  26. "AllowedHosts": "*",
  27. }

如果允许登录授权请求成功的话,那么对应的用户省份缓存也就记录在Redis中了。

3、接口对象的依赖注入处理

我们在.net core的Web API中调用相关处理,我们往往是使用接口来调用相关的处理的。

启动Web API的时候通过手工或者自动注入接口对象的方式,然后在控制器里面的构造函数中通过依赖注入方式使用接口即可。

如果是手工注入,那么你确定在Web API项目中所有用到的业务访问接口,都需要提取注入。

  1. services.AddSingleton<IDictDataService, DictDataService>();
  2. services.AddSingleton<IDictTypeService, DictTypeService>();
  3. services.AddSingleton<ICustomerService, CustomerService>();
  4. services.AddScoped<IUserService, UserService>();

但是这样接口实现一旦很多,手工加入肯定繁琐而且低效了,因此需要考虑自动注入所有相关的服务接口为佳。

为了实现这个自动注入的目标,首先我们先定义几个不同生命周期的接口声明。

  1. //用于定义这三种生命周期的标识接口
  2.  
  3. public interface IDependency
  4. {
  5. }
  6.  
  7. /// <summary>
  8. /// 瞬时(每次都重新实例)
  9. /// </summary>
  10. public interface ITransientDependency : IDependency
  11. {
  12. }
  13.  
  14. /// <summary>
  15. /// 单例(全局唯一)
  16. /// </summary>
  17. public interface ISingletonDependency : IDependency
  18. {
  19.  
  20. }
  21.  
  22. /// <summary>
  23. /// 一个请求内唯一(线程内唯一)
  24. /// </summary>
  25. public interface IScopedDependency : IDependency
  26. {
  27. }

然后通过遍历相关的DLL,然后实现所有实现指定接口的类对象,统一加入即可,如下代码所示。

  1. var baseType = typeof(IDependency);
  2. var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
  3. var getFiles = Directory.GetFiles(path, "*.dll").Where(Match); //.Where(o=>o.Match())
  4. var referencedAssemblies = getFiles.Select(Assembly.LoadFrom).ToList(); //.Select(o=> Assembly.LoadFrom(o))
  5.  
  6. var ss = referencedAssemblies.SelectMany(o => o.GetTypes());

然后进一步进行对接口的判断,如下所示。

  1. var types = referencedAssemblies
  2. .SelectMany(a => a.DefinedTypes)
  3. .Select(type => type.AsType())
  4. .Where(x => x != baseType && baseType.IsAssignableFrom(x)).ToList();
  5. var implementTypes = types.Where(x => x.IsClass).ToList();
  6. var interfaceTypes = types.Where(x => x.IsInterface).ToList();
  7. foreach (var implementType in implementTypes)
  8. {
  9. if (typeof(IScopedDependency).IsAssignableFrom(implementType))
  10. {
  11. var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType));
  12. if (interfaceType != null)
  13. services.AddScoped(interfaceType, implementType);
  14. }
  15. else if (typeof(ISingletonDependency).IsAssignableFrom(implementType))
  16. {
  17. var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType));
  18. if (interfaceType != null)
  19. services.AddSingleton(interfaceType, implementType);
  20. }
  21. else
  22. {
  23. var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType));
  24. if (interfaceType != null)
  25. services.AddTransient(interfaceType, implementType);
  26. }
  27. }

然后统一调用即可。

  1. //配置依赖注入访问数据库
  2. ServiceInjection.ConfigureRepository(builder.Services);

这样我们在对应的WebAPI 控制器中就可以方便的使用接口的构造函数注入方式了。

  1. /// <summary>
  2. /// 客户信息的控制器对象
  3. /// </summary>
  4. public class CustomerController : BusinessController<CustomerInfo, string, CustomerPagedDto>
  5. {
  6. private ICustomerService _customerService;
  7.  
  8. /// <summary>
  9. /// 构造函数,并注入基础接口对象
  10. /// </summary>
  11. /// <param name="customerService"></param>
  12. public CustomerController(ICustomerService customerService) :base(customerService)
  13. {
  14. this._customerService = customerService;
  15. }
  16. }

以上就是我们在创建.net Core项目的Web API项目中碰到的一些常见问题的总结,希望对大家有所帮助。

相关系类文章如下所示。

基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用  (本随笔)
 

基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用的更多相关文章

  1. 基于SqlSugar的数据库访问处理的封装,支持.net FrameWork和.net core的项目调用

    由于我们有时候需要在基于.net framework的项目上使用(如Winform端应用),有时候有需要在.net core的项目上使用(如.net core的WebAPI),那么我们把基于SQLSu ...

  2. 基于SqlSugar的数据库访问处理的封装,支持多数据库并使之适应于实际业务开发中

    在我的各种开发框架中,数据访问有的基于微软企业库,有的基于EFCore的实体框架,两者各有其应用场景,不过多的去比较.最近在使用SqlSugar的时候,觉得这个数据访问处理的组件确实很灵活,据说性能也 ...

  3. 基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用

    在实际项目开发中,我们可能会碰到各种各样的项目环境,有些项目需要一个大而全的整体框架来支撑开发,有些中小项目这需要一些简单便捷的系统框架灵活开发.目前大型一点的框架,可以采用ABP或者ABP VNex ...

  4. 关于PHP建立数据库访问类的封装以及操作php单例模式连接数据库封装类

    建立数据库访问类的封装 <?php   class DBDA {     public $host = "localhost"; //服务器地址     public $ui ...

  5. Java基于数据源的数据库访问

    ☞ 概述 最早接触的Java访问数据库,是通过jdbc接口.后来工作之后,一般是在服务器(如weblogic)配置数据源,通过JNDI使用数据源:最近需要在程序中动态构造数据源,查了些资料,备录于此. ...

  6. 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【五】——在Web Api中实现Http方法(Put,Post,Delete)

    系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html 前言 在Web Api中,我们对资源的CRUD操作都是通过相应的Http方法来实现——Post(新 ...

  7. ASP.NET第一次访问慢的解决方法(MVC,Web Api)

    问题现象 访问asp.net web项目的时候,第一次访问比较慢,当闲置一段时间后,再次访问还是会非常慢. 问题原因 这是IIS回收造成的,再次访问的时候会初始化操作,初始化需要耗费时间,所以访问会比 ...

  8. C# 核心语法-反射(反射类型、方法、构造函数、属性,实现可配置可扩展,完成数据库访问类反射封装)

    反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类.结构.委托.接口和枚举等)的成员和成员的信息.有了反射,即可对每一个类型了如指掌.另外我还可以直接创建对象,即使 ...

  9. C#—反射(反射类型、方法、构造函数、属性、实现可配置可扩展、数据库访问类反射封装)

    反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类.结构.委托.接口和枚举等)的成员和成员的信息.有了反射,即可对每一个类型了如指掌.另外我还可以直接创建对象,即使 ...

随机推荐

  1. nacos底层原理

    Nacos   为什么选择NacosNacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮 ...

  2. Where和having都是条件筛选关键字,它们有什么分别?

    WHERE是在数据分组前进行条件过滤, HAVING子句是在数据分组后进行条件过滤,WHERE子句中不能使用聚合函数,HAVING子句可以使用聚合函数. 需要注意说明:当同时含有where子句.gro ...

  3. Springmvc入门基础(四) ---参数绑定

    1.默认支持的参数类型 处理器形参中添加如下类型的参数处理适配器会默认识别并进行赋值. 除了ModelAndView以外,还可以使用Model来向页面传递数据, Model是一个接口,在参数里直接声明 ...

  4. docker-compose配置django web项目容器和EMQX容器

    1.Dockerfile FROM gatewayserver_null:v1.1 ADD ./GatewayServer /code ADD ./entrypoint.sh /code# 给entr ...

  5. 学习 Haproxy (三)

    HAProxy安装 # wget http://www.haproxy.org/download/1.4/src/haproxy-1.4.24.tar.gz # tar xf haproxy-1.4. ...

  6. springboot-mail发邮件,不需要邮件服务器

    很简单 步骤走起-> 1.需要一个邮箱账号,我以163邮箱为例,先开启第三方服务后获得密码,后面用来邮箱登录 2.加入mail 依赖 3.properties配置账号和第三方服务密码(不是邮箱密 ...

  7. 更改IE中的jdk版本

    一丶打开IE设置: 快捷键:Ctrl+Shift+Alt+S 二丶在设置中调设已经安装的jdk版本(前提已安装):

  8. iOS全埋点解决方案-界面预览事件

    前言 ​ 我们先了解 UIViewController 生命周期相关的内容和 iOS 的"黑魔法" Method Swizzling.然后再了解页面浏览事件($AppViewScr ...

  9. C语言杂谈

    C语言程序处理过程 预处理:宏定义展开.头文件展开.条件编译,这里并不会检查语法 编译:检查语法,将预处理后文件编译生成汇编文件 汇编:将汇编文件生成目标文件(二进制文件) 链接:将目标文件链接为可执 ...

  10. node-webkit文档翻译#package.json

    title: node-webkit文档翻译#package.json date: 2013-12-07 21:38:25 tags: node-webkit 基本示例 { "main&qu ...