This blog is moved to
http://amalhashim.wordpress.com

Monday, August 30, 2010

Custom Application MasterPage | Sharepoint 2007

Recently I have involved in branding one of our clients sharepoint portal. As part of the branding process we needed to modify the application.master file, so even the layout pages  will have the same look and feel. As part of the investigation, we come up with the best approach for doing this.

1. Create a custom master page, with all the placeholders which are available in the original application.master file. Its better to take a copy of the application.master file and add the styles/images etc as you wish

2. Create a custom HttpModule which will set the new custom master page if the current page is having the master page “application.master”. We preferred HttpModule on top of HttpHandler because HttpModule can be deployed only to the web applications we wants, while HttpHandler will hit across all web applications.

Below is the code i have created for the http module.

using System;
using System.Web;
using System.Web.UI;
using Microsoft.SharePoint;

namespace Company.HttpModules
{
/// <summary>
///
All the layout pages will be using application.master
/// inorder to make the application consistent. we have created a custom application master page
/// this module will check whether the current request is having the master page as application.master
/// then change it to our custom page
/// </summary>
public class ApplicationMasterModule : IHttpModule
{
public void Init(HttpApplication context)
{
if (context == null)
throw new ArgumentNullException("context");

context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}

void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
Page page = HttpContext.Current.CurrentHandler as Page;
if (page != null)
{
page.PreInit += new EventHandler(page_PreInit);
}
}

void page_PreInit(object sender, EventArgs e)
{
Page page = sender as Page;
if (page != null)
{
if (page.MasterPageFile != null)
{
if (page.MasterPageFile.Contains("application.master"))
{


using (SPSite connectSite = new SPSite(SPContext.Current.Site.Url))
{
using (SPWeb currentWeb = connectSite.RootWeb)
{
string url = currentWeb.ServerRelativeUrl + "/_catalogs/masterpage/our_custom_app.master";
page.MasterPageFile = url;
}
}
}
}
}
}

public void Dispose()
{
}
}
}



Now comes the important aspect of deployment. Its better to create the master page as well as http module to be deployed as a feature. See the feature.xml



<?xml version="1.0" encoding="utf-8"?>
<
Feature Id="b1531032-19b5-4fb1-87a6-6892b280c90d"
Title="Custom Application Master Page"
Description="Custom Application Master Page"
Version="12.0.0.0"
Hidden="FALSE"
Scope="Site"
DefaultResourceFile="core"
ReceiverAssembly="Company.MasterPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6c27a5016ad8e167"
ReceiverClass="Company.MasterPages.CustomAppMasterPage"
xmlns="http://schemas.microsoft.com/sharepoint/">
<
ElementManifests>
<
ElementManifest Location="elements.xml"/>
<
ElementFile Location="our_custom_app.master" />
</
ElementManifests>
</
Feature>



and elements.xml file



<?xml version="1.0" encoding="utf-8" ?>
<
Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<
Module Name="CustomApplicationMasterPage"
Url="_catalogs/masterpage"
Path=""
RootWebOnly="False">
<
File Url="our_custom_app.master"
Type="GhostableInLibrary" IgnoreIfAlreadyExists="FALSE">
<
Property Name="ContentType"
Value="$Resources:cmscore,contenttype_-9masterpage_name;" />
<
Property Name="PublishingPreviewImage"
Value="" />
<
Property Name="Description"
Value="Custom Application Master Page"/>
</
File>
</
Module>
</
Elements>



See the Feature Receiver assembly which will add the web.config entries so the HttpModule will work without manually modifying the web configuration file



using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Globalization;
using System.Diagnostics;

