Add subscriber to Mailchimp through API 3.0 - c#

I'm attempting to integrate version 3 of Mailchimp's API to add a subscriber to one of my mailing lists. The code below is what I have thus far, and my intention is to call it in my contact form method when the user fills out their email to subscribe. In theory it should get this email stored in emailAddress and POST it to MailChimp, but theorys are not practical. Below is my current method of POST data:
private string InsertIntoMailChimpGeneralList(string emailAddress)
{
var apiKey = ConfigurationManager.AppSettings["MailChimpAPIKeyGeneral"];
var dataCenter = ConfigurationManager.AppSettings["MailChimpDataCenterIDGeneral"];
var listId = ConfigurationManager.AppSettings["mailChimpListIDGeneral"];
var email_address = emailAddress;
var status = "subscribed";
using (var wc = new System.Net.WebClient())
{
// Data to be posted to add email address to list
var data = new { email_address, status };
// Serialize to JSON using Json.Net
var json = JsonConvert.SerializeObject(data);
// Base URL to MailChimp API
string apiUrl = "https://" + dataCenter + ".api.mailchimp.com/3.0/";
// Construct URL to API endpoint being used
var url = string.Concat(apiUrl, "lists/", listId, "/members?");
// Set content type
wc.Headers.Add("Content-Type", "application/json");
// Generate authorization header
string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(":" + apiKey));
// Set authorization header
wc.Headers[HttpRequestHeader.Authorization] = string.Format("Basic {0}", credentials);
// Post and get JSON response
string sendUrl = wc.UploadString(url, json);
return sendUrl;
}
}
In my contact form I have a check where I want the email address added in the contact form (emailAddress is the variable used here) to post that data to the list, when the form is submitted:
if (ConfigurationManager.AppSettings["UseMailChimpIntegration"].ToString().ToLower().Trim() == "true")
{
InsertIntoMailChimpGeneralList(emailAddress);
}
I feel I've implemented this wrong. I was able to get it working on v2 but upgrading to v3 has left me clueless at this point. My contact form runs fine and stored my values in my local database, but does not POST that data through to mailchimp.
I've triple checked my API/datacenter values, and would appreciate some assistance.

Related

ASP NET Core Twitter OAuth Request Token Issues

