2023年2月

在上篇《
C#开发微信门户及应用(22)-微信小店的开发和使用
》里面介绍了一些微信小店的基础知识,以及对应的对象模型,本篇继续微信小店的主题,介绍其中API接口的封装和测试使用。微信小店的相关对象模型,基本上包括了常规的商品、商品分组、货架、库存、订单这些模型,还有商品分类,商品分类属性、商品分类SKU、快递邮寄模板、图片管理等功能。本文介绍的接口封装也就是基于这些内容进行的,并针对接口的实现进行测试和使用。

1、商品管理接口的定义

前面文章介绍了微信小店的对象模型,如下所示。

这个图形基本上覆盖了微信小店的相关对象,并介绍了它们之间的关系了。

我们从基础的商品信息管理入手,我们知道,商品接口包含了增加、修改、查询、删除等接口,如下所示。

商品信息是所有微店的基础,因此对它的管理操作,我们需要更加清晰和完善。

综上所述的功能,我们可以定义好微信商品的接口如下所示。

        #region 商品信息
        /// <summary>
        ///创建商品/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="merchantJson">商品对象</param>
        /// <returns></returns>
        AddMerchantResult AddMerchant(stringaccessToken, MerchantJson merchantJson);/// <summary>
        ///删除商品/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="productId">商品ID</param>
        /// <returns></returns>
        CommonResult DeleteMerchant(string accessToken, stringproductId);/// <summary>
        ///修改商品///product_id表示要更新的商品的ID,其他字段说明请参考增加商品接口。///从未上架的商品所有信息均可修改,否则商品的名称(name)、商品分类(category)、商品属性(property)这三个字段不可修改。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="merchantJson">修改商品的信息</param>
        /// <returns></returns>
        CommonResult UpdateMerchant(stringaccessToken, MerchantJson merchantJson);/// <summary>
        ///根据ID查询商品信息,如果成功返回MerchantJson信息,否则返回null/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="productId">商品的Id</param>
        /// <returns></returns>
        MerchantJson GetMerchant(string accessToken, stringproductId);/// <summary>
        ///获取指定状态的所有商品/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="status">商品状态(0-全部, 1-上架, 2-下架)</param>
        /// <returns></returns>
        List<MerchantJson> GetMerchantByStatus(string accessToken, intstatus);/// <summary>
        ///商品上下架/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="status">商品上下架标识(0-下架, 1-上架)</param>
        /// <returns></returns>
        CommonResult UpdateMerchantStatus(string accessToken, string productId, intstatus);#endregion

当然,微信的商品还包含了分类、分类属性、分类SKU的基础管理,因此商品管理还需要增加这个内容

它们的功能接口定义如下所示。通过下面的接口,我们就很容易实现商品分类(不是商品分组)、SKU信息、和分类属性等信息的获取操作了。

        #region 商品分类及属性
        /// <summary>
        ///获取指定分类的所有子分类/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="cateId">大分类ID(根节点分类id为1)</param>
        /// <returns></returns>
        List<SubCategory> GetSub(string accessToken, intcate_id);/// <summary>
        ///获取指定子分类的所有SKU/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="cateId">商品子分类ID</param>
        /// <returns></returns>
        List<SubCategorySku> GetSku(string accessToken, intcate_id);/// <summary>
        ///获取指定分类的所有属性/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="cateId">分类ID</param>
        /// <returns></returns>
        List<SubCategoryProperty> GetProperty(string accessToken, intcate_id);#endregion

2、商品管理接口的实现

上面的接口定义了对应商品的接口。

对于接口的实现,我们一般都是根据官方网站的接口说明,提交到那个URL,并且是POST那些数据,然后整理成一个常规的处理方式,获得结果并转换为对应的对象即可,如添加商品操作的实现代码如下所示。

        /// <summary>
        ///创建商品/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="merchantJson">商品对象</param>
        /// <returns></returns>
        public AddMerchantResult AddMerchant(stringaccessToken, MerchantJson merchantJson)
{
var url = string.Format("https://api.weixin.qq.com/merchant/create?access_token={0}", accessToken);string postData =merchantJson.ToJson();return JsonHelper<AddMerchantResult>.ConvertJson(url, postData);
}

而返回结果,这是定义一个对象来获得添加商品的ID等内容,如下所示。

    /// <summary>
    ///创建商品信息的返回结果/// </summary>
    public classAddMerchantResult : ErrorJsonResult
{
/// <summary> ///商品ID/// </summary> public string product_id { get; set; }
}

而基类这是常规的响应内容

    /// <summary>
    ///微信返回Json结果的错误数据/// </summary>
    public classErrorJsonResult 
{
/// <summary> ///返回代码/// </summary> public ReturnCode errcode { get; set; }/// <summary> ///错误消息/// </summary> public string errmsg { get; set; }
}

通过这些对象的定义,添加商品后,我们就知道操作是否成功,如果添加成功,返回了一个刚刚创建的ID给我们使用,我们可以进行查询具体的商品信息或者进行修改、删除等操作的。

而对商品信息的修改或者删除的操作,都是返回一个是否成功的记录就可以了,因此我们定义了一个统一的回应对象CommonResult。商品修改、删除的接口实现代码如下所示。

由于代码我都进行高度的完善和整理,对于各种处理的代码都相对比较容易理解的了。

        /// <summary>
        ///删除商品/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="productId">商品ID</param>
        /// <returns></returns>
        public CommonResult DeleteMerchant(string accessToken, stringproductId)
{
var url = string.Format("https://api.weixin.qq.com/merchant/del?access_token={0}", accessToken);var data = new{
product_id
=productId
};
string postData =data.ToJson();returnHelper.GetExecuteResult(url, postData);
}
/// <summary> ///修改商品///product_id表示要更新的商品的ID,其他字段说明请参考增加商品接口。///从未上架的商品所有信息均可修改,否则商品的名称(name)、商品分类(category)、商品属性(property)这三个字段不可修改。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="merchantJson">修改商品的信息</param> /// <returns></returns> public CommonResult UpdateMerchant(stringaccessToken, MerchantJson merchantJson)
{
var url = string.Format("https://api.weixin.qq.com/merchant/update?access_token={0}", accessToken);string postData =merchantJson.ToJson();returnHelper.GetExecuteResult(url, postData);
}

为了获取商品的详细信息,我们需要定义一个商品的实体对象,以便我们把获取到的信息转换为实体类信息,方便使用和处理。

商品的信息,包含了不少细小定义的类,他们构成了商品的各个部分的内容,主体的实体类信息如下所示。

定义好相对比较复杂的商品信息实体后,我们就可以通过对象进行处理了。

获取商品详细信息的实现代码如下所示。

        /// <summary>
        ///根据ID查询商品信息,如果成功返回MerchantJson信息,否则返回null/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="productId">商品的Id</param>
        /// <returns></returns>
        public MerchantJson GetMerchant(string accessToken, stringproductId)
{
var url = string.Format("https://api.weixin.qq.com/merchant/get?access_token={0}", accessToken);var data = new{
product_id
=productId
};
string postData =data.ToJson();

MerchantJson merchant
= null;
GetMerchantResult result
= JsonHelper<GetMerchantResult>.ConvertJson(url, postData);if (result != null)
{
merchant
=result.product_info;
}
returnmerchant;
}

虽然商品的实体信息很复杂,但是一旦我们定义好,我们就很容易对结果进行转换并处理了,上面的代码并不是很难理解,主要就是提交数据后,对数据进行转换而已。

当然,我们还可以获取不同状态的商品列表内容,如下代码所示。

        /// <summary>
        ///获取指定状态的所有商品/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="status">商品状态(0-全部, 1-上架, 2-下架)</param>
        /// <returns></returns>
        public List<MerchantJson> GetMerchantByStatus(string accessToken, intstatus)
{
var url = string.Format("https://api.weixin.qq.com/merchant/getbystatus?access_token={0}", accessToken);var data = new{
status
=status
};
string postData =data.ToJson();

List
<MerchantJson> list = new List<MerchantJson>();
GetMerchantByStatus result
= JsonHelper<GetMerchantByStatus>.ConvertJson(url, postData);if (result != null)
{
list
=result.products_info;
}
returnlist;
}

我们添加商品的时候,商品的分类信息、分类属性、分类SKU信息也都是很重要的内容,我们需要指定对应商品分类才能添加到微信小店里面。

获取商品分类的操作实现代码如下所示。

        /// <summary>
        ///获取指定分类的所有子分类/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="cateId">大分类ID(根节点分类id为1)</param>
        /// <returns></returns>
        public List<SubCategory> GetSub(string accessToken, intcate_id)
{
var url = string.Format("https://api.weixin.qq.com/merchant/category/getsub?access_token={0}", accessToken);var data = new{
cate_id
=cate_id
};
string postData =data.ToJson();

List
<SubCategory> list = new List<SubCategory>();
GetSubResult result
= JsonHelper<GetSubResult>.ConvertJson(url, postData);if(result != null)
{
list
=result.cate_list;
}
returnlist;
}

3、商品管理接口的测试

为了验证我们开发的接口,我们需要增加一个测试项目,方便对我们编写的API进行测试,测试完全成功后,我们才能正式在项目中使用。

我为了方便,创建了一个Winform项目,分别对各个接口进行测试。

本篇主要介绍商品管理方面的接口,因此下面主要介绍其中商品管理部分的接口测试代码,以及对应的结果。

其中商品常规管理的接口测试代码如下所示。

        private void btnMerchant_Click(objectsender, EventArgs e)
{
//商品管理 IMerchantApi api = newMerchantApi();//获取所有商品信息 Console.WriteLine("获取所有商品信息");
List
<MerchantJson> list = api.GetMerchantByStatus(token, 0);foreach(MerchantJson json inlist)
{
Console.WriteLine(json.ToJson());
Console.WriteLine();
}
//更新商品状态 Console.WriteLine("更新商品状态");foreach (MerchantJson json inlist)
{
CommonResult result
= api.UpdateMerchantStatus(token, json.product_id, 1);
Console.WriteLine(
"商品ID:{0},商品名称:{1}, 操作:{2}",
json.product_id, json.product_base.name, result.Success
? "成功" : "失败");
}

Thread.Sleep(
1000);//根据商品ID获取商品信息 Console.WriteLine("根据商品ID获取商品信息");foreach (MerchantJson json inlist)
{
MerchantJson getJson
=api.GetMerchant(token, json.product_id);if(json != null)
{
Console.WriteLine(
"商品ID:{0},商品名称:{1}", getJson.product_id, getJson.product_base.name);
}
}
}

测试后结果如下所示(就是返回我微店铺里面的商品信息),一切正常。

返回的商品Json数据如下所示:


{"product_id": "pSiLnt6FYDuFtrRRPMlkdKbye-rE","product_base": {"category_id": ["537103312"],"property": [
{
"id": "类型","vid": "软件产品设计"}
],
"name": "代码生成工具Database2Sharp","sku_info": [],"main_img": "http://mmbiz.qpic.cn/mmbiz/mLqH9gr11Gyb2sgiaelcsxYtQENGePp0Rb3AZKbjkicnKTUNBrEdo7Dyic97ar46SoAfKRB5x2R94bDUdNpgqiaZzA/0","img": ["http://mmbiz.qpic.cn/mmbiz/mLqH9gr11Gyb2sgiaelcsxYtQENGePp0RiaheJmVXm7tbvTYUQV7OF3DgfGiaQVMh3WbeEcGDOQQiajQXGKK9tfoeA/0"],"detail": [],"buy_limit": 0,"detail_html": ""},"sku_list": [
{
"sku_id": "","ori_price": 100000,"price": 50000,"icon_url": "","quantity": 1100,"product_code": ""}
],
"attrext": {"location": {"country": "中国","province": "广东","city": "广州","address": ""},"isPostFree": 1,"isHasReceipt": 0,"isUnderGuaranty": 0,"isSupportReplace": 0},"delivery_info": {"delivery_type": 0,"template_id": 175807970,"express": [
{
"id": 10000027,"price": 0},
{
"id": 10000028,"price": 0},
{
"id": 10000029,"price": 0}
]
},
"status": 1}

View Code

测试的部分结果输出如下所示。

另外,“商品维护管理”的功能测试主要就是测试商品的增加、修改、删除操作,具体代码如下所示。

        private void btnMerchantEdit_Click(objectsender, EventArgs e)
{
IMerchantApi api
= newMerchantApi();string img1 = "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ulEKogfsiaua49pvLfUS8Ym0GSYjViaLic0FD3vN0V8PILcibEGb2fPfEOmw/0";string img2 = "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ul1UcLcwxrFdwTKYhH9Q5YZoCfX4Ncx655ZK6ibnlibCCErbKQtReySaVA/0";string img3 = "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl28bJj62XgfHPibY3ORKicN1oJ4CcoIr4BMbfA8LqyyjzOZzqrOGz3f5KWq1QGP3fo6TOTSYD3TBQjuw/0";//商品增删改处理 MerchantJson merchant = newMerchantJson();
merchant.product_base
= newMerchant_base();
merchant.product_base.name
= "测试产品";
merchant.product_base.category_id.Add(
"537074298");
merchant.product_base.img
= new List<string>() { img1, img2, img3 };
merchant.product_base.main_img
=img1;
merchant.product_base.detail.AddRange(
new List<MerchantDetail>() {newMerchantDetail()
{
text
= "test first"},newMerchantDetail()
{
img
=img2
},
newMerchantDetail()
{
text
= "test again"}
});
merchant.product_base.property.AddRange(
new List<MerchantProperty>(){newMerchantProperty
{
id
= "1075741879",
vid
="1079749967"},newMerchantProperty{
id
= "1075754127",
vid
= "1079795198"},newMerchantProperty(){
id
= "1075777334",
vid
= "1079837440"}
});
merchant.product_base.sku_info.AddRange(
new List<MerchantSku>(){newMerchantSku{
id
= "1075741873",
vid
= new List<string>() {"1079742386","1079742363"}
}
});
merchant.product_base.buy_limit
= 10;//merchant.product_base.detail_html = "<div class=\"item_pic_wrp\" style=\"margin-bottom:8px;font-size:0;\"><img class=\"item_pic\" style=\"width:100%;\" alt=\"\" src=\"http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ulEKogfsiaua49pvLfUS8Ym0GSYjViaLic0FD3vN0V8PILcibEGb2fPfEOmw/0\" ></div><p style=\"margin-bottom:11px;margin-top:11px;\">test</p><div class=\"item_pic_wrp\" style=\"margin-bottom:8px;font-size:0;\"><img class=\"item_pic\" style=\"width:100%;\" alt=\"\" src=\"http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ul1UcLcwxrFdwTKYhH9Q5YZoCfX4Ncx655ZK6ibnlibCCErbKQtReySaVA/0\" ></div><p style=\"margin-bottom:11px;margin-top:11px;\">test again</p>"; merchant.sku_list.AddRange(new List<MerchantSku_list>()
{
newMerchantSku_list(){
sku_id
="1075741873:1079742386",
price
=30,
icon_url
="http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjD3ulEKogfsiaua49pvLfUS8Ym0GSYjViaLic0FD3vN0V8PILcibEGb2fPfEOmw/0",
quantity
=800,
product_code
="testing",
ori_price
=9000000},newMerchantSku_list(){
sku_id
="1075741873:1079742363",
price
=30,
icon_url
="http://mmbiz.qpic.cn/mmbiz/4whpV1VZl28bJj62XgfHPibY3ORKicN1oJ4CcoIr4BMbfA8LqyyjzOZzqrOGz3f5KWq1QGP3fo6TOTSYD3TBQjuw/0",
quantity
=800,
product_code
="testingtesting",
ori_price
=9000000}
});
merchant.attrext
= newMerchantAttrext()
{
location
= newMerchantLocation()
{
country
= "中国",
province
= "广东省",
city
= "广州市",
address
= "T.I.T创意园"},
isPostFree
= 0,
isHasReceipt
= 1,
isUnderGuaranty
= 0,
isSupportReplace
= 0};
merchant.delivery_info
= newMerchantDelivery()
{
delivery_type
= 0,
template_id
= 0,
express
= new List<MerchantExpress>(){newMerchantExpress() {
id
=10000027,
price
=100},newMerchantExpress(){
id
=10000028,
price
=100},newMerchantExpress(){
id
=10000029,
price
=100}}
};

Console.WriteLine(merchant.ToJson());

AddMerchantResult result
=api.AddMerchant(token, merchant);
Console.WriteLine(
"添加商品:{0}", result.product_id);if (!string.IsNullOrEmpty(result.product_id))
{
//更新商品 merchant.product_id =result.product_id;
merchant.product_base.name
= "测试产品22";
CommonResult updateResult
=api.UpdateMerchant(token, merchant);
Console.WriteLine(
"更新商品:{0}", updateResult.Success ? "成功" : "失败");


CommonResult deleteResult
=api.DeleteMerchant(token, merchant.product_id);
Console.WriteLine(
"删除商品:{0}", deleteResult.Success ? "成功" : "失败");
}
}

测试的输出结果如下所示(一切成功)。

以上就是我对商品管理接口的API定义和实现,以及对接口进行测试的阐述,基本上把所有相关的内容都贴出来了,希望大家能够对微店开发部分,有更深入的了解和认识。

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

在前面微信小店系列篇《
C#开发微信门户及应用(22)-微信小店的开发和使用
》里面介绍了一些微信小店的基础知识,以及《
C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试
》里面详细介绍了微信小店商品的接口定义、实现和测试的内容,本文主要介绍微信小店货架信息管理。这个模块是在微信小店对象里面,最为复杂,也是最难理解的一个模块,对于它的对象建模,需要反复测试才能完善起来,因此这个货架管理模块,可以说是最具技术含量的一个模块了。

1、微信小店货架介绍

在微信公众号的后台里面,可以对货架信息进行维护,界面如下所示。货架的概念,就是把商品分门别类的很好展示给客户,货架就是类似一个布局良好的展柜,我们可以定义不同的货架,然后公布不同的URL进行体验。

另外,我们一般创建货架,都是基于货架的模板库来构建的,货架的模板给我们快速构建一个货架,提供了可视化的参考界面,货架模板界面如下所示。

2、货架管理的开发模型

对于利用API开发微信店铺,微信小店的货架管理操作接口,和常规的模块差不多,具有下面几个功能操作。

虽然看起来和前面的对象模型差不多,但是货架的信息非常复杂,因此你如果需要根据Json数据把它还原为实体对象的时候,需要反复进行斟酌,否则很容易建模错误。

对应着微信小店管理界面的货架模板,货架的对象信息包括了5个不同的控件模型,它们有的可以进行组合使用。

几个货架的模型展示如下所示。



通过上面5个控件模型,我们可以看到它们分别代表不一样的布局效果,而且它们可以在货架上进行组合使用的。

3、货架信息的对象建模

根据微信小店的接口说明,我们最终定义的货架实体对象信息,内容就非常丰富和弹性化。

我们通过参考微信小店的API说明,就可以看到货架的信息JSON数据很复杂,具体定义如下所示。


