wenmo8 发布的文章

在前面随笔《
C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理
》介绍了基于微信开放平台接口实现的微信扫码直接登录的过程。本篇介绍对扫码登录的一些改进和处理,以便更方便应用在实际项目中。

1、扫码登录配置处理

在我前面的随笔扫码登录处理中,介绍了账号登陆和扫码登录并存的情况,有时候我们可能想只是允许用户扫码登录即可,也就是不需要账号登陆,这种情况我们可以通过进行配置实现处理。
例如我在一个基于微信的系统应用里面,通过配置实现了仅仅允许扫码登录的效果。
也就是我们可以根据配置,因此其他登录界面,仅仅显示微信的扫码登录生成的二维码效果,如下所示。


在实现的代码里面,我们主要就是使用JS对界面显示进行控制即可。
一般情况下,我们在开发初期,可以允许账号密码登陆及扫码登录,一旦配置好相关的账号信息,就只需要扫码登录,屏蔽账号密码登陆了。


其中我们需要处理的HTML代码层就是增加一个openLoginDiv用来显示扫码登录的二维码即可。

其中的JS代码处理如下所示,直接从后台读取相关的信息,在JS上构建二维码。

2、用户扫码绑定及取消

在用户能够使用扫码登录前,我们在后台是需要先记录用户的微信标识的,也就是需要在系统上设置一个功能,让用户使用微信扫一下绑定,同时也应该设置一个功能让其解绑,方便对失效的用户进行处理。
在用户列表的二维码连接上,单击可以对用户进行微信扫码绑定,这样用户可以在扫码登录处直接扫码登录,不需要输入账号密码。


一旦成功绑定用户微信,在微信端会定位到一个绑定成功的页面,如下所示。


如果用户已经绑定了其中的一个用户而再去扫码绑定其他用户,那么会提示用户避免无法重复绑定账号即可。

另外,如果用户已经进行了二维码绑定,则可以在管理界面进行取消绑定,这样可以解绑用户账号和微信之间的关联。


绑定和取消绑定微信扫码操作的JS代码就是通过JQuery实现接口调用接口。

取消绑定微信扫码的界面的处理代码很简单,只需要在后台清楚对应的用户记录的openid和unionid即可。
具体的MVC控制器或者API代码如下所示。

而在底层BLL的代码里面,我们只需要做的就是更新指定的两个字段即可,把它们清空的代码如下所示。

在一个系统里面,往往有很多菜单项目,每个菜单项对应一个页面,一般用户只需要用到一些常用的功能,如果每次都需要去各个层次的菜单里面去找对应的功能,那确实有点繁琐。特别是在菜单繁多,而客户又对系统整体不熟悉的情况下,如果有一个类似浏览器的收藏夹模块,把一些常用的菜单连接保存起来,每次从这个收藏夹主页去找对应的页面,那样确实是省事省力,非常方便。本篇随笔就是介绍在基于Metronic的Bootstrap开发框架里面实现这个收藏夹的思路。

1、系统的收藏夹界面处理效果

为了实现这个收藏夹功能,我们也需要在系统页面的明显位置处放置一个收藏夹模块的入口,以及可以为每个页面添加到对应收藏夹的功能。
经过对比,我们把这些入口功能放在页面标题的附近,这样方便进行快速进行收藏夹,如下效果所示。


当我们在页面上单击【添加到收藏夹】按钮,我们就把对应的页面标题和连接加入到收藏夹记录里面了。

在【查看收藏夹】功能里面,我们可以展示我们加入的页面链接,单击其中某个记录,可以快速进入对应的页面,这样就实现了我们快速进入功能模块的需求了。


这里面最为关键的就是对收藏夹记录的排序处理,向上或者向下移动记录,使之能够符合界面的处理。

2、系统收藏夹的实现过程

了解了上面关于系统页面的收藏夹功能界面效果后,我们需要了解它的具体实现过程,首先我们需要设计一个表用来存储收藏夹对应的信息,页面标题、页面地址、排序等信息。
数据库设计界面如下所示。

