How to Schedule a Link using TagHelpers in ASP.NET Core 1.0
Ok, the TagHelper bug bit me. Today, I demonstrate a way to schedule links to activate in the future...yes, even when you aren't on the web.
Earlier this week, we discussed a way to create links that would deactivate themselves when it was a dead link to a website.
This was a simple task with TagHelpers, but let's take it a step further.
Here's a scenario: What if you had a group of links for an article series? Imagine the ASP.NET MVC Optimization Series. There were six links on six pages (36 links) I had to manage every time I was going to post the next article in the series.
Each page would contain a table of contents pointing to a page that hasn't been created yet (probably because I didn't write it yet), but you want to build anticipation with your audience by placing text there for when it becomes active.
Why not "schedule" your links to appear when the post is scheduled to go public?
Great idea! I'm so glad you guys are here to help me with content. ;-)
Build On What We Already Have
I've updated the SmartLinkHelper since earlier this week and it's grown up to become it's own tag. :-) It's signature is now a tag called "smartlink."
We don't need that pesky constant string called "smart-link."
Unfortunately, it's replaced with another one called "scheduled-for." This is what we'll use for our HTML attribute to identify when the link will become active.
[TargetElement("smartlink")] public class SmartLinkHelper: AnchorTagHelper
Next, we focus on how to store our attribute from the HTML. Just like the Url, we add another property called ScheduledFor.
[TargetElement("smartlink")] public class SmartLinkHelper: AnchorTagHelper { private const string ScheduledAttributeName = "scheduled-for"; public SmartLinkHelper(IHtmlGenerator generator) : base(generator) { } [HtmlAttributeName("href")] public string Url { get; set; } [HtmlAttributeName(ScheduledAttributeName)] public string ScheduledFor { get; set; }
Onto the processing of the ScheduledFor property...
Dating our Link with ScheduledFor
The processing of the link is almost exactly the same as our ProcessAsync from before, but we add a simple routine for deactivating the link based on the date.
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { // Grab the content inside the anchor tag. var child = await context.GetChildContentAsync(); var content = child.GetContent(); // Get the status of the page. var statusCode = await GetStatusCode(); // If we get a 404, remove the link. if (statusCode == HttpStatusCode.NotFound || statusCode == HttpStatusCode.InternalServerError) { RemoveLink(output, content); // Log a bad link or site if you want. return; } if (!String.IsNullOrEmpty(ScheduledFor)) { DateTime enableLinkAt; if (DateTime.TryParse(ScheduledFor, out enableLinkAt)) { if (DateTime.UtcNow <= enableLinkAt) { RemoveLink(output, content); return; } } } // Transform from <smartlink> to <a> output.TagName = "a"; // Merge the attributes into the anchor tag. MergeAttributes(context, output); // Set the content to "InnerHtml" output.Content.SetContent(content); // Business as usual await base.ProcessAsync(context, output); }
The new changes are in bold.
We check to see if we even have a date by checking if ScheduledFor is not empty. If it's not empty, convert it into a date/time format. If it's successful, we check the date with DateTime.UtcNow (you can perform your own check). If it's not time yet, we remove the link.
At the bottom, we set the output to use the anchor tag "a", merge the attributes into it, and set the InnerHtml content to what was originally there. We then let the base class finish off the processing for us.
Full Source of SmartLinkHelper
There are minor changes, but it contains everything to make a scheduled link.
[TargetElement("smartlink")] public class SmartLinkHelper: AnchorTagHelper { private const string ScheduledAttributeName = "scheduled-for"; public SmartLinkHelper(IHtmlGenerator generator) : base(generator) { } [HtmlAttributeName("href")] public string Url { get; set; } [HtmlAttributeName(ScheduledAttributeName)] public string ScheduledFor { get; set; } public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { // Grab the content inside the anchor tag. var child = await context.GetChildContentAsync(); var content = child.GetContent(); // Get the status of the page. var statusCode = await GetStatusCode(); // If we get a 404, remove the link. if (statusCode == HttpStatusCode.NotFound || statusCode == HttpStatusCode.InternalServerError) { RemoveLink(output, content); // Log a bad link or site if you want. return; } if (!String.IsNullOrEmpty(ScheduledFor)) { DateTime enableLinkAt; if (DateTime.TryParse(ScheduledFor, out enableLinkAt)) { if (enableLinkAt >= DateTime.UtcNow) { RemoveLink(output, content); return; } } } // Transform from <smartlink> to <a> output.TagName = "a"; // Merge the attributes into the anchor tag. MergeAttributes(context, output); // Set the content to "InnerHtml" output.Content.SetContent(content); // Business as usual await base.ProcessAsync(context, output); } private static void MergeAttributes(TagHelperContext context, TagHelperOutput output) { // Merge the attributes and remove the scheduleFor attributes. var attributesToKeep = context.AllAttributes .Where(e => e.Name != ScheduledAttributeName) .ToList(); if (!attributesToKeep.Any()) return; foreach (var readOnlyTagHelperAttribute in attributesToKeep) { output.Attributes.Add(new TagHelperAttribute { Name = readOnlyTagHelperAttribute.Name, Value = readOnlyTagHelperAttribute.Value, Minimized = readOnlyTagHelperAttribute.Minimized }); } } private static void RemoveLink(TagHelperOutput output, string content) { output.PreContent.SetContent(String.Empty); output.TagName = ""; output.Content.SetContent(content); output.PostContent.SetContent(String.Empty); } private async Task<HttpStatusCode> GetStatusCode() { try { using (var client = new HttpClient()) { var msg = new HttpResponseMessage(); await client.GetAsync(Url); return msg.StatusCode; } } catch { return HttpStatusCode.InternalServerError; } } }
Almost forgot about the HTML!
The HTML in your View is not too different except that it contains a wierd link called <smartlink>.
<div class="row"> <div class="col-md-12"> <h2>Smart Link Demo</h2> <ul> <li><a asp-controller="Home" asp-action="Index" title="Back to home page">Home Page</a></li> <li><smartlink href="http://www.cnn.com/">Valid Link (to cnn.com)</smartlink></li> <li><smartlink href="javascript:void(0);">Bad Link</smartlink></li> <li><smartlink href="http://www.disney.com" title="We're going to Disney!" scheduled-for="2015-10-24">Timed Link</smartlink></li> </ul> </div> </div>
In Visual Studio, you may get some invalid link tag, but trust me, it'll work. :-)
When you run your application and you set your scheduled date for October 31, 2015 (and it's currently October 29, 2015), it won't be enabled yet for you to click.
<li><smartlink href="http://www.disney.com" title="We're going to Disney!" scheduled-for="2015-10-31">Timed Link</smartlink></li>
Then your result would be:
However, if you set your HTML to October 24, 2015,
<li><smartlink href="http://www.disney.com" title="We're going to Disney!" scheduled-for="2015-10-24">Timed Link</smartlink></li>
then your View would look like this:
Scheduling links on your site just became a little bit easier.
Conclusion
In this post, we expanded on our dead link example and added the ability to have our links become aware of what time it is by giving it a watch.
As I continue moving through ASP.NET MVC 6, I will post my findings and make everything as transparent as I can.
Did you find out something that could be improved or built an awesome TagHelper yourself? Post it in the comments below.