DCSIMG
Choroshin Alex

Choroshin Alex

.NET Developer

Fast Search Pipeline Extensibility for Specific Content Source

Since the Pipeline Extensibility is not restricted to any content source and the fact that you do not have a proper API, makes it really hard to work with a specific Content Source.

Luckily for us we have a Crawled Property which is mapped to the managed property "ContentSource":

<CrawledProperty propertySet="012357BD-1113-171D-1F25-292BB0B0B0B0" varType="31" propertyName="315" />

So you can include this in your extensibility configuration and you can see which content source the data came from and apply appropriate logic.

Example:

static void Main(string[] args)
{

XDocument inputDoc = XDocument.Load(args[0]);
XElement outputElement = new XElement("Document");
//get Content Source from input file
string contentSourceName = GetContentSource(inputDoc);

if (contentSourceName == "My Content Source")
{
//your logic
}

outputElement.Save(args[1]);
}
private static string GetContentSource(XDocument inputDoc)
{
var res = from cp in inputDoc.Descendants("CrawledProperty")
where new Guid(cp.Attribute("PropertySet").Value).Equals(new Guid("012357BD-1113-171D-1F25-292BB0B0B0B0")) &&
cp.Attribute("PropertyID").Value == "315"
select cp.Value;
return res.First();
}


This way we managed to apply our own logic for a specific Content Source.

Reference:

Great answer by Mikael Svenson : http://social.technet.microsoft.com/Forums/en-US/fastsharepoint/thread/4a510295-35d9-403b-9d32-3650fb89dcb8/

http://blogs.msdn.com/b/thomsven/archive/2010/09/23/debugging-and-tracing-fast-search-pipeline-extensibility-stages.aspx?Redirected=true

I would like to thank Jorge Einbund, a talented .NET developer for helping me with this post.

Hope you’ll find this post helpful.

Posted: May 17 2013, 04:01 AM by choroshin | with no comments |
תגים:, , ,

How to Get Login Name and Display Name using SharePoint 2013 REST API

Working with REST API is quite simple and straight forward,
for example when you need to fetch data from a list you can use the following JQuery Ajax code snippet:

jQuery.ajax({
               url: "http://YourSite/_api/web/lists/getbytitle('ListName')/items",
               type: "GET",
               headers: { "Accept": "application/json;odata=verbose" },
               success: function(data, textStatus, xhr) {
                var dataResults = data.d.results;
                alert(dataResults[0].Title);     
                },
               error: function(xhr, textStatus, errorThrown) {
               alert("error:"+JSON.stringify(xhr));
               }
            });

Another good example is when you need to get specific fields like “Title”, “ID” or  “Modified” , you can use the "$select keyword.
For example:

url: "http://YourSite/_api/web/lists/getbytitle('ListName')/items$select= Title ,ID, Modified",
            type: "GET",
            headers: { "Accept": "application/json;odata=verbose" },
            success: function(data, textStatus, xhr) {
             var dataResults = data.d.results;
             alert(dataResults[0].Modified);     
                },
            error: function(xhr, textStatus, errorThrown) {
            alert("error:"+JSON.stringify(xhr));
            }
         });   

But what happens when you need to get a user/group field like “Author” ? well, things are not as obvious as they seem.

Unfortunately you can’t use /getbytitle('ListName')/items or /getbytitle('ListName')/items?filter=Author to get the user field since this field does not exist in the response data, but luckily for us we have an “AuthorId” field that (as you already  guessed) will get us the user id.

image

So after getting the user id from your list you need to make another Ajax call to get the user login name/display name by using /_api/Web/GetUserById method .

Example:

function getUser(id){
var returnValue;
  jQuery.ajax({
   url: "http://YourSite/_api/Web/GetUserById(" + id + ")",
   type: "GET",
   headers: { "Accept": "application/json;odata=verbose" },
   success: function(data) {
           var dataResults = data.d;
      //get login name  
      var loginName  = dataResults.LoginName.split('|')[1];
      alert(loginName);     
      //get display name
      alert(dataResults.Title);
   }
 });
}

 

image

Full Example:

jQuery.ajax({
    url: "/SiteName/_api/web/lists/getbytitle('ListName')/items",
    type: "GET",
    headers: { "Accept": "application/json;odata=verbose" },
    success: function(data, textStatus, xhr) {
        var dataResults = data.d.results;
        var resultId = dataResults[0].AuthorId.results[0];        
        getUser(resultId)                                                                          
    },
    error: function(xhr, textStatus, errorThrown) {
        alert("error:"+JSON.stringify(xhr));
    }
});                                                                           
 
function getUser(id){
var returnValue;
  jQuery.ajax({
   url: "http://YourSite/_api/Web/GetUserById(" + id + ")",
   type: "GET",
   headers: { "Accept": "application/json;odata=verbose" },
   success: function(data) {
           var dataResults = data.d;
      //get login name  
      var loginName  = dataResults.LoginName.split('|')[1];
      alert(loginName);     
      //get display name
      alert(dataResults.Title);
   }
 });
}

I would like to thank Ofir Elmishali for helping me with this post.

Hope you’ll find this post helpful.

SharePoint: Getting “This collection already contains an address with scheme http” Error When Creating a Custom WCF Service

Problem:

The problem is caused by the fact that IIS supports specifying multiple IIS bindings per site (which results in multiple base addresses per scheme, in our case HTTP), but a WCF service hosted under a site allows binding to only one base address per scheme.

Multiple addresses example (in our case two):

clip_image002

