2023年2月

在GIS中,地标或者道路等信息查找是一项很重要的功能,类似于我们查找数据库记录那样,需要模糊进行匹配,一般来说,找到需要的地标或者道路等地图元素后,双击可以定位到地图的具体位置,并放大地图,类似于Google Map的Fly to功能。

本文先介绍MapX如何在地图中查找所需要的地图元素。

由于地图的信息是按照层来存储的,所以我们查找信息的时候,也是按照一层层来进行检索,由于MapInfo的地图一般有很多层,每层进行遍历会比较花费时间,所以一般的专业系统,都只是关系一部分层,我们通常在配置文件中指定感兴趣的层名集合,然后查找的时候,在这些层中查找,这样可以提高检索的速度。

我们看看操作层的代码,是如何实现的。


foreach
(
string
layer
in
layerNameArray)
{

if
(
string
.IsNullOrEmpty(layer))

continue
;


try

{

#region
每层的查询


string
condition
=

string
.Format(
"
NAME like \
"
%
{
0
}
%
\
""
, SearchLocation);
MapXLib.Layer mapLayer

=
TarMap.Layers._Item(layer);

if
(mapLayer
!=

null
)
{
feature

=
mapLayer.Search(condition,
null
);

int
count
=
feature.Count;

if
(feature
!=

null
)
{
MapXLib.Dataset ds

=
TarMap.DataSets.Add(MapXLib.DatasetTypeConstants.miDataSetLayer, mapLayer, layer,

0
,
0
,
0
, Missing.Value,
false
);
Dictionary

<
string
, ResultOfSearching
>
resultList
=

new
Dictionary
<
string
, ResultOfSearching
>
();


//
To Do 遍历特性代码,待续



TarMap.DataSets.RemoveAll();
}
}

#endregion

}

catch
(Exception ex)
{
LogHelper.Error(ex);
}
}

查找的时候,我们得到了每个Feature,然后对Feature的ID和Name进行判断查找,如何有匹配的,就表示找到了一个对象,如果最后一个也没有,那么本次地图查找应该没有匹配的元素了,Feature的查找代码如下所示


#region
遍历特性


for
(
int
i
=

1
; i
<
feature.Count; i
++
)
{

try

{

object
objLocation
=
ds.get_Value(feature[i],
"
Name
"
);

object
objID
=
ds.get_Value(feature[i],
"
Id
"
);


string
strLocation
=
(objLocation
==

null
)
?

""
: objLocation.ToString();

string
strID
=
(objID
==

null
)
?

""
: objID.ToString();


if
(
!
resultList.ContainsKey(strID)
&&
feature[i].FeatureID
!=

0
)
{
ResultOfSearching searchInfo

=

new
ResultOfSearching();
searchInfo.FeatureID

=
feature[i].FeatureID;
searchInfo.ID

=
strID;
searchInfo.Layer

=
layer;
searchInfo.Location

=
feature[i].Name;


if
(
!
resultList.ContainsKey(strID))
{
resultList.Add(strID, searchInfo);
}

if
(
!
allResultList.ContainsKey(strID))
{
allResultList.Add(strID, searchInfo);
}
}
}

catch
(Exception ex)
{

//
LogHelper.Error(ex);


}
}

#endregion

我们遍历每层,对每层的Feature的信息进行查找,把结果放到集合中,当我们返回集合的时候,我们就可以把搜索到的信息显示在树形控件中了。


if
(searchResult
!=

null

&&
searchResult.Count
>

0
)
{

this
.tvwResult.BeginUpdate();

TreeNode node

=

null
;

foreach
(ResultOfSearching info
in
searchResult.Values)
{

if
(
!
string
.IsNullOrEmpty(info.Location))
{
node

=
tvwResult.Nodes.Add(info.Location);
node.Tag

=
info;
}
}


this
.tvwResult.EndUpdate();

this
.tssl_Status.Text
=

string
.Format(
"
从地图上找到 {0} 结果
"
, searchResult.Count);
}

else

{

this
.tssl_Status.Text
=

"
地图上找不到您需要的数据
"
;
}

最后我们看到查找信息的界面如下所示

鹰眼一般是指GIS应用中,系统有两个一大一小的图,小图提供快速定位地图位置的功能,这个是在GIS中应用比较广泛的一个功能,在Google地图中,右下角也有一个小图框,也是指的鹰图。如下图所示。

