Ignore base class properties during auto mapping in ElasticSearch using NEST - c#

I'm working on Elastic Search 5.4 and using NEST to create index in C#, When I do a automap for a class which in Inheriting from SortedDictionary, The base class properties like Count,Comparer,Items are added to the Mapping, Can some one please help me how to avoid this.
Thanks
public class Document : SortedDictionary<string, dynamic>
{
public int Id { get; set; }
}
The actual mapping I'm getting is
{"DocumentMapping": {
"mappings": {
"Document": {
"dynamic": "strict",
"_all": {
"enabled": false
},
"properties": null,
"Documents": {
"properties": {
"Comparer": {
"type": "object"
},
"Count": {
"type": "integer"
},
"Item": {
"type": "object",
"include_in_all": false
},
"ID": {
"type": "int "
}
}
}
}
}}}
What I need is
{"DocumentMapping": {
"mappings": {
"Document": {
"dynamic": "strict",
"_all": {
"enabled": false
},
"properties": null,
"Documents": {
"properties":
"ID": {
"type": "int "
}
}
}
}
}}}
How do I ignore the the Comparer,Count and Item from my mapping JSON.

Related

swashbuckle/swagger ui not validating guid format

If I have parameter of guid in my webapi action, the swagger UI will validate the format and not let me enter something that doesn't match the guid format.
This is my controller:
[ApiController]
[Route("[controller]")]
public class GuidTestController : ControllerBase
{
private readonly ILogger<GuidTestController> _logger;
public GuidTestController(ILogger<GuidTestController> logger)
{
_logger = logger;
}
[HttpPost("GuidTest")]
public string GuidTest(Guid id)
{
return $"{Request.ContentType}: {Request.ContentLength}";
}
}
This is the generated swagger json:
{
"openapi": "3.0.1",
"info": {
"title": "WebAPI.Test",
"version": "1.0"
},
"paths": {
"/GuidTest/GuidTest": {
"post": {
"tags": [
"GuidTest"
],
"parameters": [
{
"name": "id",
"in": "query",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
},
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": { }
}
This all works. if, in the swagger ui I enter a malformed guid, the textbox shakes and makes me correct the value.
I'm trying to create an operation filter that lets me create a multipart request. As a test, one part will be a guid and one part will be binary
It looks like this:
public class MultipartOperationFilter : IOperationFilter
{
/// <summary>
/// Apply the filter to the operation only if it is decorated with the attribute
/// </summary>
/// <param name="operation"></param>
/// <param name="context"></param>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.RequestBody = new OpenApiRequestBody() { Required = false };
operation.RequestBody.Content.Add("multipart/form-data", new OpenApiMediaType()
{
Schema = new OpenApiSchema()
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>()
{
{"a_guid",new OpenApiSchema()
{
Type="string",
Format = "uuid",
Title = "This is a guid"
}
},
{"a_file",new OpenApiSchema()
{
Type="string",
Format = "binary",
Title = "file upload"
}
},
}
}
});
}
}
And my controller action looks like this:
[HttpPost("MultiPartFormData")]
public string MultiPartFormData(Guid id)
{
return $"{Request.ContentType}: {Request.ContentLength}";
}
the resulting swagger.json looks like this:
{
"openapi": "3.0.1",
"info": {
"title": "WebAPI.Test",
"version": "1.0"
},
"paths": {
"/MultipartFilterTest/MultiPartFormData": {
"post": {
"tags": [
"MultipartFilterTest"
],
"parameters": [
{
"name": "id",
"in": "query",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"a_guid": {
"title": "This is a guid",
"type": "string",
"format": "uuid"
},
"a_file": {
"title": "file upload",
"type": "string",
"format": "binary"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
},
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": { }
}
The swagger ui will create well formed multipart request, but it doesn't validate that the guid value in the first part is formatted correctly, although it does validate that the guid parameter is the correct format. Is this just a shortcoming of either swagger or possible swashbuckle?

Read JSON output from AppInsights in C#

I want to read AppInsights API output in C# console application.
WebClient wc = new WebClient();
wc.BaseAddress = "https://api.applicationinsights.io/v1/apps/AppInsighID/query?query=requests|where timestamp>= ago(1h)|limit 100";
wc.Headers.Add("Host", "api.applicationinsights.io");
wc.Headers.Add("x-api-key", "key");
string json = wc.DownloadString("");
JObject jsonObject = JObject.Parse(json);
//With this, i got values for Rows
var rowsObject = jsonObject["tables"][0]["rows"];
Now values are in array, under rowObject, so how to read this?
I would also like to know the best practice we should follow when reading json string
I can see data like this
{
"tables": [
{
"name": "PrimaryResult",
"columns": [
{
"name": "timestamp",
"type": "datetime"
},
{
"name": "id",
"type": "string"
},
{
"name": "source",
"type": "string"
},
{
"name": "name",
"type": "string"
},
{
"name": "url",
"type": "string"
},
{
"name": "success",
"type": "string"
},
{
"name": "resultCode",
"type": "string"
},
{
"name": "duration",
"type": "real"
},
{
"name": "performanceBucket",
"type": "string"
},
{
"name": "itemType",
"type": "string"
},
{
"name": "customDimensions",
"type": "dynamic"
},
{
"name": "customMeasurements",
"type": "dynamic"
},
{
"name": "operation_Name",
"type": "string"
},
{
"name": "operation_Id",
"type": "string"
},
{
"name": "operation_ParentId",
"type": "string"
},
{
"name": "operation_SyntheticSource",
"type": "string"
},
{
"name": "session_Id",
"type": "string"
},
{
"name": "user_Id",
"type": "string"
},
{
"name": "user_AuthenticatedId",
"type": "string"
},
{
"name": "user_AccountId",
"type": "string"
},
{
"name": "application_Version",
"type": "string"
},
{
"name": "client_Type",
"type": "string"
},
{
"name": "client_Model",
"type": "string"
},
{
"name": "client_OS",
"type": "string"
},
{
"name": "client_IP",
"type": "string"
},
{
"name": "client_City",
"type": "string"
},
{
"name": "client_StateOrProvince",
"type": "string"
},
{
"name": "client_CountryOrRegion",
"type": "string"
},
{
"name": "client_Browser",
"type": "string"
},
{
"name": "cloud_RoleName",
"type": "string"
},
{
"name": "cloud_RoleInstance",
"type": "string"
},
{
"name": "appId",
"type": "string"
},
{
"name": "appName",
"type": "string"
},
{
"name": "iKey",
"type": "string"
},
{
"name": "sdkVersion",
"type": "string"
},
{
"name": "itemId",
"type": "string"
},
{
"name": "itemCount",
"type": "int"
}
],
"rows": [
[
"2020-01-16T07:07:35.8423912Z",
"ID",
"",
"POST ",
"https://",
"True",
"200",
57.679,
"<250ms",
"request",
"{\"Product Name\":\"Name\",\"Subscription Name\":\"Name\",\"Operation Name\":\"AdvancedSearch\",\"ApimanagementRegion\":\"Region\",\"ApimanagementServiceName\":\"Name\",\"Apim Request Id\":\"ID\",\"Request-Body\":\"{\\\"P1\\\":25,\\\"P2\\\":1,\\\"P3\\\":\\\"All \\\",\\\"P4\\\":\\\"Earliest\\\",\\\"P5\\\":\\\"Extended\\\",\\\"P6\\\":\\\"All \\\",\\\"P6\\\":\\\"Latest\\\",\\\"queryList\\\":[{\\\"P7\\\":\\\"physics\\\",\\\"P8\\\":\\\"A1\\\",\\\"operator\\\":\\\"\\\"}]}\",\"Cache\":\"None\",\"P9\":\"195.43.22.145\",\"API Name\":\"Name\",\"HTTP Method\":\"POST\"}",
"{\"Response Size\":776,\"Request Size\":1092,\"Client Time (in ms)\":0}",
"POST ",
"ID",
"ID",
"",
"",
"",
"1",
"",
"",
"PC",
"",
"",
"0.0.0.0",
"Milan",
"Milan",
"Italy",
"",
"Value1",
"Value2",
"ID1",
"AppInsight Name",
"Name",
"apim:0.12.885.0",
"ID",
1
]
]
}
]
}
You could deserialize the Json and fetch the Rows information. For example,
var result = JsonConvert.DeserializeObject<RootObject>(str);
var rowData = result.tables.SelectMany(x=>x.rows.SelectMany(c=>c));
If you do not want to Flatten the results, you could use
var rowData = result.tables.SelectMany(x=>x.rows.Select(c=>c));
Where RootObject is defined as
public class Column
{
public string name { get; set; }
public string type { get; set; }
}
public class Table
{
public string name { get; set; }
public List<Column> columns { get; set; }
public List<List<string>> rows { get; set; }
}
public class RootObject
{
public List<Table> tables { get; set; }
}
If you intend to eliminate empty values, you could filter them out using Linq
var rowData = result.tables.SelectMany(x=>x.rows.SelectMany(c=>c))
.Where(x=>!string.IsNullOrEmpty(x));
Or for non-Flatten results
var rowData = result.tables.SelectMany(x=>x.rows.Select(c=>c))
.Where(x=>!string.IsNullOrEmpty(x.ToString()));
Update
Based on comment, to retrieve the information and parse it to a Dto based on the position of values in array, you could do the following. The attribute could be used for handling inline Json Properties as well, as mentioned in the comment.
You could begin by defining an attribute as following.
public class DtoDefinitionAttribute:Attribute
{
public DtoDefinitionAttribute(int order)=>Order = order;
public DtoDefinitionAttribute(int order,bool isJson,Type jsonDataType)
{
Order = order;
JsonDataType = jsonDataType;
IsJson = isJson;
}
public bool IsJson{get;} = false;
public int Order{get;}
public Type JsonDataType {get;}
}
And, then you could decorate your Dto properties with the index of corresponding value in the array. Additionally, in case of Json string expected as Json, then you could use the attribute to indicate it as well, as shown in the ClientTime property For example,
public class Dto
{
[DtoDefinition(0)]
public DateTime CurrentDate{get;set;}
[DtoDefinition(1)]
public string ID{get;set;}
[DtoDefinition(2)]
public string Url{get;set;}
[DtoDefinition(11,true,typeof(Response))]
public Response Json1{get;set;}
}
public class Response
{
[JsonProperty("Response Size")]
public string ResponseSize{get;set;}
[JsonProperty("Request Size")]
public string RequestSize{get;set;}
[JsonProperty("Client Time (in ms)")]
public int ClientTime{get;set;}
}
Now you could use the rowData result obtained using
var listDto = new List<Dto>();
foreach(var row in rowData)
{
listDto.Add(AssignValues(row));
}
Where AssignValues are defined as
public Dto AssignValues(List<string> row)
{
var dto = new Dto();
var properties = typeof(Dto).GetProperties().Where(x=>x.GetCustomAttributes<DtoDefinitionAttribute>().Any());
foreach(var property in properties)
{
var attribute = property.GetCustomAttribute<DtoDefinitionAttribute>();
if(attribute.IsJson)
{
var jsonData = row[attribute.Order].ToString();
var deserializedData = JsonConvert.DeserializeObject(jsonData,attribute.JsonDataType);
property.SetValue(dto,deserializedData);
}
else
{
property.SetValue(dto,Convert.ChangeType(row[attribute.Order],property.PropertyType));
}
}
return dto;
}
The AssignValues method uses reflection to read the Attributes and create an instance of Dto based on it. In case, it finds the attribute defines it as Json, then it would deserialize the json value and use the result.
Demo Code

Using Newtonsoft.Json.Schema is there a way to intercept the generation of the "definitions" name?

Given I have the following type:
public class MyClass
{
public MyGenericClass<bool> Gen { get; set; }
}
public class MyGenericClass<T>
{
[Required]
public T Prop { get; set; }
}
and I set up the schema generator like so
var generator = new JSchemaGenerator
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
SchemaIdGenerationHandling = SchemaIdGenerationHandling.None
};
generator.GenerationProviders.Add(
new StringEnumGenerationProvider { CamelCaseText = true });
I get the following schema
{
"definitions": {
"MyGenericClass<Boolean>": {
"type": [
"object",
"null"
],
"properties": {
"prop": {
"type": "boolean"
}
},
"required": [
"prop"
]
}
},
"type": "object",
"properties": {
"gen": {
"$ref": "#/definitions/MyGenericClass<Boolean>"
}
},
"required": [
"gen"
]
}
Which is great except that the service I need to share the schema with does not like the angle brackets '<>'.
Is there a way to intercept the creation of the definition name so I can strip out the <>? Note none of the other SchemaIdGenerationHandling work either.
Annoyingly something like
public class GenericSchemaProvider : JSchemaGenerationProvider
{
public override bool CanGenerateSchema(JSchemaTypeGenerationContext context)
{
return context.ObjectType.IsGenericType && !context.ObjectType.IsAssignableFrom(typeof(IEnumerable));
}
public override JSchema GetSchema(JSchemaTypeGenerationContext context)
{
return CanGenerateSchema(context) ? CreateSchemaWithCustomId(context, context.Required) : null;
}
private static JSchema CreateSchemaWithCustomId(JSchemaTypeGenerationContext context, Required required)
{
var schema = context.Generator.Generate(context.ObjectType, required != Required.Always);
schema.Id = new Uri(TypeName(context.ObjectType), UriKind.Relative);
return schema;
}
private static string TypeName(Type type, string acc = "")
{
var name = type.Name;
var index = name.IndexOf('`');
return index == -1 ? acc+name : TypeName(type.GetGenericArguments()[0],acc+name.Substring(0, index));
}
}
almost works
{
"definitions": {
"MyGenericClassNullableDouble": {
"$id": "MyGenericClassNullableDouble",
"type": [
"object",
"null"
],
"properties": {
"prop": {
"type": "number"
}
},
"required": [
"prop"
]
},
"MyGenericClassDouble": {
"$id": "MyGenericClassDouble",
"type": [
"object",
"null"
],
"properties": {
"prop": {
"type": "number"
}
},
"required": [
"prop"
]
},
"MyGenericClassBoolean": {
"$id": "MyGenericClassBoolean",
"type": [
"object",
"null"
],
"properties": {
"prop": {
"type": "boolean"
}
},
"required": [
"prop"
]
}
},
"type": "object",
"properties": {
"genBool": {
"$ref": "MyGenericClassBoolean"
},
"genDouble": {
"$ref": "MyGenericClassDouble"
},
"genNulableDouble": {
"$ref": "MyGenericClassNullableDouble"
}
}
}
But the references are not proper references to definitions.
I believe you are on the right track, I think you just need to provide your own implementation of the JSchemaGenerationProvider, instead of using StringEnumGenerationProvider. Something like creating a custom provider:
public class FormatSchemaProvider : JSchemaGenerationProvider
{
public override JSchema GetSchema(JSchemaTypeGenerationContext context)
{
// customize the generated schema for these types to have a format
if (context.ObjectType == typeof(MyGenericClass<bool>))
{
return CreateSchemaWithFormat(
context.ObjectType, context.Required, "MyGenericClass");
}
// use default schema generation for all other types
return null;
}
private JSchema CreateSchemaWithFormat(Type type, Required required, string format)
{
JSchemaGenerator generator = new JSchemaGenerator();
JSchema schema = generator.Generate(type, required != Required.Always);
schema.Format = format;
return schema;
}
}
Then add the new generation provider:
JSchemaGenerator generator = new JSchemaGenerator();
generator.GenerationProviders.Add(new FormatSchemaProvider());
JSchema schema = generator.Generate(typeof(MyClass));

can not search elasticsearch based on document property values

I have lots of product typed documents saved in elasticsearch but couldn't search by documents property values and need help.
//Product Class
public Guid productID { get; set; }
public string productName { get; set; }
public Guid productTypeID { get; set; }
public List<Guid> categoryIds {get; set; }
I'm trying to search like this:
//Search function
var esconn = Yaziylabir.Bukalemun.DataObjects.ElasticSearchConnectionManager.GetClient();
QueryContainer q = null;
if (!ProductType.HasValue || (ProductType.HasValue && ProductType.Value == B2CVariables.PRODUCTTYPE_PRODUCT))
{
q = Query<ProductModel>.Term(t => t.ProductTypeID, B2CVariables.PRODUCTTYPE_PRODUCT);
}
if (Category != null)
{
//catListZ is also List<Guid>
q &= Query<ProductModel>.Terms(u=>u.Field(z=>z.CategoryIDs).Terms<Guid>(catListZ));
}
// as a bonus I also have keyword search
if (!string.IsNullOrEmpty(Keyword))
{
q &= Query<ProductModel>.QueryString(t => t.Query(Keyword));
}
//do search
var pp = new SearchRequest(Yaziylabir.Bukalemun.DataObjects.ElasticSearchConnectionManager.DefaultIndex, "product");
pp.Query = q;
pp.Size = PageSize;
var res = esconn.Search<ProductModel>(pp);
rtnVal = res.Documents.ToList();
Now, I tried combinations of these (only producttypeID, only categoryIDs, only keyword, etc...) and watch what is happening with fiddler.
No result comes back, no errors are raised. Only 0 hits. Request body seems ok too.
When I check documents stored in that index, they are there and they have values required and should return in result.
What is wrong here? Do you have any ideas? Please help me here. I'm feeling ashamed to be the guy who couldn't search a database properly.
EDIT:
Search's body text:
{"size":12,"query":{"term":{"productTypeID":{"value":"cae344cf-8cfa-4960-8387-8ee89899c53f"}}}}
Example document:
{
"productID": "5687b8ac-c3fe-4f1a-9643-08b0bf6cede8",
"productName": "7011 Yelek",
"productCode": "701102Y001 ",
"currency": {
"currencyID": 1,
"sign": "TL",
"rate": 0
},
"normalPrice": 170,
"currentPrice": 84.9,
"isDiscounted": true,
"taxRate": 8,
"productTypeID": "cae344cf-8cfa-4960-8387-8ee89899c53f",
"defaultImagePath": "/contents/images/productimages/75681ee4-19b3-4c7d-a24b-b3566085a980.jpg",
"totalStockCount": 8,
"totalStockRecordCount": 4,
"isInStock": true,
"statusID": "9ad17471-2ff2-4eb0-9cb0-4b86922263ea",
"categoryIDs": [
"a8c83f54-b784-4866-89c3-cabc641490d5",
"9d5a9ab7-8edb-4d5a-800b-c48bf6575d78"
]
}
I didn't include all properties because it will make document very long.
Here is the mapping:
{
"mappings": {
"product": {
"properties": {
"categoryIDs": {
"type": "string"
},
"currentPrice": {
"type": "double"
},
"isDiscounted": {
"type": "boolean"
},
"isInStock": {
"type": "boolean"
},
"normalPrice": {
"type": "double"
},
"productCode": {
"type": "string"
},
"productID": {
"type": "string"
},
"productName": {
"type": "string"
},
"productTypeID": {
"type": "string"
},
"statusID": {
"type": "string"
},
"taxRate": {
"type": "double"
},
"totalStockCount": {
"type": "long"
},
"totalStockRecordCount": {
"type": "long"
}
}
}
}
}
I suspect the productTypeID field is using either the default analyzer - standard - or any other analyzer that's splitting it in the wrong places. What you need is for productTypeID to be index: not_analyzed or analyzed with something like keyword. And you need to create the mapping manually, otherwise you can't do it the way you want it.
The idea is that ES is tokenizing by default your productTypeID values and will split them at -, so in the index you'd have tokens not the entire value. You, instead, need to have that value unchanged so that term will match it fully.
For example, not to mess up with your previous mapping, you can add fields to define a sub-field that will be not_analyzed:
"productTypeID": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
}
And your query needs to change slightly: {"size":12,"query":{"term":{"productTypeID.raw":{"value":"cae344cf-8cfa-4960-8387-8ee89899c53f"}}}}
For completeness sake, this is the complete command to create the mapping by hand. You could do it while the index is still "alive", but you'd still have to re-index the documents:
curl -XPUT "http://localhost:9200/your_index" -d'
{
"mappings": {
"product": {
"properties": {
"categoryIDs": {
"type": "string"
},
"currentPrice": {
"type": "double"
},
"isDiscounted": {
"type": "boolean"
},
"isInStock": {
"type": "boolean"
},
"normalPrice": {
"type": "double"
},
"productCode": {
"type": "string"
},
"productID": {
"type": "string"
},
"productName": {
"type": "string"
},
"productTypeID": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
},
"statusID": {
"type": "string"
},
"taxRate": {
"type": "double"
},
"totalStockCount": {
"type": "long"
},
"totalStockRecordCount": {
"type": "long"
}
}
}
}
}'

NEST ElasticSearch.Raw.IndiciesCreatePost does not get correct mappings for index

I'm trying to use NEST to create an index with raw json and it produces different results than when I use that same json string interactively against elastic search. Is this a bug or am I using it incorrectly?
Posting directly to elastic search with the following command I get exactly the mappings I want for my index (results shown below)
POST /entities
{
"mappings": {
"sis_item" :
{
"properties":
{
"FullPath":
{
"type": "string",
"index":"not_analyzed"
},
"Ident":
{
"type": "nested",
"properties":
{
"ObjectGuid":
{
"type": "string",
"index":"not_analyzed"
}
}
}
}
}
}
Result when I check the index with: GET /entities/ : (which is correct)
{
"entities": {
"aliases": {},
"mappings": {
"sis_item": {
"properties": {
"FullPath": {
"type": "string",
"index": "not_analyzed"
},
"Ident": {
"type": "nested",
"properties": {
"ObjectGuid": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
},
"settings": {
"index": {
"creation_date": "1453828294488",
"number_of_shards": "5",
"number_of_replicas": "1",
"version": {
"created": "1070499"
},
"uuid": "6_j4vRcqRwiTQw0E6bQokQ"
}
},
"warmers": {}
}
}
However I have to do this from code and using the following code the mappings I specify end up in the settings instead of the mappings as shown below.
var createIndexJson = #"
{
""mappings"": {
""sis_item"" :
{
""properties"":
{
""FullPath"":
{
""type"": ""string"",
""index"":""not_analyzed""
},
""Ident"":
{
""type"": ""nested"",
""properties"":
{
""ObjectGuid"":
{
""type"": ""string"",
""index"":""not_analyzed""
}
}
}
}
}
}
}";
var response = _client.Raw.IndicesCreatePost("entities_from_code", createIndexJson);
if (!response.Success || response.HttpStatusCode != 200)
{
throw new ElasticsearchServerException(response.ServerError);
}
Result (not correct, notice the mappings are nested inside the settings):
{
"entities_from_code": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"mappings": {
"sis_item": {
"properties": {
"FullPath": {
"type": "string",
"index": "not_analyzed"
},
"Ident": {
"type": "nested",
"properties": {
"ObjectGuid": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
},
"creation_date": "1453828283162",
"number_of_shards": "5",
"number_of_replicas": "1",
"version": {
"created": "1070499"
},
"uuid": "fdmPqahGToCJw_mIbq0kNw"
}
},
"warmers": {}
}
}
There is a newline at the very top of the json string which cause the odd result, removing it gave me the expected behaviour.

Categories

Resources