Solution:

Create a custom service factory to intercept and remove the additional unwanted base addresses that IIS was providing.

A) Add the custom service factory to your Custom.svc file

<%@ServiceHost language=c# Debug="true" Service="MySolution.Services.CustomService, $SharePoint.Project.AssemblyFullName$"
Factory="MySolution.Core.CustomHostFactory", $SharePoint.Project.AssemblyFullName$ %>
* Don’t forget to add the assembly full name: $SharePoint.Project.AssemblyFullName$ or you’ll get “The CLR Type 'typeName' could not be loaded during service compilation” error.

B) Create a custom factory by inheriting from ServiceHostFactory
and overriding the CreateServiceHost method.
By using the current request host name you can check which base address to use, and if no host name found, use the first one.

public class CustomServiceHostFactory : ServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            string hostName = HttpContext.Current.Request.Url.Host;
            foreach (Uri uri in baseAddresses)
            {
                if (uri.Host == hostName)
                    return new ServiceHost(serviceType, uri);
            }
            return new ServiceHost(serviceType, baseAddresses[0]);
        }
    }
 
    public class CustomHost : ServiceHost
    {
        public CustomHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        { }
        protected override void ApplyConfiguration()
        {
            base.ApplyConfiguration();
        }
    }
}
Hope you’ll find this post helpful.


SharePoint: How to get The “Welcome” Text Before The Name?

Here’s a quick tip for adding the “Welcome” text before name in an easy and fast way.

Using JQuery code you insert the "Welcome" text in the SearchTopLine class.

Example:

$(".SearchTopLine span a").prepend("Welcome ");

See you next time.

Getting “Your query is malformed.Please rephrase your query” Error When Using FQL Query Keyword With Double Quotes

When trying to use FQL (Fast Query Language) query keyword with double quotes, for example:

body: string(“”hello world””, mode=”and”)

You’ll get “Your query is malformed.Please rephrase your query” Error.  Since FQL has a problem with double quotes, the trick is to use backslash (\) in front of the quotes,  for example:

body: string(“\”hello world\””, mode=”and”)

Hope this trick was helpful for you as it was for me.

I’m Speaking at Israel JavaScript Conference 2013 (js-il)

The Israel first JavaScript Conference - will take place on Tuesday, June 18, 2013.

The topic of my session is:

JavaScript UI Components

As the HTML5 and JavaScript applications continues to gain incredible momentum and also the need for cross-platform development brought many companies like "Kendo UI" to realize that if they plan to stay in the game they need to re-invent them self's and offer developers a rich HTML, JavaScript framework to enhance their web development experience . In our lesson we'll discuss about why when and how we plan to use awesome UI Components like Charts, Trees, Graphs, Lists and etc. to increase our development productivity in a fast and easy way.

So come and join the largest JavaScript Conference in Israel.

Be sure to guarantee your seat!

For more information, visit http://js-il.com/ 

524210_10200647559852609_1883761462_n

Hope to see you there Smile

SharePoint: Can’t Activate Site Collection Feature When Creating New Site From a Custom Web Template.

The onet.xml file is basically divided into two parts, first is the "SiteFeatures" element and the second element called "WebFeatures". The "SiteFeatures" Section that holds the site features starts activating all the features only when creating a site collection. The "WebFeatures" Section that holds the web features starts activating all the web scoped features only when creating a site.

Scenario: you created a custom web template, deployed the solution and when trying to create a site from you custom web template you get the following error "the site template requires that the feature {GUID} be activated in the site collection". Of Corse you can always activate the site collection scoped feature manually, but lets be serous, you need that all the needed features be automatically activated. 

Solution: When creating a site you need to trigger the site collection scoped feature using a web scoped feature.

The steps are:

A) Create an empty web scoped feature and in the "FeatureActivated" Event Receiver add the following code:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                //Ensure that scope is correctly set
                if (properties.Feature.Parent is SPWeb)
                {
                    SPWeb web = (SPWeb)properties.Feature.Parent;
 
 
                    foreach (SPFeatureProperty property in properties.Feature.Properties)
                    {
                        Guid featureGuid = new Guid(property.Value);
                        //Verify feature status
                        SPFeature feature = web.Site.Features[featureGuid];
                        if (feature == null)
                        {
                            //Activate site collection scoped feature, if requested and not currently activated
                            web.Site.Features.Add(featureGuid);
                        }
                    }
                }
            }
            catch (Exception ex)
            {}
        }

B) In the onet.xml file in the "WebFeature" Element, add the following xml:

<WebFeatures>
      <!-- Custom Site collection scoped feature activation -->
       <Feature ID="YourEmptyFeatureGuid">
         <Properties xmlns="http://schemas.microsoft.com/sharepoint/">
           <Property Key="SiteScopedGUID" Value="YourSiteCollectionFeatureID"/>
         </Properties>
       </Feature>
</WebFeatures>
  • In the Feature ID element add your empty feature's ID
  • In the Property Key="SiteScopedGUID" element add the site collection feature id that you want to activate.

Hope you’ll find this post helpful.

Session at SharePoint Extreme 2013 Conference

Last week i gave a session about What’s new for WCM and Internet Sites in SharePoint 2013 at SharePoint Extreme 2013 Conference.

I would like to thank everyone who attended my session and hope to see you soon.  Smile

My presentation on SlideShare.

What’s new for WCM and Internet Sites in SharePoint 2013

Special thanks to Johnny Tordgeman for hosting a SharePoint 2013 development seminar at the SharePoint Extreme 2013 conference and helping me with this presentation.

