From d4f5e48c46cc8263b46a0cd429c7ae7412bbb849 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Tue, 26 Nov 2019 08:50:28 -0800 Subject: [PATCH] adding default Json formatters for requests --- .../WebScriptHostBuilderExtension.cs | 25 ++++++- .../CSharp/HttpTrigger-Model-v2/function.json | 16 +++++ .../CSharp/HttpTrigger-Model-v2/run.csx | 17 +++++ .../CSharp/HttpTrigger-Model/function.json | 16 +++++ .../CSharp/HttpTrigger-Model/run.csx | 17 +++++ .../WebHostEndToEnd/CSharpEndToEndTests.cs | 67 +++++++++++++++++++ 6 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/function.json create mode 100644 test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/run.csx create mode 100644 test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/function.json create mode 100644 test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/run.csx diff --git a/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs b/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs index 30f2b0d3a1..5eecdacff7 100644 --- a/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs +++ b/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Azure.WebJobs.Host.Config; using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Host.Loggers; @@ -30,7 +31,7 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP IDependencyValidator validator = rootServiceProvider.GetService(); IMetricsLogger metricsLogger = rootServiceProvider.GetService(); - builder.UseServiceProviderFactory(new JobHostScopedServiceProviderFactory(rootServiceProvider, rootScopeFactory, validator)) + _ = builder.UseServiceProviderFactory(new JobHostScopedServiceProviderFactory(rootServiceProvider, rootScopeFactory, validator)) .ConfigureServices(services => { // register default configuration @@ -75,6 +76,11 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP services.AddSingleton(); } + if (!environment.IsV2CompatibilityMode()) + { + new FunctionsMvcBuilder(services).AddNewtonsoftJson(); + } + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -126,5 +132,22 @@ private static void ConfigureRegisteredBuilders(TBuilder builder, ISer configureBuilder.Configure(builder); } } + + /// + /// Used internally to register Newtonsoft formatters with our ScriptHost. + /// + private class FunctionsMvcBuilder : IMvcBuilder + { + private readonly IServiceCollection _serviceCollection; + + public FunctionsMvcBuilder(IServiceCollection serviceCollection) + { + _serviceCollection = serviceCollection; + } + + public ApplicationPartManager PartManager { get; } = new ApplicationPartManager(); + + public IServiceCollection Services => _serviceCollection; + } } } diff --git a/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/function.json b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/function.json new file mode 100644 index 0000000000..147f9096bc --- /dev/null +++ b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/function.json @@ -0,0 +1,16 @@ +{ + "bindings": [ + { + "type": "httpTrigger", + "name": "payload", + "direction": "in", + "methods": [ "post" ], + "authLevel": "anonymous" + }, + { + "type": "http", + "name": "$return", + "direction": "out" + } + ] +} diff --git a/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/run.csx b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/run.csx new file mode 100644 index 0000000000..282f111d02 --- /dev/null +++ b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model-v2/run.csx @@ -0,0 +1,17 @@ + +public static TestPayload Run(TestPayload payload) +{ + return payload; +} + +public class TestPayload +{ + public CustomType Custom { get; set; } + + public IEnumerable CustomEnumerable { get; set; } +} + +public class CustomType +{ + public string CustomProperty { get; set; } +} \ No newline at end of file diff --git a/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/function.json b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/function.json new file mode 100644 index 0000000000..147f9096bc --- /dev/null +++ b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/function.json @@ -0,0 +1,16 @@ +{ + "bindings": [ + { + "type": "httpTrigger", + "name": "payload", + "direction": "in", + "methods": [ "post" ], + "authLevel": "anonymous" + }, + { + "type": "http", + "name": "$return", + "direction": "out" + } + ] +} diff --git a/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/run.csx b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/run.csx new file mode 100644 index 0000000000..282f111d02 --- /dev/null +++ b/test/WebJobs.Script.Tests.Integration/TestScripts/CSharp/HttpTrigger-Model/run.csx @@ -0,0 +1,17 @@ + +public static TestPayload Run(TestPayload payload) +{ + return payload; +} + +public class TestPayload +{ + public CustomType Custom { get; set; } + + public IEnumerable CustomEnumerable { get; set; } +} + +public class CustomType +{ + public string CustomProperty { get; set; } +} \ No newline at end of file diff --git a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/CSharpEndToEndTests.cs b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/CSharpEndToEndTests.cs index ac535d71dd..d23f450aaa 100644 --- a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/CSharpEndToEndTests.cs +++ b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/CSharpEndToEndTests.cs @@ -282,6 +282,72 @@ public async Task HttpTrigger_Post_Dynamic() Assert.Equal("Name: Mathew Charles, Location: Seattle", body); } + [Fact] + public async Task HttpTrigger_Model_Binding() + { + (JObject req, JObject res) = await MakeModelRequest(Fixture.Host.HttpClient); + Assert.True(JObject.DeepEquals(req, res), res.ToString()); + } + + [Fact] + public async Task HttpTrigger_Model_Binding_V2CompatMode() + { + // We need a custom host to set this to v2 compat mode. + using (var host = new TestFunctionHost(@"TestScripts\CSharp", Path.Combine(Path.GetTempPath(), "Functions"), + configureWebHostServices: webHostServices => + { + var environment = new TestEnvironment(); + environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionsV2CompatibilityModeKey, "true"); + webHostServices.AddSingleton(_ => environment); + }, + configureScriptHostWebJobsBuilder: webJobsBuilder => + { + webJobsBuilder.Services.Configure(o => + { + // Only load the functions we care about + o.Functions = new[] + { + "HttpTrigger-Model-v2", + }; + }); + })) + { + + (JObject req, JObject res) = await MakeModelRequest(host.HttpClient, "-v2"); + + // in v2, we expect the response to have a null customEnumerable property. + req["customEnumerable"] = null; + + Assert.True(JObject.DeepEquals(req, res), res.ToString()); + } + } + + private static async Task<(JObject requestContent, JObject responseContent)> MakeModelRequest(HttpClient httpClient, string suffix = null) + { + var payload = new + { + custom = new { customProperty = "value" }, + customEnumerable = new[] { new { customProperty = "value1" }, new { customProperty = "value2" } } + }; + + var jObject = JObject.FromObject(payload); + var json = jObject.ToString(); + + HttpRequestMessage request = new HttpRequestMessage + { + RequestUri = new Uri($"http://localhost/api/httptrigger-model{suffix}"), + Method = HttpMethod.Post, + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + + request.Content.Headers.ContentLength = json.Length; + + HttpResponseMessage response = await httpClient.SendAsync(request); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + return (jObject, JObject.Parse(await response.Content.ReadAsStringAsync())); + } + [Fact] public async Task HttpTriggerToBlob() { @@ -448,6 +514,7 @@ public override void ConfigureScriptHost(IWebJobsBuilder webJobsBuilder) "AssembliesFromSharedLocation", "HttpTrigger-Dynamic", "HttpTrigger-Scenarios", + "HttpTrigger-Model", "HttpTrigger-Redirect", "HttpTriggerToBlob", "FunctionExecutionContext",