SPO Azure开发之-Storage

说起Azure的storage, 还得从最早那几个项目开始,原来有个academy的迁移和online直播的东西,大概有64个TB的视频数据需要挪到云端,原来都是丢在local的集群上的,于是得写个工具把这个东西丢过去。然后发现是个网络带宽和磁盘读写的体力活。于是一阵捣腾,把Azure storage里面能用的feature都给用了一遍。
Azure的存储提供三类结构:
Table: 支持异构化的数据表操作,主要用来存储表类型数据,个人觉得最大的优势还是在于支持异构数据项目,但是在习惯redis之类的key/value数据库以后,你会发现其优势也不是十分明显,当然,azure的table是以集群的方式出现的,可靠性可以管到99.98%,三个九,还是比较牛X的。
Queue:这一块也是比较成熟的分布式队列系统了,常用的队列操作,以及队列内key时间的控制都已经比较成熟,在大量并发获取key时,我们得保证在同一时间只有一个worker能拿到当前的key,并且如果这个item被搞定,那就需要清掉这条数据,如果没有,那么这个key需要自动在一段时间以后再出来,让worker继续来进行操作。
Blob:这是微软自己的一套分布式的存储结构,后台的代码没有看到过,但是大家用Azure VM存储,以及各种镜像备份都依赖于他,同样,在media Service当中,也是借助于blob的存储来完成媒体编码的转化,这里需要关注的是,blob->container>source。在第三个层级,是没有目录的概念的,这里可以通过资源的path来模拟层级结构,但是在内部实际上是没有文件夹之类的概念的。当然,对于blob里面的每一个container,你都可以把他看作是一个文件夹。
storage-concepts
来个全家福。
下面借助于本身这个用来迁移的工具,来说明Azure里面storage的一些简单应用场景。
1. 由于TB级别数据量对单条网络带宽的占用较大,实际上网络IO已经成为瓶颈,于是得分多路来用。
2. 对于转码的要求,在media Service当中有最大job的限制,这里就需要引入queue的帮助
3. 整体是分布式架构,程序得丢到多个机器上面一起跑。
大致思路是这样的,每个程序从自己的那份table当中,获取需要迁移到blob里面的内容列表,进行校验,列表此时就可以借助Azure的table来完成,当然,部分日志记录也是利用Azure的table来完成的。然后集中丢入计划压入到queue中,然后用work机器来完成上传,其实我们这块最早的源列表是在SharePoint上面的,轻松用CSOM搞定。每个work机器开三个线程,来负责数据传输的工作,其实由于网络带宽的缘故,本来想多开几个线程的,但是于效率无意。这里其实第一步到blob以后,还需要监控其调用media service转码的过程。于是超时,重试,以及分布式队列的策略需要自己用code进行控制了。

SPO Azure开发之-Auth

在最早使用SharePoint07的时候,认证还大多基于membership以及windows.而且那时候的SharePoint自带sso模块,也只是模拟用单一用户登录而已。在进入了SharePoint2010以后,由于identity foundation的逐渐成熟,claims based authentication的基于声明的认证方式,也进入到SP的标准模块当中。所以说这个东西并不是啥子新的东西,很早就有了。对于现在O365来说,一个tenant里面的基本上所有的认证都是通过这种方式来实现的。
话说这种方式为啥好呢,其实要从诸多的异构系统说起,如果有过做多系统单点登录的经验,你就不难了解这一套认证的设计思路了。最早我们做单点登录,无非以下几种方式。
1。模拟登陆
2。统一到一套认证中
3。对码跳转
由于在不同的系统当中,都有其本系统独立的人员权限和组。一般单点很难把所有不同的系统统一起来,如果采用唯一账号模拟登陆,那跳转后的系统权限也只能是这一个账号的所有拥有的内容。当然你也可以改造各个系统,使得他们的认证都从用同一套用户和认证,这样不可避免的要对各种系统进行改造了。如果系统各式各样,改造起来估计能头大致死。如果想改造得少一点,其间还得做不同用户在不同系统当中的映射关联,比如王小五在A系统里叫wangxiaowu,但是他在B系统里面就叫wanxiaowu了。如果你说他们没区别,ok. 你眼睛有点花了。
所以很多同志们就拿出了一种折中的解决办法,以一套相对统一的标准来解决这个问题。办法如下图:
2376.image3_5F00_thumb_5F00_56056FFD
其原理就是轻度改造现有系统,让用户都到STS当中去进行验证,而STS相当于认证中心,对所有挂接在其上的系统都做WS-Trust. 而对于各个用户信息,我们用claim来声明其信息,协议一般用SAML. 用户流图如下:
Untitled picture
下面可能主要通过解析SharePoint Online的认证过程,来解析这整个过程。
1。请求SharePoint online站点,站点检查用户浏览器是否已经登录,一般是看cookies里面是否有从sts拿到的token.当然,这个token是有过期时间的
2。之后跳转到统一认证中心的认证页面里面去,让用户进行登录,用户登录完完毕以后,会返回给用户一个security token.
3。之后用户浏览器拿着这个token跑到spo的页面里面去,然后获取到对应的cookies,里面包含两个,一个是fedauth一个trfa.
image_thumb
至于代码我这里就不重复黏贴了,有兴趣的可以在这里下载下来看。

SPO Azure开发之-APP

年初听到互联星空终于在天朝取建立了azure的数据中心,其实内部测试他们好久之前就开始了。由于工作的关系,两年前就开始使用没有release的SPO和Azure开始做一些项目了(当然是非天朝版本)。总体上看微软的Azure平台还是相当牛X的,至少目前基于微软系的内容,没有比这个更牛的了,从某些网站的测试数据来看,微软的azure速度也能进入前三甲,(至少比国内那些渣渣云平台好多了,话说今天还收到国内某渣渣平台的收费短信,话说我都删除了instance了,你们是从哪里计费的。)
来个微软的全球节点图:

由于一直是在做SharePoint相关的内容,从03到现在的13,可以说SP平台走过了十多个年头,最新的版本中SharePoint提供两个,一个是on-prime版本,一个是online版本。
On-prime版本我们内部用了N个release,发现有点渣渣,限制和online几乎一样,想不出有什么公司会继续去升级这个版本,唯一的优势在于升级了很多新东西,依然能够hack自己的asp.net程序进去使用。
Online版本要舒服得多,不过是放在了o365 online 的家族中,和lync exchange office的online tenant共享license.
他的限制在于我们不能再任意的使用原来基于farm和server side object model的解决方案,只能用CSOM对site collection和ca来进行操作。内部程序版本为15.0.xxxx。这意味着,在online的app当中,只有几种方式可以host你的app。
其实最早的时候还有self-host以及auto host.今天看微软最新更新的sdk来看,这两个坑坑已经被取代了。哈哈,其实他们自己也发现,那么分不太合理。(我会说我们有些开发都分不清么)。
这里抽了几张图,应该能看出个大概。
SharePoint-hosted apps

Provider-hosted apps

Mixture-hosted apps
以下是我依据的一些个人经验,从开发的角度来区分现在的几种app的模式。这不能不从sp的发展历程开始说起
要知道在原有的版本当中,特别是2010和2007这两个SharePoint版本当中,我们的重量级的解决方案都是以wsp的包的形式,部署在farm当中的,然后再apply到对应的某一个我想使用的site collection当中.我的包当中其实本体就是一个solution。而这个solution可以包含feature,而feature可以包含我的content type,module等等。这个结构是SharePoint当中管理组件和内容的基本结构。2013同样也使用的是这样一套结构。我们开发的内容都按照这套结构来进行构建,部署和使用。这样我们才能利用SP的很多优势内容如List,library,bdc等等。
但是我们在做大型的项目时候发现,我们原来使用的这些内容,都需要部署在场的级别,而且我们的内容都是从server side object model去进行操作的, 这样当一个场,支持几十个solution,里面有几百个feature的时候,就变成了噩梦,因为所有的东西都是在服务器上跑的,什么你都能操作,什么你都能控制,对于开发性来说本来是好事,但是内容多了以后,你就会发现有很多问题,因为并不是所有的solution你都清楚,并且都很ok. 而且由于是分布式部署,我们在每次安装wsp的时候,会重启整个目标site collection前端iis. 要知道,如果我有20台前端,那会是什么一种情况。话说原来在某大型央企的一个项目,每次上包都是胆战心惊的凌晨左右时间,人工值守。如果是运维的人员,你们懂的。
所以对于场级别的解决方案以及server side object model来说,尽量使能没有就没有,于是现在的这个版本就出现了,并且都是以app的方式呈现。当然,其本质还是一个solution.
SharePoint-host: 这里就是把你的app部署到一个app的专用服务器上面,在2013版本当中有一个专门的app中心,可以来承载你的应用。
Provider-host: 这里就是把你的app部署到一个其他你想用的服务器上面,一般我们都丢到Azure里面做成一个web Role, 这个东西我会在后面的文章给大家介绍。
混合模式这里我就没有用过,所以不做评价,大家自行看微软的SDK.
说到这里大家可能会比较好奇,我随意把我的app丢到一个服务器上面,那么他们是怎么和我的farm来进行验证和交互的呢,那么我会在后面给介绍,SPO的认证模块。
由于现有的SPO以及onprime在标准的app当中无法使用SSOM来与SPO交互,所以我们这里开发app当中能走的路就只有以下几条了:
1.SharePoint Rest API
SharePoint REST service architecture