Background
I have a back end application that has a Twitter app setup and I can query and pull user tweet/post data. This is great, however, right now on the front end I don't have full Twitter integration setup. What I mean by this is that on the front end the user can enter any Twitter username and I want to know for sure that the Twitter username entered actually belongs to the user. With a Twitter application key you can pull public Twitter data for any twitter account which works well for large scale data ingestion and in my case proof of concept kind of work. At the point I am now, I need to have the assumption enforced in the back end that the data being analyzed for a particular Twitter screen name is also owned by the user of the account on my web application.
The proposed Twitter Solution
Here is a bunch of reference documentation I have been trying to follow.
https://developer.twitter.com/en/docs/basics/authentication/guides/log-in-with-twitter
https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token
https://oauth.net/core/1.0/#anchor9
https://oauth.net/core/1.0/#auth_step1
I have been trying to follow this and I have had different permutations to the code posted below (one without the callback URL as parameters, one with etc.) but at this point, not very different. I have not had any success and it's been more than a couple of days, which is killing me.
The code
This is my attempt to follow the OAuth specification proposed above in the documentation. Note that this is ASP.NET Core 2.2 + code. Also, this is the code for just Step 1 in the Twitter guide for OAuth authentication and authorization.
public async Task<string> GetUserOAuthRequestToken()
{
int timestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(timestamp.ToString()));
string consumerKey = twitterConfiguration.ConsumerKey;
string oAuthCallback = twitterConfiguration.OAuthCallback;
string requestString =
twitterConfiguration.EndpointUrl +
OAuthRequestTokenRoute;
string parameterString =
$"oauth_callback={WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}&" +
$"oauth_consumer_key={twitterConfiguration.ConsumerKey}&" +
$"oauth_nonce={nonce}&" +
$"oauth_signature_method=HMAC_SHA1&" +
$"oauth_timestamp={timestamp}" +
$"oauth_version=1.0";
string signatureBaseString =
"POST&" +
WebUtility.UrlEncode(requestString) +
"&" +
WebUtility.UrlEncode(parameterString);
string signingKey =
twitterConfiguration.ConsumerSecret +
"&" + twitterConfiguration.AccessTokenSecret;
byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
byte[] signingKeyBytes = Encoding.ASCII.GetBytes(signingKey);
HMACSHA1 hmacSha1 = new HMACSHA1(signingKeyBytes);
byte[] signature = hmacSha1.ComputeHash(signatureBaseStringBytes);
string authenticationHeaderValue =
$"oauth_nonce=\"{nonce}\", " +
$"oauth_callback=\"{WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}\", " +
$"oauth_signature_method=\"HMAC_SHA1\", " +
$"oauth_timestamp=\"{timestamp}\", " +
$"oauth_consumer_key=\"{twitterConfiguration.ConsumerKey}\", " +
$"oauth_signature=\"{Convert.ToBase64String(signature)}\", " +
$"oauth_version=\"1.0\"";
HttpRequestMessage request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(
baseUri: new Uri(twitterConfiguration.EndpointUrl),
relativeUri: OAuthRequestTokenRoute);
request.Content = new FormUrlEncodedContent(
new Dictionary<string, string>() {
{ "oauth_callback", twitterConfiguration.OAuthCallback }
});
request.Headers.Authorization = new AuthenticationHeaderValue("OAuth",
authenticationHeaderValue);
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode)
{
return await httpResponseMessage.Content.ReadAsStringAsync();
}
else
{
return null;
}
}
Notes
I have tried to remove the callback URL from the parameters as well and that didn't work. I have tried all sort of slightly different permutations (urlencoded my signature, added the callback URL in the query string, removed it etc), but I have lost track at this point the one's I have tried and haven't (encodings, quotes etc.).
Ignore the fact that I am not serializing the response into a model yet as the goal is to first hit a success status code!
I have an integration test setup for this method as well and I keep getting 400 Bad Request with no additional information (which makes sense), but is absolutely not helping with debugging.
[Fact]
public async Task TwitterHttpClientTests_GetOAuthRequestToken_GetsToken()
{
var result = await twitterHttpClient.GetUserOAuthRequestToken();
Assert.NotNull(result);
}
As an aside I had some other questions as well:
Is there a way to verify a user's Twitter account without going
through the OAuth flow? The reason I ask this is because getting
through OAuth flow is proving to be difficult
Is it safe to do the first step of the Twitter login workflow on the back end and return the response to the front end? The response
would carry a sensitive token and token secret. (If I were to answer
this myself I would say you have to do it this way otherwise you
would have to hard code app secrets into front end configuration
which is worse). I ask this because this has been on my conscious
since I have started this and I'm worried a bit.
Is there an OAuth helper library for C# ASP.NET Core that can make this easier?
I solved this by writing unit tests and working through the Twitter documentation on Creating A Signature. Since that example provides keys and results, it's possible to verify that your code is correct.
Since you asked about libraries - I wrote LINQ to Twitter with the hope of helping others like myself with this difficult task.
In addition to to signature, the page navigation can be challenging as your code works through the OAuth flow. Please review the Twitter documentation on Obtaining user access tokens to understand this better. I've also documented this in the LINQ to Twitter Wiki on Securing your Applications. Here's how this will work with LINQ to Twitter:
First, I have an OAuthController with a Begin action to redirect a user to for kicking off the authentication process:
public async Task<ActionResult> Begin()
{
//var auth = new MvcSignInAuthorizer
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore(HttpContext.Session)
{
ConsumerKey = configuration["Twitter:ConsumerKey"],
ConsumerSecret = configuration["Twitter:ConsumerSecret"]
}
};
string twitterCallbackUrl = Request.GetDisplayUrl().Replace("Begin", "Complete");
return await auth.BeginAuthorizationAsync(new Uri(twitterCallbackUrl));
}
Notice that it's using an MvcSignInAuthorizer, passing in credentials via the CredentialStore property. If you were using your own raw code, you would be setting up the HTTP request with the Authorization header.
Next, notice that I'm modifying the current URL so that it will reference the same controller, but with the Complete endpoint. That is the oauth_callback that gets sent to Twitter authorization.
That process redirects the user to the Twitter web site, they authorize your app, and then it uses the oauth_callback to redirect the user back to your site. Here's how you handle that:
public async Task<ActionResult> Complete()
{
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore(HttpContext.Session)
};
await auth.CompleteAuthorizeAsync(new Uri(Request.GetDisplayUrl()));
// This is how you access credentials after authorization.
// The oauthToken and oauthTokenSecret do not expire.
// You can use the userID to associate the credentials with the user.
// You can save credentials any way you want - database,
// isolated storage, etc. - it's up to you.
// You can retrieve and load all 4 credentials on subsequent
// queries to avoid the need to re-authorize.
// When you've loaded all 4 credentials, LINQ to Twitter will let
// you make queries without re-authorizing.
//
//var credentials = auth.CredentialStore;
//string oauthToken = credentials.OAuthToken;
//string oauthTokenSecret = credentials.OAuthTokenSecret;
//string screenName = credentials.ScreenName;
//ulong userID = credentials.UserID;
//
return RedirectToAction("Index", "Home");
}
Again, you can see that I'm using MvcAuthorizer and completing the request. After completing the request, you'll be able to pull out the oauth_token and oauth_token_secret, as well as screen_name and user_id. You can save these artifacts and re-use them for all subsequent activity by this user, making their experience better because they don't have to log in every time you need to make a request.
On your question about verification, there is a Verify Credentials endpoint.
LINQ to Twitter has an ASP.NET Core Sample, API Samples with 100% API coverate, and full documentation on the Wiki if you want to learn more.
After hours and hours of going through the documentation I found the answer out. Turns out I missed some small details from the guides.
When making a request to oauth/request_token, when you sign the
request, you don't use the access token secret (for this specific request). Also, see the "Getting Signing Key" section of the signing a request guide and read the last few paragraphs. Therefore the signing key
does not have the access token secret
You must UrlEncode every single key and value. You must UrlEncode the authorization header as well.
I will post the updated code for you all here in case you need this in C#. Note that this code is not clean. You should separate OAuth functionality into some other class. This was my attempt to just get it to work.
public async Task<string> GetUserOAuthRequestToken()
{
int timestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(timestamp.ToString()));
string consumerKey = twitterConfiguration.ConsumerKey;
string oAuthCallback = twitterConfiguration.OAuthCallback;
string requestString =
twitterConfiguration.EndpointUrl +
OAuthRequestTokenRoute;
string parameterString =
$"oauth_callback={WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}&" +
$"oauth_consumer_key={WebUtility.UrlEncode(twitterConfiguration.ConsumerKey)}&" +
$"oauth_nonce={WebUtility.UrlEncode(nonce)}&" +
$"oauth_signature_method={WebUtility.UrlEncode(OAuthSigningAlgorithm)}&" +
$"oauth_timestamp={WebUtility.UrlEncode(timestamp.ToString())}&" +
$"oauth_version={WebUtility.UrlEncode("1.0")}";
string signatureBaseString =
"POST&" +
WebUtility.UrlEncode(requestString) +
"&" +
WebUtility.UrlEncode(parameterString);
string signingKey =
WebUtility.UrlEncode(twitterConfiguration.ConsumerSecret) +
"&";
byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
byte[] signingKeyBytes = Encoding.ASCII.GetBytes(signingKey);
HMACSHA1 hmacSha1 = new HMACSHA1(signingKeyBytes);
byte[] signature = hmacSha1.ComputeHash(signatureBaseStringBytes);
string base64Signature = Convert.ToBase64String(signature);
string authenticationHeaderValue =
$"oauth_nonce=\"{WebUtility.UrlEncode(nonce)}\", " +
$"oauth_callback=\"{WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}\", " +
$"oauth_signature_method=\"{WebUtility.UrlEncode(OAuthSigningAlgorithm)}\", " +
$"oauth_timestamp=\"{WebUtility.UrlEncode(timestamp.ToString())}\", " +
$"oauth_consumer_key=\"{WebUtility.UrlEncode(twitterConfiguration.ConsumerKey)}\", " +
$"oauth_signature=\"{WebUtility.UrlEncode(base64Signature)}\", " +
$"oauth_version=\"{WebUtility.UrlEncode("1.0")}\"";
HttpRequestMessage request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(
baseUri: new Uri(twitterConfiguration.EndpointUrl),
relativeUri: OAuthRequestTokenRoute);
request.Headers.Authorization = new AuthenticationHeaderValue("OAuth",
authenticationHeaderValue);
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode)
{
string response = await httpResponseMessage.Content.ReadAsStringAsync();
return response;
}
else
{
return null;
}
}

