2023年2月

在我们开发WCF项目的时候,常常会碰到一些莫名其妙的错误,有时候如果根据它的错误提示信息,一般很难定位到具体的问题所在,而由于WCF服务的特殊性,调试起来也不是那么方便,因此往往会花费不少时间来进行跟踪处理。本文介绍我在我在我的框架里面使用WCF服务的时候,出现的一个常见错误的处理方法,它的提示信息是:
基础连接已经关闭: 连接被意外关闭
。这种情况我碰到的有两种,一种是返回DataTable的时候出现的,一种是返回实体类类中带有枚举类型的情况,下面分别对它们的处理进行介绍。

1、DataTable返回值出现错误

一般情况下,我们在设计WCF接口返回值的时候,都使用实体类或者实体类列表这种方式,但是有时候也可能需要返回一个DataTable的数据类型,如果按照下面的方式构造函数内容并返回,就可能出现错误。

定义接口

        /// <summary>
        ///根据查询条件,返回记录到DataTable集合中/// </summary>
        /// <param name="condition">查询条件</param>
        /// <param name="pagerInfo">分页条件</param>
        /// <returns></returns>
[OperationContract]
DataTable FindToDataTableWithPager(
string condition, ref PagerInfo pagerInfo);

实现函数,为了方便演示,填写了简单的测试代码。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="condition">查询的条件</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public DataTable  FindToDataTableWithPager(string condition, refPagerInfo pagerInfo)
{
//测试代码 DataTable dt = newDataTable();
dt.Columns.Add(
"Test");for (int i = 0; i < 10; i++)
{
dt.Rows.Add(
new object[] { "Test" +i });
}
returndt;
}

测试的时候,会发现出现错误

如果你按照它的提示,你可能搔破头皮也不知道具体是哪里的问题,这个地方其实很有意思,就是因为序列号DataTable的时候,没有TableName导致的,如果把函数体的实现里面,为返回的DataTable对象的TableName属性赋值一个值就可以了。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="condition">查询的条件</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public  DataTable  FindToDataTableWithPager(string condition, refPagerInfo pagerInfo)
{
//测试代码 DataTable dt = newDataTable();
dt.Columns.Add(
"Test");for (int i = 0; i < 10; i++)
{
dt.Rows.Add(
new object[] { "Test" +i });
}
dt.TableName
= "test";returndt;
}

测试之后,就发现能够顺利执行了。

2、实体类中含有枚举类型导致的错误

实体类定义,定义了一个枚举的对象
ApplyStatus

    /// <summary>
    ///流程申请单/// </summary>
[DataContract]public classApplyInfo : BaseEntity
{
........
/// <summary> ///当前状态(0:处理中,1:已完成,2:已退回,3:已撤消)(其它值为非法值)/// </summary> [DataMember]public virtualApplyStatus Status
{
get{return this.m_Status;
}
set{this.m_Status =value;
}
}
}

枚举的定义一般情况如果设置如下代码:

    /// <summary>
    ///表单当前处理状态。///当前状态(0:处理中,1:已完成,2:已退回,3:已撤消)(其它值为非法值)/// </summary>
[DataContract]public enumApplyStatus
{
/// <summary> ///处理中 = 0/// </summary> 处理中 = 0,/// <summary> ///已完成 = 1/// </summary> 已完成 = 1,/// <summary> ///已退回 = 2/// </summary> 已退回 = 2,/// <summary> ///已撤消 =3/// </summary> 已撤消 = 3}

然后定义WCF服务接口和实现代码。

WCF接口定义

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="condition">查询的条件</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
[OperationContract]
List
<T> FindWithPager(string condition, ref PagerInfo info);

WCF服务接口实现

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="condition">查询的条件</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public List<ApplyInfo> FindWithPager(string condition, refPagerInfo info)
{
returnbll.FindWithPager(condition, info);
}

那么通过WCF服务获取这个对象的信息时候,就会出现错误:
基础连接已经关闭: 连接被意外关闭

出现错误的原因,是因为枚举类型序列化出现了错误,需要在枚举的值里面增加[EnumMember]的声明,如下所示。

[DataContract]public enumApplyStatus
{
/// <summary> ///处理中 = 0/// </summary> [EnumMember]
处理中
= 0,/// <summary> ///已完成 = 1/// </summary> [EnumMember]
已完成
= 1,/// <summary> ///已退回 = 2/// </summary> [EnumMember]
已退回
= 2,/// <summary> ///已撤消 =3/// </summary> [EnumMember]
已撤消
= 3}

再次编译运行并测试,最后的结果就是能够正常解析了。

以上就是我们在开发WCF的时候,经常碰到的一些错误的解决方式,希望大家在开发WCF并进行调用的时候,碰到类似的问题,能够很好解决问题,节省宝贵的时间。

在很多Web界面中,我们都可以看到很多下拉列表的元素,有些是固定的,有些是动态的;有些是字典内容,有些是其他表里面的名称字段;有时候引用的是外键ID,有时候引用的是名称文本内容;正确快速使用下拉列表的处理,可以提高我们程序界面的美观性和友好型,本文主要介绍在我的Web开发框架以及相关的扩展Web应用中用到的一些代表性下拉列表的处理场景,希望给大家做个参考学习。

1、固定下拉列表的处理

代表性的固定列表有人员性别的处理,一般为男、女两种,而且为了方便,一般用文本直接使用,如下效果所示。

而它们的页面代码也很简单,如下所示。

<select class="easyui-combobox" id="Sex" name="Sex" style="width:120px;">
       <option>男</option>
       <option>女</option>  
 </select>

这种方式是比较简单,需要几个选项就添加,不需要额外的JS代码进行绑定处理,也不需要后台代码。但是这种硬编码的方式建议少用,除非不需要扩展,只有固定几项的情况下。

这种方式,在EasyUI的处理方式中,comboBox的控件绑定代码如下所示。

$("#Sex").combobox('setValue', info.Sex);

而获取控件的值我们就不再赘述了,一般通过JS代码把它们统一处理就可以了,如下代码所示。

var postData = $("#ffAdd").serializeArray();

固定列表还有一种方式就是显示的是文本,存储的是数值,如0,1这样的方式。如我的微信框架里面,有对用户信息修改界面里面,是否订阅公众号的信息,它后面存储的值是数值,显示的是文本。

界面代码如下所示,这种存储也是固定列表,不过它们显示和后台存储的内容不同。

                    <tr>
                        <th>
                            <labelfor="Subscribe">是否订阅该公众号:</label>
                        </th>
                        <tdcolspan="3">
                            <selectid="Subscribe"name="Subscribe"class="easyui-combobox"style="width:150px">
                                <optionvalue="1">订阅</option>
                                <optionvalue="0">取消</option>
                            </select>&nbsp;&nbsp;&nbsp;
                        </td>
                    </tr>

而对于这样控件,绑定的代码也是和上面一样,只是绑定的内容是数值而已。

$("#Subscribe1").combobox('setValue', info.Subscribe);

而如果我们需要在View视图里面,通过Lable控件进行显示这个数值为文本,那么只需要对它进行简单处理就可以了。

$("#Subscribe2").text(info.Subscribe == "1" ? "订阅": "取消");

2、动态下拉列表的处理

刚才说到,固定列表在对于一些常规简单的选项尚可,但是对于需要变化或者较多选项的情况下,需要用到动态的下拉列表,动态下拉列表有好几种方式,一种是类似字典的绑定文本方式处理,一种是绑定外键引用的名称方式等等。

下面我们来介绍一下两个不同的处理方式。

1)字典文本内容的下拉列表

