Functional programming is a ghetto
LinkedIn OAuth in F#
First of all great thanks to Luke Hoban for his ‘Twitter OAuth in F#‘. His OAuth implementation works fine for LinkedIn as well.
All you need to do is to switch the requestTokenURI/accessTokenURI/authorizeURI addresses from Twitter on LinkedIn.
Additional materials about LinkedIn public API you found at the LinkedIn developers site: https://developer.linkedin.com/
LinkedIn OAuth implementation:
open System
open System.IO
open System.Net
open System.Security.Cryptography
open System.Text
// LinkedIn OAuth Constants
let consumerKey : string = failwith "Must provide the consumerKey for an app registered at https://www.linkedin.com/secure/developer?newapp="
let consumerSecret : string = failwith "Must provide the consumerSecret for an app registered at https://www.linkedin.com/secure/developer?newapp="
let requestTokenURI = "https://api.linkedin.com/uas/oauth/requestToken"
let accessTokenURI = "https://api.linkedin.com/uas/oauth/accessToken"
let authorizeURI = "https://api.linkedin.com/uas/oauth/authorize"
// Utilities
let unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
let urlEncode str =
String.init (String.length str) (fun i ->
let symbol = str.[i]
if unreservedChars.IndexOf(symbol) = -1 then
"%" + String.Format("{0:X2}", int symbol)
else
string symbol)
// Core Algorithms
let hmacsha1 signingKey str =
let converter = new HMACSHA1(Encoding.ASCII.GetBytes(signingKey : string))
let inBytes = Encoding.ASCII.GetBytes(str : string)
let outBytes = converter.ComputeHash(inBytes)
Convert.ToBase64String(outBytes)
let compositeSigningKey consumerSecret tokenSecret =
urlEncode(consumerSecret) + "&" + urlEncode(tokenSecret)
let baseString httpMethod baseUri queryParameters =
httpMethod + "&" +
urlEncode(baseUri) + "&" +
(queryParameters
|> Seq.sortBy (fun (k,v) -> k)
|> Seq.map (fun (k,v) -> urlEncode(k)+"%3D"+urlEncode(v))
|> String.concat "%26")
let createAuthorizeHeader queryParameters =
let headerValue =
"OAuth " +
(queryParameters
|> Seq.map (fun (k,v) -> urlEncode(k)+"\x3D\""+urlEncode(v)+"\"")
|> String.concat ",")
headerValue
let currentUnixTime() = floor (DateTime.UtcNow - DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds
/// Request a token from LinkedIn and return:
/// oauth_token, oauth_token_secret, oauth_callback_confirmed
let requestToken() =
let signingKey = compositeSigningKey consumerSecret ""
let queryParameters =
["oauth_callback", "oob";
"oauth_consumer_key", consumerKey;
"oauth_nonce", System.Guid.NewGuid().ToString().Substring(24);
"oauth_signature_method", "HMAC-SHA1";
"oauth_timestamp", currentUnixTime().ToString();
"oauth_version", "1.0"]
let signingString = baseString "POST" requestTokenURI queryParameters
let oauth_signature = hmacsha1 signingKey signingString
let realQueryParameters = ("oauth_signature", oauth_signature)::queryParameters
let req = WebRequest.Create(requestTokenURI, Method="POST")
let headerValue = createAuthorizeHeader realQueryParameters
req.Headers.Add(HttpRequestHeader.Authorization, headerValue)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let txt = (new StreamReader(stream)).ReadToEnd()
let parts = txt.Split('&')
(parts.[0].Split('=').[1],
parts.[1].Split('=').[1],
parts.[2].Split('=').[1] = "true")
/// Get an access token from LinkedIn and returns:
/// oauth_token, oauth_token_secret
let accessToken token tokenSecret verifier =
let signingKey = compositeSigningKey consumerSecret tokenSecret
let queryParameters =
["oauth_consumer_key", consumerKey;
"oauth_nonce", System.Guid.NewGuid().ToString().Substring(24);
"oauth_signature_method", "HMAC-SHA1";
"oauth_token", token;
"oauth_timestamp", currentUnixTime().ToString();
"oauth_verifier", verifier;
"oauth_version", "1.0"]
let signingString = baseString "POST" accessTokenURI queryParameters
let oauth_signature = hmacsha1 signingKey signingString
let realQueryParameters = ("oauth_signature", oauth_signature)::queryParameters
let req = WebRequest.Create(accessTokenURI, Method="POST")
let headerValue = createAuthorizeHeader realQueryParameters
req.Headers.Add(HttpRequestHeader.Authorization, headerValue)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let txt = (new StreamReader(stream)).ReadToEnd()
let parts = txt.Split('&')
(parts.[0].Split('=').[1],
parts.[1].Split('=').[1])
/// Compute the 'Authorization' header for the given request data
let authHeaderAfterAuthenticated url httpMethod token tokenSecret queryParams =
let signingKey = compositeSigningKey consumerSecret tokenSecret
let queryParameters =
["oauth_consumer_key", consumerKey;
"oauth_nonce", System.Guid.NewGuid().ToString().Substring(24);
"oauth_signature_method", "HMAC-SHA1";
"oauth_token", token;
"oauth_timestamp", currentUnixTime().ToString();
"oauth_version", "1.0"]
let signingQueryParameters =
List.append queryParameters queryParams
let signingString = baseString httpMethod url signingQueryParameters
let oauth_signature = hmacsha1 signingKey signingString
let realQueryParameters = ("oauth_signature", oauth_signature)::queryParameters
let headerValue = createAuthorizeHeader realQueryParameters
headerValue
/// Add an Authorization header to an existing WebRequest
let addAuthHeaderForUser (webRequest : WebRequest) token tokenSecret queryParams =
let url = webRequest.RequestUri.ToString()
let httpMethod = webRequest.Method
let header = authHeaderAfterAuthenticated url httpMethod token tokenSecret queryParams
webRequest.Headers.Add(HttpRequestHeader.Authorization, header)
type System.Net.WebRequest with
/// Add an Authorization header to the WebRequest for the provided user authorization tokens and query parameters
member this.AddOAuthHeader(userToken, userTokenSecret, queryParams) =
addAuthHeaderForUser this userToken userTokenSecret queryParams
let testing() =
// Compute URL to send user to to allow our app to connect with their credentials,
// then open the browser to have them accept
let oauth_token'', oauth_token_secret'', oauth_callback_confirmed = requestToken()
let url = authorizeURI + "?oauth_token=" + oauth_token''
System.Diagnostics.Process.Start("iexplore.exe", url)
// *******NOTE********:
// Get the 7 digit number from the web page, pass it to the function below to get oauth_token
// Sample result if things go okay:
// val oauth_token_secret' : string = "9e571e13-d054-44e6-956a-415ab3ee6d23"
// val oauth_token' : string = "044da520-0edc-4083-a061-74e115712b61"
let oauth_token, oauth_token_secret = accessToken oauth_token'' oauth_token_secret'' ("78846")
// Test 1: Get your profile details
let streamSampleUrl2 = "http://api.linkedin.com/v1/people/~"
let req = WebRequest.Create(streamSampleUrl2)
req.AddOAuthHeader(oauth_token, oauth_token_secret, [])
let resp = req.GetResponse()
let strm = resp.GetResponseStream()
let text = (new StreamReader(strm)).ReadToEnd()
text
// Test 2: Get a connections list
let streamSampleUrl3 = "http://api.linkedin.com/v1/people/~/connections"
let req = WebRequest.Create(streamSampleUrl3)
req.AddOAuthHeader(oauth_token, oauth_token_secret, [])
let resp = req.GetResponse()
let strm = resp.GetResponseStream()
let text = (new StreamReader(strm)).ReadToEnd()
text
Navigation hierarchies and key filters with slide library : Code Fix.
According to the popularity of the blog post : “Navigation hierarchies and key filters with slide library“, I decided to share a code that fix Slide Library view. This code snippet replace existing ListViewWebPart on the slide library view to the new XsltListViewWebPart and configure it in a proper way.
If you execute this code on the all slide library views then you will get a full-functional slide library with fixed navigation and filters.
public bool ReplaceListViewWebPartForView(SPWeb web, SPList list, SPView view)
{
using (SPLimitedWebPartManager webPartManager =
web.GetLimitedWebPartManager(view.Url, PersonalizationScope.Shared))
{
SPLimitedWebPartCollection pageWebParts = webPartManager.WebParts;
ListViewWebPart listViewWebPart =
pageWebParts.OfType<ListViewWebPart>().FirstOrDefault();
if (listViewWebPart == null || listViewWebPart.IsClosed) return false;
var xsltListViewWebPart =
new XsltListViewWebPart
{
ListName = listViewWebPart.ListName,
ViewFlags = listViewWebPart.ViewFlags,
ViewId = listViewWebPart.ViewId,
ViewGuid = listViewWebPart.ViewGuid,
Visible = listViewWebPart.Visible,
DisplayName = view.Title,
Default = view.DefaultView.ToString().ToUpper(),
WebId = listViewWebPart.WebId,
NoDefaultStyle = listViewWebPart.UseDefaultStyles.ToString().ToUpper()
};
//create new Webpart and set parameters from existing ListViewWebPart
webPartManager.AddWebPart(xsltListViewWebPart, listViewWebPart.ZoneID, 0);
//fix ViewFlags since SP added "Hidden" flag
xsltListViewWebPart.ViewFlags = listViewWebPart.ViewFlags;
webPartManager.DeleteWebPart(listViewWebPart);
webPartManager.SaveChanges(xsltListViewWebPart);
list.Update();
web.Update();
//get updated list and fix issue with empty title to allow View be visible on the site
SPList list1 = web.Lists[list.ID];
SPView view1 = list1.Views[new Guid(xsltListViewWebPart.ViewGuid)];
view1.Title = view.Title;
view1.DefaultView = view.DefaultView;
view1.MobileDefaultView = view.MobileDefaultView;
view1.MobileView = view.MobileView;
view1.Update();
web.Update();
return true;
}
}
P.S. Great thanks to the Dmitry Chirun for this fix.
How to enumerate large document library
In this post by the size of the library i mean a total size of the documents in the library, not an item count.
It is relevant for cases when you need to enumerate over all documents in the library to process they, but the size of the library greater then an amount of the RAM on the SharePoint machine.
If you will do it using SPListItemCollection or ContentIterator and try to process all items as a single batch then you will get out of memory exception. It is happens because SharePoint OM download all binaries to the worker process (before or during enumeration).
This problem could be solved using content paging. You can split the library content into small pages and process it page by page. Before page processing we should release all resources allocated for previous page. Also, exist approach that rely on the humanity of the content structure. We can assume that the size of the documents from one folder is not large and can be processed as a single batch. Such processing order also has advantages over simple paging.
Below you can find an C# example of processing:
using Microsoft.Office.Server.Utilities;
using Microsoft.SharePoint;
public static void EnumerateFolder(SPFolder root, Action<SPListItem> processAction, Action<SPListItem, Exception> exceptionAction)
{
foreach (SPFolder folder in root.SubFolders)
EnumerateFolder(folder, processAction, exceptionAction);
var contentIterator = new ContentIterator();
contentIterator.ProcessFilesInFolder(root, false,
(file) => { processAction(file.Item);},
(file, exception) =>
{
exceptionAction(file.Item, exception);
return false;
});
}
EnumerateFolder method enumerate over all files into provided SPFolder and all subfolders and execute processAction on each one. The last parameter into ProcessFilesInFolder is an error handler that will be executed after each exception from item processing. Line 13 mean that we do not stop document processing after each exception. More details about ProcessFilesInFolder method you can find here.
Below you can find the same F# example.
open Microsoft.SharePoint
open Microsoft.Office.Server.Utilities
let rec enumerate (root:SPFolder) processAction exceptionAction =
for folder in root.SubFolders do
enumerate folder processAction exceptionAction
ContentIterator().ProcessFilesInFolder(root, false,
(fun file -> processAction(file.Item)),
(fun file ex -> exceptionAction(file.Item, ex); false));
P.S. To use ContentIterator you should add Microsoft.Office.Server to the project references.
How to change SPListItem Created or Modified date
Another often asked question is a ‘How to change SPListItem Created/Modified date’. Such task occur when you importing documents to the SharePoint from another source when you want to save authorship and time information.
To update Created and Modified date you can use SPListItem indexer to modify infomation and call Update() to save changes.
To modify CreatedBy and ModifiedBy properties, you can use SPListItem indexer to modify fields with internal names Author and Editor. But you should convert SPUser object into string with following format “{userId};#{userName}”
Example you can find below:
public void ApplyMetadata(SPListItem item, DateTime created, DateTime modified, SPUser createdBy, SPUser modifiedBy)
{
item["Created"] = created;
item["Modified"] = modified;
item["Author"] = GetStringByUser(createdBy);
item["Editor"] = GetStringByUser(modifiedBy);
item.Update();
}
private static string GetStringByUser(SPUser user)
{
return user.ID + ";#" + user.Name;
}
Visual Studio minimap
When I saw a Sublime Text 2 with their features at first time – I loved that. One of my favorite feature is a minimap. It is a simple but very powerful idea to replace scrollbar with minimized code map. Using this feature very easy navigate directly to where you want.
I wanna this feature for Visual Studio for sure. Fortunately, it is already there. It is a part of the Productivity Power Tools.
First of all we need to setup Productivity Power Tool using Visual Studio Extension Manager.
We need to enable this feature, because it is disabled by default. Open Tool -> Options -> Productivity Power Tools, enable Enhanced Scroll Bar and restart your Visual Studio.
Choose ‘Full map mode‘ in the Productivity Power Tools->Enchanced Scroll Bar section.
You should see minimaps in your Visual Studio now. Enjoy it!!!
How to integrate F# and Notepad++
People a faced with challenges trying to install F# interactive plugin for Notepad++ (example). I am not an exception, going through all of this I want to share my experience.
- First of all download latest version of the Notepad++ and install it (default installation settings). For today it is Notepad++ 6.1.5 (Jul 16 2012)
- Download NPPFSIPlugin Version 0.1.1.
- Extract dll from achieve and copy it to Notepad++\plugins\ folder. On the my Win7 64bit machine it is c:\Program Files (x86)\Notepad++\plugins\.
- Open or re-open you Notepad++ application.
- Go to Plugins\F# Interactive\Options menu item.
- Specify Binary Path to fsi.exe file.
For example:
F# 2.0 – C:\Program Files (x86)\Microsoft F#\v4.0\fsi.exe
F# 3.0 – C:\Program Files (x86)\Microsoft SDKs\F#\3.0\Framework\v4.0\fsi.exe
Before use this paths check that you you have installed appropriate version of F# in that - Save you changes. Now it should work.
Alt+T to open F# interactive
Alt+Enter to to send selected text to F# Interactive
It is also very nice to have a F# syntax highlighting.
- Download xml file with user definition language for Notepad++ from here or here.
- Rename file to userDefineLang.xml.
- Replace ext=”fs” to ext=”fs fsi fsx” in the file.
- Copy this file to %APPDATA%\Notepad++\ folder. (for more details go here )
Path should be like this : C:\Users\User_Name\AppData\Roaming\Notepad++ - Restart Notepad++.
Note: If you already have such file open both. (instructions is copied from here)
- Select all of the new file, copy, and paste at the end of the current file
- Delete </NotepadPlus><NotepadPlus> pair in the middle (remove 2 lines)
P.S. Post moved from http://sergey-tihon.blogspot.com/
FAST Search for SharePoint
Cool presentation about FS4SP:
How to migrate “Managed Metadata Service”
This is task appeared when I needed to migrate SharePoint site collection from one farm to another.
First time we had used SharePoint 2010 back up mechanism, but this approach hard enough. We had a lot of errors during restore: broken connector, errors with content hub, access problems and so on. Every such restoration has brought new errors. We could not document restore process well enough.But then we have found new way of migration.
Migration guide simple and short:
- Open SQL Server Management Studio and create back of Managed Metadata Service’s database (every Managed Metadata Service has their own database. Database name is something like this “Managed Metadata Service_0eb8ebdccb234c5ea23e677d816e845f”)
- Restore database from this backup into new farm to database of another Managed Metadata Service.
Keep in the mind, that you can not create two Managed Metadata Services in the one farm this way, because the Guid of the service store directly in the database. After such restore you can work with just one service.




