1

I'm working on refactoring a synchronization web app that our company uses. Currently it's merely a set of controllers that fire up a set of helpers that go through a long chain of if-else type statements.

I'm attempting to build a wrapper around the SalesForce Soap web reference, but I'm having some trouble coming up with a good inheritance model.

When using the wrapper, I want to essentially be able to say something like

var SalesForce = new SalesForceApi(Username, Password, Url);
var tmp1 = SalesForce.Users.GetUsers();
// or
var tmp2 = SalesForce.Users.GetUserById(id);
// or
var tmp3 = SalesForce.Organizations.GetOrganizations();
// you get the picture.

Ideally I would want to instantiate the SalesForceApi object, and simply pass its active connection through all of it's different requests and request types. I'm just a bit fuzzy on the implementation portion.


I think I'm somewhat getting into the right direction.

I created an Abstract class called Base that contains an object called Binding with the type SforceService. It contains login and logout methods that are used to create an active connection. When a SalesForceApi object is created with the username, password, and url, these are passed to the sub classes like Users which inherit the Base class. These values are then passed to the Base class to create an active binding object.


Edit

Here are a few classes I built that give me somewhat of a method of accomplishing what I want.

My Main SalesForce class (or SalesForceApi)

namespace SyncTool.Core.Services.SalesForce
{
    public interface ISalesForce
    {
        IAccounts Accounts { get; }
        IAttachments Attachments { get; }
        ICases Cases { get; }
        IContacts Contacts { get; }
        IGroups Groups { get; }
        IRecordTypes RecordTypes { get; }
        IUsers Users { get; }    
    }

    public class SalesForceApi:ISalesForce
    {
        public IAccounts Accounts { get; set; }
        public IAttachments Attachments { get; set; }
        public ICases Cases { get; set; }
        public IContacts Contacts { get; set; }
        public IGroups Groups { get; set; }
        public IRecordTypes RecordTypes { get; set; }
        public IUsers Users { get; set; }

        public SalesForceApi()
        {
            Accounts = new Accounts();
            Attachments = new Attachments();
            Cases = new Cases();
            Contacts = new Contacts();
            Groups = new Groups();
            RecordTypes = new RecordTypes();
            Users = new Users();
        }
    }
}

The Request Base class

namespace SyncTool.Core.Services.SalesForce.Requests
{
    public abstract class RequestBase: IDisposable
    {
        private const string User = "foo@bar.com";
        private const string Pass = "foobar";
        private const string Url = "url";

        public SforceService Binding;
        public Logger Logger = LogManager.GetCurrentClassLogger();

        protected RequestBase()
        {
            Login(User, Pass, Url);
        }

        public bool Login(string username, string password, string url)
        {
            Binding = new SforceService { Url = url, Timeout = 60000 };
            LoginResult lr;

            try
            {
                lr = Binding.login(username, password);
            }
            catch (SoapException e)
            {
                Logger.Error("An error has occurred",e);
                return false;
            }

            Binding.SessionHeaderValue = new SessionHeader { sessionId = lr.sessionId };
            Binding.Url = lr.serverUrl; 
            return true;
        }

        public bool Logout()
        {
            try
            {
                Binding.logout();
                return true;
            }
            catch (SoapException)
            {
                return false;
            }
        }

        public void Dispose()
        {
            Logout();
            Binding.Dispose();
        }
    }
}

And here's an example of one of the request classes

namespace SyncTool.Core.Services.SalesForce.Requests
{
    public interface IUsers
    {
        List<User> GetUsers();
        User GetUserById(string id);
        List<User> GetUsersByEmail(string email);
        User GetUserByEmail(string email);
    }

    public class Users : RequestBase, IUsers 
    {
        public List<User> GetUsers()
        {
            try
            {
                const string soqlQuery = "SELECT Id, Name, FirstName, LastName, Email FROM User";
                var qResult = Binding.query(soqlQuery);
                var done = false;
                if (qResult.size <= 0) return new List<User>();
                var users = new List<User>();
                while (!done)
                {
                    var records = (qResult.records.Cast<User>().ToList());
                    users.AddRange(records);
                    if (qResult.done)
                        done = true;
                    else
                        qResult = Binding.queryMore(qResult.queryLocator);
                }
                return users;
            }
            catch (SoapException e)
            {
                Logger.ErrorException("An unexpected error occurred when retreiving users.",e);
                return null;
            }
        }

