2023年2月

在上篇随笔《
Web API应用架构在Winform混合框架中的应用(1)
》中我介绍了关于如何在Winfrom里面整合WebAPI,作为一个新型数据源的接入方式,从而形成了三种不同的数据提供来源,前文在介绍整体性框架方面,着笔较多,注重整合的统一性,但是在Web API方面还不算很具体,本系列将继续这个主题,介绍Web API开发中常见到的一些问题,对其中各个技术要点进行总结,本文主要介绍Web API自定义异常结果的处理。

1、常规的异常处理

统一的异常处理,把正确的信息返回给调用者很重要,可以让接口开发人员或者用户,了解具体的原因所在,这样可以得到有效的错误处理。

参考微信API的处理,微信API,对于调用都有一个错误信息返回,不会直接裸露未经处理的异常,因此它们都是经过了一定的拦截处理,然后把错误信息包装提供给接口调用方的。如下是微信的一些接口处理错误。

错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

{"errcode":40013,"errmsg":"invalid appid"}

我们根据自己的需要,定义了一个统一的错误信息实体类,如下所示。

    /// <summary>
    ///接口返回的错误信息/// </summary>
    public classBaseResultJson
{
/// <summary> ///错误代码/// </summary> public int errcode { get; set; }/// <summary> ///如果不成功,返回的错误信息/// </summary> public string errmsg { get; set; }/// <summary> ///是否成功/// </summary> public bool success { get; set; }
}

这样我们就可以把拦截到的错误信息,转换为这样一个方便使用的实体类信息了。

拦截Web API的调用异常,一般可以结合Try Catch的方法,以及异常拦截器进行处理,如下是主动抛出的一些异常信息处理。

            //如果没有通过,则抛出异常,由异常过滤器统一处理
            if (!result.success)
{
throw newMyApiException(result.errmsg, result.errcode);
}

其中MyApiException是自定义的一个异常信息,用来承载自定义错误信息的异常类。

异常拦截器,我们在Web API里面可以通过Attribute这种标签特性进行处理,如下是我在Web API的基类里面定义了一个异常处理器。

    /// <summary>
    ///所有接口基类/// </summary>
[ExceptionHandling]public class BaseApiController : ApiController

这个特性对象的定义,它的代码如下所示。

    /// <summary>
    ///API自定义错误过滤器属性/// </summary>
    public classExceptionHandlingAttribute : ExceptionFilterAttribute
{
/// <summary> ///统一对调用异常信息进行处理,返回自定义的异常信息/// </summary> /// <param name="context">HTTP上下文对象</param> public override voidOnException(HttpActionExecutedContext context)
{
//自定义异常的处理 MyApiException ex = context.Exception asMyApiException;if (ex != null)
{
throw new HttpResponseException(newHttpResponseMessage(HttpStatusCode.InternalServerError)
{
//封装处理异常信息,返回指定JSON对象 Content = new StringContent(new BaseResultJson(ex.Message, false, ex.errcode).ToJson()),
ReasonPhrase
= "Exception"});

}
//记录关键的异常信息 Debug.WriteLine(context.Exception);//常规异常的处理 string msg = string.IsNullOrEmpty(context.Exception.Message) ? "接口出现了错误,请重试或者联系管理员": context.Exception.Message;throw new HttpResponseException(newHttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content
= newStringContent(msg),
ReasonPhrase
= "Critical Exception"});
}
}

根据这些代码,我们就可以实现对调用异常的统一封装处理,让它给我们返回统一的对象信息了,如下是其中一个调用异常,转换为自定义异常信息后的结果输出。

{"errcode":404,"errmsg":"请求的资源不支持 http 方法“POST”。","success":false}

这样我们在处理Web API的返回结果的时候,可以先处理它的异常信息,具体的处理代码如下所示。

            HttpHelper helper = newHttpHelper();
helper.ContentType
= "application/json";string content = helper.GetHtml(url, postData, true);
VerifyErrorCode(content);

T result
= JsonConvert.DeserializeObject<T>(content);return result;

我们在上面红色部分的代码就是先处理异常定义信息,如果有这些异常,我们可以在界面中进行异常处理显示了。

