Upload file to SharePoint with Silverlight

This is post is intended to respond to all developers who need a custom upload control. Majority of the solutions on internet are server side related, but in some cases only client side is allow and SharePoint Designer is the only tool you have.  The solution is a small Silverlight application which is able to convert a file into a byte array and pass it as encoded string to web service.

For the beginning, we need to create a class to read file from computer and transform it to base64 encoded string. The code below should do the work for you.

using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.ComponentModel.DataAnnotations;
using System.Windows.Documents;

public class FileUpload
{
        BinaryReader reader;
        long fileSize;

        /// <summary>
        /// Initialize FileUpload class
        /// </summary>
        /// <param name="fs">FileStream</param>
        /// <param name="size">Size of file</param>
        public FileUpload(FileStream fs, long size)
        {
            reader = new BinaryReader(fs);
            fileSize = size;
        }

        /// <summary>
        /// Read stream and convert it into base64 encoded string
        /// </summary>
        /// <returns>Base64 encoded string</returns>
        [ScriptableMember()]
        public string ReadFully()
        {
            byte[] output = new byte[fileSize];
            for (long i = 0; i < fileSize; i++)
            {
                output[i] = reader.ReadByte(); //read until done
            }
            return Convert.ToBase64String(output);
        }

}

As we need to use some JavaScript functions here, we can create a static C# class to invoke these. This would make usage of JavaScript easier.

using System;
using System.Net;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;

public static class JavaScriptInvoker
{
        /// <summary>
        /// Calling a javascript simple function without parameters
        /// </summary>
        /// <param name="fName">Function name</param>
        public static void Call(string fName)
        {
            try
            {
                HtmlPage.Window.Invoke(fName);
            }
            catch (Exception ex)
            {
                JavaScriptInvoker.Alert(ex.Message.ToString());
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="fName"></param>
        /// <param name="fParams"></param>
        public static void Call(string fName, string[] fParams)
        {
            try
            {
                HtmlPage.Window.Invoke(fName, fParams);
            }
            catch (Exception ex)
            {
                JavaScriptInvoker.Alert(ex.Message.ToString());
            }
        }

         /// <summary>
         /// Display an alert message in window
         /// </summary>
         /// <param name="message">Text to be displayed in alert</param>

        public static void Alert(string message)
        {
            HtmlPage.Window.Alert(message);
        }
}

The interface is simple. It contains a textbox and two button. Textbox (name:FileName) is displaying file name, browse button (name:UploadFile) opens file dialog picker and upload button (name:btnUpload) starts upload operation.

Silverlight upload

Here is the source for this page (MainPage.xaml).

public partial class MainPage : UserControl
{

        private FileStream fs;
        private string fName = "";

        public MainPage()
        {
            InitializeComponent();
        }

        // Browse for file to upload
        private void UploadFile_Click(object sender, RoutedEventArgs e)
        {

            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Multiselect = false;

            if (dlg.ShowDialog() == true)
            {
                fs = dlg.File.OpenRead();
                fName = dlg.File.Name.ToString();
                this.FileName.Text = fName;
            }
            else
            {
                fs = null;
                fName = "";
                this.FileName.Text = fName;
            }
        }

        private void btnUpload_Click(object sender, RoutedEventArgs e)
        {
            if ((this.fs != null) && (this.fName != ""))
            {
                App myApp = App.Current as App;

                FileUpload uploader = new FileUpload(this.fs, fs.Length);
                JavaScriptInvoker.Call("AttachFile", new string[] { myApp.SPListItemId, myApp.SPListName, this.fName, uploader.ReadFully() });
            }
        }

 }

SPListeItemId and SPListName are initiation parameters and be passed from dataformwebpart. AttachFile is JavaScript function accept list id, list item id, attachment file name and file base64 encoded as parameters and consume lists.asmx web service, using AddAttachment method.

Advertisements

Embed Silverlight in a SharePoint page.

Silverlight offer the possibility to work with SharePoint and execute some lists operations using Client Object Model. You can create an application and embed it to a SharePoint page using SharePoint Designer editor.

Embed Silverlight in SharePoint

You can note MS.SP.url initiation parameter which should be set to the url of you SharePoint. Without this you will receive an error using C# Silverlight Client Object Model.

Validate function parameters in JavaScript

JavaScript is not so well structured like other languages (C#, Java). When you define a variable like this

var myVariable;

You do not specify type of variable. It can be string, object, array or other JavaScript type. So, passing variable to a function can a problem as very easy you can pass a type which is not accepted by the function. But, ASP.NET Ajax is offering a way to validate parameters. Consider the code below:

function DoSomething(expectedVariable) {
	var e = Function._validateParams(arguments, [{ name: "expectedVariable", type: String }], true);
 	if (e) {  throw e; }
}

Code above is checking is if parameter passed to DoSomething function is a string and stop code execution if condition is not true, throwing an error. Other types can be also checked: Object, Array, Function. You can also specify if parameter is option or if it can be null.

If you are asking from where Function._validateParams comes from, you can check ASP.NET AJAX JavaScript library. It was supposed to be used internally (“-“ sign, by convention, means property or method is private), but without no issues you can use it directly in your code. If you have version 4 of the library, Microsoft included also a public version of this called “Function.validateParameters”. Please find official reference here.

ASP.NET Ajax JavaScript simple inheritance example

Seems that ASP.NET Ajax client library is not very often used. Microsoft seems is not extending this anymore and recommend JQuery instead. Personally I see no reason why this should not be used in SharePoint client side development. It offers a lot of flexibility when working with JavaScript classes and very easy you can extend a class by deriving a new from it. Check this.

Type.registerNamespace("SPControls");

SPControls.Generic = function (spControl) {
/// <signature>
///     <summary>Initialize SPControls.Generic class</summary>
///     <param name="spControl" type="" maybeBeNull="True" optional="False">Form control of anytype: checkbox, text, select, textarea</param>
/// </signature>

this.spControl = spControl;
}

SPControls.Generic.prototype = {

	get_baseControl: function () {
	/// <signature>
	///     <summary>Create observer on form control</summary>
	///     <returns>Form element</returns>
	/// </signature>
		return this.spControl;
	},

	observe: function (method, args) {
	/// <signature>
	///     <summary>Create observer on form control</summary>
	///     <param name="method" type="Function" maybeBeNull="False" optional="False">Form control of anytype: checkbox, text, select, textarea</param>
	///     <param name="args" type="Array" maybeBeNull="False" optional="False">Additional arguments to be passed to the callback method. First parameter is passed base control of the instance.</param>
	///     <returns type="Boolean"></returns>
	/// </signature>

		var e = Function._validateParams(arguments, [{ name: "method", type: Function }, { name: "args", type: Array }], true);
		if (e) {
			alert("Invalid parameters in observe method of control " + this.spControl.id + ": " + e);
			throw e;
		}

		if (this.spControl === null) return false;

		var _ctl = this.spControl;
		$addHandler(this.spControl, "change", function () { method.apply(this, [_ctl].concat(args)); }, false);
		return true;
	},

	addCssClass: function (clsName) {
	/// <signature>
	///     <summary>Add CSS class on form control</summary>
	///     <param name="clsName" type="String" maybeBeNull="False" optional="False">CSS class name to be aplied.</param>
	///     <returns type="Boolean">True if control exists or false if not.</returns>
	/// </signature>

		var e = Function._validateParams(arguments, [{ name: "clsName", type: String }], true);
		if (e) {
			alert("Invalid CSS class name to be added on control " + this.spControl.id + ": " + e);
			throw e;
		}

		if (this.spControl === null) return false;
		Sys.UI.DomElement.addCssClass(this.spControl,clsName);
		return true;
	},

	removeCssClass: function (clsName) {
	/// <signature>
	///     <summary>Remove CSS class for form control</summary>
	///     <param name="clsName" type="String" maybeBeNull="False" optional="False">CSS class name to be removed.</param>
	///     <returns type="Boolean">True if control exists or false if not.</returns>
	/// </signature>

		var e = Function._validateParams(arguments, [{ name: "clsName", type: String }], true);
		if (e) {
			alert("Invalid CSS class name to be removed from control " + this.spControl.id + ": " + e);
			throw e;
		}

		if (this.spControl === null) return false;
		Sys.UI.DomElement.removeCssClass(this.spControl,clsName);
		return true;
	},

	toogleCssClass: function (clsName) {
	/// <signature>
	///     <summary>Toogle CSS class on form control</summary>
	///     <param name="clsName" type="String" maybeBeNull="False" optional="False">CSS class name to be toogled.</param>
	///     <returns type="Boolean">True if control exists or false if not.</returns>
	/// </signature>

		var e = Function._validateParams(arguments, [{ name: "clsName", type: String }], true);
		if (e) {
			alert("Invalid CSS class name to be toogled from control " + this.spControl.id + ": " + e);
			throw e;
		}

		if (this.spControl === null) return false;
		Sys.UI.DomElement.toggleCssClass(this.spControl, clsName);
		return true;
	}
}
SPControls.Generic.registerClass("SPControls.Generic");

The class above is intended to be a generic from control created inside a namespace SPControls. This class includes generic methods and properties for textbox or single select control. Now I can built textbox class without rewriting all of these.

SPControls.DVTextBox = function (spControl) {
/// <signature>
///     <summary>Initialize SPControls.DVTextBox class</summary>
///     <param name="spControl" type="" maybeBeNull="True" optional="False">Input text control.</param>
/// </signature>
	SPControls.DVTextBox.initializeBase(this, [spControl]);
}

SPControls.DVTextBox.prototype = {

	getValue: function () {
	/// <signature>
	///     <summary>Get the value of the input text control</summary>
	///     <returns type="String">Control value of empty string if control is not found.</returns>
	/// </signature>
		if (this.spControl === null) return "";
		return this.spControl.value.toString().trim();
	},

	getText: function () {
	/// <signature>
	///     <summary>Get the value of the input text control</summary>
	///     <returns type="String">Control value of empty string if control is not found.</returns>
	/// </signature>
		return this.getValue();
	},

	setValue: function (val) {
	/// <signature>
	///     <summary>Set the value of the input text control</summary>
	///     <returns type="Boolean">True if control exists and false is not.</returns>
	/// </signature>

		var e = Function._validateParams(arguments, [{ name: "val", type: String }], true);
		if (e) {
			alert("Unable to set the value of control control " + this.spControl.id + ": " + e);
			throw e;
		}

		if (this.spControl === null) return false;
		this.spControl.value = val.trim();
		return true;
	},

	setText: function (val) {
	/// <signature>
	///     <summary>Set the value of the input text control</summary>
	///     <returns type="Boolean">True if control exists and false is not.</returns>
	/// </signature>

		var result = this.setValue(val);
		return result;
	}
}
SPControls.DVTextBox.registerClass("SPControls.DVTextBox", SPControls.Generic);

And not it is single select control turn. Noticed we do not repeat methods and properties which this control has in common with textbox.

SPControls.DVDropDown = function (spControl) {
/// <signature>
///     <summary>Initialize SPControls.DVDropDown class.</summary>
///     <param name="spControl" type="" maybeBeNull="True" optional="False">Select form control.</param>
/// </signature>
	SPControls.DVDropDown.initializeBase(this, [spControl]);
}

SPControls.DVDropDown.prototype = {

	getValue: function () {
	/// <signature>
	///     <summary>Get the value of the single select control</summary>
	///     <returns type="String">Selected option value or empty string if select control is not found.</returns>
	/// </signature>
		if (this.spControl === null) return "";
		return this.spControl.options[this.spControl.selectedIndex].value.trim();
	},

	getText: function () {
	/// <signature>
	///     <summary>Get the value of the single select control</summary>
	///     <returns type="String">Selected option value or empty string if select control is not found.</returns>
	/// </signature>
		if (this.spControl === null) return "";
		return this.spControl.options[this.spControl.selectedIndex].text.trim();
	},

	setValue: function (val) {
	/// <signature>
	///     <summary>Set selected index of single select based on option value.</summary>
	///     <params name="val" type="String" optional="False" mayBeNull="False">True if control exists and option value is present in the options list or false if not.</params>
	///     <params name="caseSensitive" type="Boolean" optional="True" mayBeNull="True">True if control exists and option value is present in the options list or false if not.</params>
	///     <returns type="Boolean">True if control exists and option value is present in the options list or false if not.</returns>
	/// </signature>

		var e = Function._validateParams(arguments, [{ name: "val", type: String, optional: false, mayBeNull: false }, { name: "caseSensitive", type: Boolean, optional: true, mayBeNull: true }], false);
		if (e) {
			throw e;
		}

		if (this.spControl === null) return false;
		var opts = this.spControl.options;
		for (var i = 0; i < opts.length; i++) {
			if (this.spControl.options[i].value.trim() === val.trim()) {
				this.spControl.selectedIndex = i;
				return true;
				break;
			}
		}
		return false;
	},

	setText: function (val, caseSensitive) {
	/// <signature>
	///     <summary>Set selected index of single select based on option value.</summary>
	///     <params name="val" type="String">True if control exists and option value is present in the options list or false if not.</params>
	///     <params name="caseSensitive" type="Boolean">True if control exists and option value is present in the options list or false if not.</params>
	///     <returns type="Boolean">True if control exists and option value is present in the options list or false if not.</returns>
	/// </signature>

		if (this.spControl === null) return false;
		var opts = this.spControl.options;
		for (var i = 0; i < opts.length; i++) {
			var isMatch = (this.spControl.options[i].text.trim() === val.trim());
			if (this.spControl.options[i].text.trim() === val.trim()) {
				this.spControl.selectedIndex = i;
				return true;
				break;
			}
		}
		return false;
	}
}
SPControls.DVDropDown.registerClass("SPControls.DVDropDown", SPControls.Generic);