jump to navigation

It’s been awhile… July 25, 2008

Posted by mmoriar1 in Uncategorized.
2 comments

It’s been about 3 weeks since my last post.  I’ve been slacking in the documentation part of my project I suppose.  Oops 🙂

I’ve done a whole lot in the past 3 weeks, as expected of me.  What I’ve done is implement pages for Triggered Send Definition CRUD (Create Retrieve Update Delete), Triggered Send Definition Tracking data retrieval, ListSubscriber Retrieval, ImportDefinition Creation and Perform, EmailSendDefinition Creation and Perform, List CRUD, Group RUD, Data Extension CD, to name a few.

What I’ll go over in this post is a few nuances which, if known, will help coding the above Web Services calls.

  • Caveats of sending a triggered send via Web Services
  • Retrieving Triggered Send Definitions
  • How to use ListSubscriber to get subs on a list or lists for a sub
  • Details around the DataExtensionObject

One major caveat of sending a triggered send through Web Services is that you must make sure that the subscriber’s required attributes are populated prior to sending a triggered send to said subscriber.  for example, if Last Name was a required subscriber attribute, and I tried to send to a subscriber without a last name attribute being populated, I would get the error: “Unable to queue Triggered Send request.  There are no valid subscribers.”

Another big caveat that is mostly common sense is that if AutoAddSubscribers and/or AutoUpdatesubscribers is/are equal to true then the TriggeredSendDefinition List must be set to a valid List.  This is so that the subscriber can be added/updated on a list if desired.

When retrieving Triggered Send Definitions (as well as a few other objects like Import Definitions, etc.) deleted triggered sends will be retrieved.  This is because, although they are labeled as Deleted in the ExactTarget system, they are still IN the system and will be retrieved as such.  You won’t be able to send or modify the Definition (or other object) in any way, but nonetheless it is there and might confuse someone who doesn’t expect to see a deleted TSD in their list of Definitions.

Working with ListSubscriber objects is pretty straightforward.  Below are two code snippets that are fairly useful:

Get all lists that a subscriber is on:

String usermm = Session[“Curr_User_Name”].ToString();
String mmpass = Session[“Curr_User_Pass”].ToString();
PartnerAPIWse integrationFramework = CreatePartnerAPI(usermm, mmpass);

SimpleFilterPart sfp = new SimpleFilterPart();
sfp.Property = “SubscriberKey”;
sfp.SimpleOperator = SimpleOperators.equals;
sfp.Value = new string[] { “SubscriberKey” }; //put the desired subkey here

RetrieveRequest listsub_request = new RetrieveRequest();
listsub_request.ObjectType = “ListSubscriber”;
listsub_request.Filter = sfp;

string[] ls_props = new string[] { “ListID”, “SubscriberKey”, “Status” };

listsub_request.Properties = ls_props;

APIObject[] results;
string status = “”;
string requestID = “”;
status = integrationFramework.Retrieve(listsub_request, out requestID, out results);

Get all subscribers on a list:

String usermm = Session[“Curr_User_Name”].ToString();
String mmpass = Session[“Curr_User_Pass”].ToString();
PartnerAPIWse integrationFramework = CreatePartnerAPI(usermm, mmpass);

SimpleFilterPart sfp = new SimpleFilterPart();
sfp.Property = “ListID”;
sfp.SimpleOperator = SimpleOperators.equals;
sfp.Value = new string[] { “1234” }; //put the desired listid here

RetrieveRequest listsub_request = new RetrieveRequest();
listsub_request.ObjectType = “ListSubscriber”;
listsub_request.Filter = sfp;

string[] ls_props = new string[] { “Status”, “SubscriberKey”, “ListID” };

listsub_request.Properties = ls_props;

APIObject[] results;
string status = “”;
string requestID = “”;
status = integrationFramework.Retrieve(listsub_request, out requestID, out results);