例如,如果自定义异常存在,我们转换后,把对应的信息显示出来,重新抛出异常即可。

                BaseResultJson errorResult = JsonConvert.DeserializeObject<BaseResultJson>(content);//非成功操作才记录异常,因为有些操作是返回正常的结果({"errcode": 0, "errmsg": "ok"})
                if (errorResult != null && !errorResult.success)
{
string error = string.Format("请求发生错误!错误代码:{0},说明:{1}", (int)errorResult.errcode, errorResult.errmsg);
LogTextHelper.Error(errorResult.ToJson());
throw new Exception(error);//抛出错误 }

2、地址接口异常处理

对于常规的异常,我们通过上面的处理方式,就可以很好进行拦截并处理了,如果接口异常是全局性的,如访问地址簿正确,或者参数多了几个信息,那么调用的接口就不是有效的地址,这样的话,返回的信息就不会被上面的拦截器进行处理了。

如我们给一个无效的API调用路径,在浏览器中获得下面错误结果。

上面结果就无法被我们的常规异常拦截器所捕获,因此不会输出经过封装好的异常信息。

如果需要拦截,我们需要增加自己的消息代理处理,用来捕获这些特殊的异常信息。

    public static classWebApiConfig
{
public static voidRegister(HttpConfiguration config)
{

..............

config.MessageHandlers.Add(
new CustomErrorMessageDelegatingHandler());

上面红色部分就是我们自己添加的消息代理处理类,用来处理一些特殊的异常信息,如下代码所示。

    /// <summary>
    ///API自定义错误消息处理委托类。///用于处理访问不到对应API地址的情况,对错误进行自定义操作。/// </summary>
    public classCustomErrorMessageDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage>SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>{
HttpResponseMessage response
=responseToCompleteTask.Result;
HttpError error
= null;if (response.TryGetContentValue<HttpError>(outerror))
{
//添加自定义错误处理//error.Message = "Your Customized Error Message"; }if (error != null)
{
//获取抛出自定义异常,有拦截器统一解析 throw new HttpResponseException(newHttpResponseMessage(HttpStatusCode.NotFound)
{
//封装处理异常信息,返回指定JSON对象 Content = new StringContent(new BaseResultJson(error.Message, false, 404).ToJson()),
ReasonPhrase
= "Exception"});
}
else{returnresponse;
}
});
}
}

经过了上面的处理后,我们进一步测试一下不存在的地址的异常处理结果,可以看到输出的内容是经过了自定义对象的转换了。

常规的调用,如果接口不对应,那么错误也是类似下面的消息

{"errcode":404,"errmsg":"找不到与请求 URI“http://localhost:9001/api/SystemType/Delete?signature=72f8d706c79dc14d70fc3f080d4706748d754021&timestamp=1443194061&nonce=0.650359861866666663&appid=website_9A39C2A8&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxIiwiaWF0IjoxNDQzMTk0MDM4LCJqdGkiOiI1YmEyYmE5Ni0yZTA4LTQ1ZTgtYTAwNy01MmY3OTkzYTg2NzEiLCJuYW1lIjoiYWRtaW4iLCJjaGFubmVsIjoiMCIsInNoYXJlZGtleSI6IjEyMzRhYmNkIn0.RRXQmmSCJzDK5Or6rmBL5wjd-YIJoEQFc0pOzqhR6IU”匹配的 HTTP 资源。","success":false}

有了这些信息,我们就可以统一我们的调用规则,并进行异常记录和显示了,非常方便。

系列文章如下所示:

Web API应用架构在Winform混合框架中的应用(1)

Web API应用架构在Winform混合框架中的应用(2)--自定义异常结果的处理

Web API接口设计经验总结

Web API应用架构在Winform混合框架中的应用(3)--Winfrom界面调用WebAPI的过程分解

Web API应用架构在Winform混合框架中的应用(4)--利用代码生成工具快速开发整套应用

在Web API接口的开发过程中,我们可能会碰到各种各样的问题,我在前面两篇随笔《
Web API应用架构在Winform混合框架中的应用(1)
》、《
Web API应用架构在Winform混合框架中的应用(2)--自定义异常结果的处理
》也进行了总的介绍,在经过我的大量模块实践并成功运行后,总结了这篇随笔,希望对大家有所帮助。

1、在接口定义中确定MVC的GET或者POST方式

