How to apply BsonRepresentation attribute by convention when using MongoDB - c#

I'm trying to apply [BsonRepresentation(BsonType.ObjectId)] to all ids represented as strings withoput having to decorate all my ids with the attribute.
I tried adding the StringObjectIdIdGeneratorConvention but that doesn't seem to sort it.
Any ideas?

Yeah, I noticed that, too. The current implementation of the StringObjectIdIdGeneratorConvention does not seem to work for some reason. Here's one that works:
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
}
public class StringObjectIdIdGeneratorConventionThatWorks : ConventionBase, IPostProcessingConvention
{
/// <summary>
/// Applies a post processing modification to the class map.
/// </summary>
/// <param name="classMap">The class map.</param>
public void PostProcess(BsonClassMap classMap)
{
var idMemberMap = classMap.IdMemberMap;
if (idMemberMap == null || idMemberMap.IdGenerator != null)
return;
if (idMemberMap.MemberType == typeof(string))
{
idMemberMap.SetIdGenerator(StringObjectIdGenerator.Instance).SetSerializer(new StringSerializer(BsonType.ObjectId));
}
}
}
public class Program
{
static void Main(string[] args)
{
ConventionPack cp = new ConventionPack();
cp.Add(new StringObjectIdIdGeneratorConventionThatWorks());
ConventionRegistry.Register("TreatAllStringIdsProperly", cp, _ => true);
var collection = new MongoClient().GetDatabase("test").GetCollection<Person>("persons");
Person person = new Person();
person.Name = "Name";
collection.InsertOne(person);
Console.ReadLine();
}
}

You could programmatically register the C# class you intend to use to represent the mongo document. While registering you can override default behaviour (e.g map id to string):
public static void RegisterClassMap<T>() where T : IHasIdField
{
if (!BsonClassMap.IsClassMapRegistered(typeof(T)))
{
//Map the ID field to string. All other fields are automapped
BsonClassMap.RegisterClassMap<T>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
});
}
}
and later call this function for each of the C# classes you want to register:
RegisterClassMap<MongoDocType1>();
RegisterClassMap<MongoDocType2>();
Each class you want to register would have to implement the IHasIdField interface:
public class MongoDocType1 : IHasIdField
{
public string Id { get; set; }
// ...rest of fields
}
The caveat is that this is not a global solution and you still have to manually iterate over your classes.

Related

OData ignore certain properties unless explicitly stated to be returned

I have this model with following attributes. (Simplified)
public class Blog {
private string Code { get; set; }
private string Name { get; set; }
private byte[] Image { get; set; }
}
When I make a request to the OData URL for ex: http://localhost/api/odata/Blog, I want only Code and Name properties to be returned, ignoring the Image. And if I make
a request something like http://localhost/api/odata/Blog?$select=(Code,Name,Image) then I want the Image to be returned. How can I make this work?
Using attributes like [IgnoreDataMember] makes it unavailable for OData query to be accessed, therefore it is not a suitable solution.
First, probably properties of the Blog class are public, not private.
I had a similar scenario and resolve it by implementing a custom serializer:
Serializer provider class:
public class MyODataSerializerProvider : DefaultODataSerializerProvider
{
MyResourceSerializer myResourceSerializer;
public MyODataSerializerProvider(IServiceProvider serviceProvider) : base(serviceProvider)
{
myResourceSerializer = new MyResourceSerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsEntity())
{
return myResourceSerializer;
}
return base.GetEdmTypeSerializer(edmType);
}
}
Serializer class:
public class MyResourceSerializer : ODataResourceSerializer
{
public MyResourceSerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider) { }
public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
{
var resource = base.CreateResource(selectExpandNode, resourceContext);
if (selectExpandNode.SelectAllDynamicProperties)
{
resource.Properties = resource.Properties.Where(p => p.Name != "Image");
}
return resource;
}
}
And configuration of course:
routeBuilder.MapODataServiceRoute("OData", "odata", b =>
{
b.AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => edmModel);
var conventions = ODataRoutingConventions.CreateDefault();
//Workaround for https://github.com/OData/WebApi/issues/1622
conventions.Insert(0, new AttributeRoutingConvention("OData", app.ApplicationServices, new DefaultODataPathHandler()));
//Custom Convention
b.AddService<IEnumerable<IODataRoutingConvention>>(Microsoft.OData.ServiceLifetime.Singleton, a => conventions);
b.AddService(Microsoft.OData.ServiceLifetime.Singleton, typeof(ODataSerializerProvider), sp => new MyODataSerializerProvider(sp));
});

Custom attributes not behaving like data annotations

