Tag: Front End

JavaScript frameworks explained to an ASP.NET dev

For most of my career I've been an ASP.NET dev and a JavaScript dev. If I was going to say I was more of an expert in one of them it would be the .NET side of things but I've never really lost touch with JavaScript.

Right now I think it's fair to say technologies in the world are starting to shift how we build websites, with JavaScript frameworks reaching a point with features like static site generation where they actually now offer a decent performance incentive to use them. At some point Blazor may get to a point where it reverses this, but right now there's a compelling argument to move.

For a ASP.NET dev this can be a daunting task. You might be thinking of trying out a headless CMS with a JavaScript front end, but just take a look at this screen grab from Prismic's sdk list.

There's 7 different JavaScript based SDK's listed there! Over half of the total and none of them are that Angular thing you had heard about. Where do you start?

Lets compare to .NET

Well recently I've been updating my JS skills again trying out some of the frameworks I hadn't used before, so I thought I'd share some learnings. The good news is as always it's not really as different as it first seems. To take some of the pain out of understanding what all these frameworks are I thought it would be good to try and relate them back to .NET and what the almost equivalent is.

Assembly Code

No not actual assembler but what does our code actually compile to. In the .NET world we have CIL (Common Intermediate Language), previously known as MSIL (Microsoft Intermediate Language) that our C#, F#, VB etc all compile down to before then being converted to the correct machine code for where they run.

In the front end world think of JavaScript being a bit like this (apart from the fact you actually write JavaScript and we don't write CIL).

View Engine

To render views into HTML, in the ASP.NET world we have Razor, but not just Razor. We also have WebForm, Brail, Bellevue, NDjango (see more here), it just happens that we mostly just use Razor.

I see the equivalents of these being ReactJS, VueJS and Angular. Its not an exact match as they also aren't exact equivalents or each other, but they're largely your functionality that will take a model and turn it into HTML.

Web Application Framework

The problem with the name framework is it applies to basically anything, but this is what I'm going with for describing ASP.NET MVC/ASP.NET Razor Pages/Web Forms, you know all those things built on-top of .NET that make it a website rather than a desktop app. They do things like routing, organising our files into controller and view folders, know how to respond to http requests etc.

Here we have Next.js, Nuxt.js and maybe Gatsby. The link between these and View Engine is a bit stronger than the ASP.NET MVC world as you essentially have a one to one mapping Next.js -> React, Nuxt.js -> Vue but they are what adds routing, static site generation and organization to your code.

Lower Level Framework

Now this one could be wrong :)

In .NET we have different version of the framework. e.g. .NET Framework /3.5/4, .NET Core, .NET 5, Mono. On the front end side they have Node.

Languages

In .NET we have choices including C#, F#, VB among other.

JavaScript has JavaScript (which I know I said was assembly), TypeScript, Coffee Script maybe more.

Not so daunting

There's probably a bunch of flaws with my comparison list and reasons people can point out why things I've said are the same are in fact different, but my point was really to show that while .NET may appear as one button on a SDK list alongside 7 JavaScript based SDK's its not that different. Out of the 7 Node is based on JavaScript. Vue and React are based on Node, and Next/Gatsby/Nuxt are based on Vue/React. There just isn't the same concept of all of it being built by one company or one particular combination being dominant in the same way that ASP.NET MVC + C# + Razor has been for the last generation of .NET websites.

Bundling and Minification with Sitecore

There's various ways to add bundling and minification to a site, but one of the easiest is to use Microsoft's support from the Microsoft.AspNet.Web.Optimization package. This implementation has some great features including:

  • Automatically create the bundles and minify them as files are changed
  • Create unique urls to each version of a bundle to force browser refreshing of the file
  • Debug mode which outputs links to the raw files rather than the bundled minified version

The functionality is also fully compatible with Sitecore. To use it in your Sitecore solution follow these steps:

1. Add Microsoft.AspNet.Web.Optimization to your project from NuGet. This is the package from Microsoft that contains the functionality to do bundling and minification

 

2. Create your CSS and Javascript bundles. Where you put the logic for this is up to you but it will need to run when the application starts. Outside of Sitecore my main development is on bespoke ASP.NET MVC and Web API projects so I like to organise startup scripts into an App_Start folder and reference it from the Application_Start event in the global ascx file.

If you are running a Sitecore instance with multiple sites or if you do not have direct access to the production config files, it may be better to keep the logic separate and use a pipeline to create the bundles.

My bundle definitions would look something like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Optimization;

public class BundleConfig
{
  public static void RegisterBundles(BundleCollection bundles)
  {
      bundles.Add(new ScriptBundle("~/bundles/scripts").Include(
                  "~/Scripts/jquery-{version}.js",
                  "~/Scripts/bootstrap.js"));

      bundles.Add(new StyleBundle("~/bundles/css").Include(
          "~/Content/bootstrap.css",
          "~/css/site.css"));
  }
}

And in my Global.asax.cs file I would have some logic like this to call the other function

protected void Application_Start(object sender, EventArgs e)
{
  BundleConfig.RegisterBundles(System.Web.Optimization.BundleTable.Bundles);
}

3. Replace your Javascript/CSS references in your layout or rendering files with a call to render the bundle

Webforms

<%: System.Web.Optimization.Scripts.Render("~/bundles/scripts") %>
<%: System.Web.Optimization.Styles.Render("~/bundles/css") %>

MVC

@Scripts.Render("~/bundles/scripts") 
@Styles.Render("~/bundles/css")

4. In your web.config file there are a couple of changes needed to get things to work.

First you need to set an ignore url prefix to stop Sitecore trying to resolve the URL to the bundle. In this example mine is called bundles (note: you should add the prefix the what is already in your config file. e.g. |/bundles)

<!--  IGNORE URLS
    Set IgnoreUrlPrefixes to a '|' separated list of url prefixes that should not be
    regarded and processed as friendly urls (ie. forms etc.)
-->
<setting name="IgnoreUrlPrefixes" value="/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing|/bundles" />

Next a dependant assembly binding needs to be set up for Web Grease. Without it you will see an error about the Web Grease version number

<dependentAssembly>
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
</dependentAssembly>

If you have done this and are still getting a version number error check the assembly binding tag. Older versions of Sitecore have the applies to property set which may be only applying the bindings to .Net 2 and you may be using .Net 4.

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" appliesTo="v2.0.50727">

5. If you are using IIS7 you will also need to make a change in your web.config file

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
  </modules>
</system.webServer>

6. Publish your changes into your Sitecore site. Depending if you have compilation mode set to debug or not you will now either have a bundle reference for all your JavaScript and CSS or the individual file references.

Responsive Images in Sitecore

By default, when you add an image to a page in Sitecore using the <sc:Image > control it will render height and width attributes on the resulting html. These are based on the images dimensions or any height and width properties you may have set server side.

The benefit of doing this is that when the browser renders the page, it already knows what space the image will take up before the image has loaded. This reduces the number of repaints the browser has to make and ultimately speeds up the page rendering time. If the height and width weren't specified then a repaint would happen after the image was loaded as the size would now be known.

However this can cause an issue when you are producing a responsive site that changes its layout based on the size of a device or width of a browser window. Because responsive design is done though CSS media tags the server does not know what the height and width of the image will be, unfortunately there's also no built in way to tell Sitecore to stop rendering the height and width tags. Not very good when you want to size your image to 100% width of an area that can change size.

The solution is to create a Pipeline. Pipelines are Sitecore's way of enabling you change how Sitecore works and their not overly complex to implement.

To create a pipeline that will affect the way an Image field is rendered your first need to create a GetImageFieldValueResponsive class as follows. The name of the class can be anything you want but I've called it GetImageFieldValueResponsive as it will be called after Sitecores GetImageFieldValue class.

namespace SitecoreCustomization.Pipelines.RenderField
{
  public class GetImageFieldValueResponsive
  {
      public void Process(RenderFieldArgs args)
      {
          if (args.FieldTypeKey != "image")
              return;
          if (args.Parameters.ContainsKey("responsive"))
          {
              string imageTag = args.Result.FirstPart;
              imageTag = Regex.Replace(imageTag, @"(&lt;img[^&gt;]*?)\s+height\s*=\s*\S+", "$1", RegexOptions.IgnoreCase);
              imageTag = Regex.Replace(imageTag, @"(&lt;img[^&gt;]*?)\s+width\s*=\s*\S+", "$1", RegexOptions.IgnoreCase);
              imageTag = Regex.Replace(imageTag, @"(&lt;img[^&gt;]*?)\s+responsive\s*=\s*\S+", "$1", RegexOptions.IgnoreCase);
              args.Result.FirstPart = imageTag;

          }
      }
  }
}

The process function in the class is what will get called as the control is rendered. First it will check that the field type is an image and then will look to see if there is a parameter called responsive. This will allow us to control when Sitecore should output height and width tags and when it shouldn't. Lastly some Regex is used to remove the height, width and responsive properties from the image tag that will already have been generated by this point.

Next we need to tell Sitecore to start using this pipeline. We do this by creating a RenderField.config file and placing it in the App_Config\Include folder of your Sitecore solution.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
  <pipelines>
    <renderField>
      <processor
          patch:after="*[@type='Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel']"
          type="SitecoreCustomization.Pipelines.RenderField.GetImageFieldValueResponsive, DLLNameOfWhereYouCreatedTheClass"/>
    </renderField>
  </pipelines>
</sitecore>
</configuration>

Lastly your control will need to have an attribute to say it should be responsive. For an xslt rendering your code will look like this:

<sc:image field="Image" responsive="1" />

And for an ascx sublayout it will look like this:

<sc:Image runat="server" Field="Image" Parameters="responsive=1" />