How to set my own KeyGenerator instance in appsettings.json? - json

I already asked that question here but I feel like Stackoverflow might be faster. This is how I'm trying to do it in my json configuration file:
{
"Serilog": {
"Using": [ "Serilog.Sinks.AzureTableStorage" ],
"WriteTo": [
{
"Name": "AzureTableStorage",
"Args": {
"storageTableName": "Logs",
"connectionString": "*************",
"keyGenerator": "MyApp.Serilog.AzureTableStorage.MyKeyGenerator"
}
}
],
"MinimumLevel": "Verbose"
}
}
This is my generator implementation:
public class MyKeyGenerator : IKeyGenerator
{
public string GeneratePartitionKey(LogEvent logEvent)
{
return Environment.MachineName;
}
public string GenerateRowKey(LogEvent logEvent, string suffix = null)
{
return SUID.nextId().ToString();
}
}
Obviously, the .ReadFrom.Configuration operation throws an InvalidCastException, since it tries to fit the string content into an IKeyGenerator parameter.
How should I set the keyGenerator parameter to ensure an instance of MyKeyGenerator class is created and given to that parameter?

I cloned serilog-settings-configuration and after digging into the code, I found how they expect the JSON setting value when the actual parameter is an interface (See StringArgumentValue.cs, line 57 to 74).
The correct way to reference the type you want to pass as parameter is to give the full class and assembly names separated by a comma. That class must have a public default constructor as well.
Ex:
{
"Serilog": {
"Using": [ "Serilog.Sinks.AzureTableStorage" ],
"WriteTo": [
{
"Name": "AzureTableStorage",
"Args": {
"storageTableName": "Logs",
"connectionString": "*************",
"keyGenerator": "MyApp.Serilog.AzureTableStorage.MyKeyGenerator, MyApp"
}
}
],
"MinimumLevel": "Verbose"
}
}
That way, the configurator can instanciate the class properly!

Related

Adding an item to a Dictionary using JsonPatch in ASP.NET Core

I have the following environment and version of .NET Core
.NET SDK (reflecting any global.json):
Version: 5.0.100
Commit: 5044b93829
Runtime Environment:
OS Name: Mac OS X
OS Version: 11.0
OS Platform: Darwin
RID: osx.11.0-x64
Base Path: /usr/local/share/dotnet/sdk/5.0.100/
Using the following code and endpoint.
public class Trie
{
public string Id { get; set; }
public Dictionary<string, TrieNode> Nodes { get; set; }
}
public class TrieNode
{
public string Name { get; set; }
}
public async Task<IActionResult> Patch(string id, [FromBody] JsonPatchDocument<Trie> patchDoc)
{
var trie = [get from database]
patchDoc.ApplyTo(trie, ModelState);
}
When I use the following op:
[
{
"op": "add",
"path": "/nodes/-", "value": { "/": { "name": "hello, world" } }
}
]
an item is added to the dictionary like this:
"Nodes": {
"-": {
"Name": null
}
}
If I use the following syntax for the op
[
{
"op": "add",
"path": "/nodes", "value": { "/": { "name": "hello, world" } }
}
]
The item is added like this:
"Nodes": {
"/": {
"Name": "hello, world"
}
}
But when I try to add a new item with another key like this:
[
{
"op": "add",
"path": "/nodes", "value": { "/my-key": { "name": "hello, world" } }
}
]
The item is not added, it's replaces the first entry so the result looks like this:
"Nodes": {
"/my-key": {
"Name": "hello, world"
}
}
How can I add, remove, replace, copy, move entries in a Dictionary<string, TrieNode>?
The format you use will set the whole property value (to a dictionary containing only one entry). That's why you see it's added but not updated.
For updating an entry via the key, looks like this is not documented anywhere. The actual syntax for updating a key (either adding or updating an existing one) is like this:
/path-to-dictionary/string-key
The path above targets existing entry for remove as well.
The value then should be just the entry value (not including key as in your code), like this:
{ "name": "hello, world" }
Here's the sample formats for different operations:
Add or Update:
[
{
"op": "add",
"path": "/nodes/my-key",
"value": { "name": "hello, world" }
}
]
Remove:
[
{
"op": "remove",
"path": "/nodes/my-key"
}
]
Replace: This is effectively like add for existing key.
[
{
"op": "replace",
"path": "/nodes/my-key",
"value": { "name": "hello, world" }
}
]
Copy:
[
{
"op": "copy",
"from": "/nodes/my-existing-key",
"path": "/nodes/my-key"
}
]
Move:
[
{
"op": "move",
"from": "/nodes/my-existing-key",
"path": "/nodes/my-key"
}
]
NOTE: The path format for dictionary requires the dictionary property to be non-null. Otherwise the path will be considered as non-existed and an error will be thrown with some message like this:
... the target location specified by path '/nodes/my-key' was not found
With the format used in your code, the whole dictionary property will be set. That's why there is always one entry remained (the final one overwrites all the others including the existing entries). With this note, you should ensure that your dictionary property is initialized first, like this:
if(trie.Nodes == null){
trie.Nodes = new Dictionary<string, TrieNode>();
}
patchDoc.ApplyTo(trie, ModelState);
About the dictionary key type:
Here in your example, the key type is string, but it can be any type as long as you have an associated TypeConverter for that type to work correctly. E.g: it can be int so you can use int keys normally, it can be DateTime so you can use key formats like yyyy-MM-dd (to use in the path) ...

