Yo soy tu guardían

El martes doce de Septiembre de este mismo año mi gatito(@eiximenis) a eso de las de las 2:21 PM publico esto  en twitter Uso de modelos dynamic en #aspnetmvc, solamente con leer el titulo era evidente que expectación iba a crear. Con lo cual sin  leerlo le di al botón de Retwittear y un comentario diversión garantizada.

Era evidente que todo el mundo estaba deseando una solución al engorro de los dto’s, automapper y demás hierbas que no hacen más que hacernos menos productivos y convertir nuestro trabajo en aburrido, o lo que es peor en vagos que devuelven toda la entidad para pintar en una vista dos campos, pero claro a eso últimamente le llaman “Optimización temprana” un antipatrón que a mi me gusta y que lo he convertido yo en el patrón “Engañabobos” básicamente dice “que le des al usuario lo que sus ojos pueden ver y no más”. Será que llamas antipatrón a no saber como funciona una base de datos.

Siguiendo un poco con la historia, mi otro gran amigo Sergio León hizo un comentario en el post Modelos de vista dinámicos en asp.net mvc invocando a un tal “Pedro” con esta frase.

“Por cierto, estoy esperando a Pedro por aquí, me encantaría saber también su opinión xD”

Ese Pedro no es otro que un servidor y le respondí, con una pincelada de lo que va a ser este post y algún que otro comentario en twiter.

Ya tienes mi respuesta.

Una pincelada de mis investigaciones

Mi frustración al darme cuenta

Es evidente que mi amigo Sergio estaba durmiendo la siesta por las horasSonrisa  y hoy o cuando pueda empezará a leer, pero como he dicho que es un gran amigo le voy a ayudar a ver cual era el sentido de mis comentarios.

Hace aproximadamente 20 años que estoy trabajando con herramientas Microsoft y me parecen excelentes, ahora me parece bastante menos excelente que se generen cosas por mí y menos que le pongas el modificador de acceso Internal.

YO ME QUIERO EQUIVOCAR Y NO QUIERO GUARDIANES

Sí señores este párrafo de Eduard tiene su miga.

Pero… no es oro todo lo que reluce. Si cambias la directiva a @model dynamic y vuelves al código donde usábamos un objeto anónimo… Obtendrás de nuevo el error ‘object’ does not contain a definition for ‘Name’. Es decir, a pesar de que declaras el tipo de modelo como dinámico en la vista, parece que para ASP.NET MVC el modelo sigue siendo un object.

Eduard la respuesta es no y si, puesto que en definitiva todo es “object”  el problema es que cuando se crearon los tipos anónimos en c# a algún iluminado no se le ocurrió otra cosa que marcar la clase como “internal”, os acompaño una muestra de código IL.

   1: .class private auto ansi sealed beforefieldinit '<>f__AnonymousType0`2'<'<Id>j__TPar','<Name>j__TPar'>

   2:        extends [mscorlib]System.Object

   3: {

   4:   .custom instance void [mscorlib]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 1D 5C 7B 20 49 64 20 3D 20 7B 49 64 7D 2C   // ...{ Id = {Id},

   5:                                                                                                  20 4E 61 6D 65 20 3D 20 7B 4E 61 6D 65 7D 20 7D   //  Name = {Name} }

   6:                                                                                                  01 00 54 0E 04 54 79 70 65 10 3C 41 6E 6F 6E 79   // ..T..Type.<Anony

   7:                                                                                                  6D 6F 75 73 20 54 79 70 65 3E )                   // mous Type>

   8:   .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 

   9: } // end of class '<>f__AnonymousType0`2'

Después unos pocos meses y años después otros Illuminati al hacer el compilador de las vistas de MVC(si ese que transforma Razor a código).

No se le ocurrió pensar que desde el 2005 “Framework 2.0” existía la posibilidad de marcar un ensamblado con este atributo InternalsVisibleToAttribute, esto lo que hace es que desde nuestra dll podamos exponer las clases internal como public o por lo menos que podamos acceder desde otra dll, si esto existe porque no me dejas marcar cuando voy a compilar el proyecto donde defino mis consultas linq que devuelven tipos anonimos y dinamicos “generar anonimos como public”, si tan sencillo como hacer esto.

GENERAR PUBLICOS

Simplemente con eso no necesito ni ExpandoObject ni la solución que voy a aportar como alternativa a Eduard DynamicObject, que a la postre no es más que eso, otra alternativa que evita tener que iterar dos veces por los datos, pero eso para el finalSonrisa.