这个其实原来2010版本就有,不过现在提供的接口更多了一些,具体的一般都放在/_api里面,大家可以从sdk里面去具体学习。http://msdn.microsoft.com/en-us/library/office/fp142380(v=office.15).aspx
这里需要注意的是,对于restful的每一次请求,需要在http头当中加入 token.
HttpWebRequest endpointRequest =
(HttpWebRequest)HttpWebRequest.Create(
“http:///_api/web/lists”);
endpointRequest.Method = “GET”;
endpointRequest.Accept = “application/json;odata=verbose”;
endpointRequest.Headers.Add(“Authorization”,
“Bearer ” + accessToken);
HttpWebResponse endpointResponse =
(HttpWebResponse)endpointRequest.GetResponse();
2.SharePoint Web Service
这个在2010里面也有,也有所丰富。
3.SharePoint client side Object Model
同样2010也有,也丰富不少。
这三个方式都是和语言无关的,你可以用JS,也可以用c#来写。都ok.

ASP.NET缓存你

最近客户反映报表系统速度过慢,开始查找原因。发现后台大概七万条左右的数据,每次都在前台构造一遍,导致速度很慢,一个页面打开要20秒。所以想办法节约页面打开的时间,
从两个方面入手,优化数据查询语句,基本上达到10w条以内的,一秒内搞定。由于数据业务逻辑层有大量的处理,所以优化了数据查询后,发现还得优化逻辑层面的东西,但是为了避免重构业务逻辑层所带来的较大工作量,开始考虑优化Cache这块的内容。排除了常见的session, application, viewstate这些常用的伎俩,用稍效率点的cache。每次动态构造出来的html都特定标识一下,让后存入到页面缓存中,下次调用就直接从缓存里面取(在缓存有效的情况下)。让后发现,除了第一次打开稍慢以外,其他时间内打开都比较迅速。
ASP.NET的缓存有页面缓存和数据缓存两类,页面缓存所达到的目的就是在客户端访问量很大的情况下,对于反复的页面get请求,调用cache里面的内容对页面进行推送,就免除了反复构造页面的麻烦。实际上这里本质是缓存了的cache internel在起作用。
做法很简单:在头部加入

<%@ outputCache 
Duration="#ofseconds"
location="Any|Client|Downstream|Server|None"
VaryByControl="ControlName"
VaryByCustom="browser|customstring"
VaryByHeader="headers"
VaryByParam="Parametername"
%>

里面的参数大家可以到sdk里面进行查询。
由于我们的数据构造,会依据客户段的postback来动态的构造整合,所以,这里就需要我们采用一种方式来存储在每个状态下构造好了的数据,这样就不会每一次请求都反复的整合构造数据了,主要对象模型采用的是,Cache,这个类主要提供以下几种方法,我们主要用到的是Insert,Add,Get。这里insert 区别于add的地方在于,对于cache已有的内容,insert做覆盖处理,而add方法的话不做覆盖处理,并且也不会报错。
样例代码如下:

System.Web.Caching.CacheDependency dep1 = new System.Web.Caching.CacheDependency(Server.MapPath("XMLFile.xml"));
string[] keyDependencies2 = { "CacheItem1" };
System.Web.Caching.CacheDependency dep2 = new System.Web.Caching.CacheDependency(null, keyDependencies2);
System.Web.Caching.AggregateCacheDependency aggDep = new System.Web.Caching.AggregateCacheDependency();
aggDep.Add(dep1);
aggDep.Add(dep2);
Cache.Insert("CacheItem5", "Cached Item 5", aggDep);

这里大家可以发现插的方法有好多种,一种是带有dependency的,一种可以忽略dependency,第三个参数,只要置空就行,这里依赖的概念,就是说利用文件,或者数据库的变动,从而达到改变缓存的目的,同样,这里还可以指定一个委托,当你的cache无效时,调用你的方法,来达到比如缓存无效了给予提示等功能。至于设定缓存时间和超时这几个参数,相对就比较简单,这里就不多说了。
代码如下:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
// necessary for CacheDependency
using System.Xml;
// necessary for Xml stuff
public partial class _Default : System.Web.UI.Page
{
    public static CacheItemRemovedCallback onRemove = null;
    protected void Page_Load(object sender, EventArgs e)
    {
        CreateGridView();
    }
    private void CreateGridView()
    {
        DataSet dsGrid; dsGrid = (DataSet)Cache["GridViewDataSet"];
        onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
        if (dsGrid == null)
        {
            dsGrid = GetDataSet();
            string[] fileDependsArray = { Server.MapPath("Northwind.xml") };
            string[] cacheDependsArray = { "Depend0", "Depend1", "Depend2" };
            CacheDependency cacheDepends = new CacheDependency(fileDependsArray, cacheDependsArray);
            Cache.Insert("GridViewDataSet", dsGrid, cacheDepends, DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration, CacheItemPriority.Default, onRemove);
            lblMessage.Text = "Data from XML file.";
        }
        else
        {
            lblMessage.Text = "Data from cache.";
        }
        gv.DataSource = dsGrid.Tables[0]; gv.DataBind();
    }
    private DataSet GetDataSet()
    {
        DataSet dsData = new DataSet();
        XmlDataDocument doc = new XmlDataDocument();
        doc.DataSet.ReadXml(Server.MapPath("Northwind.xml"));
        dsData = doc.DataSet;
        return dsData;
    }
    public void RemovedCallback(string cacheKey, Object cacheObject, CacheItemRemovedReason reasonToRemove)
    {
        WriteFile("Cache removed for following reason: " + reasonToRemove.ToString());
    }
    private void WriteFile(string strText)
    {
        System.IO.StreamWriter writer = new System.IO.StreamWriter(@"C://test.txt", true);
        string str; str = DateTime.Now.ToString() + " " + strText; writer.WriteLine(str); writer.Close();
    }
    protected void btnClear_Click(object sender, EventArgs e)
    {
        Cache.Remove("GridViewDataSet"); CreateGridView();
    }
    protected void btnInit_Click(object sender, EventArgs e)
    {
        // Initialize caches to depend on.
        Cache["Depend0"] = "This is the first dependency.";
        Cache["Depend1"] = "This is the 2nd dependency.";
        Cache["Depend2"] = "This is the 3rd dependency.";
    }
    protected void btnKey0_Click(object sender, EventArgs e)
    {
        Cache["Depend0"] = "This is a changed first dependency.";
    }
}

在文章的最后,截取了别人写的一个读书心得,对asp的处理过程做了一个简要的介绍,可以加速大家对cache的理解。
附:
IIS根据扩展名.aspx启动 aspnet_isapi.dll,
aspnet_isapi.dll转交到 aspnet_wp.exe,
wp是worker process的简称;
wp会根据情况创建一个新的AppDomain,或重用已有的AppDomain;
每个IIS有多个AppDomain。
在该AppDomain里,wp将要求转交到 ISAPIRuntime对象,该对象从ISAPI包中解构成必要的信息,调用 HttpRuntime.ProcessRequest来处理用户要求。HttpRuntime在每个AppDomain中是唯一的。HttpRuntime会创建 HttpContext和Cache两个对象,每个HttpContext管理着一个HttpSession;注意每个来访者都会对应着一个HttpContext,也意味着每个来访者都有自己的HttpSession。HttpRuntime.ProcessRequest除了创建HttpContext和Cache,还通过请求HttpApplicationFactory,创建HttpApplication:HttpApplicationFactory先解译目录中的Global.asax,然后加载Global.dll,合并两者生成Ghost Application Class,编译此class生成HttpApplication,实例句柄返回到HttpRuntime。
在生成Ghost Application Class过程中,HttpApplicationFactory具备pooling的功能,即尽可能重用HttpApplication,除非需要新建或文件更改(如Global.asax,Global.dll);此外,此过程中的Parser也是pooling的,即如果相关文件不变化,是不会进行重新编译的。HttpApplication在初始化的时候,相关的HttpModules会被加载,如Session、Authentication等模块,每个HttpModule都实现了IHttpModule接口。interface IHttpModule { void Init (HttpApplication context); void Dispose();} Init在模块被加载时被调用;Dispose在模块被释放时被调用。
注意,Modules在HttpApplication初始化期间被加载(Init),所以可以在Init中拦截一些HttpApplication后续的功能,比如SessionState Module可以拦截HttpApplication.BeginRequest/EndRequest等事件。HttpRuntime在创建HttpApplication后,调用HttpApplication.ProcessRequest,此方法将执行权转交到HttpHandler对象。
注意,在AppDomain中,HttpRuntime是唯一的,但不同的来访者有不同的HttpApplication,但由于HttpApplicationFactory的pooling作用,一些已有的HttpApplication会被重用。为什么HttpApplication可以共享Cache和Application State?
注意,Cache和Application State是由httpRuntime管理,而HttpRuntime是唯一的。每个HttpApplication对应一组HttpModules,而不是共享。但对于session,每个session的储存是由CacheInternal对象来组织,多个SessionState Module实际是访问同一个底层的CacheInternal。而一个HttpRuntime是管理着一个CacheInternal,因此Session不会受到多个HttpApplication的影响。
HttpHandler是根据mechine.config(Framework目录下)和web.config (在虚拟目录下)指定的HttpHandler来创建。.aspx对应着 System.Web.UI.PageHandlerFactory类,先创建一个PageParser对象 (实际上还有UserControlParser,PageParser也有Cache的机制,检查HttpRuntime中的Cache对象,如果已有,会直接使用已有对象来创建Page实现,而不会重复),该对象找到对应的.aspx文件,加载并解译,生成ControlBuilder对象群,然后这些对象群交到PageCompiler进行编译,生成Ghost Page Class。生成page后,HttpApplication调用 BeginProcessRequest进行请求的处理(Page本身实现了IHttpAsyncHandler接口)。Page在接到请求后,首先判断是否 Post-back模式 (查询有无__VIEWSTATE和__EVENTTARGET);决定模式后,page进行Initialize Control (调用InitRecursive,实质是迭代执行 Control.OnInit),而后
1. 如果是Post-back,则运行Post-back机制,此时Page先调用 LoadPageViewStates加载view States数据(实质是迭代调用各control的LoadViewStates),即Page还原到上一次执行后的状态,此后,调用ProcessPostData,加载各控件的Post data (注意控件如果要参与ProcessPostData,必须要实现IPostBackDataHandler接口,并在page对象中RegisterRequiresPostBack),接下来RaiseChangeEvent,发出OnChange事件,以及处理Post-back事件,如果控件已经RegisterRequiresRaiseEvent的话,此时将调用RaisePostBackEvent,否则根据__EVENTTARGET执行 Post-back。
2. 不管是否Post-back,此时开始load各个 controls,实质是调用各控件的OnLoad,此后调用OnPreRend,(注意html和javascript的render时就不能再修改,所以如果要修改,一定要在此时机进行),再调用SaveViewState将控件当前状态存入__VIEWSTATE,最后调用RenderControl将HTML写出,完成整个Page。 注意几个事件的次序,Init事件发生时所有控件处在默认值,Load事件发生前控件已经还原到先前的状态。此后PreRender也是个很重要的事件,需要注意。 在处理完用户请求后,HttpApplication会根据IHttpHandler.IsReusable决定是否重用或释放。

ASPX+WF+WSS 3.0 开发纪要

搁置了很长的一段时间,终于在2010年来继续原来的那一段没写完的东西。
原来说了INFOPATH+WF+MOSS的开发套路,现在说一下WSS下面的ASPX+WF的开发套路,其实他们本质都是采用地球人都知道的ASP.NET+WF+WSS,用ASPX的页面处理,而不用INFOPATH有很多好处,
第一:不用购买比较贵的而且不怎么实用的MOSS,其实做MOSS开发快两年了,从07就开始捣鼓,多多少少也接触了一些MOSS的东西。但就目前的MOSS的企业应用来说,用得最多的还是比较常见的一些WSS的基本功能,对于在MOSS里面扩展基于WSS的强大的功能,在商业应用上面,个人用得较少(可能是个人的工作对象比较单一,比较肤浅)。本身基于MOSS的扩展开发接口比较复杂,特别是在特殊的专业领域上面进行开发,例如采用BDC,SEARCH,EXCEL SERVICE等MOSS引以为傲的功能点为核心,大多限制都很多,虽然有很好的构想,但是实现起来却并非那么容易,从中可以看出有一定的不成熟性。特别是对于一些专业的领域,如爬网,BI等,MOSS里面所带的只能满足轻量级别的需求(当然,微软也有自己从MOSS里面独立出来的产品来满足这些需求),如果遇上实际的生产环境,很多东西都很难实现(说白了就是做出来的玩具,好玩但是不能上前线)。虽然在SHAREPOINT从01年就开始有了,经历了那么长时间的发展,还在不断改进中。也或许是现在成熟的MOSS应用还不是很丰富的缘故吧(话说奥运网站用的是MOSS,具体用了多少不太清楚,不过MOSS的集群设计倒是很牛X,前台,场控,搜索,索引,数据,文档转换都是可分离配置扩充的)。(一扯就远了,^_^)
第二: ASPX的可操控性比INFOPATH要好得多,INFOPATH从03开始就冒出来了,07进行了诸多的改进,其中就是基于OPENXML的OFFICE的整个系列的改进,所以大家可以发现07有了很多令人激动的功能(不过常用的那些还是没变,其他花里胡哨的也用得不多)。做快速的开发,INFOPATH可以更加的快捷,因为本身表单提供了一堆的规则,而且还提供了后台的编码。有一定的可扩展性,能满足常见的业务需求,但是如果要求丰富,那就比较困难了,比如加入特效动态,完美的个性化展现,INFOPATH就显得力不从心。但是ASPX的页面就不存在这种情况,只要满足基本的WF+WSS的流程需要,ASPX就可以任你玩耍,当然,代价就是稍微麻烦。
第三,用得不是很多,所以目前还没有发现,以后再多做几个估计就会又有所心得了。下面开始转入正题吧,这里就不多说什么部署的配置的步骤了,网上有中英文的step by step的四合一步骤,大家可以下载了来看。这里只抽出一些个人认为比较特殊的要点和心得说一下。
原来说了INFOPATH+WF+MOSS的开发套路,现在说一下WSS下面的ASPX+WF的开发套路,其实他们本质都是采用地球人都知道的ASP.NET+WF+WSS,用ASPX的页面处理,而不用INFOPATH有很多好处,
第一:不用购买比较贵的而且不怎么实用的MOSS,其实做MOSS开发快两年了,从07就开始捣鼓,多多少少也接触了一些MOSS的东西。但就目前的MOSS的企业应用来说,用得最多的还是比较常见的一些WSS的基本功能,对于在MOSS里面扩展基于WSS的强大的功能,在商业应用上面,个人用得较少(可能是个人的工作对象比较单一,比较肤浅)。
本身基于MOSS的扩展开发接口比较复杂,特别是在特殊的专业领域上面进行开发,例如采用BDC,SEARCH,EXCEL SERVICE等MOSS引以为傲的功能点为核心,大多限制都很多,虽然有很好的构想,但是实现起来却并非那么容易,从中可以看出有一定的不成熟性。特别是对于一些专业的领域,如爬网,BI等,MOSS里面所带的只能满足轻量级别的需求(当然,微软也有自己从MOSS里面独立出来的产品来满足这些需求),如果遇上实际的生产环境,很多东西都很难实现(说白了就是做出来的玩具,好玩但是不能上前线)。虽然在SHAREPOINT从01年就开始有了,经历了那么长时间的发展,还在不断改进中。也或许是现在成熟的MOSS应用还不是很丰富的缘故吧(话说奥运网站用的是MOSS,具体用了多少不太清楚,不过MOSS的集群设计倒是很牛X,前台,场控,搜索,索引,数据,文档转换都是可分离配置扩充的)。(一扯就远了,^_^)
第二: ASPX的可操控性比INFOPATH要好得多,INFOPATH从03开始就冒出来了,07进行了诸多的改进,其中就是基于OPENXML的OFFICE的整个系列的改进,所以大家可以发现07有了很多令人激动的功能(不过常用的那些还是没变,其他花里胡哨的也用得不多)。做快速的开发,INFOPATH可以更加的快捷,因为本身表单提供了一堆的规则,而且还提供了后台的编码。有一定的可扩展性,能满足常见的业务需求,但是如果要求丰富,那就比较困难了,比如加入特效动态,完美的个性化展现,INFOPATH就显得力不从心。但是ASPX的页面就不存在这种情况,只要满足基本的WF+WSS的流程需要,ASPX就可以任你玩耍,当然,代价就是稍微麻烦。
第三,用得不是很多,所以目前还没有发现,以后再多做几个估计就会又有所心得了。
下面开始转入正题吧,这里就不多说什么部署的配置的步骤了,网上有中英文的step by step的四合一步骤,大家可以下载了来看。这里只抽出一些个人认为比较特殊的要点和心得说一下。
要点一,如何关联ASPX页面到WF
自己一开始看demo的时候感觉很神奇,原有的INFOPATH表单是通过在FEATURE里面的WORKFLOW描述,关联INFAOPTH表单和WF,但是ASPX的WORKFLOW里面并没有采用这种方式,当然,同样也有描述,说白了,你要告诉WSS你的东西是什么,你就得写一个XML的描述文件,这个应该是和WSS对话的最基本功,也就是说,你要玩WSS,那你就得了解WSS说的“语言”。这里的关联采用的内容类型描述。
<ContentType ID=”0x0108010031c06735582e4f0faa61123f758b0xxx”
Name=”My Task Content Type”
Group=”MyContentTypes”
Description=” My Content Type”
Version=”0″>
<FieldRefs>
</FieldRefs>
<XmlDocuments>
<XmlDocument NamespaceURI=”http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url”>
<FormUrls xmlns=”http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url”>
<Display>_layouts/MyRequest/ MyRequestTaskPage.aspx</Display>
<Edit>_layouts/MyRequest/ MyRequestTaskPage.aspx</Edit>
</FormUrls>
</XmlDocument>
</XmlDocuments>
</ContentType>
要点二,如何关联ASPX页面到WSS
对于aspx的页面,要当做wss里面的初始表单,任务表单,以及assoication表单,那就得借助于内容类型,来捆绑你的aspx页面,也就是说。你利用内容类型的定义,来关联你所定义的内容类型的打开,显示,编辑时候的页面,而这个页面,就是你的aspx页面。下面可以看如下这个内容类型的描述。下面的xml文件定义了一个内容类型,利用XmlDocument来关联这个内容类型的display,edit页面。
之后在workflow的定义当中,把TaskListContentTypeId 设置为你刚定义的内容类型的id,这样在打开task的时候,就会自动调用你的内容类型了(这里我把最后三位写成xxx了,大家可以利用guid的生成工具进行生成)。也就是说,这里是把你自己定义的一个内容类型,作为task的内容类型。
要点三,如何让WSS里面的WF,和WSS里面的ASPX页面 相互 通信。
要想让aspx页面,能够和wf之间进行通信和关联,需要做以下的几部工作
1,要让你的aspx页面知道当前流程的状态,以及相关listItem项目,具体说来就是定义一堆的用户存储状态的变量。
如下:

#region Interact Data
protected SPListItem _objTaskItem;      // Workflow associated item
protected SPList _objTaskList;          // Task list object
protected SPWorkflow _objWorkflow;      // Workflow object
protected Hashtable _htTaskProperties; // Task properties
protected String _strWorkflowDesc;      // Description of workflow
protected String formView = "ReadOnly"; //Form view status
protected String status = String.Empty; //Approver Status
//protected String isGroup = String.Empty; //Is Approver Group or not
protected String isDelegated = String.Empty; //Delegation condition
protected String principal = String.Empty; //The Principal who is being delegated
protected String acted = string.Empty; //Approver action
protected SPWorkflowActivationProperties workflowProperties;
#endregion

之后初始化你的变量,

_strWorkflowDesc = "Approve WorkFlow";
String strListID = Request.QueryString["List"];
// Get workflow task
try
{
if (strListID != null)
{
_objTaskList = Web.Lists[new Guid(strListID)];
}
_objTaskItem = _objTaskList.GetItemById(Convert.ToInt32(Request.Params["ID"]));
}
catch (Exception ex)
{
throw new SPException(ex.Message + "rnFailed to get task list");
}
// Get the associated workflow
try
{
// Get task properties
_htTaskProperties = SPWorkflowTask.GetExtendedPropertiesAsHashtable(_objTaskItem);
// Get associated workflow
Guid guidWorkflowId = new Guid(_htTaskProperties["workflowInstanceID"].ToString());
formView = _htTaskProperties["formView"].ToString();
status = _htTaskProperties["status"].ToString();
isDelegated = _htTaskProperties[Constants.IsDelegetedKey].ToString();
principal = _htTaskProperties[Constants.PrincipalKey].ToString();
_objWorkflow = new SPWorkflow(Web, guidWorkflowId);

2, 你的页面最好继承于如下类,Microsoft.SharePoint.WebControls.LayoutsPageBase 这样你就可以比较方便的利用moss的母版页来进行页面布局的控制,并且让你的页面都保持统一的风格。
3,数据的交互主要采用两种方式,一种是借助于流程的ExtendedProperties,一种是直接借助于list里面项来进行。在利用表单的properties时,大家可以看到,这个属性表是一个hastable,通过键可以查找到值。同样在流程当中和你的aspx页面当中都存在。其次是你的list的列,在流程和表单当中都可以取到当前流程所关联的列表的项目,以及里面列的值。
这里如果需要交互的话,还可以采用诸如数据库之类三方存储介质,在存储你的交互数据。不过必须保证数据的同步和一致,特别是在同时修改和提交的时候,要避免死锁。
要点四,XmlFormView。如果你想让你的aspx表单,可以看到你的infopath起始表单,那么你就可以采用这里说到的这个控件,命名空间为:Microsoft.Office.InfoPath.Server.Controls
这个控件有几个事件和属性需要注意。如下:

xmlfvTaskItem.Initialize += new EventHandler(xmlfvTaskItem_Initialize);
xmlfvTaskItem.Close += new EventHandler(xmlfvTaskItem_Close);
xmlfvTaskItem.SubmitToHost += new EventHandler(xmlfvTaskItem_SubmitToHost);
xmlfvTaskItem.XmlLocation = _objWorkflow.ParentItem.Web.Url + "/" + _objWorkflow.ParentItem.Url;

分别为initialize,close,submitToHost,也就是说,你在你的aspx里面可以嵌入你的infopath表单,并且在你的aspx页面里面,可以提交你的infopath表单。

protected void xmlfvTaskItem_Initialize(object sender, EventArgs e)
{
XPathNavigator xNavMain = xmlfvTaskItem.XmlForm.MainDataSource.CreateNavigator();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xNavMain.InnerXml);
XmlNamespaceManager xNameSpace = new XmlNamespaceManager(new NameTable());
xNameSpace.AddNamespace("my", xmlDocument.DocumentElement.Attributes["xmlns:my"].Value);
XPathNavigator viewStatus = xNavMain.SelectSingleNode(@"/my:MyRequest/my:formInfo/my:viewStatus", xNameSpace);
viewStatus.SetValue(formView);
}
protected void xmlfvTaskItem_Close(object sender, EventArgs e)
{
// Redirect to the last viewed page when the InfoPath form is closed
Microsoft.SharePoint.Utilities.SPUtility.Redirect(_objWorkflow.ParentWeb.Url, SPRedirectFlags.UseSource, HttpContext,Current);
}
protected void xmlfvTaskItem_SubmitToHost(object sender,SubmitToHostEventArgs e)
{
XPathNavigator xNavMain =xmlfvTaskItem.XmlForm.MainDataSource.CreateNavigator();
XmlNamespaceManager xNameSpace =new XmlNamespaceManager(new NameTable());
xNameSpace.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2006-04-20T16:26:21");
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xNavMain.InnerXml);
xNameSpace.AddNamespace("my", xmlDocument.DocumentElement.Attributes["xmlns:my"].Value);
XPathNavigator viewStatus = xNavMain.SelectSingleNode(@"/my:MyRequest/my:formInfo/my:viewStatus", xNameSpace);
viewStatus.SetValue("ReadOnly");
XPathNavigator act = xNavMain.SelectSingleNode(@"/my:MyRequest/my:formInfo/my:act", xNameSpace);
if (act.InnerXml == "Submit")
{
SPListItem spitem = _objWorkflow.ParentItem;
System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
byte[] str = enc.GetBytes(xNavMain.InnerXml);
spitem.File.SaveBinary(str);
try
{
UpdateTaskProperties(WorkflowActionConstants.SubmittedAction);
}
catch (System.Exception ex)
{
throw new Exception("Submit failed.Details:rn" + ex.Message);
}
}
else if (act.InnerXml == "Terminated")
{
try
{
UpdateTaskProperties(WorkflowActionConstants.TaskTerminatedAction);
}
catch (System.Exception ex)
{
throw new Exception("Submit failed.Details:rn" + ex.Message);
}
}
// Redirect to the last viewed page
Microsoft.SharePoint.Utilities.SPUtility.Redirect(_objWorkflow.ParentWeb.Url, SPRedirectFlags.UseSource, HttpContext.Current);
}
private void UpdateTaskProperties(String bApproved)
{
// Update task properties
_htTaskProperties[WorkflowActionConstants.IsTaskUpdatedBySysPropertyKey] = true;
_htTaskProperties[WorkflowActionConstants.WorkflowTaskActionPropertyKey] = bApproved;
_htTaskProperties[WorkflowActionConstants.WorkflowTaskCommentsPropertyKey] = tbComments.Text;
_htTaskProperties[WorkflowActionConstants.InvolveToAccountPropertyKey] = GetApproverFromPeopleEditor();
// Update the task
// Note: Make sure the approver has write permission of the task list or else
// there'll be a security problem
SPWorkflowTask.AlterTask(_objTaskItem, _htTaskProperties, true);
}

