domingo, 22 de junio de 2014

TempData basado en MongoDB (II)

En un anterior post vimos como crear un proveedor de TempData basado en MongoDB.

Lo cierto es que adolecía de un gran problema y es que no se podían guardar tipos anónimos en el proveedor.

Rápidamente llegó al rescate Luis Ruiz Pavón y me enseño como crear un serializador personalizado para MongoDB que sí permitiera guardar tipos anónimos (en realidad el problema no era guardar tipos anónimos sino la posterior deserialización). En este gist puede ver la solución y la conversación.

Por mi parte, he añadido algo de código para evitar, en la medida de lo posible, recuperar objetos del tipo JObject y sí recuperarlos directamente a un tipo conocido. La única vez que tendremos que lidiar con JObject es con los tipos anónimos porque no hay tipo!

Para establecer valores, nada nuevo…

TempData["cadena"] = "Sergio";

TempData["numero"] = 38;          

TempData["tupla"] = new Tuple<string, int>("key1", 1);

TempData["anonimo"] = new

{

    Nombre = "Sergio",

    Edad = 38,

    Apellidos = new { Primero = "León" }

};

Y para recuperarlos también muy sencillo, excepto la salvedad de tratar a JObject (tipo anónimo deserializado) como un tipo dinámico:

var cadena = (string)TempData["cadena"];

var numero = (long)TempData["numero"];

var tupla = (Tuple<string, int>)TempData["tupla"];

dynamic anonimo = TempData["anonimo"];

var nombre = anonimo.Nombre;

var primerApelllido = anonimo.Apellidos.Primero;

Finalmente, el refrito entre el código de Luis y mío es el siguiente (el proveedor de TempData no cambia y es el mismo que el post anterior). Si te fijas, casi todo el código intenta gestionar las diferencias (que las hay) entre MongoDB y Json.NET.

    public class TempDataDocument

    {

        public ObjectId Id { get; set; }

        public Guid UniqueId { get; set; }

        public string Key { get; set; }

        [BsonSerializer(typeof(MongoDBCustomSerializer))]

        public object Value { get; set; }

    }

 

    public class MongoDBCustomSerializer : IBsonSerializer

    {

        public object Deserialize(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)

        {

            return Deserialize(bsonReader, nominalType, null, options);

        }

 

        public object Deserialize(

            MongoDB.Bson.IO.BsonReader bsonReader,

            Type nominalType,

            Type actualType,

            IBsonSerializationOptions options)

        {

            if (bsonReader.GetCurrentBsonType() != BsonType.Document)

            {

                throw new Exception("Not document");

            }

 

            var bsonDocument = BsonSerializer.Deserialize(bsonReader, typeof(BsonDocument), options) as BsonDocument;

            var json = Regex.Replace(bsonDocument.ToJson(), @"ObjectId\((.[a-f0-9]{24}.)\)", (m) => m.Groups[1].Value);

            json = GetFixedDeserializedJson(json);

            return JsonConvert.DeserializeObject<object>(json, new JsonSerializerSettings()

            {

                TypeNameHandling = TypeNameHandling.Objects

            });

        }

 

        public IBsonSerializationOptions GetDefaultSerializationOptions()

        {

            return new DocumentSerializationOptions();

        }

 

        public void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)

        {

            var json = (value == null) ? "{}" : JsonConvert.SerializeObject(value, new JsonSerializerSettings()

            {

                TypeNameHandling = TypeNameHandling.Objects

            });

            json = GetFixedSerializedJson(json);

            BsonDocument document = BsonDocument.Parse(json);

            BsonSerializer.Serialize(bsonWriter, typeof(BsonDocument), document, options);

        }

 

        private string GetFixedSerializedJson(string json)

        {

            // En MongoDB un nombre de propiedad no puede empezar por $ ni por .

            json = json.Replace("$type", "_type");

            if (!json.StartsWith("{"))

            {

                // Si la serialización es un tipo simple, añadir nombre de propiedad para que no falle BsonDocument.Parse

                json = string.Format("{{\"_value\":{0}}}", json);

            }

            return json;

        }

 

        private string GetFixedDeserializedJson(string json)

        {

            string pattern;

            if (Regex.IsMatch(json, @"""_type"""))

            {

                // No intentar deserializar a un tipo anónimo

                pattern = @"""_type""\s*:\s*""<>f__AnonymousType[\s\S]+?""\s*,{0,1}";

                json = Regex.Replace(json, pattern, "");

                // Restaurar $type para que la deserialización de Json.NET funcione

                json = json.Replace("_type", "$type");

            }

            // Si la deserialización es de un tipo simple, devolver sólo el valor

            pattern = @"^\{\s*""_value""\s*:\s*\""{0,1}"; // { "_value" : "

            var start = Regex.Match(json, pattern);

            if (start.Success)

            {

                pattern = @"""{0,1}\s*\}$"; // " }

                var end = Regex.Match(json, pattern);

                // No todos los tipos simples van entre comillas dobles

                var doubleQuote = end.Value.StartsWith("\"");

                var value = json.Substring(start.Length, end.Index - start.Length);

                json = string.Format("{0}{1}{2}", doubleQuote ? "\"" : "", value, doubleQuote ? "\"" : "");

            }

            return json;

        }

    }

Un saludo!