我们注意到排序记录用Decimal格式进行存储,我们通过一个有经度的数值进行排序,这样我们可以调整的时候,修改它们之间的大小就可以了。
使用代码生成工具Database2Sharp快速生成底层的相关代码和Web的控制器和视图代码,然后整合到框架里面,这样我们就可以具有整个模块的界面和处理代码了。

由于一般情况下,我们对数据的显示编辑界面是相对标准的,对于收藏夹的入口展示的需求不太一样,我们需要参考列表界面增加一个视图,用来展示简单的入口界面,如图介绍所示。


这个界面里面包含了对记录的移动处理,包括向上或者向下。
前面介绍了,我们对记录的排序主要通过decimal类型的Seq字段实现的。
我们在实体类初始化的时候,给排序的赋值为当前时间的Unix时间戳。

其中上面的DateTimeToInt函数代码如下所示,也是我们常用的处理方式。

        /// <summary>
        ///扩展时间接口,可以返回整形数值/// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static int DateTimeToInt(thisDateTime time)
{
System.DateTime startTime
= TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));return (int)(time -startTime).TotalSeconds;
}

为了实现记录的移动,我们需要在业务BLL层实现一个移动的逻辑处理,方便在控制器里面调用。

        /// <summary>
        ///更新向上或者向下的顺序/// </summary>
        /// <param name="id">记录的ID</param>
        /// <param name="moveUp">往上,还是往下移动,往上则为true</param>
        /// <returns></returns>
        public bool UpDown(string id, bool moveUp)

实现的函数代码如下所示

/// <summary>
///更新向上或者向下的顺序/// </summary>
/// <param name="id">记录的ID</param>
/// <param name="moveUp">往上,还是往下移动,往上则为true</param>
/// <returns></returns>
public bool UpDown(string id, boolmoveUp)
{
//设置排序的规则 bool IsDescending = true;bool result = false;
WebFavoriteInfo info
=FindByID(id);if (info != null)
{
//构建查询的条件 string condition = "";if(IsDescending)
{
condition
= string.Format("Seq {0} {1}", moveUp ? ">" : "<", info.Seq);
}
else{
condition
= string.Format("Seq {0} {1}", moveUp ? "<" : ">", info.Seq);
}
var list =baseDal.Find(condition);decimal newSeq =0M;switch(list.Count)
{
case 0:
newSeq
= info.Seq;//已在顶部或者底部,顺序默认不变 break;case 1://上面或者下面有一个记录 if(IsDescending)
{
newSeq
= moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
else{
newSeq
= !moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
break;case 2://中间区域,取平均值 newSeq = (list[0].Seq + list[1].Seq) /2M;break;default://多于两个的情况 if(moveUp)
{
newSeq
= (list[list.Count - 2].Seq + list[list.Count - 1].Seq) /2M;
}
else{
newSeq
= (list[0].Seq + list[1].Seq) /2M;
}
break;
}
//统一修改顺序 info.Seq =newSeq;
result
=Update(info, info.ID);
}
returnresult;
}

这样我们在MVC的控制器里面,对这个BLL层接口进行进一步封装,方便页面前端进行Ajax调用处理即可,封装代码如下所示。

/// <summary>
///移动记录/// </summary>
/// <param name="id">记录ID</param>
/// <param name="up">向上为true,否则为false</param>
/// <returns></returns>
[HttpPost]public ActionResult UpDown(string id, boolup)
{
CommonResult result
= newCommonResult();if(!string.IsNullOrEmpty(id))
{
try{
result.Success
= BLLFactory<WebFavorite>.Instance.UpDown(id, up);
}
catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
}
}
returnToJsonContent(result);
}

这样我们在页面前端的界面视图里面,就可以对这个方法进行调用了。
首先在通过JS绑定生成前端HTML代码,如下所示。

$("#grid_body").html("");

$.each(data.rows,
function(i, item) {var tr = "<tr>";
tr
+= "<td><a class='btn btn-sm blue' href='" + item.Url + "'>" + item.Title + "</a></td>";

tr
+= "<td>";
tr
+= "<a href='javascript:;' class='btn btn-sm green' onclick=\"Up('" + item.ID + "')\" title='向上移动'><span class='glyphicon glyphicon-arrow-up icon-state-danger'></span></a>";
tr
+= "<a href='javascript:;' class='btn btn-sm blue' onclick=\"Down('" + item.ID + "')\" title='向下移动'><span class='glyphicon glyphicon-arrow-down'></span></a>";
tr
+= "</td>";

tr
+= "</tr>";
$(
"#grid_body").append(tr);
});

然后通过Up或者Down函数进行处理,向上或者向下移动位置。

var UpDownUrl = "/WebFavorite/UpDown"
functionUp(id) {var postData = { id: id, up: true};
$.post(UpDownUrl, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {
showTips(
"向上移动成功");
Refresh();
//刷新页面数据 }else{
showTips(data.ErrorMessage);
}
});
}
functionDown(id) {var postData = { id: id, up: false};
$.post(UpDownUrl, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {
showTips(
"向下移动成功");
Refresh();
//刷新页面数据 }else{
showTips(data.ErrorMessage);
}
});
}

这样就实现了我们所需要的移动顺序的操作了,另外添加的时候,我们判断对应用户是否有添加URL了,如果存在则不需要重复添加即可,前端只需要通过Ajax调用,然后响应处理即可。
通过这些代码的实现,我们可以实现收藏夹的快速管理和快速入口,为用户的使用提供了更加友好的体验。

在上篇随笔《
基于Metronic的Bootstrap开发框架经验总结(12)--页面链接收藏夹功能的实现
》上,我介绍了链接收藏夹功能的实现,以及对收藏记录的排序处理。该篇随笔主要使用功能按钮的方式移动收藏记录,功能虽然实现的还算不错,不过文章出来后,有读者同行指出可以利用直接拖动的方式实现排序更方便,因此对其中列表记录的排序进行了研究,从而介绍了如何利用Sortable开源JS组件实现拖动排序的处理,本篇随笔介绍了该组件在连接收藏夹排序中的应用。

1、收藏记录的排序处理回顾

上篇随笔介绍的收藏夹处理,主要就是为了方便用户快速进入常用功能的一个模块,随着收藏夹记录的增多,我们有必要对它们进行合理的排序,以方便我们的使用。
原来的收藏夹记录排序界面如下所示。


这个界面里面包含了对记录的移动处理,包括向上或者向下。
实现的逻辑代码主要就是对当前记录的前后记录的排序进行调整的处理,从而实现位置的调整,代码如下所示。

/// <summary>
///更新向上或者向下的顺序/// </summary>
/// <param name="id">记录的ID</param>
/// <param name="moveUp">往上,还是往下移动,往上则为true</param>
/// <returns></returns>
public bool UpDown(string id, boolmoveUp)
{
//设置排序的规则 bool IsDescending = true;bool result = false;
WebFavoriteInfo info
=FindByID(id);if (info != null)
{
//构建查询的条件 string condition = "";if(IsDescending)
{
condition
= string.Format("Seq {0} {1}", moveUp ? ">" : "<", info.Seq);
}
else{
condition
= string.Format("Seq {0} {1}", moveUp ? "<" : ">", info.Seq);
}
var list =baseDal.Find(condition);decimal newSeq =0M;switch(list.Count)
{
case 0:
newSeq
= info.Seq;//已在顶部或者底部,顺序默认不变 break;case 1://上面或者下面有一个记录 if(IsDescending)
{
newSeq
= moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
else{
newSeq
= !moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
break;case 2://中间区域,取平均值 newSeq = (list[0].Seq + list[1].Seq) /2M;break;default://多于两个的情况 if(moveUp)
{
newSeq
= (list[list.Count - 2].Seq + list[list.Count - 1].Seq) /2M;
}
else{
newSeq
= (list[0].Seq + list[1].Seq) /2M;
}
break;
}
//统一修改顺序 info.Seq =newSeq;
result
=Update(info, info.ID);
}
returnresult;
}

以上的代码,通过判断当前移动记录的位置,然后获取排序在其上面或者下面的记录,如果记录数量为0 ,那么就是顶端或者底端的了,如果是1条记录,那么就是在该记录上增加或者减除某个数值就作为新排序位置的值即可。如果是大于或等于2条记录记录,则取其最近的两个记录,取他们的平均值即可。

2、收藏夹的拖动排序处理

上面的处理能够满足基本的要求,而且调整位置也是正确的。但是我们如果能够拖动列表项进行排序的话,那样就更加方便、更加友好的了。
基于拖动的排序,我寻找到了一个比较好的JS处理组件(
Sortable
)这个在github上排名比较高,估计用的人也很多。
这个控件的使用相对比较简单,代码如下所示。

<ulid="items">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
</ul>var el = document.getElementById('items');
var sortable = new Sortable(el);

我们先来看看我最终使用Sortable整合好的界面效果。


这样我们就可以通过移动记录的方式进行调整位置。
列表的展示,我们还是使用分页的方式,为了提高检索效率。

    <divclass="portlet-body flip-scroll">
        <divclass="portlet-body">
            <div>
                <span>每页显示</span>
                <selectid="rows"onchange="ChangeRows()">
                    <option>10</option>
                    <optionselected>50</option>
                    <option>100</option>
                    <option>1000</option>
                </select>
                <span>条记录</span>&nbsp;&nbsp;
                <span>共有记录:</span><spanid='totalCount'class="label label-success">0</span>条,总页数:<spanid='totalPageCount'class="label label-success">0</span>页。</div>
            <hr/>
            <divid="grid_body"class='list-group'></div>

            <divclass="paging-toolbar">
                <ulid='grid_paging'></ul>
            </div>
        </div>
    </div>

在这里面我们通过在grid_body里面构建一系列的列表记录即可。

<divclass="list-group-item">
    <spanclass="glyphicon glyphicon-move"aria-hidden="true"></span>
    <aclass="btn btn-sm blue"id="e1f462c6-c749-4258-836f-e13ee8c8acd7"href="http://localhost:2251/User/Index?tid=2744DBF5-A648-47C1-9E9A-D8B405884389">系统用户信息</a>
    <iclass="js-remove"></i>
</div>

在记录的更新后,该Sortable组件有一个OnUpdate的事件可以处理,如下所示。

var grid_body = document.getElementById('grid_body');newSortable(grid_body, {
handle:
'.glyphicon-move',
filter:
".js-remove",
animation:
150,
onUpdate:
function (/**Event*/evt) {var list = [];//构造集合对象 $('.list-group div a').each(function(i, item) {
list.push({
'Text': item.text, 'Value': item.href });
});
var url = "/WebFavorite/EditFavorite";var postData ={ list: list };
$.post(url, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {//showTips("操作成功"); Refresh();//刷新页面数据 }else{
showTips(data.ErrorMessage);
}
});
},
});

这样我们把业务处理交给EditFavorite方法了,这里面主要对列表记录进行统一更新即可,处理逻辑就是先删除以前的记录,然后添加列表的集合记录,并且设置它们的排序记录为合适的顺序即可。

/// <summary>
///编辑记录列表/// </summary>
/// <param name="list">记录列表</param>
/// <returns></returns>
[HttpPost]public ActionResult EditFavorite(List<CListItem>list)
{
CommonResult result
= newCommonResult();var userid =CurrentUser.ID;

DbTransaction trans
= BLLFactory<WebFavorite>.Instance.CreateTransaction();if (trans != null)
{
try{//先删除就记录 var condition = string.Format("Creator='{0}'", userid);
BLLFactory
<WebFavorite>.Instance.DeleteByCondition(condition, trans);//逐条添加记录 int i =list.Count;foreach (CListItem item inlist)
{
WebFavoriteInfo info
= newWebFavoriteInfo();
info.Title
=item.Text;
info.Url
=item.Value;
info.Seq
= i--;
info.Creator
=CurrentUser.ID.ToString();

BLLFactory
<WebFavorite>.Instance.Insert(info, trans);
}

trans.Commit();
result.Success
= true;
}
catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
trans.Rollback();
LogHelper.Error(ex);
}
}
returnToJsonContent(result);
}

以上就是对收藏夹列表进行拖动排序的改进处理,希望在实际的项目中能够合理利用这个Sortable的JS组件,能够提高我们用户的体检效果。

