当前位置: 代码网 > it编程>编程语言>Asp.net > 为ABP框架添加基础集成服务

为ABP框架添加基础集成服务

2024年05月18日 Asp.net 我要评论
定义一个特性标记这个标记用于标记一个枚举代表的信息。在abpbase.domain.shared项目,创建attributes目录,然后创建一个schemenameattribute类,其内容如下:

定义一个特性标记

这个标记用于标记一个枚举代表的信息。

在 abpbase.domain.shared 项目,创建 attributes目录,然后创建一个 schemenameattribute 类,其内容如下:

    /// <summary>
    /// 标记枚举代表的信息
    /// </summary>
    [attributeusage(attributetargets.field)]
    public class schemenameattribute : attribute
    {
        public string message { get; set; }
        public schemenameattribute(string message)
        {
            message = message;
        }
    }

全局统一消息格式

为了使得 web 应用统一响应格式以及方便编写 api 时有一个统一的标准,我们需要定义一个合适的模板。

在 abpbase.domain.shared 创建一个apis 目录。

http 状态码

为了适配各种 http 请求的响应状态,我们定义一个识别状态码的枚举。

在 apis 目录,创建一个 httpstatecode.cs 文件,其内容如下:

namespace abpbase.domain.shared.apis
{
    /// <summary>
    /// 标准 http 状态码
    /// <para>文档地址<inheritdoc cref="https://www.runoob.com/http/http-status-codes.html"/></para>
    /// </summary>
    public enum httpstatecode
    {
        status412preconditionfailed = 412,
        status413payloadtoolarge = 413,
        status413requestentitytoolarge = 413,
        status414requesturitoolong = 414,
        status414uritoolong = 414,
        status415unsupportedmediatype = 415,
        status416rangenotsatisfiable = 416,
        status416requestedrangenotsatisfiable = 416,
        status417expectationfailed = 417,
        status418imateapot = 418,
        status419authenticationtimeout = 419,
        status421misdirectedrequest = 421,
        status422unprocessableentity = 422,
        status423locked = 423,
        status424faileddependency = 424,
        status426upgraderequired = 426,
        status428preconditionrequired = 428,
        status429toomanyrequests = 429,
        status431requestheaderfieldstoolarge = 431,
        status451unavailableforlegalreasons = 451,
        status500internalservererror = 500,
        status501notimplemented = 501,
        status502badgateway = 502,
        status503serviceunavailable = 503,
        status504gatewaytimeout = 504,
        status505httpversionnotsupported = 505,
        status506variantalsonegotiates = 506,
        status507insufficientstorage = 507,
        status508loopdetected = 508,
        status411lengthrequired = 411,
        status510notextended = 510,
        status410gone = 410,
        status408requesttimeout = 408,
        status101switchingprotocols = 101,
        status102processing = 102,
        status200ok = 200,
        status201created = 201,
        status202accepted = 202,
        status203nonauthoritative = 203,
        status204nocontent = 204,
        status205resetcontent = 205,
        status206partialcontent = 206,
        status207multistatus = 207,
        status208alreadyreported = 208,
        status226imused = 226,
        status300multiplechoices = 300,
        status301movedpermanently = 301,
        status302found = 302,
        status303seeother = 303,
        status304notmodified = 304,
        status305useproxy = 305,
        status306switchproxy = 306,
        status307temporaryredirect = 307,
        status308permanentredirect = 308,
        status400badrequest = 400,
        status401unauthorized = 401,
        status402paymentrequired = 402,
        status403forbidden = 403,
        status404notfound = 404,
        status405methodnotallowed = 405,
        status406notacceptable = 406,
        status407proxyauthenticationrequired = 407,
        status409conflict = 409,
        status511networkauthenticationrequired = 511
    }
}

常用的请求结果

在相同目录,创建一个 commonresponsetype 枚举,其内容如下:

    /// <summary>
    /// 常用的 api 响应信息
    /// </summary>
    public enum commonresponsetype
    {
        [schemename("")] default = 0,

        [schemename("请求成功")] requstsuccess = 1,

        [schemename("请求失败")] requstfail = 2,

        [schemename("创建资源成功")] createsuccess = 4,

        [schemename("创建资源失败")] createfail = 8,

        [schemename("更新资源成功")] updatesuccess = 16,

        [schemename("更新资源失败")] updatefail = 32,

        [schemename("删除资源成功")] deletesuccess = 64,

        [schemename("删除资源失败")] deletefail = 128,

        [schemename("请求的数据未能通过验证")] badrequest = 256,

        [schemename("服务器出现严重错误")] status500internalservererror = 512
    }

