Cloudflare API - Purge Files By URL In C#

Earlier this week I wrote about the reasons to why I decided to use Cloudflare for my website. I've been working on utilising Cloudflare's API to purge the cache on demand for when files need to be updated within the CDN. To do this, I decided to write a method that will primarily use one API endpoint - /purge_cache. This endpoint allows a maximum of 30 URL's at one time to be purged, which is flexible enough to fit the majority of day-to-day use cases.

To communicate with the API, we need to provide three pieces of information:

  1. Account Email Address
  2. Zone ID
  3. API Key

The last two pieces of information can be found within the dashboard of your Cloudflare account.

Code - CloudflareCacheHelper Class

The CloudflareCacheHelper class consists of a single method PurgeSelectedFiles() and the following class objects used for serializing and deserializing our responses from API requests:

  • CloudflareFileInfo
  • CloudflareZone
  • CloudflareResultInfo
  • CloudflareResponse

Not all the properties within each of the class objects are being used at the moment based on the requests I am making. But the CloudflareCacheHelper class will be updated with more methods as I delve further into Cloudflare's functionality.

public class CloudflareCacheHelper
{
    public string _userEmail;
    public string _apiKey;
    public string _zoneId;

    private readonly string ApiEndpoint = "https://api.cloudflare.com/client/v4";

    /// <summary>
    /// By default the Cloudflare API values will be taken from the Web.Config.
    /// </summary>
    public CloudflareCacheHelper()
    {
        _apiKey = ConfigurationManager.AppSettings["Cloudflare.ApiKey"];
        _userEmail = ConfigurationManager.AppSettings["Cloudflare.UserEmail"];
        _zoneId = ConfigurationManager.AppSettings["Cloudflare.ZoneId"];
    }

    /// <summary>
    /// Set the Cloudflare API values explicitly.
    /// </summary>
    /// <param name="userEmail"></param>
    /// <param name="apiKey"></param>
    /// <param name="zoneId"></param>
    public CloudflareCacheHelper(string userEmail, string apiKey, string zoneId)
    {
        _userEmail = userEmail;
        _apiKey = apiKey;
        _zoneId = zoneId;
    }
        
    /// <summary>
    /// A collection of file paths (max of 30) will be accepted for purging cache.
    /// </summary>
    /// <param name="filePaths"></param>
    /// <returns>Boolean value on success or failure.</returns>
    public bool PurgeSelectedFiles(List<string> filePaths)
    {
        CloudflareResponse purgeResponse = null;

        if (filePaths?.Count > 0)
        {
            try
            {
                HttpWebRequest purgeRequest = WebRequest.CreateHttp($"{ApiEndpoint}/zones/{_zoneId}/purge_cache");
                purgeRequest.Method = "POST";
                purgeRequest.ContentType = "application/json";
                purgeRequest.Headers.Add("X-Auth-Email", _userEmail);
                purgeRequest.Headers.Add("X-Auth-Key", _apiKey);

                #region Create list of Files for Submission In The Structure The Response Requires

                CloudflareFileInfo fileInfo = new CloudflareFileInfo
                {
                    Files = filePaths
                };

                byte[] data = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(fileInfo));

                purgeRequest.ContentLength = data.Length;

                using (Stream fileStream = purgeRequest.GetRequestStream())
                {
                    fileStream.Write(data, 0, data.Length);
                    fileStream.Flush();
                }

                #endregion

                using (WebResponse response = purgeRequest.GetResponse())
                {
                    using (StreamReader purgeStream = new StreamReader(response.GetResponseStream()))
                    {
                        string responseJson = purgeStream.ReadToEnd();

                        if (!string.IsNullOrEmpty(responseJson))
                            purgeResponse = JsonConvert.DeserializeObject<CloudflareResponse>(responseJson);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return purgeResponse.Success;
        }

        return false;
    }

    #region Cloudflare Class Objects

    public class CloudflareFileInfo
    {
        [JsonProperty("files")]
        public List<string> Files { get; set; }
    }

    public class CloudflareZone
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("content")]
        public string Content { get; set; }

        [JsonProperty("proxiable")]
        public bool Proxiable { get; set; }

        [JsonProperty("proxied")]
        public bool Proxied { get; set; }

        [JsonProperty("ttl")]
        public int Ttl { get; set; }

        [JsonProperty("priority")]
        public int Priority { get; set; }

        [JsonProperty("locked")]
        public bool Locked { get; set; }

        [JsonProperty("zone_id")]
        public string ZoneId { get; set; }

        [JsonProperty("zone_name")]
        public string ZoneName { get; set; }

        [JsonProperty("modified_on")]
        public DateTime ModifiedOn { get; set; }

        [JsonProperty("created_on")]
        public DateTime CreatedOn { get; set; }
    }