How to verify stringified json in pact

I am trying to build a pact between two services using asynchronous communication.
This is the code I used for generate the pact:
#ExtendWith(PactConsumerTestExt.class)
#PactTestFor(providerName = "provider", providerType = ProviderType.ASYNCH)
public class StringifiedPactTest {
#Pact(consumer = "consumer", provider = "provider")
public MessagePact generatePact(MessagePactBuilder builder) {
return builder.hasPactWith("provider")
.expectsToReceive("A valid aws sns event")
.withContent(new PactDslJsonBody().stringType(new String[]{"MessageId", "TopicArn"}).stringValue("Message", new PactDslJsonBody().stringType("Value", "Foo").toString()))
.toPact();
}
#Test
#PactTestFor(pactMethod = "generatePact")
public void buildPact(List<Message> messages) {
}
}
And the generated pact is
{
"consumer": {
"name": "consumer"
},
"provider": {
"name": "provider"
},
"messages": [
{
"description": "A valid aws sns event",
"metaData": {
"contentType": "application/json"
},
"contents": {
"TopicArn": "string",
"Message": "{\"Value\":\"Foo\"}",
"MessageId": "string"
},
"matchingRules": {
"body": {
"$.MessageId": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.TopicArn": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.0.10"
}
}
}
This means the producer should have a "Message" that matches {"Value" : "Foo"}, any other combination like {"Value" : "Bar" } won't be successful.
Is there any way to add matching rules inside a stringified json?
Thanks!
Here's an anonymised example from a test we have. Hope it's useful. This creates a pact that matches only on type. So on the provider side, when I test against the contract, it doesn't matter what value I have for categoryName for example, as long as it's a stringType:
#PactTestFor(providerName = "provider-service", providerType = ProviderType.ASYNCH)
public class providerServiceConsumerPactTest {
private static String messageFromJson;
#BeforeAll
static void beforeAll() throws Exception {
messageFromJson = StreamUtils.copyToString(new ClassPathResource("/json/pact/consumer-service_provider-service.json").getInputStream(), Charset.defaultCharset());
}
#Pact(provider = "provider-service", consumer = "consumer-service")
public MessagePact providerServiceMessage(MessagePactBuilder builder) {
DslPart body = new PactDslJsonBody()
.object("metaData")
.stringType("origin", "provider-service")
.datetimeExpression("dateCreated", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.closeObject()
.minArrayLike("categories", 0, 1)
.stringType("id", "example data")
.stringType("categoryName", "example data")
.booleanType("clearance", false)
.closeObject()
.closeArray();
return builder
.expectsToReceive("a provider-service update")
.withContent(body)
.toPact();
}
#Test
#PactTestFor(pactMethod = "providerServiceMessage")
public void testProviderServiceMessage(MessagePact pact) {
// State
final String messageFromPact = pact.getMessages().get(0).contentsAsString();
// Assert
JSONAssert.assertEquals(messageFromPact, messageFromJson, false);
}
I'm having exactly the same issue, and unfortunately I don't think it's possible to tell Pact to parse the stringified JSON and look inside it (e.g. to verify that parse(Message).Value === "Foo" in your example).
The best you can do is write a regular expression to match the string you're expecting. This kind of sucks because there's no easy way to ignore the ordering of the JSON keys (e.g. "{\"a\":\"1\", \"b\":\"2\"}" and "{\"b\":\"2\", \"a\":\"1\"}" will compare different) but AFAIK Pact simply lacks the parsing functionality we're looking for, so the only tool it provides is regex.

.net Core and Serilog Email sink - json config

I'm using .net Core 2.0 and Serilog Email sink. I have problem to configure email sink with appsettings.json. The same configuration from program.cs is working while one from appsetting.json isn't.
The settings system (ReadFrom.Configuration()) really only does try to call methods and extension methods that it can discover and pass arguments provided from the configuration file.
Unfortunately, it only supports basic types for the time being (convertible to/from string and a few more specific cases) and therefore, parameters of type EmailConnectionInfo cannot be provided.
As a workaround, though, if you only need to pass in a few parameters, you can create your own extension method that accepts the parameters that you need and call it from the configuration system.
In your case, you would need to do the following :
First, define an extension method EmailCustom(...) that can be plugged on WriteTo (which is of type Serilog.Configuration.LoggerSinkConfiguration) and returns a LoggerConfiguration.
This would look something like (not tested, no usings etc :P) :
namespace Serilog{
public static class MyCustomExtensions
{
public static LoggerConfiguration EmailCustom(this LoggerSinkConfiguration sinkConfiguration, string param1, int param2, LogEventLevel restrictedToMinimumLevel){
// the actual call to configure the Email sink, passing in complex parameters
return sinkConfiguration.Email(... ... , restrictedToMinimumLevel , EmailConnectionInfo(){
Foo = "bar",
Baz = param1,
Qux = param2,
}
);
}
}
}
From that point on, you should be able to write C# code like :
new LoggerConfiguration()
.WriteTo.EmailCustom(param1: "my param1", param2: 42)
// ...
.CreateLogger();
Once you have that working, you can actually define that method call in json thanks to Serilog.Settings.Configuration
in that case, that would look like
{
"Serilog": {
"Using" : ["TheNameOfTheAssemblyThatContainsEmailCustom"],
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "EmailCustom",
"Args": {
"param1": "my param1",
"param2": 42,
"restrictedToMinimumLevel": "Verbose"
}
}]
}
}
This strategy can be applied for other sinks and other configuration parts of Serilog as well.
You can find a bit more about the configuration system here :
the project's README Serilog.Settings.Configuration
examples of what can be done through configuration (shameless plug :p)
For others Like me that have trouble piecing things between the lines here is a complete answer using the framework presented by tsimbalar for a solution that sends email out using SendGrid.
I added the following class to the root of my project ("MyApp"). This gets called automatically from the ReadFrom.Configuration(configuration).CreateLogger(); due to the WriteTo EmailCustom in the appsettings.
using System;
using System.Net;
using Serilog;
using Serilog.Configuration;
using Serilog.Events;
using Serilog.Sinks.Email;
namespace TrackumApi
{
public static class SerilogEmailExtension
{
public static LoggerConfiguration EmailCustom(this LoggerSinkConfiguration sinkConfiguration,
string fromEmail,
string toEmail,
string enableSsl,
string mailSubject,
string isBodyHtml,
string mailServer,
string networkCredentialuserName,
string networkCredentialpassword,
string smtpPort,
string outputTemplate,
string batchPostingLimit,
string periodMinutes,
string restrictedToMinimumLevel)
{
return sinkConfiguration.Email(new EmailConnectionInfo
{
FromEmail = fromEmail,
ToEmail = toEmail,
EnableSsl = GetBoolean(enableSsl),
EmailSubject = mailSubject,
IsBodyHtml = GetBoolean(isBodyHtml),
MailServer = mailServer,
NetworkCredentials = new NetworkCredential(networkCredentialuserName, networkCredentialpassword),
Port = GetInt(smtpPort)
}, outputTemplate, GetLevel(restrictedToMinimumLevel),
GetInt(batchPostingLimit), TimeSpan.FromMinutes(GetInt(periodMinutes))
);
}
//The system hated converting the string inputs inline so I added the conversion methods:
private static int GetInt(string instring)
{
return int.TryParse(instring, out var result) ? result : 0;
}
private static bool GetBoolean(string instring)
{
return bool.TryParse(instring, out var result) && result;
}
private static LogEventLevel GetLevel(string restrictedtominimumlevel)
{
return Enum.TryParse(restrictedtominimumlevel, true,
out LogEventLevel level) ? level : LogEventLevel.Warning;
}
}
}
In my origianl post I modified my Program.cs but it turns out that is not needed. However the addition of the Serilog.Debugging.SelfLog before any other code is still priceless:
Serilog.Debugging.SelfLog.Enable(Console.Out);
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
Finally I modified the appsettings.json as follows (forgive the extra, but I think that might also help somebody):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Serilog": {
"Using": [ "Serilog", "Serilog.Sinks.Console", "Serilog.Sinks.File", "MyApp" ],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"System": "Warning",
"Microsoft.AspNetCore.Authentication": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss.fff} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
}
},
{
"Name": "File",
"Args": {
"path": "C:\\Temp\\Logs\\MyApp.log",
"fileSizeLimitBytes": 1000000,
"rollOnFileSizeLimit": "true",
"shared": "true",
"flushToDiskInterval": 3,
"outputTemplate": "[{Timestamp:MM/dd/yy HH:mm:ss} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
"restrictedToMinimumLevel": "Verbose"
}
},
{
"Name": "EmailCustom",
"Args": {
"fromEmail": "no-reply#mydomain.com",
"toEmail": "me#mydomain.com",
"enableSsl": false,
"mailSubject": "MyApp Message",
"isBodyHtml": true,
"mailServer": "smtp.sendgrid.net",
"networkCredentialuserName": "mysendgridusername",
"networkCredentialpassword": "mysendgridpassword",
"smtpPort": 587,
"outputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}",
"batchPostingLimit": 10,
"periodMinutes": 5,
"restrictedToMinimumLevel": "Verbose"
}
}
],
"Enrich": [ "FromLogContext" ],
"Properties": {
"Application": "MyApp"
}
}
}
HTH!

