From 4378f9613e573a94d1e695d8851973cb3c771556 Mon Sep 17 00:00:00 2001 From: NTaylorMullen Date: Thu, 25 Sep 2014 01:04:55 -0700 Subject: [PATCH] Add TagHelperRunner for TagHelper runtime. - This involved adding the following core classes: TagHelper, TagHelperExecutionContext, TagHelperOutput, TagHelperContext. All of which aid in running TagHelpers. #154 --- .../TagHelpers/ITagHelper.cs | 14 +- .../TagHelpers/TagHelper.cs | 38 +++++ .../TagHelpers/TagHelperContext.cs | 27 +++ .../TagHelpers/TagHelperOutput.cs | 159 ++++++++++++++++++ .../TagHelpers/TagHelperRunner.cs | 53 ++++++ .../TagHelpers/TagHelpersExecutionContext.cs | 89 ++++++++++ 6 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs create mode 100644 src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs create mode 100644 src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs create mode 100644 src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs create mode 100644 src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelpersExecutionContext.cs diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs index badb7227e0..1b716db0a1 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs @@ -1,12 +1,22 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using System.Threading.Tasks; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { + /// + /// Contract used to filter matching HTML elements. + /// public interface ITagHelper { - // TODO: Will be implemented in https://github.com/aspnet/razor/issues/154 + /// + /// Asynchronously executes the with the given and + /// . + /// + /// Contains information associated with the current HTML tag. + /// A stateful HTML element used to generate an HTML tag. + /// A that on completion updates the . + Task ProcessAsync(TagHelperContext context, TagHelperOutput output); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs new file mode 100644 index 0000000000..91e5415f4c --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// Class used to filter matching HTML elements. + /// + public abstract class TagHelper : ITagHelper + { + /// + /// Synchronously executes the with the given and + /// . + /// + /// Contains information associated with the current HTML tag. + /// A stateful HTML element used to generate an HTML tag. + public virtual void Process(TagHelperContext context, TagHelperOutput output) + { + } + + /// + /// Asynchronously executes the with the given and + /// . + /// + /// Contains information associated with the current HTML tag. + /// A stateful HTML element used to generate an HTML tag. + /// A that on completion updates the . + /// By default this calls into .. + #pragma warning disable 1998 + public virtual async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + Process(context, output); + } + #pragma warning restore 1998 + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs new file mode 100644 index 0000000000..fc91494b38 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// Contains information related to the execution of s. + /// + public class TagHelperContext + { + /// + /// Instantiates a new . + /// + /// Every attribute associated with the current HTML element. + public TagHelperContext([NotNull] IDictionary allAttributes) + { + AllAttributes = allAttributes; + } + + /// + /// Every attribute associated with the current HTML element. + /// + public IDictionary AllAttributes { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs new file mode 100644 index 0000000000..8a04c913be --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Text; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// Class used to represent the output of an . + /// + public class TagHelperOutput + { + private string _content; + private string _tagName; + + // Internal for testing + internal TagHelperOutput(string tagName) + { + TagName = tagName; + Attributes = new Dictionary(StringComparer.Ordinal); + } + + // Internal for testing + internal TagHelperOutput(string tagName, [NotNull] IDictionary attributes) + : this(tagName, attributes, string.Empty) + { + } + + /// + /// Instantiates a new instance of . + /// + /// The HTML element's tag name. + /// The HTML attributes. + /// The HTML element's content. + public TagHelperOutput(string tagName, + [NotNull] IDictionary attributes, + string content) + { + TagName = tagName; + Content = content; + Attributes = new Dictionary(attributes, StringComparer.Ordinal); + } + + /// + /// The HTML element's tag name. + /// + /// + /// A whitespace value results in no start or end tag being rendered. + /// + public string TagName + { + get + { + return _tagName; + } + set + { + _tagName = value ?? string.Empty; + } + } + + /// + /// The HTML element's content. + /// + public string Content + { + get + { + return _content; + } + set + { + _content = value ?? string.Empty; + } + } + + /// + /// Indicates whether or not the tag is self closing. + /// + public bool SelfClosing { get; set; } + + /// + /// The HTML element's attributes. + /// + public IDictionary Attributes { get; private set; } + + /// + /// Generates the 's start tag. + /// + /// string.Empty if is string.Empty or whitespace. Otherwise, the + /// representation of the 's start tag. + public string GenerateStartTag() + { + // Only render a start tag if the tag name is not whitespace + if (string.IsNullOrWhiteSpace(TagName)) + { + return string.Empty; + } + + var sb = new StringBuilder(); + + sb.Append('<') + .Append(TagName); + + foreach (var attribute in Attributes) + { + var value = WebUtility.HtmlEncode(attribute.Value); + sb.Append(' ') + .Append(attribute.Key) + .Append("=\"") + .Append(value) + .Append('"'); + } + + if (SelfClosing) + { + sb.Append(" /"); + } + + sb.Append('>'); + + return sb.ToString(); + } + + /// + /// Generates the 's body. + /// + /// string.Empty if is true. otherwise. + /// + public string GenerateContent() + { + if (SelfClosing) + { + return string.Empty; + } + + return Content; + } + + /// + /// Generates the 's end tag. + /// + /// string.Empty if is string.Empty or whitespace. Otherwise, the + /// representation of the 's end tag. + public string GenerateEndTag() + { + if (SelfClosing || string.IsNullOrWhiteSpace(TagName)) + { + return string.Empty; + } + + return string.Format(CultureInfo.InvariantCulture, "", TagName); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs new file mode 100644 index 0000000000..a6aa597529 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// A class used to run s. + /// + public class TagHelperRunner + { + /// + /// Calls the method on s. + /// + /// Contains information associated with running s. + /// Resulting from processing all of the + /// 's s. + public async Task RunAsync([NotNull] TagHelpersExecutionContext context) + { + return await RunAsyncCore(context, string.Empty); + } + + /// + /// Calls the method on s. + /// + /// Contains information associated with running s. + /// Contains the buffered content of the current HTML tag. + /// Resulting from processing all of the + /// 's s. + public async Task RunAsync([NotNull] TagHelpersExecutionContext context, + [NotNull] TextWriter bufferedBody) + { + return await RunAsyncCore(context, bufferedBody.ToString()); + } + + private async Task RunAsyncCore(TagHelpersExecutionContext executionContext, string outputContent) + { + var tagHelperContext = new TagHelperContext(executionContext.AllAttributes); + var tagHelperOutput = new TagHelperOutput(executionContext.TagName, + executionContext.HTMLAttributes, + outputContent); + + foreach (var tagHelper in executionContext.TagHelpers) + { + await tagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); + } + + return tagHelperOutput; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelpersExecutionContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelpersExecutionContext.cs new file mode 100644 index 0000000000..dd58d2c723 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelpersExecutionContext.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// Class used to store information about a 's execution lifetime. + /// + public class TagHelpersExecutionContext + { + private readonly List _tagHelpers; + + /// + /// Instantiates a new . + /// + /// The HTML tag name in the Razor source. + public TagHelpersExecutionContext([NotNull] string tagName) + { + AllAttributes = new Dictionary(StringComparer.Ordinal); + HTMLAttributes = new Dictionary(StringComparer.Ordinal); + _tagHelpers = new List(); + TagName = tagName; + } + + /// + /// HTML attributes. + /// + public IDictionary HTMLAttributes { get; private set; } + + /// + /// bound attributes and HTML attributes. + /// + public IDictionary AllAttributes { get; private set; } + + /// + /// s that should be run. + /// + public IEnumerable TagHelpers + { + get + { + return _tagHelpers; + } + } + + /// + /// The HTML tag name in the Razor source. + /// + public string TagName { get; private set; } + + /// + /// The s' output. + /// + public TagHelperOutput Output { get; set; } + + /// + /// Tracks the given . + /// + /// The tag helper to track. + public void Add([NotNull] ITagHelper tagHelper) + { + _tagHelpers.Add(tagHelper); + } + + /// + /// Tracks the HTML attribute in and . + /// + /// The HTML attribute name. + /// The HTML attribute value. + public void AddHtmlAttribute([NotNull] string name, string value) + { + HTMLAttributes.Add(name, value); + AllAttributes.Add(name, value); + } + + /// + /// Tracks the bound attribute in . + /// + /// The bound attribute name. + /// The attribute value. + public void AddTagHelperAttribute([NotNull] string name, object value) + { + AllAttributes.Add(name, value); + } + } +} \ No newline at end of file