Outbound Call using C# Create but call goes without ring tone and disconnected

I use CallResource.Create();
But it makes a call without ring back tone and disconnected and I need pure C# code to make outbound call form browser due to browser compatibility of twilio.js and I'm confusing regarding https URI what to specify there because create already make calls on "To" number.
public ActionResult MakeCall(string number)
{
string AccountSid = "********";
string AuthToken = "*********";
TwilioClient.Init(AccountSid, AuthToken);
var to = new PhoneNumber(number);
var from = new PhoneNumber("********");
ServicePointManager.SecurityProtocol =(SecurityProtocolType)3072;
if (number!= null) {
var call = CallResource.Create(to: to, from: from, m
achineDetection: "Enable",timeout:600, url: new Uri("https://handler.twilio.com/"));
ViewBag.Message = call.Sid;
}
return View("Index");
}

DocuSign API and DocumentPDFs docPDFs = apiService.RequestDocumentPDFs(envelopeID);

I am using the code below to connect to DocuSign API.
WHAT AM I doing wrong, I keep getting Username and Password not correct when they are!
String auth = "<DocuSignCredentials><Username>john.connolly#lechase.com</Username><Password>password</Password><IntegratorKey>20be051c-4c25-46c1-b0f1-1f10575a2e40</IntegratorKey></DocuSignCredentials>";
DSAPIServiceSoapClient client = new DSAPIServiceSoapClient("DSAPIServiceSoap");
using (System.ServiceModel.OperationContextScope scope = new System.ServiceModel.OperationContextScope(client.InnerChannel))
{
System.ServiceModel.Channels.HttpRequestMessageProperty httpRequestProperty = new System.ServiceModel.Channels.HttpRequestMessageProperty();
httpRequestProperty.Headers.Add("X-DocuSign-Authentication", auth);
System.ServiceModel.OperationContext.Current.OutgoingMessageProperties[System.ServiceModel.Channels.HttpRequestMessageProperty.Name] = httpRequestProperty;
EnvelopeStatus status = client.RequestStatusEx("12d46951-1f1c-48cd-9a28-e51685d67ccd");
Console.Out.WriteLine("Subject: " + status.Subject);
}
Since you use the (Legacy Header Authentication uses the X-DocuSign-Authentication header):
Use the Authentication: login method
to retrieve the account number and the baseUrl for the account.
The url for the login method is www.docusign.net for production and
demo.docusign.net for the developer sandbox. The baseUrl field is
part of the loginAccount object. See the docs and the loginAccount
object
The baseUrl for the selected account, in production, will start with na1, na2, na3, eu1, or something else. Use the baseUrl that is
returned to create the basePath (see the next step.) Use the
basePath for all of your subsequent API calls.
As returned by login method, the baseUrl includes the API version and account id. Split the string to obtain the basePath, just the
server name and api name. Eg, you will receive
https://na1.docusign.net/restapi/v2/accounts/123123123. You want
just https://na1.docusign.net/restapi
Instantiate the SDK using the basePath. Eg ApiClient apiClient = new ApiClient(basePath);
Set the authentication header as shown in the examples by using Configuration.Default.AddDefaultHeader Ref.
Sample Code: Try a verbatim string for your auth string.
string auth = #"<DocuSignCredentials>
<Username>john.connolly#lechase.com</Username>
<Password>S3cre+p455w0Rd</Password>
<IntegratorKey>20be051c-4c25-46c1-b0f1-1f10575a2e40</IntegratorKey>
</DocuSignCredentials>";
DSAPIServiceSoapClient apiService = new DSAPIServiceSoapClient();
using (var scope = new System.ServiceModel.OperationContextScope(apiService.InnerChannel))
{
var httpRequestProperty = new System.ServiceModel.Channels.HttpRequestMessageProperty();
httpRequestProperty.Headers.Add("X-DocuSign-Authentication", auth);
System.ServiceModel.OperationContext.Current.OutgoingMessageProperties[System.ServiceModel.Channels.HttpRequestMessageProperty.Name] = httpRequestProperty;
EnvelopeStatus envStatus = apiService.CreateAndSendEnvelope(envelope);
return envStatus.EnvelopeID;
}