    public class CloudflareResultInfo
    {
        [JsonProperty("page")]
        public int Page { get; set; }

        [JsonProperty("per_page")]
        public int PerPage { get; set; }

        [JsonProperty("count")]
        public int Count { get; set; }

        [JsonProperty("total_count")]
        public int TotalCount { get; set; }
    }

    public class CloudflareResponse
    {
        [JsonProperty("result")]
        public CloudflareZone Result { get; set; }

        [JsonProperty("success")]
        public bool Success { get; set; }

        [JsonProperty("errors")]
        public IList<object> Errors { get; set; }

        [JsonProperty("messages")]
        public IList<object> Messages { get; set; }

        [JsonProperty("result_info")]
        public CloudflareResultInfo ResultInfo { get; set; }
    }

    #endregion
}

Example - Purging Cache of Two Files

A string collection of URL's can be passed into the method to allow for the cache of a batch of files to be purged in a single request. If all goes well, the success response should be true.

CloudflareCacheHelper cloudflareCache = new CloudflareCacheHelper();

bool isSuccess = cloudflareCache.PurgeSelectedFiles(new List<string> {
                                    "https://www.surinderbhomra.com/getmedia/7907d934-805f-4bd3-86e7-a6b2027b4ba6/CloudflareResponseMISS.png",
                                    "https://www.surinderbhomra.com/getmedia/89679ffc-ca2f-4c47-8d41-34a6efdf7bb8/CloudflareResponseHIT.png"
                                });

Rate Limits

The Cloudflare API sets a maximum of 1,200 requests in a five minute period. Cache-Tag purging has a lower rate limit of up to 2,000 purge API calls in every 24 hour period. You may purge up to 30 tags in one API call.

ASP.NET Core - Get Page Title By URL

To make it easy for a client to add in related links to pages like a Blog Post or Article, I like implementing some form of automation so there is one less thing to content manage. For a Kentico Cloud project, I took this very approach. I created a UrlHelper class that will carry out the following:

  • Take in an absolute URL.
  • Read the markup of the page.
  • Selects the title tag using Regex.
  • Remove the site name prefix from title text.
using Microsoft.Extensions.Caching.Memory;
using MyProject.Models.Site;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;

namespace MyProject.Helpers
{
    public class UrlHelper
    {
        private static IMemoryCache _cache;

        public UrlHelper(IMemoryCache memCache)
        {
            _cache = memCache;
        }

        /// <summary>
        /// Returns the a title and URL of the link directly from a page.
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public PageLink GetPageTitleFromUrl(string url)
        {
            if (!string.IsNullOrEmpty(url))
            {
                if (_cache.TryGetValue(url, out PageLink page))
                {
                    return page;
                }
                else
                {
                    using (WebClient client = new WebClient())
                    {
                        try
                        {
                            Stream stream = client.OpenRead(url);
                            StreamReader streamReader = new StreamReader(stream, System.Text.Encoding.GetEncoding("UTF-8"));

                            // Get contents of the page.
                            string pageHtml = streamReader.ReadToEnd();

                            if (!string.IsNullOrEmpty(pageHtml))
                            {
                                // Get the title.
                                string title = Regex.Match(pageHtml, @"\<title\b[^>]*\>\s*(?<Title>[\s\S]*?)\</title\>", RegexOptions.IgnoreCase).Groups["Title"].Value;

                                if (!string.IsNullOrEmpty(title))
                                {
                                    if (title.Contains("|"))
                                        title = title.Split("|").First();
                                    else if (title.Contains(":"))
                                        title = title.Split(":").First();

                                    PageLink pageLink = new PageLink
                                    {
                                        PageName = title,
                                        PageUrl = url
                                    };

                                    _cache.Set(url, pageLink, DateTimeOffset.Now.AddHours(12));

                                    page = pageLink;
                                }
                            }

                            // Cleanup.
                            stream.Flush();
                            stream.Close();
                            client.Dispose();
                        }
                        catch (WebException e)
                        {
                            throw e;
                        }
                    }
                }

                return page;
            }
            else
            {
                return null;
            }
        }
    }
}