538171_472183019509589_1755235837_n

A Cool New ‘Paste JSON As Classes’ Feature in ASP.NET Web Tools 2012.2 RC

Great news for Web developers, With ASP.NET and Web Tools 2012.2 RC installed, you now have a great and easy tool for converting your JSON objects into C# or VB.NET classes.

This feature uses Newtonsoft JSON parser to parse JSON text from clipboard. Once Newtonsoft JSON parser validates the clipboard data as valid JSON, then it will be converted into C# or VB.NET class depending on the selected file type.

Here’s a simple example:

A) Copy to clipboard your JSON object.

{
    "link": "http://www.microsoft.com/Surface/en-US",
    "virtual": "Virtual Keyboard",
    "partial": " The magnesium panels are finished with partial vapor deposition",
    "Price": 499.99,
    "title": "Microsoft Surface",
    "Like": true,
    "cdDrive":null,
}

B) In VS2012, open edit menu, choose ‘Past Special’ and click on ‘Past JSON As Classes’.

The result:

public class Rootobject
{
    public string link { get; set; }
    public string _virtual { get; set; }
    public string partial { get; set; }
    public float Price { get; set; }
    public string title { get; set; }
    public bool Like { get; set; }
    public object cdDrive { get; set; }
}

Of course it also supports more complex objects like Multi-Dimensional Arrays etc..

Reference:

http://blogs.msdn.com/b/webdev/archive/2012/12/18/paste-json-as-classes-in-asp-net-and-web-tools-2012-2-rc.aspx

Hope you find this article handy.

How To Create Custom Search Engine For SharePoint 2013 In Google Chrome

A great way to search something on your SharePoint 2013 site or any older version of SharePoint without the need of browsing to the site and start writing your search query cause Google Custom Search is doing it for you.

Steps for Adding a Custom Search Engine

A) Right click mouse on the address bar and choose “Edit Search Engine”

image

B) You will need to fill 3 text boxes.

  1.  Add a new search engine – the name of the search engine, by default, Google Chrome uses the site’s domain name
  2. Keyword – use a Keyword that will be easy to remember like “SP” or “SearchSP” etc..
  3. URL– insert %s in the URL where the search terms should appear.

* You will see that a lot of different search engines have already been added by Google chrome that you can use for your search.

image

Here’s an a example for adding a custom search engine:

Search Engine name:  https://choroshin.sharepoint.com

Keyword : SP

URL: https://choroshin.sharepoint.com/_layouts/15/osssearchresults.aspx?k=%s

The result:

Write “sp” in the browser’s address bar , press tab and write your search query, in my case I wrote “alex”

image

Press Enter and you’ll be redirected to your SharePoint search site along with the search query .

image

Hope you’ll find this article handy.

SharePoint Control Overrides My JavaScript “Keypress” Event

When you want to bind an event handler to the "keypress" JavaScript event, or trigger that event on an element, you can achieve this using JQuery  handler like .keypress( handler(eventObject) ) .

For example, binding "keypress" event to a text box on your custom control :

$("#input").keypress(function (event) {
var code = (event.keyCode ? event.keyCode : event.which);
if(code == 13) //Enter keycode
// your logic here...
}
});

As you can see, the task is very simple and straightforward, but when dealing with SharePoint , sometimes things get pretty messy.

Scenario:  You developed a custom control with a text box and used a "keypress" JavaScript event to trigger the “Enter” key press event . You added the control to SharePoint page and it worked, but after adding a SharePoint OOTB control that also has a text box , your "keypress" event automatically stops working. Why you ask? , well, it’s all about priorities , if your JS code is last in order,  then your code would probably work, but you can never be sure.

Solution: Use the event.preventDefault() method to stop the default action of the event, in our case, SharePoint OOTB controls .

$("#input").keypress(function (event) {
var code = (event.keyCode ? event.keyCode : event.which);
if(code == 13) //Enter keycode
//stop the default action of the Enter key
event.preventDefault();
// your logic here...
}
});

This way, we gained control over our events and managed to keep the OOTB functionality working.

 

Hope you find this article handy .

SharePoint: Getting Authentication Login Prompt When Trying To Open Office Document With Unique Permissions

The problem is in opening an Office document which is saved in SharePoint 2010:

  1. When a user has a "read" permission only to a specific document in a document library, but do not have any permissions in the document library level nor the site level.
  2. When he is accessing the document, he receives the "Download File" message with "Open" / "Save" / "Cancel" options. When clicking "Open", he is prompted with a user & password message.
  3. When clicking his correct user and password, the user & password message remains – nothing happens.
  4. Only when clicking "Cancel" in the user & password message, the document opens.
  5. Users who has read permissions in the document library level or the site level – manage to open the document successfully, meaning are not prompted with the user & password message after clicking "Open" (it opens the document).

We tried with several users, all get the same result.

During our checking, we also found out that the following dll returns "401 Unauthorized": /_vti_bin/_vti_aut/author.dll

Finally we opened a  service request and got a solution from Microsoft.

There are 3 possible ways to solve this issue:

1. Add read only permission in the root of the site collection.

2. Deactivate the feature “ViewFormPagesLockDown”:

stsadm -o deactivatefeature -url http://SERVERNAME/sites/SITENAME -filename ViewFormPagesLockDown\feature.xml

3. Run the following script on your site collection, this will add permission level to the “Limited Access” Permission level that you have in that site collection.

$siteCollectionUrl = 'http://sitecollectionurl'

