├── korebuild-lock.txt ├── NuGetPackageVerifier.json ├── korebuild.json ├── CONTRIBUTING.md ├── .github └── ISSUE_TEMPLATE.md ├── NuGet.config ├── run.cmd ├── .vsts-pipelines └── builds │ ├── ci-internal.yml │ └── ci-public.yml ├── .appveyor.yml ├── README.md ├── test ├── Microsoft.AspNetCore.Html.Abstractions.Test │ ├── Microsoft.AspNetCore.Html.Abstractions.Test.csproj │ ├── HtmlFormattableStringTest.cs │ ├── HtmlContentBuilderTest.cs │ └── HtmlContentBuilderExtensionsTest.cs ├── Microsoft.Extensions.WebEncoders.Tests │ ├── Microsoft.Extensions.WebEncoders.Tests.csproj │ ├── HtmlTestEncoderTest.cs │ └── EncoderServiceCollectionExtensionsTests.cs └── Directory.Build.props ├── .gitignore ├── .travis.yml ├── src ├── Microsoft.AspNetCore.Html.Abstractions │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Microsoft.AspNetCore.Html.Abstractions.csproj │ ├── IHtmlContent.cs │ ├── IHtmlContentContainer.cs │ ├── IHtmlContentBuilder.cs │ ├── HtmlString.cs │ ├── HtmlContentBuilder.cs │ ├── HtmlFormattableString.cs │ ├── HtmlContentBuilderExtensions.cs │ └── baseline.netcore.json └── Microsoft.Extensions.WebEncoders │ ├── WebEncoderOptions.cs │ ├── Microsoft.Extensions.WebEncoders.csproj │ ├── Testing │ ├── UrlTestEncoder.cs │ ├── HtmlTestEncoder.cs │ └── JavaScriptTestEncoder.cs │ ├── EncoderServiceCollectionExtensions.cs │ └── baseline.netcore.json ├── Directory.Build.targets ├── version.props ├── .gitattributes ├── Directory.Build.props ├── HtmlAbstractions.sln ├── run.ps1 ├── run.sh └── LICENSE.txt /korebuild-lock.txt: -------------------------------------------------------------------------------- 1 | version:3.0.0-alpha1-20180919.1 2 | commithash:3066ae0a230870ea07e3f132605b5e5493f8bbd4 3 | -------------------------------------------------------------------------------- /NuGetPackageVerifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "rules": [ 4 | "DefaultCompositeRule" 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /korebuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 3 | "channel": "master" 4 | } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ====== 3 | 4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | THIS ISSUE TRACKER IS CLOSED - please log new issues here: https://github.com/aspnet/Home/issues 2 | 3 | For information about this change, see https://github.com/aspnet/Announcements/issues/283 4 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /run.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" 3 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-internal.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | resources: 6 | repositories: 7 | - repository: buildtools 8 | type: git 9 | name: aspnet-BuildTools 10 | ref: refs/heads/master 11 | 12 | phases: 13 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools 14 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf true 3 | branches: 4 | only: 5 | - master 6 | - /^release\/.*$/ 7 | - /^(.*\/)?ci-.*$/ 8 | build_script: 9 | - ps: .\run.ps1 default-build 10 | clone_depth: 1 11 | environment: 12 | global: 13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 15 | test: 'off' 16 | deploy: 'off' 17 | os: Visual Studio 2017 18 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-public.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | # See https://github.com/aspnet/BuildTools 6 | resources: 7 | repositories: 8 | - repository: buildtools 9 | type: github 10 | endpoint: DotNet-Bot GitHub Connection 11 | name: aspnet/BuildTools 12 | ref: refs/heads/master 13 | 14 | phases: 15 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HtmlAbstractions [Archived] 2 | =========================== 3 | 4 | **This GitHub project has been archived.** Ongoing development on this project can be found in . 5 | 6 | HTML abstractions used for building HTML content, including types such as `HtmlString` and `IHtmlContent`. 7 | 8 | This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. 9 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Html.Abstractions.Test/Microsoft.AspNetCore.Html.Abstractions.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.WebEncoders.Tests/Microsoft.Extensions.WebEncoders.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | *.sln.ide/ 6 | _ReSharper.*/ 7 | packages/ 8 | artifacts/ 9 | PublishProfiles/ 10 | .vs/ 11 | bower_components/ 12 | node_modules/ 13 | **/wwwroot/lib/ 14 | debugSettings.json 15 | project.lock.json 16 | *.user 17 | *.suo 18 | *.cache 19 | *.docstates 20 | _ReSharper.* 21 | nuget.exe 22 | *net45.csproj 23 | *net451.csproj 24 | *k10.csproj 25 | *.psess 26 | *.vsp 27 | *.pidb 28 | *.userprefs 29 | *DS_Store 30 | *.ncrunchsolution 31 | *.*sdf 32 | *.ipch 33 | .settings 34 | *.sln.ide 35 | node_modules 36 | **/[Cc]ompiler/[Rr]esources/**/*.js 37 | *launchSettings.json 38 | .build/ 39 | .testPublish/ 40 | global.json 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: false 3 | dist: trusty 4 | env: 5 | global: 6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 8 | mono: none 9 | os: 10 | - linux 11 | - osx 12 | osx_image: xcode8.2 13 | addons: 14 | apt: 15 | packages: 16 | - libunwind8 17 | branches: 18 | only: 19 | - master 20 | - /^release\/.*$/ 21 | - /^(.*\/)?ci-.*$/ 22 | before_install: 23 | - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s 24 | /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib 25 | /usr/local/lib/; fi 26 | script: 27 | - ./build.sh 28 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Reflection; 5 | using System.Resources; 6 | using System.Runtime.CompilerServices; 7 | 8 | [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Html.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] 9 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/Microsoft.AspNetCore.Html.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Microsoft ASP.NET Core 5 | ASP.NET Core HTML abstractions used for building HTML content. 6 | Commonly used types: 7 | Microsoft.AspNetCore.Html.HtmlString 8 | Microsoft.AspNetCore.Html.IHtmlContent 9 | netstandard2.0 10 | true 11 | aspnetcore 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MicrosoftNETCoreApp20PackageVersion) 4 | $(MicrosoftNETCoreApp21PackageVersion) 5 | $(MicrosoftNETCoreApp22PackageVersion) 6 | $(NETStandardLibrary20PackageVersion) 7 | 8 | 99.9 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netcoreapp2.2 6 | $(DeveloperBuildTestTfms) 7 | 8 | $(StandardTestTfms);net461 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/WebEncoderOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Text.Encodings.Web; 5 | 6 | namespace Microsoft.Extensions.WebEncoders 7 | { 8 | /// 9 | /// Specifies options common to all three encoders (HtmlEncode, JavaScriptEncode, UrlEncode). 10 | /// 11 | public sealed class WebEncoderOptions 12 | { 13 | /// 14 | /// Specifies which code points are allowed to be represented unescaped by the encoders. 15 | /// 16 | /// 17 | /// If this property is null, then the encoders will use their default allow lists. 18 | /// 19 | public TextEncoderSettings TextEncoderSettings { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.WebEncoders.Tests/HtmlTestEncoderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Xunit; 5 | 6 | namespace Microsoft.Extensions.WebEncoders.Testing 7 | { 8 | public class HtmlTestEncoderTest 9 | { 10 | [Theory] 11 | [InlineData("", "")] 12 | [InlineData("abcd", "HtmlEncode[[abcd]]")] 13 | [InlineData("<<''\"\">>", "HtmlEncode[[<<''\"\">>]]")] 14 | public void StringEncode_EncodesAsExpected(string input, string expectedOutput) 15 | { 16 | // Arrange 17 | var encoder = new HtmlTestEncoder(); 18 | 19 | // Act 20 | var output = encoder.Encode(input); 21 | 22 | // Assert 23 | Assert.Equal(expectedOutput, output); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/IHtmlContent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.IO; 5 | using System.Text.Encodings.Web; 6 | 7 | namespace Microsoft.AspNetCore.Html 8 | { 9 | /// 10 | /// HTML content which can be written to a TextWriter. 11 | /// 12 | public interface IHtmlContent 13 | { 14 | /// 15 | /// Writes the content by encoding it with the specified 16 | /// to the specified . 17 | /// 18 | /// The to which the content is written. 19 | /// The which encodes the content to be written. 20 | void WriteTo(TextWriter writer, HtmlEncoder encoder); 21 | } 22 | } -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3.0.0 4 | alpha1 5 | $(VersionPrefix) 6 | $(VersionPrefix)-$(VersionSuffix)-final 7 | t000 8 | a- 9 | $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) 10 | $(VersionSuffix)-$(BuildNumber) 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | 46 | *.csproj text=auto 47 | *.vbproj text=auto 48 | *.fsproj text=auto 49 | *.dbproj text=auto 50 | *.sln text=auto eol=crlf 51 | *.sh eol=lf -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/Microsoft.Extensions.WebEncoders.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Microsoft .NET Extensions 5 | Contains registration and configuration APIs to add the core framework encoders to a dependency injection container. 6 | netstandard2.0 7 | $(NoWarn);CS1591 8 | true 9 | true 10 | aspnetcore 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | https://github.com/aspnet/HtmlAbstractions 12 | git 13 | $(MSBuildThisFileDirectory) 14 | $(MSBuildThisFileDirectory)build\Key.snk 15 | true 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/IHtmlContentContainer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.Html 5 | { 6 | /// 7 | /// Defines a contract for instances made up of several components which 8 | /// can be copied into an . 9 | /// 10 | public interface IHtmlContentContainer : IHtmlContent 11 | { 12 | /// 13 | /// Copies the contained content of this into . 14 | /// 15 | /// The . 16 | void CopyTo(IHtmlContentBuilder builder); 17 | 18 | /// 19 | /// 20 | /// Moves the contained content of this into . 21 | /// 22 | /// 23 | /// After is called, this instance should be left 24 | /// in an empty state. 25 | /// 26 | /// 27 | /// The . 28 | void MoveTo(IHtmlContentBuilder builder); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/IHtmlContentBuilder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.AspNetCore.Html 5 | { 6 | /// 7 | /// A builder for HTML content. 8 | /// 9 | public interface IHtmlContentBuilder : IHtmlContentContainer 10 | { 11 | /// 12 | /// Appends an instance. 13 | /// 14 | /// The to append. 15 | /// The . 16 | IHtmlContentBuilder AppendHtml(IHtmlContent content); 17 | 18 | /// 19 | /// Appends a value. The value is treated as unencoded as-provided, and will be HTML 20 | /// encoded before writing to output. 21 | /// 22 | /// The to append. 23 | /// The . 24 | IHtmlContentBuilder Append(string unencoded); 25 | 26 | /// 27 | /// Appends an HTML encoded value. The value is treated as HTML encoded as-provided, and 28 | /// no further encoding will be performed. 29 | /// 30 | /// The HTML encoded to append. 31 | /// The . 32 | IHtmlContentBuilder AppendHtml(string encoded); 33 | 34 | /// 35 | /// Clears the content. 36 | /// 37 | /// The . 38 | IHtmlContentBuilder Clear(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/HtmlString.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text.Encodings.Web; 7 | 8 | namespace Microsoft.AspNetCore.Html 9 | { 10 | /// 11 | /// An implementation that wraps an HTML encoded . 12 | /// 13 | public class HtmlString : IHtmlContent 14 | { 15 | /// 16 | /// An instance for . 17 | /// 18 | public static readonly HtmlString NewLine = new HtmlString(Environment.NewLine); 19 | 20 | /// 21 | /// An instance for . 22 | /// 23 | public static readonly HtmlString Empty = new HtmlString(string.Empty); 24 | 25 | /// 26 | /// Creates a new . 27 | /// 28 | /// The HTML encoded value. 29 | public HtmlString(string value) 30 | { 31 | Value = value; 32 | } 33 | 34 | /// 35 | /// Gets the HTML encoded value. 36 | /// 37 | public string Value { get; } 38 | 39 | /// 40 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 41 | { 42 | if (writer == null) 43 | { 44 | throw new ArgumentNullException(nameof(writer)); 45 | } 46 | 47 | if (encoder == null) 48 | { 49 | throw new ArgumentNullException(nameof(encoder)); 50 | } 51 | 52 | writer.Write(Value); 53 | } 54 | 55 | /// 56 | public override string ToString() 57 | { 58 | return Value ?? string.Empty; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/Testing/UrlTestEncoder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text.Encodings.Web; 7 | 8 | namespace Microsoft.Extensions.WebEncoders.Testing 9 | { 10 | /// 11 | /// Encoder used for unit testing. 12 | /// 13 | public class UrlTestEncoder : UrlEncoder 14 | { 15 | public override int MaxOutputCharactersPerInputCharacter 16 | { 17 | get { return 1; } 18 | } 19 | 20 | public override string Encode(string value) 21 | { 22 | if (value == null) 23 | { 24 | throw new ArgumentNullException(nameof(value)); 25 | } 26 | 27 | if (value.Length == 0) 28 | { 29 | return string.Empty; 30 | } 31 | 32 | return $"UrlEncode[[{value}]]"; 33 | } 34 | 35 | public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) 36 | { 37 | if (output == null) 38 | { 39 | throw new ArgumentNullException(nameof(output)); 40 | } 41 | 42 | if (value == null) 43 | { 44 | throw new ArgumentNullException(nameof(value)); 45 | } 46 | 47 | if (characterCount == 0) 48 | { 49 | return; 50 | } 51 | 52 | output.Write("UrlEncode[["); 53 | output.Write(value, startIndex, characterCount); 54 | output.Write("]]"); 55 | } 56 | 57 | public override void Encode(TextWriter output, string value, int startIndex, int characterCount) 58 | { 59 | if (output == null) 60 | { 61 | throw new ArgumentNullException(nameof(output)); 62 | } 63 | 64 | if (value == null) 65 | { 66 | throw new ArgumentNullException(nameof(value)); 67 | } 68 | 69 | if (characterCount == 0) 70 | { 71 | return; 72 | } 73 | 74 | output.Write("UrlEncode[["); 75 | output.Write(value.Substring(startIndex, characterCount)); 76 | output.Write("]]"); 77 | } 78 | 79 | public override bool WillEncode(int unicodeScalar) 80 | { 81 | return false; 82 | } 83 | 84 | public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) 85 | { 86 | return -1; 87 | } 88 | 89 | public override unsafe bool TryEncodeUnicodeScalar( 90 | int unicodeScalar, 91 | char* buffer, 92 | int bufferLength, 93 | out int numberOfCharactersWritten) 94 | { 95 | if (buffer == null) 96 | { 97 | throw new ArgumentNullException(nameof(buffer)); 98 | } 99 | 100 | numberOfCharactersWritten = 0; 101 | return false; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/Testing/HtmlTestEncoder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text.Encodings.Web; 7 | 8 | namespace Microsoft.Extensions.WebEncoders.Testing 9 | { 10 | /// 11 | /// Encoder used for unit testing. 12 | /// 13 | public sealed class HtmlTestEncoder : HtmlEncoder 14 | { 15 | public override int MaxOutputCharactersPerInputCharacter 16 | { 17 | get { return 1; } 18 | } 19 | 20 | public override string Encode(string value) 21 | { 22 | if (value == null) 23 | { 24 | throw new ArgumentNullException(nameof(value)); 25 | } 26 | 27 | if (value.Length == 0) 28 | { 29 | return string.Empty; 30 | } 31 | 32 | return $"HtmlEncode[[{value}]]"; 33 | } 34 | 35 | public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) 36 | { 37 | if (output == null) 38 | { 39 | throw new ArgumentNullException(nameof(output)); 40 | } 41 | 42 | if (value == null) 43 | { 44 | throw new ArgumentNullException(nameof(value)); 45 | } 46 | 47 | if (characterCount == 0) 48 | { 49 | return; 50 | } 51 | 52 | output.Write("HtmlEncode[["); 53 | output.Write(value, startIndex, characterCount); 54 | output.Write("]]"); 55 | } 56 | 57 | public override void Encode(TextWriter output, string value, int startIndex, int characterCount) 58 | { 59 | if (output == null) 60 | { 61 | throw new ArgumentNullException(nameof(output)); 62 | } 63 | 64 | if (value == null) 65 | { 66 | throw new ArgumentNullException(nameof(value)); 67 | } 68 | 69 | if (characterCount == 0) 70 | { 71 | return; 72 | } 73 | 74 | output.Write("HtmlEncode[["); 75 | output.Write(value.Substring(startIndex, characterCount)); 76 | output.Write("]]"); 77 | } 78 | 79 | public override bool WillEncode(int unicodeScalar) 80 | { 81 | return false; 82 | } 83 | 84 | public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) 85 | { 86 | return -1; 87 | } 88 | 89 | public override unsafe bool TryEncodeUnicodeScalar( 90 | int unicodeScalar, 91 | char* buffer, 92 | int bufferLength, 93 | out int numberOfCharactersWritten) 94 | { 95 | if (buffer == null) 96 | { 97 | throw new ArgumentNullException(nameof(buffer)); 98 | } 99 | 100 | numberOfCharactersWritten = 0; 101 | return false; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/Testing/JavaScriptTestEncoder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text.Encodings.Web; 7 | 8 | namespace Microsoft.Extensions.WebEncoders.Testing 9 | { 10 | /// 11 | /// Encoder used for unit testing. 12 | /// 13 | public class JavaScriptTestEncoder : JavaScriptEncoder 14 | { 15 | public override int MaxOutputCharactersPerInputCharacter 16 | { 17 | get { return 1; } 18 | } 19 | 20 | public override string Encode(string value) 21 | { 22 | if (value == null) 23 | { 24 | throw new ArgumentNullException(nameof(value)); 25 | } 26 | 27 | if (value.Length == 0) 28 | { 29 | return string.Empty; 30 | } 31 | 32 | return $"JavaScriptEncode[[{value}]]"; 33 | } 34 | 35 | public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) 36 | { 37 | if (output == null) 38 | { 39 | throw new ArgumentNullException(nameof(output)); 40 | } 41 | 42 | if (value == null) 43 | { 44 | throw new ArgumentNullException(nameof(value)); 45 | } 46 | 47 | if (characterCount == 0) 48 | { 49 | return; 50 | } 51 | 52 | output.Write("JavaScriptEncode[["); 53 | output.Write(value, startIndex, characterCount); 54 | output.Write("]]"); 55 | } 56 | 57 | public override void Encode(TextWriter output, string value, int startIndex, int characterCount) 58 | { 59 | if (output == null) 60 | { 61 | throw new ArgumentNullException(nameof(output)); 62 | } 63 | 64 | if (value == null) 65 | { 66 | throw new ArgumentNullException(nameof(value)); 67 | } 68 | 69 | if (characterCount == 0) 70 | { 71 | return; 72 | } 73 | 74 | output.Write("JavaScriptEncode[["); 75 | output.Write(value.Substring(startIndex, characterCount)); 76 | output.Write("]]"); 77 | } 78 | 79 | public override bool WillEncode(int unicodeScalar) 80 | { 81 | return false; 82 | } 83 | 84 | public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) 85 | { 86 | return -1; 87 | } 88 | 89 | public override unsafe bool TryEncodeUnicodeScalar( 90 | int unicodeScalar, 91 | char* buffer, 92 | int bufferLength, 93 | out int numberOfCharactersWritten) 94 | { 95 | if (buffer == null) 96 | { 97 | throw new ArgumentNullException(nameof(buffer)); 98 | } 99 | 100 | numberOfCharactersWritten = 0; 101 | return false; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/EncoderServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text.Encodings.Web; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | using Microsoft.Extensions.Options; 8 | using Microsoft.Extensions.WebEncoders; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection 11 | { 12 | /// 13 | /// Extension methods for setting up web encoding services in an . 14 | /// 15 | public static class EncoderServiceCollectionExtensions 16 | { 17 | /// 18 | /// Adds , and 19 | /// to the specified . 20 | /// 21 | /// The . 22 | /// The so that additional calls can be chained. 23 | public static IServiceCollection AddWebEncoders(this IServiceCollection services) 24 | { 25 | if (services == null) 26 | { 27 | throw new ArgumentNullException(nameof(services)); 28 | } 29 | 30 | services.AddOptions(); 31 | 32 | // Register the default encoders 33 | // We want to call the 'Default' property getters lazily since they perform static caching 34 | services.TryAddSingleton( 35 | CreateFactory(() => HtmlEncoder.Default, settings => HtmlEncoder.Create(settings))); 36 | services.TryAddSingleton( 37 | CreateFactory(() => JavaScriptEncoder.Default, settings => JavaScriptEncoder.Create(settings))); 38 | services.TryAddSingleton( 39 | CreateFactory(() => UrlEncoder.Default, settings => UrlEncoder.Create(settings))); 40 | 41 | return services; 42 | } 43 | 44 | /// 45 | /// Adds , and 46 | /// to the specified . 47 | /// 48 | /// The . 49 | /// An to configure the provided . 50 | /// The so that additional calls can be chained. 51 | public static IServiceCollection AddWebEncoders(this IServiceCollection services, Action setupAction) 52 | { 53 | if (services == null) 54 | { 55 | throw new ArgumentNullException(nameof(services)); 56 | } 57 | 58 | if (setupAction == null) 59 | { 60 | throw new ArgumentNullException(nameof(setupAction)); 61 | } 62 | 63 | services.AddWebEncoders(); 64 | services.Configure(setupAction); 65 | 66 | return services; 67 | } 68 | 69 | private static Func CreateFactory( 70 | Func defaultFactory, 71 | Func customSettingsFactory) 72 | { 73 | return serviceProvider => 74 | { 75 | var settings = serviceProvider 76 | ?.GetService>() 77 | ?.Value 78 | ?.TextEncoderSettings; 79 | return (settings != null) ? customSettingsFactory(settings) : defaultFactory(); 80 | }; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.WebEncoders.Tests/EncoderServiceCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Text.Encodings.Web; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.WebEncoders.Testing; 7 | using Xunit; 8 | 9 | namespace Microsoft.Extensions.WebEncoders 10 | { 11 | public class EncoderServiceCollectionExtensionsTests 12 | { 13 | [Fact] 14 | public void AddWebEncoders_WithoutOptions_RegistersDefaultEncoders() 15 | { 16 | // Arrange 17 | var serviceCollection = new ServiceCollection(); 18 | 19 | // Act 20 | serviceCollection.AddWebEncoders(); 21 | 22 | // Assert 23 | var serviceProvider = serviceCollection.BuildServiceProvider(); 24 | Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder 25 | Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance 26 | Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // default encoder 27 | Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance 28 | Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder 29 | Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance 30 | } 31 | 32 | [Fact] 33 | public void AddWebEncoders_WithOptions_RegistersEncodersWithCustomCodeFilter() 34 | { 35 | // Arrange 36 | var serviceCollection = new ServiceCollection(); 37 | 38 | // Act 39 | serviceCollection.AddWebEncoders(options => 40 | { 41 | options.TextEncoderSettings = new TextEncoderSettings(); 42 | options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed 43 | }); 44 | 45 | // Assert 46 | var serviceProvider = serviceCollection.BuildServiceProvider(); 47 | 48 | var htmlEncoder = serviceProvider.GetRequiredService(); 49 | Assert.Equal("abcde", htmlEncoder.Encode("abcde")); 50 | Assert.Same(htmlEncoder, serviceProvider.GetRequiredService()); // as singleton instance 51 | 52 | var javaScriptEncoder = serviceProvider.GetRequiredService(); 53 | Assert.Equal(@"a\u0062c\u0064e", javaScriptEncoder.Encode("abcde")); 54 | Assert.Same(javaScriptEncoder, serviceProvider.GetRequiredService()); // as singleton instance 55 | 56 | var urlEncoder = serviceProvider.GetRequiredService(); 57 | Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); 58 | Assert.Same(urlEncoder, serviceProvider.GetRequiredService()); // as singleton instance 59 | } 60 | 61 | [Fact] 62 | public void AddWebEncoders_DoesNotOverrideExistingRegisteredEncoders() 63 | { 64 | // Arrange 65 | var serviceCollection = new ServiceCollection(); 66 | 67 | // Act 68 | serviceCollection.AddSingleton(); 69 | serviceCollection.AddSingleton(); 70 | // we don't register an existing URL encoder 71 | serviceCollection.AddWebEncoders(options => 72 | { 73 | options.TextEncoderSettings = new TextEncoderSettings(); 74 | options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed 75 | }); 76 | 77 | // Assert 78 | var serviceProvider = serviceCollection.BuildServiceProvider(); 79 | 80 | var htmlEncoder = serviceProvider.GetRequiredService(); 81 | Assert.Equal("HtmlEncode[[abcde]]", htmlEncoder.Encode("abcde")); 82 | 83 | var javaScriptEncoder = serviceProvider.GetRequiredService(); 84 | Assert.Equal("JavaScriptEncode[[abcde]]", javaScriptEncoder.Encode("abcde")); 85 | 86 | var urlEncoder = serviceProvider.GetRequiredService(); 87 | Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /HtmlAbstractions.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.26127.0 4 | MinimumVisualStudioVersion = 15.0.26730.03 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A5A15F1C-885A-452A-A731-B0173DDBD913}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F31FF137-390C-49BF-A3BD-7C6ED3597C21}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Html.Abstractions", "src\Microsoft.AspNetCore.Html.Abstractions\Microsoft.AspNetCore.Html.Abstractions.csproj", "{68A28E4A-3ADE-4187-9625-4FF185887CB3}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Html.Abstractions.Test", "test\Microsoft.AspNetCore.Html.Abstractions.Test\Microsoft.AspNetCore.Html.Abstractions.Test.csproj", "{2D187B88-94BD-4A39-AC97-F8F8B9363301}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B4962A29-BE69-4A18-9B4F-B803EEE31EAA}" 14 | ProjectSection(SolutionItems) = preProject 15 | NuGetPackageVerifier.json = NuGetPackageVerifier.json 16 | EndProjectSection 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.WebEncoders", "src\Microsoft.Extensions.WebEncoders\Microsoft.Extensions.WebEncoders.csproj", "{DD2CE416-765E-4000-A03E-C2FF165DA1B6}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.WebEncoders.Tests", "test\Microsoft.Extensions.WebEncoders.Tests\Microsoft.Extensions.WebEncoders.Tests.csproj", "{7AE2731D-43CD-4CF8-850A-4914DE2CE930}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|Mixed Platforms = Debug|Mixed Platforms 26 | Debug|x86 = Debug|x86 27 | Release|Any CPU = Release|Any CPU 28 | Release|Mixed Platforms = Release|Mixed Platforms 29 | Release|x86 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 35 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 36 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Debug|x86.Build.0 = Debug|Any CPU 38 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 41 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU 42 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Release|x86.ActiveCfg = Release|Any CPU 43 | {68A28E4A-3ADE-4187-9625-4FF185887CB3}.Release|x86.Build.0 = Release|Any CPU 44 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 47 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 48 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Debug|x86.Build.0 = Debug|Any CPU 50 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 53 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|Mixed Platforms.Build.0 = Release|Any CPU 54 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|x86.ActiveCfg = Release|Any CPU 55 | {2D187B88-94BD-4A39-AC97-F8F8B9363301}.Release|x86.Build.0 = Release|Any CPU 56 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 59 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 60 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|x86.ActiveCfg = Debug|Any CPU 61 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Debug|x86.Build.0 = Debug|Any CPU 62 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 65 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|Mixed Platforms.Build.0 = Release|Any CPU 66 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|x86.ActiveCfg = Release|Any CPU 67 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6}.Release|x86.Build.0 = Release|Any CPU 68 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 71 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 72 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|x86.ActiveCfg = Debug|Any CPU 73 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Debug|x86.Build.0 = Debug|Any CPU 74 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 77 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|Mixed Platforms.Build.0 = Release|Any CPU 78 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|x86.ActiveCfg = Release|Any CPU 79 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930}.Release|x86.Build.0 = Release|Any CPU 80 | EndGlobalSection 81 | GlobalSection(SolutionProperties) = preSolution 82 | HideSolutionNode = FALSE 83 | EndGlobalSection 84 | GlobalSection(NestedProjects) = preSolution 85 | {68A28E4A-3ADE-4187-9625-4FF185887CB3} = {A5A15F1C-885A-452A-A731-B0173DDBD913} 86 | {2D187B88-94BD-4A39-AC97-F8F8B9363301} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21} 87 | {DD2CE416-765E-4000-A03E-C2FF165DA1B6} = {A5A15F1C-885A-452A-A731-B0173DDBD913} 88 | {7AE2731D-43CD-4CF8-850A-4914DE2CE930} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21} 89 | EndGlobalSection 90 | EndGlobal 91 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/HtmlContentBuilder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Text.Encodings.Web; 8 | 9 | namespace Microsoft.AspNetCore.Html 10 | { 11 | /// 12 | /// An implementation using an in memory list. 13 | /// 14 | public class HtmlContentBuilder : IHtmlContentBuilder 15 | { 16 | /// 17 | /// Creates a new . 18 | /// 19 | public HtmlContentBuilder() 20 | : this(new List()) 21 | { 22 | } 23 | 24 | /// 25 | /// Creates a new with the given initial capacity. 26 | /// 27 | /// The initial capacity of the backing store. 28 | public HtmlContentBuilder(int capacity) 29 | : this(new List(capacity)) 30 | { 31 | } 32 | 33 | /// 34 | /// Gets the number of elements in the . 35 | /// 36 | public int Count => Entries.Count; 37 | 38 | /// 39 | /// Creates a new with the given list of entries. 40 | /// 41 | /// 42 | /// The list of entries. The will use this list without making a copy. 43 | /// 44 | public HtmlContentBuilder(IList entries) 45 | { 46 | if (entries == null) 47 | { 48 | throw new ArgumentNullException(nameof(entries)); 49 | } 50 | 51 | Entries = entries; 52 | } 53 | 54 | // This is not List because that would lead to wrapping all strings to IHtmlContent 55 | // which is not space performant. 56 | // 57 | // In general unencoded strings are added here. We're optimizing for that case, and allocating 58 | // a wrapper when encoded strings are used. 59 | // 60 | // internal for testing. 61 | internal IList Entries { get; } 62 | 63 | /// 64 | public IHtmlContentBuilder Append(string unencoded) 65 | { 66 | if (!string.IsNullOrEmpty(unencoded)) 67 | { 68 | Entries.Add(unencoded); 69 | } 70 | 71 | return this; 72 | } 73 | 74 | /// 75 | public IHtmlContentBuilder AppendHtml(IHtmlContent htmlContent) 76 | { 77 | if (htmlContent == null) 78 | { 79 | return this; 80 | } 81 | 82 | Entries.Add(htmlContent); 83 | return this; 84 | } 85 | 86 | /// 87 | public IHtmlContentBuilder AppendHtml(string encoded) 88 | { 89 | if (!string.IsNullOrEmpty(encoded)) 90 | { 91 | Entries.Add(new HtmlString(encoded)); 92 | } 93 | 94 | return this; 95 | } 96 | 97 | /// 98 | public IHtmlContentBuilder Clear() 99 | { 100 | Entries.Clear(); 101 | return this; 102 | } 103 | 104 | /// 105 | public void CopyTo(IHtmlContentBuilder destination) 106 | { 107 | if (destination == null) 108 | { 109 | throw new ArgumentNullException(nameof(destination)); 110 | } 111 | 112 | for (var i = 0; i < Entries.Count; i++) 113 | { 114 | var entry = Entries[i]; 115 | 116 | string entryAsString; 117 | IHtmlContentContainer entryAsContainer; 118 | if ((entryAsString = entry as string) != null) 119 | { 120 | destination.Append(entryAsString); 121 | } 122 | else if ((entryAsContainer = entry as IHtmlContentContainer) != null) 123 | { 124 | // Since we're copying, do a deep flatten. 125 | entryAsContainer.CopyTo(destination); 126 | } 127 | else 128 | { 129 | // Only string, IHtmlContent values can be added to the buffer. 130 | destination.AppendHtml((IHtmlContent)entry); 131 | } 132 | } 133 | } 134 | 135 | /// 136 | public void MoveTo(IHtmlContentBuilder destination) 137 | { 138 | if (destination == null) 139 | { 140 | throw new ArgumentNullException(nameof(destination)); 141 | } 142 | 143 | for (var i = 0; i < Entries.Count; i++) 144 | { 145 | var entry = Entries[i]; 146 | 147 | string entryAsString; 148 | IHtmlContentContainer entryAsContainer; 149 | if ((entryAsString = entry as string) != null) 150 | { 151 | destination.Append(entryAsString); 152 | } 153 | else if ((entryAsContainer = entry as IHtmlContentContainer) != null) 154 | { 155 | // Since we're moving, do a deep flatten. 156 | entryAsContainer.MoveTo(destination); 157 | } 158 | else 159 | { 160 | // Only string, IHtmlContent values can be added to the buffer. 161 | destination.AppendHtml((IHtmlContent)entry); 162 | } 163 | } 164 | 165 | Entries.Clear(); 166 | } 167 | 168 | /// 169 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 170 | { 171 | if (writer == null) 172 | { 173 | throw new ArgumentNullException(nameof(writer)); 174 | } 175 | 176 | if (encoder == null) 177 | { 178 | throw new ArgumentNullException(nameof(encoder)); 179 | } 180 | 181 | for (var i = 0; i < Entries.Count; i++) 182 | { 183 | var entry = Entries[i]; 184 | 185 | var entryAsString = entry as string; 186 | if (entryAsString != null) 187 | { 188 | encoder.Encode(writer, entryAsString); 189 | } 190 | else 191 | { 192 | // Only string, IHtmlContent values can be added to the buffer. 193 | ((IHtmlContent)entry).WriteTo(writer, encoder); 194 | } 195 | } 196 | } 197 | 198 | private string DebuggerToString() 199 | { 200 | using (var writer = new StringWriter()) 201 | { 202 | WriteTo(writer, HtmlEncoder.Default); 203 | return writer.ToString(); 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /run.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | #requires -version 4 3 | 4 | <# 5 | .SYNOPSIS 6 | Executes KoreBuild commands. 7 | 8 | .DESCRIPTION 9 | Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. 10 | 11 | .PARAMETER Command 12 | The KoreBuild command to run. 13 | 14 | .PARAMETER Path 15 | The folder to build. Defaults to the folder containing this script. 16 | 17 | .PARAMETER Channel 18 | The channel of KoreBuild to download. Overrides the value from the config file. 19 | 20 | .PARAMETER DotNetHome 21 | The directory where .NET Core tools will be stored. 22 | 23 | .PARAMETER ToolsSource 24 | The base url where build tools can be downloaded. Overrides the value from the config file. 25 | 26 | .PARAMETER Update 27 | Updates KoreBuild to the latest version even if a lock file is present. 28 | 29 | .PARAMETER Reinstall 30 | Re-installs KoreBuild 31 | 32 | .PARAMETER ConfigFile 33 | The path to the configuration file that stores values. Defaults to korebuild.json. 34 | 35 | .PARAMETER ToolsSourceSuffix 36 | The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. 37 | 38 | .PARAMETER CI 39 | Sets up CI specific settings and variables. 40 | 41 | .PARAMETER Arguments 42 | Arguments to be passed to the command 43 | 44 | .NOTES 45 | This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. 46 | When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. 47 | 48 | The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set 49 | in the file are overridden by command line parameters. 50 | 51 | .EXAMPLE 52 | Example config file: 53 | ```json 54 | { 55 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 56 | "channel": "master", 57 | "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" 58 | } 59 | ``` 60 | #> 61 | [CmdletBinding(PositionalBinding = $false)] 62 | param( 63 | [Parameter(Mandatory = $true, Position = 0)] 64 | [string]$Command, 65 | [string]$Path = $PSScriptRoot, 66 | [Alias('c')] 67 | [string]$Channel, 68 | [Alias('d')] 69 | [string]$DotNetHome, 70 | [Alias('s')] 71 | [string]$ToolsSource, 72 | [Alias('u')] 73 | [switch]$Update, 74 | [switch]$Reinstall, 75 | [string]$ToolsSourceSuffix, 76 | [string]$ConfigFile = $null, 77 | [switch]$CI, 78 | [Parameter(ValueFromRemainingArguments = $true)] 79 | [string[]]$Arguments 80 | ) 81 | 82 | Set-StrictMode -Version 2 83 | $ErrorActionPreference = 'Stop' 84 | 85 | # 86 | # Functions 87 | # 88 | 89 | function Get-KoreBuild { 90 | 91 | $lockFile = Join-Path $Path 'korebuild-lock.txt' 92 | 93 | if (!(Test-Path $lockFile) -or $Update) { 94 | Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix 95 | } 96 | 97 | $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 98 | if (!$version) { 99 | Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" 100 | } 101 | $version = $version.TrimStart('version:').Trim() 102 | $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) 103 | 104 | if ($Reinstall -and (Test-Path $korebuildPath)) { 105 | Remove-Item -Force -Recurse $korebuildPath 106 | } 107 | 108 | if (!(Test-Path $korebuildPath)) { 109 | Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" 110 | New-Item -ItemType Directory -Path $korebuildPath | Out-Null 111 | $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" 112 | 113 | try { 114 | $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" 115 | Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix 116 | if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { 117 | # Use built-in commands where possible as they are cross-plat compatible 118 | Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath 119 | } 120 | else { 121 | # Fallback to old approach for old installations of PowerShell 122 | Add-Type -AssemblyName System.IO.Compression.FileSystem 123 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) 124 | } 125 | } 126 | catch { 127 | Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore 128 | throw 129 | } 130 | finally { 131 | Remove-Item $tmpfile -ErrorAction Ignore 132 | } 133 | } 134 | 135 | return $korebuildPath 136 | } 137 | 138 | function Join-Paths([string]$path, [string[]]$childPaths) { 139 | $childPaths | ForEach-Object { $path = Join-Path $path $_ } 140 | return $path 141 | } 142 | 143 | function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { 144 | if ($RemotePath -notlike 'http*') { 145 | Copy-Item $RemotePath $LocalPath 146 | return 147 | } 148 | 149 | $retries = 10 150 | while ($retries -gt 0) { 151 | $retries -= 1 152 | try { 153 | Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath 154 | return 155 | } 156 | catch { 157 | Write-Verbose "Request failed. $retries retries remaining" 158 | } 159 | } 160 | 161 | Write-Error "Download failed: '$RemotePath'." 162 | } 163 | 164 | # 165 | # Main 166 | # 167 | 168 | # Load configuration or set defaults 169 | 170 | $Path = Resolve-Path $Path 171 | if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } 172 | 173 | if (Test-Path $ConfigFile) { 174 | try { 175 | $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json 176 | if ($config) { 177 | if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } 178 | if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} 179 | } 180 | } 181 | catch { 182 | Write-Host -ForegroundColor Red $Error[0] 183 | Write-Error "$ConfigFile contains invalid JSON." 184 | exit 1 185 | } 186 | } 187 | 188 | if (!$DotNetHome) { 189 | $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` 190 | elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` 191 | elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` 192 | else { Join-Path $PSScriptRoot '.dotnet'} 193 | } 194 | 195 | if (!$Channel) { $Channel = 'master' } 196 | if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } 197 | 198 | # Execute 199 | 200 | $korebuildPath = Get-KoreBuild 201 | Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') 202 | 203 | try { 204 | Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI 205 | Invoke-KoreBuildCommand $Command @Arguments 206 | } 207 | finally { 208 | Remove-Module 'KoreBuild' -ErrorAction Ignore 209 | } 210 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Html.Abstractions.Test/HtmlFormattableStringTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | using Microsoft.AspNetCore.Testing; 8 | using Microsoft.Extensions.WebEncoders.Testing; 9 | using Xunit; 10 | 11 | namespace Microsoft.AspNetCore.Html 12 | { 13 | public class HtmlFormattableStringTest 14 | { 15 | [Fact] 16 | public void HtmlFormattableString_EmptyArgs() 17 | { 18 | // Arrange 19 | var formattableString = new HtmlFormattableString("Hello, World!"); 20 | 21 | // Act 22 | var result = HtmlContentToString(formattableString); 23 | 24 | // Assert 25 | Assert.Equal("Hello, World!", result); 26 | } 27 | 28 | [Fact] 29 | public void HtmlFormattableString_EmptyArgsAndCulture() 30 | { 31 | // Arrange 32 | var formattableString = new HtmlFormattableString(CultureInfo.CurrentCulture, "Hello, World!"); 33 | 34 | // Act 35 | var result = HtmlContentToString(formattableString); 36 | 37 | // Assert 38 | Assert.Equal("Hello, World!", result); 39 | } 40 | 41 | [Fact] 42 | public void HtmlFormattableString_MultipleArguments() 43 | { 44 | // Arrange 45 | var formattableString = new HtmlFormattableString("{0} {1} {2} {3}!", "First", "Second", "Third", "Fourth"); 46 | 47 | // Act 48 | var result = HtmlContentToString(formattableString); 49 | 50 | // Assert 51 | Assert.Equal( 52 | "HtmlEncode[[First]] HtmlEncode[[Second]] HtmlEncode[[Third]] HtmlEncode[[Fourth]]!", 53 | result); 54 | } 55 | 56 | [Fact] 57 | public void HtmlFormattableString_WithHtmlString() 58 | { 59 | // Arrange 60 | var formattableString = new HtmlFormattableString("{0}!", new HtmlString("First")); 61 | 62 | // Act 63 | var result = HtmlContentToString(formattableString); 64 | 65 | // Assert 66 | Assert.Equal("First!", result); 67 | } 68 | 69 | [Fact] 70 | public void HtmlFormattableString_WithOtherIHtmlContent() 71 | { 72 | // Arrange 73 | var builder = new HtmlContentBuilder(); 74 | builder.Append("First"); 75 | 76 | var formattableString = new HtmlFormattableString("{0}!", builder); 77 | 78 | // Act 79 | var result = HtmlContentToString(formattableString); 80 | 81 | // Assert 82 | Assert.Equal("HtmlEncode[[First]]!", result); 83 | } 84 | 85 | // This test is needed to ensure the shared StringWriter gets cleared. 86 | [Fact] 87 | public void HtmlFormattableString_WithMultipleHtmlContentArguments() 88 | { 89 | // Arrange 90 | var formattableString = new HtmlFormattableString( 91 | "Happy {0}, {1}!", 92 | new HtmlString("Birthday"), 93 | new HtmlContentBuilder().Append("Billy")); 94 | 95 | // Act 96 | var result = HtmlContentToString(formattableString); 97 | 98 | // Assert 99 | Assert.Equal("Happy Birthday, HtmlEncode[[Billy]]!", result); 100 | } 101 | 102 | [Fact] 103 | public void HtmlFormattableString_WithHtmlString_AndOffset() 104 | { 105 | // Arrange 106 | var formattableString = new HtmlFormattableString("{0, 20}!", new HtmlString("First")); 107 | 108 | // Act 109 | var result = HtmlContentToString(formattableString); 110 | 111 | // Assert 112 | Assert.Equal(" First!", result); 113 | } 114 | 115 | [Fact] 116 | public void HtmlFormattableString_With3Arguments() 117 | { 118 | // Arrange 119 | var formattableString = new HtmlFormattableString("0x{0:X} - {1} equivalent for {2}.", 50, "hex", 50); 120 | 121 | // Act 122 | var result = HtmlContentToString(formattableString); 123 | 124 | // Assert 125 | Assert.Equal( 126 | "0xHtmlEncode[[32]] - HtmlEncode[[hex]] equivalent for HtmlEncode[[50]].", 127 | result); 128 | } 129 | 130 | [Fact] 131 | public void HtmlFormattableString_WithAlignmentComponent() 132 | { 133 | // Arrange 134 | var formattableString = new HtmlFormattableString("{0, -25} World!", "Hello"); 135 | 136 | // Act 137 | var result = HtmlContentToString(formattableString); 138 | 139 | // Assert 140 | Assert.Equal( 141 | "HtmlEncode[[Hello]] World!", result); 142 | } 143 | 144 | [Fact] 145 | public void HtmlFormattableString_WithFormatStringComponent() 146 | { 147 | // Arrange 148 | var formattableString = new HtmlFormattableString("0x{0:X}", 50); 149 | 150 | // Act 151 | var result = HtmlContentToString(formattableString); 152 | 153 | // Assert 154 | Assert.Equal("0xHtmlEncode[[32]]", result); 155 | } 156 | 157 | [Fact] 158 | public void HtmlFormattableString_WithCulture() 159 | { 160 | // Arrange 161 | var formattableString = new HtmlFormattableString( 162 | CultureInfo.InvariantCulture, 163 | "Numbers in InvariantCulture - {0, -5:N} {1} {2} {3}!", 164 | 1.1, 165 | 2.98, 166 | 145.82, 167 | 32.86); 168 | 169 | // Act 170 | var result = HtmlContentToString(formattableString); 171 | 172 | // Assert 173 | Assert.Equal( 174 | "Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]] " + 175 | "HtmlEncode[[145.82]] HtmlEncode[[32.86]]!", 176 | result); 177 | } 178 | 179 | [Fact] 180 | [ReplaceCulture("en-US", "en-US")] 181 | public void HtmlFormattableString_UsesPassedInCulture() 182 | { 183 | // Arrange 184 | var culture = new CultureInfo("fr-FR"); 185 | var formattableString = new HtmlFormattableString(culture, "{0} in french!", 1.21); 186 | 187 | // Act 188 | var result = HtmlContentToString(formattableString); 189 | 190 | // Assert 191 | Assert.Equal("HtmlEncode[[1,21]] in french!", result); 192 | } 193 | 194 | [Fact] 195 | [ReplaceCulture("de-DE", "de-DE")] 196 | public void HtmlFormattableString_UsesCurrentCulture() 197 | { 198 | // Arrange 199 | var formattableString = new HtmlFormattableString("{0:D}", DateTime.Parse("01/02/2015")); 200 | 201 | // Act 202 | var result = HtmlContentToString(formattableString); 203 | 204 | // Assert 205 | Assert.Equal("HtmlEncode[[Sonntag, 1. Februar 2015]]", result); 206 | } 207 | 208 | private static string HtmlContentToString(IHtmlContent content) 209 | { 210 | using (var writer = new StringWriter()) 211 | { 212 | content.WriteTo(writer, new HtmlTestEncoder()); 213 | return writer.ToString(); 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/HtmlFormattableString.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Text.Encodings.Web; 9 | 10 | namespace Microsoft.AspNetCore.Html 11 | { 12 | /// 13 | /// An implementation of composite string formatting 14 | /// (see https://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx) which HTML encodes 15 | /// formatted arguments. 16 | /// 17 | [DebuggerDisplay("{DebuggerToString()}")] 18 | public class HtmlFormattableString : IHtmlContent 19 | { 20 | private readonly IFormatProvider _formatProvider; 21 | private readonly string _format; 22 | private readonly object[] _args; 23 | 24 | /// 25 | /// Creates a new with the given and 26 | /// . 27 | /// 28 | /// A composite format string. 29 | /// An array that contains objects to format. 30 | public HtmlFormattableString(string format, params object[] args) 31 | : this(formatProvider: null, format: format, args: args) 32 | { 33 | } 34 | 35 | /// 36 | /// Creates a new with the given , 37 | /// and . 38 | /// 39 | /// An object that provides culture-specific formatting information. 40 | /// A composite format string. 41 | /// An array that contains objects to format. 42 | public HtmlFormattableString(IFormatProvider formatProvider, string format, params object[] args) 43 | { 44 | if (format == null) 45 | { 46 | throw new ArgumentNullException(nameof(format)); 47 | } 48 | 49 | if (args == null) 50 | { 51 | throw new ArgumentNullException(nameof(args)); 52 | } 53 | 54 | _formatProvider = formatProvider ?? CultureInfo.CurrentCulture; 55 | _format = format; 56 | _args = args; 57 | } 58 | 59 | /// 60 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 61 | { 62 | if (writer == null) 63 | { 64 | throw new ArgumentNullException(nameof(writer)); 65 | } 66 | 67 | if (encoder == null) 68 | { 69 | throw new ArgumentNullException(nameof(encoder)); 70 | } 71 | 72 | var formatProvider = new EncodingFormatProvider(_formatProvider, encoder); 73 | writer.Write(string.Format(formatProvider, _format, _args)); 74 | } 75 | 76 | private string DebuggerToString() 77 | { 78 | using (var writer = new StringWriter()) 79 | { 80 | WriteTo(writer, HtmlEncoder.Default); 81 | return writer.ToString(); 82 | } 83 | } 84 | 85 | // This class implements Html encoding via an ICustomFormatter. Passing an instance of this 86 | // class into a string.Format method or anything similar will evaluate arguments implementing 87 | // IHtmlContent without HTML encoding them, and will give other arguments the standard 88 | // composite format string treatment, and then HTML encode the result. 89 | // 90 | // Plenty of examples of ICustomFormatter and the interactions with string.Format here: 91 | // https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx#Format6_Example 92 | private class EncodingFormatProvider : IFormatProvider, ICustomFormatter 93 | { 94 | private readonly HtmlEncoder _encoder; 95 | private readonly IFormatProvider _formatProvider; 96 | 97 | private StringWriter _writer; 98 | 99 | public EncodingFormatProvider(IFormatProvider formatProvider, HtmlEncoder encoder) 100 | { 101 | Debug.Assert(formatProvider != null); 102 | Debug.Assert(encoder != null); 103 | 104 | _formatProvider = formatProvider; 105 | _encoder = encoder; 106 | } 107 | 108 | public string Format(string format, object arg, IFormatProvider formatProvider) 109 | { 110 | // These are the cases we need to special case. We trust the HtmlString or IHtmlContent instance 111 | // to do the right thing with encoding. 112 | var htmlString = arg as HtmlString; 113 | if (htmlString != null) 114 | { 115 | return htmlString.ToString(); 116 | } 117 | 118 | var htmlContent = arg as IHtmlContent; 119 | if (htmlContent != null) 120 | { 121 | _writer = _writer ?? new StringWriter(); 122 | 123 | htmlContent.WriteTo(_writer, _encoder); 124 | 125 | var result = _writer.ToString(); 126 | _writer.GetStringBuilder().Clear(); 127 | 128 | return result; 129 | } 130 | 131 | // If we get here then 'arg' is not an IHtmlContent, and we want to handle it the way a normal 132 | // string.Format would work, but then HTML encode the result. 133 | // 134 | // First check for an ICustomFormatter - if the IFormatProvider is a CultureInfo, then it's likely 135 | // that ICustomFormatter will be null. 136 | var customFormatter = (ICustomFormatter)_formatProvider.GetFormat(typeof(ICustomFormatter)); 137 | if (customFormatter != null) 138 | { 139 | var result = customFormatter.Format(format, arg, _formatProvider); 140 | if (result != null) 141 | { 142 | return _encoder.Encode(result); 143 | } 144 | } 145 | 146 | // Next check if 'arg' is an IFormattable (DateTime is an example). 147 | // 148 | // An IFormattable will likely call back into the IFormatterProvider and ask for more information 149 | // about how to format itself. This is the typical case when IFormatterProvider is a CultureInfo. 150 | var formattable = arg as IFormattable; 151 | if (formattable != null) 152 | { 153 | var result = formattable.ToString(format, _formatProvider); 154 | if (result != null) 155 | { 156 | return _encoder.Encode(result); 157 | } 158 | } 159 | 160 | // If we get here then there's nothing really smart left to try. 161 | if (arg != null) 162 | { 163 | var result = arg.ToString(); 164 | if (result != null) 165 | { 166 | return _encoder.Encode(result); 167 | } 168 | } 169 | 170 | return string.Empty; 171 | } 172 | 173 | public object GetFormat(Type formatType) 174 | { 175 | if (formatType == typeof(ICustomFormatter)) 176 | { 177 | return this; 178 | } 179 | 180 | return null; 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Html.Abstractions.Test/HtmlContentBuilderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text.Encodings.Web; 7 | using Microsoft.AspNetCore.Html; 8 | using Microsoft.Extensions.WebEncoders.Testing; 9 | using Xunit; 10 | 11 | namespace Microsoft.Extensions.Internal 12 | { 13 | public class HtmlContentBuilderTest 14 | { 15 | [Fact] 16 | public void AppendString_AppendsAString() 17 | { 18 | // Arrange 19 | var content = new HtmlContentBuilder(); 20 | 21 | // Act 22 | content.Append("Hello"); 23 | 24 | // Assert 25 | Assert.Equal(1, content.Count); 26 | var result = Assert.Single(content.Entries); 27 | Assert.IsType(result); 28 | } 29 | 30 | [Fact] 31 | public void AppendString_WrittenAsEncoded() 32 | { 33 | // Arrange 34 | var content = new HtmlContentBuilder(); 35 | content.Append("Hello"); 36 | 37 | var writer = new StringWriter(); 38 | 39 | // Act 40 | content.WriteTo(writer, new HtmlTestEncoder()); 41 | 42 | // Assert 43 | Assert.Equal("HtmlEncode[[Hello]]", writer.ToString()); 44 | } 45 | 46 | [Fact] 47 | public void AppendHtml_DoesNotGetWrittenAsEncoded() 48 | { 49 | // Arrange 50 | var content = new HtmlContentBuilder(); 51 | content.AppendHtml("Hello"); 52 | 53 | var writer = new StringWriter(); 54 | 55 | // Act 56 | content.WriteTo(writer, new HtmlTestEncoder()); 57 | 58 | // Assert 59 | Assert.Equal("Hello", writer.ToString()); 60 | } 61 | 62 | [Fact] 63 | public void AppendIHtmlContent_AppendsAsIs() 64 | { 65 | // Arrange 66 | var content = new HtmlContentBuilder(); 67 | var writer = new StringWriter(); 68 | 69 | // Act 70 | content.AppendHtml(new TestHtmlContent("Hello")); 71 | 72 | // Assert 73 | Assert.Equal(1, content.Count); 74 | var result = Assert.Single(content.Entries); 75 | var testHtmlContent = Assert.IsType(result); 76 | testHtmlContent.WriteTo(writer, new HtmlTestEncoder()); 77 | Assert.Equal("Written from TestHtmlContent: Hello", writer.ToString()); 78 | } 79 | 80 | [Fact] 81 | public void CanAppendMultipleItems() 82 | { 83 | // Arrange 84 | var content = new HtmlContentBuilder(); 85 | 86 | // Act 87 | content.AppendHtml(new TestHtmlContent("hello")); 88 | content.Append("Test"); 89 | 90 | // Assert 91 | Assert.Equal(2, content.Count); 92 | Assert.Collection( 93 | content.Entries, 94 | entry => Assert.Equal("Written from TestHtmlContent: hello", entry.ToString()), 95 | entry => Assert.Equal("Test", entry)); 96 | } 97 | 98 | [Fact] 99 | public void Clear_DeletesAllItems() 100 | { 101 | // Arrange 102 | var content = new HtmlContentBuilder(); 103 | content.AppendHtml(new TestHtmlContent("hello")); 104 | content.Append("Test"); 105 | 106 | // Act 107 | content.Clear(); 108 | 109 | // Assert 110 | Assert.Equal(0, content.Count); 111 | Assert.Empty(content.Entries); 112 | } 113 | 114 | [Fact] 115 | public void CopyTo_CopiesAllItems() 116 | { 117 | // Arrange 118 | var source = new HtmlContentBuilder(); 119 | source.AppendHtml(new TestHtmlContent("hello")); 120 | source.Append("Test"); 121 | 122 | var destination = new HtmlContentBuilder(); 123 | destination.Append("some-content"); 124 | 125 | // Act 126 | source.CopyTo(destination); 127 | 128 | // Assert 129 | Assert.Equal(2, source.Count); 130 | Assert.Equal(3, destination.Count); 131 | Assert.Collection( 132 | destination.Entries, 133 | entry => Assert.Equal("some-content", Assert.IsType(entry)), 134 | entry => Assert.Equal(new TestHtmlContent("hello"), Assert.IsType(entry)), 135 | entry => Assert.Equal("Test", Assert.IsType(entry))); 136 | } 137 | 138 | [Fact] 139 | public void CopyTo_DoesDeepCopy() 140 | { 141 | // Arrange 142 | var source = new HtmlContentBuilder(); 143 | 144 | var nested = new HtmlContentBuilder(); 145 | source.AppendHtml(nested); 146 | nested.AppendHtml(new TestHtmlContent("hello")); 147 | source.Append("Test"); 148 | 149 | var destination = new HtmlContentBuilder(); 150 | destination.Append("some-content"); 151 | 152 | // Act 153 | source.CopyTo(destination); 154 | 155 | // Assert 156 | Assert.Equal(2, source.Count); 157 | Assert.Equal(1, nested.Count); 158 | Assert.Equal(3, destination.Count); 159 | Assert.Collection( 160 | destination.Entries, 161 | entry => Assert.Equal("some-content", Assert.IsType(entry)), 162 | entry => Assert.Equal(new TestHtmlContent("hello"), Assert.IsType(entry)), 163 | entry => Assert.Equal("Test", Assert.IsType(entry))); 164 | } 165 | 166 | [Fact] 167 | public void MoveTo_CopiesAllItems_AndClears() 168 | { 169 | // Arrange 170 | var source = new HtmlContentBuilder(); 171 | source.AppendHtml(new TestHtmlContent("hello")); 172 | source.Append("Test"); 173 | 174 | var destination = new HtmlContentBuilder(); 175 | destination.Append("some-content"); 176 | 177 | // Act 178 | source.MoveTo(destination); 179 | 180 | // Assert 181 | Assert.Equal(0, source.Count); 182 | Assert.Equal(3, destination.Count); 183 | Assert.Collection( 184 | destination.Entries, 185 | entry => Assert.Equal("some-content", Assert.IsType(entry)), 186 | entry => Assert.Equal(new TestHtmlContent("hello"), Assert.IsType(entry)), 187 | entry => Assert.Equal("Test", Assert.IsType(entry))); 188 | } 189 | 190 | [Fact] 191 | public void MoveTo_DoesDeepMove() 192 | { 193 | // Arrange 194 | var source = new HtmlContentBuilder(); 195 | 196 | var nested = new HtmlContentBuilder(); 197 | source.AppendHtml(nested); 198 | nested.AppendHtml(new TestHtmlContent("hello")); 199 | source.Append("Test"); 200 | 201 | var destination = new HtmlContentBuilder(); 202 | destination.Append("some-content"); 203 | 204 | // Act 205 | source.MoveTo(destination); 206 | 207 | // Assert 208 | Assert.Equal(0, source.Count); 209 | Assert.Equal(0, nested.Count); 210 | Assert.Equal(3, destination.Count); 211 | Assert.Collection( 212 | destination.Entries, 213 | entry => Assert.Equal("some-content", Assert.IsType(entry)), 214 | entry => Assert.Equal(new TestHtmlContent("hello"), Assert.IsType(entry)), 215 | entry => Assert.Equal("Test", Assert.IsType(entry))); 216 | } 217 | 218 | [Fact] 219 | public void WriteTo_WritesAllItems() 220 | { 221 | // Arrange 222 | var content = new HtmlContentBuilder(); 223 | var writer = new StringWriter(); 224 | content.AppendHtml(new TestHtmlContent("Hello")); 225 | content.Append("Test"); 226 | 227 | // Act 228 | content.WriteTo(writer, new HtmlTestEncoder()); 229 | 230 | // Assert 231 | Assert.Equal(2, content.Count); 232 | Assert.Equal("Written from TestHtmlContent: HelloHtmlEncode[[Test]]", writer.ToString()); 233 | } 234 | 235 | private class TestHtmlContent : IHtmlContent, IEquatable 236 | { 237 | private string _content; 238 | 239 | public TestHtmlContent(string content) 240 | { 241 | _content = content; 242 | } 243 | 244 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 245 | { 246 | writer.Write(ToString()); 247 | } 248 | 249 | public override string ToString() 250 | { 251 | return "Written from TestHtmlContent: " + _content; 252 | } 253 | 254 | public override int GetHashCode() 255 | { 256 | return _content.GetHashCode(); 257 | } 258 | 259 | public override bool Equals(object obj) 260 | { 261 | var other = obj as TestHtmlContent; 262 | if (other != null) 263 | { 264 | return Equals(other); 265 | } 266 | 267 | return base.Equals(obj); 268 | } 269 | 270 | public bool Equals(TestHtmlContent other) 271 | { 272 | return string.Equals(_content, other._content); 273 | } 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # 6 | # variables 7 | # 8 | 9 | RESET="\033[0m" 10 | RED="\033[0;31m" 11 | YELLOW="\033[0;33m" 12 | MAGENTA="\033[0;95m" 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" 15 | verbose=false 16 | update=false 17 | reinstall=false 18 | repo_path="$DIR" 19 | channel='' 20 | tools_source='' 21 | tools_source_suffix='' 22 | ci=false 23 | 24 | # 25 | # Functions 26 | # 27 | __usage() { 28 | echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" 29 | echo "" 30 | echo "Arguments:" 31 | echo " command The command to be run." 32 | echo " ... Arguments passed to the command. Variable number of arguments allowed." 33 | echo "" 34 | echo "Options:" 35 | echo " --verbose Show verbose output." 36 | echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." 37 | echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." 38 | echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." 39 | echo " --path The directory to build. Defaults to the directory containing the script." 40 | echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." 41 | echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." 42 | echo " -u|--update Update to the latest KoreBuild even if the lock file is present." 43 | echo " --reinstall Reinstall KoreBuild." 44 | echo " --ci Apply CI specific settings and environment variables." 45 | echo "" 46 | echo "Description:" 47 | echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." 48 | echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." 49 | 50 | if [[ "${1:-}" != '--no-exit' ]]; then 51 | exit 2 52 | fi 53 | } 54 | 55 | get_korebuild() { 56 | local version 57 | local lock_file="$repo_path/korebuild-lock.txt" 58 | if [ ! -f "$lock_file" ] || [ "$update" = true ]; then 59 | __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" 60 | fi 61 | version="$(grep 'version:*' -m 1 "$lock_file")" 62 | if [[ "$version" == '' ]]; then 63 | __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" 64 | return 1 65 | fi 66 | version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" 67 | local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" 68 | 69 | if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then 70 | rm -rf "$korebuild_path" 71 | fi 72 | 73 | { 74 | if [ ! -d "$korebuild_path" ]; then 75 | mkdir -p "$korebuild_path" 76 | local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" 77 | tmpfile="$(mktemp)" 78 | echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" 79 | if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then 80 | unzip -q -d "$korebuild_path" "$tmpfile" 81 | fi 82 | rm "$tmpfile" || true 83 | fi 84 | 85 | source "$korebuild_path/KoreBuild.sh" 86 | } || { 87 | if [ -d "$korebuild_path" ]; then 88 | echo "Cleaning up after failed installation" 89 | rm -rf "$korebuild_path" || true 90 | fi 91 | return 1 92 | } 93 | } 94 | 95 | __error() { 96 | echo -e "${RED}error: $*${RESET}" 1>&2 97 | } 98 | 99 | __warn() { 100 | echo -e "${YELLOW}warning: $*${RESET}" 101 | } 102 | 103 | __machine_has() { 104 | hash "$1" > /dev/null 2>&1 105 | return $? 106 | } 107 | 108 | __get_remote_file() { 109 | local remote_path=$1 110 | local local_path=$2 111 | local remote_path_suffix=$3 112 | 113 | if [[ "$remote_path" != 'http'* ]]; then 114 | cp "$remote_path" "$local_path" 115 | return 0 116 | fi 117 | 118 | local failed=false 119 | if __machine_has wget; then 120 | wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true 121 | else 122 | failed=true 123 | fi 124 | 125 | if [ "$failed" = true ] && __machine_has curl; then 126 | failed=false 127 | curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true 128 | fi 129 | 130 | if [ "$failed" = true ]; then 131 | __error "Download failed: $remote_path" 1>&2 132 | return 1 133 | fi 134 | } 135 | 136 | # 137 | # main 138 | # 139 | 140 | command="${1:-}" 141 | shift 142 | 143 | while [[ $# -gt 0 ]]; do 144 | case $1 in 145 | -\?|-h|--help) 146 | __usage --no-exit 147 | exit 0 148 | ;; 149 | -c|--channel|-Channel) 150 | shift 151 | channel="${1:-}" 152 | [ -z "$channel" ] && __usage 153 | ;; 154 | --config-file|-ConfigFile) 155 | shift 156 | config_file="${1:-}" 157 | [ -z "$config_file" ] && __usage 158 | if [ ! -f "$config_file" ]; then 159 | __error "Invalid value for --config-file. $config_file does not exist." 160 | exit 1 161 | fi 162 | ;; 163 | -d|--dotnet-home|-DotNetHome) 164 | shift 165 | DOTNET_HOME="${1:-}" 166 | [ -z "$DOTNET_HOME" ] && __usage 167 | ;; 168 | --path|-Path) 169 | shift 170 | repo_path="${1:-}" 171 | [ -z "$repo_path" ] && __usage 172 | ;; 173 | -s|--tools-source|-ToolsSource) 174 | shift 175 | tools_source="${1:-}" 176 | [ -z "$tools_source" ] && __usage 177 | ;; 178 | --tools-source-suffix|-ToolsSourceSuffix) 179 | shift 180 | tools_source_suffix="${1:-}" 181 | [ -z "$tools_source_suffix" ] && __usage 182 | ;; 183 | -u|--update|-Update) 184 | update=true 185 | ;; 186 | --reinstall|-[Rr]einstall) 187 | reinstall=true 188 | ;; 189 | --ci|-[Cc][Ii]) 190 | ci=true 191 | ;; 192 | --verbose|-Verbose) 193 | verbose=true 194 | ;; 195 | --) 196 | shift 197 | break 198 | ;; 199 | *) 200 | break 201 | ;; 202 | esac 203 | shift 204 | done 205 | 206 | if ! __machine_has unzip; then 207 | __error 'Missing required command: unzip' 208 | exit 1 209 | fi 210 | 211 | if ! __machine_has curl && ! __machine_has wget; then 212 | __error 'Missing required command. Either wget or curl is required.' 213 | exit 1 214 | fi 215 | 216 | [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" 217 | if [ -f "$config_file" ]; then 218 | if __machine_has jq ; then 219 | if jq '.' "$config_file" >/dev/null ; then 220 | config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" 221 | config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" 222 | else 223 | __error "$config_file contains invalid JSON." 224 | exit 1 225 | fi 226 | elif __machine_has python ; then 227 | if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then 228 | config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" 229 | config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" 230 | else 231 | __error "$config_file contains invalid JSON." 232 | exit 1 233 | fi 234 | elif __machine_has python3 ; then 235 | if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then 236 | config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" 237 | config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" 238 | else 239 | __error "$config_file contains invalid JSON." 240 | exit 1 241 | fi 242 | else 243 | __error 'Missing required command: jq or python. Could not parse the JSON file.' 244 | exit 1 245 | fi 246 | 247 | [ ! -z "${config_channel:-}" ] && channel="$config_channel" 248 | [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" 249 | fi 250 | 251 | [ -z "$channel" ] && channel='master' 252 | [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' 253 | 254 | get_korebuild 255 | set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" 256 | invoke_korebuild_command "$command" "$@" 257 | -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/HtmlContentBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Text.Encodings.Web; 9 | 10 | namespace Microsoft.AspNetCore.Html 11 | { 12 | /// 13 | /// Extension methods for . 14 | /// 15 | public static class HtmlContentBuilderExtensions 16 | { 17 | /// 18 | /// Appends the specified to the existing content after replacing each format 19 | /// item with the HTML encoded representation of the corresponding item in the 20 | /// array. 21 | /// 22 | /// The . 23 | /// 24 | /// The composite format (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx). 25 | /// The format string is assumed to be HTML encoded as-provided, and no further encoding will be performed. 26 | /// 27 | /// 28 | /// The object array to format. Each element in the array will be formatted and then HTML encoded. 29 | /// 30 | /// A reference to this instance after the append operation has completed. 31 | public static IHtmlContentBuilder AppendFormat( 32 | this IHtmlContentBuilder builder, 33 | string format, 34 | params object[] args) 35 | { 36 | if (builder == null) 37 | { 38 | throw new ArgumentNullException(nameof(builder)); 39 | } 40 | 41 | if (format == null) 42 | { 43 | throw new ArgumentNullException(nameof(format)); 44 | } 45 | 46 | if (args == null) 47 | { 48 | throw new ArgumentNullException(nameof(args)); 49 | } 50 | 51 | builder.AppendHtml(new HtmlFormattableString(format, args)); 52 | return builder; 53 | } 54 | 55 | /// 56 | /// Appends the specified to the existing content with information from the 57 | /// after replacing each format item with the HTML encoded 58 | /// representation of the corresponding item in the array. 59 | /// 60 | /// The . 61 | /// An object that supplies culture-specific formatting information. 62 | /// 63 | /// The composite format (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx). 64 | /// The format string is assumed to be HTML encoded as-provided, and no further encoding will be performed. 65 | /// 66 | /// 67 | /// The object array to format. Each element in the array will be formatted and then HTML encoded. 68 | /// 69 | /// A reference to this instance after the append operation has completed. 70 | public static IHtmlContentBuilder AppendFormat( 71 | this IHtmlContentBuilder builder, 72 | IFormatProvider formatProvider, 73 | string format, 74 | params object[] args) 75 | { 76 | if (builder == null) 77 | { 78 | throw new ArgumentNullException(nameof(builder)); 79 | } 80 | 81 | if (format == null) 82 | { 83 | throw new ArgumentNullException(nameof(format)); 84 | } 85 | 86 | if (args == null) 87 | { 88 | throw new ArgumentNullException(nameof(args)); 89 | } 90 | 91 | builder.AppendHtml(new HtmlFormattableString(formatProvider, format, args)); 92 | return builder; 93 | } 94 | 95 | /// 96 | /// Appends an . 97 | /// 98 | /// The . 99 | /// The . 100 | public static IHtmlContentBuilder AppendLine(this IHtmlContentBuilder builder) 101 | { 102 | if (builder == null) 103 | { 104 | throw new ArgumentNullException(nameof(builder)); 105 | } 106 | 107 | builder.AppendHtml(HtmlString.NewLine); 108 | return builder; 109 | } 110 | 111 | /// 112 | /// Appends an after appending the value. 113 | /// The value is treated as unencoded as-provided, and will be HTML encoded before writing to output. 114 | /// 115 | /// The . 116 | /// The to append. 117 | /// The . 118 | public static IHtmlContentBuilder AppendLine(this IHtmlContentBuilder builder, string unencoded) 119 | { 120 | if (builder == null) 121 | { 122 | throw new ArgumentNullException(nameof(builder)); 123 | } 124 | 125 | builder.Append(unencoded); 126 | builder.AppendHtml(HtmlString.NewLine); 127 | return builder; 128 | } 129 | 130 | /// 131 | /// Appends an after appending the value. 132 | /// 133 | /// The . 134 | /// The to append. 135 | /// The . 136 | public static IHtmlContentBuilder AppendLine(this IHtmlContentBuilder builder, IHtmlContent content) 137 | { 138 | if (builder == null) 139 | { 140 | throw new ArgumentNullException(nameof(builder)); 141 | } 142 | 143 | builder.AppendHtml(content); 144 | builder.AppendHtml(HtmlString.NewLine); 145 | return builder; 146 | } 147 | 148 | /// 149 | /// Appends an after appending the value. 150 | /// The value is treated as HTML encoded as-provided, and no further encoding will be performed. 151 | /// 152 | /// The . 153 | /// The HTML encoded to append. 154 | /// The . 155 | public static IHtmlContentBuilder AppendHtmlLine(this IHtmlContentBuilder builder, string encoded) 156 | { 157 | if (builder == null) 158 | { 159 | throw new ArgumentNullException(nameof(builder)); 160 | } 161 | 162 | builder.AppendHtml(encoded); 163 | builder.AppendHtml(HtmlString.NewLine); 164 | return builder; 165 | } 166 | 167 | /// 168 | /// Sets the content to the value. The value is treated as unencoded as-provided, 169 | /// and will be HTML encoded before writing to output. 170 | /// 171 | /// The . 172 | /// The value that replaces the content. 173 | /// The . 174 | public static IHtmlContentBuilder SetContent(this IHtmlContentBuilder builder, string unencoded) 175 | { 176 | if (builder == null) 177 | { 178 | throw new ArgumentNullException(nameof(builder)); 179 | } 180 | 181 | builder.Clear(); 182 | builder.Append(unencoded); 183 | return builder; 184 | } 185 | 186 | /// 187 | /// Sets the content to the value. 188 | /// 189 | /// The . 190 | /// The value that replaces the content. 191 | /// The . 192 | public static IHtmlContentBuilder SetHtmlContent(this IHtmlContentBuilder builder, IHtmlContent content) 193 | { 194 | if (builder == null) 195 | { 196 | throw new ArgumentNullException(nameof(builder)); 197 | } 198 | 199 | builder.Clear(); 200 | builder.AppendHtml(content); 201 | return builder; 202 | } 203 | 204 | /// 205 | /// Sets the content to the value. The value is treated as HTML encoded as-provided, and 206 | /// no further encoding will be performed. 207 | /// 208 | /// The . 209 | /// The HTML encoded that replaces the content. 210 | /// The . 211 | public static IHtmlContentBuilder SetHtmlContent(this IHtmlContentBuilder builder, string encoded) 212 | { 213 | if (builder == null) 214 | { 215 | throw new ArgumentNullException(nameof(builder)); 216 | } 217 | 218 | builder.Clear(); 219 | builder.AppendHtml(encoded); 220 | return builder; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) .NET Foundation and Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/Microsoft.AspNetCore.Html.Abstractions.Test/HtmlContentBuilderExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Text.Encodings.Web; 9 | using Microsoft.AspNetCore.Testing; 10 | using Microsoft.Extensions.WebEncoders.Testing; 11 | using Xunit; 12 | 13 | namespace Microsoft.AspNetCore.Html.Test 14 | { 15 | public class HtmlContentBuilderExtensionsTest 16 | { 17 | [Fact] 18 | public void Builder_AppendLine_Empty() 19 | { 20 | // Arrange 21 | var builder = new TestHtmlContentBuilder(); 22 | 23 | // Act 24 | builder.AppendLine(); 25 | 26 | // Assert 27 | Assert.Collection( 28 | builder.Entries, 29 | entry => Assert.Equal(Environment.NewLine, HtmlContentToString(entry))); 30 | } 31 | 32 | [Fact] 33 | public void Builder_AppendLine_String() 34 | { 35 | // Arrange 36 | var builder = new TestHtmlContentBuilder(); 37 | 38 | // Act 39 | builder.AppendLine("Hi"); 40 | 41 | // Assert 42 | Assert.Collection( 43 | builder.Entries, 44 | entry => Assert.Equal("Hi", Assert.IsType(entry).Value), 45 | entry => Assert.Equal(Environment.NewLine, HtmlContentToString(entry))); 46 | } 47 | 48 | [Fact] 49 | public void Builder_AppendLine_IHtmlContent() 50 | { 51 | // Arrange 52 | var builder = new TestHtmlContentBuilder(); 53 | var content = new OtherHtmlContent("Hi"); 54 | 55 | // Act 56 | builder.AppendLine(content); 57 | 58 | // Assert 59 | Assert.Collection( 60 | builder.Entries, 61 | entry => Assert.Same(content, entry), 62 | entry => Assert.Equal(Environment.NewLine, HtmlContentToString(entry))); 63 | } 64 | 65 | [Fact] 66 | public void Builder_AppendHtmlLine_String() 67 | { 68 | // Arrange 69 | var builder = new TestHtmlContentBuilder(); 70 | 71 | // Act 72 | builder.AppendHtmlLine("Hi"); 73 | 74 | // Assert 75 | Assert.Collection( 76 | builder.Entries, 77 | entry => Assert.Equal("Hi", Assert.IsType(entry).Value), 78 | entry => Assert.Equal(Environment.NewLine, HtmlContentToString(entry))); 79 | } 80 | 81 | [Fact] 82 | public void Builder_SetContent_String() 83 | { 84 | // Arrange 85 | var builder = new TestHtmlContentBuilder(); 86 | builder.Append("Existing Content. Will be Cleared."); 87 | 88 | // Act 89 | builder.SetContent("Hi"); 90 | 91 | // Assert 92 | Assert.Collection( 93 | builder.Entries, 94 | entry => Assert.Equal("Hi", Assert.IsType(entry).Value)); 95 | } 96 | 97 | [Fact] 98 | public void Builder_SetContent_IHtmlContent() 99 | { 100 | // Arrange 101 | var builder = new TestHtmlContentBuilder(); 102 | builder.Append("Existing Content. Will be Cleared."); 103 | 104 | var content = new OtherHtmlContent("Hi"); 105 | 106 | // Act 107 | builder.SetHtmlContent(content); 108 | 109 | // Assert 110 | Assert.Collection( 111 | builder.Entries, 112 | entry => Assert.Same(content, entry)); 113 | } 114 | 115 | [Fact] 116 | public void Builder_SetHtmlContent_String() 117 | { 118 | // Arrange 119 | var builder = new TestHtmlContentBuilder(); 120 | builder.Append("Existing Content. Will be Cleared."); 121 | 122 | // Act 123 | builder.SetHtmlContent("Hi"); 124 | 125 | // Assert 126 | Assert.Collection( 127 | builder.Entries, 128 | entry => Assert.Equal("Hi", Assert.IsType(entry).Value)); 129 | } 130 | 131 | [Fact] 132 | public void Builder_AppendFormat() 133 | { 134 | // Arrange 135 | var builder = new TestHtmlContentBuilder(); 136 | 137 | // Act 138 | builder.AppendFormat("{0} {1} {2} {3}!", "First", "Second", "Third", "Fourth"); 139 | 140 | // Assert 141 | Assert.Equal( 142 | "HtmlEncode[[First]] HtmlEncode[[Second]] HtmlEncode[[Third]] HtmlEncode[[Fourth]]!", 143 | HtmlContentToString(builder)); 144 | } 145 | 146 | [Fact] 147 | public void Builder_AppendFormat_HtmlContent() 148 | { 149 | // Arrange 150 | var builder = new TestHtmlContentBuilder(); 151 | 152 | // Act 153 | builder.AppendFormat("{0}!", new EncodedString("First")); 154 | 155 | // Assert 156 | Assert.Equal( 157 | "First!", 158 | HtmlContentToString(builder)); 159 | } 160 | 161 | [Fact] 162 | public void Builder_AppendFormat_HtmlString() 163 | { 164 | // Arrange 165 | var builder = new TestHtmlContentBuilder(); 166 | 167 | // Act 168 | builder.AppendFormat("{0}!", new HtmlString("First")); 169 | 170 | // Assert 171 | Assert.Equal("First!", HtmlContentToString(builder)); 172 | } 173 | 174 | [Fact] 175 | public void Builder_AppendFormatContent_With1Argument() 176 | { 177 | // Arrange 178 | var builder = new TestHtmlContentBuilder(); 179 | 180 | // Act 181 | builder.AppendFormat("0x{0:X} - hex equivalent for 50.", 50); 182 | 183 | // Assert 184 | Assert.Equal( 185 | "0xHtmlEncode[[32]] - hex equivalent for 50.", 186 | HtmlContentToString(builder)); 187 | } 188 | 189 | [Fact] 190 | public void Builder_AppendFormatContent_With2Arguments() 191 | { 192 | // Arrange 193 | var builder = new TestHtmlContentBuilder(); 194 | 195 | // Act 196 | builder.AppendFormat("0x{0:X} - hex equivalent for {1}.", 50, 50); 197 | 198 | // Assert 199 | Assert.Equal( 200 | "0xHtmlEncode[[32]] - hex equivalent for HtmlEncode[[50]].", 201 | HtmlContentToString(builder)); 202 | } 203 | 204 | [Fact] 205 | public void Builder_AppendFormatContent_With3Arguments() 206 | { 207 | // Arrange 208 | var builder = new TestHtmlContentBuilder(); 209 | 210 | // Act 211 | builder.AppendFormat("0x{0:X} - {1} equivalent for {2}.", 50, "hex", 50); 212 | 213 | // Assert 214 | Assert.Equal( 215 | "0xHtmlEncode[[32]] - HtmlEncode[[hex]] equivalent for HtmlEncode[[50]].", 216 | HtmlContentToString(builder)); 217 | } 218 | 219 | [Fact] 220 | public void Builder_AppendFormat_WithAlignmentComponent() 221 | { 222 | // Arrange 223 | var builder = new TestHtmlContentBuilder(); 224 | 225 | // Act 226 | builder.AppendFormat("{0, -25} World!", "Hello"); 227 | 228 | // Assert 229 | Assert.Equal( 230 | "HtmlEncode[[Hello]] World!", 231 | HtmlContentToString(builder)); 232 | } 233 | 234 | [Fact] 235 | public void Builder_AppendFormat_WithFormatStringComponent() 236 | { 237 | // Arrange 238 | var builder = new TestHtmlContentBuilder(); 239 | 240 | // Act 241 | builder.AppendFormat("0x{0:X}", 50); 242 | 243 | // Assert 244 | Assert.Equal("0xHtmlEncode[[32]]", HtmlContentToString(builder)); 245 | } 246 | 247 | [Fact] 248 | public void Builder_AppendFormat_WithCulture() 249 | { 250 | // Arrange 251 | var builder = new TestHtmlContentBuilder(); 252 | 253 | // Act 254 | builder.AppendFormat( 255 | CultureInfo.InvariantCulture, 256 | "Numbers in InvariantCulture - {0, -5:N} {1} {2} {3}!", 257 | 1.1, 258 | 2.98, 259 | 145.82, 260 | 32.86); 261 | 262 | // Assert 263 | Assert.Equal( 264 | "Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]] " + 265 | "HtmlEncode[[145.82]] HtmlEncode[[32.86]]!", 266 | HtmlContentToString(builder)); 267 | } 268 | 269 | [Fact] 270 | public void Builder_AppendFormat_WithCulture_1Argument() 271 | { 272 | // Arrange 273 | var builder = new TestHtmlContentBuilder(); 274 | 275 | // Act 276 | builder.AppendFormat( 277 | CultureInfo.InvariantCulture, 278 | "Numbers in InvariantCulture - {0:N}!", 279 | 1.1); 280 | 281 | // Assert 282 | Assert.Equal( 283 | "Numbers in InvariantCulture - HtmlEncode[[1.10]]!", 284 | HtmlContentToString(builder)); 285 | } 286 | 287 | [Fact] 288 | public void Builder_AppendFormat_WithCulture_2Arguments() 289 | { 290 | // Arrange 291 | var builder = new TestHtmlContentBuilder(); 292 | 293 | // Act 294 | builder.AppendFormat( 295 | CultureInfo.InvariantCulture, 296 | "Numbers in InvariantCulture - {0:N} {1}!", 297 | 1.1, 298 | 2.98); 299 | 300 | // Assert 301 | Assert.Equal( 302 | "Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]]!", 303 | HtmlContentToString(builder)); 304 | } 305 | 306 | [Fact] 307 | public void Builder_AppendFormat_WithCulture_3Arguments() 308 | { 309 | // Arrange 310 | var builder = new TestHtmlContentBuilder(); 311 | 312 | // Act 313 | builder.AppendFormat( 314 | CultureInfo.InvariantCulture, 315 | "Numbers in InvariantCulture - {0:N} {1} {2}!", 316 | 1.1, 317 | 2.98, 318 | 3.12); 319 | 320 | // Assert 321 | Assert.Equal( 322 | "Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]] HtmlEncode[[3.12]]!", 323 | HtmlContentToString(builder)); 324 | } 325 | 326 | [Fact] 327 | public void Builder_AppendFormat_WithDifferentCulture() 328 | { 329 | // Arrange 330 | var builder = new TestHtmlContentBuilder(); 331 | var culture = new CultureInfo("fr-FR"); 332 | 333 | // Act 334 | builder.AppendFormat(culture, "{0} in french!", 1.21); 335 | 336 | // Assert 337 | Assert.Equal( 338 | "HtmlEncode[[1,21]] in french!", 339 | HtmlContentToString(builder)); 340 | } 341 | 342 | [Fact] 343 | [ReplaceCulture("de-DE", "de-DE")] 344 | public void Builder_AppendFormat_WithDifferentCurrentCulture() 345 | { 346 | // Arrange 347 | var builder = new TestHtmlContentBuilder(); 348 | 349 | // Act 350 | builder.AppendFormat(CultureInfo.CurrentCulture, "{0:D}", DateTime.Parse("01/02/2015")); 351 | 352 | // Assert 353 | Assert.Equal( 354 | "HtmlEncode[[Sonntag, 1. Februar 2015]]", 355 | HtmlContentToString(builder)); 356 | } 357 | 358 | private static string HtmlContentToString(IHtmlContent content) 359 | { 360 | using (var writer = new StringWriter()) 361 | { 362 | content.WriteTo(writer, new HtmlTestEncoder()); 363 | return writer.ToString(); 364 | } 365 | } 366 | 367 | private class TestHtmlContentBuilder : IHtmlContentBuilder 368 | { 369 | public List Entries { get; } = new List(); 370 | 371 | public IHtmlContentBuilder Append(string unencoded) 372 | { 373 | Entries.Add(new UnencodedString(unencoded)); 374 | return this; 375 | } 376 | 377 | public IHtmlContentBuilder AppendHtml(IHtmlContent content) 378 | { 379 | Entries.Add(content); 380 | return this; 381 | } 382 | 383 | public IHtmlContentBuilder AppendHtml(string encoded) 384 | { 385 | Entries.Add(new EncodedString(encoded)); 386 | return this; 387 | } 388 | 389 | public IHtmlContentBuilder Clear() 390 | { 391 | Entries.Clear(); 392 | return this; 393 | } 394 | 395 | public void CopyTo(IHtmlContentBuilder destination) 396 | { 397 | foreach (var entry in Entries) 398 | { 399 | destination.AppendHtml(entry); 400 | } 401 | } 402 | 403 | public void MoveTo(IHtmlContentBuilder destination) 404 | { 405 | CopyTo(destination); 406 | Clear(); 407 | } 408 | 409 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 410 | { 411 | foreach (var entry in Entries) 412 | { 413 | entry.WriteTo(writer, encoder); 414 | } 415 | } 416 | } 417 | 418 | private class EncodedString : IHtmlContent 419 | { 420 | public EncodedString(string value) 421 | { 422 | Value = value; 423 | } 424 | 425 | public string Value { get; } 426 | 427 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 428 | { 429 | writer.Write(Value); 430 | } 431 | } 432 | 433 | private class UnencodedString : IHtmlContent 434 | { 435 | public UnencodedString(string value) 436 | { 437 | Value = value; 438 | } 439 | 440 | public string Value { get; } 441 | 442 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 443 | { 444 | encoder.Encode(writer, Value); 445 | } 446 | } 447 | 448 | private class OtherHtmlContent : IHtmlContent 449 | { 450 | public OtherHtmlContent(string value) 451 | { 452 | Value = value; 453 | } 454 | 455 | public string Value { get; } 456 | 457 | public void WriteTo(TextWriter writer, HtmlEncoder encoder) 458 | { 459 | throw new NotImplementedException(); 460 | } 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.WebEncoders/baseline.netcore.json: -------------------------------------------------------------------------------- 1 | { 2 | "AssemblyIdentity": "Microsoft.Extensions.WebEncoders, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", 3 | "Types": [ 4 | { 5 | "Name": "Microsoft.Extensions.WebEncoders.WebEncoderOptions", 6 | "Visibility": "Public", 7 | "Kind": "Class", 8 | "Sealed": true, 9 | "ImplementedInterfaces": [], 10 | "Members": [ 11 | { 12 | "Kind": "Method", 13 | "Name": "get_TextEncoderSettings", 14 | "Parameters": [], 15 | "ReturnType": "System.Text.Encodings.Web.TextEncoderSettings", 16 | "Visibility": "Public", 17 | "GenericParameter": [] 18 | }, 19 | { 20 | "Kind": "Method", 21 | "Name": "set_TextEncoderSettings", 22 | "Parameters": [ 23 | { 24 | "Name": "value", 25 | "Type": "System.Text.Encodings.Web.TextEncoderSettings" 26 | } 27 | ], 28 | "ReturnType": "System.Void", 29 | "Visibility": "Public", 30 | "GenericParameter": [] 31 | }, 32 | { 33 | "Kind": "Constructor", 34 | "Name": ".ctor", 35 | "Parameters": [], 36 | "Visibility": "Public", 37 | "GenericParameter": [] 38 | } 39 | ], 40 | "GenericParameters": [] 41 | }, 42 | { 43 | "Name": "Microsoft.Extensions.WebEncoders.Testing.HtmlTestEncoder", 44 | "Visibility": "Public", 45 | "Kind": "Class", 46 | "Sealed": true, 47 | "BaseType": "System.Text.Encodings.Web.HtmlEncoder", 48 | "ImplementedInterfaces": [], 49 | "Members": [ 50 | { 51 | "Kind": "Method", 52 | "Name": "get_MaxOutputCharactersPerInputCharacter", 53 | "Parameters": [], 54 | "ReturnType": "System.Int32", 55 | "Virtual": true, 56 | "Override": true, 57 | "Visibility": "Public", 58 | "GenericParameter": [] 59 | }, 60 | { 61 | "Kind": "Method", 62 | "Name": "Encode", 63 | "Parameters": [ 64 | { 65 | "Name": "value", 66 | "Type": "System.String" 67 | } 68 | ], 69 | "ReturnType": "System.String", 70 | "Virtual": true, 71 | "Override": true, 72 | "Visibility": "Public", 73 | "GenericParameter": [] 74 | }, 75 | { 76 | "Kind": "Method", 77 | "Name": "Encode", 78 | "Parameters": [ 79 | { 80 | "Name": "output", 81 | "Type": "System.IO.TextWriter" 82 | }, 83 | { 84 | "Name": "value", 85 | "Type": "System.Char[]" 86 | }, 87 | { 88 | "Name": "startIndex", 89 | "Type": "System.Int32" 90 | }, 91 | { 92 | "Name": "characterCount", 93 | "Type": "System.Int32" 94 | } 95 | ], 96 | "ReturnType": "System.Void", 97 | "Virtual": true, 98 | "Override": true, 99 | "Visibility": "Public", 100 | "GenericParameter": [] 101 | }, 102 | { 103 | "Kind": "Method", 104 | "Name": "Encode", 105 | "Parameters": [ 106 | { 107 | "Name": "output", 108 | "Type": "System.IO.TextWriter" 109 | }, 110 | { 111 | "Name": "value", 112 | "Type": "System.String" 113 | }, 114 | { 115 | "Name": "startIndex", 116 | "Type": "System.Int32" 117 | }, 118 | { 119 | "Name": "characterCount", 120 | "Type": "System.Int32" 121 | } 122 | ], 123 | "ReturnType": "System.Void", 124 | "Virtual": true, 125 | "Override": true, 126 | "Visibility": "Public", 127 | "GenericParameter": [] 128 | }, 129 | { 130 | "Kind": "Method", 131 | "Name": "WillEncode", 132 | "Parameters": [ 133 | { 134 | "Name": "unicodeScalar", 135 | "Type": "System.Int32" 136 | } 137 | ], 138 | "ReturnType": "System.Boolean", 139 | "Virtual": true, 140 | "Override": true, 141 | "Visibility": "Public", 142 | "GenericParameter": [] 143 | }, 144 | { 145 | "Kind": "Method", 146 | "Name": "FindFirstCharacterToEncode", 147 | "Parameters": [ 148 | { 149 | "Name": "text", 150 | "Type": "System.Char*" 151 | }, 152 | { 153 | "Name": "textLength", 154 | "Type": "System.Int32" 155 | } 156 | ], 157 | "ReturnType": "System.Int32", 158 | "Virtual": true, 159 | "Override": true, 160 | "Visibility": "Public", 161 | "GenericParameter": [] 162 | }, 163 | { 164 | "Kind": "Method", 165 | "Name": "TryEncodeUnicodeScalar", 166 | "Parameters": [ 167 | { 168 | "Name": "unicodeScalar", 169 | "Type": "System.Int32" 170 | }, 171 | { 172 | "Name": "buffer", 173 | "Type": "System.Char*" 174 | }, 175 | { 176 | "Name": "bufferLength", 177 | "Type": "System.Int32" 178 | }, 179 | { 180 | "Name": "numberOfCharactersWritten", 181 | "Type": "System.Int32", 182 | "Direction": "Out" 183 | } 184 | ], 185 | "ReturnType": "System.Boolean", 186 | "Virtual": true, 187 | "Override": true, 188 | "Visibility": "Public", 189 | "GenericParameter": [] 190 | }, 191 | { 192 | "Kind": "Constructor", 193 | "Name": ".ctor", 194 | "Parameters": [], 195 | "Visibility": "Public", 196 | "GenericParameter": [] 197 | } 198 | ], 199 | "GenericParameters": [] 200 | }, 201 | { 202 | "Name": "Microsoft.Extensions.WebEncoders.Testing.JavaScriptTestEncoder", 203 | "Visibility": "Public", 204 | "Kind": "Class", 205 | "BaseType": "System.Text.Encodings.Web.JavaScriptEncoder", 206 | "ImplementedInterfaces": [], 207 | "Members": [ 208 | { 209 | "Kind": "Method", 210 | "Name": "get_MaxOutputCharactersPerInputCharacter", 211 | "Parameters": [], 212 | "ReturnType": "System.Int32", 213 | "Virtual": true, 214 | "Override": true, 215 | "Visibility": "Public", 216 | "GenericParameter": [] 217 | }, 218 | { 219 | "Kind": "Method", 220 | "Name": "Encode", 221 | "Parameters": [ 222 | { 223 | "Name": "value", 224 | "Type": "System.String" 225 | } 226 | ], 227 | "ReturnType": "System.String", 228 | "Virtual": true, 229 | "Override": true, 230 | "Visibility": "Public", 231 | "GenericParameter": [] 232 | }, 233 | { 234 | "Kind": "Method", 235 | "Name": "Encode", 236 | "Parameters": [ 237 | { 238 | "Name": "output", 239 | "Type": "System.IO.TextWriter" 240 | }, 241 | { 242 | "Name": "value", 243 | "Type": "System.Char[]" 244 | }, 245 | { 246 | "Name": "startIndex", 247 | "Type": "System.Int32" 248 | }, 249 | { 250 | "Name": "characterCount", 251 | "Type": "System.Int32" 252 | } 253 | ], 254 | "ReturnType": "System.Void", 255 | "Virtual": true, 256 | "Override": true, 257 | "Visibility": "Public", 258 | "GenericParameter": [] 259 | }, 260 | { 261 | "Kind": "Method", 262 | "Name": "Encode", 263 | "Parameters": [ 264 | { 265 | "Name": "output", 266 | "Type": "System.IO.TextWriter" 267 | }, 268 | { 269 | "Name": "value", 270 | "Type": "System.String" 271 | }, 272 | { 273 | "Name": "startIndex", 274 | "Type": "System.Int32" 275 | }, 276 | { 277 | "Name": "characterCount", 278 | "Type": "System.Int32" 279 | } 280 | ], 281 | "ReturnType": "System.Void", 282 | "Virtual": true, 283 | "Override": true, 284 | "Visibility": "Public", 285 | "GenericParameter": [] 286 | }, 287 | { 288 | "Kind": "Method", 289 | "Name": "WillEncode", 290 | "Parameters": [ 291 | { 292 | "Name": "unicodeScalar", 293 | "Type": "System.Int32" 294 | } 295 | ], 296 | "ReturnType": "System.Boolean", 297 | "Virtual": true, 298 | "Override": true, 299 | "Visibility": "Public", 300 | "GenericParameter": [] 301 | }, 302 | { 303 | "Kind": "Method", 304 | "Name": "FindFirstCharacterToEncode", 305 | "Parameters": [ 306 | { 307 | "Name": "text", 308 | "Type": "System.Char*" 309 | }, 310 | { 311 | "Name": "textLength", 312 | "Type": "System.Int32" 313 | } 314 | ], 315 | "ReturnType": "System.Int32", 316 | "Virtual": true, 317 | "Override": true, 318 | "Visibility": "Public", 319 | "GenericParameter": [] 320 | }, 321 | { 322 | "Kind": "Method", 323 | "Name": "TryEncodeUnicodeScalar", 324 | "Parameters": [ 325 | { 326 | "Name": "unicodeScalar", 327 | "Type": "System.Int32" 328 | }, 329 | { 330 | "Name": "buffer", 331 | "Type": "System.Char*" 332 | }, 333 | { 334 | "Name": "bufferLength", 335 | "Type": "System.Int32" 336 | }, 337 | { 338 | "Name": "numberOfCharactersWritten", 339 | "Type": "System.Int32", 340 | "Direction": "Out" 341 | } 342 | ], 343 | "ReturnType": "System.Boolean", 344 | "Virtual": true, 345 | "Override": true, 346 | "Visibility": "Public", 347 | "GenericParameter": [] 348 | }, 349 | { 350 | "Kind": "Constructor", 351 | "Name": ".ctor", 352 | "Parameters": [], 353 | "Visibility": "Public", 354 | "GenericParameter": [] 355 | } 356 | ], 357 | "GenericParameters": [] 358 | }, 359 | { 360 | "Name": "Microsoft.Extensions.WebEncoders.Testing.UrlTestEncoder", 361 | "Visibility": "Public", 362 | "Kind": "Class", 363 | "BaseType": "System.Text.Encodings.Web.UrlEncoder", 364 | "ImplementedInterfaces": [], 365 | "Members": [ 366 | { 367 | "Kind": "Method", 368 | "Name": "get_MaxOutputCharactersPerInputCharacter", 369 | "Parameters": [], 370 | "ReturnType": "System.Int32", 371 | "Virtual": true, 372 | "Override": true, 373 | "Visibility": "Public", 374 | "GenericParameter": [] 375 | }, 376 | { 377 | "Kind": "Method", 378 | "Name": "Encode", 379 | "Parameters": [ 380 | { 381 | "Name": "value", 382 | "Type": "System.String" 383 | } 384 | ], 385 | "ReturnType": "System.String", 386 | "Virtual": true, 387 | "Override": true, 388 | "Visibility": "Public", 389 | "GenericParameter": [] 390 | }, 391 | { 392 | "Kind": "Method", 393 | "Name": "Encode", 394 | "Parameters": [ 395 | { 396 | "Name": "output", 397 | "Type": "System.IO.TextWriter" 398 | }, 399 | { 400 | "Name": "value", 401 | "Type": "System.Char[]" 402 | }, 403 | { 404 | "Name": "startIndex", 405 | "Type": "System.Int32" 406 | }, 407 | { 408 | "Name": "characterCount", 409 | "Type": "System.Int32" 410 | } 411 | ], 412 | "ReturnType": "System.Void", 413 | "Virtual": true, 414 | "Override": true, 415 | "Visibility": "Public", 416 | "GenericParameter": [] 417 | }, 418 | { 419 | "Kind": "Method", 420 | "Name": "Encode", 421 | "Parameters": [ 422 | { 423 | "Name": "output", 424 | "Type": "System.IO.TextWriter" 425 | }, 426 | { 427 | "Name": "value", 428 | "Type": "System.String" 429 | }, 430 | { 431 | "Name": "startIndex", 432 | "Type": "System.Int32" 433 | }, 434 | { 435 | "Name": "characterCount", 436 | "Type": "System.Int32" 437 | } 438 | ], 439 | "ReturnType": "System.Void", 440 | "Virtual": true, 441 | "Override": true, 442 | "Visibility": "Public", 443 | "GenericParameter": [] 444 | }, 445 | { 446 | "Kind": "Method", 447 | "Name": "WillEncode", 448 | "Parameters": [ 449 | { 450 | "Name": "unicodeScalar", 451 | "Type": "System.Int32" 452 | } 453 | ], 454 | "ReturnType": "System.Boolean", 455 | "Virtual": true, 456 | "Override": true, 457 | "Visibility": "Public", 458 | "GenericParameter": [] 459 | }, 460 | { 461 | "Kind": "Method", 462 | "Name": "FindFirstCharacterToEncode", 463 | "Parameters": [ 464 | { 465 | "Name": "text", 466 | "Type": "System.Char*" 467 | }, 468 | { 469 | "Name": "textLength", 470 | "Type": "System.Int32" 471 | } 472 | ], 473 | "ReturnType": "System.Int32", 474 | "Virtual": true, 475 | "Override": true, 476 | "Visibility": "Public", 477 | "GenericParameter": [] 478 | }, 479 | { 480 | "Kind": "Method", 481 | "Name": "TryEncodeUnicodeScalar", 482 | "Parameters": [ 483 | { 484 | "Name": "unicodeScalar", 485 | "Type": "System.Int32" 486 | }, 487 | { 488 | "Name": "buffer", 489 | "Type": "System.Char*" 490 | }, 491 | { 492 | "Name": "bufferLength", 493 | "Type": "System.Int32" 494 | }, 495 | { 496 | "Name": "numberOfCharactersWritten", 497 | "Type": "System.Int32", 498 | "Direction": "Out" 499 | } 500 | ], 501 | "ReturnType": "System.Boolean", 502 | "Virtual": true, 503 | "Override": true, 504 | "Visibility": "Public", 505 | "GenericParameter": [] 506 | }, 507 | { 508 | "Kind": "Constructor", 509 | "Name": ".ctor", 510 | "Parameters": [], 511 | "Visibility": "Public", 512 | "GenericParameter": [] 513 | } 514 | ], 515 | "GenericParameters": [] 516 | }, 517 | { 518 | "Name": "Microsoft.Extensions.DependencyInjection.EncoderServiceCollectionExtensions", 519 | "Visibility": "Public", 520 | "Kind": "Class", 521 | "Abstract": true, 522 | "Static": true, 523 | "Sealed": true, 524 | "ImplementedInterfaces": [], 525 | "Members": [ 526 | { 527 | "Kind": "Method", 528 | "Name": "AddWebEncoders", 529 | "Parameters": [ 530 | { 531 | "Name": "services", 532 | "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" 533 | } 534 | ], 535 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", 536 | "Static": true, 537 | "Extension": true, 538 | "Visibility": "Public", 539 | "GenericParameter": [] 540 | }, 541 | { 542 | "Kind": "Method", 543 | "Name": "AddWebEncoders", 544 | "Parameters": [ 545 | { 546 | "Name": "services", 547 | "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" 548 | }, 549 | { 550 | "Name": "setupAction", 551 | "Type": "System.Action" 552 | } 553 | ], 554 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", 555 | "Static": true, 556 | "Extension": true, 557 | "Visibility": "Public", 558 | "GenericParameter": [] 559 | } 560 | ], 561 | "GenericParameters": [] 562 | } 563 | ] 564 | } -------------------------------------------------------------------------------- /src/Microsoft.AspNetCore.Html.Abstractions/baseline.netcore.json: -------------------------------------------------------------------------------- 1 | { 2 | "AssemblyIdentity": "Microsoft.AspNetCore.Html.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", 3 | "Types": [ 4 | { 5 | "Name": "Microsoft.AspNetCore.Html.HtmlContentBuilder", 6 | "Visibility": "Public", 7 | "Kind": "Class", 8 | "ImplementedInterfaces": [ 9 | "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 10 | ], 11 | "Members": [ 12 | { 13 | "Kind": "Method", 14 | "Name": "CopyTo", 15 | "Parameters": [ 16 | { 17 | "Name": "destination", 18 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 19 | } 20 | ], 21 | "ReturnType": "System.Void", 22 | "Sealed": true, 23 | "Virtual": true, 24 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentContainer", 25 | "Visibility": "Public", 26 | "GenericParameter": [] 27 | }, 28 | { 29 | "Kind": "Method", 30 | "Name": "MoveTo", 31 | "Parameters": [ 32 | { 33 | "Name": "destination", 34 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 35 | } 36 | ], 37 | "ReturnType": "System.Void", 38 | "Sealed": true, 39 | "Virtual": true, 40 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentContainer", 41 | "Visibility": "Public", 42 | "GenericParameter": [] 43 | }, 44 | { 45 | "Kind": "Method", 46 | "Name": "WriteTo", 47 | "Parameters": [ 48 | { 49 | "Name": "writer", 50 | "Type": "System.IO.TextWriter" 51 | }, 52 | { 53 | "Name": "encoder", 54 | "Type": "System.Text.Encodings.Web.HtmlEncoder" 55 | } 56 | ], 57 | "ReturnType": "System.Void", 58 | "Sealed": true, 59 | "Virtual": true, 60 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContent", 61 | "Visibility": "Public", 62 | "GenericParameter": [] 63 | }, 64 | { 65 | "Kind": "Method", 66 | "Name": "get_Count", 67 | "Parameters": [], 68 | "ReturnType": "System.Int32", 69 | "Visibility": "Public", 70 | "GenericParameter": [] 71 | }, 72 | { 73 | "Kind": "Method", 74 | "Name": "Append", 75 | "Parameters": [ 76 | { 77 | "Name": "unencoded", 78 | "Type": "System.String" 79 | } 80 | ], 81 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 82 | "Sealed": true, 83 | "Virtual": true, 84 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 85 | "Visibility": "Public", 86 | "GenericParameter": [] 87 | }, 88 | { 89 | "Kind": "Method", 90 | "Name": "AppendHtml", 91 | "Parameters": [ 92 | { 93 | "Name": "htmlContent", 94 | "Type": "Microsoft.AspNetCore.Html.IHtmlContent" 95 | } 96 | ], 97 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 98 | "Sealed": true, 99 | "Virtual": true, 100 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 101 | "Visibility": "Public", 102 | "GenericParameter": [] 103 | }, 104 | { 105 | "Kind": "Method", 106 | "Name": "AppendHtml", 107 | "Parameters": [ 108 | { 109 | "Name": "encoded", 110 | "Type": "System.String" 111 | } 112 | ], 113 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 114 | "Sealed": true, 115 | "Virtual": true, 116 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 117 | "Visibility": "Public", 118 | "GenericParameter": [] 119 | }, 120 | { 121 | "Kind": "Method", 122 | "Name": "Clear", 123 | "Parameters": [], 124 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 125 | "Sealed": true, 126 | "Virtual": true, 127 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 128 | "Visibility": "Public", 129 | "GenericParameter": [] 130 | }, 131 | { 132 | "Kind": "Constructor", 133 | "Name": ".ctor", 134 | "Parameters": [], 135 | "Visibility": "Public", 136 | "GenericParameter": [] 137 | }, 138 | { 139 | "Kind": "Constructor", 140 | "Name": ".ctor", 141 | "Parameters": [ 142 | { 143 | "Name": "capacity", 144 | "Type": "System.Int32" 145 | } 146 | ], 147 | "Visibility": "Public", 148 | "GenericParameter": [] 149 | }, 150 | { 151 | "Kind": "Constructor", 152 | "Name": ".ctor", 153 | "Parameters": [ 154 | { 155 | "Name": "entries", 156 | "Type": "System.Collections.Generic.IList" 157 | } 158 | ], 159 | "Visibility": "Public", 160 | "GenericParameter": [] 161 | } 162 | ], 163 | "GenericParameters": [] 164 | }, 165 | { 166 | "Name": "Microsoft.AspNetCore.Html.HtmlContentBuilderExtensions", 167 | "Visibility": "Public", 168 | "Kind": "Class", 169 | "Abstract": true, 170 | "Static": true, 171 | "Sealed": true, 172 | "ImplementedInterfaces": [], 173 | "Members": [ 174 | { 175 | "Kind": "Method", 176 | "Name": "AppendFormat", 177 | "Parameters": [ 178 | { 179 | "Name": "builder", 180 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 181 | }, 182 | { 183 | "Name": "format", 184 | "Type": "System.String" 185 | }, 186 | { 187 | "Name": "args", 188 | "Type": "System.Object[]", 189 | "IsParams": true 190 | } 191 | ], 192 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 193 | "Static": true, 194 | "Extension": true, 195 | "Visibility": "Public", 196 | "GenericParameter": [] 197 | }, 198 | { 199 | "Kind": "Method", 200 | "Name": "AppendFormat", 201 | "Parameters": [ 202 | { 203 | "Name": "builder", 204 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 205 | }, 206 | { 207 | "Name": "formatProvider", 208 | "Type": "System.IFormatProvider" 209 | }, 210 | { 211 | "Name": "format", 212 | "Type": "System.String" 213 | }, 214 | { 215 | "Name": "args", 216 | "Type": "System.Object[]", 217 | "IsParams": true 218 | } 219 | ], 220 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 221 | "Static": true, 222 | "Extension": true, 223 | "Visibility": "Public", 224 | "GenericParameter": [] 225 | }, 226 | { 227 | "Kind": "Method", 228 | "Name": "AppendLine", 229 | "Parameters": [ 230 | { 231 | "Name": "builder", 232 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 233 | } 234 | ], 235 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 236 | "Static": true, 237 | "Extension": true, 238 | "Visibility": "Public", 239 | "GenericParameter": [] 240 | }, 241 | { 242 | "Kind": "Method", 243 | "Name": "AppendLine", 244 | "Parameters": [ 245 | { 246 | "Name": "builder", 247 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 248 | }, 249 | { 250 | "Name": "unencoded", 251 | "Type": "System.String" 252 | } 253 | ], 254 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 255 | "Static": true, 256 | "Extension": true, 257 | "Visibility": "Public", 258 | "GenericParameter": [] 259 | }, 260 | { 261 | "Kind": "Method", 262 | "Name": "AppendLine", 263 | "Parameters": [ 264 | { 265 | "Name": "builder", 266 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 267 | }, 268 | { 269 | "Name": "content", 270 | "Type": "Microsoft.AspNetCore.Html.IHtmlContent" 271 | } 272 | ], 273 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 274 | "Static": true, 275 | "Extension": true, 276 | "Visibility": "Public", 277 | "GenericParameter": [] 278 | }, 279 | { 280 | "Kind": "Method", 281 | "Name": "AppendHtmlLine", 282 | "Parameters": [ 283 | { 284 | "Name": "builder", 285 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 286 | }, 287 | { 288 | "Name": "encoded", 289 | "Type": "System.String" 290 | } 291 | ], 292 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 293 | "Static": true, 294 | "Extension": true, 295 | "Visibility": "Public", 296 | "GenericParameter": [] 297 | }, 298 | { 299 | "Kind": "Method", 300 | "Name": "SetContent", 301 | "Parameters": [ 302 | { 303 | "Name": "builder", 304 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 305 | }, 306 | { 307 | "Name": "unencoded", 308 | "Type": "System.String" 309 | } 310 | ], 311 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 312 | "Static": true, 313 | "Extension": true, 314 | "Visibility": "Public", 315 | "GenericParameter": [] 316 | }, 317 | { 318 | "Kind": "Method", 319 | "Name": "SetHtmlContent", 320 | "Parameters": [ 321 | { 322 | "Name": "builder", 323 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 324 | }, 325 | { 326 | "Name": "content", 327 | "Type": "Microsoft.AspNetCore.Html.IHtmlContent" 328 | } 329 | ], 330 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 331 | "Static": true, 332 | "Extension": true, 333 | "Visibility": "Public", 334 | "GenericParameter": [] 335 | }, 336 | { 337 | "Kind": "Method", 338 | "Name": "SetHtmlContent", 339 | "Parameters": [ 340 | { 341 | "Name": "builder", 342 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 343 | }, 344 | { 345 | "Name": "encoded", 346 | "Type": "System.String" 347 | } 348 | ], 349 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 350 | "Static": true, 351 | "Extension": true, 352 | "Visibility": "Public", 353 | "GenericParameter": [] 354 | } 355 | ], 356 | "GenericParameters": [] 357 | }, 358 | { 359 | "Name": "Microsoft.AspNetCore.Html.HtmlFormattableString", 360 | "Visibility": "Public", 361 | "Kind": "Class", 362 | "ImplementedInterfaces": [ 363 | "Microsoft.AspNetCore.Html.IHtmlContent" 364 | ], 365 | "Members": [ 366 | { 367 | "Kind": "Method", 368 | "Name": "WriteTo", 369 | "Parameters": [ 370 | { 371 | "Name": "writer", 372 | "Type": "System.IO.TextWriter" 373 | }, 374 | { 375 | "Name": "encoder", 376 | "Type": "System.Text.Encodings.Web.HtmlEncoder" 377 | } 378 | ], 379 | "ReturnType": "System.Void", 380 | "Sealed": true, 381 | "Virtual": true, 382 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContent", 383 | "Visibility": "Public", 384 | "GenericParameter": [] 385 | }, 386 | { 387 | "Kind": "Constructor", 388 | "Name": ".ctor", 389 | "Parameters": [ 390 | { 391 | "Name": "format", 392 | "Type": "System.String" 393 | }, 394 | { 395 | "Name": "args", 396 | "Type": "System.Object[]", 397 | "IsParams": true 398 | } 399 | ], 400 | "Visibility": "Public", 401 | "GenericParameter": [] 402 | }, 403 | { 404 | "Kind": "Constructor", 405 | "Name": ".ctor", 406 | "Parameters": [ 407 | { 408 | "Name": "formatProvider", 409 | "Type": "System.IFormatProvider" 410 | }, 411 | { 412 | "Name": "format", 413 | "Type": "System.String" 414 | }, 415 | { 416 | "Name": "args", 417 | "Type": "System.Object[]", 418 | "IsParams": true 419 | } 420 | ], 421 | "Visibility": "Public", 422 | "GenericParameter": [] 423 | } 424 | ], 425 | "GenericParameters": [] 426 | }, 427 | { 428 | "Name": "Microsoft.AspNetCore.Html.HtmlString", 429 | "Visibility": "Public", 430 | "Kind": "Class", 431 | "ImplementedInterfaces": [ 432 | "Microsoft.AspNetCore.Html.IHtmlContent" 433 | ], 434 | "Members": [ 435 | { 436 | "Kind": "Method", 437 | "Name": "get_Value", 438 | "Parameters": [], 439 | "ReturnType": "System.String", 440 | "Visibility": "Public", 441 | "GenericParameter": [] 442 | }, 443 | { 444 | "Kind": "Method", 445 | "Name": "WriteTo", 446 | "Parameters": [ 447 | { 448 | "Name": "writer", 449 | "Type": "System.IO.TextWriter" 450 | }, 451 | { 452 | "Name": "encoder", 453 | "Type": "System.Text.Encodings.Web.HtmlEncoder" 454 | } 455 | ], 456 | "ReturnType": "System.Void", 457 | "Sealed": true, 458 | "Virtual": true, 459 | "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContent", 460 | "Visibility": "Public", 461 | "GenericParameter": [] 462 | }, 463 | { 464 | "Kind": "Method", 465 | "Name": "ToString", 466 | "Parameters": [], 467 | "ReturnType": "System.String", 468 | "Virtual": true, 469 | "Override": true, 470 | "Visibility": "Public", 471 | "GenericParameter": [] 472 | }, 473 | { 474 | "Kind": "Constructor", 475 | "Name": ".ctor", 476 | "Parameters": [ 477 | { 478 | "Name": "value", 479 | "Type": "System.String" 480 | } 481 | ], 482 | "Visibility": "Public", 483 | "GenericParameter": [] 484 | }, 485 | { 486 | "Kind": "Field", 487 | "Name": "NewLine", 488 | "Parameters": [], 489 | "ReturnType": "Microsoft.AspNetCore.Html.HtmlString", 490 | "Static": true, 491 | "ReadOnly": true, 492 | "Visibility": "Public", 493 | "GenericParameter": [] 494 | }, 495 | { 496 | "Kind": "Field", 497 | "Name": "Empty", 498 | "Parameters": [], 499 | "ReturnType": "Microsoft.AspNetCore.Html.HtmlString", 500 | "Static": true, 501 | "ReadOnly": true, 502 | "Visibility": "Public", 503 | "GenericParameter": [] 504 | } 505 | ], 506 | "GenericParameters": [] 507 | }, 508 | { 509 | "Name": "Microsoft.AspNetCore.Html.IHtmlContent", 510 | "Visibility": "Public", 511 | "Kind": "Interface", 512 | "Abstract": true, 513 | "ImplementedInterfaces": [], 514 | "Members": [ 515 | { 516 | "Kind": "Method", 517 | "Name": "WriteTo", 518 | "Parameters": [ 519 | { 520 | "Name": "writer", 521 | "Type": "System.IO.TextWriter" 522 | }, 523 | { 524 | "Name": "encoder", 525 | "Type": "System.Text.Encodings.Web.HtmlEncoder" 526 | } 527 | ], 528 | "ReturnType": "System.Void", 529 | "GenericParameter": [] 530 | } 531 | ], 532 | "GenericParameters": [] 533 | }, 534 | { 535 | "Name": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 536 | "Visibility": "Public", 537 | "Kind": "Interface", 538 | "Abstract": true, 539 | "ImplementedInterfaces": [ 540 | "Microsoft.AspNetCore.Html.IHtmlContentContainer" 541 | ], 542 | "Members": [ 543 | { 544 | "Kind": "Method", 545 | "Name": "AppendHtml", 546 | "Parameters": [ 547 | { 548 | "Name": "content", 549 | "Type": "Microsoft.AspNetCore.Html.IHtmlContent" 550 | } 551 | ], 552 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 553 | "GenericParameter": [] 554 | }, 555 | { 556 | "Kind": "Method", 557 | "Name": "Append", 558 | "Parameters": [ 559 | { 560 | "Name": "unencoded", 561 | "Type": "System.String" 562 | } 563 | ], 564 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 565 | "GenericParameter": [] 566 | }, 567 | { 568 | "Kind": "Method", 569 | "Name": "AppendHtml", 570 | "Parameters": [ 571 | { 572 | "Name": "encoded", 573 | "Type": "System.String" 574 | } 575 | ], 576 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 577 | "GenericParameter": [] 578 | }, 579 | { 580 | "Kind": "Method", 581 | "Name": "Clear", 582 | "Parameters": [], 583 | "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", 584 | "GenericParameter": [] 585 | } 586 | ], 587 | "GenericParameters": [] 588 | }, 589 | { 590 | "Name": "Microsoft.AspNetCore.Html.IHtmlContentContainer", 591 | "Visibility": "Public", 592 | "Kind": "Interface", 593 | "Abstract": true, 594 | "ImplementedInterfaces": [ 595 | "Microsoft.AspNetCore.Html.IHtmlContent" 596 | ], 597 | "Members": [ 598 | { 599 | "Kind": "Method", 600 | "Name": "CopyTo", 601 | "Parameters": [ 602 | { 603 | "Name": "builder", 604 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 605 | } 606 | ], 607 | "ReturnType": "System.Void", 608 | "GenericParameter": [] 609 | }, 610 | { 611 | "Kind": "Method", 612 | "Name": "MoveTo", 613 | "Parameters": [ 614 | { 615 | "Name": "builder", 616 | "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" 617 | } 618 | ], 619 | "ReturnType": "System.Void", 620 | "GenericParameter": [] 621 | } 622 | ], 623 | "GenericParameters": [] 624 | } 625 | ] 626 | } --------------------------------------------------------------------------------