Finally, I’ve been able to Create (but not successfully delete yet, more details to come) DataExtensionObjects.  DataExtensionObjects correspond to individual records in a Data Extension table.  Here is some sample code on how to do this:

// Instantiate the API
String usermm = Session[“Curr_User_Name”].ToString();
String mmpass = Session[“Curr_User_Pass”].ToString();
PartnerAPIWse integrationFramework = CreatePartnerAPI(usermm, mmpass);

DataExtensionObject deo = new DataExtensionObject();
deo.Name = “Name”; //the name of the data extension
deo.CustomerKey = “Key”; //the external key of the data extension

deo.Properties = new APIProperty[numFields]; //numfields is the number of fields which exist in the data extension
for (int i = 0; i < numFields; i++)
{
deo.Properties[i] = new APIProperty();
deo.Properties[i].Name = “Name”; //enter the name of the field here
deo.Properties[i].Value = “Value”; // enter the desired value for the field here
}

string status = “”;
string requestID = “”;
CreateResult[] results = integrationFramework.Create(new CreateOptions(), new APIObject[] { deo }, out requestID, out status);

I’m going to start documenting a lot more soon, expect more frequent updates once my coding starts to slow up.

Thanks,

Michael

Advertisements

Timeout? Nope. TimeIN. July 1, 2008

Posted by mmoriar1 in Uncategorized.
add a comment

Since last time, I’ve completed retrieval of tracking events and minimal retrieval of triggered sends.  Some things I’ve encountered that I will talk about in this post:

  • The timeout setting on the PartnerAPIWse object is quite important and crucial to performing many API calls
  • The AJAX calendar extender is a very sleek way for the user to input dates
  • Retrieving tracking events is fairly straightforward, and when retrieving more than 2500 objects, you must check the call’s status for “MoreDataAvailable”
  • It’s much much quicker to retrieve tracking events in bulk and then filter on subscriber locally then to use multiple small retrieves and have the ExactTarget system filter on subscriber key

First I’ll talk about the timeout setting on the PartnerAPIWse object.  Before finding this setting, my API calls would timeout if I was operating on more than ~200 object.  The SOAP envelope was just too big and ExactTarget was dropping it after 90 seconds (the default setting).  After setting the timeout higher, however, I’ve been able to perform create/retrieve/update/delete calls on 12000+ subscriber objects.  So that’s the key to performing calls on a greater number of objects:

PartnerAPIWse integrationFramework = CreatePartnerAPI(usermm, mmpass);
integrationFramework.Timeout = 500000;

You’ve probably used a calendar extender before.  It’s what most travel websites use for their date input fields.  The one that is used in the ASP.NET AJAX Toolkit is pretty much the same as those.  It’s fully customizable, and it’s very easy to use and install.  Link: http://www.asp.net/learn/ajax-videos/video-124.aspx .

When retrieving more than 2500 records, you must keep inspecting the status of the API call to see if it is “MoreDataAvailable”.  Also, I’ve discovered that performing API calls on larger chunks of objects is more efficient than breaking the large group into smaller chunks.  For example, when I created 12000+ subscribers by breaking it up and creating 100 at a time, the total creation time was 28 minutes.  When I created 2500 (the limit) at a time, the creation time was 13 minutes.  These results were fairly repeatable.  Here is some sample code for retrieving all of the clickevents for a send (may total in the millions):

do
{
if (status == “MoreDataAvailable”) request.ContinueRequest = requestID;
status = partnerAPI.Retrieve(request, out requestID, out results);
if (results.Length > 0)
{
Console.WriteLine(“Found {0} ClickEvents”, results.Length.ToString());
blnFoundData = true;
createTSVFile(results);
}
else
{
Console.WriteLine(“No ClickEvents found. Status = ” + status);
}
} while (status == “MoreDataAvailable”); // Get additional results

If I remember anything else, I’ll post it here.

Until next time,

Mike