The method returns a PageLink object:

namespace MyProject.Models.Site
{
    public class PageLink
    {
        public string PageName { get; set; }
        public string PageUrl { get; set; }
    }
}

From an efficiency standpoint, I cache the process for 12 hours as going through the process of reading the markup of a page can be quite expensive if there is a lot of HTML.

ASP.NET Core - Render Partial View To String Outside Controller Context

When building MVC websites, I cannot get through a build without using a method to convert a partial view to a string. I have blogged about this in the past and find this approach so useful especially when carrying out heavy AJAX processes. Makes the whole process of maintaining and outputting markup dynamically a walk in the park.

I've been dealing with many more ASP.NET Core builds and migrating over the RenderPartialViewToString() extension I developed previously was not possible. Instead, I started using the approach detailed in the following StackOverflow post: Return View as String in .NET Core. Even though the approach was perfectly acceptable and did the job nicely, I noticed I had to make one key adjustment - allow for views outside controller context.

The method proposed in the StackOverflow post uses ViewEngine.FindView(), from what I gather only returns a view within the current controller context. I added a check that will use ViewEngine.GetView() if a path of the view ends with a ".cshtml" which is normally the approach used when you refer to a view from a different controller by using a relative path.

public static class ControllerExtensions
{
    /// <summary>
    /// Render a partial view to string.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="controller"></param>
    /// <param name="viewNamePath"></param>
    /// <param name="model"></param>
    /// <returns></returns>
    public static async Task<string> RenderViewToStringAsync<TModel>(this Controller controller, string viewNamePath, TModel model)
    {
        if (string.IsNullOrEmpty(viewNamePath))
            viewNamePath = controller.ControllerContext.ActionDescriptor.ActionName;

        controller.ViewData.Model = model;

        using (StringWriter writer = new StringWriter())
        {
            try
            {
                IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;

                ViewEngineResult viewResult = null;

                if (viewNamePath.EndsWith(".cshtml"))
                    viewResult = viewEngine.GetView(viewNamePath, viewNamePath, false);
                else
                    viewResult = viewEngine.FindView(controller.ControllerContext, viewNamePath, false);

                if (!viewResult.Success)
                    return $"A view with the name '{viewNamePath}' could not be found";

                ViewContext viewContext = new ViewContext(
                    controller.ControllerContext,
                    viewResult.View,
                    controller.ViewData,
                    controller.TempData,
                    writer,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

                return writer.GetStringBuilder().ToString();
            }
            catch (Exception exc)
            {
                return $"Failed - {exc.Message}";
            }
        }
    }
}

Quick Example

As you can see from my quick example below, the Home controller is using the RenderViewToStringAsync() when calling:

  • A view from another controller, where a relative path to the view is used.
  • A view from within the realms of the current controller and the name of the view alone can be used.
public class HomeController : Controller
{
    public async Task<IActionResult> Index()
    {
        NewsListItem newsItem = GetSingleNewsItem(); // Get a single news item.

        string viewFromAnotherController = await this.RenderViewToStringAsync("~/Views/News/_NewsList.cshtml", newsItem);
        string viewFromCurrentController = await this.RenderViewToStringAsync("_NewsListHome", newsItem);

        return View();
    }
}

 

ASP.NET Core - HTTP Error 502.5 Process Failure

It seems whenever I work on an ASP.NET Core website, I always seem to get the most unhelpful error when deploying to production:

HTTP Error 502.5 - Process Failure

I have no problem running the ASP.NET Core site whilst developing from within a local environment.

From past experience, the HTTP 502.5 error generally happens for the following reasons:

  1. The ASP.NET Core framework is not installed or your site is running the incorrect version.
  2. Website project incorrectly published.
  3. Potential configuration issue at code level.

Generally when you successfully publish a deployable version of your site, you'd expect it to just work. To get around the deployment woes, the solution is to modify your .csproj file by adding the following setting:

<PropertyGroup>
   <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
</PropertyGroup>

Once this setting has been added, you'll notice when your site is re-published a whole bunch of new DLL files are now present, forming part of all the dependencies a site requires. It's strange a normal publish does not do this already and what's even stranger is I have a different .NET Core site running without having to take this approach.

For any new .NET Core sites I work on, I will be using approach going forward.

Useful Links

ASP.NET Core MVC Numbered Pagination

This is a relatively simple pagination that will only be shown if there are enough items of data to paginate through. The user will have the ability to paginate by either clicking on the "Previous" and "Next" links as well as clicking on the individual page numbers from within the pagination.

I created a PaginationHelper.CreatePagination() method that carries out all the paging calculations and outputs the pagination as an unordered list. The method requires the following parameters:

  • currentPage - the current page number being viewed.
  • totalNumberOfRecords - the total count of records from your dataset in order to determine how many pages should be displayed.
  • pageRequest - the current request from by passing in "HttpContext.Request" to get the page URL.
  • noOfPageLinks - the number of page numbers that should be shown. For example "1, 2, 3, 4".
  • pageSize - the number of items will be shown per page.
using Microsoft.AspNetCore.Http;
using System;
using System.Text;

namespace MyProject.Helpers
{
    public static class PaginationHelper
    {
        /// <summary>
        /// Renders pagination used in listing pages.
        /// </summary>
        /// <param name="currentPage"></param>
        /// <param name="totalNumberOfRecords"></param>
        /// <param name="pageRequest">Current page request used to get the URL path of the page.</param>
        /// <param name="noOfPagesLinks">Number of pagination numbers to show.</param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static string CreatePagination(int currentPage, int totalNumberOfRecords, HttpRequest pageRequest, int noOfPagesLinks = 5, int pageSize = 10)
        {
            StringBuilder paginationHtml = new StringBuilder();

            // Only render the pagination markup if the total number of records is more than our page size.
            if (totalNumberOfRecords > pageSize)
            {
                #region Pagination Calculations

                int amountOfPages = (int)(Math.Ceiling(totalNumberOfRecords / Convert.ToDecimal(pageSize)));

                int startPage = currentPage;

                if (startPage == 1 || startPage == 2 || amountOfPages < noOfPagesLinks)
                    startPage = 1;
                else
                    startPage -= 2;

                int maxPage = startPage + noOfPagesLinks;

                if (amountOfPages < maxPage)
                    maxPage = Convert.ToInt32(amountOfPages) + 1;

                if (maxPage - startPage != noOfPagesLinks && maxPage > noOfPagesLinks)
                    startPage = maxPage - noOfPagesLinks;

                int previousPage = currentPage - 1;
                if (previousPage < 1)
                    previousPage = 1;

                int nextPage = currentPage + 1;

                #endregion

                #region Get Current Path

                // Get current path.
                string path = pageRequest.Path.ToString();

                int pos = path.LastIndexOf("/") + 1;

                // Get last route value.
                string lastRouteValue = path.Substring(pos, path.Length - pos).ToLower();

                // Removes page number from end of path if path contains a page number.
                if (lastRouteValue.StartsWith("page"))
                    path = path.Substring(0, path.LastIndexOf('/'));

                #endregion

                paginationHtml.Append("<ul>");

                if (currentPage > 1)
                    paginationHtml.Append($"<li><a href=\"{path}/Page{previousPage}\"><span>Previous page</span></a></li>");

                for (int i = startPage; i < maxPage; i++)
                {
                    // If the current page equals one of the pagination numbers, set active state.
                    if (i == currentPage)
                        paginationHtml.Append($"<li><a href=\"{path}/Page{i}\" class=\"is-active\"><span>{i}</span></a></li>");
                    else
                        paginationHtml.Append($"<li><a href=\"{path}/Page{i}\"><span>{i}</span></a></li>");
                }

                if (startPage + noOfPagesLinks < amountOfPages && maxPage > noOfPagesLinks || currentPage < amountOfPages)
                    paginationHtml.Append($"<li><a href=\"{path}/Page{nextPage}\"><span>Next page</span></a></li>");

                paginationHtml.Append("</ul>");

                return paginationHtml.ToString();
            }
            else
            {
                return string.Empty;
            }
        }
    }
}

The PaginationHelper.CreatePagination() method can then be used inside a controller where you would like to list your data as well as render the pagination. A simple example of this would be as follows:

/// <summary>
/// List all news articles.
/// </summary>
/// <param name="page"></param> 
/// <param name="pageSize"></param>
/// <returns></returns>
[Route("/Articles")]
[Route("/Articles/Page{page}")]
public ActionResult Index(int page = 1, int pageSize = 10)
{
    // Number of articles to skip.
    int skip = 0;
    if (page != 1)
        skip = (page - 1) * pageSize;

    // Get list of articles from my datasource.
    List<NewsArticle> articles = MyData.GetArticles().Skip(skip).Take(pageSize).ToList();

    //Render Pagination.
    ViewBag.PaginationHtml = PaginationHelper.CreatePagination(page, articles.Count, HttpContext.Request, pageSize: pageSize);

    return View(articles);
}

The pagination will be output to a ViewBag that can be called from within your view. I could have gone down a different route and developed Partial View along with the appropriate model. But for my use the method approach offers most flexibility, as I could have the option to either use this from within a controller or view.

Get Facebook Comments For A Page Using Graph API In ASP.NET

For a site I'm working on, the Facebook's Comments plugin is being utilised on all our article pages. There was a requirement to pull in the latest comments in a listing page for each of these article pages as well as number of comments. Facebook's JavaScript library provides the ability to display a comments counter but not the ability to pull out x number of comments. So we'll have to go server-side and use Graph API to get the data we want.

In this post, I will show you how you can get back all comments for a page by it's full URL.

Prerequisites

Before we get into the main C# logic methods, you need to make sure we have a few things in place:

  • ApiWebRequestHelper Class
  • Newtonsoft Json
  • Facebook App Settings
  • Class Objects

ApiWebRequestHelper Class

Whenever I am making a call to Facebook's Graph API endpoints, I will be making references to a "ApiWebRequestHelper" helper class. This is something I developed last month to make it easier for me to deserialize XML or JSON requests to a strongly-typed class object. You can take a look at the full code here.

Newtonsoft Json

The Newtonsoft Json library is a key ingredient to any JSON web requests. I'd be surprised if you've never heard or used it. :-) Nevertheless, you can get it here: http://www.newtonsoft.com/json.

Facebook App Settings

I haven't created a Facebook App for quite some time and things have changed very slightly in terms of the interface and options presented. The key things you need to get out of your created App is:

  • Application ID
  • Application Secret
  • Client Token

I set the security settings with the following modes, which can be found in Settings > Advanced >  Security.

Facebook App Advanced API Settings

Class Objects

The following class objects will be used to deserialize Graph API requests into class objects.

The FacebookPageInfo, FacebookPage and FacebookPageShare objects will get the core information about the queried page, such as the Title and Description, as well as the comments and share counts.

namespace Site.BusinessObjects.Facebook
{
    public class FacebookPageInfo
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("og_object")]
        public FacebookPage Page { get; set; }

        [JsonProperty("share")]
        public FacebookPageShare Share { get; set; }
    }

    public class FacebookPage
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("description")]
        public string Description { get; set; }

        [JsonProperty("title")]
        public string Title { get; set; }

        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("updated_time")]
        public DateTime UpdatedTime { get; set; }

        [JsonProperty("url")]
        public string Url { get; set; }
    }
}
namespace Site.BusinessObjects.Facebook
{
    public class FacebookPageShare
    {
        [JsonProperty("comment_count")]
        public int CommentCount { get; set; }

        [JsonProperty("share_count")]
        public int ShareCount { get; set; }
    }
}

All comments for a page will be stored in the following objects:

namespace Site.BusinessObjects.Facebook
{
    public class FacebookPageCommentInfo
    {
        public int TotalComments { get; set; }
        public List<FacebookCommentItem> Comments { get; set; }
    }
}
namespace Site.BusinessObjects.Facebook
{
    public class FacebookCommentItem
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("created_time")]
        public DateTime CreatedTime { get; set; }

        [JsonProperty("from")]
        public FacebookCommentFrom From { get; set; }

        [JsonProperty("message")]
        public string Message { get; set; }
    }

    public class FacebookCommentFrom
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }
    }
}

Facebook Logic Class

Now that we have the pre-requisites in place, lets get to the code that will perform the required functions:

namespace Site.BusinessLogic
{
    public class FacebookLogic
    {
        private string _accessToken;

        /// <summary>
        /// Uses default Client ID and Secret as set in the web.config.
        /// </summary>
        public FacebookLogic()
        {
            GetAccessToken(Config.Facebook.ClientId, Config.Facebook.ClientSecret);
        }

        /// <summary>
        /// Requires  Client ID and Secret.
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="clientSecret"></param>
        public FacebookLogic(string clientId, string clientSecret)
        {
            GetAccessToken(clientId, clientSecret);
        }

        /// <summary>
        /// Gets page info that has been shared to Facebook.
        /// </summary>
        /// <param name="pageUrl"></param>
        /// <returns></returns>
        public FacebookPageInfo GetPage(string pageUrl)
        {
            return ApiWebRequestHelper.GetJsonRequest<FacebookPageInfo>($"https://graph.facebook.com/{pageUrl}?access_token={_accessToken}");
        }