How to get json data into apex salesforce?

I have a json like this, which i am getting in the response from http call
{
"offset": 0,
"limit": 50,
"objects": [
{
"id": "59118fb6e4b0168ec4b56692",
"modifiedDate": 1494323126886,
"requestedIds": null,
"mergedIds": [],
"properties": {
"name": [
{
"value": "Abhimanyu",
"metadata": {}
}
],
"company": [],
"title": [],
"email": [
{
"value": "absinghrathore127#gmail.com",
"metadata": {}
}
]
},
"state": "ACTIVE"
},
{
"id": "590d5813e4b03a8336fa1642",
"modifiedDate": 1494046739619,
"requestedIds": null,
"mergedIds": [],
"properties": {
"name": [
{
"value": "Tim Archer",
"metadata": {}
}
],
"company": [],
"title": [],
"email": [
{
"value": "tim#avocado.com",
"metadata": {}
}
]
},
"state": "ACTIVE"
}
],
"size": 2
}
and i am able to get objects from json via this following code :
String s = res.getBody();
Map<String,Object> jsonMap = (Map<String, Object>)JSON.deserializeUntyped(s);
String jsonSubset = JSON.serialize(jsonMap.get('objects'));
What i need is the value of name and email in some variable.
Please help me out in this!!
This is going to be a tedious task but once you've classified your all data into appropriate Wrapper classes then it's fairly simple and easy to maintain.
First thing is to define your MainWrapper class. This will contain all the at it's own level. If it has any Object as key-pair then we need to make sure to include it as a List<>. So This is how your MainWrapper should be:
public class MainWrapper {
Integer offset; // Singleton variable
Integer limits; // Singleton variable
List<ObjectsWrapper> objects; // Collection variable since it starts with [],
Integer size; // Singleton variable
}
Since you've array of objects in JSON that's why I've included it as a List in MainWrapper. Now it's time to define ObjectsWrapper. Below is wrapper defined for the same.
public class ObjectsWrapper {
String id;
String modifieddate;
String requestedIds;
PropertyWrapper properties;
}
Since there is only on properties associated with objects that's why it's a non-collection type. Below is representation of properties.
public class PropertyWrapper {
List<NameWrapper> name;
List<String> company;
List<String> title;
List<EmailWrapper> email;
String state;
}
public class NameWrapper {
String name;
String metadata;
}
I guess now you've a fair idea of how to organize data of JSON into various wrapper class. Once you're done with this, simply deserialize the JSON into MainWrapper class and access it.
For example:
MainWrapper mainJSONWrapper = (MainWrapper) JSON.deserialize(JSON,MainWrapper.class);
List<ObjectsWrapper> objectsLst = mainJSONWrapper.objects;
for(ObjectsWrapper obj:objectsLst) {
List<NameWrapper> lstNameWrapper = obj.properties;
for(NameWrapper nameObj:NameWrapper) {
System.debug('Name:'+nameObj.name);
System.debug('metadata:'+nameObj.metadata);
}
}
Above code is not tested but yes, it will give idea how you should deserialize JSON in appropriate manner.
Also go through this answer..How to deserialize a JSON String to Apex

