Código sin nulls

Hace algunos días Juan Quijano escribió un post en GenBetaDev con este mismo título donde comentaba lo poco que le gustaba que la funciones devolviesen null y lo que hacía para evitar errores en ese caso.

Este post es mi respuesta a su post, ya que personalmente no me gusta la solución que presenta. En general termina con una solución como la siguiente:

  1. public class Modelo
  2. {
  3.     public Persona GetPersonaByName(string nombre)
  4.     {
  5.         Persona persona = new Persona();
  6.         if (nombre == "pepe")
  7.         { persona = new Persona { Nombre = nombre, Edad = 14 }; }
  8.         return persona;
  9.     }
  10. }
  11. public class Persona
  12. {
  13.     public string Nombre { get; set; }
  14.     public int Edad { get; set; }
  15.     public Persona()
  16.     {
  17.         Nombre = string.Empty;
  18.         Edad = 0;
  19.     }
  20. }

No me gusta por varias razones, la principal es que “oculta” la causa, devolviendo una Persona “sin datos” cuando no se encuentra la persona. Está estableciendo una convención que nadie más sabe: que una persona con el nombre vacío “no existe” en realidad. El código va a terminar llenándose de ifs para validar si el nombre es o no vacío para hacer algo o no. A diferencia de un null, donde olvidarte del if genera un error en ejecución (y por lo tanto es visible), dejarte un if en este caso hará que tu código se comporte mal… y a veces esto puede ser mucho, pero que mucho, más difícil que detectar el null reference, que al menos viene con stack trace. Otro problema es que no siempre existe en todo el rango de valores posibles un valor que pueda ser usado como “indicador de que no hay datos”.

Hay varios patrones para tratar esos caso, el más conocido el NullObject. De hecho un NullObject “mal hecho” es lo que propone Juan en su post. No soy muy amante del NullObject, aunque lo he usado a veces (la última hace poco en un refactoring, donde se tuvo que “desactivar” toda una funcionalidad. En este caso lo hicimos creando un NullObject del objeto que se estaba usando, de forma que el impacto en el resto del código (unos 90 proyectos de VS) fue nulo).

No quiero hablar del NullObject, si no presentar otra alternativa. En este caso una clase que contenga el valor más un indicador de si el valor existe o no. Vamos lo equivalente a Nullable<T> pero para cualquier tipo (sí… incluso los que pueden ser null). A priori parece que no ganamos nada pero dejadme un rato y veréis las ventajas que aporta.

La versión inicial de nuestra clase sería:

  1. public struct Maybe<T>
  2. {
  3.  
  4.     private readonly T _value;
  5.     private readonly bool _isEmpty;
  6.     private readonly bool _initialized;
  7.  
  8.     public T Value
  9.     {
  10.         get { return _value; }
  11.     }
  12.  
  13.     public bool IsEmpty
  14.     {
  15.         get { return (!_initialized) || _isEmpty; }
  16.     }
  17.  
  18.     public Maybe(T value)
  19.     {
  20.         _value = value;
  21.         _isEmpty = ((object)value) == null;
  22.         _initialized = true;
  23.     }
  24.  
  25.     public static Maybe<T> Empty()
  26.     {
  27.         return new Maybe<T>();
  28.     }
  29. }

Un punto importante es que no es una clase, es una estructura. Eso es para evitar que alguien que declare que devuelve un Maybe<T> termine devolviendo un null (recordad que queremos evitar los null).

Vale, esta estrcutura, tal cual está no nos aporta casi nada útil. El método del Modelo que presentaba Juan quedaría ahora como:

  1. public Maybe<Persona> GetPersonaByName(string nombre)
  2. {
  3.     Persona persona = new Persona();
  4.     if (nombre == "pepe")
  5.     {
  6.         persona = new Persona { Nombre = nombre, Edad = 14 };
  7.         return new Maybe<Persona>(persona);
  8.     }
  9.     return Maybe<Persona>.Empty();
  10. }

O devolvemos un Maybe relleno con la persona o devolvemos un Maybe vacío.  El test que usaba Juan quedaría como sigue:

  1. [TestMethod]
  2. public void GetPersonaByName_con_null_devuelve_string_empty()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName(null);
  6.     Assert.AreEqual(string.Empty, persona.Value.Nombre);
  7. }