在本次介绍的例子中,左边是主地图,右边是小地图,即鹰眼。

鹰眼要实现下面几个功能:

1。 主地图变化时,鹰眼也要做出相应的位置变化,以便更好展示位置所在。

2。鹰眼地图移动到任何一个位置,主地图也要做相关联的位置变化,以实现地图的粗略定位。

3。主地图实现鼠标滚轮的变化支持。

首先我们先设置主地图的鼠标滚轮支持,先在主地图上设置MouseWheelSupprot属性为miFullMousewheelSupport。该值是表示地图视图支持滚轮的操作。或者你在代码中添加下面的代码实现滚轮支持。

axMap1.MousewheelSupport
=
MapXLib.MousewheelSupportConstants.miFullMousewheelSupport;

然后在MapViewChanged的事件中添加和鹰图关联的操作



private

void
axMap1_MapViewChanged(
object
sender, EventArgs e)



{

MapXLib.Feature tempFea;

//
声明Feature变量


MapXLib.Style tempStyle
=

new
MapXLib.Style();
//
声明Style变量


Double Xmin, Xmax, Ymin, Ymax;

RegionPoint Pmin, Pmax;


//
矩形边框还没有创建时



if
(Portal.gc.MainDialog.EagleAnoLayer.AllFeatures.Count
==

0
)



{

//
设置矩形边框样式


tempStyle.RegionPattern
=
MapXLib.FillPatternConstants.miPatternNoFill;
//
设置Style的矩形内部填充样式


tempStyle.RegionBorderWidth
=

2
;
//
设置Style的矩形边框宽度


//
在图层创建大小为Map1的边界的Rectangle对象


tempFea
=
Portal.gc.MainDialog.EagleMap.FeatureFactory.CreateRegion(Portal.gc.MainDialog.MainMap.CtlBounds, tempStyle);

Portal.gc.MainDialog.EagleFea

=
Portal.gc.MainDialog.EagleAnoLayer.AddFeature(tempFea, Missing.Value);
//
添加矩形边框



return
;

}



else




{

//
否则,根据Map1的视野变化改变矩形边框的大小和位置


Xmin
=
Portal.gc.MainDialog.MainMap.CtlBounds.XMin;

Xmax

=
Portal.gc.MainDialog.MainMap.CtlBounds.XMax;

Ymin

=
Portal.gc.MainDialog.MainMap.CtlBounds.YMin;

Ymax

=
Portal.gc.MainDialog.MainMap.CtlBounds.YMax;


MapXLib.Points feaPoint

=
Portal.gc.MainDialog.EagleFea.Parts[
1
];

feaPoint.RemoveAll();

//
除去已有的矩形边框的顶点

//
添加大小和位置已变化的矩形边框的四个顶点


feaPoint.AddXY(Xmin, Ymin, Missing.Value);

feaPoint.AddXY(Xmax, Ymin, Missing.Value);

feaPoint.AddXY(Xmax, Ymax, Missing.Value);

feaPoint.AddXY(Xmin, Ymax, Missing.Value);

Portal.gc.MainDialog.EagleFea.Update(Missing.Value, Missing.Value);

//
更新显示




if
(Portal.gc.MainDialog.MainMap.Zoom
<

3
)



{
//
如果放大到实际比例的1/2倍以上,则启动局部刷新线程


Pmin.x
=
Xmin;

Pmin.y

=
Ymin;

Pmax.x

=
Xmax;

Pmax.y

=
Ymax;

}



else




{
//
否则,停止局部刷新线程


Pmin.x
=

0
;

Pmin.y

=

0
;

Pmax.x

=

0
;

Pmax.y

=

0
;

}




if
(fsFlag
==

0
)



{

oldZoom

=
Portal.gc.MainDialog.MainMap.Zoom;

oldCenterX

=
(Xmin
+
Xmax)
/

2
;

oldCenterY

=
(Ymin
+
Ymax)
/

2
;

}



}



}

其中我们注意到了操作小图的地代码,是添加一个矩形图来表示大图的当前视点。如下面的代码:

tempFea
=
Portal.gc.MainDialog.EagleMap.FeatureFactory.CreateRegion(Portal.gc.MainDialog.MainMap.CtlBounds, tempStyle);