在很多情况下,我们需要用到很多字典内容,多数情况下是引用字典的值(一般情况为文本内容)进行存储就可以了。字典列表在很多情况很方便,我们可以在后台进行字典项的动态管理,这样能够及时反馈到界面元素上,实现列表内容的动态展示了。

例如我在联系人编辑的Web界面里面,需要使用很多字典项的内容,界面效果如下所示。

上面红色框选的都是动态的下拉列表项目,那我们如何动态绑定它的内容在Web界面上的呢?

首先我们需要在字典视图的控制器上定义一个字典函数,用来返回Json信息给界面使用的,具体后台代码如下所示。

        /// <summary>
        ///根据字典类型获取对应的字典数据,方便UI控件的绑定/// </summary>
        /// <param name="dictTypeName">字典类型名称</param>
        /// <returns></returns>
        public ActionResult GetDictJson(stringdictTypeName)
{
List
<CListItem> treeList = new List<CListItem>();
CListItem pNode
= new CListItem("", "");
treeList.Insert(
0, pNode);

Dictionary
<string, string> dict = BLLFactory<DictData>.Instance.GetDictByDictType(dictTypeName);foreach (string key indict.Keys)
{
treeList.Add(
newCListItem(key, dict[key]));
}
returnToJsonContent(treeList);
}

上面的CListItem是一个自定义类,包含了Text和Value两个属性。

上面的控制器函数,返回的内容可能是下面的类似格式(Json格式)

[
{
Text:
"",
Value:
""},
{
Text:
"教授",
Value:
"教授"},
{
Text:
"护士长",
Value:
"护士长"}
]

上面说明了控制器的处理逻辑和数据格式,那么视图代码里面,如何通过JS代码进行绑定动态列表呢?

由于多次使用了绑定字典列表的操作,我们先定义一个通用的JS函数,用来减少代码重复,如下所示

        functionBindDictItem(control, dictTypeName) {
$(
'#' +control).combobox({
url:
'/DictData/GetDictJson?dictTypeName='+dictTypeName,
valueField:
'Value',
textField:
'Text'});
}

然后在需要初始化界面的字典列表的时候,调用这个JS函数就可以了,如下代码所示。

        //初始化字典信息
        functionInitDictItem() {
BindDictItem(
"Titles", "职称");
BindDictItem(
"Rank", "职务");
BindDictItem(
"Importance", "重要级别");
BindDictItem(
"Recognition", "对公司认可程度");
BindDictItem(
"InterestDemand", "客户利益诉求");
BindDictItem(
"CareFocus", "客户关心重点");
BindDictItem(
"ResponseDemand", "负责需求");
BindDictItem(
"RelationShip", "与公司关系");
BindDictItem(
"Nationality", "民族");
BindDictItem(
"Political", "政治面貌");
BindDictItem(
"JobType", "职业类型");
BindDictItem(
"Eduction", "学历");
BindDictItem(
"Animal", "属相");
BindDictItem(
"Constellation", "星座");
BindDictItem(
"MarriageStatus", "婚姻状况");
BindDictItem(
"HealthCondition", "健康状况");
BindDictItem(
"BodyType", "体型");
}

2)绑定外键引用名称

看完上面的动态列表处理,也许已经解决你大多数的问题了,但是有时候我们可能有这样的需求,在一个表里面需要引用另外一个表的ID,但是我们需要在界面编辑的时候,直观一些,也就是通过名称来代替ID的显示,后台存储的时候,存储这个控件的ID值就可以了。例如我在创建客户联系人的时候,我可能需要选择客户,因此需要设计一个功能按钮,弹出一个界面供我从列表中选择客户,选择好后在主界面中显示客户的名称;如果已经保存过进行编辑的,直接显示客户名称就可以了,需求效果如下所示。

选择客户后,效果如下所示。

了解了上面的界面效果,具体代码是如何实现的呢?