[void][system.reflection.assembly]::LoadWithPartialName('Microsoft.sharepoint')

$spsite = new-object Microsoft.sharepoint.spsite($SiteCollectionUrl)

$lmtd = $spsite.rootweb.RoleDefinitions['Limited Access']

$b1 = [Microsoft.sharepoint.spbasepermissions]::Open

$b2 = [Microsoft.sharepoint.spbasepermissions]::BrowseUserInfo

$b3 = [Microsoft.sharepoint.spbasepermissions]::UseClientIntegration

$b4 = [Microsoft.sharepoint.spbasepermissions]::UseRemoteAPIs

$lmtd.BasePermissions = "$b1,$b2,$b3,$b4"

$lmtd.Update()

I tried the second solution and it worked like a charm .

 

I would like to thank Gal Sagi for helping me with this article.

Hope you find this article handy Smile.

 

How To Create A Windows 8 App For SharePoint Part 2 – The Development Stage

 

The Development Stage

In my last post How To Create A Windows 8 App For SharePoint Part 1 – The Planning Stage we discussed about the reason I chose Windows 8 HTML5 as my development environment and why it’s the best and the most intuitive environment for SharePoint developers who plan to start creating Win8 apps.

Today we will focus on developing our Win8 app and integrate it with SharePoint Online 2013. The app will serve as a search and use The new Search REST API to pull data from SharePoint 2013 Search engine (FAST) and display it in our app.

When I decided to connect my Win8 app to SharePoint 2013 Online, it seemed like a straightforward task, all I wanted  is to programmatically connect to SharePoint Online services but reality shows otherwise. I thought the answer could be found in one of WinRT’s capabilities integrating with online web services. after exploring this API for several days and even posting a question on Windows Store apps Forums asking “How to call SharePoint Online 2013's REST API from Windows 8 HTML5 App?” unfortunately for me, still haven't got any answer. then I decided it's time to take matters in hand and develop my own custom connector to SharePoint Online services.

Before starting developing any solution, it’s important to understand how SharePoint Online authentication works. The authentication "mechanism" is called Claims based authentication. First we request the token from the STS, we pass the username and password. then the STS returns a security token. after we got our security token we sent it to SharePoint and at last we get our two cookies called “FedAuth” and “rtFa” that we need to pass every time we want to request something from SharePoint.

 

Luckily for me I found this great article by Wictor Wilén explaining how to do active authentication to Office 365 and SharePoint Online using WCF. since the code sample provided was targeting framework 3.5, I tweaked it a little bit to work with framework 4.5, called it Office365ClaimsConnector and it worked like a charm.

So let’s get to work Smile

Step 1: creating a blank application

Start VS2012 > Other Languages >JavaScript > Blank App.  Name it “Win8AppForSharePoint”

image

Step 2: creating the Windows Storage & Navigation for our pages

WinRT has a great Storage capabilities and we have access to the following types:

a) local - data but only on the current device

b) roaming - data that is available to all devices the user has your app installed on

c) temporary - data that can be removed by the system at any point after your app is closed

Since our app will only be installed on one device we’ll steak with the local Storage.

Open the “default.html” and add  <div id="contentHost"></div> we’ll use this div later for navigation purposes.

Open the “default.js” file and add the following JavaScript code.

// For an introduction to the Blank template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";

var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var containerName = "cookieContainer1";
WinJS.strictProcessing();

app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
if (isAuthenticated())
return WinJS.Navigation.navigate("/pages/Search/search.html");
else
return WinJS.Navigation.navigate("/pages/Login/login.html");
}));

}
};
WinJS.Navigation.addEventListener("navigated", function (eventObject) {
var url = eventObject.detail.location;
var host = document.getElementById("contentHost");
WinJS.Utilities.empty(host);
eventObject.detail.setPromise(WinJS.UI.Pages.render(url, host, eventObject.detail.state).then(function () {
WinJS.Application.sessionState.lastUrl = url;
}));
});
function isAuthenticated() {
if (localSettings.containers.hasKey(containerName)) {
return true;
}
return false;
}
app.start();
})();

In the isAuthenticated function we're using the Windows.Storage.ApplicationData object to check if our data (user name, password, url) is already stored and navigate to appropriate page. if Authenticated, navigate straight to Search page, else go through the login page.

Step 3: Creating WCF Service

Create a WCF Service Application Project, Name it “Office365ClaimsService”.

image

Create 2 classes, first a static class called DAL, second class called QueryStringHelper, we’ll use this class later for query formatting.

image

Open Ioffice365ClaimsService.cs and add the following code .

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

namespace Office365ClaimsService
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
[ServiceContract]
public interface IOffice365ClaimsService
{

[OperationContract]
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest, UriTemplate = "/authentication")]
TokenContainer Authentication(string url, string userName, string password);

[OperationContract]
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat=WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest, UriTemplate = "/search")]
string GetSearchData(string FedAuth, string RtFa, string url, string query);


}
[DataContract]
public class TokenContainer
{
[DataMember]
public string FedAuth { get; set; }

[DataMember]
public string RtFa { get; set; }
}

}

We’ll have 2 Services, First is an Authentication service that receives the following parameters : url, userName, password. second service is the search service receiving the following  parameters : FedAuth, RtFa, url, query

Since we’re working with JavaScript and handle some sensitive data, The 2 Services use JSON as the Request Format & Response format and the type method is POST.

Open the DAL.cs file and add the following code.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using Office365ClaimsConnector;

namespace Office365ClaimsService
{
public static class DAL
{
public static TokenContainer getTokens(string url, string userName, string password)
{
TokenContainer tc = new TokenContainer();
MsOnlineClaimsHelper claimsHelper = new MsOnlineClaimsHelper(url, userName,password);
CookieContainer cc=claimsHelper.GetCookieContainer();
tc.FedAuth = cc.GetCookies(new Uri(url))["FedAuth"].Value;
tc.RtFa = cc.GetCookies(new Uri(url))["rtFa"].Value;
return tc;
}


public static string GetDataFromSP(string FedAuth,string RtFa, string url, QueryType type, string query)
{
string responseJson = string.Empty;
var uri = QueryStringHelper.BuildQuery(QueryType.Search, url, query);
CookieContainer cc = GetCookieContainer(FedAuth, RtFa, new Uri(url));
Uri queryUri = new Uri(uri.AbsolutePath);
var request = HttpWebRequest.CreateHttp(uri);
request.Method = "GET";
var accept = "application/json";
if (accept != null)
request.Accept = accept;
request.CookieContainer=cc;
var response = request.GetResponse();
Stream res = response.GetResponseStream();
using (var reader = new StreamReader(res))
{
responseJson = reader.ReadToEnd();
}
return responseJson;
}

private static CookieContainer GetCookieContainer(string FedAuth, string rtFa, Uri uri)
{
CookieContainer _cachedCookieContainer = null;
DateTime _expires = DateTime.MinValue;
CookieContainer cc = new CookieContainer();
if (_cachedCookieContainer == null || DateTime.Now > _expires)
{


// Set the FedAuth cookie
Cookie samlAuth = new Cookie("FedAuth", FedAuth)
{
Expires = _expires,
Path = "/",
Secure = uri.Scheme == "https",
HttpOnly = true,
Domain = uri.Host
};
cc.Add(samlAuth);
// Set the rtFA (sign-out) cookie, added march 2011
Cookie rtFaCookie = new Cookie("rtFA", rtFa)
{
Expires = _expires,
Path = "/",
Secure = uri.Scheme == "https",
HttpOnly = true,
Domain = uri.Host
};
cc.Add(rtFaCookie);
}
_cachedCookieContainer = cc;
return cc;
}


}
}

You’ll need to add a reference to Office365ClaimsConnector project. as a reminder, Office365ClaimsConnector is the code sample I found in this great article by Wictor Wilén that actually does all the Authentication work using WCF bindings and contracts.

Using method GetToken we pass the following parameters: url, username and password to our Office365ClaimsConnector.MsOnlineClaimsHelper class that eventually will return our tokens to the service and back to our client.

The method GetDataFromSP is responsible for our query request, it accepts the following parameters: FedAuth,RtFa, url, search type and query. basically we pass the 2 tokens to the CookieContainer object and finally to the HttpWebRequest object along with the url and the query. If our request succeeded we’ll get our search results in a nice JSON format.

Open the QueryStringHelper.cs file and add the following code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Office365ClaimsService
{
public enum QueryType
{
Search,
Lists,
}
public static class QueryStringHelper
{
public static Uri BuildQuery(QueryType type,string url,string query)
{
UriBuilder bldr = new UriBuilder(url);
switch (type)
{
case QueryType.Search:
///_api/search/query?querytext='search is cool'
string restQuery = "/_api/search/query";
bldr.Path +=restQuery;
bldr.Query = "querytext='" + query + "'";
break;
case QueryType.Lists:
break;
default:
break;
}
return bldr.Uri;
}
}
}

I created this class for future implementation for other scenarios besides Search, like CRUD operations for lists etc..

Step 4: creating Login page

in “Win8AppForSharePoint" project Create a folder called pages and inside that folder create another folder called Login with 3 files: login.css ,login.html and login.js

image

Open the “login.html” file and add the following html.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Connect to Sharepoint Online</title>

<!-- WinJS references -->
<link rel="stylesheet" href="//Microsoft.WinJS.1.0.RC/css/ui-light.css" />
<script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
   1:  
   2:     <script  src="//Microsoft.WinJS.1.0.RC/js/ui.js">
   1: </script>
   2:     
   3:     <link rel="stylesheet" href="/pages/Login/login.css" />
   4:     <script  src="/pages/Login/login.js">
</script>

</head>
<body>
<!-- The content that will be loaded and displayed. -->
<div class="detailPage fragment">
<header role="banner" aria-label="Header content">

<div class="titleArea">
<h1 class="pageTitle win-type-xx-large"></h1>
</div>
</header>
<section role="main" aria-label="Main content">
<article>
<div>
<header>
<h2 class="title win-type-x-large"></h2>
</header>
<div class="image"></div>
<div class="content"></div>
<!-- input table -->
<table border="0" style="width: 252px; height: 60px">
<tr>
<td><label>Address:</label></td>
<td><input id="address" type="text"/></td>
</tr>
<tr>
<td><label>Customer ID:</label></td>
<td><input id="username" type="text"/></td>
</tr>
<tr>
<td><label>Password:</label></td>
<td style="height: 40px"><input id="password" type="password"/></td>
</tr>
<tr>
<td>
<label>
<input class="submitButton" type="submit" name="Submit" value="Submit" />
</label>
</td>
</tr>
<tr>
<td>
<progress class="win-ring win-large progress"></progress>
</td>
</tr>
</table>
</div>
</article>
</section>
</div>
</body>
</html>

The html file holds a simple table containing the login box that has Address , User id and Password. it also containing a progress control  (out of the box) , I also added another class called ‘progress’ to hide the control and when the Login button is pressed and our request is sent to a remote server to get the access tokens, we’ll display the loading image.

<progress class="win-ring win-large progress"></progress> 

image

Open the “login.css” file and add the following css.


.detailPage section[role=main] {
-ms-grid-row: 2;
display: block;
width: 100%;
height: 100%;
overflow-x: auto;
}

.detailPage section[role=main] article {
column-fill: auto;
column-width: 800px;
column-gap: 80px;
height: auto;
width: 800px;
margin-left: 120px;
}

.detailPage section[role=main] h1 {
margin-top: 0px;
margin-bottom: 20px;
}

.detailPage section[role=main] .image {
width: 800px;
height: 100px;
/*background: rgba(209, 211, 212, 1);*/
/*background: rgba(144, 178, 41, 1);*/
}

.detailPage section[role=main] p {
margin-right: 20px;
}

@media screen and (-ms-view-state: snapped) {
.detailPage .win-contentTitle {
font-size: 11pt;
line-height: 15pt;
}

.detailPage header[role=banner] {
display: -ms-grid;
-ms-grid-columns: 44px 1fr;
-ms-grid-rows: 1fr;
}

.detailPage section[role=main] .image {
width: 260px;
height: 140px;
}

.detailPage section[role=main] article {
display: -ms-grid;
-ms-grid-columns: 290px 1fr;
-ms-grid-rows: 1fr;
-ms-grid-row: 2;
width: 290px;
margin-left: 20px;
overflow-y: auto;
overflow-x: hidden;
}

}

.progress {
display:none;
}

The Login box:

image

Open the “login.js” file and add the following JavaScript code.


(function () {

// Track if the log in was successful
var loggedIn;
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var containerName = "exampleContainer1";
var nav = WinJS.Navigation;
var container;
var FedAuth;
var RtFa;
var address;
var username;
var password;
"use strict";
var page = WinJS.UI.Pages.define("/pages/Login/login.html", {
ready: function (element, options) {
element.querySelector('.submitButton').addEventListener('click', submitLogin, false);
}
});

function submitLogin() {
try {
if (address|| username|| password)
{
//Creating message dialog box
var messagedialogpopup = new Windows.UI.Popups.MessageDialog("Input can not be empty!", "Error");
messagedialogpopup.showAsync();
return;
}
document.querySelector(".progress").style.display = "block";
address = document.getElementById("address").value;
username = document.getElementById("username").value;
password = document.getElementById("password").value;
var json = JSON.stringify({ "url": address, "userName": username, "password": password });
}
catch (err) {
}
WinJS.xhr({
type: "POST",
url: "http://localhost:2738/Office365ClaimsService.svc/Authentication",
headers: { "content-type": "application/json; charset=utf-8" },
data: json,
}).done(loginSuccess, loginFaliure, loginProgress);
};
function loginSuccess(request)
{
var obtainedData = window.JSON.parse(request.responseText);
var container = localSettings.createContainer(containerName, Windows.Storage.ApplicationDataCreateDisposition.always);
if (localSettings.containers.hasKey(containerName)) {
localSettings.containers.lookup(containerName).values["FedAuth"] = obtainedData.FedAuth;
localSettings.containers.lookup(containerName).values["RtFa"] = obtainedData.RtFa;
localSettings.containers.lookup(containerName).values["Url"] = address;
}
WinJS.Navigation.navigate('/pages/Search/search.html');
}
function loginFaliure(request)
{
document.querySelector(".progress").style.display = "none";
//Creating message dialog box
var messagedialogpopup = new Windows.UI.Popups.MessageDialog("An error occurred!", "Error");
messagedialogpopup.showAsync();
return false;
}
function loginProgress(request) {

}
})();

Using the WinJS.UI.Pages.define object we basically get all html elements located on the page we requested. using querySelector we get the Submit button element and add the EventListener for our button click. if one of the fields is empty we add MessageDialog telling the user that his input is empty. After collecting the data (address, username, password) from the Login box, we create a JSON text using JSON.stringify and pass it to our WCF Authentication service, using WinJS.xhr function we get a Promise(callback).  if we successfully logged-in we’ll get our two tokens/cookies “FedAuth” and “rtFa” that we’ll use for all of ours requests from SharePoint Online.

It’s worth mentioning that you can’t use “alert” for debugging or any pop-up notifications anymore  as WinJS doesn't support it. for debugging use console.log() and for notification use MessageDialog.

We create a key for the Storage object we talked about, pass our tokens and url to the Storage object and navigate to Search page.

Step 5: creating Search page, Template and Binding

in “Win8AppForSharePoint" project create a folder called Search with 3 files: search.css ,search.html and search.js

image

Open the “search.html” file and add the following html.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SharePoint Search</title>

<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0.RC/css/ui-light.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
   1:  
   2:     <script src="//Microsoft.WinJS.1.0.RC/js/ui.js">
   1: </script>
   2:   
   3:     <!-- Win8AppForSharePoint references -->
   4:     <link href="/css/default.css" rel="stylesheet" />
   5:     <link rel="stylesheet" href="/pages/Search/search.css" />
   6:     <script  src="/pages/Search/search.js">
   1: </script>
   2:     <script src="/js/default.js">
   1: </script>
   2:     <script src="/js/navigator.js">
</script>

</head>
<body>

<div class="content" aria-label="Main content" role="main">
<img class="spLogo" src="../../images/sharepoint-logo.jpg" />
<div id="searchLbl">SharePoint Online Search</div>
<input id="searchBox" name="search" type="text" placeholder="Search..." />
<button id="searchBtn" type="button">Search</button>
<div><progress class="win-ring win-large progress"></progress></div>

<div id="searchResultsTemplate" data-win-control="WinJS.Binding.Template">
<div class="itemResult">
<div><h2><a class="resultTitleLink" data-win-bind="innerText: title;source: url"></a></h2></div>
<span> <a class="resultUrl" data-win-bind="innerText: url;source: url" ></a>
<span class="resultDate" data-win-bind="innerText: date"></span>
</span>
<div class="resultContent" data-win-bind="innerText: content"></div>


</div>
</div>
<div id="searchListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemTemplate:searchResultsTemplate,selectionMode:'none', layout: { type: WinJS.UI.ListLayout }}"></div>
</div>
</body>
</html>

The HTML will hold 3 section:

A) HTML elements containing our Search box and the “Search” button.

B) DIV marked with data-win-control="WinJS.Binding.Template" attribute tells WinJS to treat it like a template, element marked with data-win-bind=" " attribute inside the template will help the binding engine to know which JavaScript properties from the data source to map to the appropriate HTML. read more about it here.

C) DIV marked with data-win-control="WinJS.UI.ListView" attribute *transforms this simple DIV to JavaScript ListView control. inside the ListView Control we have another attribute called data-win-options="{}" that we’ll use to tell the control which template to bind, which  Layout etc.. read more about it here.

*This operations is done thanks to a JavaScript code: WinJS.UI.processAll();  Since we added the ListView to a Page control, we don't need to call WinJS.UI.processAll because the Page control does it for us.

Open the “search.css” file and add the following css.

#searchBox {
width: 500px;
height: 35px;
padding: 10px 20px 10px 10px;
margin: 1px auto 50px;

}
#searchLbl {
font-family:'Segoe UI Symbol';
font-weight: bold;

}

.content
{
text-align: center; padding-top: 50px;

}
.pagetitle{


}
.resultTitleLink{
text-decoration: underline;


}
.resultAuthor{


}
.resultDate{
color:#777;
font-size: small;
}
.resultUrl{
color:#388222;
font-size: small;

}
.resultContent {
font-size: small;
}
.spLogo {
height: 103px;
}
.progress {
display:none;
margin: 1px auto 50px;
}

I also added an Office image and the Search box got a nice and simple search based UI :

image

Open the “search.js” file and add the following JavaScript code.


(function () {
"use strict";
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var containerName = "exampleContainer1";
var searchResults = new Array();
var page = WinJS.UI.Pages.define("/pages/Search/search.html", {
ready: function (element, options) {
document.getElementById("searchBtn").addEventListener("click", getSearchData, false);
}
});

function getSearchData()
{
if (localSettings.containers.hasKey(containerName)) {
var FedAuth = localSettings.containers.lookup(containerName).values["FedAuth"];
var RtFa = localSettings.containers.lookup(containerName).values["RtFa"];
var Url = localSettings.containers.lookup(containerName).values["Url"];
var Query = document.getElementById("searchBox").value;
if (Query == "") {
//Creating message dialog box
var messagedialogpopup = new Windows.UI.Popups.MessageDialog("Input can not be empty!","Error");
messagedialogpopup.showAsync();
return;
}
var lstView = document.getElementById("searchListView").winControl;
//clear array items
searchResults = [];
var dataList = new WinJS.Binding.List(searchResults);
lstView.itemDataSource = dataList.dataSource;
document.querySelector(".progress").style.display = "block";
var json = JSON.stringify({ "FedAuth": FedAuth, "RtFa": RtFa, "url": Url, "query": Query });
WinJS.xhr({
type: "POST",
url: "http://localhost:2738/Office365ClaimsService.svc/search",
headers: { "content-type": "application/json; charset=utf-8" },
data: json,
}).done(processData, dataError);

}
}


function processData(response) {
document.querySelector(".progress").style.display = "none";

var data = JSON.parse(response.response);
data = JSON.parse(data);

var results = data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;
for (var i = 0, len = results.length; i < len; i++) {
var item = results[i].Cells;
var date = new Date(item.results[8].Value);
var resultItem = {
author: item.results[4].Value,
title: item.results[3].Value,
date: date.toDateString(),
url: item.results[6].Value,
content: item.results[10].Value,
};
searchResults.push(resultItem);
}
var lstView = document.getElementById("searchListView").winControl;
var dataList = new WinJS.Binding.List(searchResults);
lstView.itemDataSource = dataList.dataSource;

}
function dataError(data)
{
document.querySelector(".progress").style.display = "none";
//Creating message dialog box
var messagedialogpopup = new Windows.UI.Popups.MessageDialog("Error!", "Error");
messagedialogpopup.showAsync();
var d = data;
}
})();

After we got all the html elements using  WinJS.UI.Pages.define object. we get the Search button element and add the EventListener for our button click. We grab all the data from our storage object using a key , of course if our query is empty we’ll throw a pop-up notifying the user .

Since our search data will change every time we click our search button we need to send an empty array list to ListView control to clear the data. we use a binding list that excepts our JavaScript array and connecting it to ListView itemDataSource to display the data. after collecting the data ( FedAuth,RtFa, url, query ) from the storage object, we create a JSON text using JSON.stringify and pass it to our WCF Search service we talked about earlier to pull data from SharePoint Online using Search REST API. if the operation succeeded we get our JSON string, then we parse it and start populating the JavaScript list array, init our binding list with our data and connect it to ListView.itemDataSource to display the content that will be showed in a template we created on the HTML page.