大家可以看到,在SubmitToHost 中,我们只是反序列化出来了infoPath的表单,然后把表单里面的一些属性节进行了修改,在此同时,我们还需要对workflow里面的相应的属性值进行修改。并调用SPWorkflowTask.AlterTask对属性值进行提交。
总结下来,不论是利用INFOPATH还是ASPX,在WSS和MOSS当中开发流程,必不可少的应该是对XML的查找修改删除,对基本WF引擎的了解,虽然MOSS在WF的基础上封装了一些适用于WSS的ACTIVITY,但是基础的流程流转的引擎仍然没有改变,并且依然具有强大的可定制性。借助于WF引擎所带来的诸多优点,让日常办公中,电子化的流程得以舒坦的运行。通过一些定制化的开发,以及合理的版本控制,无缝的升级。虽然开发过程较为繁琐,开发步骤也比较另类,但是不失为目前流程化比较成熟的方案了。

Crystal Report 开发技术简要

最近上了一个新的BI系统,把原有的Crystal Report X的,用asp1.0写的十分古老的程序做了一个提升和改进(其实本来还要建CUBE和DW的),用asp2.0完全重写了系统,并且加入了和Moss整合的部分,采用的开发包由原来X升级到了,XI的开发包。   提到Crystal Report开发,用到对象模型的地方,大部分还是在系统的整合和一些定制化的报表查询上。其实大部分的业务逻辑和展现,都已经分离到了存储过程和报表的设计上面。用Designer设计报表,如果是简单的应用的话,基本上可以不采用代码。
Crystal Report的报表可以有两种形式进行存在:
1,嵌入式的报表:说白了,就是独立的rpt文件,与项目存储在一起,此报表可以存储在文档库当中,也可以存放在物理路径里面,其实里面放置的就是一堆的xml描述,用于描述报表,借助于报表对象模型对其进行展现时,直接加载这个rpt文件就可以了。
2,非嵌入式报表:这种类型的报表一般是存储在服务器上的,通常借助于BOE的服务器,或者是CR的服务器,报表在物理的磁盘或者文件夹下,并不存储具体的rpt文件,报表实际都是存储在报表服务器的数据库当中的。
两者各有优点和缺点,对于轻量级别的小应用,或者是没有必要买BO那一大套服务和程序的项目来说,用嵌入式报表就能很好的满足业务的需要了。只是报表的维护的修改不是十分容易,而且对后台数据的访问,在代码中也得十分注意,对整个项目的报表的规划和要求比较高。非嵌入式的报表,一般应用于大型的BI项目中,由于独立的报表服务器提供了一整套不同级别要求的方案,所以在应用程序项目中,只是需要将发布到报表服务器上面的东西抓取过来进行显示而已,不过仍旧有需要注意的地方,比如变量,参数,子报表,以及和服务器交互的一些维护。后面代码里面可能会有所涉猎。
以下是官方的一些晦涩的翻译,个人觉得不是很好理解,说得很扯(估计是我个人理解能力的问题),如果有需要的可以看下,提供参考。
嵌入式报表在编译时直接嵌入项目程序集。非嵌入式报表存储在单独的目录中。将非嵌入式报表放在单独的目录中使部署变得复杂,但是可以轻松地重新分发更新的报表:只有一个或多个报表需要更新,而不必重新编译然后重新分发整个应用程序。对于RAS,英文解释是Report Application Server,个人理解是从属于BO下面的一个轻量级别的解决方案,在RAS包含两个东东,一个是Server,一个开发的SDK,Server负责处理和解析报表,并把报表返回给View。
RAS又分为两种类别,一种是托管的,一种是非托管的,托管的RAS和非托管的RAS区别,在个人反复看了中文SDK以后,仍旧没有任何思路和具体的认识(理解能力再一次体现),个人从开发角度来看,托管的RAS, Server是归属于BO的整个framework模型下来进行管理和控制的,而非托管的RAS,Server是独立于BO的framework来进行运行的,从对象模型上来看,基本没有什么差异,只不过非托管的RAS原来用的是ReportClientDocument对象来承载报表而已,而不是ReportDocument而已,为了避免自己的理解误导大家,下面还是附上SDK里面的中文翻译做参考吧。
非托管 RAS:非托管 RAS 提供了用于处理、查看或修改 Crystal Reports 文件的基本 BusinessObjects Enterprise 服务。RAS standalone
托管 RAS:托管 RAS 是 Enterprise 结构中的服务器之一,它处理报表并向用户提供创建和修改报表的报表功能。BusinessObjects Enterprise 内部的 RAS。
说完了Crystal Report开发当中那么逻辑和层面上的废话,下面切入正题开始说具体的开发,话说Crystal Report在03年以前就有支持java平台,asp, JS下的开发库了,近来虽然升级到了2008,命名空间的版本也由原来的10.上升到了12,不过本质的东西感觉仍然没有变化多少,后台封装的COM仍旧没有很大的改善。在.NET层面上的,重写了ReportDocument类,把它变成了ReportClientDocument的一个子集。在定制化开发报表应用程序上,主要应用到的对象模型有一下几个。
CrystalReportViewer 控件中的显示功能。
ReportDocument 对象模型,一种范围更广的对象模型。
ReportClientDocument 对象模型,适用于高级报表创建和修改。
InfoStore 用于在服务端检索对象和内容。
下面给一张对象模型的大图吧(估计没多少人会去仔细研究的,不过仔细研究下还是比较有用的)。 13029027231309
在看完了关键核心的ReportDocument类图以后,就可以开始我们的编码工作了,因为后续的大部分工作都是围绕着这个类来展开的,下面吧请求分为两类来进行处理,一种是处理嵌入式的报表,另外一种是处理非嵌入式的报表。 1, 嵌入式报表的处理 对于嵌入式报表的处理,其实可以直接用ReportDocument对象模型直接Load进来,但是你会发现在报表请求数据库的时候,会要求你重新输入数据的验证信息,所以这里你会发现在你的报表里面是没有存储数据库的登陆验证信息的,或者说你当前请求的数据库验证信息已经失效,这就要求你编码把数据库的登陆信息加入进去,具体的请看下面的代码,其实里面需要多加一些数据库密码加密解密的信息。当然,如果你不想这样显示的设定密码的话,如果你用集成化的身份认证的话,可以把iis的运行池的帐户添加到数据库的访问者里面。当然,这里设定登陆信息只采用了一种方法,其实还有一些其他的方式,在对象模型ReportDocument中,有兴趣的可以去研究刚才给出的那张大图。里面还有通过DataSource的方式来设定数据库的访问。