{"shelf_data": {"module_infos": [
{
"group_info": {"filter": {"count": 2},"group_id": 50},"eid": 1},
{
"group_infos": {"groups": [
{
"group_id": 49},
{
"group_id": 50},
{
"group_id": 51}
]
},
"eid": 2},
{
"group_info": {"group_id": 52,"img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5Jm64z4I0TTicv0TjN7Vl9bykUUibYKIOjicAwIt6Oy0Y6a1Rjp5Tos8tg/0"},"eid": 3},
{
"group_infos": {"groups": [
{
"group_id": 49,"img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0"},
{
"group_id": 50,"img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5G1kdy3ViblHrR54gbCmbiaMnl5HpLGm5JFeENyO9FEZAy6mPypEpLibLA/0"},
{
"group_id": 52,"img": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0"}
]
},
"eid": 4},
{
"group_infos": {"groups": [
{
"group_id": 43},
{
"group_id": 44},
{
"group_id": 45},
{
"group_id": 46}
],
"img_background": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl29nqqObBwFwnIX3licVPnFV5uUQx7TLx4tB9qZfbe3JmqR4NkkEmpb5LUWoXF1ek9nga0IkeSSFZ8g/0"},"eid": 5}
]
},
"shelf_banner": "http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2ibrWQn8zWFUh1YznsMV0XEiavFfLzDWYyvQOBBszXlMaiabGWzz5B2KhNn2IDemHa3iarmCyribYlZYyw/0","shelf_name": "测试货架"}

View Code

我们根据JSON数据的定义,定义了几个货架控件的对象,他们的关系如下所示。

我们可以根据JSON数据进行实体对象的建模,然后有了这些对象,我们就可以进一步定义好货架的相关操作接口了,接口定义如下所示。

        #region 货架管理

        /// <summary>
        ///增加货架/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="shelfBanner">货架招牌图片Url</param>
        /// <param name="shelfName">货架名称</param>
        /// <param name="controls">货架控件1,2,3,4,5类型的集合</param>
        /// <returns></returns>
        AddShelfResult AddShelf(string accessToken, string shelfBanner, string shelfName, List<ShelfControlBase>controls);/// <summary>
        ///删除货架/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="shelfId">货架Id</param>
        /// <returns></returns>
        CommonResult DeleteShelf(string accessToken, intshelfId);/// <summary>
        ///修改货架/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="shelfId">货架Id</param>
        /// <param name="shelfBanner">货架招牌图片Url</param>
        /// <param name="shelfName">货架名称</param>
        /// <param name="controls">货架控件1,2,3,4,5类型的集合</param>
        /// <returns></returns>
        CommonResult UpdateShelf(string accessToken, int shelfId, string shelfBanner, string shelfName, List<ShelfControlBase>controls);/// <summary>
        ///获取所有货架/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <returns></returns>
        List<ShelfJson> GetAllShelf(stringaccessToken);/// <summary>
        ///根据货架ID获取货架信息/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="shelfId">货架Id</param>
        /// <returns></returns>
        ShelfJson GetShelfById(string accessToken, intshelfId);#endregion

有了这些接口的定义,我们就需要实现对应的接口,从而实现我们向微信API的封装处理了。

微信小店的货架管理实现内容如下所示(部分内容,增删改)。

        /// <summary>
        ///增加货架/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="shelfBanner">货架招牌图片Url</param>
        /// <param name="shelfName">货架名称</param>
        /// <param name="controls">货架控件1,2,3,4,5类型的集合</param>
        /// <returns></returns>
        public AddShelfResult AddShelf(string accessToken, string shelfBanner, string shelfName, List<ShelfControlBase>controls)
{
var url = string.Format("https://api.weixin.qq.com/merchant/shelf/add?access_token={0}", accessToken);var data = new{
shelf_data
= new{
module_infos
=controls
},
shelf_banner
=shelfBanner,
shelf_name
=shelfName
};
string postData =data.ToJson();return JsonHelper<AddShelfResult>.ConvertJson(url, postData);
}
/// <summary> ///删除货架/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="shelfId">货架Id</param> /// <returns></returns> public CommonResult DeleteShelf(string accessToken, intshelfId)
{
var url = string.Format("https://api.weixin.qq.com/merchant/shelf/del?access_token={0}", accessToken);var data = new{
shelf_id
=shelfId
};
string postData =data.ToJson();returnHelper.GetExecuteResult(url, postData);
}
/// <summary> ///修改货架/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="shelfId">货架Id</param> /// <param name="shelfBanner">货架招牌图片Url</param> /// <param name="shelfName">货架名称</param> /// <param name="controls">货架控件1,2,3,4,5类型的集合</param> /// <returns></returns> public CommonResult UpdateShelf(string accessToken, int shelfId, string shelfBanner, string shelfName, List<ShelfControlBase>controls)
{
var url = string.Format("https://api.weixin.qq.com/merchant/shelf/mod?access_token={0}", accessToken);var data = new{
shelf_id
=shelfId,
shelf_data
= new{
module_infos
=controls
},
shelf_banner
=shelfBanner,
shelf_name
=shelfName
};
string postData =data.ToJson();returnHelper.GetExecuteResult(url, postData);
}

4、微信小店货架管理的接口测试

由于货架管理的对象和接口定义比较复杂一些,一定需要进行反复的测试才能正式使用,如果不注意有可能你定义的实体类,获取不到某个字段信息。

我为了方便,创建了一个Winform项目,分别对各个接口进行测试。

对于货架管理内容的接口测试,测试代码如下所示。

        private void btnShelf_Click(objectsender, EventArgs e)
{
IMerchantApi api
= newMerchantApi();
List
<ShelfJson> list =api.GetAllShelf(token);
Console.WriteLine(list.ToJson());
foreach(ShelfJson json inlist)
{
Console.WriteLine(
"货架信息:");
ShelfJson getJson
=api.GetShelfById(token, json.shelf_id.Value);
Console.WriteLine(getJson.ToJson());
}
string shelf_banner = "http://mmbiz.qpic.cn/mmbiz/mLqH9gr11Gyb2sgiaelcsxYtQENGePp0RgeNlAQicfZQokjbJMUq4h8MHtjpekJNEWKuMN3gdRz5RxfkYb7NlIrw/0";string shelf_name = "测试货架";
ShelfControl1 c11
= new ShelfControl1(6, 202797386);
ShelfControl1 c12
= new ShelfControl1(4, 202797397);
List
<ShelfControlBase> controlList = new List<ShelfControlBase>(){c11, c12};
AddShelfResult result
=api.AddShelf(token, shelf_banner, shelf_name, controlList);if (result != null && result.shelf_id > 0)
{
Console.WriteLine(
"增加的货架信息:");
ShelfJson getJson
=api.GetShelfById(token, result.shelf_id);
Console.WriteLine(getJson.ToJson());

shelf_name
= "测试货架-修改";
controlList
= new List<ShelfControlBase>(){c11};
CommonResult updateReuslt
=api.UpdateShelf(token, result.shelf_id, shelf_banner, shelf_name, controlList);
Console.WriteLine(
"修改货架操作:{0}", updateReuslt.Success ? "成功" : "失败");

CommonResult deleteResult
=api.DeleteShelf(token, result.shelf_id);
Console.WriteLine(
"删除货架操作:{0}", deleteResult.Success ? "成功" : "失败");
}
}

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

在上篇随笔《
C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)
》介绍了有关企业号的消息发送,官方特别声明消息是不用加密发送的。但是在回调的服务器上,也就是我们网站的服务器上,微信传过来的消息是加密的,需要我们调用类库对消息和事件进行解密操作,由于官方的例子不全,因此摸索了不少时间,最终顺利解密收到的各种消息和事件。本文主要介绍 微信企业号的消息和事件的接收处理及解密操作。

1、企业号回调模式的设置

和公众号一样,微信企业号如果需要进行二次开发,也是需要在后台设置好对应的回调参数,如下界面所示。

设置好这些后,检查通过后,我们就可以在自己微信应用服务器上进行消息的收发操作了。

在回调的消息入口处,我们需要对POST数据和普通的GET数据进行分开处理,GET数据是微信自身的验证处理,POST数据是微信消息的交互操作。

    /// <summary>
    ///企业号回调信息接口。统一接收并处理信息的入口。/// </summary>
    public classcorpapi : IHttpHandler
{
/// <summary> ///处理企业号的信息/// </summary> /// <param name="context"></param> public voidProcessRequest(HttpContext context)
{

上面我们定义了一个一般应用处理程序来对消息进行处理。

然后我们分开不同的消息类型(POST、GET 方式),针对性的进行处理。

                    if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
{
using (Stream stream =HttpContext.Current.Request.InputStream)
{
Byte[] postBytes
= newByte[stream.Length];
stream.Read(postBytes,
0, (Int32)stream.Length);
postString
=Encoding.UTF8.GetString(postBytes);
}
if (!string.IsNullOrEmpty(postString))
{
Execute(postString, accountInfo);
}
}
else{
Auth(accountInfo);
}

2、微信回调消息的验证

下面是微信对于回调模式,验证URL的说明。

验证URL有效性

当你提交以上信息时,企业号将发送GET请求到填写的URL上,GET请求携带四个参数,
企业在获取时需要做urldecode处理
,否则会验证不成功。

参数 描述 是否必带
msg_signature 微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
timestamp 时间戳
nonce 随机数
echostr 加密的随机字符串,以msg_encrypt格式提供。需要解密并返回echostr明文,解密后有random、msg_len、msg、$CorpID四个字段,其中msg即为echostr明文 首次校验时必带

企业通过参数msg_signature对请求进行校验,如果确认此次GET请求来自企业号,那么
企业应用对echostr参数解密并原样返回echostr明文(不能加引号)
,则接入验证生效,回调模式才能开启。

后续回调企业时都会在请求URL中带上以上参数(echostr除外),校验方式与首次验证URL一致。

根据上面的说明,我们需要获取这些参数,然后调用微信提供的消息处理函数进行加解密处理。

在验证URL的Auth(accountInfo);操作里面,我们可以看到核心的内容如下所示,就是获取到这些传递过来的参数信息,然后交给基类处理消息的签名内容。

                        #region 具体处理逻辑
                        string echoString = HttpContext.Current.Request.QueryString["echoStr"];string signature = HttpContext.Current.Request.QueryString["msg_signature"];//企业号的 msg_signature
                        string timestamp = HttpContext.Current.Request.QueryString["timestamp"];string nonce = HttpContext.Current.Request.QueryString["nonce"];string decryptEchoString = "";if (new CorpBasicApi().CheckSignature(token, signature, timestamp, nonce, corpId, encodingAESKey, echoString, refdecryptEchoString))
{
if (!string.IsNullOrEmpty(decryptEchoString))
{
HttpContext.Current.Response.Write(decryptEchoString);
HttpContext.Current.Response.End();
}
}
#endregion

验证代码部门如下所示。

        /// <summary>
        ///验证企业号签名/// </summary>
        /// <param name="token">企业号配置的Token</param>
        /// <param name="signature">签名内容</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">nonce参数</param>
        /// <param name="corpId">企业号ID标识</param>
        /// <param name="encodingAESKey">加密键</param>
        /// <param name="echostr">内容字符串</param>
        /// <param name="retEchostr">返回的字符串</param>
        /// <returns></returns>
        public bool CheckSignature(string token, string signature, string timestamp, string nonce, string corpId, string encodingAESKey, string echostr, ref stringretEchostr)
{
WXBizMsgCrypt wxcpt
= newWXBizMsgCrypt(token, encodingAESKey, corpId);int result = wxcpt.VerifyURL(signature, timestamp, nonce, echostr, refretEchostr);if (result != 0)
{
LogTextHelper.Error(
"ERR: VerifyURL fail, ret:" +result);return false;
}
return true;
}

3、企业号的消息处理

上面介绍了,微信企业号对URL的验证过程,还有另外一个消息处理过程,就是微信服务器把消息发送给我们自己的应用服务器进行处理的过程,我们应用服务器需要在收到消息后,及时进行常规回复处理。

也就是下面的代码逻辑。

                    if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
{
using (Stream stream =HttpContext.Current.Request.InputStream)
{
Byte[] postBytes
= newByte[stream.Length];
stream.Read(postBytes,
0, (Int32)stream.Length);
postString
=Encoding.UTF8.GetString(postBytes);
}
if (!string.IsNullOrEmpty(postString))
{
Execute(postString, accountInfo);
}
}

同样,我们给微信服务器回应消息的时候,我们也需要获得相应的参数,然后再行构造信息回答。

            string echoString = HttpContext.Current.Request.QueryString["echoStr"];string signature = HttpContext.Current.Request.QueryString["msg_signature"];//企业号的 msg_signature
            string timestamp = HttpContext.Current.Request.QueryString["timestamp"];string nonce = HttpContext.Current.Request.QueryString["nonce"];

而另外一些参数信息,则是来源于我们企业号账号的配置参数。

            //获取配置参数并对加解密函数初始化
            string CorpToken =accountInfo.Token;string AESKey =accountInfo.EncodingAESKey;string CorpId = accountInfo.CorpID;

然后使用微信提供的消息加解密类,就可以顺利对消息进行加解密的处理了。具体操作代码如下所示。

            //根据参数信息,初始化微信对应的消息加密解密类
            WXBizMsgCrypt wxcpt = newWXBizMsgCrypt(CorpToken, AESKey, CorpId);//对收到的密文进行解析处理
            string sMsg = "";  //解析之后的明文
            int flag = wxcpt.DecryptMsg(signature, timestamp, nonce, postStr, refsMsg);if (flag == 0)
{
//LogTextHelper.Info("记录解密后的数据:");//LogTextHelper.Info(sMsg);//记录解密后的数据 CorpApiDispatch dispatch= newCorpApiDispatch();string responseContent =dispatch.Execute(sMsg);//加密后并发送//LogTextHelper.Info(responseContent); string encryptResponse = "";
timestamp
=DateTime.Now.DateTimeToInt().ToString();
wxcpt.EncryptMsg(responseContent, timestamp, nonce,
ref encryptResponse, refsignature);

HttpContext.Current.Response.ContentEncoding
=Encoding.UTF8;
HttpContext.Current.Response.Write(encryptResponse);
}
else{
LogTextHelper.Info(
"解密消息失败!");
}

最终,我们把解密完成的消息交给对应的封装类进行统一处理就可以了。

                CorpApiDispatch dispatch = newCorpApiDispatch();string responseContent = dispatch.Execute(sMsg);

这样我们在企业号API的封装,就可以只需要关注消息如何应答的逻辑就可以了,其他的不用关心。


如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

在我们很多程序的登陆过程中,如果对于一些复杂的内容,可能都有一些配置管理的对话框,本文介绍的混合式开发框架,是指什么呢,我下面给了一个定义。

《混合式开发框架》是指混合了传统《Winform开发框架》和《WCF开发框架》的特点,可以在两者之间自由切换,统一了系统界面层对业务服务的调用模式,所有组件模块均实现两种方式的调用,是一种弹性化非常好的框架应用,既可用于单机版软件或者基于局域网内的应用软件,也可以用于分布式技术的互联网环境应用。

对于这种混合式模式,上面说到了有两种方式,一种是常规的访问数据库方式,一种是访问WCF服务方式,而访问WCF服务,可能有基于局域网(内网)模式,也可能是基于互联网(外网)模式,那么我们就可能配置三种登陆方式,如果每次需要手工修改配置文件,可能比较麻烦,如果我们能够通过程序方式来实现参数的界面化配置,那么工作就轻松很多,也不容易出错。

1、配置界面和配置内容介绍

关于配置的管理,我肯定是首先推荐SettingsProvider.net这个组件,大家需要了解这个组件的使用,可以参考我的随笔《
Winform开发框架之参数配置管理功能实现-基于SettingsProvider.net的构建
》,这里可以结合FireFoxDialog界面效果实现比较友好的配置管理,界面效果如下所示。

在WCF的配置文件中,我们可以看到很多对于IP地址的配置,而一般我们把WCF的配置信息放到一个独立的配置文件里面,方便管理。

对于局域网的WCF客户端的配置信息,大概效果如下所示。

而对于外网,也只是把这些IP换成公网IP即可,一般来说,这些是独立的配置文件,我们一般还有一个主配置文件**.exe.config,用来配置我们框架常用到的一些配置信息。

如下所示,在我们混合式需要访问本地数据库的时候,就可以在主配置文件里面保存主要的数据库连接信息。

2、配置对话框的设计和实现

前面介绍了关于参数配置和WCF配置文件的一些相关信息,基于这些考虑,我们应该在混合式模式的登陆界面里面,设计好相关界面,让用户可以方便配置访问不同的数据源,以便实现真正混合式的强大功能和便利性。

首先我们来介绍一下我的混合式框架,登陆界面的设计。

单击参数设置按钮,可以进入登陆访问参数的界面对话框,在里面可以友好直观的配置相关的WCF访问参数,界面如下所示,这里面使用了类似FireFox的界面组件,后台的文件存储,主要是基于本地的XML存储,使用了SettingsProvider.net这个组件进行数据的存储管理。

如果是选择“
单机版模式
”,那么我们把对应的
网络方式
设置为不可选择,这样的效果比较好体现本地模式的排他性。

这些界面基本上介绍了如何处理混合式开发框架的参数配置内容了,具体的实现操作是如何的呢?下面我们来一步步分析介绍下。

首先我们定义一个用来保存和加载参数的一个实体类,用来存储对应的参数信息。

    /// <summary>
    ///用户登录的连接方式、访问方式/// </summary>
    public classLoginParameter
{
/// <summary> ///系统最后登录账号/// </summary> [DefaultValue("admin")]public string LoginId { get; set; }/// <summary> ///登陆密码/// </summary> [ProtectedString]public string Password { get; set; }/// <summary> ///记住密码/// </summary> [DefaultValue(false)]public bool RememberPassword { get; set; }/// <summary> ///是否为本地数据库连接方式,否则为使用WCF服务模式(内网,外网)/// </summary> [DefaultValue(false)]public bool IsLocalDatabase { get; set; }/// <summary> ///内网WCF的主机地址/// </summary> [DefaultValue("192.168.1.10")]public string InternalWcfHost { get; set; }/// <summary> ///内网WCF的端口/// </summary> [DefaultValue(8000)]public int InternalWcfPort { get; set; }/// <summary> ///外网WCF的主机地址/// </summary> [DefaultValue("183.6.161.193")]public string ExternalWcfHost { get; set; }/// <summary> ///外网WCF的端口/// </summary> [DefaultValue(8000)]public int ExternalWcfPort { get; set; }
}

这样我们就可以以实体类为载体,对数据进行获取和保存操作了。

配置信息的加载和保存操作代码如下所示,这些保存操作就是基于SettingsProvider.net的。

        /// <summary>
        ///从本地XML文件中加载参数信息/// </summary>
        private voidLoadParameter()
{
store
= new PortableStorage();//保存在本地计算机 settings = newSettingsProvider(store);
parameter
= settings.GetSettings<LoginParameter>();if (parameter != null)
{
this.cmbzhanhao.Text =parameter.LoginId;this.chkRemember.Checked =parameter.RememberPassword;if(parameter.RememberPassword)
{
this.tbPass.Text =parameter.Password;
}
else{this.tbPass.Text = "";
}
this.chkLocalVersion.Checked =parameter.IsLocalDatabase;//确保为正确的访问方式,网络版还是单机版 SetAccessType(this.chkLocalVersion.Checked);
}
}
/// <summary> ///把用户的信息保存到本地XML文件里面/// </summary> private voidSaveParameter()
{
store
= new PortableStorage();//保存在本地计算机 settings = newSettingsProvider(store);
parameter
= settings.GetSettings<LoginParameter>();if (parameter != null)
{
parameter.LoginId
= this.cmbzhanhao.Text;if(parameter.RememberPassword)
{
parameter.Password
= this.tbPass.Text;
}
else{
parameter.Password
= "";
}
parameter.IsLocalDatabase
= this.chkLocalVersion.Checked;

settings.SaveSettings
<LoginParameter>(parameter);
}
}

对于如果选择单机版模式,那么我们需要设置相应的参数,用来告诉混合式框架采用本地数据库模式,具体代码如下所示。

        /// <summary>
        ///单机版模式的复选框事件处理/// </summary>
        private void chkLocalVersion_CheckedChanged(objectsender, EventArgs e)
{
SetAccessType(
this.chkLocalVersion.Checked);
}
/// <summary> ///设置为网络方式还是单机版模式/// </summary> /// <param name="localType">是否为单机版模式</param> private void SetAccessType(boollocalType)
{
this.lblNetType.Enabled = !localType;this.radNetType.Enabled = !localType;

AppConfig config
= newAppConfig();
config.AppConfigSet(
"CallerType", localType ? "win" : "wcf");
ConfigurationManager.RefreshSection(
"appSettings");
}

如果是网络方式,还需要区分内网模式,还是外网模式,根据参数来对相关的配置文件进行修改,具体逻辑代码如下所示。

        /// <summary>
        ///网络方式(内网、外网)切换处理事件/// </summary>
        private void radNetType_SelectedIndexChanged(objectsender, EventArgs e)
{
ChangeConfig();
}
/// <summary> ///修改配置文件/// </summary> private voidChangeConfig()
{
if (parameter != null)
{
bool isIntranet = radNetType.EditValue.ToString() == "内网";if(isIntranet)
{
UpdateConfig(parameter.InternalWcfHost, parameter.InternalWcfPort);
}
else{
UpdateConfig(parameter.ExternalWcfHost, parameter.ExternalWcfPort);
}
}
else{
MessageDxUtil.ShowError(
"获取参数信息失败");
}
}
/// <summary> ///WCF模式下,修改配置文件中的主机地址信息和端口/// </summary> /// <param name="serverIPAddress">主机地址信息</param> /// <param name="serverPort">端口</param> private void UpdateConfig(string serverIPAddress, intserverPort)
{
string basePath =System.Environment.CurrentDirectory;
UpdateConfigFile(serverIPAddress, serverPort, Path.Combine(basePath,
"BaseWcfConfig.config"));
UpdateConfigFile(serverIPAddress, serverPort, Path.Combine(basePath,
"WcfConfig.config"));
}
/// <summary> ///通过正则标识方式替换其中的主机信息和端口参数/// </summary> /// <param name="serverIPAddress">主机地址信息</param> /// <param name="serverPort">端口</param> /// <param name="exeFilePath">配置文件地址</param> private void UpdateConfigFile(string serverIPAddress, int serverPort, stringexeFilePath)
{
string address =File.ReadAllText(exeFilePath, System.Text.Encoding.UTF8);string pattern = "://.*?/";string replacement = string.Format("://{0}:{1}/", serverIPAddress, serverPort);
address
=Regex.Replace(address, pattern, replacement);

File.WriteAllText(exeFilePath, address, System.Text.Encoding.UTF8);
}

以上就是我对于混合式开发框架的登陆方式和登陆参数进行的配置信息管理的一个案例,希望对大家有帮助。

很久没有写博客了,一些读者也经常问问一些问题,不过最近我确实也很忙,除了处理日常工作外,平常主要的时间也花在了继续研究微软的实体框架(
EntityFramework
)方面了。这个实体框架加入了很多特性(例如LINQ等),目前也已经应用的比较成熟了,之所以一直没有整理成一个符合自己开发模式的实体框架,是因为这个框架和原来我的基于EnterpriseLibrary的模式还是有很大的不同,不过
实体框架推出来也很久了,目前也去到了EntityFramework6了,听说7也快出来了。

随着我自己参考阅读了大量的项目源码以及对实体框架各个技术点的学习深入,对其中很多的方面都有自己的一些见解和心得,希望通过这个系列,能够和读者一步步分析,一步步深入学习这个微软目前最为流行的.NET开发框架。本篇主要从基础开始一步步介绍基于泛型的仓储模式实体框架(The Entity Framework of Generic Repository Pattern ),希望大家耐心阅读。

1、实体框架的初步印象

最简单的实体框架,你可以在Winform或者Web项目里面添加一个【ADO.NET实体数据模型】项开始,一步步创建一个基于SqlServer的实体框架项目。最开始,我们可以不考虑什么设计模式,能够使用即可,因此我们可能创建一个比较简单的项目代码,这个有助于我们了解实体框架的一些基础工作原理。

为这个项目选定数据连接以及供测试使用的一两个表的对象,然后完成创建工作,这个【ADO.NET实体数据模型】创建完成后,我们可以看到项目里面添加了一个Model1.edmx的文件,并且同时生成了几个项目文件,其中包括了数据访问对象SqlserverContext和几个实体类(默认为表名称),我们也可以打开edmx的文件进行实体类属性的修改,如下所示。

默认生成后,我们就可以使用这个数据访问上下文对象SqlserverContext, 来进行相关的数据处理操作了,简单的测试代码如下所示。

        private voidGetIntData()
{
//创建数据访问对象 var context = newSqlserverContext();//新建一个实体类并赋值 TB_Province info = newTB_Province();
info.ID
= 100001;
info.ProvinceName
= "测试省份";
context.TB_Province.Add(info);
context.SaveChanges();
//根据主键判断记录是否存在 TB_Province info2 =context.TB_Province.Find(info.ID);if (info2 != null)
{
Console.WriteLine(
"记录已存在!");//如果存在对象,先删除 context.TB_Province.Remove(info2);
context.SaveChanges();
//检查是否删除对象 info2 =context.TB_Province.Find(info.ID);if (info2 == null)
{
Console.WriteLine(
"记录已删除!");
}
}
//把记录全部获取并绑定到列表上。 var list =context.TB_Province.ToList();this.dataGridView1.DataSource =list;
}

最后获得的界面效果就是能够顺利执行各种操作后把记录显示出来到列表上了。

2、实体框架的工作原理

1)数据访问上下文对象介绍

从上面的代码我们可以看到,数据访问上下文对象SqlserverContext已经可以直接和数据库交互了,能够实现表对象基本增删改查的操作功能了,那么这个类是如何的呢?为什么具有这个功能呢?

我们先看看它的代码,
SqlserverContext的类代码如下所示(代码为自动生成的)。

    public partial classSqlserverContext : DbContext
{
publicSqlserverContext()
:
base("name=SqlserverContext")
{
}
protected override voidOnModelCreating(DbModelBuilder modelBuilder)
{
throw newUnintentionalCodeFirstException();
}
public virtual DbSet<TB_City> TB_City { get; set; }public virtual DbSet<TB_Province> TB_Province { get; set; }public virtual DbSet<TB_DictType> TB_DictType { get; set; }
}

其中代码DbSet<TB_Province> TB_Province代表一个具体的数据访问对象,对表TB_Province的数据访问,其他的类似。我们查看.NET的内置对象DbSet的已经支持了一些常规的操作了。

而EMDX文件的本身是一个XML文件,它的内容如下所示。

实体框架本身通过XML映射的方式(ORM方式),封装了从数据库到实体类,以及实体类到数据库的交互过程,具体的过程我们可以参考下面的实体数据模型 (EDM)介绍。

2)实体数据模型 (EDM)介绍

Entity Framework 实体框架的要点是实体数据模型 (EDM),一个用于描述应用程序域对象的概念模型。 Entity Framework 实体框架让开发人员可以针对实体数据模型提出查询,而不必操心数据库的具体操作。 实体数据模型的实体以及实体之间的关系以 XML 形式定义,而开发人员基于该模型的实体来处理强类型化类。

在运行时,利用特定于数据库的 ADO.NET 提供程序,Entity Framework 实体框架将针对实体数据模型而创建的查询转换为存储查询(例如 T-SQL),然后送至数据库。 Entity Framework 将查询结果转换为由强类型化实体类所定义的对象。

实体数据模型 (EDM),由三个概念组成。概念模型由概念架构定义语言文件 (.csdl)来定义,映射由映射规范语言文件 (.msl),存储模型(又称逻辑模型)由存储架构定义语言文件 (.ssdl)来定义。这三者合在一起就是EDM模式。EDM模式在项目中的表现形式就是扩展名为.edmx的文件。这个包含EDM的文件可以使用Visual Studio中的EDM设计器来设计。由于这个文件本质是一个xml文件,可以手工编辑此文件来自定义CSDL、MSL与SSDL这三部分。

CSDL定义了EDM或者说是整个程序的灵魂部分 – 概念模型。这个文件完全以程序语言的角度来定义模型的概念。即其中定义的实体、主键、属性、关联等都是对应于.NET Framework中的类型。

SSDL这个文件中描述了表、列、关系、主键及索引等数据库中存在的概念。

MSL这个文件即上面所述的CSDL与SSDL的对应,主要包括CSDL中属性与SSDL中列的对应。

通过以上三个XML文件的映射关系,在程序里面,就主要是利用强类型数据的实体类进行处理了,而对实体类的任何处理修改,最终会解析后得到相应的数据库执行语句,然后进行提交处理了。

3、基于泛型的仓储模式实体框架

如果基于第一点来构建框架,虽然很快速,但是这样的做法在中大型的项目里肯定不可取,因为生成后的代码还需要进行多个步骤的修改调整,而且也没有很好实现重用的目的,
很多地方需要自己手动编码处理,结构也不是很清晰,因此需要对框架进行一步步的优化和提炼。

在介绍基于泛型的仓储模式实体框架(The Entity Framework of Generic Repository Pattern )前,我们先来回顾一下我之前的Winform开发框架分层结构,这个基于Enterprise Library的框架,常见的分层模式,可以分为UI层、BLL层、DAL层、IDAL层、Entity层、公用类库层等等。

这种分层可以在数据库设计完成后,可以通过代码生成工具,获取到表对象的信息和关系,直接快速生成相应的分层代码,从而实现架构、分层、命名规则等方面的一致化,并且是快速开发。

而且这种分层模式也是一种比较通用的分层结构了,那么我们要介绍的实体框架是否也可以依照这种方式来构建呢?是否可以结合代码生成工具的生成模板来进行整体性框架的开发呢?

下面我们来介绍一下泛型的仓储模式框架的具体实现过程。

1)实体类的代码如下所示(先按表名生成)。

    public partial classTB_City
{
public long ID { get; set; }public string CityName { get; set; }public string ZipCode { get; set; }public Nullable<long> ProvinceID { get; set; }
}

2)数据访问基类接口层(定义了几个测试的基类接口)

    /// <summary>
    ///数据访问层基类接口/// </summary>
    /// <typeparam name="T">实体对象类型</typeparam>
    public interface IBaseDAL<T> where T : class{
T Get(
objectid);

IList
<T> GetAll(Expression<Func<T, bool>>whereCondition);

IList
<T>GetAll();
}