由于我们整个Web API平台是基于MVC的基础上进行的API开发,因此整个Web API的接口,在定义的时候,一般需要显示来声明接口是[HttpGet]或者[HttpPost],虽然有些接口也可以不用声明,但是避免出现类似下面的错误信息,显式声明还是有好处的。

请求的资源不支持 http 方法“POST

例如在基类定义的查找对象接口如下所示。

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="id">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
[HttpGet]public virtual T FindByID(string id, string token)

如果是增删改的接口,一般需要声明为POST方式提交数据,而且基于安全性的考虑,需要携带更多的参数。

        /// <summary>
        ///插入指定对象到数据库中/// </summary>
        /// <param name="info">指定的对象</param>
        /// <returns>执行操作是否成功。</returns>
[HttpPost]public virtual CommonResult Insert(T info, string token, string signature, string timestamp, string nonce, string appid)

2、动态对象的接口定义

在一般的Web API接口里面,我们可能都会碰到很多简单类型的参数,但是又想让它们以POST方式提交数据,那么我们就可以有两种方法来处理,一种是定义一个类来放置这些参数,一种是采用动态的JObject参数,前者有很多不方便的地方,因为我们不可能为每个接口参数定义多一个实体类,这样可能会有很多难以管理的类定义。如下面是微信API的调用接口案例,我们也需要设置这样的处理规则。

接口调用请求说明
http请求方式: POST(请使用https协议)
https://api.weixin.qq.com/cgi-bin/groups/update?access_token=ACCESS_TOKEN
POST数据格式:json
POST数据例子:{"group":{"id":108,"name":"test2_modify2"}}

那么我们采用JObject是这么样的呢,我们来看接口的定义和处理代码。JObject是Newtonsoft.Json.Linq命名空间下的一个对象。

        /// <summary>
        ///修改用户密码/// </summary>
        /// <param name="param">包含userName和userPassword的复合对象</param>
        /// <param name="token">用户访问令牌</param>
        /// <returns></returns>
[HttpPost]public CommonResult ModifyPassword(JObject param,  stringtoken)
{
//令牌检查,不通过则抛出异常 CheckResult checkResult =CheckToken(token);dynamic obj =param;if (obj != null)
{
string userName =obj.userName;string userPassword =obj.userPassword;bool success = BLLFactory<User>.Instance.ModifyPassword(userName, userPassword);return newCommonResult(success);
}
else{throw new MyApiException("传递参数出现错误");
}
}

其中我们把JObject对象转换为我们所需要的对象的时候,因为我们没有定义具体的实体类,因此采用了dynamic语法,声明这是一个动态对象,由运行时获取对应的属性。

dynamic obj = param;

这样我们就可以在调用的时候,动态POST对应的JSON对象给Web API接口,而不需要预先定义各种接口参数的类了。

        /// <summary>
        ///调用Web API接口,修改用户密码/// </summary>
        /// <param name="userName">用户名称</param>
        /// <param name="userPassword">修改的密码</param>
        /// <returns>如果修改成功返回true,否则返回false</returns>
        public bool ModifyPassword(string userName, stringuserPassword)
{
var action = "ModifyPassword";var postData = new{
userName
=userName,
userPassword
=userPassword
}.ToJson();
string url =GetTokenUrl(action);

CommonResult result
= JsonHelper<CommonResult>.ConvertJson(url, postData);return (result != null) ? result.Success : false;
}

其中GetTokenUrl是根据token和API的地址等参数,构建一个完整的提交地址。我们在上面代码通过

            var postData = new{
userName
=userName,
userPassword
=userPassword
}.ToJson();

就可以动态创建一个对象,并生成它的JSON字符串,把数据POST提交到对应的API接口里面即可,然后对结果进行对象的转换就算完成了。

3、集合和分页的处理

在很多接口里面,我们都需要用到分页的处理,Web API也不例外,这样可以提交数据检索效率,减少服务器数据处理的压力,同时也提交客户端的数据显示速度。

一般的集合接口定义如下所示(通用性基类接口)。

        /// <summary>
        ///返回数据库所有的对象集合/// </summary>
        /// <returns>指定对象的集合</returns>