map json response into nested typescript object with rxjs

In my Angular2-App I´m receiving a JSON-Response via http-Request that kind of looks like that:
{
"documents": [
{
"title": "Example-Doc 1",
"versions": [
{
"fileSize": 15360
},
{
"fileSize": 2048
}
]
},
{
"title": "Example-Doc 2",
"versions": [
{
"fileSize": 15360
},
{
"fileSize": 2048
}
]
}
],
"meta": {
"total": [2]
}
}
Now i wonder how to map this structure into my TypeScript-Classes, i checked different approaches, but it never worked. I actually need the constructor of the Version class to be called.
export class Document {
title: string; // Titel des Dokuments
versions: Version[];
}
If you have complex classes that need to be serialized and deserialized, I suggest that you implement static methods to your classes like fromJson and toJson - however without knowing your classes the rest of the answer will be kind of a guess-work:
Assuming you have a fromJson in place, then you can map your data like the following:
const myDocuments: Document[] = myJson.documents.map(Document.fromJson);
And the fromJson-method could look like this:
class Document {
constructor(public title: string, public versions: Version[])
public static fromJson(json: any): Document {
return new Document(
json.title,
json.versions.map(Version.fromJson)
);
}
}
class Version {
constructor(public fileSize: number) {}
public static fromJson(json: any): Version {
return new Version(json.fileSize);
}
}