Using the Table Storage Upsert (Insert or Replace)

I just implemented the new Upsert functionality introduced in the Azure SDK 1.6 and wanted to document the solution as I missed one detail that kept me struggling for a while – hopefully this post can help someone else avoid it!

My intent with this change was to replace another update method that first checked to see if the entity already existed, and then either added the new entity or updated the existing one. Upsert will cut down my storage transactions for updates in half as it only executes one request per update. It will also boost performance for the same reason.

Step 1:
Make sure that a specific version header is sent in the REST requests to the storage service. I have a static method that creates a TableServiceContext that I updated with one row (line 10):

        public static TableServiceContext CreateContext()
        {
            var context = new TableServiceContext(BaseAddress, Credentials)
            {
                IgnoreResourceNotFoundException = true // FirstOrDefault() will return null if no match, instead of 404 exception
            };

            // Add specific header to support Upsert
            // TODO: Remove when Azure supports this by default
            context.SendingRequest += (sender, args) => (args.Request).Headers["x-ms-version"] = "2011-08-18";

            return context;
        }

Step 2:
Use the AttachTo method combined with UpdateObject and the ReplaceOnUpdate option for the SaveChanges method to execute the upsert (I have a generic class with the table storage methods):

        public void AddOrReplace(T obj)
        {
            _tableServiceContext.AttachTo(_tableName, obj);
            _tableServiceContext.UpdateObject(obj);
            _tableServiceContext.SaveChangesWithRetries(SaveChangesOptions.ReplaceOnUpdate);
        }

Note: I Already had an Update method that used the same code but with the following slightly different AttachTo call (the last asterisk parameter). This caused a ResourceNotFound error when saving changes and it took me a while to find and fix this error.

_tableServiceContext.AttachTo(_tableName, obj, "*");

That’s it!

Advertisements

Authenticating mobile Facebook users

Up until now, I have been using Facebook C# SDK to handle authentication of users for Am I Interesting. However, when Facebook released separate handling of mobile users – which is welcome – the SDK did not support that. The Facebook error message shown to the users was:

The mobile version of the app “Am I Interesting” is unavailable because it is misconfigured. It appears to be caught in a redirect loop.

The underlying issue is that all users, no matter how they access the application, get redirected to the same URL when using the SDK, in my case to apps.facebook.com/amiinteresting, and that does not work for mobile users. For mobile users, we have to redirect them outside of Facebook’s IFrame.

My authentication goal:
Get a Facebook Access Token and Facebook User Id for all users who login

What I need as far as URLs go:

  • “Regular” users should be redirected to apps.facebook.com/amiinteresting (IFrame within Facebook)
  • Mobile users should be redirected to www.amiinteresting.net (outside Facebook’s frame)

For the solution described below, I followed Facebook’s developer documentation about authentication at:
http://developers.facebook.com/docs/authentication/

For the first login step, I use the following code:

        public ActionResult Login()
        {
            var redirectUrl = string.Format("https://www.facebook.com/dialog/oauth?client_id={0}&scope={1}&redirect_uri={2}",
            _configurationHelper.FacebookAppId,
            string.Join(",", FacebookPermissions.RequiredPermissions),
            _configurationHelper.FacebookRedirectUrl);
            var html = string.Format("<html><body><script type=\"text/javascript\">window.top.location='{0}';</script></body></html>", redirectUrl);
            return Content(html, "text/html");
        }

The Facebook Redirect Url configuration setting above is always http://www.amiinteresting.net. Facebook takes care of instead redirecting to apps.facebook.com/amiinteresting if it’s not a mobile user.

After fixing the redirect problem, I noticed that:

  • When a “regular” user arrives to the application at apps.facebook.com within an IFrame, Facebook posts what’s called a signed_request that can be decoded and contains an access token (which I need), the User ID, and some other nice info (described here).
  • When a mobile user arrives to the application outside Facebook’s frame, I don’t get a signed_request, but rather a “code” in the querystring that can be used to query Facebook together with the application secret to get the access_token and other info as needed.

So – I needed two separate ways to handle this, and implemented them in a method that I call CheckNewFacebookLogin (supporting methods further down):

        public bool CheckNewFacebookLogin()
        {
            var codeInUrl = _currentHttpContext.QueryString["code"];
            var signedRequest = _currentHttpContext.Form["signed_request"];

            if (codeInUrl == null && signedRequest == null)
            {
                return false;
            }

            if (signedRequest != null)
            {

                // Signed Request
                var parametersInSignedRequest = ExtractDataFromRequest(signedRequest);
                var accessToken = parametersInSignedRequest["oauth_token"].ToString();
                var userId = Convert.ToInt64(parametersInSignedRequest["user_id"]);
                if (_sessionVariables.AccessToken != null && _sessionVariables.AccessToken == accessToken)
                {
                    return false; // User already authenticated and no new user logged in
                }
                _sessionVariables.InvalidateSession(); // New app login or new user accessed application
                _sessionVariables.AccessToken = accessToken;
                _sessionVariables.UserInfo = new PersonEntity
                                                 {
                                                     UserId = userId
                                                 };
            }
            else
            {

                // Code in URL
                if (_sessionVariables.FacebookCode != null && _sessionVariables.FacebookCode == codeInUrl)
                {
                    return false; // User already authenticated and no new user logged in
                }
                _sessionVariables.InvalidateSession(); // New app login or new user accessed application
                _sessionVariables.FacebookCode = codeInUrl;
            }

            return true;
        }

If the method returns true = a new user logged in, I display a landing/waiting page and in parallell call this CompleteLogin method (simplified):

        public void CompleteLogin()
        {
            // Get token & userId
            if (_sessionVariables.FacebookCode != null)
            {
                var accessToken = GetFacebookAccessToken(_sessionVariables.FacebookCode);
                _sessionVariables.AccessToken = accessToken;

                var userId = _facebookService.GetUserIdFromFacebookToken(_sessionVariables.AccessToken);
                _sessionVariables.UserInfo = new PersonEntity
                {
                    UserId = userId
                };
            }
        }

        private string GetFacebookAccessToken(string code)
        {
            var requestUrl = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&client_secret={1}&code={2}&redirect_uri={3}",
                _configurationHelper.FacebookAppId,
                _configurationHelper.FacebookAppSecret,
                code,
                _configurationHelper.FacebookRedirectUrl);
            var response = _facebookRequest.GetTextResponse(new Uri(requestUrl));
            var parameters = response.Split('&');
            foreach (var parameter in parameters)
            {
                var key = parameter.Split('=')[0];
                var value = parameter.Split('=')[1];
                if (key == "access_token")
                {
                    return value;
                }
            }
            return null;
        }

Supporting methods:

        private static Dictionary<string, object> ExtractDataFromRequest(string signedRequest)
        {
            var jsonEncodedObject = signedRequest.Split('.')[1];
            var json = Base64_Url_Decode(jsonEncodedObject);
            var parameters = SerializeHelpers.DeserializeFromJson(json);
            var parameterDictionary = new Dictionary<string, object>();
            foreach (var parameter in parameters)
            {
                parameterDictionary.Add(parameter.Key, parameter.Value);
            }
            return parameterDictionary;
        }

        public static string Base64_Url_Decode(string input)
        {
            // From http://www.hazardbrick.com/post/2011/04/05/Facebook-Login-Registration-signed_request-Base64-problems-in-ASPNet-C.aspx
            string fixedString;
            var fixedDashString = input.Replace('-', '+');
            var fixedUnderscoreString = fixedDashString.Replace('_', '/');
            if (fixedUnderscoreString.Length % 4 != 0)
            {
                fixedString = String.Format("{0}", fixedUnderscoreString);
                int paddingCount = fixedString.Length % 4;
                while (paddingCount % 4 != 0)
                {
                    fixedString += '='; paddingCount++;
                }
            }
            else
            {
                fixedString = fixedUnderscoreString;
            }
            var inputBytes = Convert.FromBase64String(fixedString);
            return Encoding.UTF8.GetString(inputBytes);
        }

Let me know if you have the same problem and need me to further expand on the solution.

Good luck! 🙂