API Documentation

API Overview

Overview

The MemberSuite System was designed with a Service Oriented Architecture. Simply speaking, this means that while object-oriented principles dominate the internals of the system, all significant functionality provided by the System is exposed via a series of endpoints, or services. The services comprise our Application Programming Interface, or API, and allow external applications access to System functionality. The baseline MemberSuite user interface and surrounding services makes use of exactly these services to do their work. The result of this architectural design is a clean separation of concerns between the User Interface, running agents and jobs, and the core of MemberSuite itself. The other benefit is the ability of external developers to extend, integrate with, and leverage the MemberSuite platform for their customers.

SSO - Single Sign-On

Reverse SSO FAQs

Who owns the credentials?

In this case, typically, MemberSuite would own the credentials and users would log in through our login page, however it would be possible to use regular SSO where another site owns the credentials and forwards the user through SSO to the MemberSuite portal. In this case, Reverse SSO could still be used to jump back to the originating site (if there was concern about session expiration) or to a completely different site.

How do I implement this?

There are 2 steps to this process:

Building the Receive SSO Token page

A sample “ReceiveSSOToken” page can be found in the C# sample code. This page requires many of the same prerequisites and setup as the regular SSO process including Access Keys and Signing Certificates. This page simply validates that the Token passed to it is valid through a call to the MemberSuite API.

Note: This token is only valid for a very short amount of time. You would not be able to cache it and use it later. You would want to make sure to generate the proper login session for the new site immediately and then continue with the process.

Creating Reverse SSO Links

To test that the new “ReceiveSSOToken” works, you would need to add a link somewhere in the MemberSuite Portal site content. This could be in the Portal Skin, a generic Portal Link, or in any other content exposed in the site (like an Event description or Portal Text Override).

The format of the link href would need to be:

1javascript:JumpToExternalLink('http://mysite.org/ReceiveSSOToken.aspx', 'http://google.com/', true);

This is a custom JavaScript function that will post the URLs appropriately to the page that will generate the Portal Security Token and redirect to the Client’s page. The parameters are:

  1. The URL of the page the client has built to receive and process the token
  2. The URL of the page to pass as the “NextUrl” to the client page (for redirecting after login)
  3. “true” to open in a new tab or “false” to keep in the same tab\window

We also pass to the client page the current URL as the “ReturnUrl” in case the receiving page wants to provide a “return to MemberSuite” link that takes the user back to exactly where they were.

Optional Keep-Alive Process

Since you may now be jumping between these 2 websites, one problem you may encounter is that your session on the MemberSuite Portal may expire while the user is looking at pages on your other site. The MemberSuite Portal will expire the session after 20 minutes of inactivity.

The simplest way to handle linking out of MemberSuite would be to push the external link to a new tab\window in the browser. This would allow the MemberSuite portal to remain open and continue to manage its own session timeout. If, however, you prefer a more seamless transition between MemberSuite and non-MemberSuite pages all within the same tab\window, you would need to implement keep-alive pages. Effectively, what this means is that every time a MemberSuite portal page loads, it will dynamically load a URL on the non-MemberSuite site to keep that session from expiring and then any pages on the non-MemberSuite site would need to also dynamically load a URL on the MemberSuite site.

You would want to put something like this in the header or footer of your non-MemberSuite site HTML so that is loads with every page:

1<div style="display: none">
2<iframe frameborder="0"height="0"src="https://site.membersuite.com/KeepAlive.aspx"width="0"></iframe>
3</div>

You would also need to add something similar to you Portal Skin HMTL to connect to a similar page on your non-MemberSuite site.

Signing Certificates

Overview

Certain methods on the Concierge require you to verify your identity with a Digital Signature. This Digital Signature is required on sensitive methods such as the CreatePortalSecurityToken method used for Single Sign On. This signature is required in addition to the standard Secret Access Key message signature included in the header of all Concierge API method calls. Therefore, a client with an Access Key is authorized to execute Concierge API method calls but NOT authorized to login as other users unless they also have access to a registered Signing Certificate.

There are two ways to register a new Signing Certificate: using an existing X.509 Certificate, or allowing the Concierge API to generate a new public/private key pair. In both scenarios, the client code must have access to the private key while the public key ONLY is saved in MemberSuite.

Once the Signing Certificate is registered, the private key can be used to generate a unique signature of some data. This data, the Signing Certificate ID, and the signature are then passed to MemberSuite where the Concierge API can use the public key to verify that the signature was generated by a signer with access to the paired private key.

You are responsible to maintain the security of your private key. Additionally, you should not allow users to submit data to be signed until their credentials/identity has been verified.

Creating/Using a Signing Certificate

The simplest way to create a Signing Certificate is to allow MemberSuite to generate a RSA-SHA1 key pair. MemberSuite will then allow you to download an XML document containing the private key and will automatically store the paired public key.

Once you have saved the XML file with the private key, it can be used to construct a new RSA-SHA1 signer. This signer can then be used to generate unique digital signatures. The following code snippet demonstrates this scenario:

01private static byte[] GenerateDigitalSignature(string dataToSign)
02{
03//This is an XML file so use Unicode encoding
04string xmlKeyString = File.ReadAllText("signingcertificate.xml", Encoding.Unicode);
05
06//Create a new signer using RSA and a SHA1 hash using the private key
07RSACryptoServiceProvider signer = newRSACryptoServiceProvider();
08signer.FromXmlString(xmlKeyString);
09
10returnsigner.SignData(Encoding.ASCII.GetBytes(dataToSign), newSHA1CryptoServiceProvider());
11}

PHP and other platforms have various options for a HMAC-SHA1 implementation. In order to test your algorithms, use the following:


Sample SigningCertificate.XML

01<RSAKeyValue>
02<Modulus>ze3EJPfwdYAIvTlroR9FxZxHcRKc9SbU+gtFmy3dqsJPB85mxSab31RPG8kbSZ90wya9CVIa4z5MJE2teq7J5lM9nLQ9iHiASQ6YMYLZj21ksN1xWXTChmIkDdm8kLGCRN6f1M0eUlITYGW8+CYmQzsC0eIB8YmaF3/ZmJrU9JM=</Modulus>
03<Exponent>AQAB</Exponent>
04<P>+aWaWTxJLNksC5RpkAtrTC+NQFR3PZh1WykG9UkN7enZ/DAZtoSBIwm4dHbV7tGpaL7vt970G12J5xSeslj3yw==</P>
05<Q>0ytZVAFLlJKdMzXojYsxRQGs3ejr/sIWjDkEvi3NpX6ZKbzQn8E5Pak7qfrpQEquSmCxLF3CIokcPnDrZsSNWQ==</Q>
06<DP>ez2eeckCEsrPLJRCnKKWgJDE+Wn5R4YZATy6u6Ip5zZXr2CLgQfevE5TKeN0byY/rH791laRSWUe693JDiBPiw==</DP>
07<DQ>V231OCCpKERjrZY+SIw+w/FjnyUWwI2fREM/QXY5VLHLvEoenmYjyvHMcB4ggKvq6YSLFnFjNWVLOlcKP6xVUQ==</DQ>
08<InverseQ>JDPOSPR8lhnTHnBSdIF1EOjDq60c+5BhOiGfREfSDUva9km7g5m0HFQK3uhftHZlNKOG9DEDNwN9HyttMXG97A==</InverseQ>
09<D>BbJfK7qkL0v0ce+lt5Za4sWZAFrIdsu1NpmbOljBehjYANpH44pW4428hoNAuv+rRj7LGffogILXrmVruCofsa+6T86+w+o7qfGAu35cpMVMsytE77nNvpMozNBsOz9y7WdTo8LX6RQnW7d5MEE9SAPBWDic/S/x7d/W1XPnNDE=</D>
10</RSAKeyValue>

