From e70667ab6362e1f7ffaa44084d581db524660427 Mon Sep 17 00:00:00 2001 From: SondreJDigdir Date: Tue, 17 Dec 2024 09:13:54 +0100 Subject: [PATCH] feat: upgrade to net8 (#5) --- .gitignore | 1 + .../Config/PluginConstants.cs | 18 ++++++ .../Config/Settings.cs | 10 ++++ .../Dan.Plugin.DATASOURCENAME.csproj | 11 ++-- src/Dan.Plugin.DATASOURCENAME/Metadata.cs | 55 ++++++++++--------- .../Models/ExampleModel.cs | 2 + src/Dan.Plugin.DATASOURCENAME/Plugin.cs | 45 +++++---------- src/Dan.Plugin.DATASOURCENAME/Program.cs | 4 +- src/Dan.Plugin.DATASOURCENAME/Settings.cs | 10 ---- .../local.settings.json.template | 9 +-- .../Dan.Plugin.DATASOURCENAME.Test.csproj | 10 ++-- 11 files changed, 91 insertions(+), 84 deletions(-) create mode 100644 src/Dan.Plugin.DATASOURCENAME/Config/PluginConstants.cs create mode 100644 src/Dan.Plugin.DATASOURCENAME/Config/Settings.cs delete mode 100644 src/Dan.Plugin.DATASOURCENAME/Settings.cs diff --git a/.gitignore b/.gitignore index bc5d5ba..3302a48 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ local.settings.json *.user *.userosscache *.sln.docstates +.idea # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/src/Dan.Plugin.DATASOURCENAME/Config/PluginConstants.cs b/src/Dan.Plugin.DATASOURCENAME/Config/PluginConstants.cs new file mode 100644 index 0000000..c376017 --- /dev/null +++ b/src/Dan.Plugin.DATASOURCENAME/Config/PluginConstants.cs @@ -0,0 +1,18 @@ +namespace Dan.Plugin.DATASOURCENAME.Config; + +public static class PluginConstants +{ + // These are not mandatory, but there should be a distinct error code (any integer) for all types of errors that can occur. The error codes does not have to be globally + // unique. These should be used within either transient or permanent exceptions, see Plugin.cs for examples. + public const int ErrorUpstreamUnavailble = 1001; + public const int ErrorInvalidInput = 1002; + public const int ErrorNotFound = 1003; + public const int ErrorUnableToParseResponse = 1004; + + // The datasets must supply a human-readable source description from which they originate. Individual fields might come from different sources, and this string should reflect that (ie. name all possible sources). + public const string SourceName = "Digitaliseringsdirektoratet"; + + // The function names (ie. HTTP endpoint names) and the dataset names must match. Using constants to avoid errors. + public const string SimpleDatasetName = "SimpleDataset"; + public const string RichDatasetName = "RichDataset"; +} diff --git a/src/Dan.Plugin.DATASOURCENAME/Config/Settings.cs b/src/Dan.Plugin.DATASOURCENAME/Config/Settings.cs new file mode 100644 index 0000000..2be0386 --- /dev/null +++ b/src/Dan.Plugin.DATASOURCENAME/Config/Settings.cs @@ -0,0 +1,10 @@ +namespace Dan.Plugin.DATASOURCENAME.Config; + +public class Settings +{ + public int DefaultCircuitBreakerOpenCircuitTimeSeconds { get; init; } + public int DefaultCircuitBreakerFailureBeforeTripping { get; init; } + public int SafeHttpClientTimeout { get; init; } + + public string EndpointUrl { get; init; } +} diff --git a/src/Dan.Plugin.DATASOURCENAME/Dan.Plugin.DATASOURCENAME.csproj b/src/Dan.Plugin.DATASOURCENAME/Dan.Plugin.DATASOURCENAME.csproj index f99047f..900238a 100644 --- a/src/Dan.Plugin.DATASOURCENAME/Dan.Plugin.DATASOURCENAME.csproj +++ b/src/Dan.Plugin.DATASOURCENAME/Dan.Plugin.DATASOURCENAME.csproj @@ -1,14 +1,15 @@ - net6.0 + net8.0 v4 Exe - - - - + + + + + diff --git a/src/Dan.Plugin.DATASOURCENAME/Metadata.cs b/src/Dan.Plugin.DATASOURCENAME/Metadata.cs index 2d29a1e..1afdc9a 100644 --- a/src/Dan.Plugin.DATASOURCENAME/Metadata.cs +++ b/src/Dan.Plugin.DATASOURCENAME/Metadata.cs @@ -5,10 +5,12 @@ using Dan.Common.Enums; using Dan.Common.Interfaces; using Dan.Common.Models; +using Dan.Plugin.DATASOURCENAME.Config; using Dan.Plugin.DATASOURCENAME.Models; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Newtonsoft.Json.Schema.Generation; +using NJsonSchema; namespace Dan.Plugin.DATASOURCENAME; @@ -18,56 +20,57 @@ namespace Dan.Plugin.DATASOURCENAME; public class Metadata : IEvidenceSourceMetadata { /// - /// + /// /// /// public List GetEvidenceCodes() { - JSchemaGenerator generator = new JSchemaGenerator(); - - return new List() - { - new() + return + [ + new EvidenceCode { - EvidenceCodeName = global::Dan.Plugin.DATASOURCENAME.Plugin.SimpleDatasetName, - EvidenceSource = global::Dan.Plugin.DATASOURCENAME.Plugin.SourceName, - Values = new List() - { - new() + EvidenceCodeName = PluginConstants.SimpleDatasetName, + EvidenceSource = PluginConstants.SourceName, + Values = + [ + new EvidenceValue { EvidenceValueName = "field1", ValueType = EvidenceValueType.String }, - new() + + new EvidenceValue { EvidenceValueName = "field2", ValueType = EvidenceValueType.String } - } + ] }, - new() + new EvidenceCode { - EvidenceCodeName = global::Dan.Plugin.DATASOURCENAME.Plugin.RichDatasetName, - EvidenceSource = global::Dan.Plugin.DATASOURCENAME.Plugin.SourceName, - Values = new List() - { - new() + EvidenceCodeName = PluginConstants.RichDatasetName, + EvidenceSource = PluginConstants.SourceName, + Values = + [ + new EvidenceValue { // Convention for rich datasets with a single JSON model is to use the value name "default" EvidenceValueName = "default", ValueType = EvidenceValueType.JsonSchema, - JsonSchemaDefintion = generator.Generate(typeof(ExampleModel)).ToString() + JsonSchemaDefintion = JsonSchema + .FromType() + .ToJson(Newtonsoft.Json.Formatting.Indented) } - }, - AuthorizationRequirements = new List - { + ], + AuthorizationRequirements = + [ new MaskinportenScopeRequirement { - RequiredScopes = new List { "altinn:dataaltinnno/somescope" } + RequiredScopes = ["altinn:dataaltinnno/somescope"] } - } + ] } - }; + ]; } diff --git a/src/Dan.Plugin.DATASOURCENAME/Models/ExampleModel.cs b/src/Dan.Plugin.DATASOURCENAME/Models/ExampleModel.cs index e123b49..27e216b 100644 --- a/src/Dan.Plugin.DATASOURCENAME/Models/ExampleModel.cs +++ b/src/Dan.Plugin.DATASOURCENAME/Models/ExampleModel.cs @@ -1,7 +1,9 @@ +using System; using Newtonsoft.Json; namespace Dan.Plugin.DATASOURCENAME.Models; +[Serializable] public class ExampleModel { [JsonRequired] diff --git a/src/Dan.Plugin.DATASOURCENAME/Plugin.cs b/src/Dan.Plugin.DATASOURCENAME/Plugin.cs index 986d122..e7e5786 100644 --- a/src/Dan.Plugin.DATASOURCENAME/Plugin.cs +++ b/src/Dan.Plugin.DATASOURCENAME/Plugin.cs @@ -8,6 +8,7 @@ using Dan.Common.Interfaces; using Dan.Common.Models; using Dan.Common.Util; +using Dan.Plugin.DATASOURCENAME.Config; using Dan.Plugin.DATASOURCENAME.Models; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -24,20 +25,6 @@ public class Plugin private readonly HttpClient _client; private readonly Settings _settings; - // The datasets must supply a human-readable source description from which they originate. Individual fields might come from different sources, and this string should reflect that (ie. name all possible sources). - public const string SourceName = "Digitaliseringsdirektoratet"; - - // The function names (ie. HTTP endpoint names) and the dataset names must match. Using constants to avoid errors. - public const string SimpleDatasetName = "SimpleDataset"; - public const string RichDatasetName = "RichDataset"; - - // These are not mandatory, but there should be a distinct error code (any integer) for all types of errors that can occur. The error codes does not have to be globally - // unique. These should be used within either transient or permanent exceptions, see Plugin.cs for examples. - private const int ERROR_UPSTREAM_UNAVAILBLE = 1001; - private const int ERROR_INVALID_INPUT = 1002; - private const int ERROR_NOT_FOUND = 1003; - private const int ERROR_UNABLE_TO_PARSE_RESPONSE = 1004; - public Plugin( IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory, @@ -52,7 +39,7 @@ public Plugin( _logger.LogDebug("Initialized plugin! This should be visible in the console"); } - [Function(SimpleDatasetName)] + [Function(PluginConstants.SimpleDatasetName)] public async Task GetSimpleDatasetAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestData req, FunctionContext context) @@ -68,7 +55,7 @@ public async Task GetSimpleDatasetAsync( () => GetEvidenceValuesSimpledataset(evidenceHarvesterRequest)); } - [Function(RichDatasetName)] + [Function(PluginConstants.RichDatasetName)] public async Task GetRichDatasetAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestData req, FunctionContext context) @@ -84,9 +71,9 @@ private async Task> GetEvidenceValuesSimpledataset(EvidenceH var url = _settings.EndpointUrl + "?someparameter=" + evidenceHarvesterRequest.OrganizationNumber; var exampleModel = await MakeRequest(url); - var ecb = new EvidenceBuilder(_evidenceSourceMetadata, SimpleDatasetName); - ecb.AddEvidenceValue("field1", exampleModel.ResponseField1, SourceName); - ecb.AddEvidenceValue("field2", exampleModel.ResponseField2, SourceName); + var ecb = new EvidenceBuilder(_evidenceSourceMetadata, PluginConstants.SimpleDatasetName); + ecb.AddEvidenceValue("field1", exampleModel.ResponseField1, PluginConstants.SourceName); + ecb.AddEvidenceValue("field2", exampleModel.ResponseField2, PluginConstants.SourceName); return ecb.GetEvidenceValues(); } @@ -97,15 +84,9 @@ private async Task> GetEvidenceValuesRichDataset(EvidenceHar var url = _settings.EndpointUrl + "?someparameter=" + evidenceHarvesterRequest.OrganizationNumber; var exampleModel = await MakeRequest(url); - var ecb = new EvidenceBuilder(_evidenceSourceMetadata, RichDatasetName); + var ecb = new EvidenceBuilder(_evidenceSourceMetadata, PluginConstants.RichDatasetName); - // Here we reserialize the model. While it is possible to merely send the received JSON string directly through without parsing it, - // the extra step of deserializing it to a known model ensures that the JSON schema supplied in the metadata always matches the - // dataset model. - // - // Another way to do this is to not generate the schema from the model, but "hand code" the schema in the metadata and validate the - // received JSON against it, throwing eg. a EvidenceSourcePermanentServerException if it fails to match. - ecb.AddEvidenceValue("default", JsonConvert.SerializeObject(exampleModel), SourceName); + ecb.AddEvidenceValue("default", exampleModel, PluginConstants.SourceName); return ecb.GetEvidenceValues(); } @@ -120,16 +101,16 @@ private async Task MakeRequest(string target) } catch (HttpRequestException ex) { - throw new EvidenceSourceTransientException(ERROR_UPSTREAM_UNAVAILBLE, "Error communicating with upstream source", ex); + throw new EvidenceSourceTransientException(PluginConstants.ErrorUpstreamUnavailble, "Error communicating with upstream source", ex); } if (!result.IsSuccessStatusCode) { throw result.StatusCode switch { - HttpStatusCode.NotFound => new EvidenceSourcePermanentClientException(ERROR_NOT_FOUND, "Upstream source could not find the requested entity (404)"), - HttpStatusCode.BadRequest => new EvidenceSourcePermanentClientException(ERROR_INVALID_INPUT, "Upstream source indicated an invalid request (400)"), - _ => new EvidenceSourceTransientException(ERROR_UPSTREAM_UNAVAILBLE, $"Upstream source retuned an HTTP error code ({(int)result.StatusCode})") + HttpStatusCode.NotFound => new EvidenceSourcePermanentClientException(PluginConstants.ErrorNotFound, "Upstream source could not find the requested entity (404)"), + HttpStatusCode.BadRequest => new EvidenceSourcePermanentClientException(PluginConstants.ErrorInvalidInput, "Upstream source indicated an invalid request (400)"), + _ => new EvidenceSourceTransientException(PluginConstants.ErrorUpstreamUnavailble, $"Upstream source retuned an HTTP error code ({(int)result.StatusCode})") }; } @@ -140,7 +121,7 @@ private async Task MakeRequest(string target) catch (Exception ex) { _logger.LogError("Unable to parse data returned from upstream source: {exceptionType}: {exceptionMessage}", ex.GetType().Name, ex.Message); - throw new EvidenceSourcePermanentServerException(ERROR_UNABLE_TO_PARSE_RESPONSE, "Could not parse the data model returned from upstream source", ex); + throw new EvidenceSourcePermanentServerException(PluginConstants.ErrorUnableToParseResponse, "Could not parse the data model returned from upstream source", ex); } } } diff --git a/src/Dan.Plugin.DATASOURCENAME/Program.cs b/src/Dan.Plugin.DATASOURCENAME/Program.cs index 00e9243..f0098e9 100644 --- a/src/Dan.Plugin.DATASOURCENAME/Program.cs +++ b/src/Dan.Plugin.DATASOURCENAME/Program.cs @@ -1,11 +1,11 @@ -using Dan.Plugin.DATASOURCENAME; using Microsoft.Extensions.Hosting; using Dan.Common.Extensions; +using Dan.Plugin.DATASOURCENAME.Config; using Microsoft.Extensions.DependencyInjection; var host = new HostBuilder() .ConfigureDanPluginDefaults() - .ConfigureAppConfiguration((context, configuration) => + .ConfigureAppConfiguration((_, _) => { // Add more configuration sources if necessary. ConfigureDanPluginDefaults will load environment variables, which includes // local.settings.json (if developing locally) and applications settings for the Azure Function diff --git a/src/Dan.Plugin.DATASOURCENAME/Settings.cs b/src/Dan.Plugin.DATASOURCENAME/Settings.cs deleted file mode 100644 index 7f215be..0000000 --- a/src/Dan.Plugin.DATASOURCENAME/Settings.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Dan.Plugin.DATASOURCENAME; - -public class Settings -{ - public int DefaultCircuitBreakerOpenCircuitTimeSeconds { get; set; } - public int DefaultCircuitBreakerFailureBeforeTripping { get; set; } - public int SafeHttpClientTimeout { get; set; } - - public string EndpointUrl { get; set; } -} diff --git a/src/Dan.Plugin.DATASOURCENAME/local.settings.json.template b/src/Dan.Plugin.DATASOURCENAME/local.settings.json.template index 43d2a82..5c89e15 100644 --- a/src/Dan.Plugin.DATASOURCENAME/local.settings.json.template +++ b/src/Dan.Plugin.DATASOURCENAME/local.settings.json.template @@ -1,15 +1,16 @@ { "IsEncrypted": false, + "Host":{ + "LocalHttpPort": 7075, + "CORS": "*", + "CORSCredentials": false + }, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - - "DefaultCircuitBreakerOpenCircuitTimeSeconds": "10", "DefaultCircuitBreakerFailureBeforeTripping": "4", "SafeHttpClientTimeout": "30", - "EndpointUrl": "https://example.com" - } } diff --git a/test/Dan.Plugin.DATASOURCENAME.Test/Dan.Plugin.DATASOURCENAME.Test.csproj b/test/Dan.Plugin.DATASOURCENAME.Test/Dan.Plugin.DATASOURCENAME.Test.csproj index 2eb5620..c9b94a5 100644 --- a/test/Dan.Plugin.DATASOURCENAME.Test/Dan.Plugin.DATASOURCENAME.Test.csproj +++ b/test/Dan.Plugin.DATASOURCENAME.Test/Dan.Plugin.DATASOURCENAME.Test.csproj @@ -8,14 +8,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive