Monday, November 30, 2009

SharePoint 2010: Metadata Service

In SharePoint 2010 two metadata related supports are added which were a big challenges in SharePoint 2007. One Challenges I faced personally was not having the support of taxonomy or hierarchical metadata. Another challenges was the metadata could not be shared across site collection boundaries. So in SharePoint 2010 the following two new features are added to solve the issues:

1. Taxonomy Support:

Taxonomy is a mean of managing data in hierarchy. Though other CMSs have this taxonomy support out of the box, till SharePoint 2007 we had not this support out of the box. But SharePoint 2010 has this support built in. We can now use hierarchical metadata to tag content in SharePoint 2010.

2. Shared Metadata

Prior to SharePoint 2010 there was no way to share metadata across site collection boundaries. So if you ever needed to share metadata then you would go and copy the same metadata in different site collection. Now in SharePoint 2010 there’s a shared service called “Shared Metadata Service” which will facilitate the metadata sharing across the site collection and  web application level.

 

Few definitions associated with Metadata Service

There are two types of metadata that can be associated with an item in SharePoint 2010:

1. Managed Metadata or Taxonomy: As the name suggested these metadata are managed centrally (from central administration). Users with appropriate permission can only add/edit/deleted these kind of metadata. Other users just use these metadata to tag item.

2. Unmanged Metadata or Folksonomies: These unmanged metadata are not managed centrally. If a user find that managed metadata can’t meet his requirement then he can add his own metadata to the item.

Few others terms are described below:

Term: Term is keyword or phrase that can be associated with a content. Simply we can say term is a single node in taxonomy.

Term Sets: A Collection of terms. So simply we can say term sets as a subset of taxonomy.

Term Store: A store (say database) which store all terms.

Managed Keywords: Managed keyword is a kind of metadata field which supports only non-hierarchical list called keyword-set. Managed Keyword columns allows to select from existing metadata store. It also allow to add the keyword to the store if it doesn’t exist already. This is actually an way to implement the Folksonomies.

 

How to define Term sets and Terms?

Go to Central administration site –> Application Management –> Manage Service Applications (under service applications). Then click Managed Metadata Service and you’ll be landed in the term store properties page. In that page move your mouse on the “Managed Metadata Service” and you’ll get a popup menu “New Group” as shown  below:

image 

Figure 1: New metadata group.

Once you create a new group you can create term set and term. In the following figure I have defined one group (Sales System) then I have defined two Term sets (IT Product and Region). Then I have defined different terms under the term sets.

image

Figure 2: Term sets and terms under group.

 

Use terms in list/library column

To assign metadata you need to create a column of type “Managed Metadata” for your list or libraries. If you go to add a column to your list you’ll find a new column type “Managed Metadata”. In that column you have two options to set your managed metadata.

  • Use a Managed term set: With this option user will just be able to select from available terms. User will not be able to add new terms in the term set. As shown in the following figure, this column will only allow the terms defined in the term set “IT Product”

image 

  • Customize your term set: This option is for “Managed Keywords”. With this option selected user will be able to add new term in the custom term set. when you will select this option a new term set will be created for you.

I had created a column ProductType that is bound to the term set “IT Product”. When I landed to add new item page and started typing in the Product Type field, the suggestions were presented as shown below:

image

Figure 3: Metadata auto suggestion

Summary

In summary, the shared metadata service will help much to manage metadata from a central place. Also taxonomy will help better content tagging and better content organization.

Thursday, November 26, 2009

SharePoint: Download, Upload file from Document Library

Download from Document Library

If you need download a file from document library you can do so easily. First you need to get the SPFile object from the file url. then you need to create a file stream for local file. Finally you need to write the SPFile object to the strem. The following code snippet shows how easy it is

SPFile spFile = currentWeb.GetFile(docLibFileUrl); 
FileStream outStream = new FileStream(localFileName, FileMode.Create);
byte[] fileData = spFile.OpenBinary(); 
outStream.Write(fileData, 0, fileData.Count()); 
outStream.Close();        

Upload to Document Library

If you ever need to overwrite a file in document library then first you need to get the SPFile. After that you need to checkout the file and finally call the SaveBinary method to save data to the spfile.


SPFile spFile = currentWeb.GetFile(docLibFileUrl);
spFile.CheckOut();
byte[] binaryData=File.ReadAllBytes(localFileName);
spFile.SaveBinary(binaryData, true);
spFile.CheckIn(string.Empty);

Sunday, November 22, 2009

