domingo, 9 de noviembre de 2014

IIS Express con Sublime Text y Brackets

Intentando salir de la zona de confort una de las probables vías a explorar es cambiar de IDE. Lo cierto es que Visual Studio es un magnifico IDE y además nos presta todo tipo de ayudas y comodidades para ni pensar tener que abandonarlo, pero claro, si queremos hacer cosas distintas tendremos que probar herramientas distintas.

Algunas de las opciones a valorar como nuevo IDE podrían ser Sublime Text o Brackets (también y seguramente como opción más confiable sería WebStorm, pero es de pago y prefiero por ahora no abordarlo).

Para ambos editores (llamarlos IDE o editores de texto lo dejo a tu elección) he echado en falta la existencia de un servidor local para trabajar desde http:// en vez de desde file:///. Lógicamente en VS eso no es problema porque cuando creamos un proyecto web se crea un nuevo sitio web en IIS Express para albergar nuestra aplicación. ¿Y por qué no utilizar entonces IIS Express también para Sublime Text o Brackets? Lo cierto es que un servidor ligero, de fácil instalación a través de Web Platform Installer y además que ya conocemos…o casi… porque su principal problema es su administración o, mejor dicho, la inexistencia de una herramienta gráfica para administrarlo. Es cierto que hay intentos de dotar a IIS Express de interfaz gráfica, pero con franqueza, ninguno parece estar completamente operativo, algunos ejemplos son https://iisexpressgui.codeplex.com/ o http://iisem.codeplex.com/

En este punto lo única opción viable parece administrar IIS Express desde línea de comandos con la utilidad appcmd.exe. Para equipos de 64 bits podremos encontrarla en C:\Program Files (x86)\IIS Express.

Con appcmd podremos realizar las operaciones de administración más comunes como listar, crear, modificar o eliminar sitios, aplicaciones o directorios virtuales. En nuestro caso nos centraremos en administrar sitios, que es lo que crea VS en IIS Express cuando creamos un proyecto web.

image

La configuración de IIS Express se guarda en el fichero %USERPROFILE%\Documents\IISExpress\config\applicationhost.config, aquí y dentro de la sección sites, encontraemos tantos elementos site como sitios tengamos configurados.

Un site luce más o menos así:

        <site id="1" name="WebApplication1">

            <application applicationpool="Clr4IntegratedAppPool" path="/">

                <virtualdirectory path="/" physicalpath="C:\Users\Sergio\Visual Studio 2013\Projects\WebApplication1\WebApplication1">

                </virtualdirectory>

            </application>

            <bindings>

                <binding bindinginformation="*:65534:localhost" protocol="http">

                </binding>

            </bindings>
        </site>

Para listar todos los sitios configurados ejecutaremos el comando appcmd list site