[HttpGet]public virtual List<T> GetAll(stringtoken)
{
//检查用户是否有权限,否则抛出MyDenyAccessException异常 base.CheckAuthorized(AuthorizeKey.ListKey, token);

List
<T> list =baseBLL.GetAll();returnlist;
}

但是这样的返回记录会比较多,一般情况下需要分页,那么分页的处理接口定义如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <returns>指定对象的集合</returns>
[HttpPost]public virtual PagedList<T> FindWithPager(string condition, PagerInfo pagerInfo, string token)

分页接口,在这里返回的结果里面,用了一个PageList的泛型类,这个方便我们获取当前的记录及总数,它的定义如下所示。

    /// <summary>
    ///分页集合/// </summary>
    /// <typeparam name="T">对象</typeparam>
    public class PagedList<T>{/// <summary>
        ///返回记录的总数/// </summary>
        public int total_count { get; set; }/// <summary>
        ///列表集合/// </summary>
        public List<T> list { get; set; }
}

最后整个分页的处理Web API接口实现如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <returns>指定对象的集合</returns>
[HttpPost]public virtual PagedList<T> FindWithPager(string condition, PagerInfo pagerInfo, stringtoken)
{
//检查用户是否有权限,否则抛出MyDenyAccessException异常 base.CheckAuthorized(AuthorizeKey.ListKey, token);

List
<T> list =baseBLL.FindWithPager(condition, pagerInfo);//构造成Json的格式传递 var result = new PagedList<T>() { total_count = pagerInfo.RecordCount, list =list };returnresult;
}

最后客户端调用分页的Web API代码如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="condition">查询的条件</param>
        /// <param name="pagerInfo">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public virtual List<T> FindWithPager(string condition, refPagerInfo pagerInfo)
{
var action = "FindWithPager";string url = GetTokenUrl(action) + string.Format("&condition={0}", condition);var postData =pagerInfo.ToJson();

List
<T> result = new List<T>();
PagedList
<T> list = JsonHelper<PagedList<T>>.ConvertJson(url, postData);if (list != null)
{
pagerInfo.RecordCount
= list.total_count;//修改总记录数
result =
list.list;
}
returnresult;
}

4、混合框架界面整合Web API接口

在整个Web API的平台构建以及在混合框架的整合过程中,我把各个模块还是遵循相对独立的方式进行开发和整合,它们实现了从直接访问数据库、以WCF服务获取数据,以及通过WebAPI调用方式获取数据几种方式的统一,从而实现了整个混合框架的高度整合。

整个混合框架的核心是以相对独立的方式,整合各个可重用的模块,我们可以遵循一定的基础上,快速构建统一的应用平台。

搭建完毕的整个WebAPI平台,其中包括了服务端内容,以API控制器的方式,发布了对应的Web API接口。

在每个混合框架的独立模块里面,我们封装了对应的Web API客户端调用处理,从而实现了Web API的调用方式。

在Win10下,使用Web API模式运行混合框架,获得的主体界面效果如下所示。

独立模块权限管理系统界面如下所示。

系列文章如下所示:

Web API应用架构在Winform混合框架中的应用(1)

Web API应用架构在Winform混合框架中的应用(2)--自定义异常结果的处理

Web API接口设计经验总结

Web API应用架构在Winform混合框架中的应用(3)--Winfrom界面调用WebAPI的过程分解

Web API应用架构在Winform混合框架中的应用(4)--利用代码生成工具快速开发整套应用

Web API应用架构在Winform混合框架中的应用(5)--系统级别字典和公司级别字典并存的处理方式

在Winform方面,基于业务的不同,我们可以利用工具的效率,快速做很多不同的业务系统,前期做了一个缺陷管理系统,想把单位的测试业务规范下,也好统计和分析每个版本的缺陷信息,本篇整理这些数据库设计和界面设计的相关内容,做一个介绍,希望给大家一个对比参照的案例进行学习了解。

1、缺陷管理系统的业务分析

在很多缺陷管理系统里面,我们往往需要管理的就是缺陷信息的记录,以及缺陷记录的开闭过程,从而实现了测试人员-》开发人员-》测试人员的整个闭环过程。