SharePoint: Integrate SSO with third party site

Say you have a sharepoint site which needs to authenticate to a third party site with web service. So authentication on your SharePoint site will be against the third party site. Here how I have implemented:
Created a custom membership provider. In this membership provider I have connected to the third party site with token or username/password.

Create a custom  Membership Provider

First of all I have developed a custom membership provider. The following code snippet shows my custom membership provider. FYI, I have overridden few methods of membership just for making things working:
public class MyMembershipProvider : MembershipProvider
{
    //actually token is passed in the parameter usernameToMatch
    public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
    {
        AuthUser user = webService.GetUserByToken(usernameToMatch);
        MembershipUserCollection users = new MembershipUserCollection();
        if (user != null)
        {
            users.Add(new MembershipUser(Membership.Provider.Name, user.UserName, user.UserName, "aa@bb.com", string.Empty, string.Empty, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now));
            totalRecords = 1;
        }
        else
        {
            totalRecords = 0;
        }
        return users;
    }


    public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            int total=0;
           MembershipUserCollection users = //find user by username
           if (users.Count == 1)
           {
               return users[username];
           }
           return null;
        }


    public override bool RequiresQuestionAndAnswer
    {
        get { return false; }
    }

    public override bool RequiresUniqueEmail
    {
        get { return false; }
    }


    public override bool ValidateUser(string username, string password)
    {
        if (string.IsNullOrEmpty(password))
        {
           //validate by token as this will be called from autologin page
        }
        else
        {
           //validate by username password
        }
    }
}
Code Snippet 1: Membership Provider

In the above code snippet FinduserByUsername will find user by token and GetUser will find user by username.By setting enableSearchMethods=false in the provider settings in the web.config, you can ensure that the find method will not be invoked by sharepoint. Its only be invoked by you.

Modify web config to put custom membership in action

Once you have finished with custom membership provider and deployed in gac or bin and added the dll in safecontrol list you need to use it. You need to make sure you have put your custom provider web config as shown below:
<membership defaultProvider="AuthProvider"> 
<
providers>
<
add name="AuthProvider"
type="K2.Providers.K2MembershipProvider,K2.Providers,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=c2c2b5c92b3495f9
"
enableSearchMethods="false" connectionProtection="None" />
</
providers>
</
membership>
Code Snippet 2: Define membership provider in web.config

You need to put this configuration in both your web application’s web.config as well as site collection web application’s web.config file. Also if you need some kind of configuration in the appsettings section of web.config then you need to put it central admin web application’s web config also. This is because when you will set site collection administrator from central admin site, central admin site will try to use your membership provider and definitely any appsettings value your membership provider will try to read from config file will fail if the value is not in the web.coinfg file.

Change the Authentication Provider from central admin site

Once you have modified both web.config file of current and central admin site, navigate to the central admin site. Go to the page Application Management –> Authentication Providers (under application security section). Make sure your web application is selected from list and then click on the default provider. In the provider text box put the provider name you have defined in the web.config file for your provider (which is AuthProvider in my case as shown in the code snippet 2). Once you have finished this go to Application Management –> Site Collection Administrators (under sharepoint site management section). Make sure your site collection is selected in the list. And then enter the user name in site collection administrator box. If everything is ok then you will find that SharePoint is getting the user.
image


Figure 1: Setting site collection administrator in central administration site.

Auto login the user

Now say you to auto login user when someone try with the url http://myserver.com/customlogin.aspx?token=xxxxx. Here you use this token to validate with membership provider. For this create a custom aspx page and in the page load event take the token from querystring and validate with the membership provider. The following code in the page load event will of check the token with membership provider.
string token=Request.QueryString["token"];
if (string.IsNullOrEmpty(token))
{
   Response.Redirect("AccessDenied.aspx");
}
else
{
   if (Membership.Provider.ValidateUser(token, string.Empty))
   {
      UserCollection users=Membership.Provider.FindUsersByName(token,......);
      FormsAuthentication.RedirectFromLoginPage(users[0].UserName, true);
   }
}


In the above code block once the token is validated, we get the user by the FindUsersByName method then we redirect from login page passing the user name.

To deploy the custom login page you can put in the web service virtual directory and you also need to change the markup of the aspx file as shown below:
<%@ Assembly Name="K2.UI.Utils, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c2c2b5c92b3495f9" %>
<%@ Page Language="C#" AutoEventWireup="true" Inherits="K2.UI.Utils.MyLoginPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div> 
    </div>
    </form>