public void LoadReportFromUrl(string reportUrl, string dbServerName, string dbDBName, string dbUserID, string dbPassword, ref ReportDocument report)
{
    report.Load(reportUrl);
    ConnectionInfo conInfo = new ConnectionInfo();
    conInfo.ServerName = dbServerName;
    conInfo.DatabaseName = dbDBName;
    conInfo.UserID = dbUserID;
    conInfo.Password = dbPassword;
    foreach (CrystalDecisions.CrystalReports.Engine.Table table in report.Database.Tables)
    {
        TableLogOnInfo info = new TableLogOnInfo();
        info = table.LogOnInfo;
        info.ConnectionInfo = conInfo;
        table.ApplyLogOnInfo(info);
    }
}

2, 非嵌入式报表的处理对于非嵌入式的报表,处理起来就比较繁琐一些,因为现在我的程序都是在web下面开发,所以我这里就单说web下面的一些处理方式。你的应用程序上,要请求BOE服务器上面的报表,就需要创建一个session让后穿件一个service,之后创建一个InfoStore来读取报表上面的信息,这里的InfoStore的query是通过类似与SQL的语句进行查询的,返回的对象为InfoObject。可以用来载入到ReportDocument里面进行处理了。下面的函数里面给出的只是一个对象模型的一个调用的示意代码,如果真要用到程序中,估计还有一些细节的东西需要处理,比如说EnterpriseSession的存储,以及在query的快速定位到报表,都需要注意,一般通常的做法是吧EnterpriseSession对象存储到网站的session当中,同样,InfoStore也可以存储到站点的Session当中,这样就可以不用反复的验证和请求APP报表服务器了。代码code
 