在很多项目里面,对条形码和二维码的生成和打印也是一种很常见的操作,在Web项目里面,我们可以利用JS生成条形码和二维码的组件有很多。本文引入两个比较广泛使用的JS组件,用来处理条形码和二维码的生成处理,并介绍如何利用CLODOP组件实现内容的打印输出。
生成条形码使用组件JsBarcode,生成二维码使用组件qrcodejs。

1、条形码的生成

条码的作用一般在一些商品标签上,方便使用条码枪快速、准确录入信息。
如下所示是一种条形码


这里条形码生成使用了JsBarcode组件进行处理,它支持很多格式的条码格式,如下所示。


它的简单代码例子如下所示。

//HTML代码<svgid="barcode"></svg>
<!--or-->
<canvasid="canvas"></canvas>
<!--or-->
<imgid="barcode"/>//JS代码
JsBarcode("#barcode", "Hi!");
// or with jQuery
$("#barcode").JsBarcode("Hi!");

生成的图片格式如下所示。


JsBarcode组件支持多种选项参数的设置,如下所示的代码

JsBarcode("#barcode", "1234", {
format:
"pharmacode",
lineColor:
"#0aa",
width:
4,
height:
40,
displayValue:
false});

JsBarcode(
"#barcode")
.options({font:
"OCR-B"}) //Will affect all barcodes .EAN13("1234567890128", {fontSize: 18, textMargin: 0})
.blank(
20) //Create space between the barcodes .EAN5("12345", {height: 85, textPosition: "top", fontSize: 16, marginTop: 15})
.render();

通过该组件的案例代码介绍,我们可以了解到生成常规二维码的使用方式。
例如我们在界面上添加了以下HTML代码。

<divclass="row"id="barDiv1">
    <divclass="col-md-4 col-sm-4 col-xs-4">条形码(770 145 896 701):<br/>
        <imgalt=""id="barcode1" />
    </div>
    <divclass="col-md-4 col-sm-4 col-xs-4">条形码(770 145 896 702):<br/>
        <imgalt=""id="barcode2" />
    </div>
    <divclass="col-md-4 col-sm-4 col-xs-4">条形码(770 145 896 703):<br/>
        <imgalt=""id="barcode3" />
    </div>
</div>

然后通过JS代码实现二维码的生成。

//使用JsBarcode生成条形码
for (var i = 1; i < 10; i++) {var barcodeValue = "77014589670" + i;//前缀 + 数值
    JsBarcode("#barcode" +i, barcodeValue, {
format:
"CODE128",
displayValue:
true,
fontSize:
24,
lineColor:
"#0cc"});
}

最后我们可以看到具体的生成效果如下所示。


从这里我们可以看到,使用JS实现条形码的生成是非常方便简洁的,同事速度也是很不错的。

2、二维码的生成

二维码实现可以通过使用组件qrcodejs进行生成,二维码也可以使用组件jquery-qrcode进行生成,也相对比较简洁,不过打印二维码文档的时候,jquery-qrcode没有显示二维码图片,而组件qrcodejs则工作正常,因此推荐使用组件qrcodejs。
这个qrcodejs组件使用方式也很简单,基础使用代码如下所示。

<divid="qrcode"></div>
<scripttype="text/javascript">
newQRCode(document.getElementById("qrcode"),"http://jindo.dev.naver.com/collie");</script>

复杂一点的使用JS代码如下所示。

<divid="qrcode"></div>

