Add TagHelperRunner for TagHelper runtime.

- This involved adding the following core classes: TagHelper, TagHelperExecutionContext, TagHelperOutput, TagHelperContext.  All of which aid in running TagHelpers.

#154
This commit is contained in:
NTaylorMullen 2014-09-25 01:04:55 -07:00 committed by N. Taylor Mullen
parent dfe41eced8
commit 4378f9613e
6 changed files with 378 additions and 2 deletions

View File

@ -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
{
/// <summary>
/// Contract used to filter matching HTML elements.
/// </summary>
public interface ITagHelper
{
// TODO: Will be implemented in https://github.com/aspnet/razor/issues/154
/// <summary>
/// Asynchronously executes the <see cref="ITagHelper"/> with the given <paramref name="context"/> and
/// <paramref name="output"/>.
/// </summary>
/// <param name="context">Contains information associated with the current HTML tag.</param>
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
/// <returns>A <see cref="Task"/> that on completion updates the <paramref name="output"/>.</returns>
Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
}
}

View File

@ -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
{
/// <summary>
/// Class used to filter matching HTML elements.
/// </summary>
public abstract class TagHelper : ITagHelper
{
/// <summary>
/// Synchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
/// <paramref name="output"/>.
/// </summary>
/// <param name="context">Contains information associated with the current HTML tag.</param>
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
public virtual void Process(TagHelperContext context, TagHelperOutput output)
{
}
/// <summary>
/// Asynchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
/// <paramref name="output"/>.
/// </summary>
/// <param name="context">Contains information associated with the current HTML tag.</param>
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
/// <returns>A <see cref="Task"/> that on completion updates the <paramref name="output"/>.</returns>
/// <remarks>By default this calls into <see cref="Process"/>.</remarks>.
#pragma warning disable 1998
public virtual async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
Process(context, output);
}
#pragma warning restore 1998
}
}

View File

@ -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
{
/// <summary>
/// Contains information related to the execution of <see cref="ITagHelper"/>s.
/// </summary>
public class TagHelperContext
{
/// <summary>
/// Instantiates a new <see cref="TagHelperContext"/>.
/// </summary>
/// <param name="allAttributes">Every attribute associated with the current HTML element.</param>
public TagHelperContext([NotNull] IDictionary<string, object> allAttributes)
{
AllAttributes = allAttributes;
}
/// <summary>
/// Every attribute associated with the current HTML element.
/// </summary>
public IDictionary<string, object> AllAttributes { get; private set; }
}
}

View File

@ -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
{
/// <summary>
/// Class used to represent the output of an <see cref="ITagHelper"/>.
/// </summary>
public class TagHelperOutput
{
private string _content;
private string _tagName;
// Internal for testing
internal TagHelperOutput(string tagName)
{
TagName = tagName;
Attributes = new Dictionary<string, string>(StringComparer.Ordinal);
}
// Internal for testing
internal TagHelperOutput(string tagName, [NotNull] IDictionary<string, string> attributes)
: this(tagName, attributes, string.Empty)
{
}
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperOutput"/>.
/// </summary>
/// <param name="tagName">The HTML element's tag name.</param>
/// <param name="attributes">The HTML attributes.</param>
/// <param name="content">The HTML element's content.</param>
public TagHelperOutput(string tagName,
[NotNull] IDictionary<string, string> attributes,
string content)
{
TagName = tagName;
Content = content;
Attributes = new Dictionary<string, string>(attributes, StringComparer.Ordinal);
}
/// <summary>
/// The HTML element's tag name.
/// </summary>
/// <remarks>
/// A whitespace value results in no start or end tag being rendered.
/// </remarks>
public string TagName
{
get
{
return _tagName;
}
set
{
_tagName = value ?? string.Empty;
}
}
/// <summary>
/// The HTML element's content.
/// </summary>
public string Content
{
get
{
return _content;
}
set
{
_content = value ?? string.Empty;
}
}
/// <summary>
/// Indicates whether or not the tag is self closing.
/// </summary>
public bool SelfClosing { get; set; }
/// <summary>
/// The HTML element's attributes.
/// </summary>
public IDictionary<string, string> Attributes { get; private set; }
/// <summary>
/// Generates the <see cref="TagHelperOutput"/>'s start tag.
/// </summary>
/// <returns><c>string.Empty</c> if <see cref="TagName"/> is <c>string.Empty</c> or whitespace. Otherwise, the
/// <see cref="string"/> representation of the <see cref="TagHelperOutput"/>'s start tag.</returns>
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();
}
/// <summary>
/// Generates the <see cref="TagHelperOutput"/>'s body.
/// </summary>
/// <returns><c>string.Empty</c> if <see cref="SelfClosing"/> is <c>true</c>. <see cref="Output"/> otherwise.
/// </returns>
public string GenerateContent()
{
if (SelfClosing)
{
return string.Empty;
}
return Content;
}
/// <summary>
/// Generates the <see cref="TagHelperOutput"/>'s end tag.
/// </summary>
/// <returns><c>string.Empty</c> if <see cref="TagName"/> is <c>string.Empty</c> or whitespace. Otherwise, the
/// <see cref="string"/> representation of the <see cref="TagHelperOutput"/>'s end tag.</returns>
public string GenerateEndTag()
{
if (SelfClosing || string.IsNullOrWhiteSpace(TagName))
{
return string.Empty;
}
return string.Format(CultureInfo.InvariantCulture, "</{0}>", TagName);
}
}
}