其实就是需要定义一个ID的隐藏字段用于存储数据库,一个是显示的只读的文本框用来显示名称,还有一个按钮就可以了,部分代码如下所示。

  <tr> 
          <th>
             <labelfor="Customer_ID">客户名称:</label>
           </th>
           <tdcolspan="3">
              <inputclass="easyui-validatebox"style="width:300px;background-color:peachpuff"readonly="readonly"type="text"id="Customer_Name"name="Customer_Name"data-options="required:true,validType:'length[1,50]'"/>
               <inputtype="hidden"style="width:300px"id="Customer_ID"name="Customer_ID" />
               <ahref="javascript:void(0)"class="easyui-linkbutton"id="btnSelectCustomer"iconcls="icon-search">选择客户</a>
            </td>
</tr>

选择客户的时候,我们通过调用一个窗口进行客户信息的展示,然后客户选择后,把主界面的内容更新一下就可以,具体JS界面代码如下所示。

        //绑定选择客户按钮的事件
        functionBindSelectCustomerEvent() {
$(
"#btnSelectCustomer").click(function() {
$.showWindow({
title:
'选择客户',
useiframe:
true,
width:
900,
height:
700,
content:
'url:/Customer/SelectCustomer',
data: { id: $(
'#Customer_ID'), name: $('#Customer_Name') },
buttons: [{
text:
'确认',
iconCls:
'icon-ok',
handler:
'doOK' //此方法在弹出页面中 }, {
text:
'取消',
iconCls:
'icon-cancel',
handler:
function(win) {
win.close();
}
}],
onLoad:
function(win, content) {//window打开时调用,初始化form内容 if(content) {
content.doInit(win);
}
}
});
});
}

在用户已有数据的情况下如果打开界面我们把客户的名称转义过来赋值给对应名称的控件就可以了,ID隐藏的控件按正常赋值就可以了,如下所示

                $.getJSON("/Customer/GetCustomerName?id=" + info.Customer_ID, function(result) {
$(
"#Customer_Name").val(result);
});

基于MVC4+EasyUI的Web开发框架的系列文章:

基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍


基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用

基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用

基于MVC4+EasyUI的Web开发框架形成之旅--框架总体界面介绍

基于MVC4+EasyUI的Web开发框架形成之旅--基类控制器CRUD的操作

基于MVC4+EasyUI的Web开发框架形成之旅--权限控制

基于MVC4+EasyUI的Web开发框架经验总结(1)-利用jQuery Tags Input 插件显示选择记录

基于MVC4+EasyUI的Web开发框架经验总结(2)- 使用EasyUI的树控件构建Web界面

基于MVC4+EasyUI的Web开发框架经验总结(3)- 使用Json实体类构建菜单数据

基于MVC4+EasyUI的Web开发框架经验总结(4)--使用图表控件Highcharts

基于MVC4+EasyUI的Web开发框架经验总结(5)--使用HTML编辑控件CKEditor和CKFinder

基于MVC4+EasyUI的Web开发框架经验总结(6)--在页面中应用下拉列表的处理

基于MVC4+EasyUI的Web开发框架经验总结(7)--实现省份、城市、行政区三者联动

基于MVC4+EasyUI的Web开发框架经验总结(8)--实现Office文档的预览

基于MVC4+EasyUI的Web开发框架经验总结(9)--在Datagrid里面实现外键字段的转义操作

基于MVC4+EasyUI的Web开发框架经验总结(10)--在Web界面上实现数据的导入和导出

基于MVC4+EasyUI的Web开发框架经验总结(11)--使用Bundles处理简化页面代码

基于MVC4+EasyUI的Web开发框架经验总结(12)--利用Jquery处理数据交互的几种方式

基于MVC4+EasyUI的Web开发框架经验总结(13)--DataGrid控件实现自动适应宽带高度

基于MVC4+EasyUI的Web开发框架经验总结(14)--自动生成图标样式文件和图标的选择操作

为了提高客户体验和进行一些技术探索,现在正准备把我自己的客户关系管理系统CRM在做一个Web的版本,因此对基于MVC的Web界面继续进行一些研究和优化,力求在功能和界面上保持和Winform一致,本文主要介绍在我的CRM系统中用到的全国省份、城市、行政区三者的两种效果,在Winform上实现没问题,在Web上基于MVC的EasyUI实现,同样也没有问题。

1、Winform上省份、城市、行政区的联动效果

在很早的时候,我在Winform框架的一篇随笔《
Winform开发框架之字典管理模块的更新,附上最新2013年全国最新县及县以上行政区划代码sql脚本
》中介绍了在Winform版本里面的三者联动效果,界面如下所示。

并附赠送了我自己根据统计局数据修改整理后的全国省份、城市、行政区的数据脚本。全国地区的省份、城市、区县这些新政区划的最新Sql脚本下载地址如下所示:
https://files.cnblogs.com/wuhuacong/CityDistrict.rar

里面主要通过对控件本身选择的事件进行处理,然后动态获取不同的数据进行展示,大致的逻辑就是先初始化省份数据,然后省份控件选择时触发获取该省份下的城市信息,城市控件选择的时候触发获取该城市的行政区数据,大概的代码如下所示。

        private void txtProvince_SelectedIndexChanged(objectsender, EventArgs e)
{
CListItem item
= this.txtProvince.SelectedItem asCListItem;if (item != null)
{
string provinceId =item.Value;this.txtCity.Properties.BeginUpdate();this.txtCity.Properties.Items.Clear();
List
<CityInfo> cityList = BLLFactory<City>.Instance.GetCitysByProvinceID(provinceId);foreach (CityInfo info incityList)
{
this.txtCity.Properties.Items.Add(newCListItem(info.CityName, info.ID.ToString()));
}
this.txtCity.Properties.EndUpdate();
}
}
private void txtCity_SelectedIndexChanged(objectsender, EventArgs e)
{
CListItem item
= this.txtCity.SelectedItem asCListItem;if (item != null)
{
string cityId =item.Value;this.txtDistrict.Properties.BeginUpdate();this.txtDistrict.Properties.Items.Clear();
List
<DistrictInfo> districtList = BLLFactory<District>.Instance.GetDistrictByCity(cityId);foreach (DistrictInfo info indistrictList)
{
this.txtDistrict.Properties.Items.Add(newCListItem(info.DistrictName, info.ID.ToString()));
}
this.txtDistrict.Properties.EndUpdate();
}
}