namespace Company.UIUpdates
{
/// <summary>
///
On Feature Activation => Add HttpModule Entry in Web.Config file
/// On Feature DeActivation => Remove the HttpModule Entry from Web.Config file
/// </summary>
class CustomAppMasterPage : SPFeatureReceiver
{
static SPWebConfigModification CreateModification(string Name, string XPath, string Value)
{
SPWebConfigModification modification = new SPWebConfigModification(Name, XPath);
modification.Owner = "Custom Application Master Page.wsp";
modification.Sequence = 0;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modification.Value = Value;
return modification;
}

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
try
{
using (SPSite site = properties.Feature.Parent as SPSite)
{
SPWebApplication webApp = site.WebApplication;
string name = "add[@name='ApplicationMasterModule']";
string path = "configuration/system.web/httpModules";
string value = @"<add name=""ApplicationMasterModule"" type=""Company.HttpModules.ApplicationMasterModule,Company.HttpModules, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2391931b556f037"" />";
if (webApp != null)
{
SPWebConfigModification modification = CreateModification(name, path, value);
webApp.WebConfigModifications.Add(modification);
webApp.Update();
SPWebService.ContentService.ApplyWebConfigModifications();
SPWebService.ContentService.WebApplications[webApp.Id].Update();
//Applies the web config settings in all the web application in the farm
SPWebService.ContentService.WebApplications[webApp.Id].WebService.ApplyWebConfigModifications();
}
}
}
catch (Exception ex)
{
WriteMessageToEventLog(ex.ToString());
}
}

static private void WriteMessageToEventLog(string message)
{
string EVENT_SOURCE = "CustomAppMasterPage Feature Receiver";
SPSecurity.RunWithElevatedPrivileges(delegate()
{
if (!EventLog.SourceExists(EVENT_SOURCE))
{
EventLog.CreateEventSource(EVENT_SOURCE, "Application");
}

EventLog.WriteEntry(EVENT_SOURCE, message);
});
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
try
{
using (SPSite site = properties.Feature.Parent as SPSite)
{
SPWebApplication webApp = site.WebApplication;
string name = "add[@name='ApplicationMasterModule']";
string path = "configuration/system.web/httpModules";
string value = @"<add name=""ApplicationMasterModule"" type=""Company.HttpModules.ApplicationMasterModule,Company.HttpModules, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2391931b556f037"" />";
if (webApp != null)
{
SPWebConfigModification modification = CreateModification(name, path, value);
webApp.WebConfigModifications.Remove(modification);
webApp.Update();
SPWebService.ContentService.ApplyWebConfigModifications();
SPWebService.ContentService.WebApplications[webApp.Id].Update();
//Applies the web config settings in all the web application in the farm
SPWebService.ContentService.WebApplications[webApp.Id].WebService.ApplyWebConfigModifications();
}
}
}
catch (Exception ex)
{
WriteMessageToEventLog(ex.ToString());
}
}

public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{

}

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{

}
}
}


As part of the WSP, we included the following



1. Custom Master Page



2. Http Module dll which will go into GAC



3. Feature Receiver



Hope this helps. Feel free to contact me if you have any queries.

Thursday, August 19, 2010

Microsoft Sync Framework 2.1 Software Development Kit (SDK) Released

Microsoft Sync Framework is a comprehensive synchronization platform that enables collaboration and offline scenarios for applications, services, and devices. Using Microsoft Sync Framework, developers can build applications that synchronize data from any source using any protocol over any network.

Sync Framework 2.1 introduces new features that let you synchronize a SQL Server or SQL Server Compact database on your computer with a SQL Azure database. This release also introduces parameter-based filtering, the ability to remove synchronization scopes and templates from a database, and performance enhancements to make synchronization faster and easier.

SQL Azure Synchronization


With Sync Framework 2.1, you can extend the reach of your data to the web by leveraging the Windows Azure Platform and SQL Azure Database. By synchronizing a SQL Server database on your business premises to SQL Azure, you make some or all of your data available on the web without the need to provide your customers with a connection to your on premises SQL Server database. After you configure your SQL Azure database for synchronization, users can take the data offline and store it in a client database, such as SQL Server Compact or SQL Server Express, so that your applications operate while disconnected and your customers can stay productive without the need for a reliable network connection. Changes made to data in the field can be synchronized back to the SQL Azure database and ultimately back to the on premises SQL Server database. Sync Framework 2.1 also includes features to interact well with the shared environment of Windows Azure and SQL Azure. These features include performance enhancements, the ability to define the maximum size of a transaction to avoid throttling, and automatic retries of a transaction if it is throttled by Windows Azure. Sync Framework gives you flexibility in the way you structure your synchronization community, but two typical ways are to use a 2-tier architecture or an N-tier architecture.

  • 2-tier architecture: Sync Framework runs on the local computer and uses a SqlSyncProvider object to connect directly to the SQL Azure database without going through a middle tier or a web server, such as Internet Information Services (IIS).
  • N-tier architecture: A Sync Framework database provider runs in a Windows Azure hosted service and communicates with a proxy provider that runs on the local computer.

Bulk Application of Changes


Sync Framework 2.1 takes advantage of the table-valued parameter feature of SQL Server 2008 and SQL Azure to apply multiple inserts, updates, and deletes by using a single stored procedure call, instead of requiring a stored procedure call to apply each change. This greatly increases performance of these operations and reduces the number of round trips between client and server during change application. Bulk procedures are created by default when a SQL Server 2008 or SQL Azure database is provisioned.