3)数据访问层基类实现层

    /// <summary>
    ///数据访问层基类实现层/// </summary>
    /// <typeparam name="T">实体对象类型</typeparam>
    public abstract class BaseDAL<T> : IBaseDAL<T>  where T : class{protectedDbContext baseContext;protected IDbSet<T>objectSet;publicBaseDAL(DbContext context)
{
this.baseContext =context;this.objectSet = this.baseContext.Set<T>();
}
public T Get(objectid)
{
returnobjectSet.Find(id);
}
public IList<T>GetAll()
{
return objectSet.ToList<T>();;
}
public IList<T> GetAll(Expression<Func<T, bool>>whereCondition)
{
return objectSet.Where(whereCondition).ToList<T>();
}
}

4)具体数据访问对象接口定义(城市表为例)

    /// <summary>
    ///城市数据访问层接口/// </summary>
    public interface ICityDAL : IBaseDAL<City>{
}

5)具体数据访问对象实现层(城市表为例)

    /// <summary>
    ///城市数据访问对象/// </summary>
    public class CityDAL : BaseDAL<TB_City>{protectedMyDataContext context;/// <summary>
        ///构造函数/// </summary>
        /// <param name="context"></param>
        public CityDAL(MyDataContext context) :base(context)
{
this.context =context;
}

6)数据仓储对象(上下文对象)

    public classMyDataContext : DbContext
{
public MyDataContext() : base("name=sqlserver")
{
}
protected override voidOnModelCreating(DbModelBuilder modelBuilder)
{
throw newUnintentionalCodeFirstException();
}
public virtual DbSet<TB_City> City { get; set; }
}

BLL、IBLL的分层和数据访问层的类似,主要是提高一份,方便做业务整合实现而已,在此不再赘述。

最终实现仓储模式框架的分层结构如下所示。

以上就是我对基于泛型的仓储模式的实体框架的一个初探性的开端,下面会在这个系列里面继续分析其中存在的问题,并继续优化改良这个基于泛型的仓储模式的实体框架。希望大家喜欢并继续支持。

这个系列文章索引如下:

Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)

Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)