Thursday 28 May 2015

Using skin file in SharePoint Project

Building many custom input form for SharePoint which use many control style/configure can be tedious work. Especially if you have many developer working on the same project. CSS can be used for this purpose, but it work for general and not specific to styling certain control. ASPX have skin file that can be used for theme in web application project and it is for specific control. I've experimented on how to add this skin file to SharePoint project and use that skin feature for our SharePoint visual web part. This is how I've done it to make our project use skin file for configuring and styling web control easier.
  1. In your SharePoint Project, create folder "App_Themes"
  2. Inside that folder create a sub folder with name "Default" (we use Default for skin name)
  3. Add text file item into that folder and name it Default.skin (filename should be same with its folder)
  4. Add control you want to skin, for this example I'll use TextBox with SkinID "DefaultInput"

    <asp:TextBox runat="server" SkinID="DefaultInput" Width="200px"></asp:TextBox>
    

  5. As you can see I only add folder and not special SharePoint folder that will be packaged inside wsp file. This folder won't get deployed whenever we deploy this project and want to apply some skin, we need to get around with this problem.
  6. Skin files need to be put under IIS root. We could copy it manually into SharePoint folder inside wwwroot but I prefer creating cmd files that will copy it.
  7. Again add text file item and give it a name and add extension .cmd, I'll name it "DeploySkinFile.cmd"
  8. Insert this dos command into that file, this command will copy all files under App_Themes folder to "C:\inetpub\wwwroot\wss\VirtualDirectories\80"

    SET ACTIVEDIR=%1
    SET TARGETASP=C:\inetpub\wwwroot\wss\VirtualDirectories\80
    cd %ACTIVEDIR%
    IF not exist %TARGETASP%\App_Themes ( md %TARGETASP%\App_Themes )
    xcopy App_Themes %TARGETASP%\App_Themes /E /G /Y
    

  9. We need to run this automatically when we built this project. Right click on Project name -> Properties to open its properties window and head to "Build Events" tab
  10. Add this command under "Post-build event command line"

    "$(ProjectDir)DeploySkinFile.cmd" "$(ProjectDir)"
    

  11. We need to tell aspx to use this skin file, this can be done on page directive or on pages tag inside web.config. Since I'm using this for visual web part, I'm going to put it inside web.config. Open web.config under root folder of SharePoint, add this on the last tag pages
  12. Let's create a visual web part with textbox control and SkinID="DefaultInput" to test this.
  13. Now deploy the project and add that visual web part. Browse to it and view the source, the textbox will have width 200px as defined in skin file above
Download sample project here

Thursday 14 May 2015

Create REST for Upload file and Transferring Parameter

We used SharePoint as a repository for some of our documents and was using CSOM as a way to upload it from another application. The problem came when we upload file and set some of its property after upload it. It turned out that update property after upload take a longer time than when you upload 11MB file. Doing a 11MB file upload using CSOM took 9.7s, while updating its property took 23.38s. We came out with the idea of using built-in SharePoint REST service, but we got bumped into 403 forbidden access error. And it wasn't the best solution because, upload and updating property will required 2 process (upload 1st than update property). If we could create a custom REST, you can just sent those properties in URL and send the stream of file into http request.

There were many tutorial about uploading files to REST service but there wasn't much on how to create service that upload file inside SharePoint 2013. Base on many reference on internet I've created a REST service which run under SharePoint 2013 project (Full trust code). So how do you add a svc file inside SharePoint 2013 project? You can search using this keyword "creating a custom svc in SharePoint 2013" on google. Or just create that svc on another project and put it under ISAPI directory on SharePoint project.



Then we start with setting up interface and define class definition that will become the output from our REST service.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Threading.Tasks;

namespace UploadFileUsingREST
{
    [ServiceContract]
    interface IUploadRest
    {
        [OperationContract]
        [WebInvoke(Method = "POST",
            UriTemplate = "UploadFile/{fileName}/{title}")]
        ConfirmationFileUpload Upload(string fileName, string title, Stream fileContent);
    }
    [DataContract]
    public class ConfirmationFileUpload
    {
        [DataMember]
        public long FileReceivedLength;
        [DataMember]
        public bool IsSuccess;
        [DataMember]
        public string ErrorMessage;
    }
}

After that we begin implementing code that will receive stream and update property of that file.


using Microsoft.SharePoint;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Activation;
using System.Text;
using System.Threading.Tasks;

namespace UploadFileUsingREST
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class UploadRest : IUploadRest
    {
        public const string SiteURL = http://siteurl;
        public ConfirmationFileUpload Upload(string fileName, string title, System.IO.Stream fileContent)
        {
            ConfirmationFileUpload result = new ConfirmationFileUpload();
            try
            {
                using (SPSite site = new SPSite(SiteURL))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        using (MemoryStream writer = new MemoryStream())
                        {
                            int readCount;
                            byte[] buffer = new byte[8192];
                            while ((readCount = fileContent.Read(buffer, 0, buffer.Length)) != 0)
                                writer.Write(buffer, 0, readCount);
                            web.AllowUnsafeUpdates = true;
                            SPFile file = web.Files.Add(string.Format("{0}/Shared Documents/{1}", SiteURL, fileName), writer, true);
                            file.Update();
                            SPListItem item = file.ListItemAllFields;
                            item[SPBuiltInFieldId.Title] = title;
                            item.Update();
                            web.AllowUnsafeUpdates = false;
                            result.FileReceivedLength = file.Length;
                        }
                    }
                }
                result.IsSuccess = true;
            }
            catch (Exception e)
            {
                //log to ULS
                result.IsSuccess = false;
                result.ErrorMessage = e.Message;
            }
            finally
            {
                fileContent.Close();
            }
            return result;
        }
    }
}

The code was simple that it will receive the stream and save it to MemoryStream first before add it using Files.Add. Then we get listitem and update its property. You need to setting AllowUnsafeUpdates to true before add files to prevent error page need revalidation.

At this point you can try upload files using this REST service, but SharePoint limit max receive content length to only 18KB. Googling it out many people suggesting to define service reference in web.config, but it won't work. You can't change svc endpoint on web.config or on anything else as I aware of, it took me two days to figure this out before I found this site. It needs to be done on code (or PowerShell?) . We need to set that maxReceiveMessageSize larger than it's default value 18KB. How do you add those FeaturedInstalled method in SharePoint 2013? I googled and found another site that explained this. Basically you need to override a FeaturedInstalled method and set that endpoint in there. Wrapping it all together, this is what I did. We need to set maxreceivecontent on wcfServiceSetting for our REST service file. We can set it during feature deployment.
  1. Add a new class, I name it FeatureEvent.cs
  2. Inherits that class to SPFeatureReceiver
  3. Override method FeatureInstalled
  4. Then add code to change ReaderQuotasMaxStringContentLength, ReaderQuotasMaxArrayLength, ReaderQuotasMaxBytesPerRead, MaxReceivedMessageSize just like bellow

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UploadFileUsingREST
{
    public class FeatureEvent : SPFeatureReceiver
    {
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
            SPWebService contentService = SPWebService.ContentService;
            SPWcfServiceSettings wcfServiceSettings = new SPWcfServiceSettings();
            wcfServiceSettings.ReaderQuotasMaxStringContentLength = Int32.MaxValue;
            wcfServiceSettings.ReaderQuotasMaxArrayLength = Int32.MaxValue;
            wcfServiceSettings.ReaderQuotasMaxBytesPerRead = Int32.MaxValue;
            wcfServiceSettings.MaxReceivedMessageSize = Int32.MaxValue;
            contentService.WcfServiceSettings["uploadrest.svc"] = wcfServiceSettings;
            contentService.Update(true);
        }
    }
}

We then need to create a feature that will run that file when the feature got installed.
  1. Create a new SharePoint feature file
  2. Open that feature and click on Manifest tab
  3. Expand Edit option
  4. Then add ReceiverAssembly and ReceiverClass just like bellow

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/" ReceiverAssembly="UploadFileUsingREST, Version=1.0.0.0, Culture=neutral, PublicKeyToken=282a31b1b9f5b608" ReceiverClass="UploadFileUsingREST.FeatureEvent">
</Feature>

Deploy it and test it, I've created a console program to test this svc


    class Program
    {
        static void Main(string[] args)
        {
            FileInfo fi = new FileInfo(@"C:\testfile.pdf");
            Console.WriteLine(fi.Length);
            string urlFull = string.Format("http://documentrepositorysite/_vti_bin/uploadrest.svc/UploadFile/{0}/{1}", fi.Name, fi.Name);
            HttpWebRequest client = (HttpWebRequest)WebRequest.Create(urlFull);
            client.Credentials = new NetworkCredential("username", "userpassword", "userdomain");
            client.Method = "POST";
            client.UseDefaultCredentials = true;
            client.PreAuthenticate = true;
            using (var requestStream = client.GetRequestStream())
            {
                using (var file = fi.OpenRead())
                {
                    file.CopyTo(requestStream);
                }
            }
            using (var response = client.GetResponse())
            {
                StreamReader reader = new StreamReader(response.GetResponseStream());
                string respon = reader.ReadToEnd();
                Console.WriteLine(respon);
                Console.ReadLine();
            }
        }
    }

Testing it with 11MB file, and it completed in 10s.
Complete source code