        /// <summary>
        /// Gets comments for a page based on its absolute URL.
        /// </summary>
        /// <param name="pageUrl"></param>
        /// <param name="maxComments"></param>
        public FacebookPageCommentInfo GetPageComments(string pageUrl, int maxComments)
        {
            try
            {
                // Get page information in order to retrieve page ID to pass to commenting.
                FacebookPageInfo facebookPage = GetPage(pageUrl);

                if (facebookPage.Page != null)
                {
                    return new FacebookPageCommentInfo
                    {
                        TotalComments = facebookPage.Share.CommentCount,
                        Comments = GetCommentsByPageId(facebookPage.Page.Id, maxComments).Comments
                    };
                }
                else
                {
                    return null;
                }
            }
            catch (Exception ex)
            {
                // NOTE: Log exception here...

                return null;
            }
        }

        /// <summary>
        /// Gets comments by Facebook's Page ID.
        /// </summary>
        /// <param name="fbPageId"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        public FacebookCommentInfo GetCommentsByPageId(string fbPageId, int max = 10)
        {
            return ApiWebRequestHelper.GetJsonRequest<FacebookCommentInfo>($"https://graph.facebook.com/comments?id={fbPageId}&access_token={_accessToken}&limit={max}");
        }

        /// <summary>
        /// Retrieves Access Token from Facebook App.
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="clientSecret"></param>
        private void GetAccessToken(string clientId, string clientSecret)
        {
            UriBuilder builder = new UriBuilder($"https://graph.facebook.com/oauth/access_token?client_id={Config.Facebook.ClientId}&client_secret={Config.Facebook.ClientSecret}&grant_type=client_credentials");

            try
            {
                using (WebClient client = new WebClient())
                {
                    // Get Access Token from incoming response.
                    string data = client.DownloadString(builder.Uri);

                    NameValueCollection parsedQueryString = HttpUtility.ParseQueryString(data);

                    _accessToken = parsedQueryString["access_token"];
                }
            }
            catch (Exception ex)
            {
                // NOTE: Log exception here...
            }
        }
    }
}

By default, on initiation of the FacebookLogic class, the Application ID and Secret values will be inherited from the web.config, or you can pass in these values directly with the class overload parameters.

Out of all the methods used here, we're interested in only using one: GetPageComments(). What you will notice from this method is that we cannot get the comments from one API call alone. We first have to make an extra API call to get the ID of the page. This ID is passed to the GetCommentsByPageId() method, to return all comments.

Usage

Comments for a page can be returned by adding the following in your code, where you will then be able to access properties to iterate through the comments:

FacebookLogic fbl = new FacebookLogic();

// Pass in the page URL and number of comments to be returned.
var pageComments = fbl.GetPageComments("https://www.surinderbhomra.com/", 2);

Whenever you call this piece of code, I would make sure you cache the results for 5 - 10 minutes, so you do not use up your API request limits.

Useful Method To Deserialize XML or JSON To A Class Object

I have created a helper class that will allow me to consume any XML or JSON request for deserialization into a class object. As you can see from the code below, the GetJsonRequest() and GetXmlRequest() methods allow you to pass an unknown type as well as the URL to where you are getting your request from. This makes things very straight-forward when you want to easily strongly type the data.

public class ApiWebRequestHelper
{
    /// <summary>
    /// Gets a request from an external JSON formatted API and returns a deserialized object of data.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="requestUrl"></param>
    /// <returns></returns>
    public static T GetJsonRequest<T>(string requestUrl)
    {
        try
        {
            WebRequest apiRequest = WebRequest.Create(requestUrl);
            HttpWebResponse apiResponse = (HttpWebResponse)apiRequest.GetResponse();

            if (apiResponse.StatusCode == HttpStatusCode.OK)
            {
                string jsonOutput;
                using (StreamReader sr = new StreamReader(apiResponse.GetResponseStream()))
                    jsonOutput = sr.ReadToEnd();
                    
                var jsResult = JsonConvert.DeserializeObject<T>(jsonOutput);

                if (jsResult != null)
                    return jsResult;
                else
                    return default(T);
            }
            else
            {
                return default(T);
            }
        }
        catch (Exception ex)
        {
            // Log error here.

            return default(T);
        }
    }