The final result:

image

Step 6: Summary

Today we successfully created our Windows 8 App integrated with SharePoint Online services. We programmatically connected to SharePoint using Claims-based Authentication, got our data with the help of SharePoint search REST API and displayed our search results. of course for displaying the search results I could iterate the JSON object and start appending the HTML elements but instead we used the Template and the Binding capabilities to render our data in an easy and simple way that WinJS offers us.

We've only seen the tip of the iceberg, Windows 8 app development is a rich and exciting world so I strongly advise you to start exploring it Smile.

Soon we’ll try to extend this app to interact with the operation system.

You can download the full project on CodePlex.

I hope this post was helpful .

How To Create A Windows 8 App For SharePoint Part 1 – The Planning Stage

 

The Planning Stage

There’s a big buzz going on around Windows 8 app development, In my earlier post “Create a Metro Live Tile using MetroJS, JsRender and the new REST API” I showed how to create a metro live tile using html and JavaScript and today we continue exploring this subject area and create Windows 8 app that interacts with SharePoint.

We will focus on Windows 8 HTML5 app, There are 4 main reasons why I chose to use HTML5 development in this example.

A) SharePoint developers will feel much more comfortable around JavaScript and HTML rather then in XAML development.
B) Leverage your existing skills, Win 8 App uses Internet Explorer 10 engine that has extensive support for web standards like HTML5 and CSS3 that we can use in our App development to create rich native apps as described in this article .
C) Create a *cross-platform application that can easily become a mobile app or even a SharePoint app.
D) HTML5 is the future so it's best to always be up to date.

*When talking about Win8 HTML5 app and cross-platform it’s a bit tricky,why you ask?,Well Windows 8 introduced a new term called Windows Runtime or WinRT , basically WinRT architecture gives us the ability to talk directly to window’s COM-based API, a privilege that only C++ programmers had. Of course C# and VB.NET programmers could use P/Invoke to call the native API but it wasn’t an easy task and a significant overhead. Microsoft decided No more discrimination, C# and VB.NET, as well as JavaScript programmers now have the same power. With that said let’s get back to our cross-platform issue, it’s important to remember that you can use WinRT functionality only on Microsoft’s products like Windows 8 or Windows Phone so when you're planning a cross-platform application maybe you should think using a cross-platform JavaScript libraries like Jquery, KnockoutJS etc. but keep in mind that it always more preferable to use the native API.

windows-8-apps

In our example we plan to use WinJS  or “Microsoft Window Library for JavaScript SDK”. WinJS library offers us a similar development experience we get from Jquery and KnockoutJS ,It provides a set of controls, a templating engine, a binding engine, Promises to handle the asynchronous calls etc..

Part 2 is the development stage and we plan to create a working Windows 8 HTML5 app so see u next time.

SharePoint: Save Site As Template Common Errors/Mistakes

Hi guys,

I’ll try to explain couple of common mistakes/errors made when trying to save a site as site template.

1) Save publishing site as site template.

The Problem

Common mistake is made when trying to save publishing site as site template , There is a  good reason why this publishing template is not supported and does not include the ‘Save as Template’ link, There is a workaround to enable this feature as explained in this post. It maybe a quick solution but in the long run this solution is very problematic when later trying to upgrade the site templates generated from this template.

The Solution

Develop Site definition /web template.

2) Getting “InvalidOperationException Error generating solution files in temporary directory” error when trying to save as site template

The Problem

When saving as site template the WSP generated is temporary saved in the c:\temp or in c:\windows\temp folder and later in the solution gallery on SharePoint. Usually what happens is that the App pool account that creates the WSP is unauthorized to access the c:\temp or the c:\windows\temp folder.

The Solution

Give the App pool account full permissions to c:\temp and c:\windows\temp folder.

3) Getting “Microsoft.SharePoint.SPException: Error exporting the site field named "SomeLookupField" error when trying to save as site template.

The Problem

When trying to save as site template and the site contains some lookup field you might get Microsoft.SharePoint.SPException: Error exporting the site field named "SomeLookupField” exception.

The Solution

a) You need to remove the curly brackets from ID definition of your field.

b) Add ‘overwrite’ attribute to your field and set it to 'TRUE'.

The full article.

4) Save as site template SharePoint 2010 Blog.

The Problem

When trying to save blog as site template the posts list appear 5 times in the site as 5 different views.

The Solution

The workaround that Manas found is:
a) Navigate to “View all site content “.
b) Select one of the “Posts” >>navigate to its “List settings” .
c) Further go down into “My  Posts” List and select the option “Make this as default view “.
d) now you would see only one “Posts” list.

5) Getting “Error exporting the list named “Announcements” at the URL: Lists/Announcements” error when trying to save as site template.

The Problem

When creating a site as web template and then creating sub sites which you wish to save as a web template you receive the following error “Error exporting the list named “Announcements” at the URL: Lists/Announcements”.

The Solution

As posted by Jonathan Adams in this great post

Run the SharePoint 2010 Management Shell as Administrator.  Run the following command to disable the default content type feature:

Disable-SPFeature –Identity ctypes –url http://SiteCollection

Then enable the content type feature:

Enable-SPFeature –Identity ctypes –url http://SiteCollection

Try saving the subsite as a template again, it should succeed.

 

Hope you find this post helpful Smile

see u next time…

More Posts Next page »