public void LoadReportFromAppServer(string ceAppServer, string ceUserID, string ceUserPwd, string CEAuthtype, ReportDocument report)
{
    try
    {
        SessionMgr mgr = new SessionMgr();
        EnterpriseSession mySession = mgr.Logon(ceUserID, ceUserPwd, ceAppServer, CEAuthtype);
        if (mySession != null)
        {
            EnterpriseService myService = mySession.GetService("InfoStore");
            InfoStore myInfoStore = new InfoStore(myService);
            string queryStr = "Select * from CI_InfoObjects where SI_PROGID=" + "CrystalEnterprise.Report";
            InfoObjects infoObjCol = myInfoStore.Query(queryStr);
            foreach (InfoObject tempObj in infoObjCol)
            {
                //这里可以做你想做的了 report.Load(tempObj, mySession);
            }
        }
    }
    catch (Exception ex)
    {
    }
}

3, 参数的传递下面说到开发当中最常见的一个问题,就是参数的传递,大家可能发现,所有带参数的报表,都有一些自定的控件来绑定报表的参数,提供一种默认的参数选择的方式,但是在用户使用起来以后发现,采用报表默认的参数选择器,很难满足客户的查询需要。所以很多参数查询的页面需要进行一些自定义的开发。通常做法,本人还是采用传统的queryString来进行页面间参数的传递,但是从页面间的,到报表内的,就需要做一些简单的转化和编码了。在报表当中的变量,主要是通过ParameterFields来进行定义的,实际上就是域的名称和域的值,和Moss的SPField十分的类似,也是有定义和SPFieldValue,因为值可能是多个,所以这里用的是ParameterDiscreteValue离散值的这个模型在承载value。在后面的ReportDocument对象的操作中,有ParameterFields这个域来存储报表里面的所有的定义的变量域,如代码中的操作,只是在原有的基础上加入新的域值,或者是直接给在view层面上赋域的值,或者借助于ReportDocument对象的FieldDefinition来进行赋值。具体代码如下:

public void BuildCEParamter(string ceFieldName, string ceFieldValue, ReportDocument ceReport)
{
    ParameterFields paramFields = new ParameterFields();
    ParameterField pfItemYr = new ParameterField();
    pfItemYr.ParameterFieldName = ceFieldName;
    //year is Crystal Report Parameter name. ParameterDiscreteValue dcItemYr = new ParameterDiscreteValue();
    dcItemYr.Value = ceFieldValue;
    pfItemYr.CurrentValues.Add(dcItemYr);
    paramFields.Add(pfItemYr);
    //第一种
    //如果有View的话,可以直接
    crystalReportViewer1.ParameterFieldInfo = paramFields;
    //第二种
    ParameterFields reportFieldCol = ceReport.ParameterFields;
    foreach (ParameterField pfield in reportFieldCol)
    {
        if (pfield.Name == pfItemYr.Name)
        {
            pfield.CurrentValues.Add(ceFieldValue);
        }
    }
    ceReport.ParameterFields[ceFieldName].CurrentValues.Add(ceFieldValue);
    ceReport.ParameterFieldInfo.Add(paramFields);
}
//第三种
private void SetCurrentValuesForParameterField(ReportDocument reportDocument, ArrayList arrayList)
    {
        ParameterValues currentParameterValues = new ParameterValues();
        foreach (object submittedValue in arrayList)
        {
            ParameterDiscreteValue parameterDiscreteValue = new ParameterDiscreteValue();
            parameterDiscreteValue.Value = submittedValue.ToString();
            currentParameterValues.Add(parameterDiscreteValue);
        }
        ParameterFieldDefinitions parameterFieldDefinitions = reportDocument.DataDefinition.ParameterFields;
        ParameterFieldDefinition parameterFieldDefinition = parameterFieldDefinitions[PARAMETER_FIELD_NAME];
        parameterFieldDefinition.ApplyCurrentValues(currentParameterValues);
}

记得原来高中时候学写议论文,写到最后总要归纳一个中心思想的,所以养成了习惯些文章都要写一个结尾的总结(恶习啊)。写了那么粗浅的东西,总觉得目前看到的都是一些很层面上的东西,毕竟本人目前就只是停留在应用层面上,做一些企业信息化的项目,目前接触过最大的项目,也就是全国的社会保障外地委托系统。其实在技术层面上,做企业信息化更多的是一些管理和概念上的想法。很多时候都是拿现成的产品过来进行集成,或者做一些深入的开发(当然也是在SDK的基础上)。BI这块在企业内部用得很多,当然大部分国内企业还只是停留在简单的应用层面上,少数企业用得很牛B,比如路透啊,等等。

REST in net OF EVE

最近大半年一直在耍一个叫EVE的游戏,让后发现他提供很好的开放式的API,对于世界服务器来说很多来自世界各地的老外都做了不少开放性的程序,可以让大家脱离于游戏客户端而使用。所以开始研究这一套开放API的玩法。其基础的技术就是用到了很久以前就比较火爆的REST技术,其实在很久以前,REST技术就已经开始比较火爆了,各个语言和技术体系也陆续出现了不少的对应框架,其实如果要系统的了解这套体系的初衷,还得从Roy的论文开始说起。作为一个从HTTP1.1协议的编写者和libwww-perl的主要参与者,ROY对网络和WEB的理解深度,已然走在非常前沿的位置,作者以学术的观点比较了诸多不同类型的框架结构之后,综合提出了REST的网络构架,并且从不同的角度来阐述了此架构的优劣。原谅我从ROY的论文当中把这段引注拿出来,因为我也比较喜欢,没有用翻译后的中文,看原文更加有味道。我想这也许就是我们所追求的。至少在黑客帝国当中已经淋漓的体现出来了。
Each one of us has, somewhere in his heart, the dream to make a living world,  a universe. Those of us who have been trained as architects have this desire  perhaps at the very center of our lives: that one day, somewhere, somehow, we  shall build one building which is wonderful, beautiful, breathtaking, a place  where people can walk and dream for centuries.  — Christopher Alexander       
当前的互联网基础技术和框架难以满足现有的应用的要求的时候,势必会有新的体系和技术出来代替。万维网的出生,就代表了信息资源的共享和互通,为了便于信息的交互,考虑到广泛的可适用性和可扩展性,并且脱离于特定语言和特定的技术实现,原有的体系表现出了很大的局限性,从原有的web1.0,迈入web2.0时代,w3c的规范在不断的修正和更新。从自然适应的条件的角度,与结构逻辑的角度,分析现在和未来发现的态势。于是整合了一堆的观点的技术,提出了新的架构,并且对比了优劣,分析了还存在的不足。有兴趣的同志,可以研究一下ROY的论文,虽然很多地方比较学术,但是如果联系到实际态势和经验,还是能有所斩获。包括从约束,从过程:所以以下就是从论文当中摘出的相关的REST的特点和介绍。
Representational State Transfer,REST  REST改善了用户接口跨多个平台的可移植性,并且通过简化服务器组件,改善了系统的可伸缩性。最为关键的是通过分离用户接口和数据存储这两个关注点,使得不同用户终端享受相同数据成为了可能。
1. 无状态性  无状态性是在客户-服务器约束的基础上添加的又一层规范。他要求通信必须在本质上是无状态的,即从客户到服务器的每个request都必须包含理解该request所必须的所有信息。这个规范改善了系统的可见性(无状态性使得客户端和服务器端不必保存对方的详细信息,服务器只需要处理当前request,而不必了解所有的request历史),可靠性(无状态性减少了服务器从局部错误中恢复的任务量),可伸缩性(无状态性使得服务器端可以很容易的释放资源,因为服务器端不必在多个request中保存状态)。同时,这种规范的缺点也是显而易见得,由于不能将状态数据保存在服务器上的共享上下文中,因此增加了在一系列request中发送重复数据的开销,严重的降低了效率。
2. 缓存  为了改善无状态性带来的网络的低效性,我们填加了缓存约束。缓存约束允许隐式或显式地标记一个response中的数据,这样就赋予了客户端缓存response数据的功能,这样就可以为以后的request共用缓存的数据,部分或全部的消除一部分交互,增加了网络的效率。但是用于客户端缓存了信息,也就同时增加了客户端与服务器数据不一致的可能,从而降低了可靠性。  B/S架构的优点是其部署非常方便,但在用户体验方面却不是很理想。为了改善这种情况,我们引入了REST. REST在原有的架构上增加了三个新规范:统一接口,分层系统和按需代码。
3. 统一接口  REST架构风格的核心特征就是强调组件之间有一个统一的接口,这表现在REST世界里,网络上所有的事物都被抽象为资源,而REST就是通过通用的链接器接口对资源进行操作。这样设计的好处是保证系统提供的服务都是解耦的,极大的简化了系统,从而改善了系统的交互性和可重用性。并且REST针对Web的常见情况做了优化,使得REST接口被设计为可以高效的转移大粒度的超媒体数据,这也就导致了REST接口对其它的架构并不是最优的。
4. 分层系统  分层系统规则的加入提高了各种层次之间的独立性,为整个系统的复杂性设置了边界,通过封装遗留的服务,使新的服务器免受遗留客户端的影响,这也就提高了系统的可伸缩性。
5. 按需代码  REST允许对客户端功能进行扩展。比如,通过下载并执行applet或脚本形式的代码,来扩展客户端功能。但这在改善系统可扩展性的同时,也降低了可见性。所以它只是REST的一个可选的约束。
说了那么多理论和基础,最终还是要落实在代码和技术实现上的,一直主修微软体系的我,也就从微软的技术实现开始下手了,其实大家都注意到了WCF最新提供的webUrl template,实际上就是一个rest的服务实现,有兴趣的同志可以研究一下,里面的格式提供两种,一种为xml一种为Json. 由于html5的推广和原生支持Json,对于数据量更小的,而且更加高效的Json来说,势必会大规模的使用。由于REST还是基于http协议,以及对应资源路径寻径,所以一开始如果需要写服务的话,需要详细的规划出资源的路径。对于EVE的api实现,可以从测试的数据当中分析出来。
通过对资源http://www.eveonline.com/api/eve/AllianceList.xml.aspx  提交的post请求和数据返回的头和数据格式来看,可以解析出。  http://www.eveonline.com/api/    
POST /eve/AllianceList.xml.aspx HTTP/1.0  
Host: api.eve-online.com  
Content-Type: application/x-www-form-urlencoded  
Content-Length: 0  
Connection: close  
HTTP/1.1 200 OK  
Cache-Control: private  
Content-Type: application/xml; charset=utf-8  
Server: Microsoft-IIS/7.0  
X-AspNet-Version: 4.0.30319  
X-Powered-By: ASP.NET  
Date: Fri, 23 Dec 2011 02:33:40 GMT  
Eve目前的开放平台服务以2.0版本的api来说,是利用asp.net4.0的iis服务,基本可以肯定是ii7承载的wcf, web服务,当然,如果完全自己写一套框架也是可以的,但是就加载的程序集来看,应该就是利用的WCF4.0的REST封装,当然,这里也只是猜测而已。因为从eve的后台DB来看,用的是最新的sql2008版本,所以整个eve的服务端,应该利用的是微软体系的架构。  
为了便于大家调用eve所提供的API,和.net程序员使用,网上已经有openSource的EVEAI的.net客户端库供大家使用,所以大家在构造请求,获取数据,以及利用数据来说就要方便的得多。对于简单的consumer来说,调用一般都是很简单的,理解了http协议,构造http请求头,请求对应的http-resoure-url。如果请求成功的话,就可以收到最新的对应信息了,最后就可以对接收的数据进行整理,而展现的内容就控制在了consumer的手中。  具体的实现方式就有很多种类了,在这里我就简单的用调用eve api的一段代码来演示。
9537498486032
看完了简单的代码,其实大家应该就对eve函数的调用有了一定的认识了,对于拿到了数据,如何解析和使用就看各位自己的程序是如何编写的了。而如果是作为一个服务器端的设计者,需要考虑的内容就会更多,在这里不一一说明。最后预祝EVE的世界更加精彩—与人斗,其乐无穷。

SAP BOE x3.1和他的小老婆水晶易表(Xcelsius)的故事

最近的时间,基本上大部分都花在了和BOE打交道上了。话说有一日发现Xcelsius的美观和实用,准备开始用水晶易表做一些专题业务的应用,后来发现水晶易表在Server端没有像大老婆crystalReportView这种专用控件设备,虽然可以将小老婆娶回家,和大老婆放一个院里(就是说吧xcelsius嵌入到水晶报表当中)。但是速度十分缓慢,操控性不是很好,而且看起来比较丑,丧失了小老婆本有的眉毛多姿,风情万种。于是只能自己动脑筋开发了(我现在知道为啥都喜欢在外面买小别墅给二奶了,偷腥的感觉,独立的感觉还真是不错。要想皇帝那么多老婆,都一个院的,美女最后都被整成白痴和残废了,就剩下一些心狠手辣的老女人了,皇帝每天就听老女人撒娇,多么悲剧啊,所以皇帝都到民间到处播种)。言归正传,话说BOE服务器上的东西,就是将一大堆的数据,文件,连接封装到了一个InfoObject里面,不仅仅是报表,连里面的用户,用户组,以及结构,都是InfoObject,所以,只要能从InfoObject里面,找出对应的文件格式和文件流,然后解析出来就可以了。当然,如果数据是及时查询的,涉及到回调,那就要解决权限的问题。这内部就相对比较复杂了,不过万变不离其宗–数据结构+算法=程序。
   对于水晶易表和后台数据的封装,有以下几种方式,当然,版本是在x3.1中
  1, xcelsius->webi->BOE
  2, xcelsius->webi->live office->BOE
  3, xcelsius->webService->BOE
   其中的变化就可以让大家自己去进行自由组合,可以衍生出更多的方法。下面开始介入正题,在Google搜索了一圈以后,发现从BOE上取SWF的文件,没有啥子价值的资料,让后就开始自己开动脑筋来解析了,因为发布到BOE服务器上面的swf文件,是能直接在BOE服务器上面查看的,而且在BOE的服务器上,也发现了解析swf的dll的存在,于是就依据连接,找到了iis当中,解析易表的文件夹,进去一看,靠,就三个文件GetSwf View FlashControl,打开一看,还都只有几行内容,都是用来查看flash文件的,巨简单,而且View里面就加了一个控件,控件的就是FlashControl.ascx, 不过看到了一些绑定的内容。
  在FlashControl.ascx中,

  //object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
   codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"
   width="100%" height="100%" id="myMovieName" VIEWASTEXT>
   param name="movie" value="<%= MovieURL %>"/>
   param name="quality" value="high"/>
   param name="bgcolor" value="#FFFFFF"/>
   param name="FlashVars" value="<%= FlashVars%>"/>
   embed src="<%= MovieURL %>" flashvars="<%= FlashVars%>" quality="high" bgcolor="#FFFFFF" width="100%" height="100%"
   name="myMovieName" align="" type="application/x-shockwave-flash" pluginspace="http://www.macromedia.com/go/getflashplayer"/>
  ///object>

   由此可以看出,里面肯定有后台代码来获取其中的数据,于是开动发编译大法,从后台dll中找寻价值代码。经过一番摸索,就在一个不经意间找到了他们的实现类。
  于是乎,从BOE请求的过程就跃然纸上了。其实和我们普通请求报表的过程没有什么区别,无非就是创建session,创建service,query对应的InfoObject,让后调用了一个特殊的在InfoObject下面的类,StreamContent这个类来获得取过来的文件流,让后打到输出流当中,就会将文件返回给客户端了。整个过程不超过一百行代码,当然,大部分方法都经过了封装。下面将关键代码粘贴如下,不知道如果粘贴整个类会不会遭到审查,所以就贴点关键代码吧。

  private void LoadData()
   {
   string s = base.Request[ActionHelper.OBJID_PARAM];
   if (s != null)
   {
   EnterpriseSession session = (EnterpriseSession) this.Session["boeSession"];
   InfoStore service = (InfoStore) session.GetService("InfoStore");
   int id = int.Parse(s);
   InfoObject obj2 = TraversalLogic.GetInstance().GetInfoObject(service, id, "SI_PROGID, SI_FILES");
   string progID = obj2.ProgID;
   string str3 = ".swf";
   string str4 = "view";
   string str5 = "application/x-shockwave-flash";
   base.Response.Clear();
   string str6 = "inline";
   base.Response.AppendHeader("content-disposition", str6 + "; filename="" + str4 + str3 + """);
   base.Response.ContentType = str5;
   InfoObject.StreamContent.WriteContent(obj2, base.Response.OutputStream);
   base.Response.Flush();
   base.Response.End();
   }
   }

  
   经过上述一个简单的过程,我们的swf流就可以得到了,但是这里出现了一个问题,试想,你的文件都是发布到BOE服务器上的,而你数据连接,都是封装到水晶易表里面的,如果没有经过验证的用户,单独的打开swf文件,肯定是无法取到数据的,而在BOE服务器上面查看,BOE就能够不用输入任何东西,就能够查看数据,所以我们还得模拟的写一个页面,来仿照BOE查看数据的内容,其实思路也非常清晰,当我们从BOE上面请求到InfoObject时,里面的内容就包含了你的验证的信息,我们只需要通过他的属性,就可以把这些信息拼接出来,让后再构造模拟串,就可以完成。
  核心代码如下:

   EnterpriseSession enterpriseSession = ActionHelper.GetEnterpriseSession(base.Request);
   this.Session["boeSession"] = enterpriseSession;
   string str = base.Request[ActionHelper.OBJID_PARAM];
   if (str == null)
   {
   str = base.Request["iDocID"];
   }
   string str2 = "GetSwf.aspx?" + ActionHelper.OBJID_PARAM.ToString() + "=" + Encoder.Encode(str);
   string s = enterpriseSession.LogonTokenMgr.CreateWCATokenEx("", 0x3e8, -1);
   string serializedSession = enterpriseSession.SerializedSession;
   string str5 = "";
   int id = int.Parse(str);
   InfoObject obj2 = TraversalLogic.GetInstance().GetInfoObject(base.GetInfoStore(base.Request, base.Response), id, "SI_FLASHVARS_STRING");
   if ((obj2 != null) && (obj2.Properties.Count > 0))
   {
   str5 = obj2.Properties["SI_FLASHVARS_STRING"].Value.ToString() + "&CELogonToken=" + Encoder.EncodeURL(s);
   }
   else
   {
   str5 = "CELogonToken=" + Encoder.EncodeURL(s);
   }
   str5 = str5 + "&CESerializedSession=" + Encoder.EncodeURL(serializedSession);
   this.FlashControl1.MovieURL = str2;
   this.FlashControl1.FlashVars = str5;
   }

   这样,就可以从BOE服务器上取回我们的swf文件了,但是发现我在回传的时候,会提示跨域的错误,因为swf里面封装的数据连接,在你的应用程序域当中执行的时候,会被boe服务器认为是不可信的,没关系,加个可信的跨域文件,就可以轻松搞定具体的可以搜索到这个内容的描述。
  Cross-domain Policy File Specification
  至此,和BOE的小老婆的故事就结束了,其实小老婆姿色的确不错,动态实时,效果酷炫,就是速度比较慢。比大老婆好多了(所以大款都喜欢去包那么一两个二奶)。

带ssl的webService调用

最近闲来,正好帮一位朋友处理几个小问题,程序的目标是调用WebService,进行一些扩展模块的开发,原有的SOA平台所开放出来的webService是采用java编写的,而且验证方面采用了证书和加密隧道,所以访问起来颇费了一些周折。相比之下,还是云计算的SIP调用方式比较简单。这个SOA平台开放的服务,本身一个SDK的说明都不给,相当垃圾,没办法只能摸着石头过河。在asp.net里面调用webService的方式有两种(一种是采用直接引用的方式,一种是采用动态调用的方式),直接引用方式相当的简单,VS提供了非常智能的本地service类的生成,我们唯一需要做的就是添加web应用,编译器会自动依据webService的WSDL文件,来自动的生成webService类,让后我们new 对象,调用方法就可以了,这种是最快最傻的方法。第二种是采用动态的调用方式,需要用到一些反射的东西,相当于在客户端利用反射来调用实例方法。
代码事例如下。