View File

@ -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
{
/// <summary>
/// A class used to run <see cref="ITagHelper"/>s.
/// </summary>
public class TagHelperRunner
{
/// <summary>
/// Calls the <see cref="ITagHelper.ProcessAsync"/> method on <see cref="ITagHelper"/>s.
/// </summary>
/// <param name="context">Contains information associated with running <see cref="ITagHelper"/>s.</param>
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
/// <paramref name="context"/>'s <see cref="ITagHelper"/>s.</returns>
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelpersExecutionContext context)
{
return await RunAsyncCore(context, string.Empty);
}
/// <summary>
/// Calls the <see cref="ITagHelper.ProcessAsync"/> method on <see cref="ITagHelper"/>s.
/// </summary>
/// <param name="context">Contains information associated with running <see cref="ITagHelper"/>s.</param>
/// <param name="bufferedBody">Contains the buffered content of the current HTML tag.</param>
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
/// <paramref name="context"/>'s <see cref="ITagHelper"/>s.</returns>
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelpersExecutionContext context,
[NotNull] TextWriter bufferedBody)
{
return await RunAsyncCore(context, bufferedBody.ToString());
}
private async Task<TagHelperOutput> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Class used to store information about a <see cref="ITagHelper"/>'s execution lifetime.
/// </summary>
public class TagHelpersExecutionContext
{
private readonly List<ITagHelper> _tagHelpers;
/// <summary>
/// Instantiates a new <see cref="TagHelpersExecutionContext"/>.
/// </summary>
/// <param name="tagName">The HTML tag name in the Razor source.</param>
public TagHelpersExecutionContext([NotNull] string tagName)
{
AllAttributes = new Dictionary<string, object>(StringComparer.Ordinal);
HTMLAttributes = new Dictionary<string, string>(StringComparer.Ordinal);
_tagHelpers = new List<ITagHelper>();
TagName = tagName;
}
/// <summary>
/// HTML attributes.
/// </summary>
public IDictionary<string, string> HTMLAttributes { get; private set; }
/// <summary>
/// <see cref="ITagHelper"/> bound attributes and HTML attributes.
/// </summary>
public IDictionary<string, object> AllAttributes { get; private set; }
/// <summary>
/// <see cref="ITagHelper"/>s that should be run.
/// </summary>
public IEnumerable<ITagHelper> TagHelpers
{
get
{
return _tagHelpers;
}
}
/// <summary>
/// The HTML tag name in the Razor source.
/// </summary>
public string TagName { get; private set; }
/// <summary>
/// The <see cref="ITagHelper">s' output.
/// </summary>
public TagHelperOutput Output { get; set; }
/// <summary>
/// Tracks the given <paramref name="tagHelper"/>.
/// </summary>
/// <param name="tagHelper">The tag helper to track.</param>
public void Add([NotNull] ITagHelper tagHelper)
{
_tagHelpers.Add(tagHelper);
}
/// <summary>
/// Tracks the HTML attribute in <see cref="AllAttributes"/> and <see cref="HTMLAttributes"/>.
/// </summary>
/// <param name="name">The HTML attribute name.</param>
/// <param name="value">The HTML attribute value.</param>
public void AddHtmlAttribute([NotNull] string name, string value)
{
HTMLAttributes.Add(name, value);
AllAttributes.Add(name, value);
}
/// <summary>
/// Tracks the <see cref="ITagHelper"/> bound attribute in <see cref="AllAttributes"/>.
/// </summary>
/// <param name="name">The bound attribute name.</param>
/// <param name="value">The attribute value.</param>
public void AddTagHelperAttribute([NotNull] string name, object value)
{
AllAttributes.Add(name, value);
}
}
}