Blog

Blogging on programming and life in general.

  • I have some websites on a production environment that need to be run from within a subdirectory and in order to carry out proper testing during development to ensure all references to CSS, JS and images files work. By default, when a .NET Core site is run from Visual Studio it will always start from the root, resulting in a broken looking page.

    From .NET Core 2.0, within your Startup.cs file, you can set a sub-directory using the UsePathBase extension within the Configure method:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
            app.UsePathBase("/mydirectory");
    }
    

    Now when the site runs, it’ll be accessible from /mydirectory. In my code example, I only want to set the path base if it development mode. When released to production, the path will be configured at IIS level.

    The only annoyance is when you run the site in Visual Studio, it will still start at the root and not at your newly declared subdirectory. I was surprised to see that the site is still accessible at the root, when you would expect the root path to be disabled or even greeted with a 404 response.

    On first glance, I thought there was a bug in my path base declaration and perhaps I missed something. After viewing a closed Github issue raised back in 2017, it was stated that this is in fact the intended functionality. This is a minor bug bear I can live with.

  • Published on
    -
    11 min read

    Maldives and Vilamendhoo Island Resort

    Writing about my holiday has started to become a bit of a tradition (for those that are worthy of such time and effort!) which seem to start when I went to Bali last year. I find it's a way to pass the time in airports and flights when making the return journey home. So here's another one...

    Sun, sea, sand and a beach facing hut with an open roofed bathroom... Yes, I have arrived at the Vilamendhoo Resort tucked away amongst the many other beautiful islands of the Maldives.

    I’m not supposed to be here...

    Vilamendhoo Island Resort - Entrance

    If I told you our holiday location and resort was in fact booked by mistake, you’d probably think I was lying. But that’s exactly what happened. Originally, the holiday was supposed to be at the Preskill Resort in Mauritius. I can almost understand the travel agent getting “Mauritius” and “Maldives” mixed up, but how can the resort name “Preskill” get misconstrued with “Vilamendhoo”? Yes, I haven’t quite figured out this conundrum myself. Obviously, the travel agent had other plans and we were both talking on deaf ears. In fact, I have a tendency to fall deaf on hearing two words: “All inclusive”. :-)

    It was only upon being emailed the confirmation and itinerary of the holiday, I did not get what I asked for. But being the type of person who is a great believer that there are no mistakes and everything happens for a reason through the twists and turns in the grand journey we call life, I carried out some research on my new destination and was indeed impressed what my I saw.

    The Maldives

    I’ve seen the Maldives through the visual haze of TV, internet and brochures. - All of which never truly sold the destination to me as I deemed everything always seemed too perfect. I think we all have experienced in the past where promotional pictures look nothing like the real thing. Thus never really planned on ever making a visit any time soon.

    Vilamendhoo Island Resort - Shoreline

    This could not be further from the truth.

    The Maldives is one of the god given gems that needs to be witnessed in reality. This only becomes apparent when you hop onto the propella powered plane from Male airport as you soar over the infinite blue seas and islands naturally formed in all shapes and sizes. Your eyes are in for a visual treat and will salivate in glee!

    The only variable is the accommodation you come to choose and Vilamendhoo did not disappoint.

    Getting there

    I was flying from the UK which encompassed:

    • Flight from Gatwick to Doha.
    • Connecting flight from Doha to Male International Airport.
    • A Maldivian domestic flight.
    • Small bus ride to the coast.
    • Speed boat ride that has multiple drop-off points to other island resorts depending on the number of people you’re with.

    Due to my lack of geographic knowledge and overall preparedness for this trip alone, I was quite naive in comprehending the effort involved. Getting to Vilamendhoo could have been a lot more straight-forward if I opted for a sea plane transfer from Male, something that only came to light after speaking to other holiday-goers. Paying extra for sea plane transfer alone saves you around an hour (excluding waiting time) of additional travel time.

    Villa Air Flight

    Male airport is the central hub and  for providing various transport to the final leg of your trip depending on where your final destination resides. Trying to find the connecting transport was a little confusing and lacked clarity. But the people there are more than happy to guide and help with queries.

    It’s a no thrills airport that lacks free Wi-Fi (a standard staple by todays standards) where you’ll need to make your own entertainment, so make sure you get some films and books downloaded prior to pass the time. Or, you could get your first glimpse of what is in store by sitting outside and watching the sea planes and yachts pass by as they float over the blue sea.

    Due to the effort alone to get here, I would recommend you plan on staying in the Maldives for a minimum of 10 days to make it more worthwhile. Next time I plan on staying longer... if my work lets me. ;-)

    Greetings - Vilamendhoo Style!

    Upon arriving at the dock by speed boat, you hear sounds of a beating drum as you are greeted by one of the representatives and make your short walk onto the island. After some quick form filling and introduction on over a refreshing mocktail at reception, we’re good to head on over to our accommodation.

    No Shoes, No News

    What I found immediately different at Vilamendhoo compared to the other resorts I’ve stayed at before is their motto: no shoes, no news, which is evident both by the workers and holiday-goers. You can literally walk around the island, enter reception, restaurants in bare feet! All floors surfaces (excluding spa and your hut) have a thick layer of sand. It’s just like walking on a beach everywhere you go! Being the Indian I am, I still walked around with flip-flops as some areas were a little to rough for my sensitive feet.

    Accommodation - Beach Villa

    Vilamendhoo has varied accommodation based on where you’d like to be situated on the island and most importantly price. There are four options to choose from (ordered by cheapest):

    1. Garden Room
    2. Beach Villa
    3. Jacuzzi Beach Villa
    4. Jacuzzi Water Villa

    The Garden Room and Beach Villa offer the same amenities with the main difference being the location as highlighted by the name. If you can afford to pay a little extra definitely go for the Beach Villa, you won’t regret it. How can you afford not to? You basically have a little slice of your own beach front paradise meters away from the sea. I found this to be the most exceptional location if you’re fond of snorkelling.

    When I think back to my last years holiday in Bali where to access the beach was a 5-10 minute walk, the Beach Villa’s close proximity is a priceless gift that takes the headache out of a casual swim in the sea. It’s also worth noting at this point, you won’t be hassled by anyone trying to sell you anything whilst you lounge on the beach, which was a regular occurrence in Bali.

    Vilamendhoo Island Resort - Jacuzzi Water Villa

    Talking about the Water Villa’s would cause me too much mental anguish as I have seen how truly amazing they are situated in a prime location over the water in the lagoon. The Beach Villa’s might be close to the sea, but the Water Villa’s takes things to the next level with private sundeck and stairs to the sea. Who knows... maybe next time?

    I stayed in one of the many Beach Villa’s dotted around the coast of the island. My Beach Villa faced the south side where you will see much boat activity and if you gaze further into the distance you’ll see Dhangethi island (it’s the one with a massive satellite aerial sticking up). The upside of this location is its closeness to the reception, restaurant and bar. However, I would recommend the north side with its calmer waters due to little to no boat activity and its slightly lusher sands (I swear it feels different!).

    The villa is clean, simple and very comfortable with its tropical decor, controllable air conditioning and king size bed. At 55 sqm you have a lot of space to move around and at no time do you feel cramped. TV is an important factor wherever I go on holiday for times when I just want to do nothing. Luckily, you are provided with a 32 inch cable TV with access to a wide range of channels. I found myself watching a film every night before bed. Now thats what I call luxury!

    One thing you’ll find very different is the bathroom. It’s slightly outdoors, or “open air” as Vilamendhoo like to call it. So you’ll find yourself either looking up at the sky, surrounding trees or the mini garden that forms part of the bathroom during a shower or toilet time. It feels very liberating! You’re literally one with nature as nature calls.

    Vilamendhoo Island Resort - Bathroom Garden

    The included shower gels, shampoo and creams are exceptional. I’m an absolute snob when it comes to resort/hotel provided washes as they are just dirt cheap, but I was pleasantly surprised by the naturally derived Healing Earth collection. So feel free to leave your own toiletries behind as (like myself) more than likely will not be using them.

    Dining - Food Glorious Food!

    If you’re an “all inclusive” customer you will not go hungry. It’s worth every penny - I’ll be talking more about this further down in this post.

    The main dining area is the Funama Restaurant housed on the south-side at the centre of the island, making it easily accessible wherever your hut is.

    On your first day entering the dining area you will be given a table that will be your assigned seating throughout your stay which I originally thought would be quite restrictive. I was wrong. It’s efficient and you get a sense of familiarity with the staff who serve you. It’s at this point I have to make a shout out to our waiter - Ilham. He provided first class service, gave me a plaster for my cut toe (yes!) and shared his wealth of knowledge of the resort and made recommendations throughout our stay. Miss that guy! He deserves a promotion!

    The variety of food on offer is astounding that spans across breakfast, lunch and dinner. It’s a buffet each day. Every evening the food on offer is based on a theme, such as (to name a few):

    • Maldivian
    • Indian
    • Italian
    • Chinese

    If you’re not too keen on an evenings theme, there are still other dishes on offer that might be to your liking. It’s impossible for you not to be content with whats on offer.

    The food is outstanding across all meal time with a lot of variety. We were tempted to dine at the Asian Wok, but the quality buffet suited us perfectly. All the chefs really do deserve a lot of gratitude just due to the sheer quality and quantity of the food. The service runs like clockwork. As soon as one dish is all consumed in the buffet area, it’s quickly refilled.

    The desserts are pure bite-sized art forms.

    Vilamendhoo Island Resort - Desserts

    From looking at a handful of other reviews, some have commented on the ambience of the dining hall and the dim lighting. Ignore the naysayers. I quite like it!

    Excursions

    Being my first time visiting Vilamendhoo Island and the Maldives, I didn’t actually partake in many excursions for the sole reason of feeling an overwhelming sense of contentment. You are surrounded by serene beauty wherever you look. Judging by the serendipitous chain of events where the stars aligned, going to the Maldives has been a long time in the coming - I just didn’t know it.

    The handful of excursions I did get involved in consisted of:

    • Kayaking
    • Tour of Dhangethi Island
    • Spa (can I class this as a excursion?)
    • Sunset Punch Cruise

    Kayaking

    If you’re not 100% confident in going further out to sea or extreme water sports, kayaking is the way to go. You can take your time and go as far out to sea as you feel able to within the 45 minute session. Vilamendhoo Island is deceivingly small and if (like myself) you decided to kayak your way around the island, this can be done in around 30 minutes with time to spare for a little messing around.

    Tour of Dhangethi Island

    Dhangethi - Boat HullDhangethi is a is a 20-30 minute boat ride away - the island with a massive phone antenna you can see over the horizon from the south-side of Vilamendhoo.

    The island is home to a small population of Maldivians with its primary infrastructure involving fishing, construction, boat building and tourism. Some areas you will have the chance to see for yourself during the guided tour of the island along with the local schools and hospital. For me the highlight was viewing an under construction hull of a ship. An image doesn’t give justice on the sheer scale.

    At the time of the tour I wasn’t very impressed with the island visit probably because I was expecting something different, but my opinion has somewhat changed as I look back and adjusted my expectations. It was a fascinating insight looking into the culture of the locals and history.

    The only thing I would say is the trip was 30 minutes too long. Myself and others found ourselves lingering near the dock waiting to leave.

    I wasn’t too interested in the souvenir shops as there wasn’t anything of interest to me, however I heard the goods were a lot cheaper when compared to the Vilamendhoo’s own shop.

    Sunset Punch Cruise

    The Sunset Punch Cruise is a late afternoon boat trip that takes you further out to sea and immerse yourself in the tropical sunset with a glass of Vilamendhoo’s “special (non-alcoholic) punch”. You may even encounter some dolphins along the way. It was uncertain to whether we’d actually see any dolphins as the cruise from the previous day had no such encounter so our expectations were kept to a minimum.

    I am glad to report that luck was on our side and what we experienced wasn’t just a brief encounter, it was a close encounter of the third kind! Not only did we see shoal of dolphins but they were also swimming up close interweaving around the bow of the ship for quite sometime in all their splendour, to the amazement and awe to all on the cruise. Sometimes I question myself on whether I really did see those dolphins and I wasn’t on some form of hallucinogenic trip from the “special punch”. Thankfully, I can confirm I was of sound mental state as I have photographic evidence such an experience did exist. :-)

    Even though the likelihood of seeing the dolphins is little hit or miss, I would still recommend this excursion just to have a chance of seeing these wonderful creatures in a close proximity.

    Go All Inclusive!

    Vilamendhoo Island Resort - Asian Wok Restaurant At Night

    If you are able to get an “All Inclusive” package at a good price, just go for it! It’s freedom! You know from a cost perspective you will not have to spend a penny more as long as you stay within the confines of the inclusive package. All in all, it’s a good value as you will also get some excursions free or at a discounted rate.

    As humans we have a natural instinct of wanting more and there is always a hint of disappointment when you find some red tape when in this case “All Inclusive” has some limitations. I would list the limitations out but they are so minor it isn’t really worth mentioning.

    It would be nice is Asian Wok was part of the inclusive package or offered a few meals, but I can understand why they don’t offer this as it lacks the table capacity unlike Funama Restaurant.

    Final Thoughts

    Going to the Maldives is what I can only call a proper holiday. You have an excuse to just relax and take life at the pace you wish. You could spend all your days being very active scuba diving, windsurfing and snorkelling through the crystal clear waters. Or just sit back on the sun lounger with a couple drinks to hand letting the days pass by. Generally, when on holiday I find myself feeling the need to explore the local surroundings to make the most of it - not here. While away your days guilt free!

    Vilamendhoo is small island of paradise that puts a spell on you from the very first moment you step onto the dock to the sound of the welcoming beat of the drum.

  • There are times when what I want to express does not form into words, which is very much unlike me if I look back at my journey through blogging. I'm noticing more so than ever that writers block is becoming a regular occurrence resulting in a lack the energy to write my thoughts on subjects of interest.

    One blogs not just for others, but for themselves!

    I sometimes question if a post is worth the time it takes to write as it might not even be of interest to anyone. This in itself is not the right attitude. One blogs not just for others, but most importantly for themselves! This is what I have to keep telling myself during times of self doubt. I have always had the opinion that if I manage to help just one person from one of my posts, then it's truly a job well done!

    Every blogger has a process they go through before publishing a post. I have the problem of just wanting to get a post out as quickly as humanly possible just to see the end result, to the detriment of quality. Over the last few years, this small site of mine has gained traction from both readers and search engines (the stats speak for themselves) and it is during this time I constantly fight to reign myself in to ensure the content I put out is up to the mark. Maybe I am putting too much pressure on focusing on the numbers (Google Analytics, Adsense, etc) than words.

    I look at my blogging heroes like Scott Hanselman, Troy Hunt, Mosh Hamedani and Iris Classon (to name a few) and at times ponder if I will have the ability to churn out great posts on a regular basis with such ease and critical acclaim as they do. Do they even experience writers block? What is their writing process?

    As for my process, it’s changed somewhat. Writing has become more of a special event rather than on an ad-hoc basis, where I now schedule time within the comfort of my new office setup (still need to blog about that!) a couple of times weekly to write and plan future posts. In addition, before getting into the nitty-gritty detail, I’ve learnt to create a skeletal structure first to outline a posts milestones. I’ve ended up doing this across the initial stages of many posts, as I gather my thought processes.

    The new approach has also made writing less daunting and more manageable as I am not just focusing all my efforts on producing a single post alone. I literally have an Evernote notebook created specifically with a collection of post ideas. Some bear fruit, some don’t.

    I like to end this post on a positive note. The upside of this situation is I know deep down writing is a release for me and it’s not something I could ever grow tired of. Yes, it can be frustrating at times, but I will continue to write when I can and even more so when I can’t. It’ll show progress and how far I’ve come.

  • Making the transition in moving photos from physical to digital form can be quite an undertaking depending on the volume of photos you have to work with. Traditional flat-bed scanning and Photoshop combinations aren’t really up to the task if you want a process that requires minimal manual intervention. It can all be quite cumbersome, from placing the photo correctly on the scanner to then carrying out any photo enhancements, cropping and exporting. Yes, you get a fantastic digital print but it comes at a cost - time.

    If you are really serious in digitising a bulk load of photos, there are a couple viable options:

    1. Photo-scanning service where you post all the photos you wish to digitise. The costs can be relatively low (around 1p per photo) and is good if you have a specific number of photo’s to digitise.
    2. Purchase a photo scanner where photos are scanned manually in a document feeding process, which makes for a less intensive job.

    Due to the large number of photos that have accumulated over the years, I preferred to purchase a photo scanner. Sending off photos to a photo-scanning service didn’t seem viable and could prove quite costly. I also had the fear of sending over photos via post where I do not have the original negatives. They could be lost in transit or handled incorrectly by the photo-scanning service. Not a risk I was willing to take. Photos are precious memories - a snapshot of history.

    The most ideal photo scanner for a job of this undertaking needs to be sheet-fed, where the photos are fed through a scanning mechanism. There are quite a number of these type of scanners, mostly being document scanners, which isn’t the type of scanner you want. From personal experience I found document scanners lack the resolution required and the feeding mechanism can be quite rough on photos.

    I decided to go for the Plustek ePhoto Z300 as it seems to fit the bill at a really good price (at time to writing £170).

    Initial Impressions

    The Plustek scanner doesn’t look like a scanner you’ve ever seen and almost looks other worldly. Due to its upright position, it requires very little real-estate on your desk when compared to a flat-bed scanner.

    All functions are performed from the software you can download from the Plustek site or via the CD provided in the box. Once the software is installed and scanner calibrated you’re good to go.

    Software

    I’m generally very reluctant to install software provided directly by hardware manufacturers as they encompass some form of bloatware and prefer a minimum install of just the drivers. The software provided by Plustek is very minimal and does exactly what it says on the tin - no thrills!

    Just to be sure you’re running the most up-to-date software, head over to the Plustek site.

    When your photos are scanned you’ll be presented with thumbnails in the interface where you can export a single or group selection of images to the following formats:

    • JPG
    • PDF
    • PNG
    • TIFF
    • Bitmap

    I exported all my scans to JPEG in high quality.

    There is a slight bug-bare with the Mac OS version of the software as it doesn't seem to be as stable as its Windows counterpart. This only became apparent after installing the software on my Dad’s computer running on Windows. I noticed when you have collected quite a few scans, the Mac OS version seems to lag and crash randomly, something that doesn’t seem to occur on a Windows machine. This is very annoying after you’ve been scanning over a 100 photos.

    The hardware specifications on both machines are high running on i7 processors and 16GB of RAM, so the only anomaly is the software itself. A more stable Mac OS version of the scanning software would be welcome. In the meantime, I would recommend Mac users to regularly save small batches of their scans.

    The Scanning Process

    The speed of scanning varies depending on the resolution set from within the software, where you have either 300 or 600 dpi to choose from. I scanned all my prints at 600 dpi, which taken around 15 seconds to scan each 4x6 photo, whereas 300 dpi was done in a matter of seconds. I wanted to get to the best resolution for my digitised photos and thought it was worth the extra scanning time opting for 600 dpi.

    Even though Plustek ePhoto Z300 is a manually fed scanner, I was concerned that I would have to carry out some form of post-editing in the software. By enabling "Auto crop and auto deskew” and “Apply quick fix” within the scan settings, all my photos were auto-corrected very well even when accidentally feeding a photo a that wasn’t quite level.

    To save time in correcting the rotation of your images post-scan, just always ensure you feed the photos top first.

    Conclusion

    The Plustek Scanner performs very well both on price and performance. I have been pretty happy with the quality when scanning photos in either black and white or colour.

    The only thing that didn’t come to mind at time of purchase is scanning is a very manual process, especially when churning through hundreds of photos. It would be great if Plustek had another version of the Z300 that encompassed an automatic feeding mechanism. There were times when I would feed in the next photo before the currently scanned photo had finished, resulting in two photos scanned into one. This didn’t become a regular occurrence once you have got into the flow of the scanning process.

    Not having an automatic feeding mechanism is not at all a deal breaker at this price. You get a more than adequate photo scanner that makes the tedious job of digitising batches of photos somewhat surmountable.

  • Published on
    -
    5 min read

    Generate Code Name For Tags In Kentico

    With every Kentico release that goes by, I am always hopeful that they will somehow add code name support to Tags where a unique text-based identifier is created, just like Categories (via CategoryName field). I find the inclusion of code names very useful when used in URL as wildcards to filter a list of records, such as blog posts.

    In a blog listing page, you'll normally have the ability to filter by both category or tag and to make things nice for SEO, we include them in our URLs, for example:

    • /Blog/Category/Kentico
    • /Blog/Tag/Kentico-Cloud

    This is easy to carry out when dealing with categories as every category you create has "CategoryName" field, which strips out any special characters and is unique, fit to use in slug form within a URL! We're not so lucky when it comes to dealing with Tags. In the past, to allow the user to filter my blog posts by tag, the URL was formatted to look something like this: /Blog/Tag/185-Kentico-Cloud, where the number denotes the Tag ID to be parsed into my code for querying.

    Not the nicest form.

    The only way to get around this was to customise how Kentico stores its tags on creation and update, without impacting its out-of-the-box functionality. This could be done by creating a new table that would store newly created tags in code name form and link back to Kentico's CMS_Tag table.

    Tag Code Name Table

    The approach on how you'd create your table is up to you. It could be something created directly in the database, a custom table or module. I opted to create a new class name under one of my existing custom modules that groups all site-wide functionality. I called the table: SurinderBhomra_SiteTag.

    The SurinderBhomra_SiteTag consists of the following columns:

    • SiteTagID (int)
    • SiteTagGuid (uniqueidentifier)
    • SiteTagLastModified (datetime)
    • TagID (int)
    • TagCodeName (nvarchar(200))

    If you create your table through Kentico, the first four columns will automatically be generated. The "TagID" column is our link back to the CMS_Tag table.

    Object and Document Events

    Whenever a tag is inserted or updated, we want to populate our new SiteTag table with this information. This can be done through ObjectEvents.

    public class ObjectGlobalEvents : Module
    {
        // Module class constructor, the system registers the module under the name "ObjectGlobalEvents"
        public ObjectGlobalEvents() : base("ObjectGlobalEvents")
        {
        }
    
        // Contains initialization code that is executed when the application starts
        protected override void OnInit()
        {
          base.OnInit();
    
          // Assigns custom handlers to events
          ObjectEvents.Insert.After += ObjectEvents_Insert_After;
          ObjectEvents.Update.After += ObjectEvents_Update_After;
        }
    
        private void ObjectEvents_Insert_After(object sender, ObjectEventArgs e)
        {
          if (e.Object.TypeInfo.ObjectClassName.ClassNameEqualTo("cms.tag"))
          {
            SetSiteTag(e.Object.GetIntegerValue("TagID", 0), e.Object.GetStringValue("TagName", string.Empty));
          }
        }
    
        private void ObjectEvents_Update_After(object sender, ObjectEventArgs e)
        {
          if (e.Object.TypeInfo.ObjectClassName.ClassNameEqualTo("cms.tag"))
          {
            SetSiteTag(e.Object.GetIntegerValue("TagID", 0), e.Object.GetStringValue("TagName", string.Empty));
          }
        }
    
        /// <summary>
        /// Adds a new site tag, if it doesn't exist already.
        /// </summary>
        /// <param name="tagId"></param>
        /// <param name="tagName"></param>
        private static void SetSiteTag(int tagId, string tagName)
        {
          SiteTagInfo siteTag = SiteTagInfoProvider.GetSiteTags()
                                .WhereEquals("TagID", tagId)
                                .TopN(1)
                                .FirstOrDefault();
    
          if (siteTag == null)
          {
            siteTag = new SiteTagInfo
            {
              TagID = tagId,
              TagCodeName = tagName.ToSlug(), // The .ToSlug() is an extenstion method that strips out all special characters via regex.
            };
    
            SiteTagInfoProvider.SetSiteTagInfo(siteTag);
          }
        }
    }
    

    We also need to take into consideration when a document is deleted and carry out some cleanup to ensure tags no longer assigned to any document are deleted from our new table:

    public class DocumentGlobalEvents : Module
    {
        // Module class constructor, the system registers the module under the name "DocumentGlobalEvents"
        public DocumentGlobalEvents() : base("DocumentGlobalEvents")
        {
        }
    
        // Contains initialization code that is executed when the application starts
        protected override void OnInit()
        {
          base.OnInit();
    
          // Assigns custom handlers to events
          DocumentEvents.Delete.After += Document_Delete_After;
        }
    
        private void Document_Delete_After(object sender, DocumentEventArgs e)
        {
          TreeNode doc = e.Node;
          TreeProvider tp = e.TreeProvider;
    
          GlobalEventFunctions.DeleteSiteTags(doc);
        }
    
        /// <summary>
        /// Deletes Site Tags linked to CMS_Tag.
        /// </summary>
        /// <param name="tnDoc"></param>
        private static void DeleteSiteTags(TreeNode tnDoc)
        {
          string docTag = tnDoc.GetStringValue("DocumentTags", string.Empty);
    
          if (!string.IsNullOrEmpty(docTag))
          {
            foreach (string tag in docTag.Split(','))
            {
              TagInfo cmsTag = TagInfoProvider.GetTags()
                               .WhereEquals("TagName", tag)
                               .Column("TagCount")
                               .FirstOrDefault();
    
              // If the the tag is no longer stored, we can delete from SiteTag table.
              if (cmsTag?.TagCount == null)
              {
                List<SiteTagInfo> siteTags = SiteTagInfoProvider.GetSiteTags()
                                     .WhereEquals("TagCodeName", tag.ToSlug())
                                     .TypedResult
                                     .ToList();
                if (siteTags?.Count > 0)
                {
                  foreach (SiteTagInfo siteTag in siteTags)
                    SiteTagInfoProvider.DeleteSiteTagInfo(siteTag);
                }
              }
            }
          }
        }
    }
    

    Displaying Tags In Page

    To return all tags linked to a page by its "DocumentID", a few of SQL joins need to be used to start our journey across the following tables:

    1. CMS_DocumentTag
    2. CMS_Tag
    3. SurinderBhomra_SiteTag

    Nothing Kentico's Object Query API can't handle.

    /// <summary>
    /// Gets all tags for a document.
    /// </summary>
    /// <param name="documentId"></param>
    /// <returns></returns>
    public static DataSet GetDocumentTags(int documentId)
    {
      DataSet tags = DocumentTagInfoProvider.GetDocumentTags()
                        .WhereID("DocumentID", documentId)
                        .Source(src => src.Join<TagInfo>("CMS_Tag.TagID", "CMS_DocumentTag.TagID"))
                        .Source(src => src.Join<SiteTagInfo>("SurinderBhomra_SiteTag.TagID", "CMS_DocumentTag.TagID"))
                        .Columns("TagName", "TagCodeName")
                        .Result;
    
      if (!DataHelper.DataSourceIsEmpty(tags))
        return tags;
    
      return null;
    }
    

    Conclusion

    We now have our tags working much like categories, where we have a display name field (CMS_Tag.TagName) and a code name (SurinderBhomra_SiteTag.TagCodeName). Going forward, any new tags that contain spaces or special characters will be sanitised and nicely presented when used in a URL. My blog demonstrates the use of this functionality.

  • My Fujifilm X100F camera only comes out of hibernation when I go on holiday. Most of the time, I fail to ensure my camera settings are correct before I take the very first snap. This happened on my last holiday to Loch Lomond.

    When it came to the job of carrying out some image processing from RAW to JPEG, I noticed all of my photos EXIF dates were incorrect. I am such stickler for correct EXIF information, including geolocation wherever possible. EXIF information is so useful for cataloging when consumed by photo applications, whether it’s on my Synology or uploaded to Google Photos.

    Due to the high number of photos with incorrect date stamps, I needed a tool that will automate the correction process. After a bit of Googling, I found an application called exiftool by Phil Harvey that allows the EXIF date/time stamp to be modified using a method in the documentation called “Shift”.

    The exiftool has no GUI (graphical user interface) and will need to be run in Terminal on a Mac or command line for Windows users. The command to use is relatively simple and the only complex thing you will have to do is calculate how many days, months, years, hours, minutes and seconds you need to add or subtract.

    In my case, the calculation was a matter of subtracting 3 days from all the photos and the command to do this looks like the following:

    exiftool -AllDates-='0:0:3 0:0:0' -m /Volumes/LochLomond
    

    Lets breakdown the command to get a better understanding what each part does.

    • exiftool: Runs the application and you have to ensure that your Terminal/Command Line is run in the same directory exiftool is housed.
    • AllDates: Modifies all dates in a photo.
    • -=‘0:0:3 0:0:0’: Subtract 3 days off the photos exif date. If you wanted to add 3 days, use “+=” instead. The date time format is presented as “<year>:<month>:<day> <hours>:<minute>:<second>”.
    • -m: Ignore minor errors and warnings (as stated in the documentation).
    • /Volumes/LochLomond: Location to where all the photos reside.

    When making mass changes to files, it’s always recommended to ensure you have a back up of all photos for you too fallback on if you accidentally mess up the EXIF update.

  • There are times when you need to know what widgets are being used on a page. In my case, I needed to know this information to render JavaScript code at the bottom of the page that each of my widgets depends on.

    Why don't I just place all the JavaScript code my site and widgets use in one file? Loading one large JavaScript file isn't the best approach for page performance. Instead, I use LabJS to dynamically load scripts in specific execution order without blocking other resources. So if I created a Carousel widget in Kentico, I would only load the JavaScript plugin if added to the page.

    I'll use my JavaScript scenario as a basis for demonstrating the way to list out widgets used in a page.

    If we delve into the CMS_Document table, Kentico uses the "DocumentPageBuilderWidgets" field that stores a JSON structure consisting of a list of all the widgets and their property values. All we are interested in is the type property.

    Let's get to the code.

    Controller - SharedController

    I created a SharedController class containing a GenerateWidgetJavascript() PartialViewResult. This will convert the JSON from the "DocumentPageBuilderWidgets" field into a JSON Object to then be queried (using SelectTokens) to select every iteration of the type field in the hierarchy.

    /// <summary>
    /// Get widget used on a page to render any required JavaScript.
    /// </summary>
    /// <returns></returns>
    public PartialViewResult GenerateWidgetJavascript()
    {
        List<string> widgetTypes = new List<string>();
    
        if (Page.GetStringValue("DocumentPageBuilderWidgets", string.Empty) != string.Empty)
        {
            JObject pageWidgetJson = JObject.Parse(Page.GetStringValue("DocumentPageBuilderWidgets", string.Empty));
    
            if (pageWidgetJson != null)
                widgetTypes = pageWidgetJson.SelectTokens("$.editableAreas[*].sections[*].zones[*].widgets[*].type").Select(jt => jt.ToString().Substring(jt.ToString().LastIndexOf('.') + 1)).Distinct().ToList();
        }
    
        return PartialView("Widgets/_PageWidgetJs", widgetTypes);
    }
    

    Additional manipulation is carried out on the type field using LINQ to return a distinct set of results, as there might be a case where the same widget is used multiple times on a page. Since I name all my widgets in the following format - <SiteName>.<WidgetGroup>.<WidgetName>, I am only interested in the <WidgetName>. For example, my widget would be called "SurinderSite.Layout.Carousel". The controller will simply output "Carousel".

    To avoid confusion in my code snippet, it's worth noting I use a Page variable. This contains information about the current page and is populated in my base controller. It has a type of TreeNode. You can see my approach to getting the current page information in this post.

    Partial View - _PageWidgets

    The most suitable place to add my widget dependent JavaScript is in the /View/Shared/Widgets directory - part of the recommended Kentico project structure.

    All we need to do in the view is iterate through the string collection of widget types and have a bunch of if-conditions to render the necessary JavaScript.

    @model List<string>
    
    @if (Model.Any())
    {
        <script>
            @foreach (string widgetType in Model)
            {
                if (widgetType == "Carousel")
                {
                    <text>
                        $LAB
                            .script("/resources/js/plugins/slick.min.js")
                            .script("/resources/js/carousel.min.js").wait(function () {
                                {
                                    FECarousel.Init();
                                }
                            });
                    </text>
                }
            }
        </script>
    }
    

    Layout View

    The Layout view will be the best place to add the reference to our controller in the following way:

    @{ Html.RenderAction("GenerateWidgetJavascript", "Shared"); }
    
  • Following up on my previous post about the joy that is using my new iPad Air, I thought I’d write about what I deem are essential accessories and applications. It’s only been a couple of weeks since making my purchase and has surprisingly found the transition from Android to iOS not too much of a pain. It’s fast becoming part of my daily workflow for creative writing and note-taking.

    Here are some applications and accessories I use…

    Accessories

    Keyboard Case

    Apple’s own Smart Keyboard Cover felt very unnatural to use and didn’t provide enough protection for my nice new tablet. The Inateck Keyboard Case is an absolute pleasure to use and the keys have a very nice responsive rebound. I can literally use this anywhere and feels just as stable on my lap as it is when being used on a desk.

    The only downside is the connectivity relies on Bluetooth rather than Apple’s own Smart connector which would normally power the keyboard. Nevertheless, the pairing has no latency and the battery lasts weeks even with daily usage.

    Apple Pencil

    The iPad Air is only compatible with the first generation pencil and has a really ridiculous way to charge using the lightning connector. Apple could have quite easily made the iPad Air work with the second generation pencil. If the iPad Pro was a cracker, then the second generation pencil would be the caviar.

    Regardless of the design, it’s refreshing to scribble away notes to store electronically. Previously to keep track of my written notes, I would write on paper (oh how old fashioned!?) and then scan digitally using Evernote on my phone.

    Draw Screen Protector

    Writing on glass using the Apple Pencil is a little slippery and need something that gives the texture to almost simulate the friction you would get when writing on paper. There are a handful of screen protectors that provide this with varying degrees of success. The most popular being is Paperlike, which I plan on putting an order for when I’ve worn out my current screen protector.

    My current screen protector is Nillkin and isn’t too bad. It provides adequate protection as well as giving enough texture with enough anti-reflection qualities that doesn’t hinder screen visibility. Added bonus: a nice light scratchy sound as you'd expect if writing with an old-fashioned pencil!

    Applications

    I'm deliberately leaving out the most obvious and well-known apps that we are well aware of such as YouTube, Netflix, Gmail, Kindle, Twitter, Spotify etc.

    Jump Desktop

    I wrote about this very briefly in my previous post. If you want a link to your laptop/workstation from your iPad, Jump Desktop is your best option. Once you have the application installed on your iPad and host machine you are up and running in minutes. Judging by past updates, it’s getting better with every release.

    Evernote

    I don’t think I can speak about Evernote highly enough. I am a premium member and is one of my most highly used applications across all mediums. Worth every penny! It organises my notes, scribbles and agendas with little effort.

    Evernote is effectively my brain dump of ideas.

    Notes haven’t looked so good with the use of a recent feature - Templates. On creation of a new note, you have the option to select a predefined template based from the many Evernote provides from their own Template Gallery.

    Grammarly

    Grammarly is a must for all writers to improve the readability of your content. I myself had only started using Grammarly since last year and now can't think of writing a post without it. In the iPad form, Grammarly forms part of the keyboard that carries out checks as you type. This works quite well with my writing workflow when using Evernote.

    Autodesk Sketchbook

    If the Apple Pencil has done anything for me, is to allow me to experiment more with what it can do and in the process allowing me to try things I don’t generally do. In this case, sketch! I would be lying if I said Autodesk Sketchbook is the best drawing apps out there as I haven’t used any others. For an app that is free, it has a wide variety of features that will accommodate both novice and experts alike.

    1.1.1.1

    Developed by the team who brought you the Cloudflare CDN infrastructure comes 1.1.1.1, an app for providing faster and more private internet. This is something I always have running in the background to have a form or protection using public hotspots and to stop my ISP from snooping where I go on the internet.

    When compared to other DNS directory services, Cloudflare touts 1.1.1.1 as the fastest. As everything you do on the internet starts with a DNS request, choosing the fastest DNS directory will accelerate the online experience.

  • I’ve been looking for a tablet for quite some time and doing some in-depth research on the best one to get. I am always a stickler for detail and wanting to get best for the time based on budget and specification.

    Only having ever owned two tablets in the past - an iPad 2 and Nexus 7. Being someone who has semented himself in the Android/Google ecosystem, I automatically got along with the Nexus and quickly became my daily driver for web browsing and reading the vast variety of books from Amazon and Google Books. That was 5-6 years ago. The tablet game has changed... No longer is it just about viewing information, watching videos with some minor swipe gestures and basic gaming. It’s more!

    Ever since Microsoft released the first version of their Surface tablet computer, it shifted the industry standards to what we should now expect from a tablet, which then led to more innovation such as:

    • Keyboard support
    • Writing with palm rejection (not that old school stylus from yesteryear!)
    • Multitasking with the ability to view multiple apps in one screen, which is only getting better by the day!
    • Near laptop replacement - We’ll go into this a little later

    I wasn’t so quick to jump on the new iterations of tablets entering the market as I was waiting to see the proof in the pudding and for prices to go down. I just don’t think its worth spending over £600 on a tablet - looking at you iPad Pro! Nevertheless, from initially piquing my interest, it now got my full attention. For the first in a long time, I could see how having a tablet be useful in my day to day activities again.

    Do I Really Need A Tablet?

    Short answer: Yes.

    If you asked me this question last year I would have more than likely have said no. My Pixel 2 smartphone fit the bill for for my portable needs. Tablet life was soon being relegated to just holidays and long weekends away.

    The only thing that has changed is the increased amount of blogging and writing I now do. Typing on a smartphone really made my thumbs tired for long periods of time for when I didn’t have a computer to hand. On the other hand, I found lugging around my MacBook Pro 15" just for writing was a little excessive and lacking all day battery life.

    I could see myself buying a tablet along with a Bluetooth keyboard for easy quick note taking for when going to conferences and for writing something a little more indepth. For anyone who writes, they will probably tell you when you have a sudden spark of inspiration you need to just write it down.

    Conundrum: To Android or Not To Android

    There seems to be a real lack of good Android tablets going around that has good build quality, vanilla OS with accessories to match. It’s guaranteed that if you go for an Android tablet, you’ll be subjected to inferior cheap cases and hardware. This was indeed the case when looking for a nice flip case for my old Nexus 7.

    One would be forgiven for being given the impression accessory manufacturers don’t give Android tablets the light of day - very annoying. I still love the nice leather Kavaj case I purchased soon after getting my iPad 2. My iPad 2 may not be getting used, but still looks the part resting on the bookshelf! I guess it’s understandable why accessory manufacturers are not providing the goods for where there is limited demand. It all comes down to a lack of flagship Android devices and I was hoping the Pixel Slate would change this. Not a chance! I really wanted to go for the Pixel Slate but the main unknown factor for me is the longevity of a device that starts at £749.

    The only choice was to consider an iPad.

    What About The Microsoft Surface?

    The Microsoft Surface is a computer powerhouse and if I needed another laptop, this would have been a great purchase. I look forward to owining one in the future. Again, it all comes down to price. You have to take into consideration the cost of the computer itself as well as the added type cover. Plus I feared I would be greeted with a long Windows Update when I have a sudden spark of writing inspiration.

    Choosing An iPad

    Apple’s have positioned their iPad lineup that should meet all demand:

    I opted for the iPad Air for the 10.5” screen, A12 Bionic processor and Smart connector. The Smart connector was something previously available to the Pro series only and it was a welcome addition to their mid range tablet as this will give me the ability to connect a keyboard cover and any further peripherals that maybe on the horizon. Future proof!

    The performance and multi-tasking support is pretty good as well. I am writing this very post with Evernote in one window, Chrome in another whilst listening to Spotify.

    Near Laptop Replacement and iPad OS

    I have no expectation to make the iPad a laptop replacement. But it’s the nearest experience to it. In all honesty, I don’t understand how some even find the iPad Pro a complete replacement. Would someone enlighten me?

    I found using Jump Desktop to remote onto my laptop a really good way to get a laptop experience on my iPad. Very useful when I need to use applications I’d never be able to run on a tablet like VMWare. Jump Desktop is one of the best remote desktop applications you can use on an iPad.

    Jump Desktop features one of the fastest RDP rendering engines on the planet. Built in-house and hand tuned for high performance on mobile devices. Jump’s RDP engine also supports audio streaming, printer and folder sharing, multi-monitors, touch redirection, RD Gateway and international keyboards.

    I am really looking forward to the release of iPad OS as it might lead to a more immersive experience that bridges the gap closer to the basic features we expect from a laptop. I always felt iOS still lacks some of features currently present in Android, such as widgets to see app activity at a glance and more control over your files.

    Conclusion

    If you’re looking for a tablet that has the capability to do a lot of wonderful things with a lot of nice supported accessories, you can’t go wrong with an iPad Air.

    Update (21/06/2019): Android Abandoning The Tablet Market

    Well this totally caught me off guard. The Verge reported on 20th June that Android are exiting the tablet market and concentrating their efforts on building laptops. This further validates my purchase and choosing an iPad was indeed the right decision.

  • I should start off by saying how much I love TortoiseGit and it has always been the reliable source control medium, even though it's a bit of a nightmare to set up initially to work alongside Bitbucket. But due to a new development environment for an external project, I am kinda forced to use preinstalled Git programs:

    • SourceTree
    • Git Bash

    I am more inclined to use a GUI when interacting with my repositories and use the command line when necessary.

    One thing that has been missing from SourceTree ever since it was released, is the ability to export changes over multiple commits. I was hoping after many years this feature would be incorporated. Alas, no. After Googling around, I came across a StackOverflow post that showed the only way to export changes in Sourcetree based on multiple commits is by using a combination of the git archive and git diff commands:

    git archive --output=archived_changes.zip HEAD $(git diff --diff-filter=ACMRTUXB --name-only hash1 hash2)
    

    This can be run directly using the Terminal window for a repository in Sourcetree. The "hash1" and "hash2" values are the long 40 character length commit ID's.

    The StackOverflow post has helped me in what I needed to achieve and as a learning process, I want to take things a step further in my post to understand what the archive command is actually doing for my own learning. So let's dissect the command into manageable chunks.

    Part 1

    git archive --output=archived_changes.zip HEAD
    

    This creates the archive of the whole repository into a zip file. We can take things further in the next section to select the commits we need.

    Part 2

    git diff --diff-filter=ACMRTUXB
    

    The git diff command shows changes in between commits. The filter option gives us more flexibility to select the files that are:

    • A Added
    • C Copied
    • D Deleted
    • M Modified
    • R Renamed
    • T have their type (mode) changed
    • U Unmerged
    • X Unknown
    • B have had their pairing Broken

    Part 3

    --name-only hash1 hash2
    

    The second part of the git diff command uses the "name-only" option that just shows which files have changed over multiple commits based on the hash values entered.

    Part 4

    The git diff command needs to be wrapped around parentheses to act as a parameter for the git archive command.