using System;
using System.Web;
using System.Xml;
using System.Collections;
using System.Net;
using System.Text;
using System.IO;
using System.Xml.Serialization;/**////
///  利用WebRequest/WebResponse进行WebService调用的类,///
public class WebSvcCaller
{
    private static Hashtable _xmlNamespaces = new Hashtable();
    //缓存xmlNamespace,避免重复调用GetNamespace   
    /**/
    ///    
    /// /// 需要WebService支持Post调用   
    ///
    ///    
    public static XmlDocument QueryPostWebService(String URL, String MethodName, Hashtable Pars)
    {
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL + "/" + MethodName);
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";
        SetWebRequest(request);
        byte[] data = EncodePars(Pars);
        WriteRequestData(request, data);
        return ReadXmlResponse(request.GetResponse());
    }
    /**/
    ///     /// 需要WebService支持Get调用   
    ///    
    public static XmlDocument QueryGetWebService(String URL, String MethodName, Hashtable Pars)
    {
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL + "/" + MethodName + "?" + ParsToString(Pars));
        request.Method = "GET";
        request.ContentType = "application/x-www-form-urlencoded";
        SetWebRequest(request);
        return ReadXmlResponse(request.GetResponse());
    }
    /// 通用WebService调用(Soap),参数Pars为String类型的参数名、参数值    ///    
    public static XmlDocument QuerySoapWebService(String URL, String MethodName, Hashtable Pars)
    {
        if (_xmlNamespaces.ContainsKey(URL))
        {
            return QuerySoapWebService(URL, MethodName, Pars, _xmlNamespaces[URL].ToString());
        }
        else
        {
            return QuerySoapWebService(URL, MethodName, Pars, GetNamespace(URL));
        }
    }
    private static XmlDocument QuerySoapWebService(String URL, String MethodName, Hashtable Pars, string XmlNs)
    {
        _xmlNamespaces[URL] = XmlNs;//加入缓存,提高效率     
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL);
        request.Method = "POST";
        request.ContentType = "text/xml; charset=utf-8";
        request.Headers.Add("SOAPAction", XmlNs + (XmlNs.EndsWith("/") ? "" : "/") + MethodName);
        SetWebRequest(request);
        byte[] data = EncodeParsToSoap(Pars, XmlNs, MethodName);
        WriteRequestData(request, data);
        XmlDocument doc = new XmlDocument(), doc2 = new XmlDocument();
        doc = ReadXmlResponse(request.GetResponse());
        XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
        mgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
        String RetXml = doc.SelectSingleNode("//soap:Body/*/*", mgr).InnerXml;
        doc2.LoadXml("" + RetXml + "");
        AddDelaration(doc2);
        return doc2;
    }
    private static string GetNamespace(String URL)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL + "?WSDL");
        SetWebRequest(request);
        WebResponse response = request.GetResponse();
        StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(sr.ReadToEnd());
        sr.Close();
        return doc.SelectSingleNode("//@targetNamespace").Value;
    }
    private static byte[] EncodeParsToSoap(Hashtable Pars, String XmlNs, String MethodName)
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml("");
        AddDelaration(doc);
        XmlElement soapBody = doc.CreateElement("soap", "Body", "http://schemas.xmlsoap.org/soap/envelope/");
        XmlElement soapMethod = doc.CreateElement(MethodName);
        soapMethod.SetAttribute("xmlns", XmlNs);
        foreach (string k in Pars.Keys)
        {
            XmlElement soapPar = doc.CreateElement(k);
            soapPar.InnerXml = ObjectToSoapXml(Pars[k]);
            soapMethod.AppendChild(soapPar);
        }
        soapBody.AppendChild(soapMethod);
        doc.DocumentElement.AppendChild(soapBody);
        return Encoding.UTF8.GetBytes(doc.OuterXml);
    }
    private static string ObjectToSoapXml(object o)
    {
        XmlSerializer mySerializer = new XmlSerializer(o.GetType());
        MemoryStream ms = new MemoryStream();
        mySerializer.Serialize(ms, o);
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(Encoding.UTF8.GetString(ms.ToArray()));
        if (doc.DocumentElement != null)
        {
            return doc.DocumentElement.InnerXml;
        }
        else
        {
            return o.ToString();
        }
    }
    private static void SetWebRequest(HttpWebRequest request)
    {
        request.Credentials = CredentialCache.DefaultCredentials;
        request.Timeout = 10000;
    }
    private static void WriteRequestData(HttpWebRequest request, byte[] data)
    {
        request.ContentLength = data.Length;
        Stream writer = request.GetRequestStream();
        writer.Write(data, 0, data.Length);
        writer.Close();
    }
    private static byte[] EncodePars(Hashtable Pars)
    {
        return Encoding.UTF8.GetBytes(ParsToString(Pars));
    }
    private static String ParsToString(Hashtable Pars)
    {
        StringBuilder sb = new StringBuilder();
        foreach (string k in Pars.Keys)
        {
            if (sb.Length < 0)
            {
                sb.Append("");
            }
            sb.Append(HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(Pars[k].ToString()));
        }
        return sb.ToString();
    }
    private static XmlDocument ReadXmlResponse(WebResponse response)
    {
        StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
        String retXml = sr.ReadToEnd();
        sr.Close();
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(retXml);
        return doc;
    }
    private static void AddDelaration(XmlDocument doc)
    {
        XmlDeclaration decl = doc.CreateXmlDeclaration("1.0", "utf-8", null);
        doc.InsertBefore(decl, doc.DocumentElement);
    }
    private object InvokeWebservice(string url, string @namespace, string classname, string methodname, object[] args)
    {
        try
        {
            System.Net.WebClient wc = new System.Net.WebClient();
            System.IO.Stream stream = wc.OpenRead(url + "?WSDL");
            System.Web.Services.Description.ServiceDescription sd = System.Web.Services.Description.ServiceDescription.Read(stream);
            System.Web.Services.Description.ServiceDescriptionImporter sdi = new System.Web.Services.Description.ServiceDescriptionImporter();
            sdi.AddServiceDescription(sd, "", "");
            System.CodeDom.CodeNamespace cn = new System.CodeDom.CodeNamespace(@namespace);
            System.CodeDom.CodeCompileUnit ccu = new System.CodeDom.CodeCompileUnit();
            ccu.Namespaces.Add(cn);
            sdi.Import(cn, ccu);
            Microsoft.CSharp.CSharpCodeProvider csc = new Microsoft.CSharp.CSharpCodeProvider();
            System.CodeDom.Compiler.ICodeCompiler icc = csc.CreateCompiler();
            System.CodeDom.Compiler.CompilerParameters cplist = new System.CodeDom.Compiler.CompilerParameters();
            cplist.GenerateExecutable = false;
            cplist.GenerateInMemory = true;
            cplist.ReferencedAssemblies.Add("System.dll");
            cplist.ReferencedAssemblies.Add("System.XML.dll");
            cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
            cplist.ReferencedAssemblies.Add("System.Data.dll");
            System.CodeDom.Compiler.CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu);
            if (true == cr.Errors.HasErrors)
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                {
                    sb.Append(ce.ToString());
                    sb.Append(System.Environment.NewLine);
                }
                throw new Exception(sb.ToString());
            }
            System.Reflection.Assembly assembly = cr.CompiledAssembly;
            Type t = assembly.GetType(@namespace + "." + classname, true, true);
            object obj = Activator.CreateInstance(t);
            System.Reflection.MethodInfo mi = t.GetMethod(methodname);
            return mi.Invoke(obj, args);
        }
        catch (Exception ex)
        {
            throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace));
        }
    }
    #region InvokeWebService
    //动态调用web服务       
    public static object InvokeWebService(string url, string methodname, object[] args)
    {
        return WebServiceHelper.InvokeWebService(url, null, methodname, args);
    }
    public static object InvokeWebService(string url, string classname, string methodname, object[] args)
    {
        string @namespace = "EnterpriseServerBase.WebService.DynamicWebCalling";
        if ((classname == null) || (classname == ""))
        {
            classname = WebServiceHelper.GetWsClassName(url);
        }
        try
        {                //获取WSDL               
            WebClient wc = new WebClient();
            Stream stream = wc.OpenRead(url + "?WSDL");
            ServiceDescription sd = ServiceDescription.Read(stream);
            ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
            sdi.AddServiceDescription(sd, "", "");
            CodeNamespace cn = new CodeNamespace(@namespace);
            //生成客户端代理类代码              
            CodeCompileUnit ccu = new CodeCompileUnit();
            ccu.Namespaces.Add(cn); sdi.Import(cn, ccu);
            CSharpCodeProvider csc = new CSharpCodeProvider();
            ICodeCompiler icc = csc.CreateCompiler();
            //设定编译参数              
            CompilerParameters cplist = new CompilerParameters();
            cplist.GenerateExecutable = false;
            cplist.GenerateInMemory = true;
            cplist.ReferencedAssemblies.Add("System.dll");
            cplist.ReferencedAssemblies.Add("System.XML.dll");
            cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
            cplist.ReferencedAssemblies.Add("System.Data.dll");
            //编译代理类               
            CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu);
            if (true == cr.Errors.HasErrors)
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                {
                    sb.Append(ce.ToString());
                    sb.Append(System.Environment.NewLine);
                } throw new Exception(sb.ToString());
            }
            //生成代理实例,并调用方法               
            System.Reflection.Assembly assembly = cr.CompiledAssembly;
            Type t = assembly.GetType(@namespace + "." + classname, true, true);
            object obj = Activator.CreateInstance(t);
            System.Reflection.MethodInfo mi = t.GetMethod(methodname);
            return mi.Invoke(obj, args);
        }
        catch (Exception ex)
        {
            throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace));
        }
    }
    private static string GetWsClassName(string wsUrl)
    {
        string[] parts = wsUrl.Split('/');
        string[] pps = parts[parts.Length - 1].Split('.');
        return pps[0];
    }
    #endregion
}

 
下面进入精彩的环节,问题一:在webService发布以后,发现ssl信道无法创建的问题,一开始怀疑是证书的问题,因为在编译环境下面是没有问题了,后来发布到iis上面就出现了问题,最后改动了x509证书的获取方式。以及应用程序池的账户权限,解决了这个垃圾问题。X509的证书获取有几种方式,一种是从*.pfx证书文件里面取,一种是从*.cer的文件里面取,一种是从本地的证书池里面获取。srv.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(@"c:cert.pfx", "password"));如果用 X509Certificate.CreateFromCertFile( "d:cert.cer ")也是可以的,同样,从证书池里面取也是可以的。至此,问题一解决问题二:发现每次调用的时候,都需要重行进行连接,否则就会出现logOut的现象,经过研究发现,每次的访问请求,都是一个新的连接,所以需要维持原有的会话的session,这里需要利用时间戳来定位会话的session,实际上很简单。代码如下:

System.Net.CookieContainer cookieJar = new System.Net.CookieContainer();        
// Assign the CookieContainer to the proxy class.          
objWSFunc.CookieContainer = cookieJar;        
// Get CurrentTime.        
DateTime dt = DateTime.Now;     
string CurrentTime = dt.ToString("s");         
// Store the cookies received in the session state for future retrieval by this session.        
Session.Add("Time", cookieJar);

第二次调用的时候,用这个时间戳来定位session,很简单,
就一行代码objWSFunc.CookieContainer = (System.Net.CookieContainer) Session["Time"];         
很简单吧,搞定。
附注1:
//获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。  
str5=Application.StartupPath;//可获得当前执行的exe的文件名。      
str1=Process.GetCurrentProcess().MainModule.FileName;//获取和设置当前目录(即该进程从中启动的目录)的完全限定路径。  
备注: 按照定义,如果该进程在本地或网络驱动器的根目录中启动,则此属性的值为驱动器名称后跟一个尾部反斜杠(如“C:”)。如果该进程在子目录中启动,则此属性的值为不带尾部反斜杠的驱动器和子目录路径(如“C:mySubDirectory”)。   str2=Environment.CurrentDirectory;//获取应用程序的当前工作目录。
str3=Directory.GetCurrentDirectory();//获取基目录,它由程序集冲突解决程序用来探测程序集。   str4=AppDomain.CurrentDomain.BaseDirectory;//获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。str5=Application.StartupPath; //获取启动了应用程序的可执行文件的路径,包括可执行文件的名称。  str6=Application.ExecutablePath;//获取或设置包含该应用程序的目录的名称。str7=AppDomain.CurrentDomain.SetupInformation.ApplicationBase
附注2:
1. 在ASP.NET中专用属性:    
获取服务器电脑名:Page.Server.ManchineName    
获取用户信息:Page.User    
获取客户端电脑名:Page.Request.UserHostName    
获取客户端电脑IP:Page.Request.UserHostAddress
2. 在网络编程中的通用方法:    
获取当前电脑名:static System.Net.Dns.GetHostName()   
根据电脑名取出全部IP地址:static System.Net.Dns.Resolve(电脑名).AddressList    
也可根据IP地址取出电脑名:static System.Net.Dns.Resolve(IP地址).HostName3. 系统环境类的通用属性:     
当前电脑名:static System.Environment.MachineName     
当前电脑所属网域:static System.Environment.UserDomainName    
当前电脑用户:static System.Environment.UserName

cli

C++/CLI描绘的是一种多元组合,此处的 C++ 当然是指 Bjarne Stroustrup 在 Bell 实验室发明的C++编程语言。它支持速度和执行 文件的大小都得到优化的静态对象模型。但除了堆内存分配以外,它不支持运行时程序对对象的更改。它允许对底层机器进行无限制的访问,但对于正在运行的程序中的活动类型、以及相关的程序基础构造,它的访问能力却非常有限、或者根本就不可能。我在微软的同事 Herb Sutter,也是C++/CLI的主架构师,认为C++是一个混凝土语言。
“CLI”即公共语言基础结构(Common Language Infrastructure),这是一个支持动态组件编程模型的多层架构。在许多方面,它所表示的对象模型和C++的完全相反。它是一个运行时软件层 ,一个虚拟执行系统,运行在应用程序和底层操作系统之间。对底层机器的访问受到相当严格的限制。支持对运行中程序的活动类型以及关联程序的基础构造 进行存取——发现和建构。斜线“/”表示 C++ 和 CLI 之间的一种绑定(binding),有关这种绑定的细节构成本专栏的常规主题。
所以,对于“什么是C++/CLI?”这个问题第一个最近似的答案是:它是静态C++对象模型到动态CLI组件对象模型的一种绑定, 简言之,它就是你如何用C++进行.NET编程,而不是用C#或Visual Basic.NET。象C#和CLI自己一样,C++/CLI正在经历 ECMA(欧洲计算机制造商协会) 标准化并最终要经历ISO标准认证。
公共语言运行时(CLR)是微软版的CLI,专门用于 Windows 操作系统,同样,Visual C++ 2005是C++/CLI 的实现。
第二个近似答案是:我觉得C++/CLI在C++内集成.NET编程模型与以前贝尔实验室在当时的C++中用模板集成泛型编程一样有异曲同工之处。两种情况中,你在现有C++代码库上的投资以及你现有的C++专业技术都得到保护。这是C++/CLI设计的一个基本 要求。
学习C++/CLI
一种C++/CLI语言的设计有三个层面,这三个层面也适用于所有语言:语言层语法到公共类型系统(CTS) 的映射;选择为程序员直接操作而公开的底层CLI基本组织结构 的详细程度;以及选择要提供的超越CLI直接支持的附加功能。第一个层面是所有CLI语言在很大程度上都共有的,第二个层面和第三层面是某一CLI语言区别于其它语言的地方。根据所要解决的问题,你可以选择某一 种语言,也可以将多种CLI语言结合起来。学习C++/CLI语言需要掌握这三个设计层面。
怎样将C++/CLI 映射到CTS?
了解底层CTS 对学习C++/CLI非常有帮助,它主要包括三个常规类类型:
多态引用类型,其用于所有的类继承;
非多态值类型,其用于实现需要运行时效率的具体类型,如数字类型;
抽象接口类型,其用于定义一个实现该接口的一组引用类型或值类型共同使用的公共操作集;
在设计方面,虽然CTS到一组内置的语言类型的映射对于所有CLI语言来说都是共同的,当然,每一种CLI语言的语法各不相同。例如,在C#中,我们可以 这样来定义一个抽象基类 Shape,从这个类派生特定的几何模型对象。
abstract class Shape { … } // C#
而在C++/CLI中,可以象下面这样写,以表示完全相同的底层引用类型:
ref class Shape abstract { … }; // C++/CLI
在底层 IL(中间语言)中,以上两种声明以完全相同的方式表示。同样,在C#中,我们可以用下面的代码来定义一个具体的 Point2D 类 : struct Point2D { … } // C#
而在C++/CLI中写成: value class Point2D { … }; // C++/CLI
借助 C++/CLI 支持的类类型家族表现了一种本机方式的 CTS 集成。它确定了你的语法选择,例如: class native {};value class V {};ref class R {};interface class I {};
CTS 也支持枚举类类型,其行为方式与本机枚举稍微有些区别,C++/CLI对二者都提供支持: enum native { fail, pass };enum class CLIEnum : char { fail, pass};
同样,CTS支持其自己的数组类型,其行为也与本机数组类型有一定差别,微软同样对二者提供支持: int native[] = { 1,1,2,3,5,8 }; array<int>^ managed = { 1,1,2,3,5,8 };
那种认为任何一种 CLI 语言比另一种语言更接近或几乎就是到底层CLI的映射是不精确的。相反,每一种CLI语言都只是表达了自己对底层CLI对象模型的一种 见解。在下一节你将更清楚地看到这一点。
CLI 的细节标准
在设计CLI语言时必须考虑的第二个设计层面是要将什么程度的底层CLI实现模型结合到该语言中。这个语言解决什么样的问题?要解决这些问题必须要什么样的工具? 此外,该语言很可能吸引哪一类程序员?
下面,我们利用发生在托管堆中的值类型问题。在许多情况下,值类型可以在托管堆中找到自己:通过隐式的框入/框出操作(boxing)——当值类型的某个实例被赋值给一对象时,或者通过某个未被改写的值类型调用一个虚拟方法时;当值类型被当作为引用类类型的成员时;
当值类型被当作CLI数组元素存储时;是否允许程序员处理这种值类型地址是设计CLI语言时必须要解决的问题。存在的问题?位于托管堆中的任何对象在垃圾回收器进行清扫收缩的过程中都有可能遭遇重新分配,指向这些对象的任何指针必须被追踪并在运行时得到更新,而程序员 无法自己手动追踪它们,因此,如果你被允许用某个可能在托管堆中的值类型的地址,那么除了本机指针外,还需要一个追踪形态的指针。
到底该怎样去权衡呢?一方面,需要考虑简洁和安全。直接引入对一个或一组追踪指针的支持会使语言变得更复杂。如果不提供这种支持,由于所需的复杂程度降低,从而可以找到的程序员人群就会增加。此外,允许程序员访问这些生命期短暂的值类型,则增加了程序员出错的可能性。她经意或不经意地对内存做一些危险动作。不支持追踪指针,可以潜在地创建较安全的运行时环境。另一方面,必须考虑效率和灵活性。每次将值类型赋值给相同的对象,该值都会发生新的框入/框出操作。允许访问这种经过框入/框出操作的值类型 ,就允许在内存中进行更新操作,这样便可能提供重要的性能改进。没有某种形式的追踪指针,你将无法用指针算法遍历CLI数组,这意味着CLI数组将不能 融入STL(标准模板库)中的迭代器模式,也无法与泛型算法协同工作。允许访问框入/框出值类型将会大大提高设计的灵活性。在C++/CLI 中,微软选择提供一系列在托管堆中处理值类型的寻址模式:int ival = 1024;int^ boxedi = ival; array<int>^ ia = gcnew array<int>{1,1,2,3,5,8};  interior_ptr<int> begin = &amp;ia[0];value struct smallInt { int m_ival; … } si;  pin_ptr<int> ppi = &amp;si.m_ival; 典型的 C++/CLI 开发人员是一个经验丰富的系统程序员,其任务是提供底层架构以及作为基础的核心应用,以此为基础来构建未来。她必须解决可伸缩性和性能相关的问题,并且必须从系统一级来看待底层 CLI。某种 CLI 语言的细节标准反映了其程序员的面貌。复杂性本身并不是对质量的否定,人类生命比单核细胞复杂得多,这当然不是一件坏事,然而,当单一概念的表达变得复杂化以后,这常常被认为是一件坏事。在C++/CLI中,CLI开发团队已经 尝试提供一种优雅的方式来表达一个复杂的主体。
附加功能
第三个设计层面是特定语言层功能要超过被CLI直接支持的功能,这样就需要建立一种语言层支持与CLI底层实现模型之间的映射。 在某些情况下,这是做不到的,因为该语言无法调解CLI的行为,在基类的构造函数和析构函数中解决虚函数便是例子。为了在这种情况中反映ISO-C++语义,需要在每个基类的构造函数和析构函数中 重新安排虚表。这是不可能的,因为虚表操作是由运行时托管的,而非单独的语言托管。 因此,这一设计层面是优越性和可行性的折中。C++/CLI 提供的附加功能主要有三个方面:引用类型的资源获取(Resource Acquisition)形式是Initialization(RAII), 尤其是为被称作占据稀有资源的垃圾回收类型确定性终止化(deterministic finalization)提供一个自动化的机制;与C++拷贝构造函数和拷贝赋值操作符相关的深度拷贝语义形式,但它不能扩展到值类型;
除了 CLI泛型机制之外——这原来是我第一个专栏的主题,还为CTS类型提供C++模板的直接支持,另外,还提供用于 CLI 类型的 STL 可验证版本;让我们看一个简单的例子:确定性终止化问题。与对象关联的内存被垃圾回收器回收之前,若存在与之相关连的 Finalize 方法,该方法将会被调用。你可以把 该方法看作是一种超级析构函数,因为它不依赖于该对象程序的生命期,它被称为终止化。调用 Finalize 方法的时间,甚至是否调用它是未定义的。这就是垃圾回收器不确定的终止化操作含义之所在。
不确定性终止化在进行动态内存管理时可以很有效地工作,当可用内存空间严重不足时,垃圾回收器会发挥作用并解决问题。但是当对象涉及的是某些重要资源,比如数据库连接、某种 类型的锁、本地堆内存时,