Hosting WCF RESTful Ajax Service inside SharePoint 2010
The title explains it all, so let's get started.
The SharePoint Project and WCF enclosure
You can use an existing SharePoint project, or create a new one : choose a farm solution.
On the project, right click add > Sharepoint Mapped Folder and select ISAPI folder.
Add, an svc file and web.config under it
Obviously you can use a supplement web.config file to add the end point into the main web.config, but I find putting it in the same folder as the svc file is a lot easier to maintain : just be careful by doing this the config file will get overidden everytime you deploy. So don't customize anything once you deploy! If you need to, put it as a supplement web.config instead.
Create the SVC file manually and point it to the Service class : in this case residing in a separate project.
<%@ ServiceHost Debug="true" Language="C#" Service="Intranet.Web.LogonStorageService,Intranet.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d31094219f0447a8" %>
The WCF Implementation
Create the implementation service class. I prefer to put it on a separate project, like so:
Ensure that the project is strongly signed to be used by Sharepoint and included in the Sharepoint Project deployment package (open up the package, advanced, add, select GAC).
If you've lost the public key, compile it and use Reflector to get it back.
Making it RESTful
I have ILogonStorageService which contains the WCF service contract.
[ServiceContract] public interface ILogonStorageService { [OperationContract] [WebGet( ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare )] LoginResponse Validate(); [OperationContract] [WebInvoke( RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped )] void SetCredentials(string sourceUserName, string targetUserName, string userPassword); }
Note that I've specified that I ’˜m allowing REST-style HttpGet on Validate(), and accepting the data in JSON.
Whereas on SetCredentials I'm using REST-style HTTP POST (you can override the method to HTTP PUT if necessary).
The Body style determines if the result is wrapped within an object or not. You can only use this with WebGet, hence the difference style between the two operation contracts.
Plain Old Service Object
I also have LogonStorageService which contains the implementation. This class is *almost* just a normal class : contains no references to WCF and can called elsewhere directly without going through WCF service (handy if you need to reuse the logic on the SharePoint webpart server side).
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )] public class LogonStorageService : ILogonStorageService { //... public LoginResponse Validate() { /* .. */ } void SetCredentials ... }
The only thing I have to add here that makes it slightly related to a service, I've got AspNetCompatibility set to allowed. This is because I'm using HttpContext.Session within the implementation.
By default, Sharepoint 2010 doesn't seem to have Session State turned on, so we will turn it on later.
The Web.Config
<?xml version="1.0"?> <configuration> <system.serviceModel> <!-- we need this to enable session --> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> <behaviors> <endpointBehaviors> <!-- our configuration for rest relies on web http --> <behavior name="RestBehavior"> <webHttp /> </behavior> </endpointBehaviors> </behaviors> <bindings> <webHttpBinding> <!-- a selection of security bindings that you can use in the service registration below--> <binding name="WindowsAuthenticationBasicHttpBinding"> <security mode="TransportCredentialOnly"> <transport clientCredentialType="Ntlm" /> </security> </binding> <binding name="NoSecurityHttpBinding"> <security mode="None"> <transport clientCredentialType="None" /> </security> </binding> </webHttpBinding> </bindings> <services> <!-- register our wcf service --> <service name="Intranet.Web.LogonStorageService"> <endpoint address="" binding="webHttpBinding" behaviorConfiguration="RestBehavior" contract="Intranet.Web.ILogonStorageService" bindingConfiguration="NoSecurityHttpBinding"> </endpoint> </service> </services> </system.serviceModel> </configuration>
Hopefully that was self explanatory. This is where the magic of WCF combines our svc enclosure (LogonStorageService.svc), http/transport bindings, contract (ILogonStorageService) and implementation (LogonStorageServicee) together.
Session State
To enable session, the short story is you need to make some changes on the web.config and IIS modules. Complete story here.
Hitting it
Right, so now all you have to do is deploy the Sharepoint Project and we should be able to access it from https://{sharepoint}/_vti_bin/LogonService/LogonStorageService.svc/
You can call it from a client using jQuery as such:
$.getJSON("/_vti_bin/LogonService/LogonStorageService.svc/Validate", function (response) { //... });
Get is easy and your description explains it well. However, you do not describe if and how your WebInvoke works. When I followed your example and combined it with a WebContract and tried to get the Request object I got a 400 error every time and I could not figure out how to force the method to recognize my (valid) Json request much less hydrate my contract using the Json source. In the end I had to work around it using a Stream input together with the JSONDataContractSerializer. Example (from StackOverflow): [WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)] int CreateItem(Stream streamOfData); I'd be curious to know how you got yours to work.
Get is easy and your description explains it well. However, you do not describe if and how your WebInvoke works. When I followed your example and combined it with a WebContract and tried to get the Request object I got a 400 error every time and I could not figure out how to force the method to recognize my (valid) Json request much less hydrate my contract using the Json source. In the end I had to work around it using a Stream input together with the JSONDataContractSerializer. Example (from StackOverflow): [WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)] int CreateItem(Stream streamOfData); I'd be curious to know how you got yours to work.
I'm getting the following error: "Could not find a base address that matches scheme http for the endpoint with binding WebHttpBinding. Registered base address schemes are []." nothing i did (including adding a baseAddressPrefixFilters) helps. anyone?