Este test falla y la razón es obvia: persona.Value es null por lo que persona.Value.Nombre da un NullReferenceException. Podría añadir un if en el código para validar si person.IsEmpty es true, y en este caso no hacer nada. Personalmente prefiero mil veces un if (person.IsEmpty) que un if (person.Nombre ==””) ya que el primer if deja mucho claro que se pretende. Pero está claro, que no hemos ganado mucho. Como digo, dicha estructura apenas aporta nada.

Lo bueno es preparar dicha estructura para que pueda ser usada como un monad. Lo siento, soy incapaz de encontrar palabras sencillas para definir que es un monad porque el concepto es muy profundo, así que os dejo con el enlace de la wikipedia: http://en.wikipedia.org/wiki/Monad_(functional_programming)

Ahora vamos a preparar nuestra estructura para que pueda ser usada como un monad:

  1. public struct Maybe<T>
  2. {
  3.  
  4.     private readonly T _value;
  5.     private readonly bool _isEmpty;
  6.     private readonly bool _initialized;
  7.  
  8.     public T Value
  9.     {
  10.         get { return _value; }
  11.     }
  12.  
  13.     public bool IsEmpty
  14.     {
  15.         get { return (!_initialized) || _isEmpty; }
  16.     }
  17.  
  18.     public Maybe(T value)
  19.     {
  20.         _value = value;
  21.         _isEmpty = ((object)value) == null;
  22.         _initialized = true;
  23.     }
  24.  
  25.     public static Maybe<T> Empty()
  26.     {
  27.         return new Maybe<T>();
  28.     }
  29.  
  30.     public void Do(Action<T> action)
  31.     {
  32.         if (!IsEmpty) action(Value);
  33.     }
  34.  
  35.     public void Do(Action<T> action, Action elseAction)
  36.     {
  37.         if (IsEmpty)
  38.         {
  39.             action(Value);
  40.         }
  41.         else
  42.         {
  43.             elseAction();
  44.         }
  45.     }
  46.  
  47.     public TR Do<TR>(Func<T, TR> action)
  48.     {
  49.         return Do(action, default(TR));
  50.     }
  51.  
  52.     public TR Do<TR>(Func<T, TR> action, TR defaultValue)
  53.     {
  54.         return IsEmpty ? defaultValue : action(Value);
  55.     }
  56.  
  57.  
  58.     public Maybe<TR> Apply<TR>(Func<T, TR> action)
  59.     {
  60.         return IsEmpty ? Maybe<TR>.Empty() : new Maybe<TR>(action(Value));
  61.     }
  62. }

He añadido dos familias de métodos:

  1. Método Do para hacer algo solo si Maybe tiene valor
  2. Método Apply para encadenar Maybes. Este es el más potente y lo veremos luego.

Empecemos por los métodos Do. Dichos métodos básicamente nos permiten evitar el if(). Son poco más que una pequeña ayuda que nos proporciona la estructura. Mi test quedaría de la siguiente manera:

  1. [TestMethod]
  2. public void GetPersonaByName_con_null_devuelve_string_empty()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName(null);
  6.     var name = string.Empty;
  7.     persona.Do(p => name = p.Nombre);
  8.     Assert.AreEqual(string.Empty, name);
  9. }

El código del Do se ejecuta solo si hay valor, es decir si se ha devuelto una persona.

Podríamos reescribir el test usando otra de las variantes de Do:

  1. [TestMethod]
  2. public void GetPersonaByName_con_null_devuelve_string_empty()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName(null);
  6.     var name = persona.Do(p => p.Nombre, "no_name");
  7.     Assert.AreEqual("no_name", name );
  8. }

No hay mucho más que decir sobre los métodos Do… porque el método más interesante es Apply 😉

El método Apply me permite encadenar Maybes. Para ver su potencial, cambiaré el método del Modelo:

  1. public Maybe<Persona> GetPersonaByName(string nombre)
  2. {
  3.     Persona persona = new Persona();
  4.     if (nombre == "pepe")
  5.     {
  6.         persona = new Persona {Nombre = null, Edad = 42};
  7.         return new Maybe<Persona>(persona);
  8.     }
  9.     return Maybe<Persona>.Empty();
  10. }

Ahora si le paso “pepe” me da a devolver una Persona pero con el Nombre a null. Tratar esos casos con ifs se vuelve muy complejo y costoso. Apply viene en nuestra ayuda:

  1. [TestMethod]
  2. public void Acceder_a_nombre_null_no_da_probleamas()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName("pepe");
  6.     // En este punto tenemos un Maybe relleno pero value.Nombre es null
  7.     var nombreToUpper = string.Empty;
  8.     nombreToUpper = persona.Apply(p => p.Nombre).Do(s =>s.ToUpper(), "NO_NAME");
  9.     Assert.AreEqual("NO_NAME", nombreToUpper);
  10. }

La variable persona es un Maybe<Persona> con un valor. El método Apply lo que hace es básicamente ejecutar una transformación sobre el valor (el objeto Persona) y devolver un Maybe con el resultado. En este caso transformamos el objeto persona a p.Nombre, por lo que el valor devuelto por Apply es un Maybe<string>. Y como el valor de p.Nombre era null, el Maybe está vacío.

La combinación de Apply y Do permite tratar con valores nulos de forma muy sencilla y elegante.

Si os pregunto que capacidades funcionales tiene C# seguro que muchos responderéis LINQ… Porque no hacemos que nuestra clase Maybe<T> pueda participar del juego de LINQ? Por suerte eso es muy sencillo. Para ello basta con que Maybe<T> implemente IEnumerable<T> añadiendo esas dos funciones:

  1. public IEnumerator<T> GetEnumerator()
  2. {
  3.     if (IsEmpty) yield break;
  4.     yield return _value;
  5. }
  6.  
  7. IEnumerator IEnumerable.GetEnumerator()
  8. {
  9.     return GetEnumerator();
  10. }

Básicamente un Maybe<T> lleno se comporta como una colección de un elemento de tipo T, mientras que un Maybe<T> vacío se comporta como una colección vacía. A partir de aquí… tenemos todo el poder de LINQ para realizar transformaciones, consultas, uniones, etc… con nuestros Maybe<T> con otros Maybe<T> o cualquier otra colección. P. ej. podríamos tener el siguiente código:

  1. [TestMethod]
  2. public void Comprobar_Que_Nombre_es_Null()
  3. {
  4.     var modelo = new Modelo();
  5.     var persona = modelo.GetPersonaByName("pepe");
  6.     var tiene_nombre= persona.Apply(p => p.Nombre).Any();
  7.     Assert.IsFalse(tiene_nombre);
  8. }

Y por supuesto podemos iterar con foreach sobre los elementos de un Maybe<T> 🙂

