Create WPF UI from JSON schema - c#

Is there a way to create a WPF UI using a JSON Schema? I am aware that it is possible to convert it into a HTML form with the help of AngularJS and others. But looking for a way to create WPF out of the same has not been fruitful.
There exists a Source by Rico Suter
on how to create a Visual Json Editor. My requirement is slightly different from what is given here. In my case, I want to create WPF controls based on the schema and the properties mentioned within the schema. And, with the help of the UI, I want to be able to create as many JSON objects by entering the values into the UI controls.
For example, let's consider the below JSON schema as a sample.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {},
"id": "http://example.com/example.json",
"properties": {
"checked": {
"default": false,
"description": "An explanation about the purpose of this instance.",
"id": "/properties/checked",
"title": "The Checked Schema",
"type": "boolean"
},
"dimensions": {
"id": "/properties/dimensions",
"properties": {
"height": {
"default": 10,
"description": "An explanation about the purpose of this instance.",
"id": "/properties/dimensions/properties/height",
"title": "The Height Schema",
"type": "integer"
},
"width": {
"default": 5,
"description": "An explanation about the purpose of this instance.",
"id": "/properties/dimensions/properties/width",
"title": "The Width Schema",
"type": "integer"
}
},
"type": "object"
},
"id": {
"default": 1,
"description": "An explanation about the purpose of this instance.",
"id": "/properties/id",
"title": "The Id Schema",
"type": "integer"
},
"name": {
"default": "A green door",
"description": "An explanation about the purpose of this instance.",
"id": "/properties/name",
"title": "The Name Schema",
"type": "string"
},
"price": {
"default": 12.5,
"description": "An explanation about the purpose of this instance.",
"id": "/properties/price",
"title": "The Price Schema",
"type": "number"
},
"tags": {
"id": "/properties/tags",
"items": {
"default": "home",
"description": "An explanation about the purpose of this instance.",
"id": "/properties/tags/items",
"title": "The Empty Schema",
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
I want to be able to display a checkbox for the checked property. Similarly, a GroupBox or something with 2 TextBox controls within to enter the dimensions(height and width). This UI should enable the user to enter desired values based on which a JSON object can be generated. Something like,
{
"checked": false,
"dimensions": {
"width": 5,
"height": 10
},
"id": 1,
"name": "A green door",
"price": 12.5,
"tags": [
"home",
"green"
]
}
Currently, I am creating a list of JSchema objects and and deserializing every property into type JSchema and then adding it to the list. And thereafter, I am trying to create controls for the same. This is just messy and I have not completely reached my goal. Yet I do not feel that I will be satisfied with the end result. If you could suggest a way to achieve the same it would be of great help. Thanks.
Samples taken from
here.

So, it is certainly possible. What you would need to do is define the deserialization routine to make List<T>/ObservableCollection<T> objects that implement INotifyPropeertyChanged. You could do this via Newtonsoft Json or write a JSchema to ViewModel converter
Next, you can make a ContentControl or even a Listbox/StackPanel bound to this enumerable, like a Master Detail view, and the details view can implement a Property Grid on a selected object. Example of Property grid.
Make sure all your bindings are TwoWay to preserve changes you make.
Additionally, you could implement the OnSelectionChanged event on your StackPanel to serialize the changes.
Resources
Master detail view
Property grid source

I wanted to do same thing for UWP, but didn't find a working solution that I could use.
Besides PropertyGrid mentioned above I also found
DataGrid from Windows Community Toolkit and DataForm from Telerik.
Using those still would require converting Json to Object model and back.
As it turned out Newtonsoft.Json is built with databinding in mind, so it is pretty easy to generate controls from Json that would bind to Json properties.
Here is a code snippet to do that:
private void RenderForm(JArray jArray)
{
StackPanel stackPanel = new StackPanel() { Orientation = Orientation.Vertical };
this.Content = stackPanel;
stackPanel.Height = this.Height;
stackPanel.Width = this.Width;
stackPanel.Children.Add(button);
foreach (JObject element in jArray)
{
String type = element["type"].ToString();
TextBlock textBlock = new TextBlock() { Text = element["name"].ToString() };
textBlock.Padding = new Thickness() { Top = 5 };
switch (type)
{
case "hiddendata":
break;
case "bool":
CheckBox checkBox = new CheckBox();
checkBox.DataContext = element;
Binding checkBoxBinding = new Binding() { Path = new PropertyPath("[value].Value"), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
checkBoxBinding.Source = element;
checkBox.SetBinding(CheckBox.IsCheckedProperty, checkBoxBinding);
stackPanel.Children.Add(textBlock);
stackPanel.Children.Add(checkBox);
break;
case "image":
if (!String.IsNullOrEmpty(element["value"].Value<String>()))
{
Image image = new Image();
image.MaxHeight = 200;
image.MaxWidth = 200;
var ignore = SetImageSource(element["value"].Value<String>(), image);
stackPanel.Children.Add(textBlock);
stackPanel.Children.Add(image);
}
break;
case "info":
if (!String.IsNullOrEmpty(element["value"].Value<String>()))
{
TextBlock displayTextBlock = new TextBlock();
displayTextBlock.DataContext = element;
Binding displayTextBlockBinding = new Binding() { Path = new PropertyPath("[value].Value"), Mode = BindingMode.OneWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
displayTextBlockBinding.Source = element;
displayTextBlock.SetBinding(TextBlock.TextProperty, displayTextBlockBinding);
stackPanel.Children.Add(textBlock);
stackPanel.Children.Add(displayTextBlock);
}
break;
case "password":
PasswordBox passwordBox = new PasswordBox();
passwordBox.DataContext = element;
Binding passwordBoxBinding = new Binding() { Path = new PropertyPath("[value].Value"), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
passwordBoxBinding.Source = element;
passwordBox.SetBinding(PasswordBox.PasswordProperty, passwordBoxBinding);
stackPanel.Children.Add(textBlock);
stackPanel.Children.Add(passwordBox);
break;
case "string":
default:
TextBox textBox = new TextBox();
textBox.DataContext = element;
Binding textBoxBinding = new Binding() { Path = new PropertyPath("[value].Value"), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
textBoxBinding.Source = element;
textBox.SetBinding(TextBox.TextProperty, textBoxBinding);
stackPanel.Children.Add(textBlock);
stackPanel.Children.Add(textBox);
break;
}
}
}

Related

JSON Schema conditional result

I am trying to create a JSON schema validator. My Json schema validates a certain property value and based on that it assigns value to another property. In my C# code I want to perform an action based on that. If you notice in my schema, if country is either "United States of America" or "Canada" then, I am setting effect be "compliant" else "non-complaint". And, In my C# code, I need the value of "effect" so that I can do some further processing. Is that possible? If not, what should be my approach? I am new to Json Schema (I have seen Azure polices doing something similar to this.)
Here is my Schema
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"enum": [ "United States of America", "Canada" ]
},
"effect": {"type": "string"}
},
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"effect": "Compliant"
},
"else": {
"effect": "Non-Compliant"
}
}
Here is my Document
{
"properties": {
"street_address": "1600 Pennsylvania Avenue NW",
"country": "Canada",
"postal_code": "20500"
}
}
Here is my C# Code
JObject data = null;
var currentDirectory = Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.FullName;
using (StreamReader r = new StreamReader(currentDirectory + #"/Documents/document.json"))
using (JsonTextReader reader = new JsonTextReader(r))
{
data = (JObject)JToken.ReadFrom(reader);
}
JsonSchema schema = JsonSchema.Parse(File.ReadAllText(currentDirectory + #"/Schemas/Schema.json"));
IList<string> messages;
var properties = (JObject)data["properties"];
bool valid = properties.IsValid(schema, out messages);

ElasticSearch's dynamic index mapping dropping logs due to poor field names

I have a semi-large project that has been using nlog, and throughout I re-used alot of field names for different datatypes. I started to send my logs (including all log properties/fields) to ElasticSearch, and now its starting to haunt me. I noticed if ElasticSearch is unable to convert a field to it's dataType it will just drop the log entirely. The dynamic index mapping is deciding the dataType depending on what it's seen first.
Is there anyway I can tell the index to use a default dataType like string?
PS; The Instance is cloud hosted, I access it through Kibana, and I have no Idea where to find a log that tells me if/when it drops logs for parsing errors.
Edit
Index Mapping
PUT /indexName
{
"mappings": {
"properties": {
"#domain": {
"type": "keyword"
},
"#logTarget": {
"type": "keyword"
},
"#logger": {
"type": "keyword"
},
"#memUsage": {
"type": "long"
},
"#processID": {
"type": "integer"
},
"#serviceGUID": {
"type": "keyword"
},
"#timestamp": {
"type": "date"
},
"level": {
"type": "keyword"
},
"message": {
"type": "text"
}
}
}
}
Unfortunately I don't know the api for pushing logs to elastic but here are some examples of what they might look like. Theses aren't the best examples but they show the overlapping/re-use of field names.
Example 1:
//NLog structured log
logger.LogInfo("That Lady has {count} cats", 5);
//JSON Object
{
"#domain": "Service1",
"#logTarget": "None",
"#logger": "AppName.Program.Main",
"#memUsage": 100,
"#processID": 17000,
"#serviceGUID": 0,
"level": "INFO",
"message": "That Lady has 5 cats",
"count": 5,
}
Example 2:
//NLog structured log
int count = 3;
logger.LogInfo("Loaded {count}", count + " dogs");
//JSON Object
{
"#domain": "Service1",
"#logTarget": "None",
"#logger": "AppName.Program.Main",
"#memUsage": 100,
"#processID": 17000,
"#serviceGUID": 0,
"level": "INFO",
"message": "Loaded "5 dogs"",
"count": 5,
}
Example 3:
//NLog structured log
object count = nil;
logger.LogInfo("Value is {count}", count);
//JSON Object
{
"#domain": "Service1",
"#logTarget": "None",
"#logger": "AppName.Program.Main",
"#memUsage": 100,
"#processID": 17000,
"#serviceGUID": 0,
"level": "INFO",
"message": "Loaded "5 dogs"",
"count": 5,
}

Referencing TargetElements when when authoring Adaptive Cards

I am authoring an Adaptive Card in C# and need to add an Image with a ToggleVisibilityAction. The JSON equivalent is:
{
"type": "Image",
"selectAction": {
"type": "Action.ToggleVisibility",
"title": "expand",
"targetElements": [ "REFERENCE_1", "REFERENCE_2",]
},
"url": "MY_IMAGE_URL",
"altText": "visible"
}
In the above REFERENCE_1 and REFERENCE_2 are the Id fields of the elements I want to target.
When authoring it in C#, I have
new AdaptiveImage()
{
SelectAction = new AdaptiveToggleVisibilityAction()
{
Title = "collapse",
TargetElements = REFERENCE_COLLECTION_HERE,
},
}
My challenge is that the JSON version accepts a string reference with an Id of the TargetElement but the C# version expects a List<AdaptiveTargetElement> where REFERENCE_COLLECTION_HERE is. How do I manage to reference the TargetElement while being able to add it where I want it in my card layout.
You can just use AdaptiveTargetElement objects instead of strings:
new AdaptiveImage()
{
SelectAction = new AdaptiveToggleVisibilityAction()
{
Title = "collapse",
TargetElements = new List<AdaptiveTargetElement>
{
new AdaptiveTargetElement("REFERENCE_1"),
new AdaptiveTargetElement("REFERENCE_2"),
},
},
}
You can see the documentation for them here: https://adaptivecards.io/explorer/TargetElement.html

Properly format a message for slack in bot framework

I'd like to send a message with buttons to the slack channel. I'm using the bot framework (c#). I want to use the "blocks" (attachments are deprecated according to the slack api docs). So I composed a sample message in the slack "Bot Kit Builder":
The json for it looks like this:
[
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Which pill do you want to take?"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Red",
"emoji": true
},
"value": "red"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Blue",
"emoji": true
},
"value": "blue"
}
]
}
]
As I understand, I have to provide this content in the ChannelData property of the message I sent to the channel:
if (turnContext.Activity.ChannelId == Channels.Slack)
{
message = turnContext.Activity.CreateReply();
message.ChannelData = ChannelDataBuilder.Create("Which pill do you want to take?", "Red", "Blue");
}
The code of the ChannelDataBuilder looks like this:
public static dynamic Create(string text, params string[] choices)
{
var blocks = new List<Block> { new Section { Text = new Text { TextValue = text } } };
var elements = choices.Select(
c => new Button { Text = new Text { TextValue = c, Type = "plain_text" }, Value = c });
blocks.Add(new Actions { Elements = elements.ToArray() });
return JArray.FromObject(blocks, new JsonSerializer { NullValueHandling = NullValueHandling.Ignore });
}
The resulting json of this method looks like this:
{[
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Which pill do you want to take?"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Red"
},
"action_id": "9e8ea9fb9267484a9f02b1837f716f69",
"value": "Red"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Blue"
},
"action_id": "34c3d9509fc04e2ea37ed54a70b78486",
"value": "Blue"
}
]
}
]}
So, basically I wonder how I should generate this array of json object using c#. Currently the array is still surrounded by the curly brackets (the list object), but I guess I have to provide an array of json objects.
I've already tried using the JsonConvert class and setting the ChannelData as string. But then nothing appears in the slack channel.
The channelData property allows you to pass a complete Slack message, but your are missing the required top-level properties.
If you want to include blocks, than those have to be defined under the blocks property.
So your JSON need to look more like this (not include the channelData property):
{
"blocks":
[
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Which pill do you want to take?"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Red"
},
"action_id": "9e8ea9fb9267484a9f02b1837f716f69",
"value": "Red"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Blue"
},
"action_id": "34c3d9509fc04e2ea37ed54a70b78486",
"value": "Blue"
}
]
}
]
}
See here for the relevant documentation for the botframework.
And here you can see how a message payload for Slack is defined.
Update
As mentioned by #mdrichardson the botframework currently does not support blocks. (See this issue on their github)
So while syntactically correct this solutions does currently not work.
Until botframework supports blocks I would suggest to use secondary attachments.

