Elastisearch.net property opt in? - nest

I'm using Elastisearch.NET with NEST 2.3. I want to use attribute mapping but I only want to index certain properties. As I understand it all properties are indexed unless you ignore them using for example [String(Ignore = true)] Is it possible to ignore all properties by default and only index the ones that have a nest attribute attached to them? Like JSON.NETs MemberSerialization.OptIn

You could do this using a custom serializer to ignore any properties not marked with a NEST ElasticsearchPropertyAttributeBase derived attribute.
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(
pool,
new HttpConnection(),
new SerializerFactory(s => new CustomSerializer(s)));
var client = new ElasticClient(connectionSettings);
client.CreateIndex("demo", c => c
.Mappings(m => m
.Map<Document>(mm => mm
.AutoMap()
)
)
);
}
public class Document
{
[String]
public string Field1 { get; set;}
public string Field2 { get; set; }
[Number(NumberType.Integer)]
public int Field3 { get; set; }
public int Field4 { get; set; }
}
public class CustomSerializer : JsonNetSerializer
{
public CustomSerializer(IConnectionSettingsValues settings, Action<JsonSerializerSettings, IConnectionSettingsValues> settingsModifier) : base(settings, settingsModifier) { }
public CustomSerializer(IConnectionSettingsValues settings) : base(settings) { }
public override IPropertyMapping CreatePropertyMapping(MemberInfo memberInfo)
{
// if cached before, return it
IPropertyMapping mapping;
if (Properties.TryGetValue(memberInfo.GetHashCode(), out mapping))
return mapping;
// let the base method handle any types from NEST
// or Elasticsearch.Net
if (memberInfo.DeclaringType.FullName.StartsWith("Nest.") ||
memberInfo.DeclaringType.FullName.StartsWith("Elasticsearch.Net."))
return base.CreatePropertyMapping(memberInfo);
// Determine if the member has an attribute
var attributes = memberInfo.GetCustomAttributes(true);
if (attributes == null || !attributes.Any(a => typeof(ElasticsearchPropertyAttributeBase).IsAssignableFrom(a.GetType())))
{
// set an ignore mapping
mapping = new PropertyMapping { Ignore = true };
Properties.TryAdd(memberInfo.GetHashCode(), mapping);
return mapping;
}
// Let base method handle remaining
return base.CreatePropertyMapping(memberInfo);
}
}
which produces the following request
PUT http://localhost:9200/demo?pretty=true
{
"mappings": {
"document": {
"properties": {
"field1": {
"type": "string"
},
"field3": {
"type": "integer"
}
}
}
}
}

Related

How might I construct a non-default constructor model from a query string in ASP.Net Core?

I would like to construct an object that has a non-default constructor from query string parameters in ASP.Net Core. In essence, I have two models that have a common ancestor class with different parameterizations. Based on some conditions in an API endpoint, one model will be constructed with parameters from the query string.
[HttpGet("{model}")]
public ModelBase Get(string model)
{
switch (model)
{
case "foo":
ModelFoo foo = GetModelFromQueryString<ModelFoo>();
return foo;
case "bar":
ModelBar bar = GetModelFromQueryString<ModelBar>();
return bar;
}
return null;
}
GetModelFromQueryString<TModel> is obviously the magical function that I wish I knew existed. If it already exists or someone could help provide implementation details, that would answer my question.
The example model classes would be like the following:
class ModelFoo : ModelBase
{
public ModelFoo(int param1=1, int param2=2)
{
// ...
}
}
class ModelBar : ModelBase
{
public ModelBar(int paramBaz=3)
{
// ...
}
}
This would ideally make the following HTTP calls yield the desired results:
GET api/foo?param1=7&param2=9 yields new ModelFoo(param1:7, param2:9).
GET api/bar?paramBaz=42 yields new ModelBar(paramBaz:42).
GET api/foo yields new ModelFoo(param1:1, param2:2).
GET api/foo?param2=11 yields new ModelFoo(param1:1, param2:11).
How might I go about this? Should I restructure entirely?
I realize that this may be a bit of a complicated, multi-faceted question so any and all help is much appreciated!
You can try to use my working demo.
Class:
public class ModelBase
{
}
class ModelFoo : ModelBase
{
public int param1 { get; set; }
public int param2 { get; set; }
public ModelFoo(int param1 = 1, int param2 = 2)
{
this.param1 = param1;
this.param2 = param2;
}
}
class ModelBar : ModelBase
{
public int paramBaz { get; set; }
public ModelBar(int paramBaz = 3)
{
this.paramBaz = paramBaz;
}
}
Action:
[HttpGet("{model}")]
public ModelBase Get(string model,int param1,int param2,int paramBaz)
{
switch (model)
{
case "foo":
if(param1!=0 ^ param2!=0)
{
if(param1 != 0)
{
ModelFoo foo1 = new ModelFoo
{
param1 = param1,
};
return foo1;
}
if (param2 != 0)
{
ModelFoo foo2 = new ModelFoo
{
param2 = param2,
};
return foo2;
}
}
if (param1 == 0 && param2 == 0)
{
ModelFoo foo3 = new ModelFoo();
return foo3;
}
ModelFoo foo4 = new ModelFoo
{
param1 = param1,
param2 = param2,
};
return foo4;
case "bar":
if (paramBaz != 0)
{
ModelBar bar = new ModelBar
{
paramBaz = paramBaz,
};
return bar;
}
ModelBar bar1 = new ModelBar();
return bar1;
}
return null;
}
Not really a direct answer to your question, but the needed building blocks are already available.
Newtonsoft JSON.net supports creating types if the constructor parameter matches the property names and also supports creating instances of an interface if the concrete type is set within a $type property:
public static class Program
{
public static async Task<int> Main(string[] args)
{
var sourceList = new List<IModel> { new ModelFoo(3, 7), new ModelBar(11) };
var jsonList = JsonConvert.SerializeObject(sourceList, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
var list = JsonConvert.DeserializeObject<List<IModel>>(jsonList, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
return 0;
}
}
public interface IModel { }
public class ModelFoo : IModel
{
public ModelFoo(int first, int second)
{
First = first;
Second = second;
}
public int First { get; }
public int Second { get; }
}
public class ModelBar : IModel
{
public ModelBar(int third)
{
Third = third;
}
public int Third { get; }
}

How can I get independent JSON reference resolution between requests in ASP.NET Core?

I am attempting to add a custom IReferenceResolver implementation to an ASP.NET Core 2.2 MVC API application to reduce data in a JSON payload. However the reference resolutions are being shared between different requests.
It appears that a single instance of the ReferenceResolver is shared between requests. I want the references to be resolved independent of other requests, as different users of my won't have this shared reference context.
This is my ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opts =>
{
opts.SerializerSettings.ReferenceResolverProvider = () => new ThingReferenceResolver();
});
}
This is my controller implementation along with my custom IReferenceResolver
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet("")]
public ActionResult<ThingsResponse> Get()
{
return new ThingsResponse
{
MainThing = new Thing { Id = "foo" },
Things = new List<Thing>
{
new Thing { Id = "foo" },
new Thing { Id = "bar" }
}
};
}
}
public class ThingsResponse
{
[JsonProperty(IsReference = true)]
public Thing MainThing { get; set; }
[JsonProperty(ItemIsReference = true)]
public List<Thing> Things { get; set; }
}
public class Thing
{
public string Id { get; set; }
}
public class ThingReferenceResolver : IReferenceResolver
{
private readonly IDictionary<string, Thing> _idReference = new Dictionary<string, Thing>();
public void AddReference(object context, string reference, object value)
{
_idReference[reference] = (Thing)value;
}
public string GetReference(object context, object value)
{
var thing = (Thing)value;
_idReference[thing.Id] = thing;
return thing.Id.ToString();
}
public bool IsReferenced(object context, object value)
{
var thing = (Thing)value;
return _idReference.ContainsKey(thing.Id);
}
public object ResolveReference(object context, string reference)
{
_idReference.TryGetValue(reference, out Thing thing);
return thing;
}
}
On my first request I get the following response:
{
"mainThing": {
"$id": "foo",
"id": "foo"
},
"things": [
{
"$ref": "foo"
},
{
"$id": "bar",
"id": "bar"
}
]
}
On my second request I get the following response:
{
"mainThing": {
"$ref": "foo"
},
"things": [
{
"$ref": "foo"
},
{
"$ref": "bar"
}
]
}
I want my second request to look like my first request i.e. repeatable outputs.
You get different results for the second request because MVC creates one serializer and caches it, which then caches references if you have reference tracking on like you do.
I think if you return a JsonResult with new serializer settings in each result then you won't have this problem:
new JsonResult(yourData, new JsonSerializerSettings { ... })
One option I have come up with is to bypass configuring the JSON serializer that MVC provides and create my own for the request in question.
[HttpGet("")]
public ActionResult<ThingsResponse> Get()
{
var serializerSettings = JsonSerializerSettingsProvider.CreateSerializerSettings();
serializerSettings.ReferenceResolverProvider = () => new ThingReferenceResolver();
return new JsonResult(
new ThingsResponse
{
MainThing = new Thing { Id = "foo" },
Things = new List<Thing>
{
new Thing { Id = "foo" },
new Thing { Id = "bar" }
}
},
serializerSettings
);
}
In my specific scenario this is OK, because I do not have many endpoints that this would need to be configured for.
This means the following code from the example Startup.cs is not needed to solve my problem (as I define it per request)
.AddJsonOptions(opts =>
{
opts.SerializerSettings.ReferenceResolverProvider = () => new ThingReferenceResolver();
});
I think I will settle on this option for my circumstances, but would love to know if there are better ways to implement it.

How to serialize top level objects with Json.NET as objects and nested objects as references

I want to be able to serialize an object fully if it is at the top level of the serialization context but serialize objects lower in the context by reference.
I've searched and tried tests with Custom Contract Resolvers, Custom Json Converters and custom IReferenceResolver but I can't find a way to do this.
For example, imagine an IdType class that at the top level I want to serialize with all its properties but where I come across a reference to such an object in a property or list or dictionary I want to produce a reference.
For this type and test
public class IdType
{
public IdType(string id)
{
Id = id;
}
public string Id {get;}
public string Name {get;set;}
public int Number {get; set;}
public IdType OtherType { get; set; }
public IEnumerable<IdType> Types { get; set;}
public IDictionary<IdType, string> { get; set; }
public IDictionary<string, IdType> {get; set; }
}
[TestMethod]
public void SerializeTest()
{
var t1 = new IdType(1) { Name = 'Alice', Number = 42 };
var t2 = new IdType(2) { Name = 'Bob', Number = 21, OtherType = t1 };
var t3 = new IdType(2) { Name = 'Charlie', Number = 84, OtherType = t2, Types = new[] {t1, t2} };
var testTypes = new[]
{
t1,
t3
};
var serializer = new JsonSerializer
{
Formatting = Formatting.Indented,
};
StringWriter writer;
using (writer = new StringWriter())
{
serializer.Serialize(writer, myObject);
}
Console.WriteLine(writer.ToString());
}
I want output like this
[
{
"Id": "1",
"Name": "Alice"
"Number": 42,
},
{
"Id": "3",
"Name": "Charlie"
"Number": 84,
"OtherType": 2
"Types": [
"Id" : 1, 2
]
}
]
A JsonConverter has no context so it'll either always convert one way or another.
A custom resolver (derived from DefaultContractResolver) will work for a property of type IdType but I can't work out how to make it work with lists and dictionaries.
Latterly I've tried using PreserveReferenceHandling and a custom IReferenceResolver that has the IDs of the top level elements. But this doesn't work because the serialization is depth first.
Any suggestions to achieve this would be gratefully received
I think I've answered my own question. If I use a combination of a custom contract resolver and a custom converter and conditionally add the converter to the properties I want to serialize to IDs then it seems to work.
I've not implemented dictionaries yet but this works for basic properties and lists:
public class CustomResolver : DefaultContractResolver
{
readonly CamelCasePropertyNamesContractResolver innerResolver = new CamelCasePropertyNamesContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jsonProperty = base.CreateProperty(member, memberSerialization);
if (!jsonProperty.PropertyType.IsPrimitive && jsonProperty.PropertyType != typeof(string) && jsonProperty.Readable)
{
var innerContract = innerResolver.ResolveContract(jsonProperty.PropertyType);
if (typeof(IdType).IsAssignableFrom(innerContract.UnderlyingType) && innerContract is JsonObjectContract objectContract)
{
jsonProperty.Converter = new IdConverter();
}
else if (typeof(IEnumerable<IdType>).IsAssignableFrom(innerContract.UnderlyingType) && innerContract is JsonArrayContract arrayContract)
{
jsonProperty.Converter = new IdConverter();
}
}
return jsonProperty;
}
}
internal class IdConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (typeof(IdType).IsAssignableFrom(objectType) ||
typeof(IEnumerable<IdType>).IsAssignableFrom(objectType));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IdType item)
{
JToken token = JToken.FromObject(item.Id);
token.WriteTo(writer);
}
else if (value is IEnumerable<IdType> itemCollection)
{
JArray array = new JArray();
foreach (var i in itemCollection)
{
JToken token = JToken.FromObject(i.Id);
array.Add(token);
}
array.WriteTo(writer);
}
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To serialize and making use of the custom resolver you would:
var serializer = new JsonSerializer
{
Formatting = Formatting.Indented,
ContractResolver = new CustomResolver(),
};

NInject kernel GetAll returns empty

I've two projects (class library projects) which implement one interface:
The first one:
public class MailPlugin : Extensibility.IProductorPlugin
{
...
}
The second one:
public class FileSystemPlugin : Extensibility.IProductorPlugin
{
...
}
Extensibility.IProductorPlugin, is a interface of a third project:
namespace Extensibility
{
public delegate void NotifyDigitalInputs(List<Domain.DigitalInput> digital_inputs);
public interface IProductorPlugin
{
String Name { get; }
String Description { get; }
String Version { get; }
List<Domain.Channel> AvailableChannels { get; }
IEnumerable<Guid> TypeGuids { get; }
event NotifyDigitalInputs OnDigitalInputs;
}
}
In my composition root, I've created this class:
namespace UI
{
public sealed class NinjectServiceLocator
{
private static readonly Lazy<NinjectServiceLocator> lazy = new Lazy<NinjectServiceLocator>(() => new NinjectServiceLocator());
public static NinjectServiceLocator Instance { get { return lazy.Value; } }
public Ninject.IKernel Kernel { get; private set; }
private NinjectServiceLocator()
{
using (var k = this.Kernel = new Ninject.StandardKernel())
{
k.Bind(b => b.FromAssembliesMatching("*")
.SelectAllClasses()
.InheritedFrom(typeof(Extensibility.IProductorPlugin))
.BindAllInterfaces()
);
}
}
}
}
So, when I want to look for all plugins, I just perform this:
protected void initialize()
{
foreach (Extensibility.IProductorPlugin productor_plugin in NinjectServiceLocator.Instance.Kernel.GetAll(typeof(Extensibility.IProductorPlugin)))
{
using (var channel_tile = new DevExpress.XtraBars.Docking2010.Views.WindowsUI.Tile() { Group = "Plugin Channels" })
{
foreach (Domain.Channel channel in productor_plugin.AvailableChannels)
{
channel_tile.Elements.Add(new DevExpress.XtraEditors.TileItemElement() { Text = channel.Name });
channel_tile.Elements.Add(new DevExpress.XtraEditors.TileItemElement() { Text = channel.Description });
this.tileContainer1.Items.Add(channel_tile);
}
}
}
}
However, GetAll returns anything.
What am I doing wrong?
I'll appreciate a lot your help.
Thanks for all.
try removing the using() from around the Kernel instantiation. a using will dispose the object at the end of the scope, which we don't want for a kernel.
using (var k = this.Kernel = new Ninject.StandardKernel())

RavenDB static index on dictionary

I have an application that uses documents, that contain list of attributes in a dictionary, for some reason we need to use a static index and query/filter over these attributes.
A prototype looks like this:
class Program
{
static void Main(string[] args)
{
IDocumentStore store = new DocumentStore() { DefaultDatabase = "Test", Url = "http://localhost:8081" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Program).Assembly, store);
using (var session = store.OpenSession())
{
session.Store(new Document { Id = "1", Name = "doc_name", Attributes = new Dictionary<string, object> { { "Type", "1" }, { "Status", "Active" } } });
session.SaveChanges();
}
using (var session = store.OpenSession())
{
// works
var l1 = session.Query<Document, Documents_Index>().Where(a => a.Attributes["Type"] == "1").ToList();
// not working
var l2 = session.Query<Document, Documents_Index>().Where(a => a.Attributes["Status"] == "Active").ToList();
}
}
}
public class Documents_Index : AbstractIndexCreationTask<Document>
{
public Documents_Index()
{
Map = docs => docs.Select(a =>
new
{
a.Name,
a.Attributes,
Attributes_Type = a.Attributes["Type"]
});
}
}
[Serializable]
public class Document
{
public string Id { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Attributes { get; set; }
}
But since I need to query using any arbitrary Attribute name/value this index does solve our problem. Actually the list of attributes is known at run-time (so we tried modifying the Map expression to inject any number of attribute names, but so far we weren't successful). Is there a way how to define the index in some dynamic fashion?
You need to write it like:
public class Documents_Index : AbstractIndexCreationTask<Document>
{
public Documents_Index()
{
Map = docs => docs.Select(a =>
new
{
a.Name,
_ = a.Attributes.Select(x=>CreateField("Attributes_"+x.Key, x.Value),
});
}
}