I am trying to create a custom attribute in console application but it is not working. My custom attribute never gets called. I found a good example here Custom Attribute not being hit
but not happy with its implementation.
I am wondering how data annotations works in MVC. we don't have to call it separately.
Is MVC calling those data annotations attribute behind the scene?
I wish to create custom attribute that I can use it on any class property same like data annotations attribute. But calling it separately like in above link is not what i am looking.
Here is what I have tried:
using System;
namespace AttributePractice
{
[AttributeUsage(AttributeTargets.Property)]
public class CustomMessageAttribute : Attribute
{
public static readonly CustomMessageAttribute Default = new CustomMessageAttribute();
protected string Message { get; set; }
public CustomMessageAttribute() : this(string.Empty)
{
Console.WriteLine("Default message is empty");
}
public CustomMessageAttribute(string message)
{
Message = message;
}
public string MyMessage =>
Message;
public override bool Equals(object obj)
{
if (obj == this)
return true;
if (obj is CustomMessageAttribute customMessageAttribute)
return customMessageAttribute.Message == MyMessage;
return false;
}
public override int GetHashCode()
{
return MyMessage.GetHashCode();
}
public override bool IsDefaultAttribute()
{
return Equals(Default);
}
}
public class Person
{
//This never works
// I am looking to use this attribute anywhere without calling it
// separately , same like data annotations
[CustomMessage("Hello world")]
public string Name { get; set; }
public int Age { get; set; }
public void DisplayPerson()
{
Console.WriteLine(Name);
Console.WriteLine(Age);
}
}
internal static class Program
{
private static void Main(string[] args)
{
var personObj = new Person
{
Name = "Tom",
Age = 28
};
personObj.DisplayPerson();
}
}
}
Can anybody tell me how to make my custom attribute works like data annotation way?
yes, if you need 10 custom attributes, you should create 10 separate.

How to correctly implement generic interface method?

I'm trying to implement generic interface method but keep getting an error. I'm pasting the code to better explain what I want to do.
What I'm trying to achieve is: based on some input data (SomeModelA, SomeModelB) I want to get the same return type (Template).
namespace GenericInterfacePuzzle
{
class Program
{
static void Main(string[] args)
{
var workerA = new WorkerA();
var itemsBasedOnModelA = workerA.Get(new List<SomeModelA>());
var workerB = new WorkerB();
var itemsBasedOnModelB = workerB.Get(new List<SomeModelB>());
}
}
public interface IWorker
{
Template Get<T>(List<T> someModels);
}
public class WorkerA : IWorker
{
public Template Get<SomeModelA>(List<SomeModelA> someModels)
{
ProcessModels(someModels);
return new Template(); // let's say it's based on the result of ProcessModels
}
private void ProcessModels(List<SomeModelA> models)
{
var x = models.First();
}
}
public class WorkerB : IWorker
{
public Template Get<SomeModelB>(List<SomeModelB> someModels)
{
ProcessModels(someModels);
return new Template(); // let's say it's based on the result of ProcessModels
}
private void ProcessModels(List<SomeModelB> models)
{
var x = models.First();
}
}
public class SomeModelA
{
public string Name { get; set; }
}
public class SomeModelB
{
public string Age { get; set; }
}
public class Template
{
// Irrevelant return type
}
}
I want to know at the level of WorkerA/WorkerB class that I'm dealing with a concrete model, and based on that I want to return a Template class instance
The problem is that in the lines that call Process:
ProcessModels(someModels);
I get an error saying:
Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.List of SomeModelA' to 'System.Collections.Generic.List of GenericInterfacePuzzle.SomeModelA'
Any feedback appreciated what might be going wrong here, and why doesn't it recognize the model classes when passed to the functions.
Chris
1) You need to define the generic parameter on the level of your interface. Otherwise the T parameter is not known to the compiler:
public interface IWorker<T> where T: SomeModel
{
Template Get(List<T> someModels);
}
2) you need to make a constraint since you probably don't want any type to be given to your interface. It would be preferable to make a baseclass for your models and let them inherit from it:
public abstract class SomeModel { ... }
public class SomeModelA : SomeModel
{
public string Name { get; set; }
}
public class SomeModelB : SomeModel
{
public string Age { get; set; }
}
This way it will allow you to specify the model directly in the declaration of the class which will implement the interface (see point 3)
3) Now you need to specify in the child classes which model belongs to which workertype:
public class WorkerA : IWorker<SomeModelA>
{
public Template Get(List<SomeModelA> someModels)
{
ProcessModels(someModels);
return new Template(); // let's say it's based on the result of ProcessModels
}
private void ProcessModels(List<SomeModelA> models)
{
var x = models.First();
}
}
public class WorkerB : IWorker<SomeModelB>
{
public Template Get(List<SomeModelB> someModels)
{
ProcessModels(someModels);
return new Template(); // let's say it's based on the result of ProcessModels
}
private void ProcessModels(List<SomeModelB> models)
{
var x = models.First();
}
}
You also should remove the generic specification in your Get method!
public Template Get<SomeModelA>(List<SomeModelA> someModels)
^
|
remove this
this is already specified when you implement the interface:
public class WorkerA : IWorker<SomeModelA>
4) and the last thing is you test in the main method:
var worker = new WorkerA();
var itemsBasedOnModelA = worker.Get(new List<SomeModelA>());
var workerB = new WorkerB();
var itemsBasedOnModelB = worker.Get(new List<SomeModelB>());
^
|
this should be [workerB]!

