Showing posts with label twitter. Show all posts
Showing posts with label twitter. Show all posts

Monday, 19 October 2015

Moving Twitter to a background thread.

In my previous blog I added some simple twitter integration to tweet the entries on I’m never going back there.  This was being done in same thread as the users http request – which is not great as the API calls to twitter could be slow or fail.

So, my 30 minute task this week was to move it to a background thread.

[more]

Design

The design is fairly simple:

  1. Create a flag on the comment to indicate if it has been tweeted.
  2. Write code that retrieves all un-tweeted comments, tweets them and updates their status.
  3. Execute this code in a background thread as each comment is added and on application start up.

Without some locking (such as a distributed transaction with serialized read isolation level) this could lead to duplicate twitter entries if two comments were submitted simultaneously.

However, twitter does not allow duplicate tweets so the second tweet should be rejected by twitter.

Database changes

Since we’re now in production we need to script our database changes.

Adding extra column and changes to insert stored procedure:

ALTER TABLE tblComment ADD bTweeted bit not null default 0
GO
ALTER PROCEDURE [dbo].[usp_CommentInsert]
(
    @p_vcrComment nvarchar(1000),
    @p_intVenueId INT,
    @p_vcrUserUpdated  MediumString
)
AS
DECLARE @ident INT
INSERT INTO tblComment VALUES(@p_vcrComment,
    @p_intVenueId,
    1,
    @p_vcrUserUpdated,
    GetDate(),
    GetDate(),
    0)
SET @ident = SCOPE_IDENTITY()
SELECT @ident
GO

We will also need to update our comments -

CREATE PROCEDURE usp_CommentUpdate
(
    @p_intCommentId INT,
    @p_vcrComment nvarchar(1000),
    @p_intVenueId INT,
    @p_vcrUserUpdated  MediumString,
    @p_tweeted bit
)
AS
UPDATE tblComment
SET vcrComment = @p_vcrComment,
intVenueId = @p_intVenueId,
vcrUserUpdated = @p_vcrUserUpdated,
dteLastUpdated = GetDate(),
bTweeted = @p_tweeted
WHERE
intCommentId = @p_intCommentId
GO
GRANT EXEC ON usp_CommentUpdate TO VICTOR
GO

And fetch un-tweeted comments:


CREATE PROCEDURE usp_CommentGetUntweeted
AS
SELECT * from tblComment c
INNER JOIN tblVenue v
ON c.intVenueId = v.intVenueId
INNER JOIN tblVenueType t
ON v.intVenueTypeId = t.intVenueTypeId
WHERE  (c.bTweeted = 0 AND blnApproved=1)
GO
GRANT EXEC ON usp_CommentGetUntweeted TO VICTOR
GO

Changes to the Business objects

The Comment class has an extra attribute to indicate if it has been tweeted.

Changes to the Data Access Layer (DAL).

The data access layer changes :

  1. Populate the new attribute on the class as the object is created from a data reader (amended create method described in previous blog "Ditching the Entity Framework".).
  2. Expose methods for the new update and search stored procedures.

Changes to the Business Processes

A couple of new methods here to handle the threading -

public static void TweetCommentsAsync()
       {
           ThreadPool.QueueUserWorkItem(new WaitCallback(TweetComments));
       }

       public static void TweetComments(Object stateInfo)
       {
           ICollection<Comment> commentsToTwwet = DBComment.GetUntweetedComments();
           foreach (Comment c in commentsToTwwet)
           {
               TweetComment(c);
           }
       }

 

The TweetCommentsAsync() method will sets the TweetComments method to be called on a thread from the thread pool.  The runtime controls these threads, normally limiting the number of active threads at any one time to prevent too much context switching.  I believe 25 threads per CPU used to be the normal allocation in ASP.NET – but this is from memory.

An overloaded version of ThreadPool.QueueUserWorkItem allows data to be past to the method to be invoked.

Obviously the TweetComment method is also updated to update the database.

TwitterResponse<TwitterStatus> resp = TwitterStatus.Update(tokens, tweet);
                   if (resp.Result == RequestResult.Success)
                   {
                       // update teh comment
                       c.Tweeted = true;
                       UpdateComment(c);
                       ret = true;
                   }

Obviously we call the TweetCommentsAsync each time a comment is added and also from the application start-up code in the Global.asax

Conclusion

ThreadPool.QueueUserWorkItem provides a simple, safe mechanism of performing asynchronous operations for example, when we want a thread to process as quickly as possible to optimise user responsiveness.

A quick bit of twitter integration.

OK – so it looks like I’m not going to replace Facebook or Twitter with my social networking site I’m Never Going Back There.   After a full day in production there’s only been one comment.