Ya para finalizar vamos a añadir un poco más de infrastructura a la clase Maybe<T>. En concreto soporte para la comparación:

  1. public static bool operator ==(Maybe<T> one, Maybe<T> two)
  2. {
  3.     if (one.IsEmpty && two.IsEmpty) return true;
  4.     return typeof(T).IsValueType ?
  5.         EqualityComparer<T>.Default.Equals(one._value, two._value) :
  6.     object.ReferenceEquals(one.Value, two.Value);
  7. }
  8.  
  9. public bool Equals(Maybe<T> other)
  10. {
  11.     return _isEmpty.Equals(other._isEmpty) && EqualityComparer<T>.Default.Equals(_value, other._value);
  12. }
  13.  
  14. public override bool Equals(object obj)
  15. {
  16.     if (ReferenceEquals(null, obj)) return false;
  17.     return obj is Maybe<T> && Equals((Maybe<T>)obj);
  18. }
  19.  
  20. public static bool operator !=(Maybe<T> one, Maybe<T> two)
  21. {
  22.     return !(one == two);
  23. }
  24.  
  25. public override int GetHashCode()
  26. {
  27.     unchecked
  28.     {
  29.         return (_isEmpty.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(_value);
  30.     }
  31. }

Maybe<T> intenta replicar el comportamiento de comparación de T. Es decir:

  • Dos Maybe<T> son “Equals” si los dos Ts de cada Maybe son “Equals”
  • Un Maybe<T> == otro Maybe<T> si:
    • Ambos Ts son el mismo objeto (en el caso de tipo por referencia)
    • Ambos Ts son “Equals” en el caso de tipos por valor

P. ej. el siguiente test valida el comportamiento de ==:

  1. var i = 10;
  2. var i2 = 10;
  3. var p = new Persona();
  4. var p2 = new Persona();
  5. Assert.IsTrue(new Maybe<int>(i) == new Maybe<int>(i2));
  6. Assert.IsFalse(new Maybe<Persona>(p) == new Maybe<Persona>(p2));

Y finalmente añadimos soporte para la conversión implícita de Maybe<T> a T:

  1. public static implicit operator Maybe<T>(T from)
  2. {
  3.     return new Maybe<T>(from);
  4. }

Dicha conversión nos permite simplificar las funciones que deben devolver un Maybe<T>. Así ahora la función del modelo puede ser:

  1. public Maybe<Persona> GetPersonaByName(string nombre)
  2. {
  3.     return nombre == "pepe" ?
  4.         new Persona {Nombre = null, Edad = 42} :
  5.         null;
  6. }

Fíjate que la función GetPersonaByName sigue devolviendo un Maybe<Persona> pero para el código es como si devolviese un Persona. El return null se traduce a devolver un Maybe<Persona> vacío.

Bueno… con eso termino el post. Espero que os haya resultado interesante y que hayáis visto otras posibles maneras de lidiar con las dichosas referencias null.

Saludos!

ASP.NET vNext–Crea tus propios “comandos K”

Una de las características de ASP.NET vNext son los “comandos K”, es decir aquellos comandos que se invocan desde línea de comandos a través del fichero K.cmd.

Dichos comandos están definidos en el project.json y la idea es que ofrezcan tareas necesarias durante el ciclo de vida de compilación y pruebas. Así podemos tener un comando (p. ej. K run) que nos ejecute el proyecto y otro (K test) que nos lance los tests unitarios. Recordad siempre que ASP.NET vNext se crea con el objetivo de que sea multiplataforma total: no solo que sea ejecutable en Linux o MacOSX a través de Mono, si no que sea posible desarrollar en esos sistemas operativos. Y eso implica “desligarse” de Visual Studio. Por supuesto, eso no quita que VS añada e implemente su propio soporte para el ciclo de vida de compilación y pruebas.

De hecho, incluso actualmente en VS no es nuevo usar comandos para gestionar algunas de las tareas necesarias para el ciclo de vida. Así muchos de vosotros conoceréis el comando Install-Package para instalar un paquete NuGet. Así, a dia de hoy, usamos la “Package Manager Console” que no es más que un wrapper sobre PowerShell. Para algunos comandos (como el citado Install-Package) el propio VS ofrece una alternativa gráfica pero hay otros comandos que solo se pueden lanzar desde dicha consola. El ejemplo más claro son todos los comandos de EF Migrations (p. ej. Update-Database).

La razón de que ASP.NET vNext se “olvide” de los comandos Powershell es que Powershell solo funciona en Windows y recuerda… ASP.NET vNext es multiplataforma de verdad.

Por supuesto nosotros podemos crear nuestros propios “comandos K” para añadir tareas que nos sean necesarias o útiles para el ciclo de vida de compilación y pruebas de la aplicación. Porque básicamente un comando definido en el project.json lo único que hace es invocar a un ensamblado.

Vamos a ver como podemos hacerlo. 🙂

Los comandos residen en un assembly que debe ser compilado con vNext. En nuestro caso vamos a crear una “ASP.NET vNext Console Application” usando VS14 CTP3. En mi caso la he llamado TestCommands.

image

La verdad es que el template que viene con VS14 CTP3 para dicho tipo de aplicaciones es casi inútil, pero bueno… menos da una piedra. Dicho template genera la clásica clase “Program” con su método Main, útil para ejecutables, pero en nuestro caso nuestra aplicación estará lanzada a través de un comando K, así que será el propio framework de ASP.NET vNext quien invocará nuestra aplicación.

Lo primero es agregar una dependencia a Microsoft.Framework.Runtime.Common en nuestro project.json:

  1. "dependencies": {
  2.     "Microsoft.Framework.Runtime.Common": "1.0.0-alpha3"
  3. },

Luego podemos modificar la clase Program para que quede de la siguiente así:

  1. public class Program
  2. {
  3.     public Program(IApplicationEnvironment env)
  4.     {
  5.         Console.WriteLine("In Progrm.ctor " + env.ApplicationBasePath);
  6.     }
  7.     public void Main(string[] args)
  8.     {
  9.         Console.WriteLine("In main");
  10.         Console.ReadLine();
  11.     }
  12. }

Si lo ejecutas con VS directamente verás algo parecido:

image

Por supuesto puedes ejecutarlo también usando KRE. Para ello asegúrate de tener en el path la carpeta donde está KRE instalado. Puedes tener varios KREs instalados side by side y por defecto se instalan en %HOME%.krepackages. Así p. ej. en mi maquina tengo:

image

Debes agregar al path la carpeta bin del KRE que quieras usar. Así p. ej. yo tengo agregado al path la carpeta C:Usersetomas.krepackagesKRE-svr50-x86.1.0.0-alpha3bin

Así si navegamos a la carpeta donde está el fichero project.json y ejecutamos “k run”:

image

En el caso de que os de un error de que no puede encontrar algún assembly lanzando un mensaje de error parecido al siguiente:

System.InvalidOperationException: Failed to resolve the following dependencies:
   Microsoft.Framework.Runtime.Common 1.0.0-alpha3

Debes ejecutar el comando “kpm restore”. Eso es necesario cuando la maquina en la que estás ejecutando no tiene alguno de los paquetes marcados como dependencias en el project.json. Si es la propia maquina en la que tienes VS14 eso no te ocurrirá (VS14 se descarga los paquetes) pero, p. ej. yo he copiado el proyecto a otra máquina donde no tenía VS14, pero sí el runtime de vNext y he necesitado ejecutar dicho comando.

Fijate en cuatro detalles:

  1. Se pasa primero por el constructor de la clase Program
  2. El framework nos inyecta automáticamente el objeto IApplicationEnvironment
  3. Luego se llama automáticamente al método Main
  4. No es necesario que el método Main sea estático.

Si te preguntas como pasar parámetros al método main, pues simplemente añadiéndolos al comando “k run”. Así, si p. ej. tecleas “k run remove /s” el método Main recibirá dos parámetros (“remove” y “/s”). Para pasar parámetros con VS debes usar las propiedades del proyecto (sección Debugging).

Podríamos implementar un comando “list” que listase todos los ficheros del proyecto:

  1. public class Program
  2. {
  3.     private readonly string _folder;
  4.     public Program(IApplicationEnvironment env)
  5.     {
  6.         _folder = env.ApplicationBasePath;
  7.     }
  8.     public void Main(string[] args)
  9.     {
  10.         if (args.Length == 1 && args[0] == "list")
  11.         {
  12.             ListFiles();
  13.         }
  14.         Console.ReadLine();
  15.     }
  16.  
  17.     private void ListFiles()
  18.     {
  19.         var files = Directory.EnumerateFiles(_folder, "*.*", SearchOption.AllDirectories);
  20.         foreach (var file in files)
  21.         {
  22.             Console.WriteLine(file);
  23.         }
  24.     }
  25. }

Para poder usar la clase Directory debes añadir una dependencia en el project.json al paquete System.IO.FileSystem (recuerda que en vNext todo el framework está fragmentado y dividido en paquetes NuGet).

Bien, ahora ya sabemos que podemos ejecutar este programa con “k run list”, pero a nosotros lo que nos interesa es tener un “comando k” adicional para usar con otro programa vNext.

Para ver como lo podemos montar vamos a agregar otro proyecto de consola de ASP.NET vNext a la solución. Yo lo he llamado DemoLauncher. No es necesario que toques nada del código.

Ahora debes agregar una dependencia desde DemoLauncher al otro proyecto (que en mi caso se llamaba TestCommands):

  1. "dependencies": {
  2.     "TestCommands": ""
  3. },

Puedes agregar esta dependencia porque, a pesar de que TestCommands no

está en NuGet está en la misma solución.

Ahora damos de alta el comando. Los comandos se dan de alta en el project.json. Así editamos el project.json de DemoLauncher para añadir una sección de commands:

  1. "commands": {
  2.     "tc" : "TestCommands"
  3. }

Con esto le indicamos que cuando se lance el comando “tc” se invoque al ensamblado “TestCommands”.

Ahora puedo ir a la carpeta donde está el project.json de DemoLauncher y si tecleo “k run” se ejecutará DemoLauncher (eso era de esperar):

image

Pero lo bueno viene ahora. Si desde esa misma carpeta tecleas “k tc list” se te listarán todos los ficheros que haya en la carpeta (y subcarpetas) de DemoLauncher:

image

Al lanzar “k tc” como en el project.json hay definido el comando “tc” se invoca al ensamblado “TestCommands” y se le pasan los parámetros que se hayan pasado después de tc. Así pues TestCommands recibe el parámetro “list” y lista todos los ficheros. Lo interesante es que TestCommands se ejecuta bajo el contexto de ejecución de DemoLauncher (la propiedad ApplicationBasePath del IApplicationEnvironment apunta al directorio donde está DemoLauncher).

En un escenario final real, tendríamos “TestCommands” publicado a NuGet, pero el resto vendría a ser lo mismo que hemos visto.

La gente de EF ya ha empezado a usar esa táctica para los comandos de Migrations (y así posibilitar el uso de Migrations en entornos no windows al no depender más de Powershell). Y personalmente creo que vamos a ver bastantes de esos futuros comandos.

Un saludo!

ASP.NET MVC6 (vNext)–ViewComponents

En ASP.NET vNext se unifican MVC y WebApi en una nueva API llamada MVC6. Aunque MVC6 se parece a MVC5 no es compatible con ella, del mismo modo que WebApi se parece a MVC pero por debajo son muy distintas.

Ya hemos viso algunas de las novedades o cambios que trae MVC6 (temas de model binding, controladores POCO, …) y en este post vamos a explorar uno más: los ViewComponents.

Resumiendo: los ViewComponents sustituyen a las vistas parciales. Ya no existe este concepto en MVC6. De hecho, tampoco nos engañemos, desde razor la diferencia entre vistas parciales y vistas normales (a nivel del archivo .cshtml) es muy pequeña: se puede usar una vista parcial como vista normal tan solo cambiando el “return PartialView()” por un “return View()” (o viceversa). En el motor de vistas de ASPX eso no era así, ya que las vistas eran archivos .aspx y las vistas parciales eran archivos .ascx.

En Razor la única diferencia actual entre una vista parcial y una normal es que en la segunda se procesa el archivo de Layout (usualmente _Layout.cshtml) y en la primera no. Pero no es el archivo .cshtml quien determina si es vista normal o parcial. Es el ActionResult devuelto. Si devuelves un ViewResult el archivo .cshtml se procesará como vista normal. Si devuelves un PartialViewResult el archivo .cshtml se procesará como vista parcial.

El código “clásico” en MVC5 para tener una vista parcial era algo como:

  1. public ActionResult Child()
  2. {
  3.     return PartialView();
  4. }

El problema con este enfoque es que esta acción es enrutable, por lo que cualquiera puede ir a la URL que enrute esa acción (p. ej. Home/Child si suponemos HomeController) y recibirá el contenido HTML de la vista parcial. Eso, generalmente, no se desea (para algo la vista es parcial).

Para solventar esto, en MVC4 se añadió el atributo [ChildActionOnly] que evitaba que una acción se enrutase. Así si decoramos la acción con dicho atributo cuando el usuario navega a la URL que debería enrutar dicha acción recibirá un error:

image

La acción se puede invocar a través del helper Html.RenderAction:

  1. @{ Html.RenderAction("Child", "Home"); }

Nota: Se puede usar Html.Partial o Html.RenderPartial para renderizar una vista parcial directamente (sin pasar por un controlador). Eso es útil en el caso de que no haya lógica asociada a dicha vista parcial (si la hay, lo suyo es colocarla en la acción y usar Html.RenderAction).

Bueno… así tenemos las cosas hoy en día: básicamente colocamos las acciones “hijas” en un controlador (porque es donde podemos colocar lógica) pero luego las quitamos del sistema de enrutamiento (con [ChildActionOnly]) y las llamamos indicando directamente que acción y que controlador es.

Realmente ¿tiene sentido que las acciones hijas estén en un controlador? No. Porque la responsabilidad del controlador es, básicamente, responder a peticiones del navegador y eso no es una petición del navegador.

Así en MVC6 se elimina el concepto de vista parcial y el PartialViewResult, y se sustituye por el concepto de ViewComponent. Ahora lo que antes eran acciones hijas son clases propias que derivan de ViewComponent:

  1. [ViewComponent(Name = "Child")]
  2. public class ChildComponent : ViewComponent
  3. {
  4.     public async Task<IViewComponentResult> InvokeAsync()
  5.     {
  6.         return View();
  7.     }
  8. }

El atributo [ViewComponent] nos permite especificar el nombre que damos al componente. El siguiente paso es definir el método InvokeAsync que devuelve una Task<IViewComponentResult> con el resultado. La clase ViewComponent nos define el método View() que devuelve la vista asociada a dicho componente (de forma análoga al método View() de un controlador).

La ubicación por defecto de la vista asociada a un componente es /Views/Shared/Components/[NombreComponente]/Default.cshtml. Es decir en mi caso tengo el fichero Default.cshtml en /Views/Shared/Components/Child:

image

Por supuesto ahora tengo un sitio donde colocar la lógica (si la hubiera) de dicho componente: la propia clase ChildComponent.

Finalmente nos queda ver como renderizamos el componente. Ya no tenemos Html.RenderAction, si no que en su lugar usamos la propiedad Component que tienen las vistas de MVC6:

  1. @await Component.InvokeAsync("Child")

Simplemente le pasamos el nombre del componente (el mismo definido en el atributo [ViewComponent].

Y listos 🙂