Sharepoint


My friend asked me that he has a document repository site in sharepoint and wants to edit the documents online.

 Much like the “edit in Microsoft Word” option of sharepoint, but he needed to do that programmatically.

Such features are available in MS Office 2007 version.So, my friend first needed to check the MS Office version of the client machine. As you know, it is very difficult to get software information of the client machine through web applications.

My friend tried various options,he checked the registry but it only returned the servers information not the clients.

After searching the net for long, the sharepoint upload page gave him a good idea.

He noticed that the “Upload multiple files” is shown in some versions and not in others. Digging deep he found the “Upload Multiple Documents” feature is an ActiveX control that installs with Office 2003/2007 so if you aren’t running either Office versions then the option won’t show up in SharePoint. This ActiveX control is called STSUpld.UploadCtl.

Now that we know which activeX control to call to check the version of office, now we need to edit the document online as it can be done through “Edit in Microsoft office word”.

After seraching the net, I found an ActiveX control that allows users to edit documents with their associated applications in the context of Microsoft Windows SharePoint Services called OpenDocuments Control.

So, in the final code, first we check whether STSUpld.UploadCtl exists or not and then we call OpenDocuments axtiveX to edit the document on the fly.

The javascript for this is shown below:

function EditDoconFly(strDocument){var objEditor;var objactivex; try{if(objactivex = new ActiveXObject(‘STSUpld.UploadCtl’)){objEditor = new ActiveXObject(‘SharePoint.OpenDocuments.1′);if(objEditor){ objEditor.EditDocument(strDocument, ”); }else{ alert(‘Cannot edit documents on fly below Microsoft Office 2007′);  }objactivex = ”;objEditor=”;}else{alert(‘Cannot edit documents on fly below Microsoft Office 2007′);}}catch(e){alert(‘Cannot edit documents on fly below Microsoft Office 2007′);} return false;} 

To view users in Sharepoint we have to ge to http://servername/_layouts/people.aspx. There we can see all user groups and users added to each group. Remembering this path might be easy for sharepoint developers but what about common users who want to view a report displaying all users, their email id and usergroups?

To do this, we need to do some amount of custom coding…. which is shown below.

protected void Page_Load(object sender, EventArgs e)
{
string siteUrl = SPControl.GetContextSite(Context).Url;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
string Globaltbl = string.Empty;
using (SPSite ospSite = new SPSite(siteUrl))
{
using (SPWeb site = ospSite.OpenWeb())
{
try
{
SPGroupCollection groups = site.SiteGroups;
dtUserTable.Columns.Add(new DataColumn(“Name”, typeof(string)));
dtUserTable.Columns.Add(new DataColumn(“Email”, typeof(string)));
dtUserTable.Columns.Add(new DataColumn(“Group”, typeof(string)));
Globaltbl += “<table width=’100%’ border=’0′ cellpadding=’6′ cellspacing=’4′ >”;
Globaltbl += “<tr><th align=’left’ style=’width:33%; font-family:verdana, Arial ;font-size:11px; color:#000000;’>Name</th><th align=’left’ style=’width:34%; font-family:verdana, Arial ;font-size:11px; color:#000000;’>Email</th><th align=’left’ style=’width:33%; font-family:verdana, Arial ;font-size:11px; color:#000000;’>Group Name</th></tr>”;
foreach (SPGroup grp in groups)
{

if (grp != null)
{
SPUserCollection users = grp.Users;
if (users != null)
{
if (users.Count > 0)
{
foreach (SPUser user in users)
{
Globaltbl += “<tr width=’100%’><td style=’width:33%; font-family:verdana, Arial ;font-size:11px; color:#000000;’>” + user.Name.ToString() + “</td><td style=’width:34%; font-family:verdana, Arial ;font-size:11px; color:#000000;’>” + user.Email.ToString() + “</td><td style=’width:33%; font-family:verdana, Arial ;font-size:11px; color:#000000;’>” + grp.Name.ToString() + “</td></tr>”;
DataRow temprow = dtUserTable.NewRow();
temprow["Name"] = user.Name.ToString();
temprow["Email"] = user.Email.ToString();
temprow["Group"] = grp.Name.ToString();
dtUserTable.Rows.Add(temprow);
temprow = null;
}
}
}
}
}
Globaltbl += “</table>”;
Page.Controls.Add(new LiteralControl(Globaltbl.Trim()));
}
catch (Exception ex)
{
SPUtility.HandleAccessDenied(ex);
}
finally
{
site.Dispose();
ospSite.Dispose();
}
}
}
});
}

