Friday 5 October 2012

Packaging InfoPath Forms into Site Features

Ever had an InfoPath Form that you wanted to wrap up into a re-deployable feature ?

The problem is that any data connections will still point to the their original locations and the Form itself will have an incorrect PublishURL.

An InfoPath Form is really a cabinet (.cab) file.  Unfortunately it's not part of the Open Office XML SDK, so you can't use the System.IO.Packaging class in the WindowsBase dll to extract it, this only works with zip files.  I didn't want to sacrifice my principles and start using unmanaged code or shelling out calls to cabarc.exe, so I scoured the internet for an alternative solution.  I found that the WiX open source installer project has some assemblies that can extract and package cabinet files:

Microsoft.Deployment.Compression.Cab.dll
Microsoft.Deployment.Compression.dll

Just download and install WiX and reference the above 2 files from the WiX SDK folder.

InfoPath has the ability to use external data connection files and Sharepoint has a Data Connections Library template to store them in.  This solution depends on this feature as we'll need to update any data connections that the form uses in our feature activated event reciever.  I use Sharepoint 2010 Foundation which runs on my Windows 7 laptop, the Data Connections Library template isn't available out of the box in the Foundation edition, but it is part of Search Server 2010 Express.  So I downloaded and installed Search Server 2010 Express, but I didn't configure it as I'm not really interested in actually using it.

As a scenario to ascertain the feasibility of creating a re-deployable InfoPath feature I created a Form that had a 2 data connection files.  One was a receive data connection that was connected to the site user list and the other a submit connection to a Form Library.

Now that we have all the pre-requisites needed it's time to fire up Visual Studio and create a new empty Sharepoint 2010 project.

Add a new List Instance, I called mine Data Connections.  Here is the Elements.xml file, note the TemplateType is set to 130 which is a Data Connection Library.

  
  

I added a new module called Connection Files and copied in the 2 data connection files, here's what the Elements.xml file looks like.


  
    
      
    
    
      
    


Now that we have a Data Connections Library with some connections in it, I'll add a new Content Type item called DCTest for the InfoPath Form. Here's the Elements.xml:


  
  
    
    
        
  

I added another Module called Form Template to the project with the xsn file in it.


  
    
  

Finally, just before we get into coding the feature activated event receiver I added another List Instance item to the project called Form Library.


      
  