2、基于MVC+EasyUI的Web上实现省份、城市、行政区的联动

有了全国的省份、城市、行政区数据,加上对三者的数据访问进行了封装,参考Winform版本的实现过程,当然在EasyUI的Web上实现起来,也是可以的。

我们先来看看实现的效果,然后在分析其中的实现思路和代码,基于MVC+EasyUI的实现效果如下所示。


上面的效果是如何实现的呢?

1)定义相关的控制器函数,提供Json数据源

为了实现控件的数据绑定,我们第一步需要为这几个控件定义一些控制器的函数,方便获取相关的数据。其中的CListItem有Text 和 Value两个属性,可以用于绑定操作。

        /// <summary>
        ///获取所有的省份/// </summary>
        /// <returns></returns>
        publicActionResult GetAllProvince()
{
List
<CListItem> treeList = new List<CListItem>();

List
<ProvinceInfo> provinceList = BLLFactory<Province>.Instance.GetAll();foreach (ProvinceInfo info inprovinceList)
{
treeList.Add(
newCListItem(info.ProvinceName, info.ProvinceName));
}
returnToJsonContent(treeList);
}
        /// <summary>
        ///根据省份名称获取对应的城市列表/// </summary>
        /// <param name="provinceName">省份名称</param>
        /// <returns></returns>
        public ActionResult GetCitysByProvinceName(stringprovinceName)
{
List
<CListItem> treeList = new List<CListItem>();

List
<CityInfo> cityList = BLLFactory<City>.Instance.GetCitysByProvinceName(provinceName);foreach (CityInfo info incityList)
{
treeList.Add(
newCListItem(info.CityName, info.CityName));
}
returnToJsonContent(treeList);
}
        /// <summary>
        ///根据城市名称获取对应的行政区划类别/// </summary>
        /// <param name="cityName">城市名称</param>
        /// <returns></returns>
        public ActionResult GetDistrictByCityName(stringcityName)
{
List
<CListItem> treeList = new List<CListItem>();string condition = string.Format("");
List
<DistrictInfo> districtList = BLLFactory<District>.Instance.GetDistrictByCityName(cityName);foreach (DistrictInfo info indistrictList)
{
treeList.Add(
newCListItem(info.DistrictName, info.DistrictName));
}
returnToJsonContent(treeList);
}

2)在视图里面添加控件绑定数据的JS代码

为了实现三个ComboBox的控件的联动效果,我们需要使用JS代码实现数据的绑定,并绑定控件的Change事件,一旦用户选择其中一个,那么可能触发其他另外一个获取数据源。

        //绑定省份、城市、行政区信息
        functionBindProvinceCity() {var province = $('#Province').combobox({
valueField:
'Value', //值字段 textField: 'Text', //显示的字段 url: '/Province/GetAllProvince',
editable:
true,
onChange:
function(newValue, oldValue) {
$.get(
'/City/GetCitysByProvinceName', { provinceName: newValue }, function(data) {
city.combobox(
"clear").combobox('loadData', data);
district.combobox(
"clear")
},
'json');
}
});
var city = $('#City').combobox({
valueField:
'Value', //值字段 textField: 'Text', //显示的字段 editable: true,
onChange:
function(newValue, oldValue) {
$.get(
'/District/GetDistrictByCityName', { cityName: newValue }, function(data) {
district.combobox(
"clear").combobox('loadData', data);
},
'json');
}
});
var district = $('#District').combobox({
valueField:
'Value', //值字段 textField: 'Text', //显示的字段 editable: true});
}

然后界面上需要摆放这几个控件。

                        <tr>      
                            <th>
                                <labelfor="Province">所在省份:</label>
                            </th>
                            <td>
                                <selectclass="easyui-combobox"id="Province"name="Province"style="width:120px;"></select> 
                            </td>
                            <th>
                                <labelfor="City">城市:</label>
                            </th>
                            <td>
                                <selectclass="easyui-combobox"id="City"name="City"style="width:120px;"></select> 
                            </td>
                        </tr>
                        <tr>
                            <th>
                                <labelfor="District">所在行政区:</label>
                            </th>
                            <td>
                                <selectclass="easyui-combobox"id="District"name="District"style="width:120px;"></select>
                            </td>
                            <th>
                                <labelfor="Hometown">籍贯:</label>
                            </th>
                            <td>
                                <selectclass="easyui-combobox"id="Hometown"name="Hometown"style="width:120px;"></select>
                            </td>
                        </tr>

OK,我们实现了数据的初始化绑定,一旦用户选择了省份数据,那么对应的城市数据列表也会更新了,选择城市,那么行政区也接着更新了,这一切似乎都搞定了?

还没有,还需要考虑对编辑状态下的数据赋值,如果实体类的信息里面,已经有数据了,那么绑定控件后,是否会正常显示呢?

3)控件内容的绑定

一般情况下,我们通过Ajax操作来获取控制器的数据,然后绑定到界面控件上,如下所示。

            $.getJSON("/Contact/FindByID?id=" + ID, function(info) {//赋值有几种方式:.datebox('setValue', info.Birthday);.combobox('setValue', info.Status);.val(info.Name);.combotree('setValue', info.PID);
                $("#ID").val(info.ID);
$(
"#Customer_ID").val(info.Customer_ID);
$(
"#HandNo").val(info.HandNo);
$(
"#Name").val(info.Name);$("#Province").combobox('setValue', info.Province);
$("#City").combobox('setValue', info.City);
$("#District").combobox('setValue', info.District);
$("#Hometown").combobox('setValue', info.Hometown);
..................
});
}