This will display the user records on the web page as:

 

 

Name Email Group Name
Gitolekha Ray gitolekha.ray@someplace.com Admin
Charles Dickens charles.dickens@someplace.com Visitor

 

  This will open a web page displaying all users in the system, their name, email address and user group. In case the user profile only contains the user id and not the name, you can write code to fetch it from the LDAP. You can also export the data to excel so that the user finds it easy to view the records.

The below code is only to show, how to export this data to excel, so that the user can view the user details exactly as shown above in excel, so the code to export to excel is shown below:

This will export the user details to excel in exactly the same format as shown above, so that the user finds it easy to search for a user and see which group he belongs to rather than remembering the complicated sharepoint url.

 
 protected void btnExport_Click(object sender, EventArgse)
{

 string defaultFileName = “UsersReport.xls”;
 Response.ClearContent();
 Response.AddHeader(“content-disposition”, “attachment;filename=”+ defaultFileName);
 Response.ContentType = “application/excel”;
 this.EnableViewState = false;
 Response.Charset = “”;
 StringWriter sw = new StringWriter();
 HtmlTextWriter hw = new HtmlTextWriter(sw);
}

 
public string CreateExcelFile(DataTable dt)
{
 SPSite site = SPContext.Current.Site;
 SPWeb web = site.OpenWeb();
 string sTableStart = “<HTML><BODY><TABLE Border=1>”;
 string sTableEnd = “</TABLE></BODY></HTML>”;
 string sTHead = “<TR>”;
 StringBuilder sTableData = new StringBuilder();
  try
  {

  sTHead += “<th scope=’col’>Name</th><th scope=’col’>Email</th><th scope=’col’>Group   Name</th></tr>”;
    foreach (DataRow row in dt.Rows)
    {
 sTableData.Append(“<TR>”);
 sTableData.Append(“<TD>” + row["Name"] + “</TD>”);
 sTableData.Append(“<TD>” + row["Email"] + “</TD>”);
 sTableData.Append(“<TD>” + row["Group"] + “</TD>”);
  sTableData.Append(“</TR>”);
    }
  }
  catch (Exception ex)
  {
 throw ex;
  }
  finally
  {
 site.Close();
 site.Dispose();
 web.Close();
 web.Dispose();
  }
  string sTable = sTableStart + sTHead + sTableData.ToString() + sTableEnd;
  return sTable;
}

This will open the excel file in exactly the same format as the web page appears.

I was getting the Access Denied page when I tried to access the UsersCollection of a SPGroup Instance or SiteGroups of SPWeb object.
I used RunWithElevatedPrivileges to be able to access the users collection but all in vein.
After searching through the net, I found that all code should be inside elevated priviledge, because if we access SiteGroups from a SPWeb object that was created prior to using elevated priviledge, the problem would still sustain.

So, I changed my code to define the objects inside elevated priviledges like:

SPSecurity.SPWeb (delegate()
{       SPSite ospSite = SPContext.Current.Site;
        SPWeb site = ospSite.OpenWeb();
……code to access users
}

The code was still giving errors because Context, SPContext or SPControl.GetWebContext, etc. cannot be used inside elevated privileges. The reason is that Context object is created before the code with elevated privileges is executed.

So, the change will be:

 string siteUrl = SPControl.GetContextSite(Context).Url;
        SPSecurity.RunWithElevatedPrivileges(delegate()
  {
        string Globaltbl = string.Empty;
        using (SPSite ospSite = new SPSite(siteUrl))
        {
            using (SPWeb oWeb = ospSite.OpenWeb())
            {
                    SPGroupCollection groups = oWeb.SiteGroups;  
                    foreach (SPGroup grp in groups)
                    {
                        SPUserCollection users = grp.Users;
                        if (users != null)
                        {
                            foreach (SPUser user in users)
                            {
                                //MyCode//
                            }
                        }
                    }
}
}
});

And Voila! everything works just fine.

Ever thought of creating files on the fly (say by typing contents  in a textbox) and saving them to the document library? refer to my previous post http://techolyvia.wordpress.com/2009/01/13/how-to-create-files-dynamically-and-save-that-in-document-library/ which talks about this.

Now, if we want to edit the uploaded data, we need to read the content from the document library and show the content in the textbox. So, how do we do that? See the code below:

SPSite ospSite = SPContext.Current.Site;
SPWeb ospWeb = ospSite.OpenWeb();
/*docPath returns the url of the file as sharepoint url/document library/filename*/
string docPath = Request.QueryString["Path"];
SPFile tempFile = ospWeb.GetFile(docPath);
/*OpenBinaryStream() opens the file as a stream*/
StreamReader reader = new StreamReader(tempFile.OpenBinaryStream());
/*txtUpload is the textbox control where we insert the value of the text file*/
txtUploadText.Text = reader.ReadToEnd();
reader.Close();

So, we use StreamReader to read the content of the text file in document library and display the content in a Multiline Textbox, so that the user can  edit the content of the textfile.

For details refer to http://www.dotnetspider.com/forum/189095-Read-file-content-from-document-library-write-textbox.aspx

The other day, I was working on how to create files on the fly(dynamically create any file of any extension like .doc, .txt etc.). To do this, we will first have to provide an editor like rich text box where the user can type in something and when he saves,a dynamic file is created which is saved in the document library.
I used a FCK editor in this case.

I intended to save other meta data along with the file in the document library(will talk about it later in this article).

string strRTFFileName = string.Empty;
SPList ospList = null;
Hashtable metadataTable = new Hashtable();
MemoryStream mstream = new MemoryStream();
Stream stream = null;
SPWeb ospWeb = ospSite.OpenWeb();
string strReader = string.Empty;
SPWeb sourceWeb = SPContext.Current.Web;
string sourceList = “Brochure”; //Name of Document library to save file.
SPList sourceListObj = sourceWeb.Lists[sourceList];

//Declare a streamwriter and output it’s value to memory stream
StreamWriter sw = new StreamWriter(mstream);
//FCKeditor1 is name of the FCK Editor.
sw.Write(FCKeditor1.Value);
// Creating file name dynamically based on datetime value

strRTFFileName = string.Format(“text-{0:yyyy-MM-dd_hh-mm-ss-tt}.rtf”, DateTime.Now);
string pattern = “[^a-z0-9A-Z. ]“;
Regex objRegex = new Regex(pattern);
strRTFFileName = objRegex.Replace(strRTFFileName, “”);

// End of dynamic file name creation

// On flushing StreamWriter, value is copied to memorystream.
sw.Flush();
byte[] contents = new byte[mstream.Length];
mstream.Read(contents, 0, (int)mstream.Length);

ospWeb.AllowUnsafeUpdates = true;
sourceItem = sourceWeb.Files.Add(sourceWeb.Url + “/” + sourceListObj.RootFolder.ToString() + “/” + strRTFFileName, mstream, true);
mstream.Close();
ospWeb.AllowUnsafeUpdates

Once my colleague was getting an error “The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again” while trying to move documents from one document library to another document library.

His piece of code was,

public void MoveDocument(string filePath,string destinationList)
{
try
{
SPWeb ospWeb = SPContext.Current.Web;
SPList destList = ospWeb.Lists[destinationList];
SPFile ospFile = ospWeb.GetFile(filePath);
ospFile.MoveTo(ospWeb.Url + "/" + destList.RootFolder + "/" + ospFile.Name);
}
catch (Exception ex)
{
throw ex;
}
}

After some amount of searching in the net, it was found that the error is caused due the security validation in sharepoint. So, there should be a method we turn  off the security validation:

One method to turn off security validation is,
Central Administration—>application management—->web application general settings–>”turn security validation off”

but,it is dangerous to do….as it could potentially leave your site open to malicious code.

Ideally, the security validation should be turned off only long enough for your own code to execute and should be turned back on again as soon as the code finishes execution.

So, the code needs to be recoded as,
public void MoveDocument(string filePath,string destinationList)
{
try
{
SPWeb ospWeb = SPContext.Current.Web;
Microsoft.SharePoint.Administration.SPWebApplication webApp = ospWeb.Site.WebApplication;
webApp.FormDigestSettings.Enabled = false;
SPList destList = ospWeb.Lists[destinationList];
SPFile ospFile = ospWeb.GetFile(filePath);
ospFile.MoveTo(ospWeb.Url + “/” + destList.RootFolder + “/” + ospFile.Name);
webApp.FormDigestSettings.Enabled = true;
}
catch (Exception ex)
{
throw ex;
}
}

 

We have observed in different cases, suppose we have a video file in a sharepoint library, if we click the file  it prompts for username/password. It is not very user friendly, as the password needs to be entered every time.

 

After some searches we found many people facing this problem so we suggested that we can open the video in a web page instead of opening it in Windows Media Player.

 

So, we created an aspx page in _layouts folder.

Now, whenever the user clicks to open a video file, we redirect it to the aspx page like,

 

http://gitolekha/_layouts/ad.aspx?lnk=http://gitolekha/Videos/sharepoint.wm

 

in ad.aspx we pass the name of the document library, here videos and the file name, here gito sharepoint.wm

 

here, we embed the video file in the aspx web page,

 

< OBJECT id="MediaPlayer" CLASSID="CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95">

<PARAM NAME="SRC" VALUE=<%=strVal %>>

<PARAM NAME="AUTOSTART" VALUE="1><PARAM NAME="SHOWCONTROLS" VALUE="1"><param name="ShowStatusBar" value="1">

</OBJECT >

 

strVal is the name of the file which we get from the code behind,

 

if (Request.QueryString["lnk"] != null)

{

   string strVal = Request.QueryString["lnk"].ToString();

}

 Our job is done, we have embedded the video file in the webpage, so it dosen’t ask for username/password every time we try to open it as it was before when we opened it in Windows Media Player.

 

Now, how to download it?

We can always download from Sharepoint document library through some coding, but I am lazy, I want to leave it to the default download system of windows as right click -&gt; save as. How do we do that?

 

On top of the webpage I give a hyperlink as shown below, now I want to stop users from left clicking so that they are forced to right click -&gt; save target as.

 

<a href=<%=strVal %>; onMouseOver=’document.onmousedown=noLeftClick’ onMouseOut=’document.onmousedown=null’><b>Download File</b></a>;

 

 

<script language=”javascript”>

function noLeftClick()

{

   if (event.button==1)

   {

    alert(‘Please use the rightclick \”Save Target As\” Command to Download’)

    }

 }

<script>

 

Now, how do users send email of the link of the video to other users?

 

We send it as shown below to the same aspx page, however we need to encode it, here filename = gito sharepoint.wmv, so while encoding it is sent as below,

 

http://gitolekha/_layouts/ad.aspx?lnkftp=ftp://174.456.12.45/CPD%20sharepoint.wmv

 

see the code below,

void emailImage_DataBinding(object sender, EventArgs e)

{

 HyperLink mailImage = (HyperLink)sender;

mailImage.NavigateUrl = “mailto:?body=” + mailImage.Page.Server.UrlPathEncode(FinalLink + “&subject=Link to “ + fileTitle);

}

 

Here final link is the link shown above(http://gitolekha/_layouts/ad.aspx?lnkftp=ftp://174.456.12.45/gito%20sharepoint.wmv

) and final title is the title to be shown in the subject line of the mail.

 

I had to create a site definition for a blog site that I created so that it could be used as a template. My site had various custom webparts  and I also created some lists. So to incorporate all that I also used features. Here is a brief explanation of it:

Site Definitions are located in the following folder on the SharePoint server: C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplatesThe aspx pages and various lists that make up the site definition are stored here.Inside the xml subfolder is the onet.xml which contains the various configurations and modules of the site definition like the various web part zones in the pages.

Steps:

1.       First I traversed to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplatesCopied the Blog folder and pasted it back, renamed it to SampleBlog.

2.       Next, connect the new site definition to sharepoint by copying the WEBTEMP.XML file in “C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML” and paste it to the same location renaming it as webtempSampleblog.xml.

Open the file and delete any unnecessary <template> tag as shown below:

<?xml version=”1.0″ encoding=”utf-8″?>

<!– _lcid=”1333″ _version=”12.0.4518″ _dal=”1″ –>

<!– _LocalBinding –>

<Templates xmlns:ows=”Microsoft SharePoint”>

 <Template Name=”SampleBlog” ID=”1333″>

    <Configuration ID=”0″ Title=”Sample Blog Template” Hidden=”FALSE” ImageUrl=”/_layouts/images/BLOGPREV.PNG” Description=”Sample blog template” DisplayCategory=”Sample Templates”></Configuration>

</Template>

</Templates>

Change the name attribute to “Sample Blog Template” and the ID so that it is unique, generally numbers above 1000 will be safe.

3.       Reset IIS.

4.    Customizing the site definition.

After this point, one can see the new site definition and create new sites,but we want to customize our site definition, add various web parts etc.To do that we need to change the ONET.XML

The <Module> is created  for various pages.Inside that is <View> which represents various web part zones of the page. So, I exported the webparts and copied the xml’s in respective <View> tags representing the proper zone.

My next challenge was  with copying the xmlof DataView Web parts, as by default  Sharepoint Designer binds the control to the list instance using the list instance GUID. So, it is difficult to create a new site using this template along with the GUID’s of the DataView Web Part.

So, we need to replace the GUID’s with the list names.

i) On the attributes of the DataFormWebPart element replace the attribute ListName=”{GUID}” by ListName=”LIST_NAME” where LIST_NAME is the name of the list that you are binding to.