一般情况下,缺陷管理系统可以部署在局域网内,或者公网内方便各个项目组成员的使用,因此可以采用BS的架构,也可以CS架构实现分布式的应用,通常情况下,采用CS的方式,在数据处理方面会友好一些,因此结合需求,我这里采用了我常用的混合型框架,数据采用WCF服务获取,实现数据的共享。

缺陷信息,一般是属于具体某个项目,某个版本,某个模块下的具体信息,因此这些分类必须独立进行维护,才能方便进行后面的统计和分析,一般缺陷信息还需要提交出错的图片附件信息,方便直观的了解和开发人员排错。

缺陷项目的信息,为了分类,我们还可以分为一个域进行管理,也就是分组的概念,如按不同类型的项目,分为不同的项目域,如产品项目、客户项目、研究性项目等分类都可以。

以上这些都是缺陷里面很重要的属性信息,因此我们需要在缺陷管理系统里面很好的设计才能使得我们的系统符合我们的业务流程。

一般情况下,版本和模块是对应不同项目的,缺陷状态、缺陷类型、优先级、紧急程度,这些是通用的字典项目,不会因为项目而变化。

另外,缺陷处理一般是一个闭环流程,最初一般由测试人员发起(状态为未解决),提交给开发人员,开发人员根据缺陷记录,设置合理的状态,如果是正常需要处理的缺陷,一般需要修正错误,并设置状态为已解决,然后测试人员对缺陷进行回归测试,如果缺陷已经解决,则设置缺陷状态为已关闭,否则重新打开缺陷为未解决状态,依次循环,最后大家达到共识,是不解决或者推迟解决等。这些修改的过程也需要在缺陷系统里面进行记录,已进行跟踪历史。

2、缺陷管理系统的数据库设计

为了存储上面的缺陷信息,我们需要设计一些表进行数据的存储,除了一些通用性的信息存储,如字典信息、附件信息,采用通用模块存储外,对于缺陷的相关信息,我们还需要设计如下的表对象来存储信息。

上面的表之间有一定的关系,在红色部分进行了标识。

3、缺陷系统的界面设计

界面设计还是采用常见的工具栏的方式进行布局,根据缺陷的信息关系,我们查看信息 的时候,让用户先在具体的域下面选择具体的项目,然后展示该项目下的缺陷信息。

选择项目信息后,我们一般需要根据项目显示它的缺陷列表,缺陷信息提供重要的信息展示。

双击可以展开具体的缺陷信息,其中包括附件提交和查看管理,缺陷的修改历史等信息。

对于缺陷里面的一些字典属性,我们可以通过统一的字典模块进行维护,如下所示,可以对缺陷状态、缺陷类型、优先级、严重程度等信息进行独立的维护管理。

一般情况下,我们最终还是需要根据缺陷的记录,获得一定的统计分析报表,以便对该版本是否能够正常发布进行一个评估工作,或者是了解缺陷的分类、趋势等信息的需要。

例如,我们可以在缺陷分类统计里面,把缺陷状态、缺陷类型、严重程度、优先级等根据版本信息进行一个分类统计,也可以根据缺陷的趋势进行统计等等。

我们在日常接触到的一些软件界面,往往喜欢深入分析其实现机理,通过学习、分析,可以提高我们对软件架构的分析能力,从而实现更加弹性、可重用的模块。在无意间,看到一个短信模块的设计,模板里面可以包含了很多变量,从而引发我对其深入分析的兴趣,本文主要介绍我对这些实现的一些理解,介绍一个包含动态变量的短信模板的设计分析。

1、短信模板的效果介绍

如下图所示,上面包含了短信模板的列表信息,以及对具体模板的内容设计,可以在其中插入变量的处理。

如果是硬编码对个别的实现应该很容易,如果我们考虑可扩展性、重用性等因素,需要进行弹性的设计,那么就需要寻找好的思路来动态处理这些变量了。

如它的每个模板的变量都是可以不同的,如其中一个模板有如下变量可以插入到模板内容里面,变量本身用左右的括弧{}进行标识,还是很有规律的。

2、短信模板的设计分析

由于模板是针对某个企业的,企业可能根据需要进行一定的调整,模板可以恢复为最初的版本,因此我们可以考虑设计两个部分,一个部分是基础模板,一个部分是对企业的消息模板,前者不变,后者可以根据需要修改,如无修改保存的,则采用对应的基础模板,也就是模板的模板了。