Otra cosa que quiero transmitir es lo que realmente hacen nuestras vistas razor cuando se compilan, para ello te invito a crear un proyecto MVC4 y antes borra el contenido de esta carpeta.

C:WindowsMicrosoft.NETFrameworkv4.0.30319Temporary ASP.NET Filesroot

Sí, es en esa carpeta donde se generan todas las dll’s de las vistas que a la postre son las que no pueden acceder al tipo internal de otra dll y causantes del error y posteriores inventos.

En la versión anterior(creo recordar) que  se creaba una dll por cada vista, en la versión actual han hecho un esfuerzo y se crea una por cada carpeta.

Vamos con las vistas y el resultado final del código generado.

 

   1: @model dynamic

   2: @foreach (dynamic item in Model)

   3: {   

   4:     <br />

   5:     <span>@item.Id</span>

   6:     <br />

   7:     <span>@item.Name</span>

   8: }

Está sencilla vista genera un archivo csharp con el siguiente código.

   1: #pragma checksum "C:JWTMvcApplication6MvcApplication6ViewsHomeContact.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "8C64704206437FFED454D2F32D27976E1F09CE01"

   2: //------------------------------------------------------------------------------

   3: // <auto-generated>

   4: //     Este código fue generado por una herramienta.

   5: //     Versión de runtime:4.0.30319.18052

   6: //

   7: //     Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si

   8: //     se vuelve a generar el código.

   9: // </auto-generated>

  10: //------------------------------------------------------------------------------

  11:  

  12: namespace ASP {

  13:     using System;

  14:     using System.Collections.Generic;

  15:     using System.IO;

  16:     using System.Linq;

  17:     using System.Net;

  18:     using System.Web;

  19:     using System.Web.Helpers;

  20:     using System.Web.Security;

  21:     using System.Web.UI;

  22:     using System.Web.WebPages;

  23:     using System.Web.Mvc;

  24:     using System.Web.Mvc.Ajax;

  25:     using System.Web.Mvc.Html;

  26:     using System.Web.Optimization;

  27:     using System.Web.Routing;

  28:     using WebMatrix.Data;

  29:     using WebMatrix.WebData;

  30:     using Microsoft.Web.WebPages.OAuth;

  31:     using DotNetOpenAuth.AspNet;

  32:     

  33:     

  34:     public class _Page_Views_Home_Contact_cshtml : System.Web.Mvc.WebViewPage<dynamic> {

  35:         

  36: #line hidden

  37:         

  38:         public _Page_Views_Home_Contact_cshtml() {

  39:         }

  40:         

  41:         protected ASP.global_asax ApplicationInstance {

  42:             get {

  43:                 return ((ASP.global_asax)(Context.ApplicationInstance));

  44:             }

  45:         }

  46:         

  47:         public override void Execute() {

  48:             

  49:             #line 2 "C:JWTMvcApplication6MvcApplication6ViewsHomeContact.cshtml"

  50:  foreach (dynamic item in Model)

  51: {   

  52:  

  53:             

  54:             #line default

  55:             #line hidden

  56: BeginContext("~/Views/Home/Contact.cshtml", 56, 12, true);

  57:  

  58: WriteLiteral("    <br />rn");

  59:  

  60: EndContext("~/Views/Home/Contact.cshtml", 56, 12, true);

  61:  

  62: BeginContext("~/Views/Home/Contact.cshtml", 68, 10, true);

  63:  

  64: WriteLiteral("    <span>");

  65:  

  66: EndContext("~/Views/Home/Contact.cshtml", 68, 10, true);

  67:  

  68: BeginContext("~/Views/Home/Contact.cshtml", 79, 7, false);

  69:  

  70:             

  71:             #line 5 "C:JWTMvcApplication6MvcApplication6ViewsHomeContact.cshtml"

  72:      Write(item.Id);

  73:  

  74:             

  75:             #line default

  76:             #line hidden

  77: EndContext("~/Views/Home/Contact.cshtml", 79, 7, false);

  78:  

  79: BeginContext("~/Views/Home/Contact.cshtml", 86, 9, true);

  80:  

  81: WriteLiteral("</span>rn");

  82:  

  83: EndContext("~/Views/Home/Contact.cshtml", 86, 9, true);

  84:  

  85: BeginContext("~/Views/Home/Contact.cshtml", 95, 12, true);

  86:  

  87: WriteLiteral("    <br />rn");

  88:  

  89: EndContext("~/Views/Home/Contact.cshtml", 95, 12, true);

  90:  

  91: BeginContext("~/Views/Home/Contact.cshtml", 107, 10, true);

  92:  

  93: WriteLiteral("    <span>");

  94:  

  95: EndContext("~/Views/Home/Contact.cshtml", 107, 10, true);

  96:  

  97: BeginContext("~/Views/Home/Contact.cshtml", 118, 9, false);

  98:  

  99:             

 100:             #line 7 "C:JWTMvcApplication6MvcApplication6ViewsHomeContact.cshtml"

 101:      Write(item.Name);

 102:  

 103:             

 104:             #line default

 105:             #line hidden

 106: EndContext("~/Views/Home/Contact.cshtml", 118, 9, false);

 107:  

 108: BeginContext("~/Views/Home/Contact.cshtml", 127, 9, true);

 109:  

 110: WriteLiteral("</span>rn");

 111:  

 112: EndContext("~/Views/Home/Contact.cshtml", 127, 9, true);

 113:  

 114:             

 115:             #line 8 "C:JWTMvcApplication6MvcApplication6ViewsHomeContact.cshtml"

 116: }

 117:             

 118:             #line default

 119:             #line hidden

 120:         }

 121:     }

 122: }

