#24 Picture Tag Helper
Sunday, March 31, 2019
In this article we will round out our image related tag helpers by creating a picture tag helper. The code required to generate the required html is a little bit more involved due to the nature of the attributes required for the source tags but we will manage to sort them out here. We finish up by updating our base class so that we can specify a class, id, and/or additional attributes, such as data dash attributes, in the rendered html using our tag helpers.
Parts
- Part 29: Offset Pager Urls
- Part 28: Offset Pager Start
- Part 27: Mock Context Builder
- Part 26: Mock Repository
- Part 25: Mock Async
- Part 24: Picture Tag Helper
- Part 23: Img DPR Tag Helper
- Part 22: Img Responsive Tag Helper
- Part 21: Img Optimized Display
- Part 20: Img Optimization
- Part 19: Img Lazy Loading
- Part 18: Img Responsive
- Part 17: Bottom Nav
- Part 16: Main Nav Cookie
- Part 15: Main Nav Mobile
- Part 14: Main Nav Search
- Part 13: Main Nav Auth
- Part 12: Main Nav Anchors
- Part 11: Main Nav Logo
- Part 10: Search Results
- Part 9: Search Manager
- Part 8: Search Start
- Part 7: Seeding the Database
- Part 6: Domain Database
- Part 5: Emailing Exceptions
- Part 4: Mailkit
- Part 3: View Renderer
- Part 2: Upgrade to 2.1
- Part 1: Quick Start
Not if it is a picture tag
- WebUi
- Features
- Images
- ImageTagHelper.cs
- Images
- Features
In the previous article we created a base class that both of our previous image tag helpers inherit from and we will use it again in this case but we need to make a small adjustment to it (a).
ImageTagHelper.cs
...
namespace WebUi.Features.Images
{
...
public abstract class ImageTagHelper : TagHelper
{
...
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context.TagName != "picture")
{
output.TagName = "img";
output.TagMode = TagMode.StartTagOnly;
if (IsLazyLoaded)
{
output.AddClass("lazyload", HtmlEncoder.Default);
}
output.Attributes.Add("alt", Alt);
}
...
}
...
}
}
The picture tag helper
- WebUi
- Features
- Images
- PictureTagHelper.cs
- Images
- Features
Now we are ready to create the picture tag helper (b). This one is of course a bit more involved than the previous two since we have to add a few child elements to the parent picture element.
PictureTagHelper.cs
using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace WebUi.Features.Images
{
[HtmlTargetElement("picture", Attributes = "break-points")]
public class PictureTagHelper : ImageTagHelper
{
[HtmlAttributeName("break-points")] public string BreakPoints { get; set; }
protected override void Process(TagHelperOutput output)
{
output.TagName = "picture";
output.TagMode = TagMode.StartTagAndEndTag;
var breakPoints = BreakPoints.Split(",").Select(int.Parse).ToArray();
for (var i = 0; i < breakPoints.Length + 1; i++)
{
var source = new TagBuilder("source");
if (i == 0)
{
var max = breakPoints[i] - 1;
source.Attributes.Add("media", $"(max-width: {max}px)");
}
else if (i == breakPoints.Length)
{
var min = breakPoints[i - 1];
source.Attributes.Add("media", $"(min-width: {min}px)");
}
else
{
var min = breakPoints[i - 1];
var max = breakPoints[i] - 1;
source.Attributes.Add("media",
$"(min-width: {min}px) and (max-width: {max}px)");
}
source.Attributes.Add(IsLazyLoaded ? "data-srcset" : "srcset", ImageSrc(SizeStrings[i]));
output.Content.AppendHtml(source);
}
var img = new TagBuilder("img");
string srcKey;
if (IsLazyLoaded )
{
srcKey = "data-src";
img.AddCssClass("lazyload");
}
else
{
srcKey = "src";
}
output.Attributes.Add(srcKey, ImageSrc(SizeStrings[0]));
output.Content.AppendHtml(img);
}
}
}
Testing things out
- WebUi
- Pages
- Image-Tag-Helper.cshtml
- Pages
Next up is making sure that our tag helper creates the code we are looking for it to create. To do this we of course return to our razor page and add the code shown in (c).
Image-Tag-Helper.cshtml
...
else if (Model.Problem == "art-direction")
{
...
if (Model.Query == "picture")
{
<div class="image-container">
<picture src-fragment="/images/responsive-images/woman-balloon-chair-dark"
sizes="480,640,800"
break-points="480,800"
extension="jpg"
alt="Woman sitting on a chair beside a balloon"
lazy-load="false">
</picture>
<div class="image-caption">
<p>Photo by Daria Shevtsova from Pexels</p>
<p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
</div>
</div>
}
}
else if (Model.Problem == "lazy-loading")
{
...
else if (Model.Query == "picture")
{
for(var i = 1; i <= 10; i++)
{
<div class="image-container">
<picture src-fragment="/images/responsive-images/woman-balloon-chair-dark-Copy@(i)"
sizes="480,640,800"
break-points="480,800"
extension="jpg"
alt="Woman sitting on a chair beside a balloon">
</picture>
<div class="image-caption">
<p>Photo by Daria Shevtsova from Pexels</p>
<p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
</div>
</div>
}
}
}
Cleaning up
- WebUi
- Features
- Images
- ImageTagHelper.cs
- Images
- Features
The last thing we are going to do with our tag helpers is to add just a little more functionality. We are adding the ability to specify the class, id, and any other attributes that we may like to add in the future (d).
ImageTagHelper.cs
using System.Collections.Generic;
using System.Linq;
...
using System.Text.RegularExpressions;
...
namespace WebUi.Features.Images
{
...
public abstract class ImageTagHelper : TagHelper
{
[HtmlAttributeName("attributes")] public object Attributes { get; set; }
...
[HtmlAttributeName("class")] public string Class { get; set; }
...
[HtmlAttributeName("id")] public string Id { get; set; }
...
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
if (string.IsNullOrWhiteSpace(Class) == false)
{
output.AddClass(Class, HtmlEncoder.Default);
}
if (string.IsNullOrWhiteSpace(Id) == false)
{
output.Attributes.Add("id", Id);
}
if (Attributes != null)
{
var attributes = Attributes.ToDictionary();
foreach (var kvp in attributes)
{
output.Attributes.Add(kvp.Key.PascalToKebabCase(), kvp.Value);
}
}
...
}
...
}
public static class ImageExtensions
{
public static Dictionary<string, string> ToDictionary(this object obj)
{
if (obj == null)
{
return new Dictionary<string, string>();
}
return (from x in obj.GetType().GetProperties() select x).ToDictionary(
x => x.Name,
x => x.GetGetMethod().Invoke(obj, null) == null
? ""
: x.GetGetMethod().Invoke(obj, null).ToString());
}
public static string PascalToKebabCase(this string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
return Regex.Replace(value, "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])", "-$1", RegexOptions.Compiled)
.Trim()
.ToLower();
}
}
}
Make sure it's working again
- WebUi
- Pages
- Image-Tag-Helper.cshtml
- Pages
Before we call it quits we just need to make sure that our changes are working the way we would like them to by updating one of our previous examples (e).
Image-Tag-Helper.cshtml
...
@if (Model.Problem == "resolution-switching")
{
...
if (Model.Query == "different-sizes")
{
<div class="image-container">
<responsive-image src-fragment="/images/responsive-images/woman-balloon-chair-dark"
sizes="320,480,800"
extension="jpg"
alt="Woman sitting on a chair beside a balloon"
lazy-load="false"
class="class"
id="id"
attributes="@(new {someName = "some-name"})"></responsive-image>
<div class="image-caption">
<p>Photo by Daria Shevtsova from Pexels</p>
<p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
</div>
</div>
}
}