项目基本完成了,一期的东西基本都做完了,采用的纯Moss下面的开发,也应该总结一些要点和东西,为了不使自己以后完全忘记,或者说:为了让广大困惑的人,能在此找到一些捷径和关键点,在此能有所用,也就欣慰不已。
要点一:INFOPATH的XML处理
在对带有VBA的INFOPATH编码当中,你会发现在支持浏览器的INFOPATH表单里面,能够使用的对象模型和处理十分有限,但是为了快捷,我们采用了它,因为利用现有的XML处理,加上页面当中的规则,基本上可以完成所有的业务逻辑。其实在简单的理解来说,InfoPath表单,其本质数据完全是XML,只是在展现的时候,用特定的控件来展示你XML的数据的,而控件基本都是采用绑定的形式和你的XML数据交互的,并且辅之以相当强大的规则和操作,来完成你的应用逻辑,所以处理方式比较特殊。所有规则能做的,代码都能做,但是目前在展示方面还有挺多的局限,后面会有所提到。
问题一:如何处理你的重复节?
重复节,也许就是我们在INFOPATH里面用的最多的东西,其十分类似于我们在Asp.NET里面的Reperate,可以用来存放重复数据字段,而其在INFOPATH的XML数据的定义中,通常是三级层次结构的,如图,第一层是整个节点,第二层是每个子节点,每个子节点里面可以包含多个属性或者字段,用xpath来解析的话,就是类似于如此:
/my:HQContentType/my:Enclosuregroup
/my:HQContentType/my:Enclosuregroup/my:EnclosureNode
/my:HQContentType/my:Enclosuregroup/my:EnclosureNode/my:Enclosure
最前面的是我的表单的名字。而用来处理节的对象模型,在支持浏览器的InfoPath表单里面,我们用得最多的是:
XmlNamespaceManager 和XPathNavigator,XmlNamespaceManager是对整个XML的命名控件管理的对象模型,也可以理解为表单的命名空间,而XPathNavigator就是采用XPATH来对xml数据进行操作的对象模型。
读和取的办法如下代码:
/// <summary>
/// 把节里的重复节里的DropDownlist的值连成字符串
/// </summary>
/// <param name="xpath_Group">节里的组</param>
/// <param name="strDll_Name">DropDownlist的名字</param>
/// <param name="splitchar">分割字符</param>
/// <returns>正却执行返回结果,异常返回空值</returns>
public string RepeatDllValuesTostring(string xpath_Group, string strDll_Name, char splitchar)
{
string returnValue = string.Empty;
try
{
XmlNamespaceManager ns = this.NamespaceManager;
XPathNavigator xpRoot = this.MainDataSource.CreateNavigator();
XPathNavigator xpCompanyGroup = xpRoot.SelectSingleNode(xpath_Group, ns);
XPathNodeIterator xpGroupIter = xpCompanyGroup.SelectChildren(XPathNodeType.Element);
StringBuilder SBvalue = new StringBuilder();
while (xpGroupIter.MoveNext())
{
XPathNavigator xpCurrent = xpGroupIter.Current;
string strValue = xpCurrent.SelectSingleNode("my:" + strDll_Name, ns).Value;
if (!(string.IsNullOrEmpty(strValue) || strValue.Trim().Length <= 0))
{
SBvalue.Append(strValue + splitchar);
}
}
if (SBvalue.Length > 0)
{
returnValue = SBvalue.ToString();
returnValue = returnValue.Remove(returnValue.LastIndexOf(splitchar));
}
}
catch (SPException spex)
{
//错误处理
return string.Empty;
}
return returnValue;
}
问题二:如何处理你的附件。
附件节的处理方式,比较多样,默认的如果附件节存储的地方是InfoPath本身的话,InfoPath有自己的一套编码转化方式,实际上还是将附件打成了流,存储到了相应的XML的节里面。试想,如果文件比较大,那么表单的载入将会十分的缓慢,这样做的效率比较低下。但是可以利用附件节做为媒介,运用文档库,把文件上传到Moss里面,在前台留一个简单的url,同样,在InfoPath表单载入的时候,也可以把文档库里面的文件读取到InfoPath的附件节里,提供用户下载查看。
这里提供两个类,一个用来编码,一个用来解码,下面是编码和解码的类,这里有需要注意的是,附件节对应的xml节中,如果读入了文件,要把nil属性给去掉,否则会报错。
XPathNavigator xpExcelFileNode = xpExcels.SelectSingleNode("my:AssistFileNode", ns);
xpExcels.AppendChild(xpExcelFileNode);
XPathNodeIterator xpExcelsIterator = xpExcels.SelectChildren(XPathNodeType.Element);
while (xpExcelsIterator.MoveNext())
{
XPathNavigator xptempExcelFileNode = xpExcelsIterator.Current;
XPathNavigator xpExcel = xptempExcelFileNode.SelectSingleNode("my:AssistFile", ns);
if (xpExcel.MoveToAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance"))
{
xpExcel.DeleteSelf();
InfoPathAttachmentEncoder encoder = new InfoPathAttachmentEncoder();
string fileStream = encoder.GetTheBase64String(fo);
xpExcel.SetValue(fileStream);
break;
}
}
public class InfoPathAttachmentEncoder
{
private string base64EncodedFile = string.Empty;
private string fullyQualifiedFileName;
/// <summary>
/// Creates an encoder to create an InfoPath attachment string.
/// </summary>
/// <param name="fullyQualifiedFileName"></param>
public InfoPathAttachmentEncoder(string fullyQualifiedFileName)
{
if (fullyQualifiedFileName == string.Empty)
throw new ArgumentException("Must specify file name", "fullyQualifiedFileName");
if (!File.Exists(fullyQualifiedFileName))
throw new FileNotFoundException("File does not exist: " + fullyQualifiedFileName, fullyQualifiedFileName);
this.fullyQualifiedFileName = fullyQualifiedFileName;
}
public InfoPathAttachmentEncoder()
{
}
public string GetTheBase64String(SPFile spFile)
{
Stream tempFileStream = spFile.OpenBinaryStream();
//BinaryReader br = new BinaryReader(tempFileStream);
// This memory stream will hold the InfoPath file attachment buffer before Base64 encoding.
MemoryStream ms = new MemoryStream();
// Get the file information.
using (BinaryReader br = new BinaryReader(tempFileStream))
{
string fileName = spFile.Name;
uint fileNameLength = (uint)fileName.Length + 1;
byte[] fileNameBytes = Encoding.Unicode.GetBytes(fileName);
using (BinaryWriter bw = new BinaryWriter(ms))
{
// Write the InfoPath attachment signature.
bw.Write(new byte[] { 0xC7, 0x49, 0x46, 0x41 });
// Write the default header information.
bw.Write((uint)0x14); // size
bw.Write((uint)0x01); // version
bw.Write((uint)0x00); // reserved
// Write the file size.
bw.Write((uint)br.BaseStream.Length);
// Write the size of the file name.
bw.Write((uint)fileNameLength);
// Write the file name (Unicode encoded).
bw.Write(fileNameBytes);
// Write the file name terminator. This is two nulls in Unicode.
bw.Write(new byte[] { 0, 0 });
// Iterate through the file reading data and writing it to the outbuffer.
byte[] data = new byte[64 * 1024];
int bytesRead = 1;
while (bytesRead > 0)
{
bytesRead = br.Read(data, 0, data.Length);
bw.Write(data, 0, bytesRead);
}
}
}
// This memorystream will hold the Base64 encoded InfoPath attachment.
MemoryStream msOut = new MemoryStream();
using (BinaryReader br = new BinaryReader(new MemoryStream(ms.ToArray())))
{
// Create a Base64 transform to do the encoding.
ToBase64Transform tf = new ToBase64Transform();
byte[] data = new byte[tf.InputBlockSize];
byte[] outData = new byte[tf.OutputBlockSize];
int bytesRead = 1;
while (bytesRead > 0)
{
bytesRead = br.Read(data, 0, data.Length);
if (bytesRead == data.Length)
tf.TransformBlock(data, 0, bytesRead, outData, 0);
else
outData = tf.TransformFinalBlock(data, 0, bytesRead);
msOut.Write(outData, 0, outData.Length);
}
}
msOut.Close();
return base64EncodedFile = Encoding.ASCII.GetString(msOut.ToArray());
}
/// <summary>
/// Returns a Base64 encoded string.
/// </summary>
/// <returns>String</returns>
public string ToBase64String()
{
if (base64EncodedFile != string.Empty)
return base64EncodedFile;
// This memory stream will hold the InfoPath file attachment buffer before Base64 encoding.
MemoryStream ms = new MemoryStream();
// Get the file information.
using (BinaryReader br = new BinaryReader(File.Open(fullyQualifiedFileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
string fileName = Path.GetFileName(fullyQualifiedFileName);
uint fileNameLength = (uint)fileName.Length + 1;
byte[] fileNameBytes = Encoding.Unicode.GetBytes(fileName);
using (BinaryWriter bw = new BinaryWriter(ms))
{
// Write the InfoPath attachment signature.
bw.Write(new byte[] { 0xC7, 0x49, 0x46, 0x41 });
// Write the default header information.
bw.Write((uint)0x14); // size
bw.Write((uint)0x01); // version
bw.Write((uint)0x00); // reserved
// Write the file size.
bw.Write((uint)br.BaseStream.Length);
// Write the size of the file name.
bw.Write((uint)fileNameLength);
// Write the file name (Unicode encoded).
bw.Write(fileNameBytes);
// Write the file name terminator. This is two nulls in Unicode.
bw.Write(new byte[] { 0, 0 });
// Iterate through the file reading data and writing it to the outbuffer.
byte[] data = new byte[64 * 1024];
int bytesRead = 1;
while (bytesRead > 0)
{
bytesRead = br.Read(data, 0, data.Length);
bw.Write(data, 0, bytesRead);
}
}
}
// This memorystream will hold the Base64 encoded InfoPath attachment.
MemoryStream msOut = new MemoryStream();
using (BinaryReader br = new BinaryReader(new MemoryStream(ms.ToArray())))
{
// Create a Base64 transform to do the encoding.
ToBase64Transform tf = new ToBase64Transform();
byte[] data = new byte[tf.InputBlockSize];
byte[] outData = new byte[tf.OutputBlockSize];
int bytesRead = 1;
while (bytesRead > 0)
{
bytesRead = br.Read(data, 0, data.Length);
if (bytesRead == data.Length)
tf.TransformBlock(data, 0, bytesRead, outData, 0);
else
outData = tf.TransformFinalBlock(data, 0, bytesRead);
msOut.Write(outData, 0, outData.Length);
}
}
msOut.Close();
return base64EncodedFile = Encoding.ASCII.GetString(msOut.ToArray());
}
}
public class InfoPathAttachmentDecoder
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public InfoPathAttachmentDecoder(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);
DecodeAttachment(theReader);
}
}
private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to get the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);
fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;
byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
decodedAttachment = theReader.ReadBytes(fileSize);
}
public void SaveAttachment(string saveLocation)
{
string fullFileName = saveLocation;
if (!fullFileName.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullFileName += Path.DirectorySeparatorChar;
}
fullFileName += attachmentName;
if (File.Exists(fullFileName))
File.Delete(fullFileName);
FileStream fs = new FileStream(fullFileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(decodedAttachment);
bw.Close();
fs.Close();
}
public string Filename
{
get { return attachmentName; }
}
public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
}
问题三:怎么处理表单的关闭和数据的提交?
在InfoPath里面,怎么向宿主环境提交数据,怎么吧表单提交到宿主里面呢?实现的方法有三种,一种是直接利用InfoPath的条件和规则以及操作无需编码,另外一个是在后台代码里面进行填写,前者局限性比较大,虽然能够完成一定的功能,但是比较局限,而用后台代码的话就不存在这个问题,可以很轻松的控制自己的逻辑和业务。第三种采用两者结合的方式,在尽量少的编码情况下,做到尽量多的东西(懒人的终极选择)。在提交的时候,采用的对象模型为,FileSubmitConnection,给段样例如下,原来还研究了一下ADO数据源的读取和提交,发现在表单的InternalStartup()以后,对其数据源的东西就无法更改了,所以和数据库操作联动的话,采用的还是自定义的assembly来进行操作,简单实用。
//获取 提交数据源 并设置提交的参数 提交到的文件夹 文件名
FileSubmitConnection fileSubmit = (FileSubmitConnection)this.DataConnections["FileSubmit"];
if (CurrentWeb.Url.EndsWith("/"))
{
fileSubmit.FolderUrl = HQIssueDocLib.ParentWeb.Url + HQIssueDocLib.RootFolder.Url;
}
else
{
fileSubmit.FolderUrl = HQIssueDocLib.ParentWeb.Url + "/" + HQIssueDocLib.RootFolder.Url;
}
//如果表单不存在
if (xpIsExist.Value.Equals("0"))
{
//提交
if (xpSubmitOrNot.Value.Equals("1"))
{
if (xpRedtape.Value.Length <= 0)
{
xpErrorMessage.SetValue(formSaveSate.EmptyRedFile);
xpDispatchTitle.SetValue(string.Empty);
return;
}
//设置 CanModify为"0"为不可修改,IsSubmit为"1"已提交,IsExist为"1"为已存在,并提交
xpCanModify.SetValue("0");
xpIsSubmit.SetValue("1");
xpIsExist.SetValue("1");
fileSubmit.Filename.SetStringValue(filename);
fileSubmit.Execute();
}
}
问题四:怎么来处理表单的多视图?
一个InfoPath表单,可以采用多个视图来进行展现,并且绑定相关xml数据源的控件,可以定义新的规则和新的展现方式,这个个人认为是InfoPath良好设计的最佳体现。充分分离了数据和视图,但是目前感觉在WEB这块的INFOPATH表单,展现局限性还是很大,虽然可以自定自己的类似于AciviteX的控件,但是十分麻烦。不过后期肯定会陆续完善,因为微软内部的新Moss测试版本已经到14了。你可以在载入的时候根据读入的xml数据,来选择你所需要展现的视图,回传的时候,同样也可以更新你的视图,具体例子代码如下,思路就是通过检查传入的变量,调用SetDefaultView方法,这两个都是包含在FormLoad的LoadingEventArgs里面的,具体取传入的变量的方法是看InputParameters键值对集合里面的值,和queryString类似。
public bool SetCanModify(LoadingEventArgs loadingEventArgs)
{
try
{
XmlNamespaceManager ns = this.NamespaceManager;
XPathNavigator xpRoot = this.MainDataSource.CreateNavigator();
//表单的标题
XPathNavigator xpDispatchTitle = xpRoot.SelectSingleNode("my:HQContentType/my:DispatchTitleNode/my:DispatchTitle", ns);
//值 "0" 没有提交,值 "1"提交
XPathNavigator xpIsSubmit = xpRoot.SelectSingleNode("my:HQContentType/my:IsSubmit", ns);
//值 "0"不允许修改 ,"1"允许修改
XPathNavigator xpCanModify = xpRoot.SelectSingleNode("my:HQContentType/my:CanModify", ns);
//值 "0"不允许关闭,"1"允许关闭
XPathNavigator xpIsAllowClose = xpRoot.SelectSingleNode("my:HQContentType/my:StateGroup/my:IsAllowClose", ns);
//表单是否存在
XPathNavigator xpIsExist = xpRoot.SelectSingleNode("my:HQContentType/my:StateGroup/my:IsExist", ns);
//包含openfrom,表示是从表单中的链接打开的
bool isOpenFromForm = loadingEventArgs.InputParameters.ContainsKey("openfrom");
//Task5是特殊情况,链接中的参数canmodify是标志允许修改
bool isSpecialCanModify = loadingEventArgs.InputParameters.ContainsKey("canmodify");
//对视图和关闭按钮进行设置
if (xpDispatchTitle != null && xpDispatchTitle.Value.Trim().Length > 0 && xpIsExist != null && xpIsExist.Value.Equals("1"))
{
using (SPWeb web = new SPSite(SPContext.Current.Site.ID).OpenWeb(SPContext.Current.Web.ID))
{
//获得当前项目
SPList HQIssueDocLib = web.Lists["总部发文库"];
SPListItem currentItem = FindItemByFileName(HQIssueDocLib, xpDispatchTitle.Value);
//取Field的显示名
string strAuthor = string.Empty;
string strFileOrigin = GetDisplayNameByStaticName(HQIssueDocLib, "FileOrigin");
string strCanModify = GetDisplayNameByStaticName(HQIssueDocLib, "CanModify");
//如果文件存在,且CanModify域值不为空
if (xpIsExist != null && xpIsExist.Value.Equals("1") && currentItem[strCanModify] != null)
{
//依据不同情况取得文件的创建者的Field的显示名,下属呈文的时候Drafter是我们需要的创建者
if (currentItem[strFileOrigin] != null && currentItem[strFileOrigin].ToString().Equals("下属呈文"))
{
strAuthor = GetDisplayNameByStaticName(HQIssueDocLib, "Drafter");
}
else
{
strAuthor = GetDisplayNameByStaticName(HQIssueDocLib, "Author");
}
//取得当前用户和文件创建者的相关对象
SPFieldUserValue userValue = new SPFieldUserValue(web, currentItem[strAuthor].ToString());
SPUser currentUser = SPContext.Current.Web.CurrentUser;
string currentUserLogName = currentUser.LoginName;
//通过项的CanModify域值设置不同的视图和参数
string strVaue = currentItem[strCanModify].ToString();
switch (strVaue)
{
case "是":
//允许修改,当前用户就是创建者,则采取默认视图
if (currentUserLogName.Equals(userValue.User.LoginName))
{
//并且是从表单里打开,则不允许关闭表单
if (isOpenFromForm)
{
xpIsAllowClose.SetValue("0");
}
else//从文档库打开,允许关闭表单
{
xpIsAllowClose.SetValue("1");
}
xpCanModify.SetValue("1");
loadingEventArgs.SetDefaultView("DefaultView");
}
else//允许修改,但当前用户不是创建者,设置只读视图,并且不允许关闭表单
{
//if (isOpenFromForm)
//{
// xpIsAllowClose.SetValue("0");
//}
//else
//{
// xpIsAllowClose.SetValue("1");
//}
xpIsAllowClose.SetValue("0");
xpCanModify.SetValue("0");
loadingEventArgs.SetDefaultView("ReadOnlyView");
}
break;
default:
//这个是特殊的标志为,强制进行修改,是从表单链接打开,不允许关闭
if (isSpecialCanModify)
{
xpCanModify.SetValue("1");
xpIsAllowClose.SetValue("0");
loadingEventArgs.SetDefaultView("DefaultView");
return true;
}
if (isOpenFromForm)//不允许修改,从表单链接打开,不允许关闭
{
xpIsAllowClose.SetValue("0");
}
else//不允许修改,从文档库链接打开,允许关闭
{
xpIsAllowClose.SetValue("1");
}
xpCanModify.SetValue("0");
loadingEventArgs.SetDefaultView("ReadOnlyView");
break;
};
}
}
}
}
catch (SPException spex)
{
return false;
}
return true;
}