    /// <summary>
    /// Gets a request from an external XML formatted API and returns a deserialized object of data.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="requestUrl"></param>
    /// <returns></returns>
    public static T GetXmlRequest<T>(string requestUrl)
    {
        try
        {
            WebRequest apiRequest = WebRequest.Create(requestUrl);
            HttpWebResponse apiResponse = (HttpWebResponse)apiRequest.GetResponse();

            if (apiResponse.StatusCode == HttpStatusCode.OK)
            {
                string xmlOutput;
                using (StreamReader sr = new StreamReader(apiResponse.GetResponseStream()))
                    xmlOutput = sr.ReadToEnd();

                XmlSerializer xmlSerialize = new XmlSerializer(typeof(T));

                var xmlResult = (T)xmlSerialize.Deserialize(new StringReader(xmlOutput));

                if (xmlResult != null)
                    return xmlResult;
                else
                    return default(T);
            }
            else
            {
                return default(T);
            }
        }
        catch (Exception ex)
        {
            // Log error here.
            return default(T);
        }
    }
}

The ApiWebRequestHelper class relies on the following namespaces:

  • Newtonsoft Json
  • System.Xml.Serialization
  • ​​System.IO;

The ApiWebRequestHelper can be used in the following way:

// Get Json Request
ApiWebRequestHelper.GetJsonRequest<MyCustomJsonClass>("http://www.surinderbhomra.com/api/result.json");

// Get XML Request
ApiWebRequestHelper.GetXmlRequest<MyCustomXMLClass>("http://www.surinderbhomra.com/api/result.xml");

Is ReactJS A Worthwhile Addition To An ASP.NET MVC Application?

ReactJSI've been meddling around with ReactJS over the last week or so, seeing if this is something viable to use for future client projects. I am always constantly on the lookout to whether there are better alternatives on how my fellow developers and I develop our sites.

Throughout the sample applications I've been building, I constantly asked myself one question: Why Would I Use ReactJS In My Day To Day Development? I am ASP.NET developer who build websites either using Web Forms or MVC Razor. So I am finding it difficult to comprehend whether using ReactJS is viable in these frameworks, especially MVC.

ReactJS is primarily a view framework where you have the ability to write component-based web applications directly into your JavaScript that then gets output to the DOM virtually - making for a very fast and responsive webpage. It's a different approach to developing websites that I quite like. But for the moment, I just don't see how it can benefit me when the full MVC framework does a pretty good job with all the bells and whistles.

For example, I segregate all my key HTML markup into partial views in order to increase re-use throughout my web application, which works really well when making AJAX calls where the markup needs to be displayed on the page asynchronously as well as server-side. I can just see by implementing ReactJS, I will be duplicating this process at JavaScript and CSHTML level if a markup change ever needed to be made. If partial views does the job effectively, I'm not too sure the need for ReactJS in my future ASP.NET MVC creations.

Don't get me wrong - I really like ReactJS. It makes writing JavaScript an even more enjoyable experience purely due to the JSX syntax. Long gone are the days where you have to concatenate strings to form HTML. More importantly, it's readable and truly scalable.

Unfortunately, it doesn't look like ReactJS is a viable option for me at this moment in time. I can see how it would be a very useful framework for building web applications where there is a requirement for the view to be created strictly client-side along with heavy use of AJAX calls from an API layer to serve data to your application. But in situations where you have two frameworks that provide the ability to create views, in this case ReactJS and ASP.NET MVC, it doesn't make sense.

I could be talking absolute nonsense and missing the whole point. If this is the case (most likely!), please leave a comment.

My Paypal REST API .NET Starter Kit

I've just completed working on a site that required PayPal integration to carry out credit card payments, using PayPal's REST API interface. I did find some aspects of the implementing PayPal's REST API a little confusing and there seemed to be a blurred line on what is the best approach. This is probably due to the vast number of .NET examples provided in PayPal's own Github repository.

I decided to create my own PayPal REST API .NET Starter kit, by combining my own efforts together with PayPal documentation and code examples from other developers online. Feel free to fork it from my Bitbucket repository: https://bitbucket.org/SurinderBhomra/paypal-.net-starter-kit.

The PayPal REST API .NET Starter kit contains everything you need to make a start in making your first card payment. It encompasses a very basic form to enter test transactions, as well as the following Nuget package references:

  • log4net
  • Newtonsoft.Json
  • PayPalCoreSDK
  • RestApiSDK