<scripttype="text/javascript">
varqrcode= newQRCode(document.getElementById("qrcode"), {
text:
"http://jindo.dev.naver.com/collie",
width:
128,
height:
128,
colorDark :
"#000000",
colorLight :
"#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
</script>

通过了解该组件的使用,我们可以在项目中增加一个实际的代码进行测试,如下所示。

<tableborder='0'width='100%'>
    <tr>
        <td>设备二维码(9001):<br/>
            <divid="imgDevice9001"></div>
        </td>
        <td>设备二维码(9002):<br/>
            <divid="imgDevice9002"></div>
        </td>
        <td>设备二维码(9003):<br/>
            <divid="imgDevice9003"></div>
        </td>
    </tr>
    <trstyle="height:20px"><tdcolspan="3"></td></tr>......</table>

具体我们可以利用JS动态生成相关的二维码。

//设备图片二维码
for (var i = 9001; i < 9010; i++) {var url = "http://www.iqidi.com/H5/device?devicecode=" +i;//使用jquery.qrcode的做法
    //$("#imgDevice" + i).qrcode({ width: 100, height: 100, text: url });
    //$("#imgDevice" + i).css("height", "100px");

    //使用qrcodejs的做法
    var qrcode = new QRCode(document.getElementById("imgDevice" +i), {
text: url,
width:
128,
height:
128,
colorDark:
"#000000",
colorLight:
"#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
}

最后界面生成的二维码如下所示。

3、条形码和二维码的打印处理

介绍我二维码和条形码的生成,关于它们的打印,可以利用我介绍过的CLODOP进行打印处理,需要了解可以参考下随笔《
基于MVC+EasyUI的Web开发框架经验总结(16)--使用云打印控件C-Lodop打印页面或套打报关运单信息

我们在这里介绍的打印,也是基于这个控件的打印处理的。
当然,如果打印,也是可以利用PrintThis这个组件进行处理的(详细可以参考随笔《
基于Metronic的Bootstrap开发框架经验总结(9)--实现Web页面内容的打印预览和保存操作
》),不过总体效果没有上面的CLODOP的打印效果佳。
打印JS代码如下所示,可以采用下面两个函数的其中之一进行处理。

//使用表格格式化输出
functionPrintBarcodeWithTable() {
LODOP
=getLodop();
LODOP.PRINT_INIT(
"条码_格式化输出");var strHTML = "<table border='0' width='100%' >";
strHTML
= strHTML + "<tr><td>";
strHTML
= strHTML + $("#barDiv1").html();
strHTML
= strHTML + "</td></tr>";
strHTML
= strHTML + "<tr><td>";
strHTML
= strHTML + $("#barDiv2").html();
strHTML
= strHTML + "</td></tr>";
strHTML
= strHTML + "<tr><td>";
strHTML
= strHTML + $("#barDiv3").html();
strHTML
= strHTML + "</td></tr>";
LODOP.ADD_PRINT_TABLE(
88, 200, 700, 900, strHTML);

LODOP.SET_SHOW_MODE(
"PREVIEW_IN_BROWSE", 1);
LODOP.PREVIEW();
}
//使用分页输出 functionPrintBarcodeWithPaging() {
LODOP
=getLodop();
LODOP.PRINT_INIT(
"条码_分页输出");
LODOP.ADD_PRINT_HTM(
100, 100, 600, 800, $("#barDiv1").html());
LODOP.NewPage();
LODOP.ADD_PRINT_HTM(
100, 100, 600, 800, $("#barDiv2").html());
LODOP.NewPage();
LODOP.ADD_PRINT_HTM(
100, 100, 600, 800, $("#barDiv3").html());
LODOP.NewPage();

LODOP.SET_SHOW_MODE(
"PREVIEW_IN_BROWSE", 1);
LODOP.PREVIEW();
}

条码的打印效果如下所示。

而打印二维码的JS代码如下所示。

//打印二维码
functionPrintQrcode2() {
CreatePrintData($(
"#qrcode").html());
LODOP.SET_SHOW_MODE(
"PREVIEW_IN_BROWSE", 1);
LODOP.PREVIEW();
}
//打印数据构建 functionCreatePrintData(html) {
LODOP
=getLodop();

LODOP.PRINT_INIT(
"");//var strBodyStyle = "<link type='text/css' rel='stylesheet' href='/Content/Themes/Default/style.css' />"; var strBodyStyle = "<style><!--table { border:1;background-color: #CBCBCC } td {border: 1; }";
strBodyStyle
+= " th { background-color:#F1F1F3;padding-left:5px;border:1}--></style>";var strFormHtml = strBodyStyle + "<body>" + html + "</body>";
LODOP.ADD_PRINT_HTM(
20, 40, 710, 900, strFormHtml);
LODOP.PREVIEW();
}

得到的二维码打印效果如下所示。


最终完成了这个关于条形码、二维码的动态生成,以及图形打印的相关操作了。整个模块的界面如下所示。


关于条形码、二维码的处理,我们这里引入的条形码组件JsBarcode和二维码组件qrcodejs是非常不错的开源JS组件,满足了我们大多数的要求,而且使用方便、简洁,希望这些内容能够给你的项目提供灵感及用处。

在之前开发的很多Web API项目中,为了方便以及快速开发,往往把整个Web API的控制器放在基目录的Controllers目录中,但随着业务越来越复杂,这样Controllers目录中的文件就增加很快,难以管理,而且如果有不同业务模块有重复的控制器名的话,还需要尽量避免。引入Area的作用就是把控制器按照不同的业务模块进行区分,方便管理,而且控制器名称可以重名。

1、Web API项目引入Area进行分类

Area在项目中可以称之为区域,每个Area代表应用程序的不同功能模块,Area 使每个功能模块都有各自的文件夹,文件夹中有自己的Controller、View和Model,但对于管理也增加了一定的难度。如果是Web API项目,我们可以把不必要的目录移除即可,简化对目录的管理。
引入Area可以是我们不同的业务模块可以重名,而且各个业务模块管理起来也更加方便,在原先的Web API项目里面,它们的目录是这样的。


虽然我们把它们的目录归类,但是它们还是存放在一个命名空间下的。

namespace MyWebApi.Controllers


这样使用虽然也没有什么问题,但是还是存在一些弊端,因此引入Area的方式对不同业务模块的控制器进行管理,以达到我们分类管理的目的。
引入Area前,我们的API路径如下所示

http://localhost:9001/api/User

引入Area后,我们把常规的权限管理、字典管理等基础模块放到Framework的Area里面,那么这个时候API路径和具体的Area相关,地址则变成了如下:

http://localhost:9001/api/Framework/User

我们再来看看具体的项目目录,Web API项目中使用Area后,Controller的目录如下所示。


除了在各个不同Area下有不同的控制器,而且也增加了一个
**
AreaRegistration.cs的文件,如对应Framework的Area,有一个FrameworkAreaRegistration.cs文件
这样对应下面的控制器,它的命名空间如下所示。

namespace WebAPI.Areas.Framework.Controllers

2、Web API项目对Area控制器的路径映射

上面小节介绍了使用Area来对Web API控制器的分类管理,并介绍了引入Area后对控制器位置、命名空间、Web API的URL等方面的不同。这样如果我们要解析对应地址的Web API,那么也需要做一定的处理,否则是无法找到对应的控制器,从而出现错误信息。
首先我们需要修改Web API里面WebApiConfig的配置信息,如下所示。


上面指定了默认的Web API映射,并指定结果只做JSON格式的输出(移除XML输出)。
为了对不同的Area实现API的地址处理,我们先设计一个基类,然后让不同的Area注册类继承它,方便统一处理。


其中基类Area注册类的CustomAreaRegistration类代码如下所示。


有了上面的基类映射 RegisterArea函数,我们只需要在子类设置对应的AreaName基类实现不同Area子类的正确映射API路径处理了。

/// <summary>
///框架基础Area的注册类/// </summary>
public classFrameworkAreaRegistration : CustomAreaRegistration 
{
public override stringAreaName
{
get{return "Framework";
}
}
}

当然为了实现对Area的Web API控制器的URL正确解析,获取属于Action、Controller、以及对应命名空间的对象,那么还需要在global.asa.cs里面添加一行代码,如下所示。

 //对Web API的Area进行支持
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),  new AreaHttpControllerSelector(GlobalConfiguration.Configuration));