Graph: Filter calendar events by schema property

I have this schema extension:
{
"id": "intnovaction_Docu2EventMetadata",
"description": "Eventos de Docu2",
"targetTypes": [
"event"
],
"status": "Available",
"owner": "d1aaf0fa-549f-4692-8929-22eb90b33099",
"properties": [
{
"name": "ActuacionId",
"type": "String"
},
{
"name": "ExpedienteId",
"type": "String"
}
]
}
I am able to extend event properties using this schema. I can set values for 'ActuacionId' and 'ExpedienteId' on an event and I can get these values through this request: https://graph.microsoft.com/v1.0/me/events?$select=id,intnovaction_Docu2EventMetadata
that returns
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('6d418063-df8b-4f47-921b-1072baf4a949')/events(id,intnovaction_Docu2EventMetadata)",
"value": [
{
"#odata.etag": "W/\"FwgXoe8hSUuEcCnxk8/heAAALdjYcQ==\"",
"id": "AAMkAGE1MDUwMDZkLWRmZDctNGMxMi1hN2ZiLTUwNTBlYTc1NmRkYwBGAAAAAABIbknKwqd9SI8d_mLMOg2XBwAXCBeh7yFJS4RwKfGTz_F4AAAAAAENAAAXCBeh7yFJS4RwKfGTz_F4AAAtI8LHAAA=",
"intnovaction_Docu2EventMetadata": {
"ActuacionId": "1",
"ExpedienteId": "2"
}
}
}
the problem comes when I try to filter for those properties:
https://graph.microsoft.com/v1.0/me/events?$select=id,intnovaction_Docu2EventMetadata&$filter=intnovaction_Docu2EventMetadata/ActuacionId eq '1'
Then I am receiving this error response
{
"error": {
"code": "RequestBroker-ParseUri",
"message": "Could not find a property named 'e2_3be22c6901b942889d07616b14e79402_intnovaction_Docu2EventMetadata' on type 'Microsoft.OutlookServices.Event'.",
"innerError": {
"request-id": "4137b6f4-1c8d-4c1e-84fd-02e8ccaab860",
"date": "2017-10-02T19:25:28"
}
}
}
Is it not possible to filter events by schema properties?
It looks like we missed something in our documentation. It's not currently possible to filter on schema extensions defined on Outlook based entity types (events, messages and personal contacts). We could also improve our error messages to make this more clear too. I'll file some items for this.
Hope this helps,

Categories

Resources