- You need to have 3 static IP. 1 dedicated IP for server WFE1, another 1 dedicated IP for server WFE2, and the 3rd IP is for Cluster IP (Virtual IP on both server).
- You need to have a DNS name that pointed to IP Cluster (ex: farmaddress.company.com), make sure that your system admin didn't point that address to IP server (create DNS with type A).
- Configure your Alternate Access Mapping default zone to DNS name (ex: farmaddress.company.com)
- Some router didn't support multicast. So try configure NLB using unicast first, then you can try using multicast ref
- If you couldn't access DNS, you can try your configuration by adding that cluster IP using hosts file configuration on client PC.
- For how to test this NLB, you can look at this article Confirming Windows Load Balancing
This is my personal blog to store all my experience as a SharePoint developer. I work mostly on developing 'In-House' solution that use Asp.net which run on top of SharePoint. I've experienced in developing this solution from SharePoint 2007, 2010, 2013 and 2016.
Monday, 9 November 2015
SharePoint Farm configuration
Configure SharePoint Farm using NLB can be stressful, especially when you don't know anything about setting DNS and has access to it. Here a list of what you need to setup & know on how to successfully setup this configuration. I'm using this example configuration 2 WFE server (WFE1 & WFE2).
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.
- In your SharePoint Project, create folder "App_Themes"
- Inside that folder create a sub folder with name "Default" (we use Default for skin name)
-
Add text file item into that folder and name it Default.skin (filename should be same with its folder)
-
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>
- 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.
- 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.
-
Again add text file item and give it a name and add extension .cmd, I'll name it "DeploySkinFile.cmd"
-
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
- 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
-
Add this command under "Post-build event command line"
"$(ProjectDir)DeploySkinFile.cmd" "$(ProjectDir)"
-
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
-
Let's create a visual web part with textbox control and SkinID="DefaultInput" to test this.
-
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
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.
After that we begin implementing code that will receive stream and update property of that file.
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.
We then need to create a feature that will run that file when the feature got installed.
Deploy it and test it, I've created a console program to test this svc
Testing it with 11MB file, and it completed in 10s.
Complete source code
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.
- Add a new class, I name it FeatureEvent.cs
- Inherits that class to SPFeatureReceiver
- Override method FeatureInstalled
- 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.
- Create a new SharePoint feature file
- Open that feature and click on Manifest tab
- Expand Edit option
- 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
Wednesday, 22 April 2015
Using JSON as an input parameter for Custom Web Part
Yesterday I stumbled into a problem where we need to create a sophisticated parameter for our web part. An object of class that store configuration for that SharePoint web part. We could create many parameter or using some unique character that could distinguished some information we need. But creating that parameter required additional script to convert it into an object. I know about JSON and what it could do to make transferring object from server side to JavaScript easy. .NET Framework has provided a method to translate a string into a class or vice versa. It was fit to our requirement so we used JSON as an input parameter. First of all I created a class library that will become the structure for JSON input in our Web Part.
I've created an enum class to show that it can be used to convert enum which become handy for storing configuration parameter. If you want to use this class as an JSON object, you need to add attribute Data DataContract() to its class and DataMember() to its property. Then I created a parameter inside class web part.
We could setup a default value for this parameter so that we could use it as an input example. To setup default value inside web part, add that value inside webpart file.
I set that default value as a generic DomainClass list (List<DomainClass>) . The next thing was to code a class that will deserialize it into object.
Here I used extension class to make it easy when Deserialize or serialize an object. The serialize class will come in handy when you want to make JSON for this web part, just call that method from another project. To deserialize that JSONInput parameter call this in our code.
The result…
download the complete project here: JSON Input
public enum EnumClass { Enum1, Enum2 } public class DomainClass { public string String1 { get; set; } public EnumClass EnumField { get; set; } }
I've created an enum class to show that it can be used to convert enum which become handy for storing configuration parameter. If you want to use this class as an JSON object, you need to add attribute Data DataContract() to its class and DataMember() to its property. Then I created a parameter inside class web part.
[WebBrowsable(true), WebDisplayName("JSON"), WebDescription("Input JSON String"), Personalizable(PersonalizationScope.Shared), Category("JSON")] public string JSONInput { get; set; }
We could setup a default value for this parameter so that we could use it as an input example. To setup default value inside web part, add that value inside webpart file.
<properties> <property name="Title" type="string">JSONParameter - WebPartJSONParameter</property> <property name="Description" type="string">JSON</property> <property name="JSONInput" type="string">[{"EnumField":0,"String1":"String1"},{"EnumField":1,"String1":"String2"}]</property> </properties>
I set that default value as a generic DomainClass list (List<DomainClass>) . The next thing was to code a class that will deserialize it into object.
public static class Converter { public static string Serialize<T>(this T obj) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType()); MemoryStream ms = new MemoryStream(); serializer.WriteObject(ms, obj); string retVal = Encoding.UTF8.GetString(ms.ToArray()); return retVal; } public static T Deserialize<T>(this string json) { T obj = Activator.CreateInstance<T>(); MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)); DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType()); obj = (T)serializer.ReadObject(ms); ms.Close(); return obj; } }
Here I used extension class to make it easy when Deserialize or serialize an object. The serialize class will come in handy when you want to make JSON for this web part, just call that method from another project. To deserialize that JSONInput parameter call this in our code.
List<DomainClass> result = JSONInput.Deserialize<List<DomainClass>>();
The result…
download the complete project here: JSON Input
Saturday, 18 April 2015
Create a class that will switch AppFabric & Page.Cache easily
As I have written on previous blog, that I used AppFabric to store frequently used data within SharePoint 2013 farm. This AppFabric is controlled by Distributed Cache service and because I realize that Distributed Cache wasn't supposed to be used by custom application. I've created a class that will handle the switch between using Page.Cache and AppFabric.
Inside tag Configuration after closing tag configSections
Inside tag assemblyBinding
Inside tag appSettings
To store an object to cache with key "Key1", use this command
To get an object from cache with key "Key1", use this command
To change from AppFabric to HttpRuntime.Cache (equivalent with page.cache), set value to false in appSetting "IsUseAppFabric".
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; using System.Configuration; using Microsoft.ApplicationServer.Caching; using Microsoft.SharePoint.Administration; using Microsoft.SharePoint.DistributedCaching.Utilities; using Microsoft.SharePoint.Utilities; namespace Utility { public class CacheManager { private static readonly object _lock = new object(); private static CacheManager _cacheManager; private static bool? isAllowedToUseAppFabric = null; public CacheManager() { } public static CacheManager CurrentCache { get { if (_cacheManager == null) _cacheManager = new CacheManager(); return _cacheManager; } } /// <summary> /// get or store cache object /// </summary> /// <param name="cacheKey">cache key</param> /// <param name="isUseAppFabric">indicate if you want to use AppFabri for storing cache data</param> /// <returns>object that was stored in cache</returns> public object this[string cacheKey, bool isUseAppFabric] { get { if (IsAllowedToUseAppFabric && isUseAppFabric) { try { return GetAppFabricCache[cacheKey]; } catch (Exception e) { //Log the exception to log; } } return HttpRuntime.Cache[cacheKey]; } set { lock (_lock) { if (value != null) { if (IsAllowedToUseAppFabric && isUseAppFabric) { try { GetAppFabricCache[cacheKey] = value; return; } catch (Exception e) { //Log the exception to log } } HttpRuntime.Cache[cacheKey] = value; } else Remove(cacheKey); } } } /// <summary> /// get or store cache object /// </summary> /// <param name="cacheKey">cacheName</param> /// <returns>object that was stored in cache</returns> public object this[string cacheKey] { get { return this[cacheKey, true]; } set { this[cacheKey, true] = value; } } /// <summary> /// remove item from cache /// </summary> /// <param name="cacheKey"cacheName>the name of cache object you want to remove</param> /// <param name="isUseAppFabric">remove object from AppFabric cache</param> /// <returns>true if the object has been removed</returns> /// <remarks> /// If not using AppFabric this method return object that was stored in cache /// </remarks> public object Remove(string cacheKey, bool isUseAppFabric) { lock (_lock) { if (IsAllowedToUseAppFabric && isUseAppFabric) { try { return GetAppFabricCache.Remove(cacheKey); } catch (Exception e) { //Log exception to Log } } return HttpRuntime.Cache.Remove(cacheKey); } } /// <summary> /// remove item from cache /// </summary> /// <param name="cacheKey"cacheName>the name of cache object you want to remove</param> /// <returns>true if the object has been removed</returns> public object Remove(string cacheKey) { return Remove(cacheKey, true); } #region AppFabric private static DataCache _appFabricCache; private DataCache CreateCache() { DataCacheFactory factory = new DataCacheFactory(); DataCache cache = factory.GetCache(CacheName); return _appFabricCache; } private string CacheName { get { return "CacheServiceName";//change this to get a value from configuration file } } private DataCache GetAppFabricCache { get { if (_appFabricCache == null) _appFabricCache = CreateCache(); return _appFabricCache; } } private bool IsAllowedToUseAppFabric { get { if (!isAllowedToUseAppFabric.HasValue) isAllowedToUseAppFabric = bool.parse(ConfigurationManager.AppSettings["IsUseAppFabric"]); return isAllowedToUseAppFabric.Value; } } #endregion } }
Configuration file (web.config)
Inside tag configSections<section name="dataCacheClient" type="Microsoft.ApplicationServer.Caching.DataCacheClientSection, Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" allowLocation="true" allowDefinition="Everywhere" />
Inside tag Configuration after closing tag configSections
<dataCacheClient> <hosts> <host name="<cacheservername>" cachePort="22233" /> </hosts> </dataCacheClient>
Inside tag assemblyBinding
<dependentAssembly> <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.ApplicationServer.Caching.Core.dll"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.ApplicationServer.Caching.Client.dll"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.WindowsFabric.Common" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.WindowsFabric.Common.dll"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.WindowsFabric.Data.Common" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <codeBase version="1.0.0.0" href="C:\Program Files\AppFabric 1.1 for Windows Server\Microsoft.WindowsFabric.Data.Common.dll"/> </dependentAssembly>
Inside tag appSettings
<add key="IsUseAppFabric" value="true" />
To store an object to cache with key "Key1", use this command
Utility.CacheManager.CurrentCache["Key1"]; = object1;
To get an object from cache with key "Key1", use this command
var object1 = Utility.CacheManager.CurrentCache["Key1"];
To change from AppFabric to HttpRuntime.Cache (equivalent with page.cache), set value to false in appSetting "IsUseAppFabric".
Some point to note:
- If SharePoint is configured using load balancer with 2 or more WFE, AppFabric should be used to prevent data inconsistency.
- HttpRuntime.Cache is faster than AppFabric and can process up to 500.000 items a second. Use HttpRuntime.Cache if SharePoint is having only 1 WFE.
- AppFabric will store your data by serializing it this include object from linq to sql. But there is one object that is FK field column which reference to another table that didn't get DataMember attribute and It won't get stored by AppFabric (it will lose it's value when it get retrieved from cache).
- Unless you have a very fast network connection between server. You shouldn't save big data into the AppFabric cache, transporting that data over the network can take some time. You should make sure that data transfer between server should be fast and short (network latency should be bellow <1 ms)
Friday, 10 April 2015
AppFabric Command
This are a list of useful command that I've gathered while developing custom web part under SharePoint 2013. This is a distributed cache services command and I've developed my custom web part to use this services. I'm aware that Microsoft didn't support custom development against this distributed cache services. But I need a simple cache solution to support our custom application under SharePoint farm.
Remove
Distributed Cache
Stop-SPDistributedCacheServiceInstance -Graceful Remove-SPDistributedCacheServiceInstance
Host
not found when issuing Remove command above
$SPFarm = Get-SPFarm $cacheClusterName = "SPDistributedCacheCluster_" + $SPFarm.Id.ToString() $cacheClusterManager = [Microsoft.SharePoint.DistributedCaching.Utilities.SPDistributedCacheClusterInfoManager]::Local $cacheClusterInfo = $cacheClusterManager.GetSPDistributedCacheClusterInfo($cacheClusterName); $instanceName ="SPDistributedCacheService Name=AppFabricCachingService" $serviceInstance = Get-SPServiceInstance | ? {($_.Service.Tostring()) -eq $instanceName -and ($_.Server.Name) -eq $env:computername} $serviceInstance.Delete()
Add a
server and Start Distributed Cache service
Add-SPDistributedCacheServiceInstance
Stop
distributed cache service instance
$instanceName ="SPDistributedCacheService Name=AppFabricCachingService" $serviceInstance = Get-SPServiceInstance | ? {($_.service.tostring()) -eq $instanceName -and ($_.server.name) -eq $env:computername} $serviceInstance.Unprovision()
Start
distributed cache service instance
$instanceName ="SPDistributedCacheService Name=AppFabricCachingService" $serviceInstance = Get-SPServiceInstance | ? {($_.service.tostring()) -eq $instanceName -and ($_.server.name) -eq $env:computername} $serviceInstance.Provision()
Add
cache
New-Cache <CacheName>
Set timetolive
cache data
Stop-CacheCluster Set-CacheConfig -CacheName <cachename> -TimeToLiveMins 20 Start-CacheCluster
Remove
cache
Remove-Cache <CacheName>
Get
all cache available
Get-Cache | select CacheName
Get
cache statistic
Get-CacheStatistics <cachename>
Subscribe to:
Posts (Atom)