Ok, so I’ve been working with some of the .NET 3.5 functionality in SharePoint 2010, some of this functionality has been difficult to implement. The idea is to use the AJAX functionality, together with the WCF capabilities for client integration in SharePoint, for now this will include JavaScript client side proxies for services (Service Reference in the AJAX Script Manager). We need a few things to get this to function.
- The AJAX ScriptManager must be added to the page.
- To be used by the AJAX ScriptManager, the WCF service needs to use the enableWebScript web.config option (or WebScriptEnablingBehavior class added to the endpoint)
JavaScript proxy generation is an extremely useful function of the AJAX toolkit in .NET. Using WCF and a ScriptManager, you can get things rolling without having to worry about generating soap request envelopes yourself, dealing with the XHTTPRequest object in JavaScript, and parsing JSON results. Everything is done for you.
In fact if you use Data Contracts in the responses from your services, the Object will be serialized, transported and de-serialized automatically, you’ll just be able to access the object properties using JavaScript.
To keep things simple you should consider the following:
- You don’t want to be making changes to MasterPages in order to get the AJAX ScriptManager added to the page, you should register it on all pages when a feature is activated. This will allow you to disable it if needed.
- You don’t want to have to mess around with web.config settings for the WCF service, you should use a factory class to pre-configure the service.
So the first thing we are going to look at is the ScriptManager, we will create a delegate control which adds a ScriptManager to the page if it doesn’t already exist, the delegate control will also register the WCF Service we create with the script manager as well as any other CSS/JavaScript files you’ll need.
It’ll look something like this (can’t take credit for this code comnpletely, it’s based on something I found some time back and unable to find at the moment), it is attached to the AdditionalPageHead delegate by a feature.
public class MyDelegate : WebControl
{
public static String ControllerFileName = "Controller.js";
protected override void OnInit(EventArgs e)
{
Page.Init += delegate(object sender, EventArgs e_Init)
{
if (ScriptManager.GetCurrent(Page) == null)
{
ScriptManager sMgr = new ScriptManager();
Page.Form.Controls.AddAt(0, sMgr);
}
if (ScriptManager.GetCurrent(Page) != null)
{
// build reference to Service Script
String url = SPContext.Current.Web.Url;
if(!url.EndsWith("/")) url += "/";
Uri service = new Uri(new Uri(url),"_vti_bin/MyService/MyService.svc");
ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference(service.ToString()));
// register scripts
ClientScriptManager csm = this.Page.ClientScript;
if (!csm.IsClientScriptIncludeRegistered(ControllerFileName))
{
csm.RegisterClientScriptInclude(ControllerFileName, this.ResolveClientUrl("/_layouts/MyService/" + ControllerFileName));
csm.RegisterClientScriptBlock(typeof(CheckInNotifyDelegate), "CSS", "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + this.ResolveClientUrl("/MyService/Styles.css") + "\"/>");
}
}
};
base.OnInit(e);
}
This causes the ScriptManager to be added the Controls collection on the Page.Init event, this is the correct place in the rendering life cycle of ASP.NET to modify the Controls collection.
The Feature can then contain a delegate control which is added to the AdditionalPageHead delegate.The element manifest will look something like this.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Control Id="AdditionalPageHead" Sequence="50" ControlClass="MyAssembly.MyDelegate" ControlAssembly="$SharePoint.Project.AssemblyFullName$"/>
</Elements>
The Control itself basically adds the ScriptManager to the page (if one doesn't exist already) registers the service with the Script Manager, creates a reference to a controller JavaScript file on the page (which would contain any logic needed to process the results of the service invocation).
What I normally do for any style sheets, is ghost them in the content database, it makes customization of the style sheets easier without having to re-deploy the solution file.
I would recommend you try use XSLT where you can, because it can be ghosted in the content database, and then easily modified if format tweaks are needed. You can easily serialize DataContracts into XML for processing against XSLT.
The next thing is the WCF Service. SharePoint has factory classes which provision the service to handle the detail around things like authentication (Claims based authentication for instance). This removes the need for customised web.config entries or custom code to handle the complexities around this. The Service Host is configured automatically. This is a great help. These are the options.
Service Type |
Service Factory |
Description |
SOAP service |
MultipleBaseAddressBasicHttpBindingServiceHostFactory |
Basic HTTP binding must be used, which creates endpoints for a service based on the basic HTTP binding. |
REST Service |
MultipleBaseAddressWebServiceHostFactory |
The service factory creates endpoints with web bindings. |
ADO.NET Data Service |
MultipleBaseAddressDataServiceHostFactory |
A data service host factory can be used. |
To make use of one of these you .svc file would look something like this.
<%@ ServiceHost Language="C#"
Service="MyAssembly.MyService,
$SharePoint.Project.AssemblyFullName$" Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressBasicHttpBindingServiceHostFactory,
Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c "%>
None of these options however support the enableWebScripts behaviour on the end points. The enableWebScripts behaviour (or WebScriptEnablingBehavior class) is used by the AJAX framework, and allows the service to return a service proxy in javascript when “/js” or “/jsdebug” is added to the URL of the service.
The Service Host Factory classes in SharePoint do however set things up like Claims Based Authentication, or whatever authentication method you are using on the site automatically. What we want is a service host that does both, it configures the service host based according to SharePoint, and automatically adds the enableWebScripts behaviour to the endpoints.
Luckily we can build our own service hosts and service host factories. All we need is a custom class for the service host and a custom class for the factory class. We extend the REST based SharePoint Service Host, we will need to remove any end point behaviours that are there already, and add the enableWebScript behaviour.
That will be done in the custom service host, but first we need to build our own service host factory class, also extending the REST based SharePoint factory class.
public class MyServiceHostFactory : Microsoft.SharePoint.Client.Services.MultipleBaseAddressBasicHttpBindingServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new MyServiceHost (serviceType, baseAddresses);
}
}
Then we extend the MultipleBaseAddressWebServiceHost class and modify the behaviours when the service host is opened.
public class MyServiceHost : Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHost
{
public MyServiceHost (Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
}
protected override void OnOpening()
{
base.OnOpening();
foreach (ServiceEndpoint endpoint in base.Description.Endpoints)
{
if (((endpoint.Binding != null) && (endpoint.Binding.CreateBindingElements().Find<WebMessageEncodingBindingElement>() != null)) && (endpoint.Behaviors.Find<WebScriptEnablingBehavior>() == null))
{
// try remove any previous behaviours
while (endpoint.Behaviors.Count > 0)
{
endpoint.Behaviors.RemoveAt(0);
}
endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
}
}
ServiceDebugBehavior debug = this.Description.Behaviors.Find<ServiceDebugBehavior>();
// if not found - add behavior with setting turned on
if (debug == null) {
this.Description.Behaviors.Add(
new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true }); }
else {
// make sure setting is turned ON
if (!debug.IncludeExceptionDetailInFaults)
{
debug.IncludeExceptionDetailInFaults = true;
}
}
ServiceMetadataBehavior metadata = this.Description.Behaviors.Find<ServiceMetadataBehavior>();
// if not found - add behavior with setting turned on
if (metadata == null)
{
this.Description.Behaviors.Add(
new ServiceMetadataBehavior() { HttpGetEnabled = true });
}
else
{
// make sure setting is turned ON
if (!metadata.HttpGetEnabled)
{
metadata.HttpGetEnabled = true;
}
}
}
}
This also sets some service behaviours as well to enable debugging and metadata.
So now your SVC file can look like this.
<%@ ServiceHost Language="C#"
Service="MyAssembly.MyService,
$SharePoint.Project.AssemblyFullName$"
Factory=" MyAssembly.MyServiceHostFactory, $SharePoint.Project.AssemblyFullName$"%>
Now when you load up your service with the “/js” or “/jsdebug” added to the URL you’ll get a JavaScript Service Proxy that the script manager will use.
One last thing, the Interface for the service should have the correct serialization attributes. These will ensure that your service functions when accessed via the AJAX Service Proxy class. The Service Interface Class would look something like this.
[ServiceContract(Namespace="", Name="MyService")]
public interface IMyService
{
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json)]
string GetMyParameter(String Option1, String Option2);
}
Just for completeness I’ll include the implementation of the service as well.
[ServiceBehavior(Namespace="MyService")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService : IMyService
{
public string GetMyParameter(String Option1, String Option2)
{
return "This Worked!!!";
}
}