其中AreaHttpControllerSelector是我们自定义的HTTP控制器地址解析器,需要根据我们的地址提取出具体的控制器、Area名称、程序集类型等,方便构建对应的解析器。

privateHttpControllerDescriptor GetApiController(HttpRequestMessage request)
{
var controllerName = base.GetControllerName(request);var areaName =GetAreaName(request);if (string.IsNullOrEmpty(areaName))
{
return null;
}
var type =GetControllerTypeByArea(areaName, controllerName);if (type == null)
{
return null;
}
return newHttpControllerDescriptor(_configuration, controllerName, type);
}

有了这些基础的管理,我们就可以定义好我们所需要Area,然后构建具体业务范畴下的控制器接口即可。

3、Web API在客户端的接口调用

所有的Web API地址,都是与具体的Area有关系,例如在Framework业务下的字典模块,它们Web API配置的地址如下所示。

<!--字典Web API模块配置-->

<add key="DictType" value="http://localhost:27206/api/Framework/DictType"/>

<add key="DictData" value="http://localhost:27206/api/Framework/DictData"/>

<add key="CorpDictData" value="http://localhost:27206/api/Framework/CorpDictData"/>

<add key="City" value="http://localhost:27206/api/Framework/City"/>

<add key="District" value="http://localhost:27206/api/Framework/District"/>