ii) Go through all of the DataFormParameter elements and replace:   

    <WebPartPages:DataFormParameter Name=”ListID” ParameterKey=”ListID” PropertyName=”ParameterValues” DefaultValue=”{GUID}”/>

With:

<WebPartPages:DataFormParameter Name=”ListName” ParameterKey=”ListName” PropertyName=”ParameterValues” DefaultValue=”LIST_NAME”/>

iii) Go to the ParameterBindings element and replace

    <ParameterBinding Name=”ListID” Location=”None” DefaultValue=”{GUID}”/>

With:

    <ParameterBinding Name=”ListName” Location=”None” DefaultValue=”LIST_NAME”/>   

This should give you a GUID free DataForm webpart that can be placed on a page layout used by multiple sites on your site collection (as long as the name of the list is the same on all sites).

5. IMPLEMENTING FEATURES

I had created several lists in my site, so to include those while I create a new site from my site definition, I had to use features.

To implement a feature we need to create a folder in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES

So, I created one named SampleBlogSite, and here is the content:

<Feature Id=”67409a48-46c8-4a28-8f2e-bca0a56e4213″

  ActivateOnDefault=”False”

  Title=”Sample Blog Site”

  Scope=”Web”

  ReceiverAssembly=”Sample.Blog.FeatureDefinition, Version=1.0.0.0, Culture=neutral, PublicKeyToken=246c59317fd65cd2″

  ReceiverClass=”Sample.Blog.FeatureDefinition.FeatureListner”

  Description = “Activate this feature to convert current blog site to Sample Blogs site. this feature can only be activated to a blogs site defintion.”

  xmlns=”http://schemas.microsoft.com/sharepoint/”>

</Feature>

Here, Sample.Blog.FeatureDefinition is the dll  implementing the feature.

Create a class inherited from SPFeatureReceiver. Implement the methods FeatureActivated, FeatureDeactivating, FeatureInstalled and FeatureUninstalling by overriding the base methods.

Write your code into the FeatureActivated-method.

Now change your feature.xml in the props ReceiverClass and ReceiverAssembly and set the corresponding values there for your assembly.

Refer this feature from your ONET.XML, as:

<Feature ID=67409a48-46c8-4a28-8f2e-bca0a56e4213 />

So, this completes the entire process.