Portal.gc.MainDialog.EagleFea

=
Portal.gc.MainDialog.EagleAnoLayer.AddFeature(tempFea, Missing.Value);
//
添加矩形边框

在小图中,为了能够单击鼠标就可以定位到主地图位置上,我们需要处理鼠标单击的时间,如代码


private

void
axMap1_MouseDownEvent(
object
sender, AxMapXLib.CMapXEvents_MouseDownEvent e)
{

double
MapX
=

0
;
//
定义x坐标变量



double
MapY
=

0
;
//
定义y坐标变量

//
把屏幕坐标转换为地图坐标


axMap1.ConvertCoord(
ref
e.x,
ref
e.y,
ref
MapX,
ref
MapY, MapXLib.ConversionConstants.miScreenToMap);

//
设置主图的中心x坐标和y坐标


Portal.gc.MainDialog.MainMap.CenterX
=
MapX;
Portal.gc.MainDialog.MainMap.CenterY

=
MapY;

}

这样基本上就能够实现两个地图之间的视图关联,从而更好定位地图的位置,如果为了鹰图更友好和更方便操作,也可以通过在主图变化的时候,绘制出更好的图形来展现主图的当前位置。

我们一般在Winform的开发中,需要使用HTML的地方,很多时候,需要用到WebBrowser这个控件,这个控件是引用了MSHTML.dll这个COM类库的,在开发的时候感觉很好,很强大,可是发布给客户的时候,很多时候,就因为这个COM控件,会出现很多令人头痛的问题,因为这个控件导致的问题,也有很多解决方法,但是问题还是经常发生。

我曾经修改完成过一个HTML Editor的控件(
WinForm界面开发之“HTML内容编辑控件”
),用来集成HTML邮件发送的,后来发现在很多使用客户的电脑上,点击该软件后没有反应,或者直接跳出错误对话框,原因就是使用WebBrowser这个控件,而这个控件就是属于MSHTML.dll程序集的,弄了很久也没有能够在虚拟机上(只安装了Dotnet framework2.0),因此不知道究竟哪里出了问题,因为我已经能把网上所有方法都尝试过了,包括把程序集部署到Assembly中,在开发环境中把C:\Program Files\Common Files\Merge Modules\vs_piaredist.exe 拷贝到虚拟机上进行安装,好像也是不行的,郁闷了很久。。。。

下面是一些解决问题的文章:

Microsoft.mshtml到底是什么


mshtml组件引用的问题


如果不使用微软这个自带的HTML浏览器控件,那么还有一个代替的选择,就是使用另外一个COM控件,但是相对比较小,不依赖于MSHTML.dll程序集的。

参考文章地址:
http://www.codeproject.com/KB/miscctrl/csEXWB.aspx?msg=2814607#xx2814607xx

这个控件很好,不过好像对支持中文有点问题。实现HTML编辑的控件界面是:

HTMLEditor image - htmleditor.gif

其源码地址是在:
http://code.google.com/p/csexwb2/downloads/list
上。

本人在使用中,如果在Edit编辑框中有中文字符,在Edit和Source中切换的时候,如果在Source的选项输入一些其他的字符 ,就会得到乱码,因此也是很麻烦的事情,本来想修改其源码的,发现也是一个C++的源码,实现思路很复杂。

不过如果我们只是把Source中的HTML作为一个展示,而不做修改的话,那么就不会出现乱码,不过这个不是好的方法,因为我们如果不能修改HTML的话,干脆就只要一个Edit中的HTML编辑页面就得了。

其他的模仿实现HTML编辑的控件或者程序,总的来说就是界面太丑,功能太简单了,一般是没有什么用处的。

QQ校友里面很多数据是按照学校进行归类的,因此,我们只要知道学校的名称,根据一些条件就可以查找您感兴趣的校友了。

QQ校友的数据查看或者查询,是需要登录才行的,QQ校友的登录,可以让用户通过输入验证码方式进行登录。博客已经有很多大牛找出很多解决方法,在此不说这块,我们这里介绍下,如何分析页面,合理使用正则表达式,获取所需要的数据。

通过抓包工具IE Http Analyzer 可以跟踪到QQ校友的资料查询URL地址类似下面的格式:

http://xiaoyou.qq.com/index.php?mod=search
&act
=result
&name
=%E7%8E%8B
&schooltype
=3
&slevel
=GF
&sname
=%E8%A5%BF%E5%8D%97%E4%BA%A4%E5%A4%A7
&sid
=12229
&deptname
=
&deptid
=0
&syear
=1997
&sex
=0
&age
=-1
&year
=0
&month
=0
&day
=0
&astro
=0
&homeprov
=0
&homecity
=0
&nowprov
=0
&nowcity
=0

在Google浏览器上,你看到的中文地址信息是:

http://xiaoyou.qq.com/index.php?mod=search&act=result&name=%E7%8E%8B&schooltype=3&slevel=GF&sname=%E8%A5%BF%E5%8D%97%E4%BA%A4%E5%A4%A7&sid=12229&deptname=&deptid=0&syear=1997&sex=0&age=-1&year=0&month=0&day=0&astro=0&homeprov=0&homecity=0&nowprov=0&nowcity=0

如果你登陆后,执行这个页面,你就可以看到的部分页面源码是如下所示(我截取了关心的那部分HTML代码):

<
ul
class
="results_list"
>


<
li
class
="request_one"
>


<
a
href
="/index.php?mod=profile&amp;u=c265e4bd629300c500a439a57fc4b1c6d2458796fbfe9608"
class
="pic_user_m xy_card"
><
span
class
="skin_portrait_round"
></
span
><
img
src
="http://xy.store.qq.com/c265e4bd629300c500a439a57fc4b1c6d2458796fbfe96080"
alt
="王明灵"

/></
a
>


<
div
class
="list_info"
>


<
p
class
="user_info"
><
a
href
="/index.php?mod=profile&amp;u=c265e4bd629300c500a439a57fc4b1c6d2458796fbfe9608"
class
="user_name xy_card"
>
王***
</
a
>

</
p
>
西南交大

</
div
>


<
ul
>


<
li
><
img
src
="http://imgcache.qq.com/campus_v2/ac/b.gif"
alt
=""
class
="icon_script"

/><
a
href
="javascript:is_sendscrip('c265e4bd629300c500a439a57fc4b1c6d2458796fbfe9608');"
>
发小纸条
</
a
></
li
>


<
li
><
img
src
="http://imgcache.qq.com/campus_v2/ac/b.gif"
alt
=""
class
="icon_add"

/><
a
href
="javascript:void(0)"
onclick
="add_friends_frame('c265e4bd629300c500a439a57fc4b1c6d2458796fbfe9608')"
>
加为好友
</
a
></
li
>


</
ul
>


</
li
>

<
li
class
="request_one"
>


<
a
href
="/index.php?mod=profile&amp;u=c265e4bd629300c5202a08ff05052aadd58b7e634ad8d204"
class
="pic_user_m xy_card"
><
span
class
="skin_portrait_round"
></
span
><
img
src
="http://xy6.store.qq.com/c265e4bd629300c5202a08ff05052aadd58b7e634ad8d2040"
alt
="王*霞"

/></
a
>


<
div
class
="list_info"
>


<
p
class
="user_info"
><
a
href
="/index.php?mod=profile&amp;u=c265e4bd629300c5202a08ff05052aadd58b7e634ad8d204"
class
="user_name xy_card"
>
王*霞
</
a
>

</
p
>
北京理工大学

</
div
>

其实我们需要把里面:用户编号、姓名、学校,然后我们根据用户编号就可以进一步获取该用户的信息了。

用户详细的介绍内容页面地址是:
http://xiaoyou.qq.com/index.php?mod=profile&u=c265e4bd629300c500a439a57fc4b1c6d2458796fbfe9608

然后我们看到,这个页面的源码是:

<
div
class
="fun_info_class"
>


<
p
><
span
class
="c_tx2"
>

&nbsp;&nbsp;&nbsp;&nbsp;
别:
&nbsp;
</
span
><
span
>

</
span
></
p
>


<
p
><
span
class
="c_tx2"
>

&nbsp;&nbsp;&nbsp;&nbsp;
日:
&nbsp;
</
span
><
span
>
1978年12月27日
</
span
></
p
>


<
p
><
span
class
="c_tx2"
>

&nbsp;&nbsp;&nbsp;&nbsp;
座:
&nbsp;
</
span
><
span
>
魔羯座
</
span
></
p
>