How do I update tokens in a JSON file using Rest API C#

Browserstack.com have a REST API that stores the results of Automated test sessions in a JSON file.
By default it sets the status token to "done" but you can update using the REST API e.g. "passed" or "failed".
It requires Basic authorization using your username & password.
They give an example of how to do this but it's a bit messy. So I wanted a simpler way of updating the file and only the tokens I was interested in.
I was able to do this using DalSoft's RestClient application:
public static async Task DalSoft(string SessionID, string TestStatus)
{
string Uri = "https://www.browserstack.com/automate/sessions/" + SessionID +
".json";
string AuthToken = "Basic " +
Convert.ToBase64String(Encoding.Default.GetBytes("username:password"));
dynamic client = new DalSoft.RestClient.RestClient(Uri);
var status = new { status=TestStatus };
var result = await client
.Headers(new { Authorization = AuthToken })
.Patch(status);
string Myresults = result.ToString();
}
https://github.com/DalSoft/DalSoft.RestClient/issues/40#issuecomment-334219145

Sendgrid C# bulk email X-SMTPAPI header not working

I am trying to send email with SendGrid to multiple recipients in an ASP.Net C# web application
According to the SendGrid documentation I need to add X-SMTPAPI header to my message in JSON formatted string. I do so, for first check I just added a hand-typed string before building my json email list progamatically here is my code:
string header = "{\"to\": [\"emailaddress2\",\"emailaddress3\"], \"sub\": { \"%name%\": [\"Ben\",\"Joe\"]},\"filters\": { \"footer\": { \"settings\": { \"enable\": 1,\"text/plain\": \"Thank you for your business\"}}}}";
string header2 = Regex.Replace(header, "(.{72})", "$1" + Environment.NewLine);
var myMessage3 = new SendGridMessage();
myMessage3.From = new MailAddress("emailaddress1", "FromName");
myMessage3.Headers.Add("X-SMTPAPI", header2);
myMessage3.AddTo("emailaddress4");
myMessage3.Subject = "Test subject";
myMessage3.Html = "Test message";
myMessage3.EnableClickTracking(true);
// Create credentials, specifying your user name and password.
var credentials = new NetworkCredential(ConfigurationManager.AppSettings["xxxxx"], ConfigurationManager.AppSettings["xxxxx"]);
// Create an Web transport for sending email.
var transportWeb = new Web(credentials);
// Send the email, which returns an awaitable task.
transportWeb.DeliverAsync(myMessage3);
But it just seems to ignore my header, and sends the email to the one email "emailaddress4" used in "addto".
According the documentation if the header JSON is parsed wrongly, then SendGrid sends an email about the error to the email address set in "FROM" field, but I get no email about any error.
Anyone got any idea?
For me using the latest 9.x c# library the only way I could solve this was by using the MailHelper static functions like this:
var client = new SendGridClient(HttpClient, new SendGridClientOptions { ApiKey = _sendGridApiKey, HttpErrorAsException = true });
SendGridMessage mailMsg;
var recipients = to.Split(',').Select((email) => new EmailAddress(email)).ToList();
if (recipients.Count() > 1)
{
mailMsg = MailHelper.CreateSingleEmailToMultipleRecipients(
new EmailAddress(from),
recipients,
subject,
"",
body);
}
else
{
mailMsg = MailHelper.CreateSingleEmail(
new EmailAddress(from),
recipients.First(),
subject,
"",
body);
}
if (attachment != null)
{
mailMsg.AddAttachment(attachment.Name,
attachment.ContentStream.ToBase64(),
attachment.ContentType.MediaType);
}
var response = await client.SendEmailAsync(mailMsg).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
_log.Trace($"'{subject}' email to '{to}' queued");
return true;
}
else {
throw new HttpRequestException($"'{subject}' email to '{to}' not queued");
}
I'm not sure why you wouldn't recieve any errors at your FROM address, but your JSON contains the following flaws:
, near the end makes the string invalid json
spaces around the first % in %name%, that might make sendgrid think it's an invalid substitution tag
if you use the X-SMTPAPI header to specify multiple recipients, you are not supposed to add a standard SMTP TO using AddTo().
Besides that, you didn't wrap the header at 72 characters (see the example in the documentation).
I figured that however the X-SMTPAPI documentation talks about passing the header as JSON, the API itself expects it as a parameter, containing Ienumerable string. So the working code is:
var myMessage3 = new SendGridMessage();
myMessage3.From = new MailAddress("email4#email.com", "Test Sender");
myMessage3.AddTo("email2#email.com");
myMessage3.Subject = "Új klubkártya regisztrálva";
myMessage3.Html = "Teszt üzenet";
myMessage3.EnableClickTracking(true);
/* SMTP API
* ===================================================*/
// Recipients
var addresses = new[]{
"email2#email.com", "email3#email.com"
};
//string check = string.Join(",", addresses);
myMessage3.Header.SetTo(addresses);
// Create credentials, specifying your user name and password.
var credentials = new NetworkCredential(ConfigurationManager.AppSettings["xxxxxxx"], ConfigurationManager.AppSettings["xxxxxxxxx"]);
// Create an Web transport for sending email.
var transportWeb = new Web(credentials);
// Send the email, which returns an awaitable task.
transportWeb.DeliverAsync(myMessage3);

Categories