diff --git a/samples/MvcSample/Views/Home/MyView.cshtml b/samples/MvcSample/Views/Home/MyView.cshtml index 9eee55d9f8..6f05da0cf1 100644 --- a/samples/MvcSample/Views/Home/MyView.cshtml +++ b/samples/MvcSample/Views/Home/MyView.cshtml @@ -3,6 +3,7 @@ @{ Layout = "/Views/Shared/_Layout.cshtml"; ViewBag.Title = "Home Page"; + string nullValue = null; }
@@ -11,7 +12,8 @@

Learn more »

-

Hello @Model.Name!

+

Hello @Model.Name!

+

Getting started

diff --git a/src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs b/src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs new file mode 100644 index 0000000000..a48d70efaf --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.Mvc.Razor +{ + public class AttributeValue + { + public AttributeValue(PositionTagged prefix, PositionTagged value, bool literal) + { + Prefix = prefix; + Value = value; + Literal = literal; + } + + public PositionTagged Prefix { get; private set; } + + public PositionTagged Value { get; private set; } + + public bool Literal { get; private set; } + + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We are using tuples here to avoid dependencies from Razor to WebPages")] + public static AttributeValue FromTuple(Tuple, Tuple, bool> value) + { + return new AttributeValue(value.Item1, value.Item2, value.Item3); + } + + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We are using tuples here to avoid dependencies from Razor to WebPages")] + public static AttributeValue FromTuple(Tuple, Tuple, bool> value) + { + return new AttributeValue(value.Item1, new PositionTagged(value.Item2.Item1, value.Item2.Item2), value.Item3); + } + + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We are using tuples here to avoid dependencies from Razor to WebPages")] + public static implicit operator AttributeValue(Tuple, Tuple, bool> value) + { + return FromTuple(value); + } + + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We are using tuples here to avoid dependencies from Razor to WebPages")] + public static implicit operator AttributeValue(Tuple, Tuple, bool> value) + { + return FromTuple(value); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs b/src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs new file mode 100644 index 0000000000..526bb4937c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics; + +namespace Microsoft.AspNet.Mvc.Razor +{ + [DebuggerDisplay("({Position})\"{Value}\"")] + public class PositionTagged + { + public PositionTagged(T value, int offset) + { + Position = offset; + Value = value; + } + + public int Position { get; private set; } + + public T Value { get; private set; } + + public override string ToString() + { + return Value.ToString(); + } + + public static implicit operator T(PositionTagged value) + { + return value.Value; + } + + public static implicit operator PositionTagged(Tuple value) + { + return new PositionTagged(value.Item1, value.Item2); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index acc9fef15f..656184f479 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -92,6 +92,104 @@ namespace Microsoft.AspNet.Mvc.Razor } } + public virtual void WriteAttribute(string name, + PositionTagged prefix, + PositionTagged suffix, + params AttributeValue[] values) + { + WriteAttributeTo(Output, name, prefix, suffix, values); + } + + public virtual void WriteAttributeTo(TextWriter writer, + string name, + PositionTagged prefix, + PositionTagged suffix, + params AttributeValue[] values) + { + bool first = true; + bool wroteSomething = false; + if (values.Length == 0) + { + // Explicitly empty attribute, so write the prefix and suffix + WritePositionTaggedLiteral(writer, prefix); + WritePositionTaggedLiteral(writer, suffix); + } + else + { + for (int i = 0; i < values.Length; i++) + { + AttributeValue attrVal = values[i]; + PositionTagged val = attrVal.Value; + PositionTagged next = i == values.Length - 1 ? + suffix : // End of the list, grab the suffix + values[i + 1].Prefix; // Still in the list, grab the next prefix + + if (val.Value == null) + { + // Nothing to write + continue; + } + + // The special cases here are that the value we're writing might already be a string, or that the + // value might be a bool. If the value is the bool 'true' we want to write the attribute name instead + // of the string 'true'. If the value is the bool 'false' we don't want to write anything. + // + // Otherwise the value is another object (perhaps an IHtmlString), and we'll ask it to format itself. + string stringValue; + bool? boolValue = val.Value as bool?; + if (boolValue == true) + { + stringValue = name; + } + else if (boolValue == false) + { + continue; + } + else + { + stringValue = val.Value as string; + } + + if (first) + { + WritePositionTaggedLiteral(writer, prefix); + first = false; + } + else + { + WritePositionTaggedLiteral(writer, attrVal.Prefix); + } + + // Calculate length of the source span by the position of the next value (or suffix) + int sourceLength = next.Position - attrVal.Value.Position; + + if (attrVal.Literal) + { + WriteLiteralTo(writer, stringValue ?? val.Value); + } + else + { + WriteTo(writer, stringValue ?? val.Value); // Write value + } + wroteSomething = true; + } + if (wroteSomething) + { + WritePositionTaggedLiteral(writer, suffix); + } + } + } + + private void WritePositionTaggedLiteral(TextWriter writer, string value, int position) + { + WriteLiteralTo(writer, value); + } + + private void WritePositionTaggedLiteral(TextWriter writer, PositionTagged value) + { + WritePositionTaggedLiteral(writer, value.Value, value.Position); + } + protected virtual HtmlString RenderBody() { if (BodyContent == null)