Dónde podemos ver que heredamos de WebViewPage<TModel> y el parametro TModel es dynamic, pero aún no hemos acabado puesto que ese dynamic vamos a ver como se convierte object, creo que al igual que cualquier dinamico y a la postre dar la razón a EduardSonrisa.

Para ello con una herramienta como ILSpy .NET Decompiler, vamos a abrir las dll’s que se generan por cada carpeta de vistas de nuestro proyecto MVC4 y localizar la vista en mi caso _Page_Views_Home_Contact_cshtm y centrarnos en dos puntos.

1. Nuestro TModel ya no es dynamic es object(Eduard que listo eres).

2. Es digno de observar el método execute y todo lo que un dynamic hace por nosotros.

   1: using Microsoft.CSharp.RuntimeBinder;

   2: using System;

   3: using System.Collections;

   4: using System.Runtime.CompilerServices;

   5: using System.Web.Mvc;

   6: namespace ASP

   7: {

   8:     [Dynamic(new bool[]

   9:     {

  10:         false,

  11:         true

  12:     })]

  13:     public class _Page_Views_Home_Contact_cshtml : WebViewPage<object>

  14:     {

  15:         [CompilerGenerated]

  16:         private static class <Execute>o__SiteContainer0

  17:         {

  18:             public static CallSite<Func<CallSite, object, IEnumerable>> <>p__Site1;

  19:             public static CallSite<Action<CallSite, _Page_Views_Home_Contact_cshtml, object>> <>p__Site2;

  20:             public static CallSite<Func<CallSite, object, object>> <>p__Site3;

  21:             public static CallSite<Action<CallSite, _Page_Views_Home_Contact_cshtml, object>> <>p__Site4;

  22:             public static CallSite<Func<CallSite, object, object>> <>p__Site5;

  23:         }

  24:         protected global_asax ApplicationInstance

  25:         {

  26:             get

  27:             {

  28:                 return (global_asax)this.Context.ApplicationInstance;

  29:             }

  30:         }

  31:         public override void Execute()

  32:         {

  33:             if (_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site1 == null)

  34:             {

  35:                 _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, IEnumerable>>.Create(Binder.Convert(CSharpBinderFlags.None, typeof(IEnumerable), typeof(_Page_Views_Home_Contact_cshtml)));

  36:             }

  37:             foreach (object item in _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site1.Target(_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site1, base.Model))

  38:             {

  39:                 base.BeginContext("~/Views/Home/Contact.cshtml", 56, 12, true);

  40:                 this.WriteLiteral("    <br />rn");

  41:                 base.EndContext("~/Views/Home/Contact.cshtml", 56, 12, true);

  42:                 base.BeginContext("~/Views/Home/Contact.cshtml", 68, 10, true);

  43:                 this.WriteLiteral("    <span>");

  44:                 base.EndContext("~/Views/Home/Contact.cshtml", 68, 10, true);

  45:                 base.BeginContext("~/Views/Home/Contact.cshtml", 79, 7, false);

  46:                 if (_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site2 == null)

  47:                 {

  48:                     _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, _Page_Views_Home_Contact_cshtml, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.InvokeSimpleName | CSharpBinderFlags.ResultDiscarded, "Write", null, typeof(_Page_Views_Home_Contact_cshtml), new CSharpArgumentInfo[]

  49:                     {

  50:                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),

  51:                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)

  52:                     }));

  53:                 }

  54:                 Action<CallSite, _Page_Views_Home_Contact_cshtml, object> arg_170_0 = _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site2.Target;

  55:                 CallSite arg_170_1 = _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site2;

  56:                 if (_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site3 == null)

  57:                 {

  58:                     _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site3 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "Id", typeof(_Page_Views_Home_Contact_cshtml), new CSharpArgumentInfo[]

  59:                     {

  60:                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)

  61:                     }));

  62:                 }

  63:                 arg_170_0(arg_170_1, this, _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site3.Target(_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site3, item));

  64:                 base.EndContext("~/Views/Home/Contact.cshtml", 79, 7, false);

  65:                 base.BeginContext("~/Views/Home/Contact.cshtml", 86, 9, true);

  66:                 this.WriteLiteral("</span>rn");

  67:                 base.EndContext("~/Views/Home/Contact.cshtml", 86, 9, true);

  68:                 base.BeginContext("~/Views/Home/Contact.cshtml", 95, 12, true);

  69:                 this.WriteLiteral("    <br />rn");

  70:                 base.EndContext("~/Views/Home/Contact.cshtml", 95, 12, true);

  71:                 base.BeginContext("~/Views/Home/Contact.cshtml", 107, 10, true);

  72:                 this.WriteLiteral("    <span>");

  73:                 base.EndContext("~/Views/Home/Contact.cshtml", 107, 10, true);

  74:                 base.BeginContext("~/Views/Home/Contact.cshtml", 118, 9, false);

  75:                 if (_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site4 == null)

  76:                 {

  77:                     _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site4 = CallSite<Action<CallSite, _Page_Views_Home_Contact_cshtml, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.InvokeSimpleName | CSharpBinderFlags.ResultDiscarded, "Write", null, typeof(_Page_Views_Home_Contact_cshtml), new CSharpArgumentInfo[]

  78:                     {

  79:                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),

  80:                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)

  81:                     }));

  82:                 }

  83:                 Action<CallSite, _Page_Views_Home_Contact_cshtml, object> arg_2C9_0 = _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site4.Target;

  84:                 CallSite arg_2C9_1 = _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site4;

  85:                 if (_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site5 == null)

  86:                 {

  87:                     _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site5 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(_Page_Views_Home_Contact_cshtml), new CSharpArgumentInfo[]

  88:                     {

  89:                         CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)

  90:                     }));

  91:                 }

  92:                 arg_2C9_0(arg_2C9_1, this, _Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site5.Target(_Page_Views_Home_Contact_cshtml.<Execute>o__SiteContainer0.<>p__Site5, item));

  93:                 base.EndContext("~/Views/Home/Contact.cshtml", 118, 9, false);

  94:                 base.BeginContext("~/Views/Home/Contact.cshtml", 127, 9, true);

  95:                 this.WriteLiteral("</span>rn");

  96:                 base.EndContext("~/Views/Home/Contact.cshtml", 127, 9, true);

  97:             }

  98:         }

  99:     }

 100: }

En pocas palabras ya tenemos al descubierto la magia de Razor y de Dynamic, vamos todo en unoSonrisa.

Como era algo que estaba en mente desde mis inicios públicos El respositorio genérico.Un Derrochador en épocas de crisis y últimamente mis dos grandes compañeros y amigos @XaviPaper y Carlos(Carrillo pa los amigos:), se me quejaban todos los días del follón de los dto’s a mí también me lo parecían, pero como los escriben ellos xDDDD.

Venga vamos a darle la solución y hacer de Eduard mi gatitoSonrisa.

La solución de Eduard es buenísima y denota eso, lo que es un maestro, pero le veo un problema dos iteraciones por los datos y a mi me gusta abusar el patrón “Engañabobos”.

Para ello no voy a crear un ExpandoObject sino una clase que hereda de DynamicObject y en esta reemplazar el método TryGetMember.

   1: public class Midinamico : DynamicObject

   2:    {

   3:        

   4:        public Midinamico()

   5:        {

   6:            

   7:        }

   8:        public Object Value { private get; set; }

   9:        public override bool TryGetMember(GetMemberBinder binder, out object result)

  10:        {

  11:            result = Value.GetType().InvokeMember(

  12:                binder.Name,

  13:                BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,

  14:                null,

  15:                Value,

  16:                null);

  17:  

  18:            // Always return true, since InvokeMember would have thrown if something went wrong

  19:            return true;

  20:        }

  21:    }

Y donde está la magia en el tercer parámetro de la llamada al método “InvoqueMember” ese flag de Reflection de toda la vida que hace que por mucho que declares algo como privado siempre habrá alguien que pueda acceder desde otra dll, cosas de .NetSonrisa.

BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic

Madre ese Public|NonPublic que peligro tieneSonrisa.

Y al final la solución completa.

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Data.Entity;

   4: using System.Dynamic;

   5: using System.Linq;

   6: using System.Reflection;

   7: using System.Text;

   8: using System.Threading.Tasks;

   9:  

  10: namespace Datos

  11: {

  12:     public class Context : DbContext

  13:     {

  14:         public Context()

  15:             : base("DefaultConnection")

  16:         {

  17:  

  18:         }

  19:         public DbSet<ClaseLarga> ClaseLarga { get; set; }

  20:  

  21:         public dynamic GetData()

  22:         {

  23:             return this.ClaseLarga.Select(x => new Midinamico() { Value = new { Id = x.Id, Name = x.Name } });

  24:         }

  25:     }

  26:     public class Midinamico : DynamicObject

  27:     {

  28:         

  29:         public Midinamico()

  30:         {

  31:             

  32:         }

  33:         public Object Value { private get; set; }

  34:         public override bool TryGetMember(GetMemberBinder binder, out object result)

  35:         {

  36:             result = Value.GetType().InvokeMember(

  37:                 binder.Name,

  38:                 BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,

  39:                 null,

  40:                 Value,

  41:                 null);           

  42:             return true;

  43:         }

  44:     }

  45:     public class ClaseLarga

  46:     {

  47:         public int Id { get; set; }

  48:         public string Name { get; set; }

  49:         public string MasCamposQueMeCanso { get; set; }

  50:     }

  51: }

Y nuestro controlador quedaría de esta forma.

   1: public ActionResult Contact()

   2: {

   3:    var ctx = new Context();          

   4:    return View(ctx.GetData());

   5:              

   6: }

P.D. El código genera un error en ejecución pero muy sencillo de solucionar y evidentemente nunca escribiría un código así sobre todo en el “Context”, pero eso os lo dejo a vosotros.

Gracias Sergio por destapar las ganas de volver a escribir y moraleja “No escribo un dto más en mi vida para pintar una vista”. Xavi Carri vosotros sí Sonrisa.

Eduard gracias!!! Como siempre un maestro que me ha hecho calentarme la cabeza para buscar otra alternativa.

Pero alguien pensaba que esto finalizaba. No me queda una parte.Toda la inspiración vino por este post Passing anonymous objects to MVC views and accessing them using dynamic, si nos fijamos en el final este señor utiliza la directiva @page de asp para decir a su vista de que clase hereda Inherits. ¿Tanto cuesta implementar esto en Razor?, porque hasta donde yo se todo depende de esa otra joyita que es el web.config de la carpeta donde están las vistas “View”

   1: <system.web.webPages.razor>

   2:    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

   3:    <pages pageBaseType="System.Web.Mvc.WebViewPage">

   4:      <namespaces>

   5:        <add namespace="System.Web.Mvc" />

   6:        <add namespace="System.Web.Mvc.Ajax" />

   7:        <add namespace="System.Web.Mvc.Html" />

   8:        <add namespace="System.Web.Optimization"/>

   9:        <add namespace="System.Web.Routing" />

  10:      </namespaces>

  11:    </pages>

  12:  </system.web.webPages.razor>

En definitiva si esto existiese en Razor yo podría escribir mis consultas linq limpias sin extensores como en el caso de Eduard ni como en mi caso creando un objeto que hereda de de DynamicObject.

Conclusiones

Que me ha quedado una duda, esto servirá para algo. Puesto que lo que estoy reclamando a gritos son dos pequeñas modificaciones una en MVC y otra en Visual Studio(Olvídate de los internal o déjame que los míos los genere yo, aunque sean anónimos, les pongo EditorBrowsableAttribute para que no los vea nadie en el intellisense), pero  como yo los quiera “my guardian” y los quiero “public” que me ahorran tiempo y me quitan la desesperación 🙂