如果没有联动的效果处理,一般情况下,这种赋值的操作,没有问题的,但是我发现使用这种方法操作城市和行政区的数据显示不正常,开始百思不得其解,测试了几种方法操作,都没有使得城市、行政区的界面控件能够正常显示。

原来发现,造成这种问题的原因,可能是使用异步操作,它们的联动效果还没有处理完毕,就执行赋值操作了,导致可能数据无法正常显示。

因此改用设置为同步的操作,如下红色代码所示,把async设置为false就表示同步,测试后发现这个设置后,界面控件能够正常显示了,一切都正常,终于解决心头大患了。

            //使用同步方式,使得联动的控件正常显示
            $.ajaxSettings.async = false;//首先用户发送一个异步请求去后台实现方法
            $.getJSON("/Contact/FindByID?id=" + ID, function (info) {

以上就是我对于经常用到的全国省份、城市、行政区的Web上的联动操作的界面效果和实现代码,希望给大家提供一个参考的案例,共同提高。

基于MVC4+EasyUI的Web开发框架的系列文章:

基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍


基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用

基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用

基于MVC4+EasyUI的Web开发框架形成之旅--框架总体界面介绍

基于MVC4+EasyUI的Web开发框架形成之旅--基类控制器CRUD的操作

基于MVC4+EasyUI的Web开发框架形成之旅--权限控制

基于MVC4+EasyUI的Web开发框架经验总结(1)-利用jQuery Tags Input 插件显示选择记录

基于MVC4+EasyUI的Web开发框架经验总结(2)- 使用EasyUI的树控件构建Web界面

基于MVC4+EasyUI的Web开发框架经验总结(3)- 使用Json实体类构建菜单数据

基于MVC4+EasyUI的Web开发框架经验总结(4)--使用图表控件Highcharts

基于MVC4+EasyUI的Web开发框架经验总结(5)--使用HTML编辑控件CKEditor和CKFinder

基于MVC4+EasyUI的Web开发框架经验总结(6)--在页面中应用下拉列表的处理

基于MVC4+EasyUI的Web开发框架经验总结(7)--实现省份、城市、行政区三者联动

基于MVC4+EasyUI的Web开发框架经验总结(8)--实现Office文档的预览

基于MVC4+EasyUI的Web开发框架经验总结(9)--在Datagrid里面实现外键字段的转义操作

基于MVC4+EasyUI的Web开发框架经验总结(10)--在Web界面上实现数据的导入和导出

基于MVC4+EasyUI的Web开发框架经验总结(11)--使用Bundles处理简化页面代码

基于MVC4+EasyUI的Web开发框架经验总结(12)--利用Jquery处理数据交互的几种方式

基于MVC4+EasyUI的Web开发框架经验总结(13)--DataGrid控件实现自动适应宽带高度

基于MVC4+EasyUI的Web开发框架经验总结(14)--自动生成图标样式文件和图标的选择操作

在博客园很多文章里面,曾经有一些介绍Office文档预览查看操作的,有些通过转为PDF进行查看,有些通过把它转换为Flash进行查看,但是过程都是曲线救国,真正能够简洁方便的实现Office文档的预览的还是比较少,这里的Office文档包括了Word、Excel、PPT文档。本文介绍两种方式,一种方式是通过在线预览的方式,利用微软的平台进行Office文档的在线查看;一种是把Office文档生成HTML文件后进行查看。然后对比他们的优缺点,并进行总结。

1、利用微软的平台进行Office文档的在线查看

一直以来,都希望找一个合适的Office文档查看的控件或者解决方案,这样客户在使用系统的时候,可以直接查看预览一些文档,而不需要安装Office,或者下载到本地在查看。

一个偶然的机会,在网上搜到微软自己提供了一个在线服务,可以实现把Office文档转换为在线的内容进行查看,大家可以通过下面的链接地址大致看看:
http://blogs.office.com/2013/04/10/office-web-viewer-view-office-documents-in-a-browser/

使用浏览器直接查看Office文档的链接地址格式如下所示:

http://view.officeapps.live.com/op/view.aspx?src=http%3a%2f%2fvideo.ch9.ms%2fbuild%2f2011%2fslides%2fTOOL-532T_Sutter.pptx

这个链接分为了两部分,一部分是http://view.officeapps.live.com/op/view.aspx?src=,后面那个是具体的文档地址,用URLEncode进行处理的链接地址。

非常酷的效果,使用这个服务唯一的要求就是你的网站是部署在互联网上的,这样服务才可以调用你的文档地址然后进行展示。

这个是使用微软的预览Office服务,当然你可以部署自己的Office预览服务,也就是需要安装Office Web Apps服务(系统要求为Windows Server 2012)

一般情况下,Office Web Apps要与其他应用配合使用,如下图所示(看起来还是很复杂的,这些东西好像也要独立安装在不同的机器):

clip_image035

好在微软给我们提供了在线的Office文档预览服务,其实好像Google Doc Viewer也是可以的,但是已经不可使用了。这样,我们就可以利用公开的接口地址实现Office文档的在线预览了。

以常用的Excel文档为例,它可以提供了完美的在线预览效果。

我们还注意到上面有一排菜单,可以展开进行相关功能的操作,尤其是文档的下载和打印很吸引。

这样我们就可以在我们的文档预览操作页面上进行集成了,下面是我在我的《基于MVC4+EasyUI的Web开发框架》集成的一个例子,对Office文档进行查看

感觉效果还不错吧。

Word文档也是可以顺利进行预览,但是就没有打印和下载的功能了,不过预览的效果还是很不错的。

2、把Office文档生成HTML文件后进行查看

上面说了,使用在线的Office预览服务来查看Office文档,如果我们的管理系统是在局域网内跑的应用,那么我们就是用不了了,那么我们如果需要使用这种在线预览Office文档的服务,应该如何操作呢?

虽然自行搭建Office Web Apps服务应用可以解决这个问题,但是一般来说,搭建这样的平台环境,太过繁琐和昂贵了,有没有更好的方式实现Office文档的查看呢?

有的,下面我来介绍一下,如何使用Aspose组件把Office文档生成HTML,然后进行查看的做法。

对应不同的Office文档,Aspose提供了不同的组件,如Aspose.Word、Aspose.Cells、Aspose.Slides等不同的组件用来处理Word/Excel/PPT等几种文档。

我们知道,Aspose组件在处理Office文档方面非常的强大,但是也是收费软件,所以需要可以购买支持,但是我们这里纯粹讨论它的功能效果。

对不同的文件类型,我们调用不同的组件进行HTML的转换就可以了,核心部分代码如下所示。

                            if (ext == "doc" || ext == "docx")
{
Aspose.Words.Document doc
= newAspose.Words.Document(templateFile);
doc.Save(generateFilePath, Aspose.Words.SaveFormat.Html);
}
else if (ext == "xls" || ext == "xlsx")
{
Workbook workbook
= newWorkbook(templateFile);
workbook.Save(generateFilePath, SaveFormat.Html);
}
else if (ext == "ppt" || ext == "pptx")
{
templateFile
= templateFile.Replace("/", "\\");
PresentationEx pres
= newPresentationEx(templateFile);
pres.Save(generateFilePath, Aspose.Slides.Export.SaveFormat.Html);
}

这样,我们第一次使用的时候,判断目录里面是否有对应的HTML文件了,如果没有,就使用上面的代码生成就可以了,查看的时候,就返回对应的路径给客户端进行查看文件就可以了。

下面是几个文档的效果截图,供参考。

3、集成在线和本地生成文件两者的预览方式

我们在做项目的时候,往往不知道具体的应用是部署在公网(互联网)的环境,还是在局域网的环境,因此要求我们很多功能需要有一定的弹性,如果能够使用公网网的,利用公网微软的Office预览功能,呈现出来的效果比使用aspose组件生成的效果更好,有时候使用aspose组件生成的文档,格式可能有一些不太一样(虽然总体还好)。

因此,如果能够根据需要,对两者能够配置一下,进行切换,应该是比较理想的方案。

下面是我根据需要对它们两者的预览方案进行了集成,通过一个变量进行切换,当然,变量可以写到配置文件里面,这样以后也可以不同修改代码就可以自由切换了。

                bool officeInternetView = false;//是否使用互联网在线预览
                string hostName = HttpUtility.UrlPathEncode("http://www.iqidi.com/");//可以配置一下,如果有必要

                if (ext == "xls" || ext == "xlsx" || ext == "doc" || ext == "docx" || ext == "ppt" || ext == "pptx")
{
if(officeInternetView)
{
//返回一个微软在线浏览Office的地址,需要加上互联网域名或者公网IP地址 viewUrl = string.Format("http://view.officeapps.live.com/op/view.aspx?src={0}{1}", hostName, filePath);
}
else{#region 动态第一次生成文件 //检查本地Office文件是否存在,如不存在,先生成文件,然后返回路径供查看 string webPath = string.Format("/GenerateFiles/Office/{0}.htm", info.ID);string generateFilePath =Server.MapPath(webPath);if (!FileUtil.FileIsExist(generateFilePath))
{
string templateFile = BLLFactory<FileUpload>.Instance.GetFilePath(info);
templateFile
= Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, templateFile.Replace("\\", "/"));if (ext == "doc" || ext == "docx")
{
Aspose.Words.Document doc
= newAspose.Words.Document(templateFile);
doc.Save(generateFilePath, Aspose.Words.SaveFormat.Html);
}
else if (ext == "xls" || ext == "xlsx")
{
Workbook workbook
= newWorkbook(templateFile);
workbook.Save(generateFilePath, SaveFormat.Html);
}
else if (ext == "ppt" || ext == "pptx")
{
templateFile
= templateFile.Replace("/", "\\");
PresentationEx pres
= newPresentationEx(templateFile);
pres.Save(generateFilePath, Aspose.Slides.Export.SaveFormat.Html);
}
}
#endregionviewUrl=webPath;
}
}
else{
viewUrl
=filePath;
}

最后,我在附件管理模块里面实现所有文档的显示,对Office文档和图片文档都可以进行预览,以及其他文档进行下载的操作。

后面进一步介绍一下Web框架上的附件管理模块的操作功能。

基于MVC4+EasyUI的Web开发框架的系列文章:

基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍


基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用

基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用

基于MVC4+EasyUI的Web开发框架形成之旅--框架总体界面介绍

基于MVC4+EasyUI的Web开发框架形成之旅--基类控制器CRUD的操作

基于MVC4+EasyUI的Web开发框架形成之旅--权限控制

基于MVC4+EasyUI的Web开发框架经验总结(1)-利用jQuery Tags Input 插件显示选择记录

基于MVC4+EasyUI的Web开发框架经验总结(2)- 使用EasyUI的树控件构建Web界面

基于MVC4+EasyUI的Web开发框架经验总结(3)- 使用Json实体类构建菜单数据

基于MVC4+EasyUI的Web开发框架经验总结(4)--使用图表控件Highcharts

基于MVC4+EasyUI的Web开发框架经验总结(5)--使用HTML编辑控件CKEditor和CKFinder

基于MVC4+EasyUI的Web开发框架经验总结(6)--在页面中应用下拉列表的处理

基于MVC4+EasyUI的Web开发框架经验总结(7)--实现省份、城市、行政区三者联动

基于MVC4+EasyUI的Web开发框架经验总结(8)--实现Office文档的预览

基于MVC4+EasyUI的Web开发框架经验总结(9)--在Datagrid里面实现外键字段的转义操作

基于MVC4+EasyUI的Web开发框架经验总结(10)--在Web界面上实现数据的导入和导出

基于MVC4+EasyUI的Web开发框架经验总结(11)--使用Bundles处理简化页面代码

基于MVC4+EasyUI的Web开发框架经验总结(12)--利用Jquery处理数据交互的几种方式

基于MVC4+EasyUI的Web开发框架经验总结(13)--DataGrid控件实现自动适应宽带高度

基于MVC4+EasyUI的Web开发框架经验总结(14)--自动生成图标样式文件和图标的选择操作

我们在使用EasyUI的时候,很多情况下需要使用到表格控件datagrid,这个控件控件非常强大,使用起来很简洁,但是我在使用中,发现对于一个表里面的外键字段进行转义,并显示引用表的一些名称的操作,却显得比较难以实现,找了很多资料,基本上没有找到对应的解决方案。本文主要介绍我对这种外键字段转义的操作的实现方式,以便供大家参考了解。

1、DataGrid的初始化操作

在了解对内容的解析前,我们先来了解EasyUI里面Datagrid的初始化操作过程,然后逐步进行分析,寻求解决方式。

一般情况下,Datagrid内容的初始化代码如下所示,注意下面红色部分的内容Customer_ID,就是我们需要转换为客户名称的处理。因为我们这里返回的表数据包含了一个外键ID:Customer_ID,我需要把它转换一下客户的名称。

        //实现对DataGird控件的绑定操作
function InitGrid(queryData) {
$(
'#grid').datagrid({ //定位到Table标签,Table标签的ID是grid url: '/Contact/FindWithPager', //指向后台的Action来获取当前用户的信息的Json格式的数据 title: '客户联系人',
iconCls:
'icon-view',
height:
650,
width: function () {
return document.body.clientWidth * 0.9 },//自动宽度 nowrap: true,
autoRowHeight:
false,
striped:
true,
collapsible:
true,
pagination:
true,
pageSize:
50,
pageList: [
50, 100, 200],
rownumbers:
true,//sortName: 'Seq',//根据某个字段给easyUI排序 sortOrder: 'asc',
remoteSort:
false,
idField:
'ID',
queryParams: queryData,
//异步查询的参数 columns: [[
{ field:
'ck', checkbox: true }, //选择 { title:'客户ID', field: 'Customer_ID', width: 180, sortable: true},{ title:'编号', field: 'HandNo', width: 80, sortable: true},
{ title:
'姓名', field: 'Name', width: 80, sortable: true},
{ title:
'身份证号码', field: 'IDCarNo', width: 120, sortable: true},
{
title:
'出生日期', field: 'Birthday', width: 120, sortable: true,
formatter: function (value, rec, index) {
if (value ==undefined) {return "";
}
if ((value + '').indexOf('1900') == 0) {return "";
}
returnvalue;
}
}
]],
toolbar: [{
id:
'btnAdd',
text:
'添加',
iconCls:
'icon-add',
handler: function () {
ShowAddDialog();
//实现添加记录的页面 }
},
'-', {
id:
'btnEdit',
text:
'修改',
iconCls:
'icon-edit',
handler: function () {
ShowEditOrViewDialog();
//实现修改记录的方法 }
},
'-', {
id:
'btnDelete',
text:
'删除',
iconCls:
'icon-remove',
handler: function () {
Delete();
//实现直接删除数据的方法 }
},
'-', {
id:
'btnView',
text:
'查看',
iconCls:
'icon-table',
handler: function () {
ShowEditOrViewDialog(
"view");//实现查看记录详细信息的方法 }
},
'-', {
id:
'btnReload',
text:
'刷新',
iconCls:
'icon-reload',
handler: function () {
//实现刷新栏目中的数据 $("#grid").datagrid("reload");
}
}],
onDblClickRow: function (rowIndex, rowData) {
$(
'#grid').datagrid('uncheckAll');
$(
'#grid').datagrid('checkRow', rowIndex);
ShowEditOrViewDialog();
}
})
};

2、尝试失败的操作

1)使用格式化函数Formatter

针对以上的处理,有些人可能很快就想到使用格式化Formatter来实现了,一般情况下处理转义和自定义显示操作,非此莫属。

有可能想使用的代码如下所示。

{
title:
'客户名称', field: 'Customer_ID', width: 180, sortable: true,
formatter:
function(value, row) {
$.ajaxSettings.async
= false;
$.getJSON(
"/Customer/GetCustomerName?id=" + value, function(result) {return "<span>" + result + "</span>";
});
}
},

但是,如果这样做,你会发现格式化函数没有办法调用ajax的操作,获取对应的数据,也就是无法进行解析客户的ID为名称。因此这种方法,失败!

2)使用onLoadSuccess函数

这个onLoadSuccess函数,本意就是在Datagrid顺利加载后执行的函数,一般情况下,我想在加载后,在更新表格里面的数据,如下面的代码所示。

                onLoadSuccess: function(data) {var rows = $("#grid").datagrid("getRows");if (rows.length >= 1) {for (var i = 0; i < rows.length; i++) {
$.getJSON(
"/Customer/GetCustomerName?id=" + rows[i].Customer_ID, function(result) {
$(
'#grid').datagrid('updateRow', { index: 0, row: { field: result } });
});
}
}
}

下面的方法也是通过Ajax的方式获取数据,然后进行更新,不过很不幸,也不通过,无法正常解析。

3、成功解析的方式

好了,既然无法通过上面脚本的方式来进行解析,我们通过曲线救国的方式,应该也是可以的。

由于Datagrid显示的数据是下面的方式就可以的

            var result = new { total = list.Count, rows = list };

那么我们在返回数据给datagrid的控制器函数里面,对返回的内容,增加一个“
客户名称
”的信息,应该就可以了。

1)转换内容为DataTable并增加字段