Parameter-based Filtering


ync Framework 2.1 enables you to create parameter-based filters that control what data is synchronized. Parameter-based filters are particularly useful when users want to filter data based on a field that can have many different values, such as user ID or region, or a combination of two or more fields. Parameter-based filters are created in two steps. First, filter and scope templates are defined. Then, a filtered scope is created that has specific values for the filter parameters. This two-step process has the following advantages:

  • Easy to set up. A filter template is defined one time. Creating a filter template is the only action that requires permission to create stored procedures in the database server. This step is typically performed by a database administrator.
  • Easy to subscribe. Clients specify parameter values to create and subscribe to filtered scopes on an as-needed basis. This step requires only permission to insert rows in synchronization tables in the database server. This step can be performed by a user.
  • Easy to maintain. Even when several parameters are combined and lots of filtered scopes are created, maintenance is simple because a single, parameter-based procedure is used to enumerate changes.


Removing Scopes and Templates


Sync Framework 2.1 adds the SqlSyncScopeDeprovisioning and SqlCeSyncScopeDeprovisioning classes to enable you to easily remove synchronization elements from databases that have been provisioned for synchronization. By using these classes you can remove scopes, filter templates, and the associated metadata tables, triggers, and stored procedures from your databases.


Upgrading the Metadata Format


The metadata format for the database providers changed in Sync Framework 2.1. The new metadata format is incompatible with previous versions of the database providers. The upgrade to the new metadata format cannot be undone, and when you try to use an earlier version of the database providers to synchronize a database that is in the 2.1 format, Sync Framework throws an exception. However, the SqlSyncProvider class in Sync Framework 2.1 detects whether the metadata is in the 2.0 or 2.1 format, and operates in a backward compatibility mode to synchronize a database that contains metadata in the 2.0 format. Sync Framework can synchronize a database in the 2.0 format with a database in either the 2.0 or the 2.1 format. Therefore, it is not necessary to upgrade all of the databases in your synchronization community at the same time. For example, in an N-tier architecture you can upgrade the server Sync Framework components and database metadata format and continue to synchronize with clients that use Sync Framework 2.0. Clients can then upgrade when it is convenient for them to do so.


SQL Server Compact 3.5 SP2 Compatibility


The Sync Framework 2.1 SqlCeSyncProvider database provider object uses SQL Server Compact 3.5 SP2. Existing SQL Server Compact databases are automatically upgraded when Sync Framework connects to them. Among other new features, SQL Server Compact 3.5 SP2 makes available a change tracking API that provides the ability to configure, enable, and disable change tracking on a table, and to access the change tracking data for the table. SQL Server Compact 3.5 SP2 can be downloaded here.


Sync Framework 2.1 Redistributable Package


To download the Microsoft Sync Framework 2.1 redistributables, rather than the SDK package, click on the link: Microsoft Sync Framework 2.1 Redistributable Package

Thursday, August 12, 2010

Sharepoint 2007 Deploying Styles as Feature

Assuming you have WSP Builder installed.
Open Visual Studio, From File –> New Project
image
Clicking OK will create a solution with the following folder structure
image
Now right click the Project “Test_Style” –> Add –> New Item
image
From the Add New Item dialog Select WSPBuilder –> Blank Feature
image
Clicking Add will bring up the following dialog
image
If you want the style to be applied on Site Collection level, then select the scope as Site, and Click OK will add feature/element files as shown below
image
Now add your style file under the folder TestStyle as shown below
image
Open feature.xml file and copy paste the following replacing the existing ElementManifests tag
<ElementManifests>
        <
ElementManifest Location="elements.xml"/>
        <
ElementFile Location="MyStyle.css" />        
</
ElementManifests>

Open elements.xml file and copy paste the following replacing existing contents
<?xml version="1.0" encoding="utf-8" ?>
<
Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<
Module Name="New Style" Url="Style Library" Path="" RootWebOnly="TRUE" >
<
File Url="MyStyle.css" Type="GhostableInLibrary" IgnoreIfAlreadyExists="TRUE"></File>
</
Module>
</Elements>






Now build the WSP by Right Clicking the project from Solution Explorer








image





Add the solution, and activate the feature.








Now, if you notice the feature was suppose to overwrite the file if it exists. But, if the file exists its not overwriting, even if we provide “IgnoreIfAlreadyExists” flag in elements.xml file. As a solution for this, we need to handle the FeatureActivated event and manually checkout the file and overwrite it and checkin the file. To achieve this we need to build a class library, which should go to GAC.







