diff --git a/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestFormLayoutRendererTests.cs b/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestFormLayoutRendererTests.cs new file mode 100644 index 00000000..06bac6ff --- /dev/null +++ b/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestFormLayoutRendererTests.cs @@ -0,0 +1,147 @@ +using NLog.Web.Tests.LayoutRenderers; +using System; +using System.Collections.Generic; +using System.Text; +using NLog.Web.LayoutRenderers; +using NLog.Web.Tests; +using NSubstitute; +using System.Collections.Specialized; +#if ASP_NET_CORE +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +#else +using System.Web; +#endif +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestFormLayoutRendererTests : TestInvolvingAspNetHttpContext + { + [Fact] + public void ShouldReturnEmptyIfFormCollectionIsEmpty() + { + // Arrange + var expectedResult = ""; + var renderer = CreateRenderer(false); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ShouldReturnAllIfDefaultsAreUsed() + { + // Arrange + var expectedResult = "id=1,name=Test Person,token=86abe8fe-2237-4f87-81af-0a4e522b4140"; + var renderer = CreateRenderer(); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ShouldReturnOnlySpecifiedIfIncludeIsUsed() + { + // Arrange + var expectedResult = "id=1,name=Test Person"; + var renderer = CreateRenderer(); + renderer.Include.Add("id"); + renderer.Include.Add("name"); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ShouldNotReturnKeysSpecidiedInExclude() + { + // Arrange + var expectedResult = "id=1,name=Test Person"; + var renderer = CreateRenderer(); + renderer.Exclude.Add("token"); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ShouldUseTheSpecifiedSeparator() + { + // Arrange + var expectedResult = "id=1\r\nname=Test Person\r\ntoken=86abe8fe-2237-4f87-81af-0a4e522b4140"; + var renderer = CreateRenderer(); + renderer.ItemSeparator = "${newline}"; + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ExcludeShouldTakePrecedenceOverInclude() + { + // Arrange + var expectedResult = "name=Test Person"; + var renderer = CreateRenderer(); + renderer.Include.Add("id"); + renderer.Include.Add("name"); + renderer.Exclude.Add("id"); + + // Act + string result = renderer.Render(new LogEventInfo()); + + // Assert + Assert.Equal(expectedResult, result); + } + + private AspNetRequestFormLayoutRenderer CreateRenderer(bool hasFormValues = true) + { +#if ASP_NET_CORE + var httpContext = this.HttpContext; + httpContext.Request.ContentType = "application/x-www-form-urlencoded"; +#else + var httpContext = Substitute.For(); + httpContext.Request.ContentType.Returns("application/x-www-form-urlencoded"); +#endif + + if (hasFormValues) + { +#if ASP_NET_CORE + var formCollection = new FormCollection(new Dictionary{ + { "id","1" }, + { "name","Test Person" }, + { "token","86abe8fe-2237-4f87-81af-0a4e522b4140" } + }); + httpContext.Request.Form = formCollection; +#else + var formCollection = new NameValueCollection(){ + { "id","1" }, + { "name","Test Person" }, + { "token","86abe8fe-2237-4f87-81af-0a4e522b4140" } + }; + httpContext.Request.Form.Returns(formCollection); +#endif + } + + return new AspNetRequestFormLayoutRenderer + { + HttpContextAccessor = new FakeHttpContextAccessor(httpContext) + }; + } + } +} diff --git a/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestFormLayoutRenderer.cs b/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestFormLayoutRenderer.cs new file mode 100644 index 00000000..60f105f1 --- /dev/null +++ b/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestFormLayoutRenderer.cs @@ -0,0 +1,94 @@ +using NLog.LayoutRenderers; +using NLog.Web.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET Request Form Data + /// + /// + /// Example usage of ${aspnet-request-form}: + /// + /// ${aspnet-request-form} - Produces - All Form Data from the Request with each key/value pair separated by a comma. + /// ${aspnet-request-form:Include=id,name} - Produces - Only Form Data from the Request with keys "id" and "name". + /// ${aspnet-request-form:Exclude=id,name} - Produces - All Form Data from the Request except the keys "id" and "name". + /// ${aspnet-request-form:Include=id,name:Exclude=id} - Produces - Only Form Data from the Request with key "name" ( takes precedence over ). + /// ${aspnet-request-form:ItemSeparator=${newline}} - Produces - All Form Data from the Request with each key/value pair separated by a new line. + /// + /// + [LayoutRenderer("aspnet-request-form")] + public class AspNetRequestFormLayoutRenderer : AspNetLayoutMultiValueRendererBase + { + /// + /// Gets or sets the form keys to include in the output. If omitted, all are included. takes precedence over . + /// + /// +#if ASP_NET_CORE + public ISet Include { get; set; } +#else + public HashSet Include { get; set; } +#endif + + /// + /// Gets or sets the form keys to exclude from the output. If omitted, none are excluded. takes precedence over . + /// + /// +#if ASP_NET_CORE + public ISet Exclude { get; set; } +#else + public HashSet Exclude { get; set; } +#endif + + /// + /// Constructor + /// + public AspNetRequestFormLayoutRenderer() + { + Include = new HashSet(StringComparer.OrdinalIgnoreCase); + Exclude = new HashSet(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Renders the Form Collection from the HttpRequest and appends it to the specified . + /// + /// + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + if (HttpContextAccessor?.HttpContext?.TryGetRequest() == null) + { + return; + } + + var formDataToInclude = GetPairsToInclude(); + + if (formDataToInclude.Any()) + { + SerializePairs(formDataToInclude, builder, logEvent); + } + } + + private IEnumerable> GetPairsToInclude() + { + var httpRequest = HttpContextAccessor?.HttpContext?.TryGetRequest(); + var pairs = new List>(); + + if (httpRequest.Form != null) + { + foreach (string key in httpRequest.Form.Keys) + { + if ((!Include.Any() || Include.Contains(key)) && !Exclude.Contains(key)) + { + pairs.Add(new KeyValuePair(key, httpRequest.Form[key])); + } + } + } + + return pairs; + } + } +} diff --git a/NLog.Web.Tests/NLog.Web.Tests.csproj b/NLog.Web.Tests/NLog.Web.Tests.csproj index 502e6cd0..46b00463 100644 --- a/NLog.Web.Tests/NLog.Web.Tests.csproj +++ b/NLog.Web.Tests/NLog.Web.Tests.csproj @@ -92,6 +92,9 @@ + + AspNetRequestFormLayoutRendererTests.cs + diff --git a/NLog.Web/NLog.Web.csproj b/NLog.Web/NLog.Web.csproj index b99ee61d..3c177816 100644 --- a/NLog.Web/NLog.Web.csproj +++ b/NLog.Web/NLog.Web.csproj @@ -68,6 +68,9 @@ + + AspNetRequestFormLayoutRenderer.cs +