但是我的框架里面,返回的内容都设置为了List<T>的这种方式,也就是T代表的是实体类,我们很难改变实体类里面的属性并赋值,那么我们也可以把它转换为DataTable了。

            //增加一个客户名称字段,然后进行解析,构建一个DataTable返回
            DataTable dtReturn = DataTableHelper.ListToDataTable<ContactInfo>(list);
dtReturn.Columns.Add(
"CustomerName");foreach (DataRow row indtReturn.Rows)
{
row[
"CustomerName"] = BLLFactory<Customer>.Instance.GetCustomerName(row["Customer_ID"].ToString());
}
var result = new { total = dtReturn.Rows.Count, rows = dtReturn };

通过函数DataTableHelper.ListToDataTable<ContactInfo>(list);可以把列表的内容构建成一个DataTable的内容,并增加一个CustomerName的字段,然后遍历每一行,填入解析Customer_ID后的名称,并返回记录就可以了。

在视图里面,我们通过增加一个字段进行绑定就可以了,如下所示。

{
title:
'客户ID', field: 'Customer_ID', width: 180, sortable: true, hidden:true },
{ title:
'客户名称', field: 'CustomerName', width: 180, sortable: true },

2)在实体类基类增加一些额外的字段属性

刚才我们看到,把实体类列表转换为DataTable,然后并遍历赋值,挺麻烦的一件事,也可能影响一些性能,如果我们实体类里面有一些备用的属性作为内容解析,在界面上直接使用这些备用属性就可以了,这样会更加方便。

因此我在所有实体类的基类里面增加三个属性,Data1、Data2、Data3,有点类似Visio模具形状的属性设置了,呵呵。

    /// <summary>
    ///框架实体类的基类/// </summary>
[DataContract]public classBaseEntity
{
#region 在实体类存储一些特殊的数据 /// <summary> ///用来给实体类传递一些额外的数据,如外键的转义等,该字段不保存到数据表中/// </summary> [DataMember]public string Data1 { get; set; }/// <summary> ///用来给实体类传递一些额外的数据,如外键的转义等,该字段不保存到数据表中/// </summary> [DataMember]public string Data2 { get; set; }/// <summary> ///用来给实体类传递一些额外的数据,如外键的转义等,该字段不保存到数据表中/// </summary> [DataMember]public string Data3 { get; set; }#endregion}

有了这些备用的属性,我们就可以解决外键转义的内容存储问题了。

在查询里面,我们只需要遍历一次,把转换好的内容赋值给对应的实体类属性就好了。

            foreach (ContactInfo info inlist)
{
//增加一个特殊字段的转义 info.Data1 = BLLFactory<Customer>.Instance.GetCustomerName(info.Customer_ID);
}

最后在视图里面,我们的代码如下所示。

{
title:
'客户ID', field: 'Customer_ID', width: 180, sortable: true, hidden:true},
{ title:
'客户名称', field: 'Data1', width: 180, sortable: true },

主界面里面的列表展示如下所示。

数据导入界面里面的列表展示如下所示。

基于MVC4+EasyUI的Web开发框架的系列文章:

基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍


基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用

基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用

基于MVC4+EasyUI的Web开发框架形成之旅--框架总体界面介绍

基于MVC4+EasyUI的Web开发框架形成之旅--基类控制器CRUD的操作

基于MVC4+EasyUI的Web开发框架形成之旅--权限控制

基于MVC4+EasyUI的Web开发框架经验总结(1)-利用jQuery Tags Input 插件显示选择记录

基于MVC4+EasyUI的Web开发框架经验总结(2)- 使用EasyUI的树控件构建Web界面

基于MVC4+EasyUI的Web开发框架经验总结(3)- 使用Json实体类构建菜单数据

基于MVC4+EasyUI的Web开发框架经验总结(4)--使用图表控件Highcharts

基于MVC4+EasyUI的Web开发框架经验总结(5)--使用HTML编辑控件CKEditor和CKFinder

基于MVC4+EasyUI的Web开发框架经验总结(6)--在页面中应用下拉列表的处理

基于MVC4+EasyUI的Web开发框架经验总结(7)--实现省份、城市、行政区三者联动

基于MVC4+EasyUI的Web开发框架经验总结(8)--实现Office文档的预览

基于MVC4+EasyUI的Web开发框架经验总结(9)--在Datagrid里面实现外键字段的转义操作

基于MVC4+EasyUI的Web开发框架经验总结(10)--在Web界面上实现数据的导入和导出

基于MVC4+EasyUI的Web开发框架经验总结(11)--使用Bundles处理简化页面代码

基于MVC4+EasyUI的Web开发框架经验总结(12)--利用Jquery处理数据交互的几种方式

基于MVC4+EasyUI的Web开发框架经验总结(13)--DataGrid控件实现自动适应宽带高度

基于MVC4+EasyUI的Web开发框架经验总结(14)--自动生成图标样式文件和图标的选择操作