Cuando trabajando con aplicaciones multiidioma, lo normal y lo recomendable es hacerlo mediante archivos de recursos localizados (*.resx, *.en-resx, *.es-mx.res)
http://msdn.microsoft.com/es-es/library/ms227427.aspx
Esto requiere de un poco de tacto, ya que debemos tener las mismas claves en los diferentes archivos de recursos.
Añadir, modificar o borrar un recurso cuando gestionas más de 3 recursos puede ser un poco tedioso.
Pensaba crearme una herramienta para poder gestionar varios recursos sincronizados a la vez, pero me dio por buscar y encontré una herramienta que ya hacía esto. (afortunadamente o desgraciadamente, ya está todo inventado)
Se trata de Zeta Resource Editor.
http://www.codeproject.com/Articles/16068/Zeta-Resource-Editor
Tiene incluso opción de traducción automática por Bing o Google Translator.
Espero que os sea útil!!!
Saludos.
Blog con tutoriales y comentarios acerca de las tecnologías .Net en particular y de la informática en general.
lunes, 21 de mayo de 2012
jueves, 17 de mayo de 2012
Generación de clases POCO
Estoy haciendo un curso de desarrollo de Asp.net y así me desintoxico de tanto winforms
impartido por CampusMVP (altamente
recomendable!!!) y me ha surgido una duda.
Estaba viendo los DataAnnotations
para un ejemplo en MVC y me planteaba el poder generar las clases “buddy”
automáticamente del Entity Framework en vez de crearlas de nuevo para aplicarles
las annotations. Pensaba en esas tablas
de más de 30 campos que suelen haber en escenarios reales.
No me acordaba que el Entity Framework es un generador automático
de código.
Con Entity Framework y la plantilla adecuada se pueden
generar clases POCO, (Plain Old CLR Objects) o, traducido, Objetos simples
y planos.
Entity Framework es un generador de código
El Entity Framework, cuando se compila, genera un
“code-behind” del archivo EMDX (con extensión Desiger.cs) que contiene un
código autogenerado a partir del modelo de entidades, que son en realidad los objetos que manejamos cuando trabajamos con él.
Si buceamos un poco dentro de ese archivo, encontraremos las
regiones “Contextos” y “Entidades”.
Dentro de cada entidad,
“Métodos de generador”, “Propiedades primitivas” y "Propiedades de
navegación".
Dentro de "Propiedades primitivas" tenemos las
propiedades de la clase, aunque algo enmarañadas con el código que genera el
Entity Framework, y que tanto papel nos hace (guardado, recuperación,
intellisense, tipado, etc...)
Cambiando el generador de código por defecto.
Como hemos visto, el Entity Framework genera código. Podemos
cambiar el generador de código por defecto por uno que funcione como queremos,
o incluso, crear nosotros mismos las plantillas de generación de código.
(Plantillas T4)
De hecho, el Entity Framework usa plantillas T4 para generar
el modelo de entidades.
En la ventana de propiedades del Entity Framework, le
estamos indicando que genere el código usando la plantilla T4
"SSDLToSQL10.tt"
Podemos cambiar el generador de código pulsando botón
derecho en algún hueco del explorador de entidades del EF, y eligiendo
"Agregar elemento de generación de código".
Nos aparecerán los generadores de código que tenemos
instalados (El generador por defecto de Entity Framework y el generador de STE(Self-Tracking
entities)).
Si pulsamos sobre Plantillas en línea, se muestran las
plantillas disponibles a descargar.
Sobre el menú Base de datos, elegiremos EF 4.0 DBContext
Generator for C# (me imagino que también habrá para VB.NET)
Al instalar esta
plantilla se guardan en nuestro proyecto 2 plantillas t4 vinculadas al modelo
de Entity Framework. Una contiene el contexto y otra las entidades.
TACHAAAAN. Ya tenemos generadas
las entidades POCO
Detalle del contexto.
public
DbSet<Category> Categories { get; set; }
public
DbSet<CustomerDemographic>
CustomerDemographics { get; set; }public DbSet<Customer> Customers { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<Order_Detail> Order_Details { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Region> Regions { get; set; }
public DbSet<Shipper> Shippers { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Territory> Territories { get; set; }
Detalle de una entidad
public partial class Employee
{public Employee()
{
this.Employees1 = new HashSet<Employee>();
this.Orders = new HashSet<Order>();
this.Territories = new HashSet<Territory>();
}
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Title { get; set; }
public string TitleOfCourtesy { get; set; }
public Nullable<System.DateTime> BirthDate { get; set; }
public Nullable<System.DateTime> HireDate { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string HomePhone { get; set; }
public string Extension { get; set; }
public byte[] Photo { get; set; }
public string Notes { get; set; }
public Nullable<int> ReportsTo { get; set; }
public string PhotoPath { get; set; }
public virtual ICollection<Employee> Employees1 { get; set; }
public virtual Employee Employee1 { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual ICollection<Territory> Territories { get; set; }
}
Para que nos compile esta
plantilla sería necesario tener Framework 4.1 (usa System.Data.Entity.Infrastructure),
y encima tenemos deshabilitado el
generador predeterminado de Entity Framework para que no se generen entidades
duplicadas, pero para lo que queremos, que es obtener las clases POCO de
nuestras entidades, no es necesario.
Volvamos al generador de código por defecto.
¿Qué ha pasado con el Entity Framework?
Las nuevas plantillas están usando las entidades del EF,
pero éste está deshabilitado.
Fijémonos en las clases generadas por el Entity:
// La generación de código
predeterminada está deshabilitada para el modelo 'C:\*******\MvcApplication1\MvcApplication1\Models\Model1.edmx'.
// Para habilitar la generación de código
predeterminada, cambie el valor de la propiedad del diseñador 'Estrategia de
generación de código'
// por otro valor. Esta propiedad está
disponible en la ventana Propiedades cuando se abre
// el modelo en el diseñador.
Para deshacer el camino, borramos las dos plantillas T4 que
ha creado la plantilla (los ficheros *.tt) y cambiemos la propiedad del Entity
Framework "Estrategia de generación de código" a
"Predeterminados"
Con esto hemos conseguido generar unas clases POCO que nos
pueden servir para muchas cosas, como para las validaciones con MVC y
DataAnnotations, clases para pasarlas entre capas, etc.
Saludos!!!
miércoles, 16 de mayo de 2012
Comprendiendo el Viewstate de Asp.net
El documento “Understanding Asp.net Viewstate” de Scott
Mitchell (del año 2004) explica claramente y paso por paso cómo actúa el viewState.
Como él dice, el viewstate ha sido el mayor causante de los
problemas de los desarrolladores de ASP.NET.
Como son más de 20 páginas de texto, y me sigue causando
verdaderos problemas en mis desarrollos porque todavía no lo tengo asimilado,
me animo a crear un resumen traducido.
Espero que os sirva!
El ciclo de vida
Cada vez que llega una petición a un servidor web, lo
intercepta el IIS (v6) y se lo pasa al aspnet_isapi.dll (el motor de asp.net).
Pasa por los diferentes HttpModules (filtros, BeginRequest, … ) y llama al Http
Handler ProcessRequest(), que es el método desencadena el ciclo de vida de una
página.
Al final del ciclo, se devuelve al IIS una página HTML, que
se manda al cliente.
Centrémonos en las fases del ciclo de vida de una página,
una vez se llama al ProcessRequest.
Etapa 0: Instanciación.
Las páginas están hechas de tags html y tags
runat=”server”.
En la instanciación se crea una jerarquía de controles de
página de la página asp.net solicitada.
Si hay valores asignados declarativamente, también se
asignan en el árbol de jerarquía.
<html>
<body>
<h1>Welcome to my Homepage!</h1>
<form runat="server">
What is your name?
<asp:TextBox runat="server"
ID="txtName"></asp:TextBox>
<br />What is your gender?
<asp:DropDownList
runat="server" ID="ddlGender">
<asp:ListItem Select="True"
Value="M">Male</asp:ListItem>
<asp:ListItem
Value="F">Female</asp:ListItem>
<asp:ListItem
Value="U">Undecided</asp:ListItem>
</asp:DropDownList>
<br />
<asp:Button runat="server"
Text="Submit!"></asp:Button>
</form>
</body>
</html>
Este árbol de jerarquía se almacena en la carpeta WINDOWS
\Microsoft.NET\Framework\
version\Temporary ASP.NET Files
como archivo .cs o .vb
Etapa 1: Inicialización.
La página y sus controles lanzan sus eventos INIT.
Con respecto al viewstate, esto es importante ya que:
-
Los controles de servidor no empiezan a seguir
los cambios en el viewstate hasta el final de la fase de inicialización.
-
Cuando añadimos controles dinámicos que
necesiten usar el viewstate, esos controles de tendrán que añadir en el evento
Init, en vez de en el Load.
Etapa 2: Carga del ViewState
La etapa de LoadViewState solo ocurre cuando se produce un
postback.
Durante esta etapa, los datos del viewstate que han sido
guardados en la visita de la página previa son cargados y, recursivamente,
rellenados en la jerarquía de controles.
Es durante esta fase cuando el viewstate es validado. El
viewstate podría venir inválido por muchas razones, como manipulaciones,
inyecciones de controles dinámicos, etc.
Etapa 3: Carga de datos de Postback
La etapa de la carga de los datos del Postback solo ocurre
cuando se produce un postback.
En esta etapa, se sobrescriben los valores en la jerarquía
de controles con los valores que vienen
en el form, llamando a cada función su método LoadPostData()
Es decir, si tenemos un
<input type="text" id="txtName" name="txtName"
/>
y el usuario ha introducido un “Hello, world” en el
input, y se realiza el postback, el dato
“hello world” pasa al servidor en la cabecera del HTTP POST, en el que también
viaja el viewstate.
Se carga en la jerarquía de controles el control Input con
el valor “”
Se lanza el LoadViewState. Si hubiera algún valor previo en
el viewstate, se le asignaría el valor en la jerarquía.
Se lanza el
LoadPostbackData y asigna al control el valor “Hello world”
Etapa 4: Load
Cuando se lanza el evento Load (el famoso Page_Load), el
viewState ha sido cargado junto con los datos del formulario.
Etapa 5: Lanzamiento de eventos Postback
Cuando se está realizando un postback, en esta fase es donde
los controles procesan sus eventos de postbacks (selectedindexchanged,
button_click, …)
Etapa 6: Guardado del ViewState.
En esta etapa, la clase Page construye el viewstate de la
página, que representa el estado que debe de persistir entre postbacks.
El Page llama recursivamente al método SaveViewState de cada
control en toda la jerarquía. Luego, el resultado se serializa en un string
codificado en Base64.
Etapa 7. Representación. (Render)
Etapa de representación, se llama recursivamente al método
RenderControl de cada control, y se genera el HTML que se enviará al cliente.
Estas 7 etapas son las más importantes para entender el
viewstate. A medida que continúes
leyendo el artículo, ten en mente que cada vez que se solicita una página
asp.net o se produce un postback, se procesan estas etapas.
El papel del ViewState
El objetivo del viewstate es persistir el estado a través de
postbacks.
Si no hay cambios en un control, no se crea viewstate. Lo que se guarda en el viewstate son los
cambios que ha habido en un control desde su inicialización.
Los cambios hechos en programación también se deben de
guardar en el viewstate.
Supongamos el siguiente caso:
<asp:Label runat = "server" ID = "lblMessage"
Font-Name="Verdana" Text="Hello,
World!"></asp:Label> Font-name =
"Verdana" Text = "Hola, Mundo!"/>
<br /> <br />
<asp:Button
runat="server" <asp:Button runat =
"server"
Text="Change
Message" ID="btnSubmit"></asp:Button> Text = "Cambia el mensaje de"
ID = "btnSubmit"/>
<br /> <br />
<asp:Button
runat="server" Text="Empty Postback"></asp:Button> <asp:Button runat="server" Text="Empty Postback"/>
private void btnSubmit_Click (object sender,
EventArgs e)
{ {
lblMessage.Text = "Goodbye,
Everyone!"; lblMessage.Text = "Adiós a todos!";
} }
ViewState y la adición dinámica de controles.
Muchas veces se crean dinámicamente controles. (por programación).
Recordemos que la jerarquía de controles es creada y las
propiedades declarativas se crean en la fase de instanciación.
Más tarde, en la etapa de carga del viewstate, el estado
también fue alterado.
Pensando un poco sobre esto:
-
Como el viewstate sólo persiste los cambios
entre postbacks, los controles creados dinámicamente deben ser añadidos a la
página asp.net, tanto en la página inicial como en los consecuentes postbacks.
-
Los controles dinámicos son añadidos a la
jerarquía en el code-behi nd, y por lo tanto se añaden en algún momento después
de las etapas de instancias
-
El viewstate de los controles se guardan
automáticamente en la etapa del guardado de ViewState.
Por lo tanto, los controles creados dinámicamente por
programación deben de ser añadidos a la página en cada visita de la página. El
mejor momento para añadir estos controles son en la inicialización de la página
(evento Init), que ocurre, antes de la carga del viewstate. Es decir, queremos tener una jerarquía de
controles completa antes de que llegue la carga del viewstate.
Nota: Puede ser que la carga de controles dinámicos en el
evento Load funcione, ya que la propiedad Controls.Add(dynamicControl) carga el
viewstate del control y de sus controles hijos.
La propiedad ViewState
Cada control se encarga de almacenar su estado. La propiedad ViewState (que no es lo mismo que el concepto ViewState) está definida en la clase property is defined in the System.Web.UI.Control, lo que significa que todos los controles tienen esta propiedad disponible.
Usando reflector y fijándose en una propiedad, se aprecia que se cambia directamente en la propiedad viewstate, y que se lee de la propiedad viewstate en primer lugar.
public string NavigateUrl
{
get
{
string
text = (string) ViewState["NavigateUrl"];
if
(text != null)
return text;
else
return string.Empty;
}
set
{
ViewState["NavigateUrl"] =
value;
}
}
Medir el tiempo del seguimiento del viewstate.
Recordemos que dijimos que el viewstate sólo almacena
estados que necesitan ser persistidos a través de postbacks.
Es decir, que si tenemos un control hyperlink, y
declarativamente asignamos su NavigateUrl, esta información no necesita
guardarse en el viewstate. Pero si en algún sitio ponemos
HyperLink1.NavigateUrl =
http://www.ScottOnWriting.NET,
esta
información se almacenará en el viewstate.
Si esto mismo se hace en la inicialización, antes de la
llamada a la función TrackViewState(), no se guarda en el viewstate. ¿Por qué
esta diferencia?
Los cambios en el ViewState se empiezan a auditar desde la
llamada al TrackViewState() hasta la llamada al SaveViewState().
Así pues, el método TrackViewState() permite empezar a
gestiona el viewstate una vez instanciada e inicializada la página.
Guardando información en la propiedad ViewState de la página
Como Page hereda de Control, también tiene su propiedad
ViewState. De hecho, puedes usar esta propiedad para guardar información a
través de postbacks.
El coste del viewstate
-
Durante las visitas a todas las páginas, la
clase page reúne todos los viewstates de todos los controles en la jerarquía de
controles, y lo serializa en codificación Base64. (el control Hidden
__VIEWSTATE).
Paralelamente, para cada postback, se tiene que deserializar y actualizar los estados de los controles en la jerarquía de congtroles.
Paralelamente, para cada postback, se tiene que deserializar y actualizar los estados de los controles en la jerarquía de congtroles.
-
El campo hidden __VIEWSTATE añade tamaño extra a la página web que el
cliente debe de descargarse. También, para cada postback, el control
__ViewState tiene que enviarse al servidor en la cabecera HTTP POST,
incrementando el tiempo.
Por todo esto, debemos de ser selectivo, indicando los
controles que no queremos que guarden su viewstate.
Como otra alternativa, también podríamos almacenar en el
servidor el viewstate.
Deshabilitando el viewstate.
En la etapa de guardado de ViewState, se llama a cada
control de la jerarquía de controles, invocando al método SaveViewState() de cada control. Si a un control le
establecemos la propiedad EnableViewState a false, se evitará que se llame al
SaveViewState() de este control y el de sus
hijos.
Por lo tanto, si queremos desactivar el viewstate de toda la
página, estableceremos Page.EnableViewState a false, o incluso con la directiva
<
%@Page
EnableViewState="False" %>
No todos los controles almacenan la misma
cantidad de información en el viewstate. Un label no tiene gran impacto en el
tamaño del viewstate. Sin embargo, un datagrid almacena toda la información en
el viewstate.
La pregunta es:
¿Cuándo puedo poner el EnableViewState a false?
Cuando no necesites recordar el estado del control entre
postbacks.
El DataGrid guarda su contenido en el
viewstate, así que en cada postback no hay que volver a sacar los datos y
enlazarlos.
Especificar dónde persistir el viewstate.
Después de que la página haya recopilado toda la información
de estado de la jerarquía de controles, la persiste en el control hidden
"__VIESWTATE". Éste es serializado al campo llamando al método de
Page SavePageStateToPersistenceMedium() durante la etapa de guardado del
viewstate.
Podríamos crearnos una clase que derive de Page y sobrescribir
los métodos LoadPageStateFromPersistenceMedium() y
SavePageStateToPersistenceMedium().
El viewstate es serializado y deserializado usando la clase
System.Web.UI.LosFormatter y está pensado para serializar eficientemente cierto
tipo de objetos a codificación Base64 (strings, integers, boooleans, arrays,
arraylists, hashtables, pairs, triplets)
Si sobrescribimos SavePageStateToPersistenceMedium(),
necesitaremos usar LosFormatter para serializar el viewstate y guardarlo en un
fichero del servidor web. Podemos usar
las siguientes aproximaciones:
- Montar
un buen sistema de nombres de ficheros. Ya que el viewstate de una página
variará según la interacción del usuario con la página, el viewstate guardado
debe ser único por cada página y usuario.
- Borrar los ficheros de viewstate cuando no sean
necesitados.
Podríamos llamar a los ficheros basándonos en el SessionID
del usuario unido al nombre de la URL. Esto no funcionaría con usuarios con las
cookies deshabilitadas.
Otra solución pasaría por usar un identificador único (GUID)
como nombre de fichero, guardando este guid en un campo oculto de la página
web.
Un ejemplo de cómo persistirlo sería:
public class
PersistViewStateToFileSystem : Page
{
protected override void
SavePageStateToPersistenceMedium(object viewState)
{
// serialize the view state into a base-64 encoded string
LosFormatter los = new LosFormatter();
StringWriter writer = new StringWriter();
los.Serialize(writer, viewState);
// save the string to disk
StreamWriter sw = File.CreateText(ViewStateFilePath);
sw.Write(writer.ToString());
sw.Close();
}
protected override object LoadPageStateFromPersistenceMedium()
{
// determine the file to access
if (!File.Exists(ViewStateFilePath))
return null;
else
{
// open the file
StreamReader sr = File.OpenText(ViewStateFilePath);
string viewStateString =
sr.ReadToEnd();
sr.Close();
// deserialize the string
LosFormatter los = new LosFormatter();
return
los.Deserialize(viewStateString);
}
}
public string ViewStateFilePath
{
get
{
string folderName =
Path.Combine(Request.PhysicalApplicationPath,
"PersistedViewState");
string fileName = Session.SessionID +
"-" +
Path.GetFileNameWithoutExtension(Request.Path).Replace("/",
"-") + ".vs";
return Path.Combine(folderName,
fileName);
}
}
}
Analizando el ViewState
Recorrer el estado de la vista mediante programación
Viewstate e implicaciones de seguridad.
El viewstate es fácilmente decodificable, pudiendo ver su
contenido.
Por tanto, podría ser manipulado o podríamos llegar a ver
passwords o connectionstrings.
Por ello, el LosFormatter añade una MAC (machine autentication
check) o hash en las codificaciones.
En la Page se puede configurar usando la propiedad EnableViewStateMac. En el machine.config
podemos especificar el tipo de codificación de esa hash
También podríamos cifrar el viewstate usando TripleDES. Para
indicarlo, en el machine.config , <machinekey>, atibuto Validation, poner 3DES.
Tambien el <machinekey> contiene los atributos
ValidationKey y DescryptionKey. ValidationKey informa de la clave usada para
generar las hash de la MAC, y DescryptionKey indica la clave usada para el
cifrado tripleDES.
Por defecto, las claves se autogeneran solas(AutoGenerate,IsolateApp,) pero en granjas de servidores conviene
cambiarlas y que todos los servidores compartan las claves.
La propiedad ViewStateUserKey
Esta propiedad, si es usada, se le debe asignar un string en
el evento Page_Init. El objetivo de la propiedad es asignar una clave
específica del usuario (username). El viewstateuserkey es usado como clave sal
para codificar la mac.
La propiedad ViewStateUserKey protege contra los casos en los que un
usuario malintencionado visita la página, captura el viewstate, y luego otro
usuario entra con el viewstate capturado.
Suscribirse a:
Entradas (Atom)