This is the third part of a series on Service Applications. The following has already been posted:
Find the code for this sample on my CodePlex demo portal under the Navigation Service with Proxy release.
Working with proxies
The first sample Service Application that I showed only brings a certain level of usefulness. The first demo application did not use a proxy and without a proxy you are functionally stuck with having only a single Service Application instance to service all the web applications in the entire farm. This stems from the fact that you can't configure the Web Application binding to Service Applications when you do not create a proxy. So, quite quickly you'll want to move away from this simple approach and tack on a proxy layer too. For some added background, a service proxy is a component that relays calls to your application on behalf of a consumer. It allows you to implement an extra level of indirection between caller and callee. The most common implementation of this pattern is to use the proxy as a WCF client and expose the application as a WCF service. That way you can offload incoming requests to backend application servers. Great stuff! You can also create a Service Application / Proxy combination without using WCF. Simply call methods on the application directly from the proxy instead of instantiating a WCF client and going through that route. You still get to enjoy the benefit of binding web applications to specific instances of the service, but obviously you lose out on pushing requests out to the backend.
In this blog post I will show how to add this second layer of indirection to the Navigation Application introduced in part 2. No WCF proxies just yet though. I'll cover that in the next post.
New components in this version
In my previous post I showed the Navigation Application and the two primary components. You were introduced to the SPService, representing a farm-wide service and the SPServiceApplication which implements that farm wide service for specific web applications. When moving towards a proxy based implementation you get to add a few extra pieces to the puzzle, most notably a service proxy and a service application proxy.
The SPServiceProxy class allows SharePoint to know about what the proxies in your specific solution. This is similar to how the SPService provides information about the service applications that you have. Next is the service application proxy, which is a proxy to the service application itself, instantiated by the service proxy.
Service Proxies
The service proxy in the new demo application is doing two things. First of all it serves as a factory for new service application proxies. Next, it provides a known point for SharePoint to ask about what proxies you can create, for instance to populate the Connect menu on the Service Application Management page.
Service Application Proxies
These then, are the components that forward calls to the Service Application they are a proxy for. How they do this is to be decided however. In the simplest case you just look up the correct SPServiceApplication instance by ID and call methods directly on it. The harder case uses WCF as an intermediate layer. Doing so is not as hard as exposing a WCF service in SharePoint 2007. You now benefit from built-in support for WCF, and it is not as gnarly as creating web services in 2007.
The IServiceAdministration Interface
There are a few important things that SharePoint needs to know about the Service Applications that your code supports. First of all, during farm provisioning (psconfig) you can opt in for having your service application created. Also, SharePoint needs to populate the New and Connect menus on the Manage Service Applications page. You cannot really ask the item that is created on the New menu for the content of the New menu right? You need to use the parent, and in this case that parent is the SPService. Being a generic 'farm-wide' service it makes no sense to ask all SPService objects about their support for service applications. The IServiceAdministration interface was introduced to allow SharePoint to ask registered services for their needs and capabilities. If you do not implement this interface all is not lost, but you do lose out on specific integration points. You can still create new instances using your own means though, such as a feature receiver.
The IServiceProxyAdministration interface
Similar to how the IServiceAdministration interface allows SharePoint to find out about your service application capabilities, SharePoint also provides IServiceProxyAdministration. As you can expect from the name it serves a similar purpose but not for the application itself but for the proxy. The interface provides a connection point for SharePoint to create instances of the proxy during automated tasks (psconfig principally). It also provides a way to fill the menu items shown in the 'Connect' menu.
However!…
The design for this first proxy-enabled demo is to call methods directly on the service application, in-process. Meaning, the 'Connect' capability is a bit odd since you'll connect to an in-memory construct instead of a WCF backend that might be in another farm and is identified by a URI. In effect you are not connecting at all, or at least not in the way it was intended by the Connect functionality. So, we'll not implement this interface just yet and leave that one for a later version.
Building the application
In this section I'll show some of the code that make up the new application. The focus is on what is new in this version of the demo application. If you are interested in the basics check out an earlier blog post.
The NavigationServiceProxy
In the second version of the demo application that you can download from CodePlex you'll find the NavigationServiceProxy. This class is the service proxy. A not so interesting class right now, but it'll grow on you after I have had time to write my next blog post. It's basically an empty persisted object. In most persisted objects you find at least two constructors. The first is parameterized and used for initializing a new instance and adding it to the configuration store, the second is used for deserializing the object when it is retrieved later on. A persisted object is identified by both a name and an ID. The ID defaults to the value found in the Guid attribute. Since there is only going to be exactly one instance for this proxy class the name is defaulted to String.Empty.
|
[Guid("0bc4db91-dac2-4ad6-90cf-394997131d54")] public class NavigationServiceProxy : SPServiceProxy { public NavigationServiceProxy() { }
public NavigationServiceProxy(SPFarm farm) : base(String.Empty, farm) { } } |
The NavigationApplicationProxy
Similar to the service proxy the application proxy is a persisted object stored within the configuration database. Since you can create more than one proxy the constructor takes in a name. The constructor also requires you to specify the NavigationApplication for which the proxy is created. The last parameter is a reference to the singleton NavigationServiceProxy that will serve as the parent object of the application proxy (it is passed to the base constructor).
|
[GuidAttribute("0c1a4b04-90d5-4cef-be3a-f4591b1ac873")] public class NavigationApplicationProxy : SPServiceApplicationProxy { [Persisted] Guid _serviceApplicationID;
public NavigationApplicationProxy() { }
public NavigationApplicationProxy(string name, NavigationApplication application, NavigationServiceProxy serviceProxy) : base(name, serviceProxy) { _serviceApplicationID = application.Id; }
NavigationApplication GetNavigationApplication() { return (NavigationApplication)Farm.GetObject( _serviceApplicationID); } } |
The class is completed with two static methods. The first is a factory method used for creating a new application proxy instance. The second is used for retrieving existing proxies. One added treat is that you can specify whether the newly created application proxy should be added to the default proxy group. Only a few lines of code are needed to implement this behavior.
|
public static NavigationApplicationProxy Create( string name, NavigationApplication application, NavigationServiceProxy applicationProxy, bool addToDefaultGroup) { NavigationApplicationProxy proxy = new NavigationApplicationProxy( name, application, applicationProxy); proxy.Update(); if (addToDefaultGroup) { SPServiceApplicationProxyGroup group = SPServiceApplicationProxyGroup.Default; group.Add(proxy); group.Update(); } return proxy; }
public static NavigationApplicationProxy GetProxy( SPServiceContext context) { return (NavigationApplicationProxy)context.GetDefaultProxy( typeof(NavigationApplicationProxy)); } |
The class further contains a method which implement the business side of the system (it is specific to the navigation application demo). First, the application is retrieved using the persisted application ID. Next, a method is called directly on the application.
|
public string GetNavigationInformation() { NavigationApplication application = GetNavigationApplication(); return application.GetNavigationInformation(); } |
This is the bulk of the functionality provided by the second demo application. Next there is also the IServiceProxyAdministration interface to allow some administration on the demo service.
The IServiceProxyAdministration implementation
This interface is used for two purposes. It is called by SharePoint to create new applications and proxies as part of the farm configuration. The interface is also queried for the items to show in the New menu. Since the implementation of these methods is so simple I'll omit the specifics. Download the demo application to see what tasks it performs (it just calls the existing Create method or returns some simple information).
|
public interface IServiceAdministration { SPServiceApplication CreateApplication(string name, Type serviceApplicationType, SPServiceProvisioningContext provisioningContext);
SPServiceApplicationProxy CreateProxy(string name, SPServiceApplication serviceApplication, SPServiceProvisioningContext provisioningContext);
Type[] GetApplicationTypes();
SPPersistedTypeDescription GetApplicationTypeDescription( Type serviceApplicationType);
SPAdministrationLink GetCreateApplicationLink( Type serviceApplicationType);
SPCreateApplicationOptions GetCreateApplicationOptions( Type serviceApplicationType); } |
The New Application Page
Version two of the navigation application does contain one added item of interest. The ASP.NET page used to create new service applications. You know. It's the page where you'll allow administrators to enter a name, perhaps choose an application pool or two and a database here or there. The following specifics are interesting when implementing your own New Application page.
First of all the page directive hardwires the page to use the dialog master.
<%@ Page Language="C#" Inherits="..." MasterPageFile="~/_layouts/dialog.master" %>
Next, in the OnInit for the page, the dialog master is retrieved and the OK button is hooked up to a delegate.
|
protected override void OnInit(EventArgs e) { ((DialogMaster)this.Page.Master).OkButton.Click += OkButton_Click; base.OnInit(e); } |
In the method that is bound using the delegate, the following takes place. First you use the SPLongOperation (a little gem IMHO). To finish the long operation don't redirect but instead send some script to close the dialog and that's it!
|
void OkButton_Click(object sender, EventArgs e) { using (SPLongOperation operation = new SPLongOperation(this)) { operation.LeadingHTML = "Creating new Navigation Application"; operation.Begin(); try { ... } finally { operation.EndScript("window.frameElement.commitPopup();"); } } } |
The logic contained within the try / finally block first creates and provisions a service application, and next it creates a proxy too, adding it to the default proxy group. The idea of also creating a proxy is that we are not offering any user interface that allows administrators to create new proxies. The reason for not providing this is that the demo proxy calls methods directly on the application without needing an extra level of interaction configured using a URI. So, if an administrator is not going to create a proxy, we'd better do it for him!
|
NavigationService service = SPFarm.Local.Services.GetValue<NavigationService>(); string title = nameField.Text; NavigationApplication application = NavigationApplication.Create(title, service); application.Provision(); SPFarm farm = application.Farm; NavigationServiceProxy serviceProxy = (NavigationServiceProxy)farm.GetObject( String.Empty, farm.Id, typeof(NavigationServiceProxy)); NavigationApplicationProxy proxy = NavigationApplicationProxy.Create(title, application, serviceProxy, true); proxy.Provision(); |
That's it for today. Hopefully I'll have the time to write up the next part. I'll cover how to move your service proxy to the next step by adding WCF. Hope it helps!