由于模板是包含有不同变量的,因此我们需要在设计的时候,动态解析这些变量。我们如果能够把传入的对象动态获取它的属性,并给对应的变量赋值,应该就可以实现这个功能。

那么我们就需要通过反射方式,动态获取对应object对象的各种属性名称和值了,这个处理操作如下所示。

        /// <summary>
        ///把object对象的属性反射获取到字典列表中/// </summary>
        /// <param name="data">object对象</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetProperties(objectdata)
{
Dictionary
<string, string> dict = new Dictionary<string, string>();

Type type
=data.GetType();string[] propertyNames = type.GetProperties().Select(p =>p.Name).ToArray();foreach (var prop inpropertyNames)
{
object propValue = type.GetProperty(prop).GetValue(data, null);string value = (propValue != null) ? propValue.ToString() : "";if(!dict.ContainsKey(prop))
{
dict.Add(prop, value);
}
}
returndict;
}

前面介绍了,模板的内容,是包含有左右括弧{}的变量的,因此我们可以通过正则表达式,把它们提取出来备用,这样我们就可以把前面反射对应属性的值赋值给模板内容,从而形成新的待发送的具体消息了。

            var regex = @"\{(?<name>.*?)\}";
List
<string> itemList = GetList(template, regex, "name");

其中GetList就是一个正则表达式获取对应正则内容的函数,函数代码如下所示。

        /// <summary>
        ///多个匹配内容/// </summary>
        /// <param name="sInput">输入内容</param>
        /// <param name="sRegex">表达式字符串</param>
        /// <param name="sGroupName">分组名, ""代表不分组</param>
        public static List<string> GetList(string sInput, string sRegex, stringsGroupName)
{
List
<string> list = new List<string>();
Regex re
= new Regex(sRegex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace |RegexOptions.Multiline);
MatchCollection mcs
=re.Matches(sInput);foreach (Match mc inmcs)
{
if (sGroupName != "")
{
list.Add(mc.Groups[sGroupName].Value);
}
else{
list.Add(mc.Value);
}
}
returnlist;
}