All you'll need to do is create is a PayPal Client ID and Secret, build the solution and away you go. Every time you make a transaction, a "PaypalPayment" object is returned, containing useful information to be used at application level if the payment was a success and if not, the full error information.

A successful transaction will generate the following invoice to the user's PayPal account:

PayPal Invoice Sample

I am using my PayPal Starter kit as a foundation to build upon if I ever get the opportunity to develop more features, such as refunds.

Feel free to modify my code and (even better!) add more features.

Web.Config/App.Config Maintainability

Web Configuration Snippet When working on large projects whether it be websites or software applications, I like to try and make sure that settings from within my app/web configuration files are not only easily accessible within code, but also maintainable for future updates.

Over the years, I have tried different approaches in an attempt to streamline how I use ASP.NET's configuration files within my day-to-day development and this is what I currently do.

Group and Alphabetise

I have started to sort all my settings alphabetically and group common functions/settings together. This alone makes navigating through a large configuration file much more easier.

In addition, having a standard in-house development style and agreeing with your fellow developers how sections within the configuration file is expected to be structured can be useful. For example, on web projects I've worked on, all key sections are  sorted in the following order:

  1. configSections
  2. appSettings
  3. connectionStrings
  4. system.web
  5. customErrors
  6. system.webServer
  7. locations

Common Naming Conventions

I like to name my appsettings in the following format: "<Setting-Group>.<Name>". So if I were to add appsettings that related to Twitter, it would look something as the following:

<add key="Twitter.ApiUrl" value="https://api.twitter.com/1.1/statuses/show.json" />
<add key="Twitter.ApiKey" value="" />
<add key="Twitter.ApiSecret" value="" />
<add key="Twitter.AccessToken" value="" />
<add key="Twitter.AccessTokenSecret" value="" />
<add key="Twitter.Username" value="shomra" />

This provides a simple and descriptive approach to breaking down all settings into manageable chunks.

Strongly-Type AppSettings

One thing that truly annoyed me when I first started .NET development is calling configuration values from within C# code. Not only did you have to write out (the very long-winded!) "ConfigurationManager.AppSettings["Twitter.Username"]", but also cast the value to a specific type.

Using the "ConfigurationManager.AppSettings[]" call is awful. It creates the potential for typo's that aren't caught by the compiler and there's no nice intellisense to make our coding easier.

I create a static configuration wrapper class to strongly-type all my config settings. The way I name my settings (as you can see from the "Common Naming Conventions" section) compliments the structure of my wrapper class.

public class Config
{
    public static class Social
    {
        #region Twitter

        public static class Twitter
        {
            public static string TwitterApiUrl
            {
                get
                {
                    return ConfigurationManager.AppSettings["Twitter.ApiUrl"];
                }
            }

            public static string TwitterUsername
            {
                get
                {
                    return ConfigurationManager.AppSettings["Twitter.Username"];
                }
            }

            public static string TwitterApiKey
            {
                get
                {
                    return ConfigurationManager.AppSettings["Twitter.ApiKey"];
                }
            }

            public static string TwitterApiSecret
            {
                get
                {
                    return ConfigurationManager.AppSettings["Twitter.ApiSecret"];
                }
            }

            public static string TwitterAccessToken
            {
                get
                {
                    return ConfigurationManager.AppSettings["Twitter.AccessToken"];
                }
            }

            public static string TwitterAccessTokenSecret
            {
                get
                {
                    return ConfigurationManager.AppSettings["Twitter.AccessTokenSecret"];
                }
            }
        }

        #endregion
    }
}

Admittingly, this can be quite time-consuming but when compared to the long-term benefits this has saved me a lot of headache.

What Else Could Be Done?

I've seen some developers use Applications Settings Architecture within the .NET framework when building Windows applications by creating a "Settings" file within the project.

The only downside I can see with this approach is that all config values declared within the "Settings" file will be compiled at runtime and you lose the flexibility to change configuration values externally. Here's a nice StackOverflow post that describes this method in greater detail.

If any of you readers have further suggestions or advice, I am all ears! :-)

The Future...

As Nick Dyer quite rightly pointed out to me yesterday on Google+, that our beloved web.config file will no longer form part of the applications we create in ASP.NET 5. We will have the freedom to create an applications configuration the way we want, simplifying the whole process.

As I understand, there will be support for creating configuration inputs that can be placed inside JSON, XML and INI files using the new IConfiguration and ConfigurationModel sources.

Sounds very promising.