<
p
><
span
class
="c_tx2"
>
上次登录:
</
span
>

<
span
>
1天前(共10次)
</
span
></
p
>


<
div
class
="list_sch_wrap"
><
p
><
span
class
="c_tx2"
>
就读学校:
</
span
></
p
><
ul
class
="list_sch"
><
li
><
a
href
="/index.php?mod=school&act=schoolportal&amp;school_id=12229"
>
西南交大
</
a
></
li
></
ul
></
div
>


</
div
>

这个也页面里面有性别、生日、星座、上次登录时间等相关信息,这样基本上就构成了完整的用户信息了。

好了,大的方面我们知道了,看看具体如何把页面里面的信息提取出来。

第一步,把页面内容分离,获取感兴趣的部分,正则表达式是:
<
li
\s+?class
="request_one"
\
>
\s*(.*?)\s*
<
ul
\
>

其中表达式\s的意思如下:

\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。

\s+?
标识有一个到多个空白字符

(.*?)
标识非贪婪模式匹配任何字符

我们看到正则表达式工具中得到的结果是:

其中每项都列出来了,我们把它放到一个List集合中,然后每个项目再进行一次正则表达式的匹配,获取到具体的内容。


#region
获得各项列表字符串


List

<
string
>
itemHtmlList
=

new
List
<
string
>
();

string
itemRegex
=

"
刚才的正则表达式
"
;
//
<li\s+?class="request_one"\>\s*(.*?)\s*<ul\>



Regex re

=

new
Regex(itemRegex, RegexOptions.IgnoreCase
|
RegexOptions.Singleline
|
RegexOptions.IgnorePatternWhitespace);

Match mc

=
re.Match(content);

if
(mc.Success)
{
MatchCollection mcs

=
re.Matches(content);

foreach
(Match me
in
mcs)
{

string
strValue
=
me.Groups[
1
].Value;
itemHtmlList.Add(strValue);
}
}

#endregion

上图中黑色背景下面的部分,就是我们每项要解析的内容。

然后我们再使用正则表达式:<a\s+?href="(.*?)".*?src="(.*?)".*?alt="(.*?)".*?&amp;u=(.*?)".*?</p\>\s*(.*?)\s*</div\>

就可以得到下面的效果

其中正则表达式部分字符的意思如下:

. 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。

下面我们要根据用户信息页面,去获取其他的相关信息了,我们用正则表达式:<div\s+?class="fun_info_class"\>.*?</span\>\s*?<span\>(.*?)</span\>.*?</span\>\s*?<span\>(.*?)</span\>.*?</span\>\s*?<span\>(.*?)</span\>.*?</span\>\s*?<span\>(.*?)</span\>.*?</div\>

来过滤代码,就可以了。

整个过程就是这样反复获取,解析,就可以实现对QQ校友数据的采集了。

不过有一点不好,就是QQ校友和用户ID不是QQ号码,并且你找不出和QQ号码有任何关联的地方,这或许就是隐私保护的举措吧。

如果阁下有好的方法,可以获取到关联关系,大家可以探讨一下。

接触过邮件发送的人,可能对SMTP协议以及对dotnet下面的SMTP类的操作应该不会陌生,但可能不一定了解ESMTP是什么东西,下面我们对它先做一个介绍,然后再详细介绍两种方式在发送嵌入图片(不是附件模式)操作的实现。

介绍SMTP命令和ESMTP过程


什么是 SMTP

SMTP (Simple Mail Transfer Protocol) : 电子邮件从客户机传输到服务器或从某一个服务器传输到另一个服务器使用的传输协议。 SMTP 是请求/响应协议,命令和响应都是基于 ASCII 文本,并以 CR 和 LF 符结束。响应包括一个表示返回状态的三位数字代码。SMTP 在 TCP 协议 25 端口监听连接请求。

什么是 ESMTP

ESMTP (Extended SMTP),顾名思义,扩展 SMTP 就是对标准 SMTP 协议进行的扩展。它与 SMTP 服务的区别仅仅是,使用 SMTP 发信不需要验证用户帐户,而用 ESMTP 发信时, 服务器会要求用户提供用户名和密码以便验证身份。验证之后的邮件发送过程与 SMTP 方式没有两样。

SMTP 命令

SMTP 命令包括:

HELO 向服务器标识用户身份。发送者能欺骗,说谎,但一般情况下服务器都能检测到。

EHLO 向服务器标识用户身份。发送者能欺骗,说谎,但一般情况下服务器都能检测到。

MAIL FROM 命令中指定的地址是发件人地址

RCPT TO 标识单个的邮件接收人;可有多个 RCPT TO;常在 MAIL 命令后面。

DATA 在单个或多个 RCPT 命令后,表示所有的邮件接收人已标识,并初始化数据传输,以 CRLF.CRLF 结束

VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令

EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用

HELP 查询服务器支持什么命令

NOOP 无操作,服务器应响应 OK

RSET 重置会话,当前传输被取消

QUIT 结束会话

以上这些是SMTP和ESMPT的基本知识,特别是这些命令,对我们操作ESMTP协议帮助会比较大的,说白了,ESMTP就是通过Socket流方式和邮件服务器进行交互,基本上目前所有的服务器都会提供对ESMTP协议的支持,SMTP协议在dotnet中实现就是通过调用
System.Net.Mail .SmtpClient
实现的。


发送嵌入图片或者文件邮件的原理就是,把图片资源提交(和附件类似方式,但是不同),然后通过修改HTML的IMG标识的SRC实现实现内容的相对引用的。

如我们插入本地图片文件的时候,文件的地址是“D:\test.jpg",如果这样发送出去,那么肯定是查看不到的了,我们要将它修改为:
<img src=\"cid:test\">

然后,我们在邮件正文中附加相关的文件就可以了,这个有点类似于一种特殊的定位格式。具体的示例代码如下所示。

System.Net.Mail.MailMessage mailMessage
=

new
System.Net.Mail.MailMessage();

mailMessage.From
=
"
发送者邮箱
"
;
mailMessage.To.Add(

"
收件人邮件列表
"
);
mailMessage.CC.Add(

"
抄送人邮件列表
"
);
mailMessage.Subject

=
subject;
AlternateView htmlBody

=
AlternateView.CreateAlternateViewFromString(content,
null
,
"
text/html
"
);
LinkedResource lrImage

=

new
LinkedResource(
"
test.jpg
"
,
"
image/gif
"
);
lrImage.ContentId

=

"
test
"
;
htmlBody.LinkedResources.Add(lrImage);
mailMessage.AlternateViews.Add(htmlBody);
SmtpClient.Send(mailMessage);

其实对于调用SmtpClient发送嵌入图片文件的操作,网上已经有不少文章介绍了,本人在寻找过程中,发现几篇不错的文章,不敢独享,分享出来大家学习,下面提供几篇介绍这方面知识的文章:

.NET C# 异步发送 要求回执 嵌入图片资源 自定义邮件头 失败通知 html/文本双视图 支持 notes 的邮件

.NET C# 发送邮件内容嵌入图片


Sending Emails in .NET with the System.Net.Mail Namespace

C#发送Email邮件三种方法的总结(转载)

C#用于Windows程序的HTML编辑器
(
推荐,附源码的)

以上都是基于SmtpClient类的实现,没有发现有关ESMTP实现,即通过TCP Scoket进行发送嵌入图片文件的邮件的方法。

其实在文章"
c#发送需要smtp认证的邮件
"(
http://www.legalsoft.com.cn/docs/docs/17/577.html
) 中,已经很详细介绍了如何实现使用ESMTP协议与服务器之间的交互了,其中还包括了发送邮件附件,算是比较详细的ESMTP实现了。

其实实现发送ESMTP的嵌入图片,和发送附件方式有点类似了。

1、首先你需要编写一个函数,用来解析发送的HTML内容,遇到有本地图片的,将文件路径替换为Cid:***格式,并将文件写入内存的Stream中备用,示例代码如下所示。


private
Hashtable EmbedList
=

new
Hashtable();
//
widened scope for MatchEvaluator


private

string
FixupReferences(
string
rawPayload,
ref
StringBuilder extras,
string
boundaryString)
{

//
Build a symbol table to avoid redundant embedding.


Regex imgRE, linkRE, hrefRE;
MatchCollection imgMatches;

string
imgMatchExpression
=

"
(?:img[^>]+src\\s*=\\s*(?:\
"
(
?<
1
>
[
^
\
"
]*)\
"
|
(
?<
1
>
\\S
+
))
|
url\\([
'
\"](?<1>[^
'
\
"
]*)['\
"
]\\))
"
;


