Hosting WCF Services within SalesLogix
Posted By: nicocrm on June 24th, 2008 in Uncategorized
No Gravatar

The recent releases of WCF brought a whole bunch of goodies: most notable for me are the added support for syndication and script services, as well as a "no-configuration" scenario where we did not need to add a whole page of XML in web.config anymore just to get a basic service running.

I knew that there would be a few problems to getting it to collaborate with the Saleslogix web client app because of its tight integration with the ASP.NET pipeline, but wanted to take a look anyway.

I started with this very simple service file:

<%@ ServiceHost Language="C#" Debug="true" 
    Service="SSSWorld.Scratch.SimpleService" 
    CodeBehind="SimpleService.svc.cs"
    Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory, 
      System.ServiceModel.Web, 
      Version=3.5.0.0, 
      PublicKeyToken=31bf3856ad364e35" %>

The key points is the "WebScriptServiceHostFactory.  Basically this automatically configures the endpoint for WCF without the need to add it to the web.config.

My SimpleService.cs at this point is also very simple:

public class SimpleService : ISimpleService
{
    public String GetData()
    {
        return "hello";
    }
}

And this is the corresponding "ServiceContract" (aka interface):

[ServiceContract]
public interface ISimpleService
{
    [OperationContract]
    [WebGet]
    String GetData();
}

The first problem I ran into appears to have actually been an installation problem.  I kept getting an error "Unable to load System.ServiceModel.Web".  Eventually I re-registered the assembly with the GAC (with gacutil /i /F) and all was good on that side.

Next was an IIS configuration problem.  Apparently integrated Windows authentication needs to be disabled.  So I cut it off (if you still wanted it to work with Saleslogix you could just cut it off for the folder containing the .svc file, I suppose) and finally got a

{"d":"hello"}

back at me.

Next I wanted to be able to access the Saleslogix service, so I added the following code:

if (ApplicationContext.Current == null)
                throw new InvalidOperationException("No Application Context");

Got the expected exception thrown.  I knew that ApplicationContext relies on classic ASP.NET sessions and also has some hard-coded dependencies to HttpContext.Current so I needed to enable the ASP.NET compatibility of WCF.  This is done with 2 changes, first I needed to add an attribute on the SimpleService class:

[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Required)]

And I also needed the following blurb in web.config (which I was a little bit sad about since I was hoping not to have to modify it, but oh well):

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel> 

At this point I got my "hello" back.

Now it was time to see how far we could go!  I changed "GetData" into a simple search service – what it would do is simply return a little bit of information about accounts matching the input string (well, just the account name, for now):

public String GetData(String search)
{
    if (ApplicationContext.Current == null)
        throw new InvalidOperationException("No Application Context");
    if (String.IsNullOrEmpty(search))
        return "";
    using (SessionScopeWrapper session = new SessionScopeWrapper(true))
    {
        var matches = session.CreateQuery("from Sage.SalesLogix.Entities.Account a where a.AccountName like ?")
            .SetString(0, search + "%")
            .List<IAccount>();
        if (matches.Count > 0)
        {
            return matches[0].AccountName;
        }
        else
        {
            return "";
        }
    }
}

Go to http://…../SimpleService.svc/GetData?search=Abbott (going through the login page if necessary) and this returns:

{"d":"Abbott Ltd."}

as expected.

Getting pretty close!  Now if you wanted to return a list with some more info about each account, how about this:

  • Create a data contract:
[DataContract]
public class AccountInfo
{
    [DataMember]
    public String AccountName { get; set; }

    [DataMember]
    public String MainPhone { get; set; }

    [DataMember]
    public String City { get; set; }
}
  • Change the declaration:
[OperationContract]
[WebGet]
IList<AccountInfo> GetData(String search);
  • Change the "return matches[0].AccountName" line into something like this:
var q = from m in matches
        select new AccountInfo
        {
            AccountName = m.AccountName,
            MainPhone = m.MainPhone,
            City = m.Address.City
        };
return q.ToList();

Well, you get the idea.  The data can be recovered from a script service on an ASP.NET page, etc.

Now there is one serious limitation – this requires the user to be properly authenticated (because otherwise SalesLogix can’t form the connection string!).  If we need the service to be accessible from an outside client (eg for a mashup or an RSS feed) you would have to either somehow simulate a user login, or forego the whole ApplicationContext, host your service outside of the web client, and maintain the connection string for it separately.  At this point you could either use the technique I outlined in my "Unit Testing" posts to still get access to the NHibernate session (and to the entity business rules!), or just use regular ADO.NET to retrieve the data (probably easier, more reliable, and yielding better performance, unless you need access to the business rules.

Anyway, this was an interesting exploration, I have not decided if I would make anything of it yet.


One Response to “Hosting WCF Services within SalesLogix”

  1. Nicolas GallerNo Gravatar says:

    Not working anymore as of 7.5, without some more extensive hacking through the settings (which I don’t care to do as it makes upgrades nightmarish)

Leave a Reply