Now that we have everything it's time to write the feature activated event receiver. The first job it to get a reference to the Web where the feature is being activated so that we know the new url for the InfoPath Form and Data Connections. I download a copy of the XSN file to the system temp folder, where I extract it using the WiX classes. The Manifest.xsf file can then be updated before packing it back into the .xsn file and uploading it back to Sharepoint. The data connection files are easier to update as they are just xml.
public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {        
            XNamespace ns;
            XDocument doc;
            string tempPath = Path.GetTempPath();
            string contentTypeName = "DCTest";
            using (SPWeb web = (SPWeb)properties.Feature.Parent)
            {
                SPContentType contentType = web.ContentTypes[contentTypeName];
                WebClient webClient = new WebClient();
                webClient.Credentials = CredentialCache.DefaultCredentials;         
                webClient.DownloadFile(web.Site.Url + contentType.DocumentTemplateUrl, tempPath + contentType.DocumentTemplate);
                CabInfo cab = new CabInfo(tempPath + @"\" + contentType.DocumentTemplate);
                Directory.CreateDirectory(tempPath + contentTypeName);                
                cab.Unpack(tempPath + contentTypeName);               
                doc = XDocument.Load(tempPath + @"\" + contentTypeName + @"\manifest.xsf");
                ns = "http://schemas.microsoft.com/office/infopath/2006/solutionDefinition/extensions";
                doc.Root.Attributes("publishUrl").First().Value = web.Url + "/FormLibrary/Forms/DCTest/DCTest.xsn";
                foreach (XElement elem in doc.Descendants(ns + "connectoid"))
                {
                    elem.Attribute("siteCollection").Value = web.Site.Url;
                    elem.Attribute("source").Value = "/forms/DataConnections/" + elem.Attribute("source").Value.Substring(elem.Attribute("source").Value.LastIndexOf(@"/") + 1);
                }
                doc.Save(tempPath + @"\" + contentTypeName + @"\manifest.xsf");
                cab.Pack(tempPath + @"\" + contentTypeName);
                webClient.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";                
                webClient.UploadFile(web.Site.Url + @"/forms/_cts/DCTest/DCTest.xsn", "PUT", tempPath + @"\" + contentType.DocumentTemplate);

                //Remove the default content type and add the site content type
                SPList library = web.Lists["Form Library"];
                library.ContentTypes[0].Delete();
                library.ContentTypes.Add(web.ContentTypes["DCTest"]);

                //Update the data connections
                SPList list = web.Lists["Data Connections"];
                foreach (SPFile file in list.RootFolder.Files)
                {

                    using (StreamReader sr = new StreamReader(file.OpenBinaryStream()))
                    {
                        doc = XDocument.Parse(sr.ReadToEnd());
                    }
                    ns = "http://schemas.microsoft.com/office/infopath/2006/udc";
                    if (file.Name == "DC001-Receive.udcx")
                    {
                        doc.Descendants(ns + "WebUrl").First().Value = web.Url;
                        doc.Descendants(ns + "ListId").First().Value = "{" + web.SiteUserInfoList.ID.ToString() + "}";
                    }
                    else if (file.Name == "DC001-Submit.udcx")
                    {
                        doc.Descendants(ns + "FolderName").First().Value = web.Url + "/FormLibrary";
                    }
                    byte[] byteArray = Encoding.ASCII.GetBytes(doc.Root.ToString());
                    MemoryStream stream = new MemoryStream(byteArray);
                    file.SaveBinary(stream);
                }
            }
        }

4 comments:

  1. Great post. It works perfectly for me. One question though, have you tried to deal with IP form upgrade scenario? I’m trying to get that scenarios to work flawlessly, but I'd like to hear from you if you have already walked this path.

    Assuming that I'm deploying a new version of the previously deployed codeless IP form, which maintains the same data structure (ie, same XML SCHEMA for the archived data), is it required to associate the upgraded XSN with a new ContentType that we will add on form list using a feature activation event? Is SharePoint content type inheritance the only way to go? What happen if I just replace the XSN through upload to _cts ContentType folder?

    Thanks for your time.

    Best regards, Giorgio Arata.

    ReplyDelete
  2. Hi Giorgio,

    I've never had to face the upgrade scenario yet. I'd try replacing the XSN first as if it works then it's an easy solution. Worst case is that you'll need to add a new content type. Please let me know how you get on if you attempt it!

    Thanks,
    Ian.

    ReplyDelete
  3. Hi ian,

    I am using this process for deploying forms and configuring their data sources etc at run time. The code works beautifully, but when it tries to repack the xsan file (cab.pack(...)) it fails saying the directory is invalid. If I open the directory path it is using in windows explorer, it opens fine so not sure what is the issue.

    Have you seen this before?

    ReplyDelete
  4. đồng tâm
    game mu
    cho thuê nhà trọ
    cho thuê phòng trọ
    nhac san cuc manh
    số điện thoại tư vấn pháp luật miễn phí
    văn phòng luật
    tổng đài tư vấn pháp luật
    dịch vụ thành lập công ty trọn gói
    nước cờ trên bàn thương lượng
    mbp
    erg
    nghịch lý
    chi square test
    nghệ thuật nói chuyện
    coase
    thuyết kỳ vọng
    chiến thắng con quỷ trong bạn
    cân bằng nash

    - Không biết. Ai biết được, có lẽ do thằng này mang tới.
    Thằng thanh niên nói.

    - Là bọn họ, hắn kêu bọn chúng tới.
    Đồng Úc không nhịn được mà đứng ra nói.

    - Vậy ư? Rất đơn giản. Liễu Bình, cậu cầm mấy ống tuýp lên, về lấy dấu vân tay là xong.
    Cảnh sát dẫn đầu thản nhiên nói.

    Thanh niên hơi tái mặt nhưng lập tức không thèm để ý. Y trừng mắt nhìn sang bên này rồi chửi thề. Y lại đứng sang bên gọi điện.

    Triệu Quốc Đống và Cù Vận Bạch cũng không để ý tới đối phương. Bọn họ kể qua về tình hình với cảnh sát. Lúc này tên thanh niên Dũng tử cũng giải thích với cảnh sát. Cũng không có ai bị thương nặng, xem có thể hòa giải hay không?

    Lúc này đội trưởng bảo vệ của khách sạn cũng tới. Thấy cảnh sát liền vội vàng mời thuốc, một bên kéo Cảnh sát dẫn đầu sang bên nói nhỏ gì đó.

    Triệu Quốc Đống thấy tình hình có vẻ không ổn. Mặt Cảnh sát dẫn đầu không ngừng thay đổi giống như đang suy nghĩ gì đó.

    Chờ ghi chép xong, Cù Vận Bạch càng lúc càng xấu hổ, một tay che ngực, một tay giữ váy, cô rất nhục. Nhưng thái độ của cảnh sát rất cứng, không cho phép đi.

    - Đưa hết đến Đồn công an.
    Cảnh sát dẫn đầu có lẽ nhận được điện nên rất miễn cưỡng nói.
    - Ông anh, lãnh đạo của tôi là nữ, quần áo bị đối phương xé có thể cho cô ấy đi thay bộ khác không? Tôi đi với các anh, chị ấy thay xong sẽ lập tức tới.
    Triệu Quốc Đống nói với Cảnh sát dẫn đầu.

    - Không được, phải đi cùng, phải đối xử công bằng.
    Thằng thanh niên nhảy dựng lên nói:
    - Chẳng lẽ bọn mày có đặc quyền gì sao?

    - Đối xử công bằng? Chẳng lẽ nói bị hại và tội phạm đối xử như nhau. Nực cười.
    Triệu Quốc Đống không thèm để ý tới đối phương, hắn nhìn Cảnh sát dẫn đầu rồi nói:
    - Tôi ở đây, tất cả tôi đều tham gia, chẳng lẽ còn sợ các cô ấy chạy?

    - Có thể, cho các người một tiếng, một tiếng sau hai cô này nhất định phải tới Đồn công an Lưỡng Lộ Khẩu trình diện.

    ReplyDelete