Running through the first finish line June 19, 2008

Posted by mmoriar1 in Uncategorized.
add a comment

Since last time, I’ve tentatively completed all of the Subscriber pages (yaaay) and can now start working on tracking events.  Here is a rough overview of this post’s content:

  • I changed the repeater on the single subscriber pages to be more dynamic based on the type of data being entered, so I will share my experiences with that
  • I got upsert working, and will give a few pointers on the syntax of that
  • I will explain what I have found out about deleting subscribers
  • I handled the results and statuses of the multiple subscriber actions by using a GridView in conjunction with a DataTable, I will go into more detail about that
  • A few more odds, ends, and annoyances

So.  The repeater on the single-subscriber pages is different than it was last time.  Firstly, there’s only one, not two.  I changed things around in that instead of having one repeater just for the text fields and the other repeater just for the attributes which have picklists, there’s now just one repeater which contains a text box, drop down list, and check box in each table row.  Doing this allows the repeater to select which control will be visible based on the datatype of the attribute or the presence of a picklist.  It also looks more professional and will be more flexible to different clients’ attribute pools.  Take a look:

OLD

Old

NEW:

New

As you can see, the new page looks more aligned and organized.  You might also notice the “upsert” checkbox.  Upsert does indeed work when that box is checked, but it’s hard to figure out how to implement it from the API guide alone (at least for me).  For those of you who don’t know what an “upsert” call does, it is basically creating the subscriber, but if the subscriber already exists then instead of throwing an error it will update the existing subscriber with the information from the create call.  To enable upserting on a create call, use the following code:

CreateOptions co = new CreateOptions();
co.SaveOptions = new SaveOption[1];
SaveOption so = new SaveOption();
so.PropertyName = “*”;
so.SaveAction = SaveAction.UpdateAdd;
co.SaveOptions[0] = so;

…and then pass the CreateOptions object to the call like so:

CreateResult[] results = integrationFramework.Create(co, new APIObject[] { new_sub }, out
requestID, out status);

Booya, there you go.  You’re now upserting.

Additionally, with regards to the single-subscriber pages, a workaround for the problem in the previous post of not being able to specify an EmailTypePreference upon creation is to simply perform an update call on the created subscriber making sure to specify the desired EmailTypePreference.  The update call correctly sets the preference.

Next, I experimented with the deletion of subscribers.  Deleting subscribers is a tricky subject.  This is probably because most clients would never want to delete a subscriber since then all data related to that subscriber is lost.  The more elegant and useful way would be to unsubscribe them from everything and then just let their email address slip away into oblivion.  What I’ve found is that deleting a subscriber doesn’t delete them right away.  If you try and re-create the deleted subscriber after the deletion, you’ll find that you cannot, and an error will indicate that the subscriber is still present on the all-subscriber list.  Hmm…interesting.  Even more interesting is that while you cannot retrieve or re-delete the “deleted” subscriber.  You can upsert/update using the deleted subscriber’s email address.  Upserting the deleted subscriber brings them back to life in the subscribers list.  I don’t know as of yet the full internals of a deletion through ExactTarget, but that is all that I have discovered so far.