imgRE
=

new
Regex(imgMatchExpression, RegexOptions.IgnoreCase
|
RegexOptions.Compiled);

string
linkMatchExpression
=

"
<\\s*link[^>]+href\\s*=\\s*(?:\
"
(
?<
1
>
[
^
\
"
]*)\
"
|
(
?<
1
>
\\S
+
))[
^>
]
*>
"
;


linkRE
=

new
Regex(linkMatchExpression, RegexOptions.IgnoreCase
|
RegexOptions.Compiled);

//
this one's for fixup of relative urls in anchors



string
refMatchExpression
=

"
href\\s*=\\s*(?:['\
"
](
?<
1
>
[
^
\
"
]*)['\
"
]
|
(
?<
1
>
\\S
+
))
"
;


hrefRE
=

new
Regex(refMatchExpression, RegexOptions.IgnoreCase
|
RegexOptions.Compiled);

imgMatches

=
imgRE.Matches(rawPayload);

//
translation to a Hashtable weeds out redundant references



foreach
(Match m
in
imgMatches)
{

if
(
!
EmbedList.ContainsKey(m.Groups[
1
].Value))
{
EmbedList.Add(m.Groups[

1
].Value, Guid.NewGuid());
}
}


//
Prepare embedded data


extras.Length
=

0
;

string
contentType;
ArrayList embeddees

=

new
ArrayList(EmbedList.Keys);

foreach
(
string
embeddee
in
embeddees)
{
contentType

=
embeddee.Substring(embeddee.LastIndexOf(
"
.
"
)
+

1
).ToLower();
extras.AppendFormat(boundaryString);

if
(contentType.Equals(
"
jpg
"
)) contentType
=

"
jpeg
"
;

switch
(contentType)
{

case

"
jpeg
"
:

case

"
gif
"
:

case

"
png
"
:

case

"
bmp
"
:
extras.AppendFormat(

"
Content-Type: image/{0}; charset=\
"
iso
-
8859
-
1
\
"
\r\n
"
, contentType);
extras.Append(

"
Content-Transfer-Encoding: base64\r\n
"
);
extras.Append(

"
Content-Disposition: inline\r\n
"
);
extras.AppendFormat(

"
Content-ID: <{0}>\r\n\r\n
"
, EmbedList[embeddee]);
extras.Append(GetDataAsBase64(embeddee));
extras.Append(

"
\r\n
"
);

break
;
}
}

//
Fixups for references to items now embedded


rawPayload
=
imgRE.Replace(rawPayload,
new
MatchEvaluator(_fixup));

return
rawPayload;
}

private

string
_fixup(Match m)
{

string
replaceThis
=
m.Groups[
1
].Value;

string
withThis
=

string
.Format(
"
cid:{0}
"
, EmbedList[replaceThis]);

return
m.Value.Replace(replaceThis, withThis);
}

然后你在和服务器交互的时候,就可以通过这个函数,获取解析后的HTML文件内容和图片流,对其进行操作即可。


//
判断信件格式是否html



if
(Html)
{
SendBufferstr

+=

"
Content-Type: text/html;
"

+
enter;
}

else

{
SendBufferstr

+=

"
Content-Type: text/plain;
"

+
enter;
}

//
编码信息



if
(Charset
==

""
)
{
SendBufferstr

+=

"
charset=\
"
iso
-
8859
-
1
\
""

+
enter;
}

else

{
SendBufferstr

+=

"
charset=\
""
+ Charset.ToLower() +
"
\
""

+
enter;
}
SendBufferstr

+=

"
Content-Transfer-Encoding: base64
"

+
enter;

StringBuilder extras

=

new
StringBuilder();

string
extrasBoundary
=

"
--
"

+
boundary
+
enter;

string
newBodyHtml
=
FixupReferences(
this
.Body,
ref
extras, extrasBoundary);
SendBufferstr

+=
enter
+
enter;
SendBufferstr

+=
B64StrLine(Base64Encode(newBodyHtml))
+
enter;

SendBufferstr

+=
enter
+

"
--
"

+
boundary1
+

"
--
"

+
enter
+
enter;

SendBufferstr
+=
extras.ToString();

下面是一份图片邮件的例子: