Archivos de la categoría .net

Diseñar una interfaz contando píxeles con los dedos

Cuando no puedes ver lo que estás diseñando, y además utilizas una tecnología antigua como Windows Forms que no es como WPF, en la que puedes implementar todo con medidas relativas y que no se solapen los controles, tienes que andar contando los píxeles a mano para que esto no ocurra.

Estaba hoy actualizando una aplicación que tiene algunos años, y he acabado hasta las narices de contar con los dedos para ver si había controles que solapaban a otros controles… Así que he acabado haciéndome una extensión para System.Windows.Forms.Control, que me devuelve una cadena indicándome qué solapamientos existen entre los controles hijos de ese control.

Ya sé que para quien me lea, posiblemente tendrá utilidad cero, pero me apetecía compartir las chapuzas que tengo que montar a veces para no acabar con dolor de cabeza 🙂

La extensión es la siguiente:

public static string GetOverlapping(this Control control)
		{
			StringBuilder strbOverlap = new StringBuilder();

			foreach (Control ctl in control.Controls.Cast<Control>().OrderBy(ct => ct.TabIndex))
			{
				int xFrom = ctl.Location.X, xTo = xFrom + ctl.Size.Width;
				int yFrom = ctl.Location.Y, yTo = yFrom + ctl.Size.Height;
				List<Control> controlsOverlapping = control.Controls.Cast<Control>().Where(ct =>
				ct!= ctl &&
				xFrom  < ct.Location.X + ct.Size.Width && ct.Location.X< xTo
				&& yFrom < ct.Location.Y + ct.Size.Height&& ct.Location.Y < yTo)
				.ToList();
				if (controlsOverlapping.Any())
				{
					strbOverlap.AppendLine($"{ctl.Name}  Es solapado por: " +
						$"{String.Join(", ", controlsOverlapping.Select(ct => ct.Name).ToArray())}.");
				}
				if (ctl.Controls.Count > 0)
				{
					strbOverlap.Append(ctl.GetOverlapping());
				}
			}
			return strbOverlap.ToString();
		}
	}

Para los que véis esto os parecerá una chorrada, pero oye, a mí me ha sacado los colores la muy hija de ….
Estaría bien aumentar la funcionalidad para que detecte desbordamientos, y sea capaz de decir en qué esquina se solapan los controles entre sí… Pero por lo pronto, así me vale 😉

¡Espero que os haya hecho gracia la curiosidad!

¡Un saludo!

Entrada visitada 42 veces

AppSettings de ficheros .config con tipado fuerte en c#

¡Hola!

No sé a vosotros, pero a mí me ha pasado más de una y de dos veces, eso de escribir mal una clave de configuración bien en el .config o bien en la llamada a ConfigurationManager.AppSettings[“…”], y volverme loco porque el código no funcionaba como yo quería.

Hace unos meses, un compañero me comentó que utilizaba una plantilla t4 (Text Template Transformation Toolkit)), para crear una clase estática a partir del contenido del a sección appSettings de un fichero web.config o App.config… Así que me puse a investigar un poco, y basándome en esta entrada del blog de “meie igapäevast IT’d anna meile igapäev”, me hice mi propio template para ese menester.

He de confesar que ahora no puedo vivir sin él 😉

Son unas pocas líneas de código. Solo tenéis que guardar el fichero en vuestro proyecto, añadir la referencia a System.Configuration si aún no la teníais, hacer click con el botón derecho sobre el fichero, pulsar en la opción “run custom tool”, y desde ese momento, podréis usar vuestra clase estática “AppSettings” con las claves de configuración que hayáis escrito en la sección “appSettings” de vuestro fichero config. El nombre de espacios generado será el mismo que el del ensamblado de vuestro proyecto, así que no tendréis que usar usings adicionales.

Varias consideraciones sobre la generación:

  • Las claves que contengan valores decimales se transformarán a propiedades de tipo decimal, siempre que la cultura del proyecto reconozca el formato de cadena como tal.
  • Las claves con contenido numérico entero se convertirán a propiedades de tipo entero.
  • Las claves cuyo contenido sea “true” o “false” se convertirán a propiedades booleanas.
  • En claves con caracteres no alfanuméricos, estos se reemplazarán por guiones bajos en su propiedad correspondiente.

Podéis descargar el fichero desde este enlace.

El código es el siguiente:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Configuration" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Configuration" #>
<#@ import namespace="System.Text.RegularExpressions"#>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO"#>
<#@ import namespace="System.Globalization" #>
using System.Configuration;
<# string ns = (string)System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint"); #>

namespace <#= ns#>
{
	public static class AppSettings {
<# 
	var configurationFileMap = new ExeConfigurationFileMap();
	string currentDir = Path.GetDirectoryName(this.Host.TemplateFile);
	string configFile = Path.Combine(currentDir, "Web.Config");
	if (!System.IO.File.Exists(configFile))
	{
		configFile = Path.Combine(currentDir, "App.Config");
	}
	if (!System.IO.File.Exists(configFile))
	{
		throw new System.IO.FileNotFoundException("No se encontró el fichero de configuración del que extraer los valores.");
	}

	configurationFileMap.ExeConfigFilename = configFile;
	var configuration = ConfigurationManager.OpenMappedExeConfiguration(configurationFileMap, ConfigurationUserLevel.None);
	foreach(KeyValueConfigurationElement setting in configuration.AppSettings.Settings)
	{ 
		if (setting.Key.Contains(":")) // estas suelen ser de ASP.NET MVC keys
		{
			continue;
		}
		// Reemplazamos todos los caracteres no alfanuméricos por guiones bajos
		string key = Regex.Replace(setting.Key, "[^a-zA-Z0-9]", "_");
		// Si la clave empieza por un número, le anteponemos el carácter 'N'
		if (Regex.IsMatch(key, "^[0-9]")) key = "N" + key;
		// Convertimos la clave a PascalCase. Normalmente me gusta poner las claves en
		// CamelCase, así que para que quede bien en .Net...
		key = key.Substring(0, 1).ToUpper() + key.Substring(1);
		string settingType;
		int i; bool b; decimal d;
		
		if (int.TryParse(setting.Value, out i))
		{
			settingType = "int";
		}
		else if (bool.TryParse(setting.Value, out b))
		{
			settingType = "bool";
		}
		else if (decimal.TryParse(setting.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out d))
		{
			settingType = "decimal";
		}
		else
		{
			#>
		public static string <#= key#> { get { return ConfigurationManager.AppSettings["<#= setting.Key #>"]; } }
<# continue;
} #>
		public static <#= settingType #> <#= key#> { get { return <#= settingType #>.Parse(ConfigurationManager.AppSettings["<#= setting.Key #>"]); }}
<#
} #>
	}
}

¡Espero que os sirva!

¡Un saludo!

Entrada visitada 112 veces

Rotar imágenes según sus metadatos de orientación

¡Hola!

Hay herramientas que se te ocurren pensando en qué cosas podrían ser útiles; y otras que surgen cuando te topas con un problema y dices: ¡joder, seguro que hay alguna solución para esto!

El otro día, mi mujer, @amaterasu_n estaba escribiendo en su blog de mami the vikings mama un post, y quería poner algunas fotos. Al ser ciegos, el tema de las imágenes en los posts es algo complejo. Nunca sabemos si la foto ha quedado bien, o se corta, o se descentra… así que después de ponerlas en el post, preguntamos a una compañera de trabajo, y me dijo que había fotos que estaban giradas… Ya os podéis imaginar, la típica foto tomada en horizontal, y que necesitas girar para que no tengas que darle la vuelta a la pantalla.

Así que me dije: Los smartphones tienen acelerómetros y giroscopios para detectar el cambio de orientación y adaptar las interfaces a esos cambios. ¿No guardarán esta información en los metadatos exif de las fotos? Y sorprendentemente, ¡la respuesta es sí! Imagino que también habrá cámaras que lo hacen, pero no he podido comprobarlo.

Buscando un poco por internet, me encontré con ExifExtractor, una pequeña librería en c# que me permitía extraer los datos exif de las imágenes. Por otro lado, en este artículo sobre las etiquetas exif encontré los valores y significados de esta etiqueta…

¡Problema resuelto!

He desarrollado una pequeñísima aplicación en c# que dada una imagen, extrae la orientación, la rota o voltea (rotate / flip) en consecuencia e informa al usuario de la operación que ha realizado sobre ella.

Podéis descargar la herramienta FixPictureOrientation desde este enlace.

Solo tenéis que descomprimirlo en alguna carpeta, ejecutar el exe y pulsar el botón de abrir y corregir foto, buscar la foto, abrirla… ¡y ya está!

Nota: Para ejecutar el programa deberéis tener instalado el .NET Framework 4 (si usáis Windows 7 o superior ya viene por defecto, y si no, casi seguro que lo tenéis de todos modos 😉 Si no fuera así, podéis descargar el instalador del .NET Framework 4 desde este enlace

El código c# es muy sencillo (obviando toda la parte de la extracción de datos Exif, cuyo código podéis encontrar en el sitio de codeproject que os puse arriba:

using Goheer.EXIF;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace FixPictureOrientation
{
	public partial class FrmPrincipal : Form
	{
		public FrmPrincipal()
		{
			InitializeComponent();
		}

		private void BtnCorregir_Click(object sender, EventArgs e)
		{
			OpenFileDialog dlg = new OpenFileDialog()
			{
				Filter = "Archivos de imagen JPG (*.jpg)|*.jpg",
				Title = "Abrir foto",
				ShowReadOnly = false,
				ShowHelp = false,
				InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer),
				CheckFileExists = true
			};
			DialogResult res = dlg.ShowDialog();
			if (res == DialogResult.Cancel) return;
			var rutaImagen = dlg.FileName;
			// Rotamos la imagen de acuerdo a los datos de exif
			Bitmap bmp;
			try
			{
				bmp = new Bitmap(rutaImagen);
			}
			catch (Exception)
			{
				MessageBox.Show("¡Pero qué me has dado! Esto no parece una imagen válida!", "¡Esto no se come!", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			try
			{
				var exif = new EXIFextractor(ref bmp, Environment.NewLine);
				if (exif["Orientation"] != null)
				{
					int iOrientacion;
					if (!Int32.TryParse(exif["Orientation"].ToString(), out iOrientacion))
					{
						MessageBox.Show("Los datos de orientación parecen no ser correctos. ¡Disculpa!", "Uups!", MessageBoxButtons.OK, MessageBoxIcon.Error);
						return;
					}
					string rotado;
					RotateFlipType tipoRotacion = GetDesiredRotation(iOrientacion, out rotado);
					if (tipoRotacion == RotateFlipType.RotateNoneFlipNone)
					{
						MessageBox.Show("¡Esta foto no necesita ninguna rotación! ¡Puedes usarla sin problemas!", "¡Esta ya está!", MessageBoxButtons.OK, MessageBoxIcon.Information);
						return;
					}

					bmp.RotateFlip(tipoRotacion);
					var equivalenciasExif = new Goheer.EXIF.translation();
					int idOrientacion = equivalenciasExif.Keys.OfType<int>().FirstOrDefault(k => equivalenciasExif[k].ToString() == "Orientation");
					// Reajustamos la propiedad de orientación para que coincida con la orientación actual.
					bmp.RemovePropertyItem(idOrientacion);
					exif.setTag(idOrientacion, "1");
					bmp.Save(rutaImagen, ImageFormat.Jpeg);
					MessageBox.Show($"¡Perfecto! La imagen se ha rotado {rotado}.", "¡Listo!", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
				else
				{
					MessageBox.Show("Lo siento, no hay datos de orientación en los metadatos de la foto... ¡No puedo detectar la posición de la fotografía!", "Me cachis!", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show($"Se produjo un error inesperado: {ex.Message}.", "Uuups.", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
			finally
			{
				if (bmp != null) bmp.Dispose();
			}
		}
		
		private void BtnSalir_Click(object sender, EventArgs e)
		{
			this.Close();
		}

		/// <summary>
		///  Devuelve la rotación que habría que realizar para que la foto rote a la posición adecuada en función del dato de orientación EXIF
		/// </summary>
		/// <param name="orientation"> Valor numérico de orientación que indica la orientación actual de la foto</param>
		/// <param name="rotationExplanation">Explicación de qué rotación se va a realizar</param>
		/// <returns>Uno de los valores de la enumeración RotateFlipType indicando la rotación que se debe aplicar</returns>
		private static RotateFlipType GetDesiredRotation(int orientation, out string rotationExplanation)
		{
			RotateFlipType rotationType;
			switch (orientation)
			{
				case 1: // fila 0 = arriba, columna 0 = lado izquierdo
					rotationExplanation = "0 grados";
					rotationType = RotateFlipType.RotateNoneFlipNone;
					break;
				case 2: // fila 0 = arriba, columna 0 = lado derecho
					rotationExplanation = "0 grados y volteo horizontal";
					rotationType = RotateFlipType.RotateNoneFlipX;
					break;
				case 3: // fila 0 = abajo, columna 0 = lado derecho
					rotationExplanation = "180 grados en sentido horario";
					rotationType = RotateFlipType.Rotate180FlipNone;
					break;
				case 4: // fila 0 = abajo, columna 0 = lado izquierdo
					rotationExplanation = "180 grados en sentido horario y volteo horizontal";
					rotationType = RotateFlipType.Rotate180FlipX;
					break;
				case 5: // fila 0 = lado izquierdo, columna 0 = arriba
					rotationExplanation = "90 grados en sentido horario y volteo horizontal";
					rotationType = RotateFlipType.Rotate90FlipX;
					break;
				case 6: // fila 0 = lado derecho, columna 0 = arriba
					rotationExplanation = "90 grados en sentido horario";
					rotationType = RotateFlipType.Rotate90FlipNone;
					break;
				case 7: // fila 0 = lado derecho, columna 0 = abajo
					rotationExplanation = "270 grados en sentido horario y volteo horizontal";
					rotationType = RotateFlipType.Rotate270FlipX;
					break;
				case 8: // fila 0 = lado izquierdo, columna 0 = abajo
					rotationExplanation = "270 grados en sentido horario";
					rotationType = RotateFlipType.Rotate270FlipNone;
					break;
				default:
					rotationExplanation = "0 grados";
					rotationType = RotateFlipType.RotateNoneFlipNone;
					break;
			}
			return rotationType;
		}

	}
}

¡Espero que os sea útil!

¡Un saludo!

Entrada visitada 497 veces

SonarLint. Analiza tu código mientras programas

Hace unos meses, en mi empresa montaron un departamento de QA (Quality Assurance o garantía de calidad), ¡y uno de los primeros proyectos que iban a auditar era uno de los míos! ¡Una hecatombe, desde que me enteré no podía ni dormir! ¡Iban a auditar mi código, a desmenuzarlo, a sacarme los fallos y a hacer un informe. ¡Horrible! Ya no podría generar código espagueti, ni crear funciones de 800 líneas, ni hacer copy paste sin parar, ni crear métodos con una complejidad ciclomática de siete cifras, ni crear siete variables llamadas a, b, c, d, e, f y g. ¡Estaba desolado! Ya veía el informe: 300 evidencias bloqueantes, 840 críticas, 1567 mayores, 8399 menores y 4300 informativas. ¡Pum! ¡Cataclán! SonarQube has crashed due a out of memmory while counting the critical bugs!

Lamentablemente, el concepto de calidad en el software, pese a estar muy documentado y cuya teoría todos conocemos en mayor o menor medida, es un aspecto del ciclo de desarrollo que, al menos en el entorno en el que me he movido, se ha tenido poco en cuenta. Puedo contar con los dedos de una mano los proyectos empresariales en los que me han permitido meterme a hacer testing para comprobar la calidad e integridad del software que había que entregar. Todo es para ayer, hay poco tiempo, los presupuestos hay que ajustarlos. Además, ¡los tests son de cobardes!

Gracias a dios, la cosa está cambiando, y parece que cada vez más, una parte del tiempo de desarrollo empieza a reservarse para pasar tests, reglas de calidad y en definitiva, aprender a mejorar la forma en la que desarrollamos nuestros proyectos.

Pensando en tener alguna herramienta que me ayudase a descubrir qué miserias aparecerían cuando auditaran mi proyecto, estuve buscando por internet, y encontré una herramienta muy interesante para VsStudio 2015 llamada SonarLint.

Su descripción en inglés dice:

SonarLint for Visual Studio is based on and benefits from the .NET Compiler Platform (“Roslyn”) and its code analysis API to provide a fully-integrated user experience in Visual Studio 2015.

SonarLint para Visual Studio se basa (y se beneficia) de la plataforma de compilación de .NET (“roslyn”) y su API de análisis de código, para proporcionar una experiencia totalmente integrada en Visual Studio.

Está desarrollado por la empresa Sonar Source, aunque tienen repositorio en Github, desde el que podréis contribuir en el proyecto si os apetece.

Esta extensión añade bastantes reglas de las que analiza el servidor SonarQube, aunque por el momento no tiene la capacidad de integrarse con él.
SonarLint es totalmente autónomo, y solo necesitamos la extensión para empezar a trabajar.

Lo bueno de esta extensión es que, al integrarse perfectamente con VisualStudio 2015, se beneficia de las características de accesibilidad del entorno, por lo que con JAWS 16 he conseguido trabajar con él perfectamente.
Lo que hace es enviarnos warnings a la lista de errores del entorno, y podremos hacer click (o enter) sobre cada uno para ir al fichero y línea en el que se está produciendo ese error.

Os pongo un ejemplo de código chungo y veréis todo lo que saca 😀

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;

namespace Kastwey.PruebasSonarLint
{
	class Program
	{
		static void Main(string[] args)
		{
			try
			{
				string nombre, apellidos, docIdentidad;
				nombre = "Juanjo";
				apellidos = "Montiel";
				if (nombre.ToLower() == "juanjo")
				{
					if (apellidos.ToLower() == "montiel")
					{
						// WriteLine("Línea comentada");
						WriteLine($"Nombre completo: {nombre} {apellidos}");
					}
				}
				ReadLine();

			}
			catch
			{
			}
		}
	}
}

Y aquí, el resultado, en tiempo de desarrollo (¡ni siquiera tenemos que compilar el proyecto!):

Severity Code Description Project File Line Suppression State
Warning S1118 Add a “protected” constructor or the “static” keyword to the class declaration. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 10 Active
Warning S1172 Remove this unused method parameter “args”. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 12 Active
Warning S1481 Remove this unused “docIdentidad” local variable. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 16 Active
Warning CS0168 The variable ‘docIdentidad’ is declared but never used Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 16 Active
Warning S1449 Define the locale to be used in this String operation. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 19 Active
Warning S1066 Merge this if statement with the enclosing one. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 21 Active
Warning S1449 Define the locale to be used in this String operation. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 21 Active
Warning S125 Remove this commented out code. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 23 Active
Warning S108 Either remove or fill this block of code. Kastwey.PruebasSonarLint d:\proyectos\Kastwey.PruebasSonarLint\Kastwey.PruebasSonarLint\Program.cs 31 Active

¡Espectacular!, ¿a que sí? 😉
Ahora, imaginad que por políticas de vuestro desarrollo, os piden que en algunas funciones pongáis un pequeño ejemplo de uso con parámetros fictícios, por lo que la regla de borrar el código comentado no se aplica aquí. ¿Qué podemos hacer?
Si pulsáis con el botón derecho (o tecla aplicaciones) en el aviso, veréis un submenú que dice: “Suppress”, y dentro, tenemos dos opciones: “Suppress in source” (suprimir en el código) o “suppress in suppression file” (suprimir en un archivo de supresiones).
Si pulsamos cualquiera de los dos, el aviso desaparecerá… Y fijaros qué hace si le decimos que nos elimine el error en el código:

#pragma warning disable S125 // Sections of code should not be "commented out"
						// WriteLine("Línea comentada");
#pragma warning restore S125 // Sections of code should not be "commented out"

Está utilizando preprocesadores para decirle a VsStudio: Este error no me lo muestres aunque la extensión te esté diciendo que sí.
Lo bueno de estos preprocesadores es que podemos aplicarlos, por ejemplo, a nivel de clases.
A mí me ha resultado útil para eliminar la regla de: “utilice sobrecarga de funciones en lugar de parámetros opcionales”, ya que en los controladores de MVC, usar parámetros opcionales es la única opción viable.
Y después de mirar cada error y arreglarlo, nuestro código sin errores podría quedar tal que así:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;

namespace Kastwey.PruebasSonarLint
{
	static class Program
	{
		static void Main()
		{
			try
			{
				string nombre, apellidos;
				nombre = "Juanjo";
				apellidos = "Montiel";
				if (nombre.Equals("juanjo", StringComparison.InvariantCultureIgnoreCase) && apellidos.Equals("montiel", StringComparison.InvariantCultureIgnoreCase))
				{
					WriteLine($"Nombre completo: {nombre} {apellidos}");
				}
			}
			catch (Exception ex)
			{
				WriteLine($"Error inesperado: {ex.Message}.");
			}
			ReadLine();
		}
		
	}
}

Y ya no me enrollo más. Probadla, que merece la pena, ¡y es gratis!

¡Que lo disfrutéis!

Entrada visitada 440 veces

Atributos HTML condicionales con Razor en MVC 4+

¡Hola!

Haciendo una vista en razor, tenía que pintar unas clases a los elementos li de una lista desordenada en función de algunas condicionales, así que se me ocurrió buscar la forma de poner atributos condicionales sin tener que usar sintaxis rebuscadas o duplicar código con un if dentro del HTML.
Lo que encontré me encantó por su simplicidad: Si usamos una variable nula dentro de un atributo HTML, razor obviará el atributo y no lo escribirá.
Ejemplo:

			<ul>
			@foreach (var modulo in Model.Modulos)
			{
				var claseCSS = (modulo.Completado ? "completed" : modulo.Actual ? "actual" : null);
				<li class="@claseCSS">
					<a href="@modulo.Link">
						@modulo.Titulo
					</a>
				</li>
			}
</ul>

		

Si la cadena tiene algún valor, se pintará la clase correspondiente en el elemento li. Si la cadena está a nulo, el atributo ni se pintará.
¿A que mola? 🙂

¡Nos vemos!

Entrada visitada 296 veces

ValidationSummary accesible en MVC 5

¡Hola!

Hoy os vengo con un truquillo que, en caso de estar desarrollando webs accesibles en MVC, os puede venir bien.

Una de las cosas que considero muy importantes a la hora de mostrar una lista de errores de cumplimentación de formularios, es encabezar la misma con una cabecera, de modo que el usuario de lector de pantallas, al navegar por la lista de encabezados, dé con el resumen de los errores de formulario de forma fácil. Si obviamos esta cabecera, es posible que esa lista pase desapercibida, o al menos es lo que me dice la experiencia 🙂

En MVC 4, hasta donde recuerdo, no existía la sobrecarga de ValidationSummaryFor que os muestro a continuación, y que sí está disponible en MVC 5:

@Html.ValidationSummary("Revisa los siguientes errores", new { @class = "text-danger" }, "h3")

Esta extensión de HtmlHelper pinta una lista de errores, añadiéndole la clase text-danger (estoy usando bootstrap), y encabezando la lista con una etiqueta “h3” que encerrará el mensaje pasado en el primer parámetro.

¿La única pega de todo esto? Que el encabezado con el mensaje se ve siempre aunque no haya errores 🙁 .
Sin embargo, este método pinta una clase por defecto cuando el formulario es válido: “validation-summary-valid”; así que con un poco de magia CSS, podemos añadir lo siguiente a nuestra hoja de estilos:

/* para ocultar los encabezados de los validation summary si son válidos */
.validation-summary-valid {
	display: none;
}

¿Más fácil? ¡Imposible!

Y ahora ya, como truquillo de nota, podríamos hacer una pequeña mejora a la experiencia del usuario, añadiendo, mediante jquery, una función que ponga el foco en la lista de errores cuando esta esté visible. Así, un usuario de lector de pantallas, escuchará el encabezado con el mensaje que hayamos puesto, y será consciente al instante de que algo pasa con ese formulario.
Añadamos este script a nuestro sitio (recordad que necesitaréis jquery):

$(function () {
	var encabezado = $(".validation-summary-errors:last :header");
	if (encabezado.length) {
		encabezado.attr("tabindex", "-1");
		encabezado.focus();
	}
});

¡

Et Voilà!

¿Qué estamos haciendo aquí?

  1. Adjuntándonos al evento onDocumentReady, para que esto se ejecute cuando la página esté cargada.
  2. Buscando el último elemento con la clase validation-summary-error, y dentro de él, cualquier encabezado, sea del nivel que sea.
  3. Si lo encuentra, le pone tabIndex a -1 para poder focalizar en él de forma programática.
  4. Y por último, fuerza el foco a ese encabezado.

A mí personalmente me gusta crear un bundle de scripts de accesibilidad, y meter en él pequeños ficheros, cada uno de los cuales realiza una acción concreta. Así tengo la sensación de que mi código es más fácil de mantener.

¡Espero que os sirva!

¡A disfrutar picando! 😉

Entrada visitada 383 veces

WCF. Usar excepciones nativas de .net en lugar de FaultException

¡Hola!

Para estrenar este blog, vengo con una entrada en la que os hablo de WCF, y de cómo propagar errores como Exception en lugar de usar FaultException.

¿Para qué puede servir esto?

Básicamente, en mi caso lo necesitaba para poder transmitir más información de la que por defecto FaultException transmite a la hora de lanzar un error.

Inconvenientes: Solo funcionará si tanto el servidor como el cliente están usando .net, pues esto por debajo usa Soap por interoperabilidad, pero al serializar exceptions, vamos a usar el NetDataContractSerializer; así que, ¡adiós a la interoperabilidad! 🙁

¿Ventajas? Si estamos trabajando con .net, podemos hacer cosas tan sencillas como:

try
{
    proxyCliente.RealizaOperacion();
}
catch (EntityException ex)
{
    // ...
}
catch (Exception exGeneral)
{
    // ...

Así, en nuestro error, podremos ver toda la información que el servidor posee sobre el mismo.

El artículo original es muy largo y está en inglés. Aquí os incluyo un adjunto como ejemplo para que veáis esto en código. Está compilado en Visual Studio 2010 (el original era de 2008), y queda bastante claro el ejemplo. De todas formas, aquí os dejo el artículo original en inglés.

¡Que os sirva! 🙂

WcfExtensions.zip

Entrada visitada 845 veces