        public User GetUserById(string id)
        {
            try
            {
                var qResult = Binding.query("SELECT Id, Name, FirstName, LastName, Email FROM User WHERE Id='" + id + "'");
                if (qResult.size <= 0) return new User(); 
                var records = (qResult.records.Cast<User>().ToList());

                return records.Count > 0 ? records.FirstOrDefault() : new User();
            }
            catch (SoapException e)
            {
                Logger.ErrorException("An unexpected error occurred when retreiving users.", e);
                return null;
            }
        }

        public List<User> GetUsersByEmail(string email)
        {
            try
            {
                var qResult = Binding.query("SELECT Id, Name, FirstName, LastName, Email FROM User WHERE Email='" + email + "'");
                if (qResult.size <= 0) return new List<User>();
                var records = (qResult.records.Cast<User>().ToList());

                return records;
            }
            catch (SoapException e)
            {
                Logger.ErrorException("An unexpected error occurred when retreiving users.", e);
                return null;
            }
        }

        public User GetUserByEmail(string email)
        {
            try
            {
                var qResult = Binding.query("SELECT Id, Name, FirstName, LastName, Email FROM User WHERE Email='" + email + "'");
                if (qResult.size <= 0) return new User();
                var records = (qResult.records.Cast<User>().ToList());

                return records.Count > 0 ? records.FirstOrDefault() : new User();
            }
            catch (SoapException e)
            {
                Logger.ErrorException("An unexpected error occurred when retreiving users.", e);
                return null;
            }
        }
    }
}
1

I'm attempting to build a wrapper around the SalesForce Soap web reference

I don't see anything in what you show that gets to wrapping a SOAP thingy. But please bear with me as I go through what I see...

When using the wrapper, I want to essentially be able to say something like ...

I call this "wishful thinking design." Design things they way you want them to be. Then wrap or manipulate the existing target object so we can use it the way we want to.

Sketch out classes that support what you want. So far I see:

  • SalseForceApi class, containing
    • Users collection
    • Organizations collection
    • UserName, Password, URL
      • No idea yet if these are separate properties or contained w/in 1 or more other classes
    • SforceService object
  • User class
  • User collection class
    • GetUserById
    • GetUsers
  • Organization class
  • Organization collection class
    • GetOrganizations
  • SforceService class
    • Login
    • Logout

Base (abstract class)

very bad name.

... Users which inherit the Base class.

This seems to be just one more issue in the overall design but you seem to be saying "inheritance model" is the central issue. This seems to suggest that the User contains a wrapped SOAP reference object.

I'm not seeing inheritance as the solution. Granted I'm profoundly ignorant of your problem space, but ignorance is bliss. So here we go...


sForceService

I see this as the wrapper you talk about. And we're basically talking about the adapter pattern idea here.

The wrapper encapsulates all the grunt work of creating and using the soap object. It exposes what the soap object contains.

'SalesForceApi' passes along that stuff through the "Sales Force API".


Composing the Adapter

public class sForceService {
    public sForceService ( User theUser, ??? url ) {
         // in here set up with stuff provided by theUser
   }

   // the adapter makes the connection, fetches stuff and exposes same
   // through public methods...
}


public class SalesForceApi {
    protected sForceService SFS { get; set; }

    // Maybe the api class constructor, in part, might looks a little like this
    public SalseForceApi ( string userName, string password, ??? url ) {
        User someOne = new User(userName, password);
        sForceService SFS = new sForceService ( someOne, url);
    }

    public User User(int id) {
        return SFS.User(id);
    }

    public UserCollection Users {
        get { return SFS.Users(); }
    }

    public OrganizationCollection Organizations {
        { get SFS.Organizations(); }
    }

    public OrganizationCollection UserOrganizations (User forThisGuy) { }
}


//CLIENT code
SalesForceApi sfa = new SalesForceApi ( someUser, someURL );
var tmp1 = sfa.User(34);
var tmp2 = sfa.Users;
var tmp3 = sfa.Organizations;
var tmp4 = sfa.UserOrganizations( fred );
  • sForceService is actually the SOAP object itself. It handles all the requests, so there's not much needed in setting it up. I've updated the OP with some example code that shows how I've built it out so far. I'm open to feedback as-to whether there's a better way. – JD Davis Dec 3 '15 at 22:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.