Blog

Tagged by 'asp.net'

  • Sometimes the simplest piece of development can be the most rewarding and I think my Azure Function that checks for broken links on a nightly basis is one of those things. The Azure Function reads from a list of links from a database table and carries out a check to determine if a 200 response is returned. If not, the link will be logged and sent to a user by email using the Sendgrid API.

    Scenario

    I was working on a project that takes a list of products from an API and stores them in a Hubspot HubDB table. This table contained all product information and the expected URL to a page. All the CMS pages had to be created manually and assigned the URL as stored in the table, which in turn would allow the page to be populated with product data.

    As you can expect, the disadvantage of manually created pages is that a URL change in the HubDB table will result in a broken page. Not ideal! In this case, the likelihood of a URL being changed is rare. All I needed was a checker to ensure I was made aware on the odd occasion where a link to the product page could be broken.

    I won't go into any further detail but rest assured, there was an entirely legitimate reason for this approach in the grand scheme of the project.

    Azure Function

    I have modified my original code purely for simplification.

    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Extensions.Logging;
    using SendGrid;
    using SendGrid.Helpers.Mail;
    
    namespace ProductsSyncApp
    {
      public static class ProductLinkChecker
      {
        [FunctionName("ProductLinkChecker")]
        public static void Run([TimerTrigger("%ProductLinkCheckerCronTime%"
          #if DEBUG
          , RunOnStartup=true
          #endif
          )]TimerInfo myTimer, ILogger log)
        {
          log.LogInformation($"Product Link Checker started at: {DateTime.Now:G}");
    
          #region Iterate through all product links and output the ones that return 404.
    
          List<string> brokenProductLinks = new List<string>();
    
          foreach (string link in GetProductLinks())
          {
            if (!IsEndpointAvailable(link))
              brokenProductLinks.Add(link);
          }
    
          #endregion
    
          #region Send Email
    
          if (brokenProductLinks.Count > 0)
            SendEmail(Environment.GetEnvironmentVariable("Sendgrid.FromEmailAddress"), Environment.GetEnvironmentVariable("Sendgrid.ToAddress"), "www.contoso.com - Broken Link Report", EmailBody(brokenProductLinks));
    
          #endregion
    
          log.LogInformation($"Product Link Checker ended at: {DateTime.Now:G}");
        }
    
        /// <summary>
        /// Get list of a product links.
        /// This would come from a datasource somewhere containing a list of correctly expected URL's.
        /// </summary>
        /// <returns></returns>
        private static List<string> GetProductLinks()
        {
          return new List<string>
          {
            "https://www.contoso.com/product/brokenlink1",
            "https://www.contoso.com/product/brokenlink2",
            "https://www.contoso.com/product/brokenlink3",
          };
        }
    
        /// <summary>
        /// Checks if a URL endpoint is available.
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private static bool IsEndpointAvailable(string url)
        {
          try
          {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    
            using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    
            if (response.StatusCode == HttpStatusCode.OK)
              return true;
    
            return false;
          }
          catch
          {
            return false;
          }
        }
    
        /// <summary>
        /// Create the email body.
        /// </summary>
        /// <param name="brokenLinks"></param>
        /// <returns></returns>
        private static string EmailBody(List<string> brokenLinks)
        {
          StringBuilder body = new StringBuilder();
    
          body.Append("<p>To whom it may concern,</p>");
          body.Append("<p>The following product URL's are broken:");
    
          body.Append("<ul>");
    
          foreach (string link in brokenLinks)
            body.Append($"<li>{link}</li>");
    
          body.Append("</ul>");
    
          body.Append("<p>Many thanks.</p>");
    
          return body.ToString();
        }
    
        /// <summary>
        /// Send email through SendGrid.
        /// </summary>
        /// <param name="fromAddress"></param>
        /// <param name="toAddress"></param>
        /// <param name="subject"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        private static Response SendEmail(string fromAddress, string toAddress, string subject, string body)
        {
          SendGridClient client = new SendGridClient(Environment.GetEnvironmentVariable("SendGrid.ApiKey"));
    
          SendGridMessage sendGridMessage = new SendGridMessage
          {
            From = new EmailAddress(fromAddress, "Product Link Report"),
          };
    
          sendGridMessage.AddTo(toAddress);
          sendGridMessage.SetSubject(subject);
          sendGridMessage.AddContent("text/html", body);
    
          return Task.Run(() => client.SendEmailAsync(sendGridMessage)).Result;
        }
      }
    }
    

    Here's a rundown on what is happening:

    1. A list of links is returned from the GetProductLinks() method. This will contain a list of correct links that should be accessible on the website.
    2. Loop through all the links and carry out a check against the IsEndpointAvailable() method. This method carries out a simple check to see if the link returns a 200 response. If not, it'll be marked as broken.
    3. Add any link marked as broken to the brokenProductLinks collection.
    4. If there are broken links, send an email handled by SendGrid.

    As you can see, the code itself is very simple and the only thing that needs to be customised for your use is the GetProductLinks method, which will need to output a list of expected links that a site should contain for cross-referencing.

    Email Send Out

    When using Azure functions, you can't use the standard .NET approach to send emails and Microsoft recommends that an authenticated SMTP relay service that reduces the likelihood of email providers rejecting the message. More insight into this can be found in the following StackOverflow post - Not able to connect to smtp from Azure Cloud Service.

    When it comes to SMTP relay services, SendGrid comes up favourably and being someone who uses it in their current workplace, it was my natural inclination to make use of it in my Azure Function. Plus, they've made things easy by providing a Nuget package to allow direct access to their Web API v3 endpoints.

  • After renaming my MVC project from "SurinderBhomra" to "Site.Web" (to come across less self-serving) I get the following error:

    Compiler Error Message: CS0246: The type or namespace name 'xxx' could not be found (are you missing a using directive or an assembly reference?)

    CS0246 Compiler Error - Temporary ASP.NET Files

    But the misleading part of this compiler error is the source file reference to ASP.NET Temporary Files directory, which led me to believe that my build was been cached when it was actually to do with the fact I missed some areas where the old project name still remained.

    I carried out a careful rename throughout my project by updating all the places that mattered, such as:

    • Namespaces
    • Using statements
    • "AssemblyTitle" and "AssemblyProduct" attributes in AssemblyInfo.cs
    • Assembly name and Default Namespace in Project properties (followed by a rebuild)

    The key area I missed that caused the above compiler error is overlooking the the namespace section in the /Views/web.config file.

    <system.web.webpages.razor>
      <host factorytype="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <pages pagebasetype="System.Web.Mvc.WebViewPage">
        <namespaces>
          ...
          ...
          ...
          <add namespace="System.Web">
          ...
          ...
          ...
        </add></namespaces>
      </pages>
    </host></system.web.webpages.razor>
    

    When you first create your project in Visual Studio, it automatically adds its original namespace to this file. This also goes for any other web.config you happen to have nested in other areas inside you MVC project.

  • The title of this post might seem a tad extreme, but I just feel so strongly about it! Ever since I started learning ASP.NET those many years ago, I've never been a fan of using "Eval" in data-bound controls I primarily use, such as GridViews, Repeaters and DataList. When I see it still being used regularly in web applications I cringe a little and I feel I need to express some reasons to why it should stop being used.

    I think working on an application handed down to me from an external development agency pushed me to write this post... Let's call it a form of therapy! I won't make this post a rant and will "try" to be constructive and concise. My views might come across a little one-sided, but I promise I will start with at least one good thing to say about our evil friend Eval.

    Postive: Quick To Render Simple Data

    If the end goal is to list out some string values as is from the database with some minor manipulation from a relatively small dataset, I almost have no problem with that, even though I still believe it can be used and abused by inexperienced developers.

    Negative: Debugging


    The main disadvantage of embedding code inside your design file (.aspx or .ascx) is that it's not very easy to view the output during debugging. This causes a further headache when your Eval contains some conditional statements to alter the output on a row-by-row basis.

    Negative: Difficult To Carry Out Complex HTML Changes

    I wouldn't recommend using Eval in scenario's where databound rows require some form of HTML change. I've seen some ugly implementations where complex conditional statements were used to list out data in a creative way. If the HTML ever had to be changed through design updates, it would be a lot more time consuming to carry when compared to moving around some form controls that are databound through a RowDataBound event.

    Negative: Ugly To Look At

    This point will come across very superficial. Nevertheless, what I find painful to look at is when Eval is still used to carry out more functionality by calling additional methods and potentially repeating the same functionality numerous times.

    Performance/Efficiency

    From my research, it's not clear if there specifically is a performance impact in using Eval alone, especially with the advances in the .NET framework over the years. A post from 2012 on StackExchange brought up a very good point:

    Eval uses reflection to get the value of the relevant property/field, and using Reflection to get values from object members is very slow.

    If the type of an object can be determined at runtime, you're better off explicitly declaring this. After all, it's good coding standards. In the real world, the performance impact is nominal depending on the number of records you are dealing with. Not recommended for building scalable applications. I generally notice a slow down (in milliseconds) when outputting 500 rows of data.

    I have read that reflection is not as much of an issue in the most recent versions of the .NET framework when compared to say, .NET 1.1. But I am unable to find any concrete evidence of this. Regardless, I'd always prefer to use the faster approach, even if I am happening to shave off a few milliseconds in the process.

    Conclusion

    Just don't use Eval. Regardless of the size of the dataset I am dealing with, there would only be two approaches I'd ever use:

    1. RowDataBoundEvent: A controls RowDataBoundEvent event is triggered every time a row is databound with data. This approach enables us to modify the rows appearance and structure in a specific way depending on the type of rules we have in place.
    2. Start From Scratch: Construct the HTML markup by hand based on the datasource and render to the page.

    If I were to be building a scalable application dealing with thousands of rows of data, I am generally inclined to go for option 2. As you're not relying on a .NET control, you won't be contributing to the page viewstate.

    Even though I have been working on a lot more applications using MVC where I have more control on streamlining the page output, I still have to dabble with Web Forms. I feel with Web Forms, it's very easy to make a page that performs really bad, which makes it even more important to ensure you are taking all necessary steps to ensure efficiency.

  • I have been doing a lot of Saleforce integration lately, which has been both interesting and fun. Throughout my time working on Salesforce, I noticed that I am making very similar calls when pulling information out for consumption into my website. So I decided to make an extra effort to develop methods that would allow me to re-use commonly used functionality into a class library to make overall coding quicker.

    I am adding all my Salesforce object query related functionality to a class object called "ObjectDetailInfoProvider". This will give me enough scope to expand with additional methods as I see fit.

    To start with, I decided to deal with returning all information from both picklist and multi-select picklists fields, since I find that I constantly require the values of data due to the vast number of forms I am developing. To be extra efficient in every request, I taken the extra step to cache all returned data for a set period of time. I hate the idea of constantly hammering away at an API unless absolutely necessary.

    Before we get into it, it's worth noting that I am referencing a custom "AuthenticationResponse" class I created. You can grab the code here.

    Objects

    There are around seven class objects used purely for deserialization when receiving data from Salesforce. I'll admit I won't use all fields the API has to offer, but I normally like to have a complete fieldset to hand on the event I require further data manipulation.

    The one to highlight out of all the class objects is "ObjectFieldPicklistValue", that will store key information about the picklist values, such as Label, Value and Active state. All methods will return this object.

    public class ObjectFieldPicklistValue
    {
        [JsonProperty("active")]
        public bool Active { get; set; }
    
        [JsonProperty("defaultValue")]
        public bool DefaultValue { get; set; }
    
        [JsonProperty("label")]
        public string Label { get; set; }
    
        [JsonProperty("validFor")]
        public string ValidFor { get; set; }
    
        [JsonProperty("value")]
        public string Value { get; set; }
    }
    

    I have added all other Object Field class objects to a snippets section on my Bitbucket account.

    GetPicklistFieldItems() & GetMultiSelectPicklistFieldItems() Methods

    Both methods perform similar functions; the only difference is cache keys and lambda expression to only pull out either a picklist or multipicklist by its field name.

    /// <summary>
    /// Gets a values from a specific picklist within a Salesforce object. Items returned are cached for 15 minutes.
    /// </summary>
    /// <param name="objectApiName"></param>
    /// <param name="pickListFieldName"></param>
    /// <returns>Pick list values</returns>
    public static async Task<List<ObjectFieldPicklistValue>> GetPicklistFieldItems(string objectApiName, string pickListFieldName)
    {
        string cacheKey = $"GetPicklistFieldItems|{objectApiName}|{pickListFieldName}";
    
        List<ObjectFieldPicklistValue> pickListValues = CacheEngine.Get<List<ObjectFieldPicklistValue>>(cacheKey);
    
        if (pickListValues == null)
        {
            Authentication salesforceAuth = await AuthenticationResponse.Rest();
    
            HttpClient queryClient = new HttpClient();
    
            string apiUrl = $"{SalesforceConfig.PlatformUrl}services/data/v37.0/sobjects/{objectApiName}/describe";
    
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiUrl);
            request.Headers.Add("Authorization", $"Bearer {salesforceAuth.AccessToken}");
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    
            HttpResponseMessage response = await queryClient.SendAsync(request);
    
            string outputJson = await response.Content.ReadAsStringAsync();
    
            if (!string.IsNullOrEmpty(outputJson))
            {
                // Get all the fields information from the object.
                ObjectFieldInfo objectField = JsonConvert.DeserializeObject<ObjectFieldInfo>(outputJson);
    
                // Filter the fields to get the required picklist.
                ObjectField pickListField = objectField.Fields.FirstOrDefault(of => of.Name == pickListFieldName && of.Type == "picklist");
                        
                List<ObjectFieldPicklistValue> picklistItems = pickListField?.PicklistValues.ToList();
    
                #region Set cache
    
                pickListValues = picklistItems;
    
                // Add collection of pick list items to cache.
                CacheEngine.Add(picklistItems, cacheKey, 15);
    
                #endregion
            }
        }
    
        return pickListValues;
    }
    
    /// <summary>
    /// Gets a values from a specific multi-select picklist within a Salesforce object. Items returned are cached for 15 minutes.
    /// </summary>
    /// <param name="objectApiName"></param>
    /// <param name="pickListFieldName"></param>
    /// <returns>Pick list values</returns>
    public static async Task<List<ObjectFieldPicklistValue>> GetMultiSelectPicklistFieldItems(string objectApiName, string pickListFieldName)
    {
        string cacheKey = $"GetMultiSelectPicklistFieldItems|{objectApiName}|{pickListFieldName}";
    
        List<ObjectFieldPicklistValue> pickListValues = CacheEngine.Get<List<ObjectFieldPicklistValue>>(cacheKey);
    
        if (pickListValues == null)
        {
            Authentication salesforceAuth = await AuthenticationResponse.Rest();
    
            HttpClient queryClient = new HttpClient();
    
            string apiUrl = $"{SalesforceConfig.PlatformUrl}services/data/v37.0/sobjects/{objectApiName}/describe";
    
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiUrl);
            request.Headers.Add("Authorization", $"Bearer {salesforceAuth.AccessToken}");
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
            HttpResponseMessage response = await queryClient.SendAsync(request);
    
            string outputJson = await response.Content.ReadAsStringAsync();
    
            if (!string.IsNullOrEmpty(outputJson))
            {
                // Get all the fields information from the object.
                ObjectFieldInfo objectField = JsonConvert.DeserializeObject<ObjectFieldInfo>(outputJson);
    
                // Filter the fields to get the required picklist.
                ObjectField pickListField = objectField.Fields.FirstOrDefault(of => of.Name == pickListFieldName && of.Type == "multipicklist");
    
                List<ObjectFieldPicklistValue> picklistItems = pickListField?.PicklistValues.ToList();
    
                #region Set cache
    
                pickListValues = picklistItems;
    
                // Add collection of pick list items to cache.
                CacheEngine.Add(picklistItems, cacheKey, 15);
    
                #endregion
            }
        }
    
        return pickListValues;
    }
    
  • 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.

  • 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.

  • After quite a successful stint in creating some really amazing websites using MVC Razor 5, I had to revert back to working in the world of Web Forms for a new client project. Now I have to deal with .NET web controls, Postbacks and Viewstates.

    When creating a few simple forms consisting of some drop down lists, textboxes and textareas, I noticed wherever I set a "MaxLength" property it would not work. A character limit on my input field would just be ignored in all browsers. I could only replicate this bug when the Max Length property is used alongside the "TextMode" property.

    There were various places where I had the "TextMode" property set to either "Number" or "MultiLine". Soon as this was removed, the value set in "MaxLength" would work.

    How very odd...

    Thankfully, I am not the only person to experience the same issue. Currently, the only way to get around this problem is to add the following JavaScript code (slightly refactored for my own implementation) to check the character length "onKeyDown".

    var ASPNETForm = {
        "CheckTextAreaLength": function(textBox, e, length) {
            var mLen = textBox["MaxLength"];
    
            if (null == mLen)
                mLen = length;
    
            var maxLength = parseInt(mLen);
            if (!ASPNETForm.CheckSpecialKeys(e)) {
                if (textBox.value.length > maxLength - 1) {
                    if (window.event) { //IE
                        e.returnValue = false;
                        return false;
                    }
                    else //Firefox
                        e.preventDefault();
                }
            }
        },
        "CheckSpecialKeys": function(e) {
            if (e.keyCode != 8 && e.keyCode != 46 && e.keyCode != 35 && e.keyCode != 36 && e.keyCode != 37 && e.keyCode != 38 && e.keyCode != 39 && e.keyCode != 40)
                return false;
            else
                return true;
        }
    }
    

    Add to your ASP.NET TextBox control as so:

    <asp:textbox id="Instructions" MaxLength="50" onkeydown="return ASPNETForm.CheckTextAreaLength(this,event,'50');" runat="server" textmode="MultiLine">
    </asp:textbox>
    

    I am hoping there will be a more elegant solution in ASP.NET 5, or even better, a fix. This problem has been lurking around since 2009!

  • Changing the contents of a robots.txt file when a site is moved from staging to a live environment is quite a manual and somewhat cumbersome process. I sometimes have the fear of forgetting to replace the "Disallow: /" line with correct indexable entries required for the website.

    To give me one less thing to remember during my pre-live deployments, all my current and upcoming ASP.NET MVC sites will use a dynamic robots.txt file containing different entries depending on whether a site is in stage or live. In order to do this, we need to let the ASP.NET MVC application serve up the robots.txt file. This can be done by the following:

    • Create a new controller called "SEOController"
    • Add a new FileContentResult action called "Robots".
    • Add a new route.
    • Modify web.config.

    SEOController

    I have created a new controller that renders our "Robots" action as a plain text file. As you can see, my controller is not a type of "ActionResult" but a "FileContentResult". The great thing about "FileContentResult" is that it allows us to return bytes from the controller.

    In this example, I am converting bytes from a string using Encoding.UTF8.GetBytes() method. Ideal for what we need to generate a robots.txt file.

    [Route("robots.txt")]
    [Route("Robots.txt")]
    public FileContentResult Robots()
    {
        StringBuilder robotsEntries = new StringBuilder();
        robotsEntries.AppendLine("User-agent: *");
    
        //If the website is in debug mode, then set the robots.txt file to not index the site.
        if (System.Web.HttpContext.Current.IsDebuggingEnabled)
        {
            robotsEntries.AppendLine("Disallow: /");
        }
        else
        {
            robotsEntries.AppendLine("Disallow: /Error");
            robotsEntries.AppendLine("Disallow: /resources");
            robotsEntries.AppendLine("Sitemap: http://www.surinderbhomra.com/sitemap.xml");
        }
    
        return File(Encoding.UTF8.GetBytes(robotsEntries.ToString()), "text/plain");
    }
    

    RouteConfig

    Since I add my routing at controller level, I add the "MapMvcAttributeRoutes" method to the RouteConfig.cs file. But if you prefer to add your routes directly here, then this method can be removed.

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            routes.MapMvcAttributeRoutes(); //Add this line!
    
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
    

    Web.config

    Add a "runAllManagedModulesForAllRequests" attribute to the modules tag in the web.config file to allow our robot.txt text file to be rendered by ASP.NET.

    <system.webserver>
      <modules runallmanagedmodulesforallrequests="true"></modules>
    </system.webserver>
    
  • When running my website through Google Page Insights, one of things I didn't do was cache static content, such as CSS, JavaScript and site images. Since I am on a shared hosting plan, I didn't think it was possible to have the option to cache a specific directory without direct IIS access.

    Normally, when working on client sites hosted on a dedicated server, I set the cache header within "HTTP Response Headers" area in IIS. But all this actually does is generate a web.config file within the directory you wish to cache:

    <!--?xml version="1.0" encoding="UTF-8"?-->
    <configuration>
        <system.webServer>
            <httpProtocol>
                <customHeaders>
                    <add name="Cache-Control" value="public, max-age=604800" />
                </customHeaders>
            </httpProtocol>
        </system.webServer>
    </configuration>
    

    So if you too are on shared hosting, add a web.config file with similar settings. In this case, I have cached my files for a week.

    You can also set the cache settings in your main web.config file by wrapping a location path around the <system.webServer> node:

    <location path="resources">
      <system.webServer>
            <httpProtocol>
                <customHeaders>
                    <add name="Cache-Control" value="public, max-age=604800" />
                </customHeaders>
            </httpProtocol>
        </system.webServer>
     </location>
    
  • One of the many nice things of using ASP.NET MVC Razor is that you have full control over how you segregate your HTML markup when building a page through rendering PartialViews. Since becoming an avid MVC developer, I am increasingly noticing how easy it is to make nice neat reusable code, whether it is used server or client-side.

    Just today, I found something really useful that is a truly defines this, where markup within PartialViews can be output to a page as string:

    /// <summary>
    /// Controller extension class that adds controller methods
    /// to render a partial view and return the result as string.
    ///
    /// Based on http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/
    /// </summary>
    public static class ControllerExtension
    {
     
      /// <summary>
      /// Renders a (partial) view to string.
      /// </summary>
      /// <param name="controller">Controller to extend</param>
      /// <param name="viewName">(Partial) view to render</param>
      /// <returns>Rendered (partial) view as string</returns>
      public static string RenderPartialViewToString(this Controller controller, string viewName)
      {
        return controller.RenderPartialViewToString(viewName, null);
      }
     
      /// <summary>
      /// Renders a (partial) view to string.
      /// </summary>
      /// <param name="controller">Controller to extend</param>
      /// <param name="viewName">(Partial) view to render</param>
      /// <param name="model">Model</param>
      /// <returns>Rendered (partial) view as string</returns>
      public static string RenderPartialViewToString(this Controller controller, string viewName, object model)
      {
        if (string.IsNullOrEmpty(viewName))
          viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
     
          controller.ViewData.Model = model;
     
          using (var sw = new StringWriter())
          {
            var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
            var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);
     
            return sw.GetStringBuilder().ToString();
          }
        } 
    }
    

    I can't take credit for this code. But here is the guy who can: Jan Jonas.

    Being able to output PartialViews as a string is actually quite handy, since you could have a paginated news listings page that displays the first page of articles server-side and any additional pages could be loaded in via jQuery Ajax. Each article item would be a PartialView so you could serve the same markup client-side. My code below probably explains things a little better:

    Article Listing View

    This page will list all my News Articles. As you can see, I am using an "ArticleListItem" as my PartialView.

    @model List<Article>
    
    @if (Model.Any())
    {
        <div class="article-list">
        @foreach (var a in Model.Select((value, index) => new { value, index }))
        {
            Html.RenderPartial("/Views/Article/_ArticleListItem.cshtml", new ArticleListItemView { Article = a.value, CssClass = ArticleHtmlHelper.GetItemCssClass((a.index + 1)), IsFullWidth = false});
        }
        </div>
    }
    else
    {
        <div>
            No articles could be returned.
        </div>
    }
    

    Article List Item PartialView

    My PartialView has quite a bit going on to determine how the markup should be rendered and it's definitely something I wouldn't want to have to duplicate elsewhere just to load in client-side. Nice!

    @model Site.Web.Models.Views.ArticleListItemView
    @{
        string fullWidthClass = String.Empty;
    
        if (Model.IsFullWidth)
        {
            fullWidthClass = "full-width";
        }
    }
    <div class="article-summary @Model.CssClass @fullWidthClass">
        <a href="@Model.Article.PageUrl" class="img">
            @if (Model.CssClass == "large")
            {
            <img src="@Model.Article.Images.ImageCollection[1].Url" />
            }
            else
            {
            <img src="@Model.Article.Images.ImageCollection[0].Url" />
            }
        </a>
        @if (Model.Article.Category != null)
        {
        <span class="cat">@Model.Article.Category.Name</span>
        }
        @if (Model.Article.ReadTime != null)
        {
        <span class="time">@String.Format("{0} read", Model.Article.ReadTime)</span>
        }
        <h2 class="@Model.CssClass"><a href="@Model.Article.PageUrl">@Model.Article.Title</a></h2>
        @if (Model.Article.Author != null)
        {
        <a href="@Model.Article.Author.PageUrl.Url" class="author">
            <img src="@Model.Article.Author.Images.ImageCollection[0].Url" />
            <span>@String.Concat(Model.Article.Author.FirstName, " ", Model.Article.Author.LastName)</span>
        </a>
        }
    </div>
    

    GetArticleItems() Controller

    This is where the RenderPartialViewToString() method shines! This controller is called within my jQuery Ajax function to get the next page of news articles. I am then calling my "ArticleListItem" PartialView to return the HTML markup as a string through my client-side call.

    [HttpPost]
    public JsonResult GetArticleItems(DBContext ctx, int pageNo, int pageSize, string categoryId)
    {
        ApiDocumentInfo docInfo = DocumentHelper.SearchDocuments(ctx, true, "article", "category", categoryId, pageSize, pageNo, "articles", "date desc");
    
        List<Article> articles = docInfo.Documents.Select(doc => doc.ToArticle(ctx)).ToList();
    
        StringBuilder articleHtml = new StringBuilder();
    
        if (articles.Any())
        {
            for (int a = 0; a < articles.Count; a++)
                articleHtml.Append(this.RenderPartialViewToString("_ArticleListItem", new ArticleListItemView { Article = articles[a], CssClass = ArticleHtmlHelper.GetItemCssClass((a + 1)), IsFullWidth = false } ));
        }
    
        return Json(articleHtml.ToString());
    }