Para crear un nuevo sitio que apunte a C:\Temp podemos ejecutar el comando
appcmd add site /name:"Temp Site" /bindings:http/*:65533:locahost /physicalPath:"C:\Temp"

El puerto podría ser cualquiera mayor que el 1024, puedes ver esta y otras limitaciones en Running IIS Express without Administrative Privileges.

Para arrancar ahora nuestro nuevo sitio ejecutar el comando
iisexpress /site:"Temp Site"

En este punto la consola quedará suspendido mostrando un traza de las peticiones entrantes, para detener el sitio bien podemos pulsar “q” en la consola, o detenerlo desde el icono del área de notificaciones de Windows que agrega IIS Express.

IIS Express también permite levantar un sitio directamente desde una ubicación en disco con el comando iisexpress /path:”C:\Temp”. Esto levantará el servidor en el puerto por defecto 8080, pero lógicamente podríamos haber especificado el puerto también en el comando. Aquí puedes ver toda la documentación de iisexpress. Lo importante de este último comando es que no ha sido necesario registrar previamente en la configuración el sitio, con lo que levantarlo ha sido muy rápido y directo.

Ahora que ya tenemos un servidor web sirviendo contenido de una carpeta en disco ¿Cómo encaja todo esto con Sublime Text y Brackets?

Con Brackets es muy sencillo, simplemente File > Project Settings… y allí especificar la dirección de “Live Preview Base URL”, en nuestro caso http://localhost:8080/

Cabe mencionar que utilizando nuestro propio servidor en Brackets, algunas características del Live Preview no estarán disponibles… en realidad la experiencia del Live Preview merma bastante, puedes encontrar toda la información en https://github.com/adobe/brackets/wiki/How-to-Use-Brackets en el apartado Live Preview > Using your own backend.

Con Sublime Text primero hay que instalar el paquete SideBarEnhancements (paquete must-have) que agrega al menú contextual de una carpeta abierta en Sublime un buen puñado de opciones, entre las cuales está Project > Edit Preview URLs… Al pulsar en esta opción nos abrirá un fichero vacío llamado SideBarEnhancements.json (que se guardará %APPDATA%\Sublime Text 3\Settings) donde escribiremos lo siguiente:

{

    "C:\Temp": {

        "url_testing":"http://localhost:8080/",

        "url_production":""

    }
}

Ahora al pulsar la opción “Open In Browser” en el menú contextual del fichero en el Side Bar (ojo que no funcionará si pulsamos “Open In Browser” en el menú contextual desde el editor del fichero), nos abrirá el navegador predeterminado en la url especificada en url_testing. Más información de esta característica en https://github.com/titoBouzout/SideBarEnhancements

Después de todo esto, la sensación que tengo es que abandonar VS va a ser difícil :)

Un saludo!

domingo, 26 de octubre de 2014

OutputCache con MvcDonutCaching y MongoDB

Si hablamos de cache en una aplicación ASP.NET MVC, una de las primeras opciones a valorar es utilizar el atributo OutputCache para cachear la salida de los controladores.

Aunque a priori OutputCache podría satisfacer nuestros requerimientos, lo cierto es que adolece de algunos problemas que hacen que uso no sea todo lo satisfactorio que cabría esperar.

Muchos de estos problemas están relacionados con el uso del atributo en acciones hijas (las que son invocadas con Html.Action). En concreto, encontramos las siguientes limitaciones cuando hablamos de acciones hijas:

  • Sólo soportan las propiedades Duration, VarByCustom y VarByParam. Lo más relevante aquí es que no soporta la propiedad CacheProfile y, por ende, no podemos establecer una política de expiración de caché a través del web.config.
  • Ignora por completo el valor del atributo enableOutputCache de la sección outputCache del web.config. Esto significa que algo tan habitual como jugar con este valor para activar o desactivar la cache en desarrollo, no funcionará con acciones hijas.

En cualquier caso, no sólo las acciones hijas presentan limitaciones. Si pensamos en qué querríamos cachear en el contexto de la salida de ASP.NET MVC, encontraríamos fácilmente los siguientes escenarios:

  • Cachear la página entera (Full page caching). Soportado puesto que cachear una acción padre engloba cachear todo el resultado del método de acción.
  • Cachear pequeños fragmentos de la página (también llamado cachear el agujero del donut, Donut hole caching). Igualmente soportado a través de la cache de acciones hijas.
  • Cachear la página entera excepto pequeños fragmentos (lo que se llama cachear el donut pero no el agujero, Donut caching). Esto NO está soportado por el proveedor de serie aun cuando, si recordamos, se podía hacer por ejemplo en WebForms a través del control Substitution.

Otro problema grave que nos encontramos es que invalidar la cache no es algo que esté muy conseguido. Sólo tenemos disponible un triste método Response.RemoveOutputCacheItem(path) que sólo funciona para acciones padre y además no permite invalidar de una sola vez todas las versiones de una página, es decir, tendremos que llamar a este método tantas veces como distintas versiones de path haya que incluyan parámetros como segmentos en el path.

El cómo solventar todos estos problemas de un plumazo es sencillo, utilizando el paquete MvcDonutCaching

Este paquete nos brinda un nuevo atributo llamado DonutOutputCache, que sustituye por completo a OutputCache y hace que todo funcione como debería funcionar. Ahora no hay distinción entre acciones padres e hijas, todo funciona (las acciones hijas ya pueden utilizar CacheProfile, están al tanto de enableOutputCache, etc.) y además nos permite cachear el donut (Donut caching). En este sentido, MvcDonutCaching lo pone muy fácil porque agrega sobrecargas al helper Html.Action para poder especificar si la llamada a la acción hija debe excluirse de la cache de su padre, por ejemplo en vez de escribir @Html.Action(“Acción”), si escribimos @Html.Action(“Accion”, true) ya estaríamos cacheando el donut.

MvcDonutCaching además también permite invalidar la caché de forma precisa con los métodos RemoveItem y RemoveItems de la clase OutputCacheManager.

En este punto ya somos más felices, pero si cabe (y si es necesario) podríamos serlo un poco más. Esto es debido a que a partir de .NET 4.0 se introdujo la posibilidad de crear nuestro propio proveedor de cache personalizado y configurarlo vía web.config. Siendo así podemos hacer que nuestra cache persista en un almacenamiento compartido permitiendo que varios frontales compartan la misma cache e incluso que la cache sobreviva a reinicios de la aplicación. En mi caso he optado por crear un proveedor de cache basado en MongoDB.

Para ello hay que crear una clase que herede de OutputCacheProvider, implemente los métodos necesarios y configurar el nuevo proveedor vía web.config.

El código del proveedor está disponible en github https://github.com/panicoenlaxbox/MvcDonutCaching.MongoDBOutputCache y también hay disponible paquete de Nuget para instalarlo de forma sencilla http://www.nuget.org/packages/MvcDonutCaching.MongoDBOutputCache/

Resumiendo, los pasos para configurar el cache como hemos explicado son:

1. Instalar el paquete MvcDonutCaching

2. Instalar el paquete MvcDonutCaching.MongoDBOutputCache

3. Configurar el proveedor en el web.config

    <caching>

      <outputCache defaultProvider="AspNetMongoDB">

        <providers>

          <add name="AspNetMongoDB" type="MongoDBOutputCache.MongoDBOutputCacheProvider, MongoDBOutputCache" />

        </providers>

      </outputCache>

    </caching>

4. Agregar las siguientes claves en AppSettings (lógicamente apuntando a la instancia de MongoDB que quieras utilizar – con o sin autenticación – y especificando la base de datos que quieres utilizar):

    <add key="MongoDBOutputCacheProviderConnectionString" value="mongodb://admin:admin@localhost/aspnet" />

    <add key="MongoDBOutputCacheProviderCollection" value="OutputCache" />

Y ahora sí, tenemos una cache de salida de ASP.NET MVC como hubiésemos querido en un principio!

Un saludo!

jueves, 16 de octubre de 2014

Conversiones de usuario en C#

Las conversiones de usuario permiten definir conversiones personalizadas en C#, tanto explícitas como implícitas, desde un tipo origen a un tipo destino.

Lo cierto es que no suelo utilizarlas mucho (por no decir rara y extrañísima vez), pero como siempre estamos buscando una oportunidad para poner en juego características del lenguaje, recientemente me he encontrado con un caso que he resuelto con una conversión de usuario.

Para poner en situación, primero veremos un ejemplo de cómo son y qué hacen.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var age = 38;
            Person person = age; // implícita de int a Person
            Console.WriteLine(person.Age); // 38 
            age = (int)person; // explícita, necesario cast, de Person a int            
            Console.WriteLine(age); // 38
            Console.ReadKey();
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public static implicit operator Person(int age)
        {
            return new Person() { Age = age };
        }

        public static explicit operator int(Person person)
        {
            return person.Age;
        }
    }
}
Como se puede ver en el ejemplo, la sintaxis de una conversión de usuario es:
public static {implicit|explicit} operator TipoDestino (TipoOrigen identificador) 
{
 // do something…
return TipoDestino;
}
Las restricciones que se aplican a una conversión de usuario son:

  • Sólo son válidas para clases y estructuras.
  • TipoDestino y TipoOrigen tienen que ser de distinto tipo.
  • No puede haber una relación de herencia entre TipoDestino y TipoOrigen
  • TipoDestino y TipoOrigen no pueden ser de un tipo interface ni object.
  • No se puede declarar la misma conversión tanto de forma implícita como explícita
  • No se puede usar el operador is
  • No se puede usar el operador as

Después de la teoría y ya sabiendo que es una conversión de usuario, el ejemplo donde he aplicado una conversión de usuario es en un método que devuelve si se puede o no enviar un pedido. El problema está en que si no se puede enviar el pedido, querría saber también el motivo. Es decir, el siguiente código no me servía.
    public class Order
    {
        public string CustomerId { get; set; }
        public DateTime? DeliveryDate { get; set; }

        public bool CanBeSent()
        {
            if (string.IsNullOrEmpty(CustomerId))
            {
                return false;
            }
            if (DeliveryDate == null)
            {
                return false;
            }
            return true;
        }
    }
Para solucionar el problema expuesto y haciendo uso de las conversiones de usuario, podemos hacer que el método devuelve un tipo complejo indicando si es o no posible enviar el pedido y, en caso negativo, también especificar el motivo. Además, el valor devuelto se puede trabajar tanto como un booleano como un tipo complejo cuando sea necesario.
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var order = new Order();
            var result = order.CanBeSent();
            if (result)
            {
                Console.WriteLine("Se puede enviar el pedido");
            }
            else
            {
                Console.WriteLine("No se puede enviar el pedido");
                Console.WriteLine(result.Reason);
            }
            Console.ReadKey();
        }
    }

    public class CanBeResult
    {
        public bool Value { get; set; }
        public string Reason { get; set; }

        public CanBeResult(bool value)
        {
            Value = value;
        }

        public CanBeResult(bool value, string reason)
            : this(value)
        {
            Reason = reason;
        }

        public static implicit operator bool(CanBeResult result)
        {
            return result.Value;
        }

        public static implicit operator CanBeResult(bool value)
        {
            return new CanBeResult(value);
        }
    }

    public class Order
    {
        public string CustomerId { get; set; }
        public DateTime? DeliveryDate { get; set; }

        public CanBeResult CanBeSent()
        {
            if (string.IsNullOrEmpty(CustomerId))
            {
                return new CanBeResult(false, "El pedido no tiene cliente");
            }
            if (DeliveryDate == null)
            {
                return new CanBeResult(false, "El pedido no tiene fecha de entrega");
            }
            return true;
        }
    }
}
Un saludo!

lunes, 29 de septiembre de 2014

Configuración de seguridad en MongoDB

Después de aceptar MongoDB como animal de compañía y ver como configurarlo a nivel Newbie, quedó pendiente algo tan básico y necesario como establecer unos ciertos mínimos de seguridad para que nuestra instancia de Mongo no sea un “pasen y vean”.

En mi opinión (y después de ver las distintas opciones de configuración disponibles) he agregado los siguientes parámetros al fichero de configuración.
Básicamente activamos la autenticación de usuarios, sólo permitimos conexiones desde la ip local (ya que en nuestro caso tanto IIS como Mongo están en el mismo servidor), cambiamos el puerto por defecto (para no dar pistas) y deshabilitamos cualquier acceso vía http tanto a la parte de administración como a la API Rest. De este modo el fichero .config final quedaría de la siguiente forma (basado en formato YAML).
storage: dbPath: C:\mongodb\data\db systemLog: destination: file path: C:\mongodb\log\mongodb.log logAppend: true security: authorization: "enabled" net: bindIp: "127.0.0.1" port: XXXXX #27017 http: enabled: false RESTInterfaceEnabled: false
Algunas de estas opciones tienen valores predeterminados que ya coinciden con lo aquí expuesto, pero prefiero ser explícito y así olvidarme de ello.

Después de esto y para una bd de ejemplo llamada aspnet, sólo restar comentar como agregar usuarios y activar la autenticación.

Los pasos a seguir serían:
  • Crear usuario admin con el role “root” en la bd admin
  • Crear usuario aspnet con el role “dwOwner” en la bd aspnet
  • Modificar el fichero .config con la configuración propuesta
  • Reiniciar el servicio de Mongo
> use admin switched to db admin > db.createUser({user: "admin", pwd: "1234", roles: ["root"]}) Successfully added user: { "user" : "admin", "roles" : [ "root" ] } > use aspnet switched to db aspnet > db.createUser({user: "aspnet", pwd: "1234", roles: ["dbOwner"]}) Successfully added user: { "user" : "aspnet", "roles" : [ "dbOwner" ] } >
Lógicamente el nombre de usuario podría ser cualquiera (yo he utilizado admin y aspnet que coinciden con los nombres de las bases de datos sólo por sencillez).

Finalmente, para conectar vía C# tenemos que utilizar una cadena de conexión que incluye la autenticación y base de datos por defecto.

Asumiendo que la cadena de conexión debería cumplir mongodb://usuario:contraseña@host:puerto/bd, en nuestro ejemplo quedaría como mongodb://aspnet:1234@localhost:XXXXX/aspnet
Y recuerda (porque me ha pasado), que si incluyes el caracter @ en el nombre de usuario o contraseña, tendrás que escaparlo con %40.

Un saludo!

lunes, 18 de agosto de 2014

Configurar MongoDB nivel Newbie

En nuestro último proyecto hemos apostado por MongoDB para persistir los datos de un proveedor personalizado de TempData en ASP.NET MVC.

Lo cierto es que instalar MongoDB no es complicado, pero cada vez que hay que hay instalarlo y configurarlo en un equipo nuevo en la oficina, se invierte un tiempo valioso en volver a leer la documentación y ponerlo en línea. Es por ello que en este post veremos como ponerlo en funcionamiento siguiendo unos sencillos pasos.

Una vez descargado e instalado lo siguiente será configurarlo.

Yo personalmente sólo configuro lo siguiente:

  • Ruta a las bases de datos.
  • Creación de un fichero de log.
  • Instalación como un servicio.

Aunque podemos arrancar desde la línea de comandos mongod.exe con los parámetros --dbpath y --logpath, parece más útil crear un fichero de configuración donde establecer estos valores y algún otro.

La sintaxis del fichero de configuración puede ser YAML (a partir de la versión 2.6) o estilo INI (versiones anteriores pero todavía soportado en las versión actual por compatibilidad).

Si eliges YAML ten en cuenta que no le gustan los tabuladores y que espera encontrar espacios.

El fichero de configuración en YAML sería como sigue:

storage:

  dbPath: C:\MongoDB\data\db #Por defecto C:\data\db

systemLog:

  destination: file

  path: C:\MongoDB\log\mongo.log

  logAppend: true

En formato INI sería así:

dbpath = C:\MongoDB\data\db

logpath = C:\MongoDB\log\mongodb.log

logappend = true

En ambos ficheros de configuración se está configurando:

  • La ruta a las bases de datos
  • La ruta al fichero de log
  • Se está especificando que el fichero de log no sea eliminado cada vez que mongod arranque de nuevo.

Como podrás imaginar hay un montón de configuración extra que puede especificarse, bien desde la línea de comandos bien desde un fichero de configuración. Cabe mencionar también que todos los directorios que referenciemos en nuestro fichero de configuración tienen que estar creados. Es decir, mongod no creará ningún directorio por nosotros.

Sea cual sea el formato elegido, ahora podremos arrancar nuestra instancia de mongod con el siguiente comando:

mongod --config ruta_fichero_configuración

En nuestro caso, el fichero de configuración lo guardamos en C:\MongoDB\config.conf.

En este punto ya podríamos arrancar MongoDB desde la línea de comandos, pero para ponerlo aun más sencillo (y porque en un servidor de producción no parece adecuado), veremos ahora como crear un servicio que arranque MongoDB automáticamente. La verdad es que es muy sencillo y mongod viene ya preparado para que la tarea sea coser y cantar.

Lo único que hay que hacer es abrir un cmd con permisos de administrador y escribir lo siguiente:

mongod --config "C:\MongoDB\config.conf" --install

Y ya está, MongoDB instalado como un servicio!

Lo último que tienes que hacer es arrancar el servicio y ejecutar mongo.exe desde la línea de comandos o utilizar un cliente gráfico como Robomongo (gracias @_rubenfa) o MongoVUE.

Un saludo!

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!