using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.IO;
using System.Diagnostics;




namespace UpdateStyleReceiver
{
class UpdateStyle : SPFeatureReceiver
{
SPWeb web = null;
string directoryPath = null;

// copy to location
string url = null;

public override void FeatureInstalled(SPFeatureReceiverProperties properties) { }

public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { }

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
WriteMessageToEventLog("Feature Activated");

try
{
web = ((SPSite)properties.Feature.Parent).RootWeb;

// set the directory path on the file system where the feature was activated
directoryPath = properties.Definition.RootDirectory;

// run with elevated permissions so we can overwrite the file
SPSecurity.RunWithElevatedPrivileges(OverwriteFile);
}
catch (Exception ex)
{
WriteMessageToEventLog(ex.ToString());
}
}

public void OverwriteFile()
{
// create a new site object so that elevation works properly
SPSite site = new SPSite(web.Site.ID);

// copy to location
string url = null;

// get the url to the local file
string[] localFile = System.IO.Directory.GetFiles(directoryPath, "*.css", System.IO.SearchOption.TopDirectoryOnly);

// define a fstream object so we can read the contents of the file into a byte array
FileStream fstream = File.OpenRead(localFile[0]);

byte[] contents = new byte[fstream.Length];
fstream.Read(contents, 0, (int)fstream.Length);
fstream.Close();

// get a handle to the master page gallery
SPList styleLibrary = site.OpenWeb().Lists["Style Library"];

// get a handle to the folder we want to upload the file to
SPFolder editingMenuFolder = styleLibrary.RootFolder;

SPFile customQuickAccessFile = editingMenuFolder.Files["MyStyle.css"];

// build the destination copy url
url = site.Url + "/" + editingMenuFolder.Url + "/";

// check out the file, replace it with the modified one, and check it back in, publish and approve
customQuickAccessFile.CheckOut();
customQuickAccessFile.CopyTo(url + "MyStyle_Original.css", true);

WriteMessageToEventLog(editingMenuFolder.ServerRelativeUrl);
WriteMessageToEventLog(editingMenuFolder.Url);

// check in new file
customQuickAccessFile = editingMenuFolder.Files.Add(url + "MyStyle.css", contents, true);
customQuickAccessFile.CheckIn("File over-written by activiating the Published Page View Feature");
customQuickAccessFile.Publish("File published by activating the Published Page View Feature");
customQuickAccessFile.Approve("Approved by Published Page View Feature");

site.Close();
}

public void DeleteFile()
{
// create a new site object so that elevation works properly
SPSite site = new SPSite(web.Site.ID);

// get a handle to the master page gallery
SPList styleLibrary = site.OpenWeb().Lists["Style Library"];

// get a handle to the folder we want to upload the file to
SPFolder editingMenuFolder = styleLibrary.RootFolder;

SPFile customQuickAccessFile = editingMenuFolder.Files["MyStyle_Original.css"];

// build the destination copy url
url = site.Url + "/" + editingMenuFolder.Url + "/";

customQuickAccessFile.MoveTo(url + "MyStyle.css", true);

customQuickAccessFile.Publish("File published by activating the Published Page View Feature");
customQuickAccessFile.Approve("Approved by Published Page View Feature");

site.Close();
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
WriteMessageToEventLog("Deactiviating Feature");

try
{
web = ((SPSite)properties.Feature.Parent).RootWeb;

// run with elevated permissions so we can overwrite the file
SPSecurity.RunWithElevatedPrivileges(DeleteFile);
}
catch (Exception ex)
{
WriteMessageToEventLog(ex.ToString());
}
}

private void WriteMessageToEventLog(string message)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
EventLog.WriteEntry("Copy Feature Receiver", message);
});
}
}
}



Modify the feature.xml as follows










<?xml version="1.0" encoding="utf-8"?>
<
Feature Id="20402429-a875-482d-a4c8-e8449f48d04c"
Title="Update Style"
Description="Description for Update Style"
Version="12.0.0.0"
Hidden="FALSE"
Scope="Site"
DefaultResourceFile="core"
ReceiverAssembly="UpdateStyleReceiver, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7701372303d9a778"
ReceiverClass="UpdateStyleReceiver.UpdateStyle"

xmlns="http://schemas.microsoft.com/sharepoint/">
<
ElementManifests>
<
ElementManifest Location="elements.xml"/>
<
ElementFile Location="MyStyle.css" />
</ElementManifests>
</
Feature>