Portal Username: test@membersuite.com”

The Base64 encoded signature should be:

<code>jTDKoH+INi19kGWn7WRk/PZegLv/9fPUOluaM57x8y1tkuwxOiyX86gxsZ7gU/OsStIT9Q5SVSG5NoaL3B+AxjuLY8b7XBMfTXHv2vidrDkkTTBW0D2LsrkZ3xzmvvPqqfA3tF2HXUYF+zoiTsr3bQdA32CJ+lDNkf+QjV3ZEoc=</code>

An automatically generated Signing Certificate in the Console can be created with the following steps:

  1. Click on Customer Center in the Console Toolbar
  2. Click on Manage Users
  3. Click on the View icon for the User that will own the Signing Certificate
  4. Click Create a Signing Certificate
  5. Click Generate Key Pair
  1. Click Download Key and save the private key for your new Signing Certificate and make a note of your new Signing Certificate ID

Creating/Using a Signing Certificate from a X.509 Certificate

Alternatively, if you have an X.509 certificate with a RSA-SHA1 key pair you can use it to create a Signing Certificate by registering the public key of the certificate. You can either cut & paste the contents of the certificate into the MemberSuite Console OR you can upload a .cer certificate file and MemberSuite will extract the public key only. If you are using a RSA-SHA1 key pair generated by MemberSuite as described above, you can skip this section.

If you are using the MemberSuite SDK, the following code snippet demonstrates how you can use a X.509 certificate from your Windows certificate store to generate a Digital Signature for the string data in the variable “data”. This snippet assumes there is a certificate with the subject “CN=SampleCertificate” in the Local Machine Personal certificate store. This code must be executed by a user with access to the Local Machine certificate store.

1MemberSuite.SDK.Utilities.CryptoManager.Sign(data,"CN=SampleCertificate", "");

If you are NOT using the MemberSuite SDK, the following code snippet describes the same behavior as the MemberSuite.SDK.Utilities.CryptoManager.Sign method.

01/// <summary>
02/// Welcome to MemberSuite API Samples. You can run this console app and be presented with a list of samples
03/// in this assembly. Selecting the appropriate menu item will run the selected sample.
04/// </summary>
05/// <param name="args"></param>
06static void Main(string[] args)
07{
08string data = "datatosign";
09X509Certificate2 sampleCertificate = GetCertificateByStoreAndSubject("CN=SampleCertificate","");
10byte[] signature = Sign(Encoding.ASCII.GetBytes(data), sampleCertificate.PrivateKey);
11}
12public static X509Certificate2 GetCertificateByStoreAndSubject(string subject, stringstore)
13{
14//Determine the store to use - always LocalMachine but the store name may be configured
15X509Store certStore =string.IsNullOrWhiteSpace(store)
16? new X509Store(StoreLocation.LocalMachine)
17: new X509Store(store, StoreLocation.LocalMachine);
18certStore.Open(OpenFlags.ReadOnly);
19//Not a keyed collection so query on the subject name using LINQ
20X509Certificate2 result =
21certStore.Certificates.Cast<X509Certificate2>().FirstOrDefault(
22currentCert => currentCert.Subject.Equals(subject));
23if (result == null)
24throw new ApplicationException(
25String.Format("Unable to locate certificate with Container Name '{0}' in Store {1}", subject, certStore.Name));
26return result;
27}
28public static byte[] Sign(byte[] data, AsymmetricAlgorithm key)
29{
30if (data == null || data.Length == 0)
31throw new ArgumentNullException("data","The data to sign is required.");
32if (key == null)
33throw new ArgumentNullException("data","The key to use to sign the data is required.");
34RSACryptoServiceProvider RSASigner = key asRSACryptoServiceProvider;
35if (RSASigner != null)
36//Currently only supports the certificate default SHA1
37return RSASigner.SignData(data, newSHA1CryptoServiceProvider());
38throw new ApplicationException("Unsupported key algorithm. Only RSA is supported.");
39}

Single Sign On

Overview

At this point you should have the following prerequisites as detailed in previous sections:

  • Access Key ID
  • Secret Access Key
  • Association ID
  • Signing Certificate ID
  • Signing Certificate private key in XML or X.509 Certificate format

Performing the Single Sign On entails the following steps:

  1. Construct a message header with your Access Key ID, Association ID, and Signature from your Secret Access Key
  2. Use your Signing Certificate to sign the Portal username of the person to log in.
  3. Execute the CreatePortalSecurityToken Concierge API method supplying the message header as well as the method parameters (Portal username to log in, your Signing Certificate ID, and Digital Signature of the Portal username created using your Signing Certificate)
  4. Execute a HTTP POST to the Login.aspx page in your Portal (https://[PortalURL]/Login.aspx). This POST should include the byte array received back from Step 3 as a Base64 encoded string in the variable “Token”.

Common Scenarios

A Single Sign On is usually designed in one of three styles:

  1. Using .NET and the MemberSuite SDK (recommended)
  2. Using SOAP proxy objects generated from the Concierge API WSDL. These proxy objects may be generated from tools in .NET (Web Reference using wsdl.exe / Service Reference using svcutil.exe) or frameworks for other platforms (NuSOAP, Zend, etc for PHP, Java, or other platforms).
  3. By manually constructing and POSTing a SOAP envelope. This option is completely platform independent and does not rely on any frameworks. This requires in depth understanding of SOAP/XML as you will be responsible for constructing and parsing properly formatted SOAP requests/responses.

As a companion to this document, three samples are available demonstrating each of these approaches. Although these samples are written in .NET, the concepts used in the WSDL and manual SOAP projects are transferrable to any other platform.

SSO With SDK

The SSO With SDK website demonstrates how to perform a Single Sign On when using .NET and the MemberSuite SDK. This is the recommended approach for Single Sign On.

In this scenario, the MemberSuite SDK will automatically create the required message headers and use your Secret Key to sign the message. You simply have to set your Access Key ID and Secret Access Key as demonstrated in the Global.asax.

The default behavior of the MemberSuite SDK is to store an Association ID and Session ID in ThreadStatic variables. This sample also demonstrates how to implement the IConciergeAPIAssociationIdProvider and IConciergeAPISessionIdProvider interfaces. These implementations are registered in the Global.asax and allow the MemberSuite SDK to read the Association ID from the web.config and read/store the web user’s Concierge API Session ID in the web user’s Session.

This sample also uses the helper methods in the CryptoManager class to simplify the process of creating a Digital Signature with a Signing Certificate. This sample uses a Signing Certificate private key downloaded in a XML file.

The RedirectToPortal.aspx page dynamically generates a HTML form. This page will be sent to the web client along with JavaScript that will automatically POST the form along with the Token generated from CreatePortalSecurityToken method to your Portal.

SSO With Web Reference (WSDL)

The SSO With Web Reference (WSDL) website demonstrates how to perform a Single Sign On using SOAP proxy objects generated from the Concierge API WSDL. In this case the proxy objects were generated using the Add Web Reference command (wsdl.exe). Although the proxy objects generated using other frameworks may be slightly different in structure, the concepts are the same. You must construct and populate a ConceirgeRequestHeader and pass it along with the call to CreatePortalSecurityToken.

This sample also demonstrates the process of creating a Digital Signature with a Signing Certificate. This sample uses a Signing Certificate private key downloaded in a XML file.

The RedirectToPortal.aspx page dynamically generates a HTML form. This page will be sent to the web client along with JavaScript that will automatically POST the form along with the Token generated from CreatePortalSecurityToken method to your Portal.

SSO With Manual SOAP Messages

The SSO With Manual SOAP Messages website demonstrates how to perform a Single Sign On when no framework to simplify SOAP communications is available. This style of interacting with the Concierge API is completely platform independent. It is recommended that you have in-depth knowledge of SOAP communication standards and best practices for this approach.

This sample uses no helper methods and demonstrates constructing a message signature, digital signature with a Signing Certificate, and constructing and POSTing a SOAP envelope manually. This sample uses a Signing Certificate private key downloaded in a XML file.

The RedirectToPortal.aspx page dynamically generates a HTML form. This page will be sent to the web client along with JavaScript that will automatically POST the form along with the Token generated from CreatePortalSecurityToken method to your Portal.

Sample SOAP Envelopes

Whichever approach you use to implement a Single Sign On, the primary action will be an exchange with the Concierge API. This exchange may be abstracted away with proxy objects or the MemberSuite SDK, however the structure will be the same. This exchange will consist of a SOAP request being sent to the Concierge API and a SOAP response being sent back. The response will contain the Token that must be sent to the Portal over a HTTP POST. The following is a sample of these SOAP messages.


Sample CreatePortalSecurityToken Request

01<s:Envelopexmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
02<s:Header>
03

<Action>http://membersuite.com/contracts/IConciergeAPIServ...

Action>

04<To>https://api.membersuite.com%3C/To>
05<ConciergeRequestHeaderxmlns:i="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://membersuite.com/schemas">
06<SessionId i:nil="true" />
07<AccessKeyId>AAAAAAAAAAAAAAAAAAAAAA</AccessKeyId>
08<AssociationId>00000000-0000-0000-0000-000000000000</AssociationId>
09<Signature>AAAAAAAAAAAAAAAAAAAAAAAAAAAA</Signature>
10</ConciergeRequestHeader>
11</s:Header>
12<s:Body>
13<CreatePortalSecurityTokenxmlns="http://membersuite.com/contracts">
14<portalUserName>portalusername</portalUserName>
15<signingCertificateId>AAAAAAAAAAAAAAAAAAAAAA</signingCertificateId>
16<signature>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</signature>
17</CreatePortalSecurityToken>
18</s:Body>
19</s:Envelope>


Sample CreatePortalSecurityToken Response

01<s:Envelopexmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
02<s:Header>
03<ConciergeResponseHeaderxmlns="http://membersuite.com/schemas"xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
04<SessionId>00000000-0000-0000-0000-000000000000</SessionId>
05</ConciergeResponseHeader>
06</s:Header>
07<s:Body>
08<CreatePortalSecurityTokenResponsexmlns="http://membersuite.com/contracts">
09<CreatePortalSecurityTokenResultxmlns:a="http://schemas.datacontract.org/2004/07/MemberSuit..."xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
10<a:Cacheable>false</a:Cacheable>
11<a:Errorsxmlns:b="http://schemas.datacontract.org/2004/07/MemberSuit..."/>
12<a:NotModified>false</a:NotModified>
13<a:Success>true</a:Success>
14<a:ResultValue>
15...
16</a:ResultValue>
17</CreatePortalSecurityTokenResult>
18</CreatePortalSecurityTokenResponse>
19</s:Body>
20

</s:Envelope>

Single Sign On Process Flow

1. USER submits your form containing their credentials (likely username/password) using SSL.

2. OPTIONAL – You either verify credentials in your own database OR you send the credentials to MemberSuite for verification using the LoginToPortal method

3. OPTIONAL – MemberSuite returns a result indicating if credentials were valid (this means both the username exists and the password matches).

** If no Session ID was specified in Step 2, a new API Session is constructed and a new API Session ID is also returned

4. You request a new Portal Security Token from MemberSuite by executing the CreatePortalSecurityToken method. You specify the username sent by the Client Browser that you verified as existing with a matching password in steps 2 & 3. Your right to create these tokens is verified by the signature you generate with your personal Signing Certificate

5. MemberSuite returns a new Security Token. This Token grants anyone who presents it to the Portal the right to establish a new Session with that Portal. This new session will be logged in as the user specified in Step 4 WITHOUT having to specify a username / password.

6. You return the Security Token to the Client Browser using SSL. The Client Browser can now use that Token to log themselves in to the Portal without supplying a username / password

In order to make this seamless to the user, you include JavaScript to automatically submit the Token over a POST. (POST is used here instead of GET so that the form is encrypted and submitted to the portal using SSL, ensuring the integrity of the Token and preventing man-in-the-middle attacks)

7. The Client Browser sends the Security Token to the Portal in a form POST using SSL

8. The Portal verifies the Token was properly generated by an API user who is authorized to generate Security Tokens for your portal (you)

9. The API returns a result indicating the Security Token was valid

10. A new, logged in, Session is established between the Portal and the Client Browser. The Client Browser may now navigate the Portal using that Session.

SSO FAQs

What is it?

Single Sign-on is used with MemberSuite to provide a seamless transition for members from an association’s password protected environment into the MemberSuite Portal without the user having to re-enter a password or a completely different set of credentials.

Who owns the credentials?

This is really up to the implementation. MemberSuite’s SSO can work whether or not MemberSuite owns the username and password. If MemberSuite owns the credentials, an API method can be called to authenticate a supplied username and password. If an external source is used to authenticate credentials, the assumption by MemberSuite is that the initiator of the SSO call trusts that authentication, so MemberSuite can as well.

What about shared authentication schemes like OAuth or OpenID?

The MemberSuite Portal does not currently support these natively, although it is on our roadmap.

What about synchronizing passwords between an external site and MemberSuite?

For security reasons, MemberSuite does not encourage this. MemberSuite security would not allow us to decrypt a User’s password even if we wanted to, so we could not initiate any type of password sync when a password changes on the MemberSuite side. There are API methods that allow setting of a User password, so an external application could possibly push password updates to MemberSuite if that was desired, but then of a change occurred on MemberSuite the credentials would be out-of-sync. Our strong recommendation is to choose one place to control the password, thus eliminating confusion when a password needs to be changed or reset.

How do I get started?

You should start by reviewing our documentation to understand the API:

  • Getting Started (see section below)
    • How To: Create a New Access Key
    • How To: Locate Your Association ID
    • Signing Certificates
    • Single Sign On

There is also this more technical diagram explaining the step-by-step SSO communication path: Single Sign On Process Flow (see section above)

Our recommendation is for the Association to provide the technical resource implementing the SSO at least temporary access to the MemberSuite Console application. With this access, the resource will be able to generate the necessary security keys safely, without having to pass them around via e-mail which could be dangerous.

Once you can log into the MemberSuite Console, you will need to create an Access Key ID \ Secret Access Key pair as well as a Signing Certificate ID \ Certificate File pair using the instructions found in the documentation.

The next step would be to pull down the Sample SSO implementations and try to get them running to connect with our Sample Association and test the SSO with the account Username: test, Password: test.

This should work with either the .NET Sample or PHP Sample. In both cases, the easiest version would be the SDK version (although direct WSDL and SOAP examples are provided in case you need to convert to a different language). When running, they should look like:

You can enter the password “test” or just leave the “Do not verify login credentials” checked. When clicking Login, you should be taken to:

Once you have the Sample code working with the default credentials and the MemberSuite API Sandbox association, you will want to swap in the credentials for your site.

.NET web.config:

1<add key="AssociationID" value="2537d8c3-0004-4ddb-b7e4-a6d76c09d3f9"/>
2<add key="AccessKeyID"value="AAAAAPIA/UObVXfWokrswA"/>
3<addkey="SecretAccessKey"value="PislBM1IXpWjshA/IQagSzrJ/FiXbuJu80zaKctvLO/CzHcZXYC9F8MbatI2jtFY4TZRU3TsmYflg3HsSYdWOQ=="/>
4<add key="SigningCertificateId"value="AAAAAPMAnESfVm9oock2Kw"/>
5
6<!-- Portal for SSO -->
7<add key="PortalUrl"value="https://customer14517d3f9.portal.production.member..."/>

PHP config.php:

1$config = array('AccessKeyId' =>'AAAAAPIA/UObVXfWokrswA',
2'AssociationId' => '2537d8c3-0004-4ddb-b7e4-a6d76c09d3f9',
3'SecretAccessKey' =>'PislBM1IXpWjshA/IQagSzrJ/FiXbuJu80zaKctvLO/CzHcZXYC9F8MbatI2jtFY4TZRU3TsmYflg3HsSYdWOQ==',
4'SigningcertificateId' =>'AAAAAPMAnESfVm9oock2Kw',
5'SigningcertificatePath' =>'bin/signingcertificate.xml',
6'PortalUrl' =>'https://customer14517d3f9.portal.production.membersuite.com/'
7);

The “PortalUrl” value can be found in the Console by going to “Setup \ Portal Settings” Also place the “signingcertificate.xml” in the “bin” directory with the one you downloaded from or registered with MemberSuite.

SSO Setup Checklist

  1. Log into Console and create a new API User for this specific integration and then create a new Access Key ID \ Secret Access Key pair (see section below) and download or copy the ID and Secret Access key somewhere.
  2. On this same user create a Signing Certificate (see section above) and be sure to save the XML file.
  3. Get the Association ID (see section above) for this association.
  4. Get the Portal URL from Setup \ Portal Settings.
  5. Set up either the .NET or PHP Sample SSO code and test using the credentials test \ test.
  6. Go back to your Console login and create a valid Portal Login and test it out by making sure you can log in to the PortalUrl (you can also create a new account from the Portal’s “Create Account” link).
  7. Swap in your credentials and signingcertificate.xml:
    • AssociationId
    • AccessKeyId
    • SecretAccessKey
    • SigningcertificateId
    • PortalUrl
    • \bin\signingcertificate.xml
  8. Now test the sample again with the Portal login you created above.
  9. Once you have confirmed that the credentials are working properly, you can start adding the code into your own login pages.

Optional Keep-Alive Process

Since users may now be jumping between these 2 websites, one problem they may encounter is that their session on the MemberSuite Portal may expire while they are looking at pages on your other site. The MemberSuite Portal will expire the session after 20 minutes of inactivity.

To prevent this you would need to implement keep-alive pages which means that every time a non-MemberSuite portal page loads, it will dynamically load a URL on the MemberSuite site to keep that session from expiring and then any pages on the MemberSuite site would need to also dynamically load a URL on the non-MemberSuite site.

You would want to put something like this in the header or footer of your non-MemberSuite site HTML so that is loads with every page:

1<div style="display: none">
2<iframe frameborder="0"height="0"src="https://site.membersuite.com/KeepAlive.aspx"width="0"></iframe>
3</div>

You would also need to add something similar to you Portal Skin HMTL to connect to a similar page on your non-MemberSuite site.

What options do I have when initiating SSO?

The default SSO behavior is to log the user in and send them immediately to the MemberSuite Portal main landing \ home page. We provide some additional parameters to further customize this experience. From the sample .NET code, you can see that the form you post to the Login.aspx page looks like this:

01<form name="LoginForm" method="post"id="LoginForm"action="RedirectToPortal.aspx">
02<input type="hidden"name="Token" id="Token" />
03
04<!--Once logged into Membersuite, jump to this URL-->
05<input type="hidden"name="NextUrl" id="NextUrl" />
06
07<!--In the MemberSuite Portal header, provide a return link to a custom URL-->
08<input type="hidden"name="ReturnUrl" id="ReturnUrl" />
09<input type="hidden"name="ReturnText" id="ReturnText" />
10
11<!--On logout from the MemberSuite Portal, redirect to this URL rather than the default login page-->
12<input type="hidden"name="LogoutUrl" id="LogoutUrl" />
13</form>
  • Token - This is the required security token that actually performs the SSO login.
  • NextUrl - This is the URL to jump to after the SSO login is complete. This can be a URL on the MemberSuite Portal or a link back into some other site.
  • ReturnUrl, ReturnText - If included, a link is added at the top of the MemberSuite Portal. This would be a way to include a link back into the main area of your website. This replaces what MemberSuite site admins see as "Back To Console" when logging in. To completely remove this link during the SSO process, remove these 2 fields from the form.
  • LogoutUrl - Once the MemberSuite Portal logout process is complete, redirect to this URL to perform any other logout processes.

How should the "Log Out" process be handled?

With SSO it can be important to ensure that logging out from one site will always log out of all the linked sites. The MemberSuite log out process handles this by allowing a "LogoutUrl" to be set during the initialization of the SSO session. When you click the "Log Out" link in the MemberSuite portal, we will redirect the user to the URL provided rather than the standard login page.

For any other sites that are initializing SSO with MemberSuite, you would need to add to that logout process a redirect to "LogOut.aspx" at the root of the site which will properly end the MemberSuite session. You can pass a querystring parameter "n" to this page to tell it where to redirect to after the logout process is complete. If this is not provided, the page would next try to redirect to the "LogoutUrl" provided during the initialization of the SSO session. Be very careful to avoid circular references here if the same code for your manual logout process also handles the "LogoutUrl" process. You would want to avoid "LogoutUrl" redirecting to "Logout.aspx" which would redirect to "LogoutUrl" which would redirect back to "Logout.aspx" and so on.

SOAP Developer's Guide

How To: Locate Your Association ID

You must specify your Association ID in the header of each request to the Concierge API. You can determine your Association ID by logging into the console and navigating to the Association Settings screen.

1. Click on Setup in the Console toolbar

2. Click on Association Settings

3. Make a note of your specific Association’s ID

How To: Create a New Access Key

You can create a new Access Key by logging into the MemberSuite Console and executing the following steps:

1. Click on Customer Center in the Console Toolbar.

2. Click on Manage Users.

3. Click on the View icon for the User who will own the Access Key

4. Click on Create an Access Key

5. The new Access Key is available to cut & paste on this screen. Optionally, you can download the Access Key ID and Secret Access Key in .CSV format

Getting Started

Overview

Every request to the MemberSuite API must be secure. In particular, we need to make sure of the following:

  • No one can intercept transmissions (man-in-the –middle) from you to our API
  • No one can pose as you and send damaging API requests (impersonation)
  • That each request is coming from a valid account (authentication)
  • That each request can do what it is attempting to do (authorization)

In order to solve these issues, MemberSuite has a simple yet powerful claims-based security scheme that is used to authenticate each request. This is based on three simple concepts:

  • A user – a user is the security principal in MemberSuite. Users can belong to roles, be granted (or denied) access to resources.
  • Access key – each user can have one or more “access keys.” These are unique keys that tell the MemberSuite API what user is trying to access the system. If an access key is compromised, it can be deleted and replaced with another, without changing the user account. This value will continue to be visible in the MemberSuite Console after the Access Key\Secret Key pair is generated.
  • Secret Key – This value is paired with only one Access Key ID and is generated by the MemberSuite Console. The secret key is a private, shared secret, tied to an access key. MemberSuite stores this key encrypted in the database. This key is used to “sign” each request to ensure authenticity. It will be visible on screen once, (allowing you to cut & paste the value) and you will be given the option to download the Access Key ID and Secret Access Key to a .CSV value. Your Secret Access Key is private – it should never be sent to the API and MemberSuite will never ask you for your secret key. It is only used to sign API messages (see Signature section below).
  • X.509 Certificate – this is used for single sign on, when you are “impersonating” another user. This extra layer of security ensures that the impersonation is authorized by the user account. This is not needed when single-sign on/impersonation is not being used
Best Practice

You have the ability to disable or delete each Access Key individually through the MemberSuite Console. Multiple applications that use the Concierge API can use the same Access Key or they can have their own Access Key. You should carefully consider your needs to determine when to share/create an Access Key. This can give you the ability to remotely deny API access to specific applications at any time.

Making an API Request

In order to make a request to the API, you need three things:

  • The Access Key ID (see section above)
  • The Secret Key (see section above)
  • The Association ID (see section above)

Understanding Session

Once you have established a session with the Concierge API you will receive a Session ID in the SOAP Response Header as described below. This Session ID should be passed back with each subsequent request. In this way, you can execute a LoginToPortal or LoginWithToken method and all subsequent requests will be executed in the context of the logged in user.

By default, the MemberSuite SDK stores the Session ID as a threadstatic variable and will automatically include it in the header of each request. In a web application, this value should usually be stored in the web user’s session so that each web user maintains a unique session with the Concierge API. This is accomplished by implementing the IConciergeAPISessionIdProvider interface and registering your custom implementation with the ConciergeAPIProxyGenerator.

If you are not using .NET or the MemberSuite SDK, you must manage storing the Session ID and passing it in each request header as described below.

Warning – because the Session ID is threadstatic, you should be careful when relying on the default implementation for ASP.NET requests. ASP.NET can switch requests up across multiple threads, shich would break the implementations. Instead, implement IConciergeAPISessionIDProvider and utilize HttpContext.Session to store session IDs.

Understanding the Signature

Each request to the Concierge API must be signed by a HMAC-SHA1 hash computed using your Secret Access Key. You Secret Access Key is private – it should never be sent to the API and MemberSuite will never ask you for your secret key. It is only used to generate a message signature.

Using .NET and the MemberSuite SDK

If you are using .NET and the MemberSuite SDK, this value will be calculated automatically and included in each message header as detailed below.

Using the WSDL

If you are not using .NET or the MemberSuite SDK, you will have to calculate the message signature manually using the following steps:

  1. Calculate the data to be signed. This is the concatenation of three strings: the message action, your Association ID, and the current Session ID. For example, assume the following information:
    1. You are attempting to execute the SOAP action “http://membersuite.com/contracts/ICo...Service/WhoAmI”
    2. Your Association ID is 00000000-0000-0000-0000-000000000000
    3. This is your initial call to the Concierge API so you are not specifying a Session ID
  2. In this scenario, your data to be signed is “http://membersuite.com/contracts/ICo...0-000000000000”.
  3. Once you execute any API method call you will be given a Session ID that you will supply on subsequent call. So to execute the same call once a Session ID has been established, the data to be signed would be “http://membersuite.com/contracts/ICo...1-111111111111”.
  4. Convert the data to sign from an ASCII string to a byte array
  5. Convert your Secret Access Key from a Base64 string to a byte array
  6. Use the byte array created in step 3 as the key for a HMAC-SHA1 signer
  7. Calculate the HMAC-SHA1 hash of the byte array created in step 2. The result will be a byte array
  8. Convert the byte array create in step 5 to a Base64 encoded string

In C# (.NET) this code would look like:

01public static string CalculateSignature(stringsecretAccessKey, string action,
02string associationId, string sessionId)
03{
04string dataToSignString = string.Format("{0}{1}{2}", action, associationId, sessionId);
05byte[] dataToSign = Encoding.ASCII.GetBytes(dataToSignString);
06
07byte[] key = Convert.FromBase64String(secretAccessKey);
08HMACSHA1 signer = new HMACSHA1(key);
09byte[] signature = signer.ComputeHash(dataToSign);
10
11return Convert.ToBase64String(signature);
12}

PHP and other platforms have various options for a HMAC-SHA1 implementation. No matter which implementation you choose the following should be true:

• Assuming your Secret Access Key is “AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==”

• Assuming data to sign is “http://membersuite.com/contracts/ICo...1-111111111111” (as described above)

• Signature should be “2zsMYdHb/MJUeTjv5cQl5pBuIqU=”

SOAP Request Message Headers

Each request to the Concierge API must include a ConciergeRequestHeader element in the SOAP header element.

Using .NET and the MemberSuite SDK

If you are using .NET and the MemberSuite SDK, it will construct and add these elements to each API request automatically once you have configured the required settings. The following code snippet demonstrates configuring these settings.

1ConciergeAPIProxyGenerator.SetAccessKeyId("AAAAAAAAAAAAAAAAAAAAAAAA");
2ConciergeAPIProxyGenerator.SetSecretAccessKey("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
3ConciergeAPIProxyGenerator.AssociationId = "00000000-0000-0000-0000-000000000000";

Once these settings are configured, any method executed on a proxy object generated in the style below will contain a message header with the proper structure/values.

1using(IConciergeAPIService proxy = ConciergeAPIProxyGenerator.GenerateProxy())
2{
3//Execute some API method on the proxy object
4}

Using the WSDL

If you are not using .NET and the MemberSuite SDK, you will have to construct this ConciergeRequestHeader element yourself. Usually this is accomplished by building proxy objects from the MemberSuite Concierge API WSDL, setting properties on these objects, and passing them in as parameters on each method call. However as the structure of such proxy object varies between frameworks and languages, the following examples will describe only the resulting SOAP messages. You will need to use your preferred framework to construct the messages properly.

The following complex type describes the ConciergeRequestHeader that must be sent with each message:

As described in the schema segment, the following elements must be included under the ConciergeRequestHeader:

  • AccessKeyID – This must be the ID of a valid, active Access Key as defined above in the Access Keys section.
  • AssociationID – This is the unique identifier of your Association.
  • Signature – This is a HMAC-SHA1 hash computed using the Secret Access Key known only to you pair with the specified Access Key ID.
  • SessionID – This is the current Session ID as described in the Session ID section above.


Sample Request

01<xs:complexType name="ConciergeMessageHeader" >
02<xs:sequence>
03<xs:element name="SessionId"type="xs:string" minOccurs="0"maxOccurs="1" />
04</xs:sequence>
05</xs:complexType>
06<xs:complexTypename="ConciergeRequestHeader">
07<xs:complexContent>
08<xs:extensionbase="ConciergeMessageHeader">
09<xs:sequence>
10<xs:element name="AccessKeyId"type="xs:string" minOccurs="1"maxOccurs="1" />
11<xs:element name="AssociationId"type="xs:string" minOccurs="0"maxOccurs="1" />
12<xs:element name="Signature"type="xs:string" minOccurs="1"maxOccurs="1" />
13</xs:sequence>
14</xs:extension>
15</xs:complexContent>
16</xs:complexType>

SOAP Response Message Headers

Each response from the Concierge API will include a ConciergeResponseHeader element in the SOAP header element.

Using .NET and the MemberSuite SDK

The MemberSuite SDK will automatically retrieve and handle critical values from each ConciergeResponseHeader. This includes the current Session ID which will then be sent on each subsequent API request in the ConceirgeRequestHeader.

Using the WSDL

If you are not using .NET or the MemberSuite SDK, you will have to handle the ConciergeResponseHeader element yourself. Usually this is accomplished by building proxy objects from the MemberSuite Concierge API WSDL and handling the return value from each method call. However as the structure of such proxy object varies between frameworks and languages, the following examples will describe only the return SOAP messages. You will need to use your preferred framework to handle the messages properly.

The following complex type describes the ConciergeResponseHeader that will be returned from each API call:

01<xs:complexType name="ConciergeMessageHeader" >
02<xs:sequence>
03<xs:element name="SessionId"type="xs:string" minOccurs="0"maxOccurs="1" />
04</xs:sequence>
05</xs:complexType>
06<xs:complexTypename="ConciergeRequestHeader">
07<xs:complexContent>
08<xs:extensionbase="ConciergeMessageHeader">
09<xs:sequence>
10<xs:element name="AccessKeyId"type="xs:string" minOccurs="1"maxOccurs="1" />
11<xs:element name="AssociationId"type="xs:string" minOccurs="0"maxOccurs="1" />
12<xs:element name="Signature"type="xs:string" minOccurs="1"maxOccurs="1" />
13</xs:sequence>
14</xs:extension>
15</xs:complexContent>
16</xs:complexType>

As described in the schema segment, the following elements will be included under the ConciergeResponseHeader:

  • SessionID – This is the current Session ID as described in the Session ID section above.


Sample Response

01<s:Envelopexmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
02<s:Header>
03<Actions:mustUnderstand="1"xmlns="http://schemas.microsoft.com/ws/2005/05/addressing...">
04http://membersuite.com/contracts/IConciergeAPIServ...Action>
05<ConciergeResponseHeaderxmlns:i="http://www.w3.org/2001/XMLSchema-instance"
06xmlns="http://membersuite.com/schemas">
07<SessionId>00000000-0000-0000-0000-000000000000</SessionId>
08</ConciergeResponseHeader>
09</s:Header>
10<s:Body>
11<WhoAmIResponsexmlns="http://membersuite.com/contracts">
12<WhoAmIResultxmlns:d4p1="http://schemas.datacontract.org/2004/07/MemberSuit..."xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
13<d4p1:Cacheable>false</d4p1:Cacheable>
14<d4p1:Errorsxmlns:d5p1="http://schemas.datacontract.org/2004/07/MemberSuit..."/>
15<d4p1:NotModified>false</d4p1:NotModified>
16<d4p1:Success>true</d4p1:Success>
17<d4p1:ResultValue>
18...
19</d4p1:ResultValue>
20</WhoAmIResult>
21</WhoAmIResponse>
22</s:Body>
23

</s:Envelope>

Configuration Settings

As a developer, you’ll want to avoid hard coding items in your software that could change. Additionally, you’ll want to reference items in the association’s system that are configured. This presents a couple of unique challenges.

The first is configuration; if you store your configuration in a local file, the only want it can be changed would be to access this file directly on your server. This is obviously not ideal.

The second is worse; let’s say your code relies on a custom field being present to add a value into that field. What happens if someone delete’s that field? Your code would break immediately.

To solve both of these problems, we have the concept of Configuration Settings. A Configuration Setting can be a simple name/value setting, or it can be linked to a configuration object in MemberSuite. If you create a linked configuration setting – to, say, a custom field – no one will be able to delete the object out of the system. This means you can be sure that the dependencies of your application will remain intact, impervious to accidental deletion by an unsuspecting client.

We strongly suggest that you use Configuration settings. The other huge advantage is that you can abstract away certain parts of your code. Say you have code that checks to see if a member is of a particular membership type, say Alumni. How do you do that? Let’s say you write code like this:

1if( member["Type"] =="594811f7-0006-4feb-b7c1-ae841b42a9c2")
2{
3Response.Write("Your membership is invalid");
4}

OK. But this is brittle; what happens if the ID changes? If you have to point your code at another association? Instead of doing this, you could create a configuration setting named CFG_MEM_ALUMNI, and do this:

1var alumniTypeID = proxy.GetConfigurationSetting("YourNamespace","CFG_MEM_ALUMNI").ResultValue;
2if( member["Type"] == alumniTypeID) { Response.Write("Your membership is invalid"); }

This is a much more robust process.

The MemberSuite Object

Overview

In MemberSuite’s system, domain objects are core objects that contain properties, methods, etc. But when you integrate with MemberSuite via the API, you need to be able to get at objects in a language-agnostic, serializable way. For this, we utilize the MemberSuiteObject.

The MemberSuite object is an implementation of the Data Transfer Object design pattern. Instead of being a core in-memory object, it is a collection of name/value pairs that represent the object. The MemberSuiteObject is designed to be serializable, meaning that it can travel across the API boundary without a problem. Here again we see the usefulness of aggregates; the aggregate boundary can now be considered a whole MemberSuiteObject for the purposes of transport.

Whenever you request an object from the API, the API goes through and converts the response to a MemberSuiteObject via a process called serialization. The MemberSuiteObject gives us several benefits:

  • It’s name/value pair structure can easily travel across the Internet, and be consumed by any language
  • An association’s custom fields can be placed in the name/value pair collection, making the MemberSuite object a direct representation of the association’s object structure
  • Entirely new objects can be created/transported, like Custom Objects, because the MemberSuiteObject is not constrained to any in-memory object

That second point is important. Associations have the ability to create custom fields for their objects. Those will always come across as fields that end in __c. So, you can always tell a custom field by looking for keys in the MemberSuiteObject ending in "__c".

System Fields

Since every object in MemberSuite derives from Aggregate, there are some base fields that each object has. They are:

  • ID – this is the GUID identifier that was described in the previous article
  • Name – by rule, all objects in MemberSuite must have a name. This name need not necessarily be unique, but there must be a name
  • SystemTimestamp – this is a binary field that tracks each time a record was updated. When you save an object, the timestamp is compared with the one in the database, and if the record has changed, you’ll need to get a new copy before saving again

Other Common Fields

In addition to the system fields, here are fields you see frequently on objects:

  • LocalID – this is the numeric ID assigned to the object. This is unique to the association specifically. Think of this like a identity key in a traditional database
  • Code – Configuration objects often use a code in addition to a name. This makes it easy to reference the object by Code, so that if the Name changes, references to the object do not break

Object Description

Since each association can have their own custom fields, and even their own custom objects, there needs to be a way to determine what fields are available for an object. This process is called object description. This result of description is class metadata. Class metadata incudes field names, types, default values, visibilities, and a number of other properties.

You can call the DescribeObject API method to describe an object.

Strongly Typed MemberSuiteObjects

If you’re using the MemberSuite SDK, then you can access strongly typed versions of the MemberSuite object. These are a convenience and make it easier to code, so:

1MemberSuiteObject msoOrder = new MemberSuiteObject();
2msoOrder["Total"] = 100;
3msoOrder["Date"] = DateTime.Today

Becomes:

1msOrder o = new msOrder();
2o.Total = 100;
3o.Date = DateTime.Today;

msOrder derives from MemberSuiteObjects; the properties Type and Date simply make calls to the underlying field dictionary for MemberSuiteObject; thus, this is simply a convenience for you to use with IntelliSense enabled IDEs.

Search in MemberSuite

Overview

The most common thing you will do in MemberSuite is to pull data out of the system. The mechanism for querying data in MemberSuite is called Search. There are two primary methods for search:

  • MemberSuite Query Language (MSQL) – this is a SQL-like language that allows you to select, insert, and update records
  • Search object – this is a object that represents a search, including search criteria, output columns, and sorting

MemberSuite provides an easy service for converting a MSQL string to a Search object, because internally, MemberSuite uses Search objects for everything.

MemberSuite Query Language (MSQL)

The MemberSuite Query Language is a SQL-like language that let's you SELECT/INSERT/UPDATE records. The basic syntax:

<code><span class="hljs-keyword">SELECT</span> <<span class="hljs-keyword">columns</span>> <span class="hljs-keyword">from</span> <searchType> <span class="hljs-keyword">where</span> <criteria>
<span class="hljs-keyword">UPDATE</span> <record> <span class="hljs-keyword">SET</span> <fieldsToSet> <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">ID</span>=<<span class="hljs-keyword">id</span>> 
<span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> <record> (<<span class="hljs-keyword">columns</span>>) <span class="hljs-keyword">VALUES</span> (<valuesToInsert>)
</code>

You can execute MSQL via the ExecuteMSQL method in the API. Some examples of MSQL queries:

<code><span class="hljs-keyword">Select</span> FirstName, LastName <span class="hljs-keyword">from</span> Individual <span class="hljs-keyword">where</span> Email <span class="hljs-keyword">like</span> <span class="hljs-string">'%gmail%'</span> <span class="hljs-keyword">or</span> (Age><span class="hljs-number">15</span> <span class="hljs-keyword">and</span> Gender_c=<span class="hljs-string">'Male'</span>) <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> CreatedDate
Insert <span class="hljs-keyword">into</span> Individual (FirstName, LastName) <span class="hljs-keyword">values</span> (<span class="hljs-string">'Andrew'</span>, <span class="hljs-string">'Ryan'</span>)
<span class="hljs-keyword">Update</span> Individual <span class="hljs-keyword">set</span> LastName=<span class="hljs-string">'Ryan'</span> <span class="hljs-keyword">where</span> <span class="hljs-keyword">ID</span>=<span class="hljs-string">' 594811f7-0006-4feb-b7c1-ae841b42a9c2'</span>
</code>

In order to know what to query, and what fields are available, check the Object and Search reference at the end of this manual.

The Search Object

Search Object Class Diagram

Usually, you will find it easier to use MSQL for querying data. It's simpler and easier to use. However, there may be times when want to construct your own search object, particularly if you're designing a search programmatically.

To the right is the class diagram for the search object.

At the base of a search object is a SearchOperation, which is a single operation/criterion from a search. A SearchOperationGroup is child of SearchOperation, as it is a special type of criterion that has child criteria. A Search is the principle object, which contains:

  • Type – the search type. This is usually the object type, like Individual or Organization
  • Context – if the search has a context (for instance, searching for event registrations for an event)
  • Criteria – a collection of SearchOperations that represent the search criteria
  • Output Columns – a collection of columns that represent the output of the search
  • Sort Columns – a collection of columns that represent the sort order

Because Criteria can contain SearchOperationGroups, you have the ability to design parenthetical queries. Consider the following MSMQL statement:

<code><span class="hljs-keyword">Select</span> FirstName, LastName <span class="hljs-keyword">from</span> Individual <span class="hljs-keyword">where</span> Email 
<span class="hljs-keyword">like</span> <span class="hljs-string">'%gmail%'</span> <span class="hljs-keyword">or</span> (Age>15 and Gender_c='Male') order by CreatedDate</code>

Note that we have a custom field in our MSMQL, which is perfectly legal. We know this because it ends in “__c”. The corresponding search object would be created like this:

<code>Search s = <span class="hljs-keyword">new</span> Search();
s.AddOutputColumn(<span class="hljs-string">"FirstName"</span>);
s.AddOutputColumn(<span class="hljs-string">"LastName"</span>);

s.AddCriteria(Expr.Equals(<span class="hljs-string">"Email"</span>, <span class="hljs-string">"gmail"</span>));

<span class="hljs-regexp">//</span> now, here<span class="hljs-string">'s our parenthesis, which is it'</span>s own group
SearchOperationGroup sog = <span class="hljs-keyword">new</span> SearchOperationGroup();
sog.Criteria.Add(Expr.IsGreaterThan(<span class="hljs-string">"Age"</span>, <span class="hljs-number">15</span>));
sog.Criteria.Add(Expr.Equals(<span class="hljs-string">"Gender__c"</span>, <span class="hljs-string">"Male"</span>));

s.AddCriteria(sog); <span class="hljs-regexp">//</span> add the parenthetical statement

<span class="hljs-regexp">//</span> add the sort column
s.AddSortColumn(<span class="hljs-string">"CreatedDate"</span>);
</code>

You can see this is more tedious that using a MSQL statement. But for instances where you need to build the search programmatically, you can.

Aggregate Search Functions

MemberSuite also let's you run aggregate searches. Consider this MSQL query:

<code><span class="hljs-keyword">Select</span> <span class="hljs-keyword">count</span>(FirstName), <span class="hljs-keyword">Avg</span>(Age), <span class="hljs-keyword">Min</span>(CreatedDate),<span class="hljs-keyword">Max</span>(LastModifiedDate) <span class="hljs-keyword">from</span> Individual <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> _Preferred_Address_State</code>

Not only is this perfectly good MSQL, it can be accomplished with the following search object:

<code>Search s = <span class="hljs-keyword">new</span> Search();
s.OutputColumns.Add(<span class="hljs-keyword">new</span> SearchOutputColumn { 
</code> <code><span class="hljs-attr">Name</span> = <span class="hljs-string">"FirstName"</span>, AggregateFunction = SearchOuputAggregate.Count });
s.OutputColumns.Add(new SearchOutputColumn { 
</code> <code><span class="hljs-attr">Name</span> = <span class="hljs-string">"Age"</span>, AggregateFunction = SearchOuputAggregate.Average });
s.OutputColumns.Add(new SearchOutputColumn { 
</code> <code><span class="hljs-attr">Name</span> = <span class="hljs-string">"CreatedDate"</span>, AggregateFunction = SearchOuputAggregate.Min });
s.OutputColumns.Add(new SearchOutputColumn { 
</code> <code>Name = <span class="hljs-string">"LastModifiedDate"</span>, AggregateFunction = SearchOuputAggregate.Max });

<span class="hljs-comment">// now, the group by</span>
<span class="hljs-comment">s.OutputColumns.Add(new SearchOutputColumn { </span>
</code> <code><span class="hljs-attr">Name</span> = <span class="hljs-string">"_Preferred_Address_State"</span>, AggregateFunction = SearchOuputAggregate.GroupBy });</code>

Just like in SQL, if you use an aggregate function, then all output columns must either be another aggregate or must be a GROUP BY column.

Search Operations

The following search expressions are supported:

  • Contains/DoesNotContain
  • ContainsOneOfTheFollowing/DoesNotContainOneOfTheFollowing
  • Equals/DoesNotEqual
  • IsBetween/IsNotBetween
  • IsBlank/IsNotBlank
  • IsGreaterThan
  • IsGreaterThanOrEqualTo
  • IsLessThan
  • IsLessThanorEqual

The following aggregate functions are supported:

  • None (Default)
  • GroupBy
  • Sum
  • Average (AVG)
  • Count
  • Min
  • Max

Search Specifications

Before we dive into these concepts, it's worth explaining that Search is a big deal within MemberSuite. By default, any field that is on a MemberSuiteObject, including custom fields, can be searched. However, searches very often expose extra fields that do not exist on objects. These are usually very useful calculations/aggregates that you should know about an take advantage of. The combination of these fields make up a Search Specification. Just like objects, searches can be described For example, consider an Invoice, which has the fields:

  • Total
  • BalanceDue
  • DueDate

The search specification adds these fields:

  • AmountPaid
  • Age

While these don't appear in the object themselves, you can see how they are useful calculations that you'd want to pull data based upon. For information about what fields exist in what searches, see the reference at the end of this manual.

Cross-Object Searches

A very powerful feature of searches is called cross-object searches. These allow you to search into object references; think of them like left outer joins in the SQL world. Consider the Invoice search, which has:

  • BillTo
  • Total

The BillTo field is actually a reference to an Entity. As per our aggregate rules, we'd expect an ID to come back in that field. But what if we need to know the name of all invoice owners who have invoices for more than $100? Simple:

<code><span class="hljs-keyword">Select</span> BillTo.Name <span class="hljs-keyword">from</span> Invoice <span class="hljs-keyword">where</span> Total><span class="hljs-number">100</span></code>

The dot(.) notation dereferences the object, allowing you to access it's properties. You have a depth limit of five (5) for this; building on the previous example, say we want to find all people who have invoices over $100, but we want to see what state their company is in:

<code><span class="hljs-selector-tag">Select</span> <span class="hljs-selector-tag">BillTo</span><span class="hljs-selector-class">.Name</span>, <span class="hljs-selector-tag">BillTo</span><span class="hljs-selector-class">.PrimaryOrganization</span><span class="hljs-selector-class">._PreferredAddress_State</span> <span class="hljs-selector-tag">from</span> <span class="hljs-selector-tag">Invoice</span> <span class="hljs-selector-tag">where</span> <span class="hljs-selector-tag">Total</span> > 100</code>

Whenever you see a field of type Reference this means you can perform a cross object search to drill down into it's properties.

Returning Objects

There may be time when you want to return whole objects, not just field values, for the search. An example is when you're trying to do a merge. You have an email, and you want to find the person who has that email, or create a new record if it doesn't exist. MemberSuite offers two syntaxes for this, depending on whether you want to find a single object, or multiple objects:

<code><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">OBJECT</span>() <span class="hljs-keyword">FROM</span> INDIVIDUAL <span class="hljs-keyword">WHERE</span> EMAIL=<span class="hljs-string">'andrew@gmail.com'</span>
<span class="hljs-keyword">SELECT</span> OBJECTS() <span class="hljs-keyword">FROM</span> INDIVIDUAL <span class="hljs-keyword">WHERE</span> EMAIL=<span class="hljs-string">'andrew@gmail.com'</span>
</code>

The first will only return a single object (or no object, if no such record exists), the second will return all matching records. It will use the ID field of the Individual to load the MemberSuiteObject and send it back via the API.

Consider a scenario in which you don't want to pull the object being searched on, but the related object. For instance, you wanted to pull a MemberSuiteObject for everyone who had an invoice >30 days. You could use the name of the field to use for the Object load:

<code><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">OBJECT</span>(BILLTO) <span class="hljs-keyword">FROM</span> INVOICE <span class="hljs-keyword">WHERE</span> AGE><span class="hljs-number">30</span>
<span class="hljs-keyword">SELECT</span> OBJECTS(BILLTO) <span class="hljs-keyword">FROM</span> INVOICE <span class="hljs-keyword">WHERE</span> AGE><span class="hljs-number">30</span>
</code>

Again, the first example will only pull a single object, where the second will pull all objects. In this case, instead of pulling the Invoice, you'd expect to get an Entity back.

Objects in MemberSuite

Tenancy in MemberSuite

In MemberSuite, there is a difference between a Customer and an Association. A Customer is a billing entity – we have a contractual relationship with the organization represented by this record. An Association is actually a “tenant” in our system; it’s a repository of objects that comprise an association. One customer usually has one association, but there are exceptions to this, like when a Customer is in the business of managing multiple associations (Association Management Companies), or when sandbox associations are created under the customer.

Thus, the 99% of all objects in MemberSuite live in an association.

How Objects Work in MemberSuite

MemberSuite is inherently an object oriented design; we use object to encapsulate state and behavior. There is a special class of objects that correspond to the functionality of the software called domain objects. Domain objects borrow from Eric Evan’s Domain Driven Design – by definition, these objects should represent pieces of the domain that a domain expert (or subject matter expert) can understand. Consider the following:

We can see the concept of an Individual and an Organization are both types of Entities. An Entity can own an order – which means either an individual or an Organization can define an order.

These concepts are easily readable and verifiable by a non-programmer domain expert, which is the very point of domain objects. In essence, domain objects represent the key concepts of the problem an application is trying to solve.

MemberSuite has over 200 domain objects, spanning over 20 different modules, or namespaces. Examples of these objects:

  • Individual
  • Invoice
  • Order
  • Event
  • Competition

Object Identification

Each object has a globally unique identifier, or GUID. A GUID is a 32-byte value that is designed to be unique across all time and space. For more information on GUIDs, take a look at:

http://en.wikipedia.org/wiki/Globally_unique_ident...

The GUID ensures that we can locate an object across multiple databases, and across time and space. The GUID has a specific format that allows us, from a GUID, to determine the association that the object belongs to and the type of objects.

The first 4 bytes of the GUID are called the association hint. This identifies the association the object belongs to. As a rule, these 4 bytes match the 4 bytes of the target association. In this example, MemberSuite determines what association this object belongs to so by finding an association whose ID belongs with 594811f7.

The second two bytes are called the guid hint. This tells MemberSuite what type of object it is. In this case, 0006 indicates that the object is of type Individual. The Data Dictionary reference included in this documentation will list the 2-byte guid hint for each object.

Aggregates

Borrowing further from Domain Driven Design, we define an aggregate as an object boundary. There are several important reasons for this. Consider an object, Invoice. An invoice has multiple InvoiceLineItem objects. But invoices have rules; in particular, one invoice must have at least one line item. Additionally, line items must add up to the invoice total.

If you have the ability to modify InvoiceLineItem records directly, how would one enforce these rules? It would quickly become difficult, as you would have to write logic to check for other invoice line items when saving individual line items.

Conversely, we can make a rule for ourselves; you cannot save InvoiceLineItems directly, you must save them within the context of an Invoice. This solves our problem; since invoice line items can’t be referenced or saved directly, we can rely on the Invoice object to apply any validation rules necessary. Thus, the Invoice, and the associated InvoiceLineItems, form an aggregate boundary. The Invoice is known as an aggregate. The InvoiceLineItem is known as an aggregate child.

Aggregates have the following properties:

  1. All aggregates in MemberSuite have IDs that can be referenced directly
  2. Aggregates can contain non-aggregates, but cannot contain direct links to other aggregates. If they point to other aggregates (i.e., the Order.BillTo pointing to an Entity), they must reference the ID, instead of the whole object itself
  3. Aggregates can perform their own validations
  4. Aggregates must travel together across the API boundaries – so when you pull an Invoice, you must get its invoice line items.

AssociationDomainObjects vs. The Others

Now, consider this object model:

As you can see, the fundamental concept of MemberSuite is a DomainObject. From there, we have aggregates and aggregate children. Under aggregate, we’ve got objects that live in an association (remember that 99% of objects do), and then objects that live outside of an association.

Think of MemberSuite as your apartment building. You live in an apartment. So does your neighbor. So does his. Almost everyone lives in an apartment building, except for the super-intendent. The super has his own office that isn’t an apartment, but instead supports all of the other apartments.

In MemberSuite, each association is like an apartment. Almost everything lives in an association, or an apartment. A few things don’t – they live like the super, and in MemberSuite the super’s office is called the catalog. Catalog objects are global across associations and live in a special database called – you guessed it – the Catalog.

Examples of catalog objects:

  • Customer
  • User