diff --git a/src/MongoDB.Bson/MongoDB.Bson.csproj b/src/MongoDB.Bson/MongoDB.Bson.csproj
index c7d52a029a5..e9cba0a74a3 100644
--- a/src/MongoDB.Bson/MongoDB.Bson.csproj
+++ b/src/MongoDB.Bson/MongoDB.Bson.csproj
@@ -188,6 +188,7 @@
+
diff --git a/src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs b/src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs
new file mode 100644
index 00000000000..d7abc634ad9
--- /dev/null
+++ b/src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs
@@ -0,0 +1,89 @@
+namespace MongoDB.Bson.Serialization
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Represents a BSON serialization provider which defines that some serializable types should be treated
+ /// as BSON class maps.
+ ///
+ ///
+ /// Argumented types to be forced as BSON class maps can be either concrete or also base class and interface ones.
+ ///
+ /// This serialization provider is useful when a class may implement a collection interface (for example, )
+ /// because the domain requires the class to act as a collection, but in terms of serialization, it must be serialized as a regular
+ /// POCO class.
+ ///
+ ///
+ /// For example, given the following class:
+ ///
+ ///
+ /// public interface ISomeInterface { }
+ /// public class SomeImpl : ISomeInterface { }
+ ///
+ ///
+ /// This provider can be configured both to force any SomeImpl to be treated as
+ /// BSON class map and also any implementation of ISomeInterface can be configured as a
+ /// forced type to let any implementation be serialized as a BSON class map:
+ ///
+ ///
+ /// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl));
+ ///
+ /// // or
+ ///
+ /// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(ISomeInterface));
+ ///
+ /// // or even both
+ ///
+ /// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl), typeof(ISomeInterface));
+ ///
+ ///
+ public sealed class ForceAsBsonClassMapSerializationProvider : BsonSerializationProviderBase
+ {
+ private readonly HashSet _forcedTypes;
+
+ ///
+ /// Constructor to give forced types as a type array.
+ ///
+ /// The whole types to be forced as BSON class maps
+ public ForceAsBsonClassMapSerializationProvider(params Type[] forcedTypes)
+ : this((IEnumerable)forcedTypes)
+ {
+ }
+
+ ///
+ /// Constructor to give forced types as a sequence of types.
+ ///
+ /// The whole types to be forced as BSON class maps
+ public ForceAsBsonClassMapSerializationProvider(IEnumerable forcedTypes)
+ {
+ if (forcedTypes == null || forcedTypes.Count() == 0)
+ throw new ArgumentException("Cannot configure a forced BSON class map serialization provider which contains no types to be forced as BSON class maps", "forcedTypes");
+ if (!forcedTypes.All(type => type.IsClass || type.IsInterface))
+ throw new ArgumentException("Forced types must be classes or interfaces");
+
+ _forcedTypes = new HashSet(forcedTypes);
+ }
+
+ ///
+ /// Gets a set of types to be forced as BSON class maps during their serialization.
+ ///
+ public HashSet ForcedTypes { get { return _forcedTypes; } }
+
+ ///
+ public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
+ {
+ // Forcing can happen either if type to be serialized is within forced type set, or if one of forced types
+ // is implemented or inherited by the given type.
+ if (ForcedTypes.Contains(type) || ForcedTypes.Any(forcedType => forcedType.IsAssignableFrom(type)))
+ {
+ BsonClassMapSerializationProvider bsonClassMapProvider = new BsonClassMapSerializationProvider();
+
+ return bsonClassMapProvider.GetSerializer(type);
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
index 5914baa67d8..559b10d5050 100644
--- a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
+++ b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
@@ -142,6 +142,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
}
}
+ var docDictionaryImpl = document as IDictionary;
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
var allMemberMaps = _classMap.AllMemberMaps;
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex;
@@ -191,6 +192,16 @@ public TClass DeserializeClass(BsonDeserializationContext context)
}
memberMapBitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
}
+ else if(docDictionaryImpl != null)
+ {
+ // If the document itself implements IDictionary, document to serialize could
+ // contain extra elements as document properties...
+ docDictionaryImpl.Add
+ (
+ elementName,
+ BsonTypeMapper.MapToDotNetValue(BsonValueSerializer.Instance.Deserialize(context))
+ );
+ }
else
{
if (elementName == discriminatorConvention.ElementName)
@@ -202,6 +213,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
if (extraElementsMemberMapIndex >= 0)
{
var extraElementsMemberMap = _classMap.ExtraElementsMemberMap;
+
if (document != null)
{
DeserializeExtraElementMember(context, document, elementName, extraElementsMemberMap);
@@ -564,6 +576,7 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
var bsonWriter = context.Writer;
var remainingMemberMaps = _classMap.AllMemberMaps.ToList();
+ HashSet classElementNames = new HashSet(remainingMemberMaps.Select(map => map.MemberName));
bsonWriter.WriteStartDocument();
@@ -591,14 +604,39 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
SerializeMember(context, document, memberMap);
}
+
+ // It might happen that a class implements IDictionary, so
+ // the keys can be also extra elements.
+ SerializeDictionary(context, document as IDictionary, classElementNames);
+
bsonWriter.WriteEndDocument();
}
+ private void SerializeDictionary(BsonSerializationContext context, IDictionary extraElements, HashSet classElementNames = null)
+ {
+ if (extraElements != null && extraElements.Count > 0)
+ {
+ foreach (var key in classElementNames == null ?
+ extraElements.Keys : extraElements.Keys.Where(key => !classElementNames.Contains(key)))
+
+ {
+ context.Writer.WriteName(key);
+ var value = extraElements[key];
+ var bsonValue = BsonTypeMapper.MapToBsonValue(value);
+ BsonValueSerializer.Instance.Serialize(context, bsonValue);
+ }
+ }
+ }
+
private void SerializeExtraElements(BsonSerializationContext context, object obj, BsonMemberMap extraElementsMemberMap)
{
var bsonWriter = context.Writer;
var extraElements = extraElementsMemberMap.Getter(obj);
+
+ if (extraElements == null)
+ extraElements = obj as IDictionary;
+
if (extraElements != null)
{
if (extraElementsMemberMap.MemberType == typeof(BsonDocument))
@@ -612,14 +650,7 @@ private void SerializeExtraElements(BsonSerializationContext context, object obj
}
else
{
- var dictionary = (IDictionary)extraElements;
- foreach (var key in dictionary.Keys)
- {
- bsonWriter.WriteName(key);
- var value = dictionary[key];
- var bsonValue = BsonTypeMapper.MapToBsonValue(value);
- BsonValueSerializer.Instance.Serialize(context, bsonValue);
- }
+ SerializeDictionary(context, (IDictionary)extraElements);
}
}
}