</body>
</html>

In the above code snippet, the assembly  tag defines that assembly where the code behind of the page exists.

Saturday, November 21, 2009

SharePoint: Download document Library file From Remote Client.

Say you have deployed your SharePoint on a pc www.sharepointserver.com. And you are accessing the SharePoint site from your local pc named as clientpc. Now if you need to download a file from SharePoint document library then how will do that? Let’s find if there’s any SharePoint built-in solution.

 

Has any SharePoint Solution?

At first you will try to find any built in web service in SharePoint. At least I did so and I spent few hours to find out if there’s any web service in SharePoint to download the file. I found one which is Copy.asmx. But copy.asmx will work only if source and destination url in the same web application or site collection. So if you use the copy.asmx file to download file in the same server then it’ll work fine.But if you try to use it from remote pc then you’ll get exception.

 

Non-SharePoint (but asp.net) Solution

SharePoint is finally asp.net application. So anyone can expect asp.net experience will help anyway. In asp.net you can use System.Net.WebClient to download file from web server. The same logic can be used to download file from  SharePoint considering it as an asp.net application.

System.Net.WebClient client = new System.Net.WebClient();

string userName = ConfigurationManager.AppSettings["SPUserName"];

string password = ConfigurationManager.AppSettings["SPPassword"];

string domain = ConfigurationManager.AppSettings["SPDomain"];

if (string.IsNullOrEmpty(domain))

{

client.Credentials = new NetworkCredential(userName, password);

}

else

{

client.Credentials = new NetworkCredential(userName, password, domain);

}

string urlOfTheSPFile = "http://spServer.com/mysite/Docs/file.doc";

string localPathToTheFile = @"d:\DownloadedFiles\file.doc";

client.DownloadFile(urlOfTheSPFile, localPathToTheFile);

client.Dispose();

In the above code snippet the WblClient.DownloadFile method will take the url of the file and local path of the file where the file will be saved. I have not tested if its possible to upload file with WebClient but I think it should be.

Thursday, November 19, 2009

SharePoint: User ID/Name in SPQuery

If you want to use user ID or name in SPQUery then there are few ways you can pass user information. You can use user name to filter or you can use user id. You can download U2U CAML Query builder to build query.

1. Query with user name

If you have user name and you need to query a list with that user name then you can do so by using the following CAML query:

<Query>
   <Where>
      <Eq>
         <FieldRef Name='SalesPerson' />
         <Value Type='User'>john</Value>
      </Eq>
   </Where>
</Query>

In the above CAML the value type user means the that the filed contains user data. If you use domain account then user name value will be domain\username. For domain you also need to set the show field option to account as shown below:

image

 

2. Query with User ID

If you have user ID instead of user name then you need to modify the above CAML query a bit. If you even use U2U CAML Query builder, it doesn't provide the support of User ID. The following CAML will result all list items with SalesPerson ID  7.

 

<Query>
   <Where>
      <Eq>
         <FieldRef Name='SlaesPerson' LookupId='true' />
         <Value Type='User'>7</Value>
      </Eq>
   </Where>
</Query>

In the above CAML query LookupId means to compare ID instead of user name.

FYI, CAML is case sensitive.

Tuesday, November 17, 2009

WindowsFormsHost: Host ActiveX and windows form controls in WPF

If you ever need to host ActiveX control in your wpf application then you can do it by using WindowsFormsHost control. Also sometimes you may need to add windows form control in WPF. you may have developed some controls in windows form technology and you don't want to rewrite this for WPF. WindowsFormsHost control can save you for this time. The following steps describes what to do to add ActiveX or windows form control in wpf application:

1. Add reference to WindowsFromsIntegration assembly which is WindowsFormsIntegration.dll. This is the assembly where WindowsFormsHost control is defined. So if you add WIndowsFormsHost control before adding the assembly reference you''ll find that the WindowsFormsHost control is not recognized in XAML (if you just drag and drop the control form toolbox).

 

2. Add reference to windows forms assembly which is system.windows.forms.dll. You also need to add the namespace in XAML file as shown below:

xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" 

3. Add reference to the activex control. When you add reference from VS automatically generate an AxHost wrapper for the activex control. Then add the assembly reference in XAML file as shown below:   

xmlns:ax="clr-namespace:AxLib;assembly=AxInterop.AxLib"

FYI, if you try to add the activex dll reference without adding the other two reference you may not get the namespace in the XAML. WindowsFormsHost control can also be used to host any control designed for windows form.

Now you can add your ActiveX control in one of two ways:

1. Add Control directly in XAML as shown below:

<WindowsFormsHost Name="wfHostControl">
      <ax:MyControl x:Name="axMyCtrl"/>
</WindowsFormsHost>

2. Add the control from code by instantiating and placing in the child property of the WindowsFormsHost control.

MyControl ctrl=new MyControl();

wfHostControl.Child = ctrl;

If you need to do something on form load its better to work on WindowsFormsHost control's load event instead of wpf application's load event.

Friday, November 13, 2009

SharePoint: Create blank Web Part Page Programmatically

If you ever need to create web part page from SharePoint Programmatically then how will you do so? The first thing I did was to follow how SharePoint actually do this. When you Click Site Settings -> Create -> Web Part Page, the following screen will come up.

image

Figure 1: Create web part page

I had noticed that the page is using _layouts/spcf.aspx so I had opened the page and found that its finally using /_vti_bin/owssvr.dll?CS=65001 for creating web part page. Going to explore the dll I found its not .net dll :-((. So its difficult to find how actually it works.

 

Then suddenly I found the solution by myself. The solution is to create a blank web part page and keep in the library. Then when I need to create another blank web part page I can just copy it and create new one. The following code snippet will copy the existing source file to destination.

public static string CopyFile(string sourceFileName, string listName, string destinationFileName)

        {

            try

            {

                SPList list = SPContext.Current.Web.Lists[listName];

                SPListItem sourceItem = null;

                foreach (SPListItem item in list.Items)

                {

                    if (item.Name.Equals(sourceFileName, StringComparison.OrdinalIgnoreCase))

                    {

                        sourceItem = item;

                        break;

                    }

                }

                string destinationUrl = sourceItem.Url.Replace(sourceItem.Name, string.Empty);

                destinationUrl = string.Format("{0}/{1}/{2}", SPContext.Current.Web.Url, listName, destinationFileName);

 

                sourceItem.CopyTo(destinationUrl);

 

            }

            catch (Exception exp)

            {

                return string.Format("Error: {0}", exp.Message);

            }

            return "Copied successfully";

        }

So if you call the function as shown below then it'll copy the SourceTemplate.aspx (which is the blank web part page) in list "site web part pages" to destination.aspx in the same list.

CopyFile("SourceTemplate.aspx", "Site Web Part Pages", "destination.aspx");

So the creating of web part page is implemented by copying an existing one. Good trick huh! :))

Friday, November 6, 2009

SharePoint: Create custom web service

This post will describe how to create a custom web service and deploy it in SharePoint. I have divided this post into two parts mainly. The first part will describe how to develop the web service and the second part will describe how to deploy this web service in sharepoint site.

Create a Visual Studio Project for the web service

1.Create Project: Create a new web service project from visual studio. A default web service will automatically be added in the project. Remove that one and add a new web service.


2. Use SPContext if Required: Add required methods in the web service. To use SharePoint classes add references to SharePoint Assembly. You can access the current SharePoint Context by accessing Microsoft.SharePoint.SPContext.


3. Place DLL in GAC: Once you are done with coding and the project is compiling ok then you need to place the dll of the project to GAC. Sign your assembly with strong name before deploying in GAC.


4. Modify web config by adding the the assembly in safecontrol: Add the assembly in the safe control section of the web config.


5. Change the web Service Markup: Now your web services's class in GAC so you need to link the asmx file to the dll in GAC. Open the ServiceName.asmx file (in markup mode by right clicking on the asmx file in VS and click View Markup). You will find the markup file as shown below:

<%@ WebService Language="C#" CodeBehind="RepositoryService.asmx.cs" Class="RepositoryServiceProject.RepositoryService" %>

Here the codebehind attribute refers to the cs file (in this case webservice.asmx.cs) associated with this service and class attribute refers to the class in that code behind file (RepositoryServiceProject.WebService1)will be used for this web service.


As our web service's code behind file will be in GAC so we need to link the web service to use the assembly in the GAC. You need to remove the codebehind attribute from the asmx markup and need to provide the full assembly/class name in the class attribute. For me the change was as shown below:

<%@ WebService Language="C#" Class="RepositoryServiceProject.RepositoryService, RepositoryServiceProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c2c2b5c92b3495f9" %>

 

Generate and Modify Discovery and WSDL file

To use the web service we have just developed we need to create discovery (.disco) and WSDL (.wsdl) files. To generate these two files.


1. Deploy the asmx file to a temporary site: Create a demo web site from IIS and deploy the asmx file in the web site. Say the url is http://myserver/myservice.asmx. We need this temporary deployment to generate disco and wsdl file.


2. Generate Disco and wsdl file: To generate the disco and wsdl you need to open the visual studio command prompt.
disco http://myserver/myservice.asmx /out:c:\webservicefiles
Here the out parameter defines where the output disco and wsdl file will be placed. Change the url http://myserver/myservice.asmx to one where you deployed your service temporarily.


3. Customize disco file: After successfully running the disco command you'll find two files in the output directory. Open the disco file

  • replace the line

<?xml version="1.0" encoding="utf-8"?>

with

<%@ Page Language="C#" Inherits="System.Web.UI.Page" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<% Response.ContentType = "text/xml"; %>

  • replace the following lines in the disco file
  <contractRef ref="http://server1/RepositoryService.asmx?wsdl" docRef="http://server1/RepositoryService.asmx" xmlns="http://schemas.xmlsoap.org/disco/scl/" />
  <soap address="http://server1/RepositoryService.asmx" xmlns:q1="http://server1.org/" binding="q1:RepositoryServiceSoap" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
  <soap address="http://server1/RepositoryService.asmx" xmlns:q2="http://server1.org/" binding="q2:RepositoryServiceSoap12" xmlns="http://schemas.xmlsoap.org/disco/soap/" />

with

    <contractRef ref=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request) + "?wsdl"),Response.Output); %> docRef=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> xmlns="http://schemas.xmlsoap.org/disco/scl/" />
    <soap address=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> xmlns:q1="http://tempuri.org/" binding="q1:HelloWorld" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
    <soap address=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> xmlns:q2="http://tempuri.org/" binding="q2:ServiceSoap12" xmlns="http://schemas.xmlsoap.org/disco/soap/" />

4. Customize wsdl file: Open the wsdl file.
  • replace the following line:

<?xml version="1.0" encoding="utf-8"?>

with

<%@ Page Language="C#" Inherits="System.Web.UI.Page" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<% Response.ContentType = "text/xml"; %>

  • find the following two lines in the wsdl file
<wsdl:service name="RepositoryService">
    <wsdl:port name="RepositoryServiceSoap" binding="tns:RepositoryServiceSoap">
      <soap:address location="http://server1/RepositoryService.asmx" />
    </wsdl:port>
    <wsdl:port name="RepositoryServiceSoap12" binding="tns:RepositoryServiceSoap12">
      <soap12:address location="http://server1/RepositoryService.asmx" />
    </wsdl:port>
  </wsdl:service>

and in the above section replace the location with value

<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %>
So the finally the section will look like:

<wsdl:service name="RepositoryService">
    <wsdl:port name="RepositoryServiceSoap" binding="tns:RepositoryServiceSoap">
      <soap:address location=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> />
    </wsdl:port>
    <wsdl:port name="RepositoryServiceSoap12" binding="tns:RepositoryServiceSoap12">
      <soap12:address location=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SPWeb.OriginalBaseUrl(Request)),Response.Output); %> />
    </wsdl:port>
  </wsdl:service>

5.Rename the wsdl and disco file:
  • Rename the yourservice.disco file to yourservicedisco.aspx
  • Rename the yourservice.wsdl file to yourservicewsdl.aspx
6. Copy the web service file to its final location: Now copy three files yourservice.asmx, yourservicedisco.aspx and yourservicewsdl.aspx in the location like "Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI".

7. Make final changes to the spdisco.aspx: Open the file spdisco.aspx on location "Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI". Now add the following two lines inside <discovery></discovery> tag. Remember to replace the MyService.asmx with your service name in the following code snippet.

    <contractRef ref=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(spWeb.Url + "/_vti_bin/MyService.asmx?wsdl"), Response.Output); %> docRef=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(spWeb.Url + "/_vti_bin/MyService.asmx"), Response.Output); %> xmlns=" http://schemas.xmlsoap.org/disco/scl/ " />

    <discoveryRef ref=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(spWeb.Url + "/_vti_bin/MyService.asmx?disco"),Response.Output); %> xmlns="http://schemas.xmlsoap.org/disco/" />
 

Check your web service

Now you can check if your web service is working or not. At first try to browse the web service from browser. If it works then try to write a simple client application and then try to call the web methods form that client application. The best way to check the web service is to access SPContext.Current inside web service code. If SPContext.Current is not null then definitely your web service is deployed properly.