事情是这样的:我们通过Infopath设计了一些电子表单,用来在企业内部进行一些流程审批的载体。结合SharePoint Server所提供的Forms Service,我们可以比较便捷地实现,在线填写也很方便,如下图所示
在浏览器中填写的效果如下
注意:这只是一个演示表单,我简单做了几个字段而已。重点要体现附件。
那么,现在的需求是这样:因为这些表单越来越多,而且大多有附件,导致SharePoint的内容数据库越来越大,速度受到一定的影响。用户想到一个做法,就是定期将那些已经完成审批的表单归档,而且从这个表单库中删除掉。
在归档的时候,就会遇到一个问题,如何将附件也归档,并且放到指定的磁盘文件夹上去。
首先,我们需要了解的是,Infopath的附件默认是怎么存储的呢?Infopath表单其实就是一份特殊的XML文件,它会将所有的信息,包括附件在内都保存在一个XML文件中。当然,附件会通过编码成Base64的字符串保存。我们可以将之前填写好的这个表单保存下来一份数据,以便了解它里面的结构
这个文件,我们可以用记事本直接打开
大家可以看到,其实附件的内容都是保存在这个xml文件里面的。
通过一些研究,我实现了如下的解决方案,这是一个原型,可以作为一个参考。
【备注】具体在做的时候,还要细致一些将所有数据都妥善保存,本例重点演示如何保存附件。其他常规的数据应该很容易处理。可以读取出来存放在数据库中的一个表中。
下面这个类型是我在网上找到的,不是我的原创。这个类型是一个解码器,可以将上面的Base64String还原为一个字节数组
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
/// <summary>
/// Class used to decode an InfoPath attachment.
/// Pulls the file name and the decoded file from either a base 64 byte array or string.
/// </summary>
public class InfoPathAttachmentDecoder
{
// Private string to hold the attachment name.
string _fileName;
// Private byte array to hold the decoded attachment.
byte[] _decodedFile;
/// <summary>
/// The name of the file within the InfoPath attachment.
/// </summary>
public string Filename
{
get { return _fileName; }
}
/// <summary>
/// The decoded file within the InfoPath attachment.
/// </summary>
public byte[] DecodedFile
{
get { return _decodedFile; }
}
/// <summary>
/// Constructor for the InfoPathAttachmentDecoder Class
/// </summary>
/// <param name="base64EncodedString">The attachment represented by a string</param>
public InfoPathAttachmentDecoder(string base64EncodedString)
{
// Use unicode encoding.
Encoding _encoding = Encoding.Unicode;
// The byte array containing the data.
byte[] _data = Convert.FromBase64String(base64EncodedString);
// Use a memory stream to access the data.
using(MemoryStream _memoryStream = new MemoryStream(_data))
{
// Create a binary reader from the stream.
BinaryReader _theReader = new BinaryReader(_memoryStream);
// Create a byte array to hold the header data.
byte[] _headerData = _theReader.ReadBytes(16);
// Find the file size before finding the file name.
int _fileSize = (int)_theReader.ReadUInt32();
// Get the file name.
int _attachmentNameLength = (int)_theReader.ReadUInt32() * 2;
byte[] _fileNameBytes = _theReader.ReadBytes(_attachmentNameLength);
_fileName = _encoding.GetString(_fileNameBytes, 0, _attachmentNameLength - 2);
// Get the decoded attachment.
_decodedFile = _theReader.ReadBytes(_fileSize);
}
}
/// <summary>
/// Constructor for the InfoPathAttachmentDecoder Class
/// </summary>
/// <param name="base64EncodedBytes">The attachment represented by a byte array</param>
public InfoPathAttachmentDecoder(byte[] base64EncodedBytes) : this(Convert.ToBase64String(base64EncodedBytes)) { }
/// <summary>
/// Static method that gets the file from the attachment.
/// </summary>
/// <param name="base64EncodedString">The attachment represented by a string</param>
/// <returns>Returns a byte array of the file in the attachment.</returns>
public static byte[] DecodeInfoPathAttachment(string base64EncodedString)
{
// Create an instance of the InfoPathAttachmentDecoder
InfoPathAttachmentDecoder _infoPathAttachmentDecoder = new InfoPathAttachmentDecoder(base64EncodedString);
// Return the decoded file.
return _infoPathAttachmentDecoder.DecodedFile;
}
/// <summary>
/// Static method that gets the file from the attachment.
/// </summary>
/// <param name="base64EncodedBytes">The attachment represented by a byte array</param>
/// <returns>Returns a byte array of the file in the attachment.</returns>
public static byte[] DecodeInfoPathAttachment(byte[] base64EncodedBytes)
{
// Create an instance of the InfoPathAttachmentDecoder
InfoPathAttachmentDecoder _infoPathAttachmentDecoder = new InfoPathAttachmentDecoder(base64EncodedBytes);
// Return the decoded file.
return _infoPathAttachmentDecoder.DecodedFile;
}
}
然后,我们需要实现对XML文档的读取。虽然读XML文件向来都不是什么大问题,但Infopath的XML文档结构还是挺繁琐的,有很多命名空间,直接读取相当费时费力。我一般会用下面的方式
1. 打开Visual Studio Command Prompt
2. 根据xml文件生成xsd(架构)
3.根据xsd文件生成一个强类型的class
准备工作做好了,下面我们做一个简单的程序来实现一下整个存档的逻辑
1. 创建一个Windows Forms程序
【注意】选择.NET Framework 3.5
2. 设置编译平台为x64(这是访问SharePoint服务器对象模型的要求)
3. 做一个简单的界面如下
4. 引用Microsoft.SharePoint.dll
5. 将之前生成好的10248.cs和写好的InfoPathAttachmentDecoder 类型添加到项目中来
6.编写代码
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Serialization;
using Microsoft.SharePoint;
namespace FormArchiver
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btStart_Click(object sender, EventArgs e)
{
var path = txtLibPath.Text;
var folder = txtFolder.Text;
var site = new SPSite(path);
var web = site.OpenWeb();
var list = web.GetList(path);
var items = list.Items;
foreach(SPListItem item in items)
{
var file = item.File;
var stream = file.OpenBinaryStream();
var serializer = new XmlSerializer(typeof(myFields));//这里的myFields这个类型,是之前通过xsd工具根据表单结构生成的
var result =(myFields) serializer.Deserialize(stream);
var attachment = new InfoPathAttachmentDecoder(result.group3.FirstOrDefault().field4);
var fileName = attachment.Filename;
var buffer = attachment.DecodedFile;
if(!Directory.Exists(folder))
Directory.CreateDirectory(folder);
var targetPath = Path.Combine(folder, fileName);
File.WriteAllBytes(targetPath, buffer);
}
MessageBox.Show("保存完成");
}
}
}
这个程序运行起来的效果大致如下
点击“开始”,很快的我们就可以将附件保存出来到预设的目录
这个演示程序的源代码,请通过这里下载