Automapper and inheritance from Collection or List

I'm trying to use AutoMapper (v5.1.1) to map an object which inherits from a List or Collection. The map call does not give me an error but the output is an empty list (of correct type though).
I can get a List<DestinationObject> or Collection<DestinationObject>, but it does not seem to work when having a custom class which enherits from List<T> or Collection<T>.
I've tried extending the first map definition to include the base class (List<T>) but that gives me a StackOverflowException.
cfg.CreateMap(typeof(SourceCollection), typeof(DestinationCollection)).Include(typeof(List<SourceObject>), typeof(List<DestinationObject>));
What am I missing here?
public class SourceCollection : List<SourceObject> {
}
public class DestinationCollection : List<DestinationObject> {
}
public class SourceObject {
public string Message { get; set; }
}
public class DestinationObject {
public string Message { get; set; }
}
static void Main(string[] args)
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap(typeof(SourceCollection), typeof(DestinationCollection));
cfg.CreateMap<List<SourceObject>, List<DestinationObject>>().Include<SourceCollection, DestinationCollection>();
cfg.CreateMap(typeof(SourceObject), typeof(DestinationObject));
});
AutoMapper.Mapper.AssertConfigurationIsValid();
SourceCollection srcCol = new SourceCollection() { new SourceObject() { Message = "1" }, new SourceObject() { Message = "2" } };
DestinationCollection dstCol = AutoMapper.Mapper.Map<SourceCollection, DestinationCollection>(srcCol);
}
You just have to map sourceobject to destinationobject, AutoMapper will do rest of the magic, More on this can be found on this link
cfg.CreateMap(typeof(SourceObject), typeof(DestinationObject));

Conditionally serialize a object in a collection using Json.net

There is tons of info about skipping Properties based on conditionals, but I would like to skip the entire object based on conditions within the object's class. I would like a solution that is contained within the object's class if at all possible. Keep in mind this is a collection of myObj that I am serializing.
public class myObj
{
bool conditional;
ShouldSerialize()
{
return conditional;
}
}
Or
public class myObj
{
[JsonCondition]
public bool conditional{get;}
}
Or even
[JsonCondition(typeof(MyConditionChecker))]
public class myObj
{
public bool conditional{get;}
}
class MyConditionChecker: JsonCondition
{
public override bool CanConvert(object sourceObj)
{
return (sourceObj as myObj).conditional;
}
}
What I got from your comments you would be best served creating your own wrapper around Json that applies the filtering.
public interface IConditionalSerializer
{
bool ShouldBeSerialized();
}
public static class FilteredSerializer
{
public static string SerializeConditional<T>(IEnumerable<T> input)
where T : IConiditionalSerializer
{
return JsonConvert.SerializeObject(input.Where(e => e.ShouldBeSerialized()));
}
}
public class Demo : IConditionalSerializer
{
public bool ShouldBeSerialized() => false;
}
You might also replace the interface with a reflection approach, but keep in mind the performance loss.
public interface IConiditionChecker
{
bool ShouldBeSerialized(object instance);
}
public class ConditionAttribute : Attribute
{
public Type ConditionChecker { get; set; }
}
public static class FilteredSerializer
{
public static string SerializeConditional(IEnumerable<object> input)
{
var matches = (from entry in input
let att = entry.GetType().GetCustomAttribute<ConditionAttribute>()
let hasChecker = att != null && att.ConditionChecker != null
let checker = hasChecker ? (IConiditionChecker)Activator.CreateInstance(att.ConditionChecker) : null
where checker.ShouldBeSerialized(entry)
select entry);
return JsonConvert.SerializeObject(matches);
}
}
[Condition(ConditionChecker = typeof(SomeChecker))]
public class Demo
{
}
Edit: Based on your comment you could do this. Only must decide wether to use opt-in or opt-out in the where-statement. It must ether be casted != null && casted.ShouldBeSerialized or what it currently says.
public interface IShouldBeSerialized
{
bool ShouldBeSerialized();
}
public static class FilteredSerializer
{
public static string SerializeConditional(IEnumerable<object> input)
{
var matches = (from entry in input
let casted = entry as IShouldBeSerialized
where casted == null || casted.ShouldBeSerialized()
select entry);
return JsonConvert.SerializeObject(matches);
}
}
public class Demo : IShouldBeSerialized
{
public bool ShouldBeSerialized()
{
return false;
}
}
If you're able to use the JSON.NET serializer, in terms of not serializing specific items within a collection, you could make the main collection non serializable, then add another filtered collection that does serialize.
public class Manager
{
[JsonIgnore]
public Employee[] Employees { get; set; }
[JsonProperty("Employees")]
public Employee[] SerializableEmployees
{
get { return Employees.Where(e => e.Name != "Bob").ToArray(); }
set { Employees = value; }
}
}
Alternatively, you could mark your class with the [JsonConverter] attribute and use a custom converter to check your condition. A similar approach that ignores a class entirely is detailed here.

Categories

Resources