#region Namespaces
using System;
using System.Data;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
#endregion
#region Class
/// <summary>
/// This is the class to which to add your code. Do not change the name, attributes, or parent
/// of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
/// <summary>
/// Authentication string for Web Service Authentication
/// </summary>
public string Authentication
{
get
{
byte[] bytes = Encoding.Default.GetBytes(String.Format("{0} : {1}", Variables.userName, Variables.password));
string _authentication = "Basic " + Convert.ToBase64String(bytes);
return _authentication;
}
}
#region Methods
public override void CreateNewOutputRows()
{
//Data flow interface
IDTSComponentMetaData100 compMetadata = this.ComponentMetaData;
SurveyProperties xmlResponse = null;
try
{
//Get questions from web service
SurveyProperties outPutQuestions = GetSurveyQuestions(Variables.surveyId, compMetadata, xmlResponse);
//We use a task factory to make sure we don't exit before all output buffers are done
Task[] tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(() => OutPutQuestions(outPutQuestions, compMetadata));
tasks[1] = Task.Factory.StartNew(() => OutPutUploadVariables(outPutQuestions.CustomerSampleUploadVariables, compMetadata));
//Wait for all tasks to finish
Task.WaitAll(tasks);
InfoComponent(String.Format("All data completed being sent to output buffers [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
catch (NullReferenceException)
{
InfoComponent(String.Format("No Questions Have Been Returned from Web Service [{0}]",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
catch (Exception e)
{
FailComponent(e.ToString(), compMetadata);
}
}
/// <summary>
/// Add questions and question choices to their output buffers
/// </summary>
/// <param name="outPutQuestions">The array of questions</param>
public void OutPutQuestions(SurveyProperties outPutQuestions, IDTSComponentMetaData100 compMetadata)
{
if (outPutQuestions != null)
{
InfoComponent(String.Format("Sending questions to the output buffer [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
//Output Questions
foreach (SurveyPropertiesQuestion quest in outPutQuestions.Questions)
{
QuestionsBuffer.AddRow();
QuestionsBuffer.ID = quest.id;
QuestionsBuffer.TYPEID = quest.typeID;
QuestionsBuffer.SEQUENCENUMBER = quest.seqNumber;
QuestionsBuffer.TEXT = quest.text;
QuestionsBuffer.PAGEORDERNUMBER = quest.pageOrderNo;
QuestionsBuffer.TYPETEXT = GetQuestionTypeDesc(quest.typeID);
if (quest.Choices != null)
{
//Ouput Question Choices
foreach (SurveyPropertiesQuestionChoice choice in quest.Choices)
{
QuestionChoicesBuffer.AddRow();
QuestionChoicesBuffer.QUESTIONID = quest.id;
QuestionChoicesBuffer.CHOICETYPE = choice.choiceType;
QuestionChoicesBuffer.ORDERNUMBER = choice.orderNo;
QuestionChoicesBuffer.RATING = choice.rating;
QuestionChoicesBuffer.TEXT = choice.text;
}
}
}
InfoComponent(String.Format("Finished sending questions to the output buffer [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
}
/// <summary>
/// Adds upload variables and their choices to their output buffers
/// </summary>
/// <param name="uVariables">The array of upload variables</param>
public void OutPutUploadVariables(SurveyPropertiesCSUV[] uVariables, IDTSComponentMetaData100 compMetadata)
{
if (uVariables != null)
{
InfoComponent(String.Format("Sending upload variables to the output buffer [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
foreach (SurveyPropertiesCSUV csuv in uVariables)
{
UploadVariablesBuffer.AddRow();
UploadVariablesBuffer.ID = csuv.id;
UploadVariablesBuffer.SEQUENCENUMBER = csuv.seqNumber;
UploadVariablesBuffer.TEXT = csuv.text;
UploadVariablesBuffer.TYPEID = csuv.typeID;
if (csuv.Choices != null)
{
foreach (SurveyPropertiesCSUVChoice choice in csuv.Choices)
{
UploadVariableChoicesBuffer.AddRow();
UploadVariableChoicesBuffer.ORDERNUMBER = choice.orderNo;
UploadVariableChoicesBuffer.TEXT = choice.text;
UploadVariableChoicesBuffer.CHOICETYPE = choice.choiceType;
UploadVariableChoicesBuffer.UPLOADVARIABLEID = csuv.id;
}
}
}
InfoComponent(String.Format("Finished sending upload variables to the output buffer [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
}
/// <summary>
/// Connects to the Market Tools Web Service
/// </summary>
/// <param name="uri">The uri to call</param>
/// <returns>The response object</returns>
private HttpWebResponse GetWebResponse(string uri)
{
HttpWebRequest httpWReq = (HttpWebRequest)HttpWebRequest.Create(uri);
httpWReq.Method = "GET";
httpWReq.ContentType = "text/xml";
httpWReq.Headers["Authorization"] = Authentication;
httpWReq.Timeout = Variables.timeout;
return (HttpWebResponse)httpWReq.GetResponse();
}
/// <summary>
/// Gets the Survey Questions with choices and Upload variable Questions.
/// </summary>
/// <param name="surveyID">The identifier of the survey</param>
public SurveyProperties GetSurveyQuestions(string surveyID, IDTSComponentMetaData100 compMetadata, SurveyProperties xmlResponse)
{
try
{
//Connect to web service
InfoComponent(String.Format("Connecting to web service [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
HttpWebResponse httpWResp = GetWebResponse(string.Format(Variables.surveyQuestionsUri, surveyID));
//Test the connection
if (httpWResp.StatusCode == HttpStatusCode.OK)
{
InfoComponent(String.Format("Connected to web service, getting response [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
Stream responseStream = httpWResp.GetResponseStream();
//Set xmlString using a stream reader
using (StreamReader reader = new StreamReader(responseStream))
{
//Deserialize our XML
InfoComponent(String.Format("Deserializing XML [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
XmlSerializer sr = new XmlSerializer(typeof(SurveyProperties));
xmlResponse = (SurveyProperties)sr.Deserialize(reader);
}
}
//Output connection error message
else
{
FailComponent(String.Format("Http Error! {0}:[{1}]", httpWResp.StatusCode.ToString(), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
}
catch (Exception ex)
{
FailComponent(String.Format(ex.Message+" [{0}]",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
return xmlResponse;
}
/// <summary>
/// Returns the description for the question type id passed
/// </summary>
/// <param name="typeID">The identifier for the question type</param>
private string GetQuestionTypeDesc(int typeID)
{
string typeDesc = "";
switch (typeID)
{
case 1:
{
typeDesc = "Text";
break;
}
case 2:
{
typeDesc = "Choose One";
break;
}
case 3:
{
typeDesc = "Choose All That Apply";
break;
}
case 4:
{
typeDesc = "Rating";
break;
}
case 5:
{
typeDesc = "Block of Text";
break;
}
case 6:
{
typeDesc = "Multi-Line Text";
break;
}
case 7:
{
typeDesc = "Bold Block of Text";
break;
}
case 8:
{
typeDesc = "Real Time Alert Multi-Line Text";
break;
}
case 9:
{
typeDesc = "Uploaded Variable Text";
break;
}
case 10:
{
typeDesc = "Uploaded Variable Choose One";
break;
}
case 11:
{
typeDesc = "Uploaded Variable Rating";
break;
}
case 12:
{
typeDesc = "Uploaded Variable Email";
break;
}
case 13:
{
typeDesc = "Uploaded Variable Date";
break;
}
case 14:
{
typeDesc = "Display Upload Variable";
break;
}
case 15:
{
typeDesc = "Upload Variable Number";
break;
}
case 16:
{
typeDesc = "Number";
break;
}
case 17:
{
typeDesc = "Upload Variable Code ID";
break;
}
}
return typeDesc;
}
/// <summary>Outputs an Error</summary>
/// <param name="errorMsg">The error message to send to the UI</param>
private void FailComponent(string errorMsg, IDTSComponentMetaData100 compMetadata)
{
bool fail = false;
compMetadata.FireError(1, compMetadata.Name, errorMsg, "", 0, out fail);
}
/// <summary>Outputs an information message/// </summary>
/// <param name="errorMsg">The no data returned message</param>
private void InfoComponent(string errorMsg, IDTSComponentMetaData100 compMetadata)
{
bool fail = false;
compMetadata.FireInformation(1, compMetadata.Name, errorMsg, "", 0, ref fail);
}
#endregion
}
#endregion
#region Question XML Classes
/// <remarks/>
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
[XmlRootAttribute(Namespace = "http://apiconnectservices.Customersat.com", IsNullable = false)]
public partial class SurveyProperties
{
[XmlArrayItemAttribute("CSUV", IsNullable = false)]
public SurveyPropertiesCSUV[] CustomerSampleUploadVariables;
[XmlArrayItemAttribute("Question", IsNullable = false)]
public SurveyPropertiesQuestion[] Questions;
}
/// <remarks/>
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class SurveyPropertiesCSUV
{
[XmlElement("ID")]
public int id;
[XmlElement("TypeID")]
public byte typeID;
[XmlElement("SeqNumber")]
public byte seqNumber;
[XmlElement("Text")]
public string text;
[XmlArrayItemAttribute("Choice", IsNullable = false)]
public SurveyPropertiesCSUVChoice[] Choices;
}
/// <remarks/>
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class SurveyPropertiesQuestion
{
[XmlElement("ID")]
public int id;
[XmlElement("TypeID")]
public byte typeID;
[XmlElement("PageOrderNo")]
public byte pageOrderNo;
[XmlElement("SeqNumber")]
public byte seqNumber;
[XmlElement("Text")]
public string text;
[XmlArrayItemAttribute("Choice", IsNullable = false)]
public SurveyPropertiesQuestionChoice[] Choices;
}
/// <remarks/>
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class SurveyPropertiesQuestionChoice
{
[XmlElement("OrderNo")]
public byte orderNo;
[XmlElement("Text")]
public string text;
[XmlElement("Rating")]
public string rating;
[XmlElement("ChoiceType")]
public byte choiceType;
[XmlIgnoreAttribute()]
public bool choiceTypeSpecified;
}
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class SurveyPropertiesCSUVChoice
{
[XmlElement("OrderNo")]
public byte orderNo;
[XmlElement("Text")]
public string text;
[XmlElement("ChoiceType")]
public byte choiceType;
}
#endregion
SurveyProperties outPutQuestions = GetSurveyQuestions(Variables.surveyId, compMetadata, xmlResponse); which will call our web service, deserialize our XML into classes and return an object that has all of our data. Since we have 2 sets of outputs, that will be executing in different scopes, I created a TaskFactory for these to run in. The reason for this is that because the outputs for these 2 pieces of data don't operate within the same scope, as soon as one finishes it will call SetEndOfRowSet(). According to the documentation on this "Data flow components call this method when they have finished adding rows to an Output PipelineBuffer. Calling SetEndOfRowset notifies the data flow engine that a component has finished adding rows to the PipelineBuffer. The SetEndOfRowset method then sets the value of the EndOfRowsetproperty to true on the final buffer." This means that as soon as one of the output buffers calls this method, the script component will not wait for the other output to run/finish before stopping. Thus, if you called the methods to populate these outputs(OutPutQuestions() and OutPutUploadVariables) synchronously you would only get data out of the first output method called. If you called these asynchronously, it would turn into a race for whatever output finished first to call SetEndOfRowset() and that output would get data while the other wouldn't. To solve this, we add both of these outputs to tasks, have these tasks start within the context of a task factory and runs them asynchronously. We call the WaitAll() method of the task factory to force all tasks to finish before we break.
Let's save, build and run this sequence container to see what our question data looks like in data viewers:
|
Figure 8. Question Data Viewers |
We can track the execution on the progress tab and see that both of these outputs are running in parallel:
|
Figure 9. Questions Progress |
With questions done, we can move onto the responses data flow. This will output the answers to the survey questions asked of customers. The data flow for survey responses is going to look like this:
|
Figure 10. Responses Data Flow |
To start this one, drag a script component onto the design surface of the data flow. When prompted, select source:
|
Figure 11. Script Component Type |
On the script screen make sure to add password, responseEndDate, responseStartDate, surveyId, surveyResponseUri, userName and timeout as ReadOnlyVariables, User::password,User::responseEndDate,User::responseStartDate,User::surveyId,User::surveyResponsesUri,User::userName,User::timeout
|
Figure 12. Script Component Script Screen |
Next, we go to the Inputs and Outputs screen to create an output for our survey responses:
|
Figure 13. Script Component Inputs and Outputs Screen for Responses |
We are going to derive these outputs from the XML returned from the survey responses uri. Responses are returned in an XML format that matches this:
<FeedbackResponse xmlns="http://apiconnectservices.Customersat.com">
<SurveyProperties ID="12345">
<FeedbackReportFromDate>2013-05-15T00:00:00</FeedbackReportFromDate>
<FeedbackReportToDate>3000-01-01T00:00:00</FeedbackReportToDate>
</SurveyProperties>
<Feedback>
<Page IsLastPage="true">1</Page>
<Response ID="42835517" ResponseDateTime="2013-05-15T03:47:43.63">
<Value ID="134587">samplecustomeremail@sampleemail.org</Value>
<Value ID="133861">123476</Value>
<Value ID="133862" ChoiceOrderNo="5972">1102400</Value>
<Value ID="134588" ChoiceOrderNo="6476">Customer A</Value>
<Value ID="134589">2013-05-10 00:00:00</Value>
<Value ID="134663">10:52:12</Value>
<Value ID="133864">
</Value>
<Value ID="134664">
</Value>
<Value ID="135833" ChoiceOrderNo="10">Dispatch</Value>
<Value ID="134591" ChoiceOrderNo="9056">0000000000</Value>
<Value ID="134592" ChoiceOrderNo="9">[blank]</Value>
<Value ID="134593" ChoiceOrderNo="164">Product A</Value>
<Value ID="134594">Bob</Value>
<Value ID="134595">Smith</Value>
<Value ID="134596" ChoiceOrderNo="35">Manager</Value>
<Value ID="134597">
</Value>
<Value ID="134646">
</Value>
<Value ID="134647">7ST</Value>
<Value ID="134648">PHILADELPHIA</Value>
<Value ID="134649" ChoiceOrderNo="26">PA</Value>
<Value ID="134961">19104</Value>
<Value ID="135832" ChoiceOrderNo="90">Eastern Region</Value>
<Value ID="134651" ChoiceOrderNo="31">US</Value>
<Value ID="134652" ChoiceOrderNo="6">[blank]</Value>
<Value ID="134653" ChoiceOrderNo="16">[blank]</Value>
<Value ID="134654" ChoiceOrderNo="18">[blank]</Value>
<Value ID="134655" ChoiceOrderNo="9">[blank]</Value>
<Value ID="134656" ChoiceOrderNo="185">[blank]</Value>
<Value ID="134657" ChoiceOrderNo="3551">[blank]</Value>
<Value ID="134658" ChoiceOrderNo="3">[blank]</Value>
<Value ID="134659" ChoiceOrderNo="6">Medium</Value>
<Value ID="134660" ChoiceOrderNo="8">[blank]</Value>
<Value ID="134661" ChoiceOrderNo="305034">8000605204</Value>
<Value ID="178743" ChoiceOrderNo="874">wneal</Value>
<Value ID="179384">
</Value>
<Value ID="179385">
</Value>
<Value ID="179386">
</Value>
<Value ID="179387">
</Value>
<Value ID="179388">
</Value>
<Value ID="179389">
</Value>
<Value ID="179390">
</Value>
<Value ID="179391">
</Value>
<Value ID="179392">
</Value>
<Value ID="179393">
</Value>
<Value ID="179394">
</Value>
<Value ID="179395">
</Value>
<Value ID="262650" ChoiceOrderNo="1">Y</Value>
<Value ID="131902" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131903" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131904" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131906" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131907" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131909" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131910" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131911" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131912" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="257799" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="257806" ChoiceOrderNo="2">1 hour to less than 4</Value>
<Value ID="132068" ChoiceOrderNo="3">Yes, more than once</Value>
<Value ID="132069" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="145548" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="145549" ChoiceOrderNo="1" Rating="4">Definitely would</Value>
<Value ID="145550" ChoiceOrderNo="1" Rating="4">Definitely would</Value>
<Value ID="131916" ChoiceOrderNo="10" Rating="10">10</Value>
<Value ID="131894" ChoiceOrderNo="1">Phone</Value>
<Value ID="132075" ChoiceOrderNo="6">Manager</Value>>
</Response>
</Feedback>
</FeedbackResponse>
Just like with the question XML, we need to create classes to deserialize the response XML into. You can do the same as the question XML and copy and paste into a .net 4.5 C# project by pasting XML as classes. Now that we have our XML classes, we can include them in our code. Go back to the script screen, click on the edit script button and we can start coding.
#region Namespaces
using System;
using System.Data;
using System.IO;
using System.Net;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
#endregion
/// <summary>
/// This is the class to which to add your code. Do not change the name, attributes, or parent
/// of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
/// <summary>
/// Authentication string for Web Service Authentication
/// </summary>
public string Authentication
{
get
{
byte[] bytes = Encoding.Default.GetBytes(String.Format("{0} : {1}", Variables.userName, Variables.password));
string _authentication = "Basic " + Convert.ToBase64String(bytes);
return _authentication;
}
}
/// <summary>
/// Outputs rows to the result output buffer
/// </summary>
public override void CreateNewOutputRows()
{
//Data flow interface
IDTSComponentMetaData100 compMetadata = this.ComponentMetaData;
FeedbackResponse xmlResponse = null;
int pageNumber=1;
FeedbackResponse outPutResults;
try
{
do
{
outPutResults = GetSurveyResults(compMetadata, xmlResponse, pageNumber);
if (outPutResults.Feedback.Response != null)
{
InfoComponent(String.Format("Sending rows to the results output buffer for page {0} [{1}]", pageNumber, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
//Output Results
foreach (FeedbackResponseFeedbackResponse fb in outPutResults.Feedback.Response)
{
if (fb.Value != null)
{
foreach (FeedbackResponseFeedbackResponseValue value in fb.Value)
{
ResultsBuffer.AddRow();
ResultsBuffer.SURVEYID = Convert.ToInt32(Variables.surveyId);
ResultsBuffer.RESPONSEID = fb.ID;
ResultsBuffer.RESPONSEDATETIME = fb.ResponseDateTime;
ResultsBuffer.QUESTIONID = value.ID;
ResultsBuffer.CHOICEORDERNUMBER = value.ChoiceOrderNo;
ResultsBuffer.RATING = value.Rating;
if (value.Value != null)
{
ResultsBuffer.VALUE.AddBlobData(Encoding.ASCII.GetBytes(value.Value));
}
else
{
ResultsBuffer.VALUE.SetNull();
}
}
}
}
InfoComponent(String.Format("Finished sending rows to the results output buffer for page {0} [{1}]", pageNumber, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
else
{
throw new NullReferenceException();
}
pageNumber++;
//Loop until we get the last page of returns or we got no responses
} while (outPutResults.Feedback.Response != null && !outPutResults.Feedback.Page.IsLastPage);
InfoComponent(String.Format("Finished sending all rows to the results output buffer [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
catch (NullReferenceException)
{
InfoComponent(String.Format("No Results Have Been Returned from Web Service {0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
}
/// <summary>
/// Connects to the Market Tools Web Service
/// </summary>
/// <param name="uri">The uri to call</param>
/// <returns>The response object</returns>
private HttpWebResponse GetWebResponse(string uri)
{
HttpWebRequest httpWReq = (HttpWebRequest)HttpWebRequest.Create(uri);
httpWReq.Method = "GET";
httpWReq.ContentType = "text/xml";
httpWReq.Headers["Authorization"] = Authentication;
httpWReq.Timeout = Variables.timeout;
return (HttpWebResponse)httpWReq.GetResponse();
}
/// <summary>
/// Gets the survey results
/// </summary>
/// <param name="surveyID">The identifier of the survey</param>
public FeedbackResponse GetSurveyResults(IDTSComponentMetaData100 compMetadata, FeedbackResponse xmlResponse, int pageNumber)
{
try
{
string uri = string.Format(Variables.surveyResponsesUri, Variables.surveyId, "&", Variables.responseStartDate.ToShortDateString(), "&", Variables.responseEndDate.ToShortDateString(), "&", pageNumber.ToString());
//Connect to web service
InfoComponent(String.Format("Connecting to web service [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
HttpWebResponse httpWResp = GetWebResponse(uri);
//Test the connection
if (httpWResp.StatusCode == HttpStatusCode.OK)
{
InfoComponent(String.Format("Connected to web service, getting response for Page {0} [{1}]",pageNumber, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
Stream responseStream = httpWResp.GetResponseStream();
//Set xmlString using a stream reader
using (StreamReader reader = new StreamReader(responseStream))
{
//Deserialize our XML
InfoComponent(String.Format("Deserializing XML [{0}]", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
XmlSerializer sr = new XmlSerializer(typeof(FeedbackResponse));
xmlResponse = (FeedbackResponse)sr.Deserialize(reader);
}
}
//Output connection error message
else
{
FailComponent(String.Format("Http Error! {0}:[{1}]", httpWResp.StatusCode.ToString(), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
}
catch (Exception ex)
{
FailComponent(String.Format(ex.Message+" [{0}]",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")), compMetadata);
}
return xmlResponse;
}
/// <summary>Outputs an error message</summary>
/// <param name="errorMsg">The error message to send to the UI</param>
private void FailComponent(string errorMsg, IDTSComponentMetaData100 compMetadata)
{
bool fail = false;
compMetadata.FireError(1, compMetadata.Name, errorMsg, "", 0, out fail);
}
/// <summary>Outputs an information message</summary>
/// <param name="errorMsg">The information message</param>
private void InfoComponent(string errorMsg, IDTSComponentMetaData100 compMetadata)
{
bool fail = false;
compMetadata.FireInformation(1, compMetadata.Name, errorMsg, "", 0, ref fail);
}
}
#region Result XML Classes
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
[XmlRootAttribute(Namespace = "http://apiconnectservices.Customersat.com", IsNullable = false)]
public partial class FeedbackResponse
{
[XmlElement("FeedbackResponse")]
public FeedbackResponseSurveyProperties SurveyProperties;
[XmlElement("Feedback")]
public FeedbackResponseFeedback Feedback;
}
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class FeedbackResponseSurveyProperties
{
[XmlElement("FeedbackReportFromDate")]
public System.DateTime FeedbackReportFromDate;
[XmlElement("FeedbackReportToDate")]
public System.DateTime FeedbackReportToDate;
[XmlAttribute("ID")]
public ushort ID;
}
/// <remarks/>
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class FeedbackResponseFeedback
{
[XmlElement("Page")]
public FeedbackResponseFeedbackPage Page;
[XmlElementAttribute("Response")]
public FeedbackResponseFeedbackResponse[] Response;
}
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class FeedbackResponseFeedbackPage
{
[XmlAttribute("IsLastPage")]
public bool IsLastPage;
[XmlTextAttribute()]
public byte Value;
}
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class FeedbackResponseFeedbackResponse
{
[XmlElementAttribute("Value")]
public FeedbackResponseFeedbackResponseValue[] Value;
[XmlAttribute("ID")]
public int ID;
[XmlAttribute("ResponseDateTime")]
public System.DateTime ResponseDateTime;
}
/// <remarks/>
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://apiconnectservices.Customersat.com")]
public partial class FeedbackResponseFeedbackResponseValue
{
[XmlAttribute("ID")]
public int ID;
[XmlAttribute("ChoiceOrderNo")]
public int ChoiceOrderNo;
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool ChoiceOrderNoSpecified;
[XmlAttribute("Rating")]
public string Rating;
[XmlTextAttribute()]
public string Value;
}
#endregion
This script component only has one output, so we don't have to worry about any multi-threading for this one. We call GetSurveyResults(compMetadata, xmlResponse, pageNumber); which will call the web service, desrialize our XML feed of responses and return us an object with our data in it. Then we loop through the FeedbackResponseFeedbackResponseValue array and output survey response records to the output buffer:
foreach (FeedbackResponseFeedbackResponseValue value in fb.Value)
{
ResultsBuffer.AddRow();
ResultsBuffer.SURVEYID = Convert.ToInt32(Variables.surveyId);
ResultsBuffer.RESPONSEID = fb.ID;
ResultsBuffer.RESPONSEDATETIME = fb.ResponseDateTime;
ResultsBuffer.QUESTIONID = value.ID;
ResultsBuffer.CHOICEORDERNUMBER = value.ChoiceOrderNo;
ResultsBuffer.RATING = value.Rating;
....
This is all within a do while loop. Each response comes in a "page" of 200 survey responses. The response will tell you in an attribute on the Page element (IsLastPage) whether or not all the responses have been retrieved. While this is false we increment our pageNumber variable, and grab the next page of data from the web service
Let's save, build and execute the response sequence container and see what we get in our data viewer:
|
Figure 14. Survey Responses Data Viewer |
You can also keep track of the progress on the progress tab:
|
Figure 15. Responses Progress Tab |
When all of the data is at it's database destinations, we can model it as such:
|
Figure 16. Destination Database Schema |
I had to create a supertype for this(QUESTIONS_VARIABLES), since both the primary key of QUESTIONS and the primary key of UPLOAD_VARIABLES both link to the foreign key of QUESTION_ID in RESPONSES (since a question can either be a question asked or an upload variable value returned with the survey response).