最后我们可以把内容对应上,给模板替换为最新的属性值了。

        private void button1_Click(objectsender, EventArgs e)
{
//模板文本 var template = "姓名 = {姓名}, 性别 = {性别}, 卡号 = {卡号}, 公司 = {公司}";//数据对象 var data = new { 姓名 = "伍华聪", 性别 = "", 卡号 = "10000", 公司 = "广州爱奇迪软件科技有限公司"};

ReplaceTemplate(template, data);
}
private void ReplaceTemplate(string template, objectdata)
{
var regex = @"\{(?<name>.*?)\}";
List
<string> itemList = GetList(template, regex, "name");

Console.WriteLine(
"列出模板变量对象:");foreach(string item initemList)
{
Console.WriteLine(item);
}

Dictionary
<string, string> dict =GetProperties(data);foreach(string item initemList)
{
//如果属性存在,则替换模板,并修改模板值 if(dict.ContainsKey(item))
{
template
= Replace(template, regex, dict.Values.ToList());//替换第一个 }
}

Console.WriteLine(template);
}

测试上面的代码,我们注意到我们的对象是动态构建的,也就是使用匿名类的方式构建一个类,包含有对应的属性值,把这个给模板进行解析,从而赋值形成真正的消息内容的。

var data = new { 姓名 = "伍华聪", 性别 = "", 卡号 = "10000", 公司 = "广州爱奇迪软件科技有限公司" };

测试案例,我们可以得到的消息,如下所示。

我们从上面截图可以看到,真正的消息已经正确得到,这些是替换具体的模板内容形成的,如果我们在整体的模板设计里面,采用这个思路,就可以实现动态的模板消息发送,从而实现了比较弹性化的设计处理。

本文只是对模板变量这种场景进行了分析,我们在实际项目中,可以更加完善,实现更加强大的模板消息处理,以及利用微信模板消息进行一起发送,实现短信、微信,甚至邮件的同步通知,给客户更好的体验。

以上是微信的模板消息介绍,我们也可以结合动态变量,把对应模板消息替换为我们要发送的消息,从而也实现了微信消息的动态发送。

利用好的界面控件,往往能做成比较界面体验效果。在一些界面操作里面,我们可能把它拆分为几部进行处理,这个时候引入WizardControl向导控件应该是比较不错的选择了。多步的处理方式,可以让用户避免一次性输入太多内容的烦躁心情,也可以针对性的校验部分内容,本文以利用WizardControl控件来设计找回密码的结果来进行介绍,使大家对基于DevExpress的WizardControl向导控件的使用有一个大概的了解。

1、界面效果的规划

在一般APP或者基于网络的软件界面里面,都有一个为了帮助用户找回账号密码的功能,可以让用户自助通过手机、邮件等方式获得充值密码的机会。如一般的APP界面效果如下所示。


但是一般Winform的界面,可以利用向导控件做的更好,其中DevExpress的WizardControl向导控件就是一个很好的选择。

我们一般在DevExpress的VS工具栏里面选择导航布局选项卡,就可以找到对应的WizardControl向导控件了。

最终我们实现的效果如下所示。

2、控件的使用及代码处理

上面介绍了,在在DevExpress的VS工具栏里面选择导航布局选项卡,就可以找到对应的WizardControl向导控件了。

我们拖动能这个控件到一个空白的窗体界面上,就可以看到默认有一些界面了,我们在其中可以看到一个完整的向导界面效果的。

拖动过来的控件,初始化界面效果都是英文的,可以通过控件属性对其中的文字进行修改即可。

修改后的界面效果如下所示。

然后我们修改向导控件的一些属性,如图片、文字等内容,最后在其中空白的位置,拖入一些界面控件,实现我们的界面效果即可。

另外默认的向导控件是三个界面页的,因此我们可以根据需要增加或者删除一些,如本例我就移除了一个,仅仅使用两个页面来处理密码的找回处理即可。

另外,我们为了实现向导控件界面的输入验证和处理,我们往往还需要对其中下一步、完成、取消、帮助等事件进行处理,这样才能达到较好的处理效果。

其中部分处理代码如下所示。

        private void wizardControl1_NextClick(objectsender, DevExpress.XtraWizard.WizardCommandButtonClickEventArgs e)
{
string pageText =e.Page.Text;if(pageText == "验证账号")
{
if (this.txtMobile.Text.Length == 0 || this.txtValidateCode.Text.Length == 0)
{
MessageDxUtil.ShowTips(
"请输入手机号码和验证码");
e.Handled
= true;this.txtValidateCode.Focus();return;
}
else if(!ValidateUtil.IsValidMobile(this.txtMobile.Text))
{
MessageDxUtil.ShowTips(
"请输入正确的手机号码");
e.Handled
= true;this.txtMobile.Focus();return;
}
else{bool result = CallerFactory<ISmsCodeService>.Instance.CheckSmsCode(this.txtMobile.Text, this.txtValidateCode.Text);if (!result)
{
MessageDxUtil.ShowTips(
"验证码校验不正确,请检查验证码是否在有效时间内。");this.txtValidateCode.Focus();return;
e.Handled
= true;
}
}
}
else if(pageText == "重置密码")
{
MessageDxUtil.ShowTips(pageText);
}
}

在这些Next下一步事件里面,有一个代码是需要阻塞下一步的处理的。

e.Handled = true;

这样我们就可以实现对用户输入的验证处理了,如果处理不通过,那么就停留在这个页面上,让用户校正输入即可。

如果是完成按钮页面,它的处理也是差不多。

        private void wizardControl1_FinishClick(objectsender, CancelEventArgs e)
{
if (this.txtCorpAccount.Text.Length == 0)
{
this.txtCorpAccount.Focus();
MessageDxUtil.ShowTips(
"公司账号不能为空!");
e.Cancel
= true;return;
}
else if(this.txtNewPassword.Text.Length == 0)
{
this.txtNewPassword.Focus();
MessageDxUtil.ShowTips(
"密码不能为空!");
e.Cancel
= true;return;
}
else if (!this.txtNewPassword.Text.Equals(this.txtRePassword.Text))
{
this.txtRePassword.Focus();
MessageDxUtil.ShowTips(
"两次密码不一致!");
e.Cancel
= true;return;
}


...............

最后我们实现的效果就是前面所说的一样了。

结合短信平台,我们可以给用户发送验证码以及提示消息即可。