<add key="Province" value="http://localhost:27206/api/Framework/Province"/>

<add key="UserParameter" value="http://localhost:27206/api/Framework/UserParameter"/>

我们在客户端,只需要对Web API进行封装即可,这个部分可以使用Database2Sharp代码生成工具进行统一的生成,所有继承关系统一处理好,我们所做的就是进行新增接口的处理即可。
例如对于字典模块DictData的处理,它对于Web API的封装类如下所示。

/// <summary>
///DictData, 基于API服务的Facade接口实现类/// </summary>
public class DictDataCaller : BaseApiService<DictDataInfo>, IDictDataService

这个基类,默认封装了对常规数据表业务Web API接口方式的增删改查以及各种复杂的接口处理。
如果对于一般的Web API(非数据表业务),那么只需要继承的基类做调整即可。

/// <summary>
///基于API服务的Facade接口实现类/// </summary>
public class TestCaller : NormalApiService, ITestService

这个NormalApiService基类,默认只是封装了对token和签名的读取处理,没有特殊的业务接口,具体特定的接口我们来实现处理。

对于WebAPI客户端的调用,我们主要就是需要构建对应的URL,然后通过GET传递或者POST传递一些参数,并读取HTML结果,把它解析为对应的类型数据即可,如下代码所示。

/// <summary>
///根据字典类型名称获取对应的字典记录/// </summary>
/// <param name="dictTypeName">字典类型名称</param>
/// <returns></returns>
public List<DictDataInfo> FindByDictType(stringdictTypeName)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url = GetTokenUrl(action) + string.Format("&dictTypeName={0}", dictTypeName);

List
<DictDataInfo> result = JsonHelper<List<DictDataInfo>>.ConvertJson(url);returnresult;
}

通过GetTokenUrl(action) 函数获取对应的URL地址,由于传入一个参数,接口这里没有发生数据修改,是GET方式提交参数数据,因此把参数附加在URL即可。
也就是下面代码实现了完整Web API地址的构建。

string url = GetTokenUrl(action) + string.Format("&dictTypeName={0}", dictTypeName);

构建好这些URL地址后,我们通过获取对应Web API的结果并进行序列号到具体对象即可。如下代码所示。

List<DictDataInfo> result = JsonHelper<List<DictDataInfo>>.ConvertJson(url);

关于Web API接口的设计文章,可以参考我的随笔。

通过以上的封装处理,那么对于业务表的Web API接口调用,具体使用客户端的代码如下所示。

var dictType = CallerFactory<IDictTypeService>.Instance.GetTree();
Console.WriteLine(dictType.ToJson());
var dictData = CallerFactory<IDictDataService>.Instance.GetAllDict();
Console.WriteLine(dictData.ToJson());

如果对于非数据表业务的Web API接口调用,具体使用客户端的代码如下所示。

var testAction = CallerFactory<ITestService>.Instance.TestAction();
Console.WriteLine(testAction.ToJson());
var test = CallerFactory<ITestService>.Instance.Test("123");
Console.WriteLine(test.ToJson());

这样,不管是在Web项目里面,还是在Winform项目里面,或者在跨平台的IOS项目里面(或者安卓项目),都可以以相同的方式消费Web API,这样我们所有的数据入口在一个地方,可以集中业务接口的统一开发,并且可以有效管理我们的数据提供的性能问题,如统一缓存处理,统一权限处理...
感谢大家对本文章的细心阅读,希望对您的开发有所启发或帮助。