响应模型

在 apis 目录,创建一个 apiresponsemodel`.cs 泛型类文件,其内容如下:

namespace abpbase.domain.shared.apis
{
    /// <summary>
    /// api 响应格式
    /// <para>避免滥用,此类不能实例化,只能通过预定义的静态方法生成</para>
    /// </summary>
    /// <typeparam name="tdata"></typeparam>
    public abstract class apiresponsemodel<tdata>
    {
        public httpstatecode statucode { get; set; }
        public string message { get; set; }
        public tdata data { get; set; }


        /// <summary>
        /// 私有类
        /// </summary>
        /// <typeparam name="tresult"></typeparam>
        private class privateapiresponsemodel<tresult> : apiresponsemodel<tresult> { }
    }
}

statucode:用于说明此次响应的状态;

message:响应的信息;

data:响应的数据;

可能你会觉得这样很奇怪,先不要问,也不要猜,照着做,后面我会告诉你为什么这样写。

然后再创建一个类:

using abpbase.domain.shared.helpers;
using system;

namespace abpbase.domain.shared.apis
{
    /// <summary>
    /// web 响应格式
    /// <para>避免滥用,此类不能实例化,只能通过预定义的静态方法生成</para>
    /// </summary>
    public abstract class apiresponsemodel : apiresponsemodel<dynamic>
    {
        /// <summary>
        /// 根据枚举创建响应格式
        /// </summary>
        /// <typeparam name="tenum"></typeparam>
        /// <param name="code"></param>
        /// <param name="enumtype"></param>
        /// <returns></returns>
        public static apiresponsemodel create<tenum>(httpstatecode code, tenum enumtype) where tenum : enum
        {
            return new privateapiresponsemodel
            {
                statucode = code,
                message = schemehelper.get(enumtype),
            };
        }

        /// <summary>
        /// 创建标准的响应
        /// </summary>
        /// <typeparam name="tenum"></typeparam>
        /// <typeparam name="tdata"></typeparam>
        /// <param name="code"></param>
        /// <param name="enumtype"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static apiresponsemodel create<tenum>(httpstatecode code, tenum enumtype, dynamic data)
        {
            return new privateapiresponsemodel
            {
                statucode = code,
                message = schemehelper.get(enumtype),
                data = data
            };
        }

        /// <summary>
        /// 请求成功
        /// </summary>
        /// <param name="code"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static apiresponsemodel createsuccess(httpstatecode code, dynamic data)
        {
            return new privateapiresponsemodel
            {
                statucode = code,
                message = "success",
                data = data
            };
        }

        /// <summary>
        /// 私有类
        /// </summary>
        private class privateapiresponsemodel : apiresponsemodel { }
    }
}

同时在项目中创建一个 helpers 文件夹,再创建一个 schemehelper 类,其内容如下:

using abpbase.domain.shared.attributes;
using system;
using system.linq;
using system.reflection;

namespace abpbase.domain.shared.helpers
{
    /// <summary>
    /// 获取各种枚举代表的信息
    /// </summary>
    public static class schemehelper
    {
        private static readonly propertyinfo schemenameattributemessage = typeof(schemenameattribute).getproperty(nameof(schemenameattribute.message));

        /// <summary>
        /// 获取一个使用了 schemenameattribute 特性的 message 属性值
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string get<t>(t type)
        {
            return getvalue(type);
        }

        private static string getvalue<t>(t type)
        {
            var attr = typeof(t).getfield(enum.getname(type.gettype(), type))
                .getcustomattributes()
                .firstordefault(x => x.gettype() == typeof(schemenameattribute));

            if (attr == null)
                return string.empty;

            var value = (string)schemenameattributemessage.getvalue(attr);
            return value;
        }
    }
}

上面的类到底是干嘛的,你先不要问。

全局异常拦截器

在 abpbase.web 项目中,新建一个 filters 文件夹,添加一个 webglobalexceptionfilter.cs 文件,其文件内容如下:

using abpbase.domain.shared.apis;
using microsoft.aspnetcore.http;
using microsoft.aspnetcore.mvc;
using microsoft.aspnetcore.mvc.filters;
using newtonsoft.json;
using system.threading.tasks;

namespace apbbase.httpapi.filters
{

    /// <summary>
    /// web 全局异常过滤器,处理 web 中出现的、运行时未处理的异常
    /// </summary>
    public class webglobalexceptionfilter : iasyncexceptionfilter
    {

        public async task onexceptionasync(exceptioncontext context)
        {
            if (!context.exceptionhandled)
            {

                apiresponsemodel model = apiresponsemodel.create(httpstatecode.status500internalservererror,
                    commonresponsetype.status500internalservererror);
                context.result = new contentresult
                {
                    content = jsonconvert.serializeobject(model),
                    statuscode = statuscodes.status200ok,
                    contenttype = "application/json; charset=utf-8"
                };
            }

            context.exceptionhandled = true;

            await task.completedtask;
        }
    }
}

然后 在 abpbasewebmodule 模块的 configureservices 函数中,加上:

            configure<mvcoptions>(options =>
            {
                options.filters.add(typeof(webglobalexceptionfilter));
            });

这里我们还没有将写入日志,后面再增加这方面的功能。

先说明一下

前面我们定义了 apiresponsemodel 和其他一些特性还有枚举,这里解释一下原因。

apiresponsemodel 是抽象类

apiresponsemodel<t> 和 apiresponsemodel 是抽象类,是为了避免开发者使用时,直接这样用:

            apiresponsemodel mode = new apiresponsemodel
            {
                code = 500,
                message = "失败",
                data = xxx
            };

首先这个 code 需要按照 http 状态的标准来填写,我们使用 httpstatecode 枚举来标记,代表异常时,使用 status500internalservererror 来标识。

我非常讨厌一个 action 的一个返回,就写一次消息的。

if(... ...)
	return xxxx("请求数据不能为空");

if(... ...)
	return xxxx("xxx 要大于 10");
... ..

这样每个地方一个消息说明,十分不统一,也不便于修改。

直接使用一个枚举来代表消息,而不能直接写出来,这样就可以达到统一了。

使用抽象类,可以避免开发者直接 new 一个,强制要求一定的消息格式来响应。后面可以进行更多的尝试,来体会我这样设计的便利性。

跨域请求

这里我们将配置 web 全局允许跨域请求。

在 abpbasewebmodule 模块中:

添加一个静态变量

private const string abpbasewebcosr = "allowspecificorigins";

创建一个配置函数:

        /// <summary>
        /// 配置跨域
        /// </summary>
        /// <param name="context"></param>
        private void configurecors(serviceconfigurationcontext context)
        {
            context.services.addcors(options =>
            {
                options.addpolicy(abpbasewebcosr,
                    builder => builder.allowanyheader()
                        .allowanymethod()
                        .allowanyorigin());
            });
        }

在 configureservices 函数中添加:

            // 跨域请求
            configurecors(context);

在 onapplicationinitialization 中添加:

            app.usecors(abpbasewebcosr);	// 位置在 app.userouting(); 后面

就这样,允许全局跨域请求就完成了。

配置 api 服务

你可以使用以下模块来配置一个 api 模块服务:

            configure<abpaspnetcoremvcoptions>(options =>
            {
                options
                    .conventionalcontrollers
                    .create(typeof(abpbasehttpapimodule).assembly, opts =>
                    {
                        opts.rootpath = "api/1.0";
                    });
            });

我们在 abpbase.httpapi 中将其本身用于创建一个 api 服务,abp 会将继承了 abpcontroller 、controllerbase 等的类识别为 api控制器。上面的代码同时将其默认路由的前缀设置为 api/1.0

也可以不设置前缀:

            configure<abpaspnetcoremvcoptions>(options =>
            {                options.conventionalcontrollers.create(typeof(iotcenterwebmodule).assembly);
            });

由于 api 模块已经在自己的 configureservices 创建了 api 服务,因此可以不在 web 模块里面编写这部分代码。当然,也可以统一在 web 中定义所有的 api 模块。

统一 api 模型验证消息

创建前

首先,如果我们这样定义一个 action:

        public class testmodel
        {
            [required]
            public int id { get; set; }
            
            [maxlength(11)]
            public int iphone { get; set; }
            
            [required]
            [minlength(5)]
            public string message { get; set; }
        }

        [httppost("/t2")]
        public string mywebapi2([frombody] testmodel model)
        {
            return "请求完成";
        }

使用以下参数请求:

{
    "id": "1",
    "iphone": 123456789001234567890,
    "message": null
}

会得到以下结果:

{
    "errors": {
        "iphone": [
            "json integer 123456789001234567890 is too large or small for an int32. path 'iphone', line 3, position 35."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "one or more validation errors occurred.",
    "status": 400,
    "traceid": "|af964c79-41367b2145701111."
}

这样的信息阅读起来十分不友好,前端对接也会有一定的麻烦。

这个时候我们可以统一模型验证拦截器,定义一个友好的响应格式。

创建方式

在 abpbase.web 的项目 的 filters 文件夹中,创建一个 invalidmodelstatefilter 文件,其文件内容如下:

using abpbase.domain.shared.apis;
using microsoft.aspnetcore.http;
using microsoft.aspnetcore.mvc;
using microsoft.extensions.dependencyinjection;
using system.linq;

namespace abpbase.web.filters
{
    public static class invalidmodelstatefilter
    {
        /// <summary>
        /// 统一模型验证
        /// <para>控制器必须添加 [apicontroller] 才能被此过滤器拦截</para>
        /// </summary>
        /// <param name="services"></param>
        public static void glabalinvalidmodelstatefilter(this iservicecollection services)
        {
            services.configure<apibehavioroptions>(options =>
            {
                options.invalidmodelstateresponsefactory = actioncontext =>
                {
                    if (actioncontext.modelstate.isvalid)
                        return new badrequestobjectresult(actioncontext.modelstate);

                    int count = actioncontext.modelstate.count;
                    validationerrors[] errors = new validationerrors[count];
                    int i = 0;
                    foreach (var item in actioncontext.modelstate)
                    {
                        errors[i] = new validationerrors
                        {
                            member = item.key,
                            messages = item.value.errors?.select(x => x.errormessage).toarray()
                        };
                        i++;
                    }

                    // 响应消息
                    var result = apiresponsemodel.create(httpstatecode.status400badrequest, commonresponsetype.badrequest, errors);
                    var objectresult = new badrequestobjectresult(result);
                    objectresult.statuscode = statuscodes.status400badrequest;
                    return objectresult;
                };
            });
        }


        /// <summary>
        /// 用于格式化实体验证信息的模型
        /// </summary>
        private class validationerrors
        {
            /// <summary>
            /// 验证失败的字段
            /// </summary>
            public string member { get; set; }

            /// <summary>
            /// 此字段有何种错误
            /// </summary>
            public string[] messages { get; set; }
        }
    }
}

在 configureservices 函数中,添加以下代码:

            // 全局 api 请求实体验证失败信息格式化
            context.services.glabalinvalidmodelstatefilter();

创建后

让我们看看增加了统一模型验证器后,同样的请求返回的消息。

请求:

{
    "id": "1",
    "iphone": 123456789001234567890,
    "message": null
}

返回:

{
    "statucode": 400,
    "message": "请求的数据未能通过验证",
    "data": [
        {
            "member": "iphone",
            "messages": [
                "json integer 123456789001234567890 is too large or small for an int32. path 'iphone', line 3, position 35."
            ]
        }
    ]
}

说明我们的统一模型验证响应起到了作用。

但是有些验证会直接报异常而不会流转到上面的拦截器中,有些模型验证特性用错对象的话,他会报错异常的。例如上面的 maxlength ,已经用错了,maxlength 是指定属性中允许的数组或字符串数据的最大长度,不能用在 int 类型上。大家测试一下请求下面的 json,会发现报异常。

{
    "id": 1,
    "iphone": 1234567900,
    "message": "nullable"
}

以下是一些 asp.net core 内置验证特性,大家记得别用错:

  • [creditcard]:验证属性是否具有信用卡格式。 需要 jquery 验证其他方法。
  • [compare]:验证模型中的两个属性是否匹配。
  • [emailaddress]:验证属性是否具有电子邮件格式。
  • [phone]:验证属性是否具有电话号码格式。
  • [range]:验证属性值是否在指定的范围内。
  • [regularexpression]:验证属性值是否与指定的正则表达式匹配。
  • [required]:验证字段是否不为 null。 有关此属性的行为的详细信息
  • [stringlength]:验证字符串属性值是否不超过指定长度限制。
  • [url]:验证属性是否具有 url 格式。
  • [remote]:通过在服务器上调用操作方法来验证客户端上的输入。
  • [maxlength ] maxlength 是指定属性中允许的数组或字符串数据的最大长度

参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=netcore-3.1

本系列第二篇到此,接下来第三篇会继续添加一些基础服务。

补充:为什么需要统一格式

首先,你看一下这样的代码:

在每个 action 中,都充满了这种写法,每个相同的验证问题,在每个 action 返回的文字都不一样,没有规范可言。一个人写一个 return,就加上一下自己要表达的 文字,一个项目下来,多少 return ?全是这种代码,不堪入目。

通过统一模型验证和统一消息返回格式,就可以避免这些情况。

源码地址:https://github.com/whuanle/abpbasestruct

本教程结果代码位置:https://github.com/whuanle/abpbasestruct/tree/master/src/2/abpbase

到此这篇关于为abp框架添加基础集成服务的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com