So – if you can’t beat them, join them.  Time for a bit of integration.

[more]

So – if I can post entries to twitter with a link to the site and get people to follow the twitter account then this should give me lots of links which means lots of Google ranking.

I also want to be able to get people to authenticate with there twitter account and be able to post entries to twitter.

But, this is a spare time project!  So – first a bit of twitter integration.

So I had a quick dig around on the twitter developers pages.  Seems their API is in some flux between REST and streaming API’s.  I decided I didn’t want to get involved in any low level stuff so picked Twitterizer from the list of twitter integration libraries.

Posting a tweet

My first goal was to create a twitter entry for each comment.

The Code

I downloaded the DLL’s and put them in a new folder in our Framework classes – where we reference shared components such as the MS Enterprise blocks.

I added references to them from my shiny business layer created when I ditched Entity Framework.

I registered an new twitter user @imnevergoing and registered my application using this account.  I then downloaded my two application keys (Consumer Key and Consumer Secret) and the two OAuth access tokens. 

The first two tokens authenticate my application.  The second two allow me to authenticate @imnevergoing without directing a user to authenticate on the twitter site.  The posts will always posted as @imnevergoing.

The code is pretty simple.  I form my authentication key as instructed in the twitter documentation here and for a tiny URL back to my site using the code I borrowed from “Shortening URLs Using TinyUrl API in .net” .

I then simply form my post text and post the comment!

 

    public static bool TweetComment(Comment c)
    {
        bool ret = false;

        try
        {
            OAuthTokens tokens = new OAuthTokens();
            tokens.ConsumerKey = ConfigurationSettings.AppSettings["consumerKey"];
            tokens.ConsumerSecret = ConfigurationSettings.AppSettings["consumerSecret"];
            tokens.AccessToken = ConfigurationSettings.AppSettings["oauth_token"];
            tokens.AccessTokenSecret = ConfigurationSettings.AppSettings["oauth_token_secret"];

            String url = MakeTinyUrl("http://www.imnevergoingbackthere.com/");
            StringBuilder sb = new StringBuilder();
            sb.Append(c.TheVenue.Name);
            sb.Append(" ");
            sb.Append(c.TheVenue.Town);
            sb.Append(" ");
            sb.Append(c.CommentText);

            String tweet = sb.ToString(0, Math.Min(sb.Length, 140 - (url.Length + 1)));
            tweet = String.Format("{0}-{1}", tweet, url);

            TwitterResponse<TwitterStatus> resp = TwitterStatus.Update(tokens, tweet);
            if (resp.Result == RequestResult.Success)
            {
                // update teh comment
            }
        }
        catch
        {
            // log the error
        }

        return ret;
    }
    public static string MakeTinyUrl(string Url)
    {
        try
        {
            if (Url.Length <= 30)
            {
                return Url;
            }
            if (!Url.ToLower().StartsWith("http") && !Url.ToLower().StartsWith("ftp"))
            {
                Url = "
http://" + Url;
            }
            var request = WebRequest.Create("
http://tinyurl.com/api-create.php?url=" + Url);
            var res = request.GetResponse();
            string text;
            using (var reader = new StreamReader(res.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }
            return text;
        }
        catch (Exception)
        {
            return Url;
        }
    }
}

I then call this after saving the comment to the database, and, since it’s in my business process layer, this will happen whatever the client (web service, web page …).

[DataObjectMethodAttribute(DataObjectMethodType.Insert, true)]
       public static int InsertComment(Comment comment)
       {
           int ret = 0;
           ret = DBComment.InsertComment(comment);
           TweetComment(comment);
           return ret;
       }

The Tao

Thus spake the master programmer:

``Though a program be but three lines long, someday it will have to be maintained.''

The Tao Of Programming

This may look fine but its not particularly great as

  1. It’s done synchronously as part of the web request.  This could take  a significant amount of time.
  2. If it fails it will never be tweeted.
  3. I would like a link the actual comment as more meaningful and probably better for SEO– but I don’t have the view yet!

Much better plan is to extend the object/database to indicate if the comment has been tweeted and then on each comment add and website start-up kick off a thread to retrieve all the un-tweeted comments and tweet them.

I’ll cover this in a separate blog.

Follow Me

This should be pretty easy -  a simple button from the twitter goodies site in the master page should do the trick.

Tweet this.

Ideally I’d like to be able to tweet each individual comment – but I haven’t got the view or time for this yet.

So I settle for two buttons on the master page – a tweet this button and an “add this” button – which covers a lot of other social networking sites.

The twitter button can be built from twitter’s goodies and the “Add this” from Add This"!