Showing posts with label Sharepoint 2010. Show all posts
Showing posts with label Sharepoint 2010. Show all posts

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);
                }
            }
        }

Using jQuery & Windows Azure Marketplace

I'm trying to create an Office365 webpart that displays a 5 day weather forecast. The sandbox restrictions mean that I can't call the data service from the server side code, so I need to make the call client side.

Marketplace data is accessible as JSON, so using jQuery and a cryptography library called crypto.js to handle the authentication I should be able to return some data.

Here is a simple htm page that demonstrates a working call to the Azure Marketplace, the username can be anything but the password should be your Primary Account Key from the data subscription.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Windows Azure Data Market</title>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.min.js"></script> 
    <script type="text/javascript" src="http://crypto-js.googlecode.com/files/2.5.3-crypto-min.js"></script>   
    <script language="javascript" type="text/javascript">
        $(document).ready(function () {
            var user = "{username}";
            var pwd = "{primary account key}";
            $.support.cors = true;
            $.ajax({
                type: "GET",
                beforeSend: function (xhr) {
                    var bytes = Crypto.charenc.Binary.stringToBytes(user + ":" + pwd);
                    var base64 = Crypto.util.bytesToBase64(bytes);
                    xhr.setRequestHeader("Authorization", "Basic " + base64);
                },
                url: "https://api.datamarket.azure.com/DataGovUK/MetOfficeWeatherOpenData/Site?$top=100&$format=json",
                dataType: "json",               
                success: function (data) {
                    alert('success!');
                },
                error: function (jqXHR, textStatus, errorThrown) {                    
                    alert(errorThrown.message);
                }
            });
        })
    </script>
</head>
<body>

</body>
</html>

Add jQuery to Office365 Solutions

Becuase of the restrictions on Sharepoint 2010 Sandboxed solutions it's essential to be able to write functional code client side, as you are very restricted on what you can do on the server.

Writing Javascript can be extremely labourious without the assistance of some helper libraries like jQuery, and adding javascript links and code is even a challenge in a sandboxed solution.

Here's a cool way of adding jQuery code to Office365 or Sharepoint Online sandboxed solutions.

protected override void RenderContents(HtmlTextWriter writer)
        {
            StringBuilder js = new StringBuilder();

            js.AppendLine("$(document).ready(function(){");
            js.AppendLine(" alert('hello world!');");
            js.AppendLine("});");

            base.RenderContents(writer);

            writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
            writer.AddAttribute(HtmlTextWriterAttribute.Src, "//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js");
            writer.RenderBeginTag(HtmlTextWriterTag.Script);
            writer.RenderEndTag();

            writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
            writer.RenderBeginTag(HtmlTextWriterTag.Script);
            writer.WriteLine(js.ToString());
            writer.RenderEndTag();           
        }  

“Remote Desktop Links” Custom List in Office365

I have a lot of servers that I connect to using remote desktop. Windows only remembers the last 10 servers to which you connected. There are some 3rd party applications that manage your connections, but I wanted to use Sharepoint and not install another local application.

I wanted to use a Sharepoint list to store and manage my Remote Desktop Connections. I also wanted to be able to click on a hyperlink to open the connection.

There are several obstacles to this goal:

  1. There is no protocol for opening Remote Desktop Connections via a URL.
  2. Even if there was Sharepoint won’t let you use it. It will only allow http:// or https:// in a hyperlink column.
  3. The hyperlink column doesn’t give you the option of launching the link in a new window.

If I were using an on premise Sharepoint implementation I might consider creating a custom column, but I want this to work in Office365.

Creating a rdp:// URL Protocol

This will need a Registry tweak to add a new url protocol to mstsc.exe. Here is a reg file which will update the Registry with the new rdp:// protocol.

Windows Registry Editor Version 5.00
HKEY_CLASSES_ROOT\rdp]@="URL:Remote Desktop Connection" "URL Protocol"=""
HKEY_CLASSES_ROOT\rdp\DefaultIcon]@="C:\\WINDOWS\\System32\\mstsc.exe"
HKEY_CLASSES_ROOT\rdp\shell]
HKEY_CLASSES_ROOT\rdp\shell\open]
HKEY_CLASSES_ROOT\rdp\shell\open\command]@="cmd /V:ON /s /c set url=%1 && set url=!url:rdp:=! && set url=!url:/=! && start C:\\WINDOWS\\system32\\mstsc.exe /v:!url!"

Once you’ve updated your registry you will be able to create url links like this one which, when opened will launch a new Remote Desktop Connection to the server:

rdp://server

Save the above into a file called Remote Desktop Protocol.reg. The idea is to make this reg file available through the Sharepoint site for first time users who don’t have it. There is a problem in that because you won’t be able to upload a reg file to the Site Assets library, so I suggest adding it to a Remote Desktop Protocol.zip file first.

Create a redirect page

Sharepoint won’t let us use our new rdp:// protocol in a hyperlink column, so to get around this limitation we need to create a redirect page we can use in a hyperlink column and pass it the link we really want to open in the query string.

Open up your site in Sharepoint Designer. Select All Files under Site Objects, click the File button on the Ribbon and choose to create a new ASPX file.

Name the file redirect.aspx and copy in the markup below:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta name="WebPartPageExpansion" content="full" />
    <title>Remote Desktop</title>
 </head>
 <body>
    <script type="text/javascript">
      window.open(window.location.search.substring(1));
      window.history.back()
    </script>
  </body>
</html>

Create the Custom List

Open up your site and create a new Custom List and add a new hyperlink column to it.

Create a new list item and enter the following in the Hyperlink Url:

https://companyweb.sharepoint.com/site/redirect.aspx?rdp://server

If you have applied the registry tweak, when you open the link you should see a new Remote Desktop Connection open up.

Finally, I edited the default page and added some text along with a link to the Remote Desktop Protocol.zip file which I added to the Site Assets library. I also added a List Web Part to display a customised view of just the hyperlink column from my Custom List.  Here’s what it looks like:

Add a Stylesheet to a Page from a Sharepoint 2010 Sandbox WebPart

I have a module in my project that deploys a style.css document to the site Style Library.  I wanted to add a reference to this Stylesheet from within my WebPart code to the page.  I never anticipated that this would be a problem, even in a sandbox solution and the restrictions that came with it.

I couldn't use the Microsoft.Sharepoint.WebControls.CssRegistration class becuase I was in a sandbox solution.  So I thought let's try using standard .Net methods, like adding to the Page.Headers collection, but in my sandbox WebPart that property was always null.

I couldn't find anyway of adding the Stylesheet reference server side from a WebPart in a sandbox environment.  The only option left was to use client side javascript to dynamically add the reference to the DOM.

Here is a snippet of code from my WebPart that accomplishes this:
protected override void RenderContents(HtmlTextWriter writer)
        {
            StringBuilder js = new StringBuilder();

            js.AppendLine("var added = false");
            js.AppendLine("for (i = 0; (a = document.getElementsByTagName(\"link\")[i]); i++)"); 
            js.AppendLine("{");
            js.AppendLine("  if (a.getAttribute(\"rel\").indexOf(\"style\") != -1");
            js.AppendLine("      && a.getAttribute(\"href\").indexOf(\"kwsresourcebooking365\") != -1)");
            js.AppendLine("  {");
            js.AppendLine("    added = true;");
            js.AppendLine("  }");
            js.AppendLine("}");
            js.AppendLine("if(!added)");
            js.AppendLine("{");
            js.AppendLine("  var head = document.getElementsByTagName(\"head\")[0];");  
            js.AppendLine("  if(document.createStyleSheet)");
            js.AppendLine("  {");
            js.AppendLine("    document.createStyleSheet('" + SPContext.Current.Site.Url + "/style%20library/folder/style.css" + "');");
            js.AppendLine("  } else {");
            js.AppendLine("    var css = document.createElement('link');");
            js.AppendLine("    css.type = 'text/css';");
            js.AppendLine("    css.rel = 'stylesheet';");
            js.AppendLine("    css.href = '" + SPContext.Current.Site.Url + "/style library/folder/style.css" + "';");
            js.AppendLine("    head.appendChild(css);");                        
            js.AppendLine("  }");
            js.AppendLine("}");
           
            base.RenderContents(writer);

            writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
            writer.RenderBeginTag(HtmlTextWriterTag.Script);
            writer.WriteLine(js.ToString());
            writer.RenderEndTag();
        }
The Javascript will first check to see if the Stylesheet link exists in the head. If it doesn't exist then it will check to see if the createStyleSheet method is available, which essentially means that we are in Internet Explorer or else manually create link tag. I did consider using the Sharepoint client script manager, but I think there maybe sandbox restrictions there too and I was growing tired of hitting sandbox brick walls. I hope this saves you some time and frustration!

Sharepoint 2010 Weather WebPart

Here's my latest CodePlex Sharepoint 2010 project, it's a WebPart that displays a 5 day weather forecast.


The weather feed is from the Microsoft "Dallas" Project, you must sign up at www.sqlazureservices.com and subscribe to the Weather Central Global Forecast and Data Services - Weather Central, LLC (CTP2). You will need to enter your Account Key into the WebPart properties.

The location of the forecast is controlled by entering Latitude and Longitude values in the WebPart properties. I found this site very useful to search for a location and get the Latitude and Longitude values.

The images and stylesheet are loaded into the Site Collection Style Library. Any user who has access to this can change the weather icons or styles that the WebPart uses.

Note: Please make sure you have the necessary permissions/rights to use the icon images.

Sharepoint 2010 "Hyperlink with Picture" Column Type

I've recently published my latest CodePlex project. It's a Sharepoint 2010 Custom Field Type that extends the SPUrlField type to allow you to have an image instead of a text description as the link.

When creating a new column, the feature adds a new type called "Hyperlink with Picture"



The new column is exactly the same as a Hyperlink type column, but instead of displaying the link as the description text, it shows an image instead.



When editing a list item you enter the target url and the url to the image you want to display for the link



When you view the list item the image will display and open the target url link when clicked

Sharepoint 2010: Resource Booking WebPart

I had a request to build a simple generic, configurable, weekly resource booking web part for Sharepoint 2010.  Here is a list of the design goals that we set:
  • Ability to have multiple resource booking webparts on a single Sharepoint Web
  • The available resources should be configurable by the end users
  • A weekly view of bookings
  • Ability to easily identify resources that can be booked and to filter the weekly view
  • Validation to guard against double bookings
  • Ability to delete bookings you created
  • Defaults to make creating new bookings quick and easy
  • Use AJAX to avoid ugly post backs (page reloads)
The resulting Sharepoint Feature contains a WebPart which is deployed at Site level and can be used in any site within the Site Collection.  A Web level Feature contains List Templates for the Resource list and the Bookings list which can be activated by any site administrator that wants to use the Resource Bookings WebPart.

The Resource Booking WebPart has two custom properties to link it to the Resource list and the Bookings list.  The Resource list is included on the Quick Launch menu, while the Bookings list in not.  It's possible to change this, of course, along with list permissions and adding your own views to the Bookings list to extend functionality for the users.

Below is a screenshot of the Resource Booking WebPart.


The Resource Booking WebPart defaults to show the current week starting on a Monday.  At the top it shows the week start date and end date, there are arrow buttons to navigate to the previous and next weeks.

Each box shows the Bookings for a particular day sorted in time order.  Bookings with a red cross next to them are one's made by the currently logged on user, clicking the red cross will delete the booking.  You can only delete your own bookings using the WebPart, you could open the Bookings list and delete bookings from there if you have sufficient permissions.  Hovering the mouse over a booking will popup who own's that booking, as shown in the screenshot.

In the bottom right hand corner is a list of all the resources that are available for booking.  This list is created from the Resources list which is managed by the users.  Checking the boxes against the resources will filter the view to show only those bookings against the selected resources.  You can filter the view and still navigate to previous or next weeks keeping the chosen filter in effect.

To create a new booking simply click on the icon.  Below is a screenshot of the New Resource Booking view:


The From time defaults to the current time and the To time to the current time plus one hour.  The Owner will default to the currently logged on user.  All of the fields are required and there is validation in place to ensure there is no double booking of resources.  The validation error message will tell you who has the resource booked when the times they have it reserved.

Sharepoint: Document Control

This is a 10 minute video demo of Sharepoint Document Control.  It shows the creation of a new Controlled Document and it's lifecycle through approval and revision.  You can see the integration with Microsoft Word and how the document can be revised whilst the original published document remains the only version  available to the readers. 

It is built on a standard Sharepoint Document Library which has versioning enabled and enforced check-in and check-out.  There are no additional webparts, only standard views and workflow tasks.  You can customise everything around the famework that is in place including the views, fields, word template and tasks.

I hope you find the video informative, please post any questions as comments and I will do my best to answer them.  I hope to produce some further videos that show the document review workflows and change request features.

Depending on your screen resolution you might find watching the video on the YouTube site in full screen provides a better view of the demo.  Please click here to open the YouTube page.



Sharepoint 2010 Workflow shortcut from a Ribbon Button

Here’s a snippet of code that I’ve used to create a shortcut from a list item form ribbon button to a workflow initiation form.
var workflow;

function SubmitForReview() {
    var context = SP.ClientContext.get_current();
    var web = context.get_web()
    var list = web.get_lists().getById(parent.ctx.listName);
    var workflows = list.get_workflowAssociations();
    workflow = workflows.getByName("Submit For Review")
    context.load(workflow);
    context.executeQueryAsync(onSuccessMethod, onFailureMethod);
}

function onSuccessMethod(sender, args) {
    var workflowUrl = '/_layouts/yourworkflow/initiation.aspx?List=' + parent.ctx.listName + '&ID=' + parent.ctx.ctxId + '&TemplateID=' + workflow.get_id() + '&Source=' + parent.ctx.listUrlDir;
    parent.location.href = workflowUrl;
}

function onFailureMethod(sender, args) {
    alert('request failed ' + args.get_message() + '\n' + args.get_stackTrace());
}