Next I constructed the multiple-subscriber pages.  The construction of the pages was simple: the idea was to have a user upload a file of comma-separated subscribers to be retrieved/created/deleted (in the ExactTarget format with attribute headers) and then to operate on that file.  File upload was easy (consult http://www.asp.net/learn/videos/video-255.aspx) and one thing I did was persist the file location in the Session object so that users only had to upload the file once and could then do create/retrieve/delete using the same stored file without having to upload every time.

Multiple-Creates/Retrieves/Deletes are all performed using the same API call syntax as in the single-subscriber pages, just instead of passing the API call one Subscriber to operate on, an array of Subscribers or email addresses is passed.  The tricky part was controlling the timeouts of the requests.  As of now, no more than 200 subscribers can be operated on without the request timing out.  Technically the limit is 2500, but if you have that many subscribers to create, ftp the file over to ET and do it that way. 🙂

After the call is performed, the results are displayed to the user using a GridView bound to a DataTable.  A DataTable is a neato object which is basically a database accessed like a variable.  I consulted Google quite a bit when learning about them, so I suggest that you do too.  Once the API call pops back the array of output, I populate the DataTable with that data and then bind it to the GridView.  I then consulted these resources for enabling paging and sorting for the GridView (http://www.csharpfriends.com/Forums/ShowPost.aspx?PostID=36859 , http://forums.asp.net/t/956540.aspx?PageIndex=1), and these resources for hooking up the GridView with a DetailsView for a Master/Details layout (http://www.asp.net/learn/videos/video-08.aspx , http://www.asp.net/learn/videos/video-07.aspx).  See the result:

STATUS OF THE CALL IN A GRIDVIEW:

RETRIEVED SUBSCRIBERS IN A MASTER/DETAILS VIEW:

Some odds and ends I learned are as follows:

  • It took me a long time to debug the fact that my email addresses had whitespace and newlines (\n) on the end of them, so to fix this problem I used the handy .TrimEnd() function to get rid of all the whitespace following the string.
  • It’s very annoying of the API to not give good error messages.  Error code 2, for example, says “There was an unexpected error”.  Thanks!  What was the error?!  After debugging, it turns out the first attribute in my created subscriber’s attribute array was null, and ET didn’t like that very much.  Would it really be that hard to say “Attribute cannot be null”.  I’m done complaining, the product really is awesome. 🙂

That should be it for now unless I forgot to mention something.

Until next time,

Mike

I’ve got my helmet on June 11, 2008

Posted by mmoriar1 in Uncategorized.
add a comment

Man, they’ve got me running through walls over here. 🙂 Two issues have come up in the last week or so, both of which are unresolved and can be found here:

http://developers.exacttarget.com/WEB/forums/t/522.aspx
http://developers.exacttarget.com/WEB/forums/t/520.aspx

If those pages are inaccessible, the issues are:

  • When creating a subscriber using the WebServices API, setting their EmailTypePreference to HTML or Text will always result in the creation of a subscriber with a preference of HTML.
  • The Describe call does not grab custom preferences from a user’s account. The Retrieve call does, but if there is no previous subscriber to retrieve, then we cannot get custom preferences using the Web Services API.

Aside from that, I’ve made some significant progress on the Subscriber pages. This progress includes completion of basic functionality of the create, update, delete, and retrieve pages, and the completion of more advanced aesthetic functionality on the create page. To sum up this aesthetic functionality, I present the following:

  • The default values of each attribute are loaded into their proper fields
  • The required attributes are denoted as such by a ‘*’ next to their names
  • The hidden attributes aren’t displayed on the page
  • If an attribute has a picklist of items to choose from, then the items are loaded into a drop down list with the default item initially selected
  • Each field’s data requirements are shown to the user if there is no default value (for example, if the field was “Birthday” then the corresponding text box would be populated with “Enter a date (DD-MM-YYYY)”
  • The hidden source code panel was filled with a text area with some text in it just to see how it looked. Until I figure out how to style it, that’s how it will stay (the style seems to not want to work correctly).

The create page will be complete once the two issues are resolved and I implement checkboxes into the repeaters. Here you can see the create page:

My attention for today will be solely placed on making the update page look like the create page. The update page is functional, but it needs a little bit more TLC.

Two and a half major issues which cost me a bunch of time over the past few days included the following:

  • When populating a new (uncreated) subscriber’s attributes array, you have to instantiate each attribute.This won’t work (will throw exception):new_sub.Attributes[i].Name = name
    new_sub.Attributes[i].Value = value
    This is the fix:com.exacttarget.webservice.Attribute att = new com.exacttarget.webservice.Attribute();
    att.Name = name;
    att.Value = value;
    new_sub.Attributes[i] = att;
  • When trying to fill a drop down list control inside of a repeater, consult google 🙂 Here’s what resources I used:
    http://p2p.wrox.com/topic.asp?TOPIC_ID=7731
    http://p2p.wrox.com/topic.asp?TOPIC_ID=14940

    I found that the binding of the drop down list to the list of items that is to populate it has to be done in the repeater’s ItemDataBound event. Basically what I did was in the describe I found out which attributes had picklists and then created an arraylist of DictionaryEntries (key-value pairs) of these attributes. The key of each DictionaryEntry was the name of the attribute and the value was another arraylist filled with the picklist items. Then, in the repeater’s ItemDataBound event, I bound each drop down list created to an arraylist of picklist items.

    The ItemDataBound event was created as such:
    this.Repeater.DataSource = pick_list;
    this.Repeater.ItemDataBound += new RepeaterItemEventHandler(Repeater2_ItemDataBound);
    this.Repeater.DataBind();

    Then I created a function:
    private void Repeater2_ItemDataBound(object source, RepeaterItemEventArgs e)
    {
    if (e.Item.ItemType == ListItemType.AlternatingItem ||
    e.Item.ItemType == ListItemType.Item)
    {
    bind drop down list here…
    }
    }

    That took awhile to figure out.

  • The half issue which took awhile to debug was that in the update/retrieve calls, I was forgetting to set my SimpleFilterPart to the RetrieveRequest, which meant that it just kept retrieving ALL of the subscribers and displaying the first one that it got instead of filtering out all of the subscribers except for the one I was retrieving/updating.

So that’s pretty much it, if I think of anything else that came up I’ll post about it.

All for now,

Mike

Trials and tribulations of the ET WebServices API June 5, 2008

Posted by mmoriar1 in Uncategorized.
Tags: ,
7 comments

Since last time I have implemented a (seemingly working) login system, set up the divs for the first draft of the page layout, validated the email address field, made some magic happen with repeaters, created a cool AJAX collapsible panel, and set up the site map. Extremely long sentence aside, I’ve been pretty productive.

The login system was a tricky fellow to design, but implementation was fairly simple. While not elegant, it IS functional. The tricky parts were figuring out how to authenticate the username/password with the ExactTarget system, how to go about storing the username/password for use in subsequent API calls, and how to prevent unauthenticated users from accessing other parts of the site. These difficulties were overcome by:

  • Performing a dummy API call to the ET servers and seeing if any exceptions are thrown. If an exception was thrown by the authentication, that message is displayed neatly to the user under the two username/password text boxes. If no exception was thrown, then the user is redirected to a start page.
  • Storing the username/password in the Session object. Session is naturally encrypted and is persisted as the user travels from page to page, so it works quite well.
  • Storing a boolean variable in the Session pertaining to whether or not the user has authenticated with the ET servers or not. At the top of each “restricted” page (in the page load), the page checks to see whether the user is authenticated, and if not it redirects to the login page. Despite a few display quirks, it works well.

Setting up the divs for the page layout was nothing more than fiddling with CSS. Here is what the page looks like as of now (draft 1). Ignore the debugging background colors 🙂

Validating the email field was quite easy as well after watching a tutorial video on the subject. (http://asp.net/learn/videos/video-193.aspx) It just makes sure that something is in the box using the RequiredFieldValidator and makes sure that it follows the name@site.domain format using the RegularExpressionValidator.

The magic with repeaters was fun. The idea was to fetch a Subscriber’s attributes (First Name, Last Name, Email, etc.) on page load and then display the attributes in editable text boxes so that a user could change their value(s), and then the Subscriber could be updated with an API call to the ET server. After debugging a few frustrating errors in the API/C# (discussed below), I loaded the Subscriber’s attributes into an ArrayList which was then bound to a repeater (guide consulted: http://stanleycn.blogspot.com/2006/10/how-to-bind-arraylist-to-repeater.html). The repeater simply displayed each attribute’s name on a label followed by a text box pre-populated with the attribute’s value. The result can be seen in the above image. One other thing that had to be done was to cast the Container.DataItem object in the repeater template to an attribute object like so: ((com.exacttarget.webservice.Attribute)Container.DataItem).

The AJAX in the page so far is little more than an UpdateProgress control used to visually indicate when the client is requesting stuff from the server (resources used: http://asp.net/learn/ajax-videos/video-123.aspx for the guide and http://ajaxload.info/ for the awesome animated gif) and a CollapsiblePanelExtender in order to hide/show the source code for the API call (guide: http://asp.net/learn/ajax-videos/video-89.aspx)

One time consuming debug that I had to do stemmed from the fact that in the API Guide example code on page 40 there is the line:

PartnerAPI.Send send = results[cntr] as Send;

which I reformed to say:

PartnerAPI.Subscriber sub = results[cntr] as Subscriber;

Seems OK, right? All of the rest of my code was correct, but I kept getting the compilation error that “it could not convert com.exacttarget.webservice.APIObject to Subscriber via an internal conversion”. But it had worked the previous day in its current state…hmmm… Well, after trying stuff, it turns out that it needed to be declared as such:

com.exacttarget.webservice.Subscriber sub = results[cntr] as com.exacttarget.webservice.Subscriber

Yep, fully qualified and all. Still haven’t figured out totally why that is, but it works and is pretty low on my priority list to clean it up.

The other time consuming bug which hit me was that when trying to grab a subscriber’s attributes, I would include “Attributes” in the properties list as such:

sub_request.Properties = new string[] { “ID”, “EmailAddress”, “Attributes” };

This makes sense, as Attributes are a member of the Subscriber object and are returned as an array of the Attribute object. But upon performing the retrieve call, I would get an error indicating that “the format doesn’t match the form’s field view” (or something similar). This is because you aren’t supposed to include “Attributes” in the properties list, as they are grabbed by default. I stumbled upon this fix in the developers.exacttarget.com forum. Problem solved.

Well that’s all for now, until next time,

Mike

Master pages and the glory of ASP.NET May 30, 2008

Posted by mmoriar1 in Uncategorized.
Tags: ,
add a comment

So I’ve been watching tutorial after tutorial about ASP.NET and it’s finally starting to click (I think). Coming in to ExactTarget I had no prior experience with it and it seems to be a very powerful language/tool. I would definitely recommend http://www.asp.net for tutorials and videos–it’s a very well organized site and offers a ton of lessons to everyone from beginners to advanced users.

I’ve also been doing all of my work in Visual Studio. It’s kind of daunting to get comfortable with (I’m still trying), but it’s incredibly powerful. I really love using the design window in conjunction with the properties window and the style options to design the overall look of the site. One feature of the design window that has helped debug some positioning errors has been this guy (click for larger view):

It allows one to select and then style, etc. all of the containers/tags which hold the currently selected area of the design view. PS: Don’t judge the site in the image above :-P.

I also learned about master pages and site maps in ASP.NET. The concept is pretty straightforward: a master page is a page which controls the site-wide layout and usually contains navigation controls, header information, and footer information. The benefits of a master page are fairly obvious in that if you want to change the style of your website, you only have to change one page instead of copying/pasting changes to ALL pages. In this way, I try to think of master pages’ benefits in much the same way as CSS stylesheets’ benefits: instead of having each page’s styles inline in each page’s HTML code, one stylesheet can be used to style all of the site’s pages. Then when the stylesheet is changed, those changes are reflected in all pages which use the stylesheet.

That’s all I’ve got so far, up next should be my struggle with Login and Access controls. I’ve been reading up on the Session object and I need to read up a bit more on the Profile object, because I think that’s the direction that I’ll be going with it.

Until next time, adios,

Mike