====== Aplicaciones web con ASP.NET ====== ===== Acceso a datos ===== Desde VS2008 podemos crear controles enlazados a datos simplemente arrastrando tablas desde el administrador de servidores al editor de ficheros .dbml y utilizar este como fuente de datos. \\ \\ Entre los siguiente posteriormente nombrados también existe el [[fw:othersnet:dlinq#control_linqdatasource|LinqDataSource]]. ==== Carpeta App_Data ==== Podemos crear también una base de datos propia de nuestro proyecto, para ello agregaríamos el fichero .MDF y el .LDF (si tratamos con DB SqlServer) a la carpeta ''App_Data'' de la aplicación web. Este directorio es una ruta segura ya que su contenido no se hace público, puede sernos útil también para almacenar ficheros XML. A las cadenas de conexión para DB locales agregaremos la propiedad ''AttachDbFileName'', que especifica la ubicación del fichoer de DB. Si a la ruta ''|DataDirectory|'' esto se sustituirá por el path del directorio App_Data en tiempo de ejecución. La propiedad ''User Instance=true'' indica que cada usuario ejecutará un nuevo proceso para atacar a la DB. "server=(local)\SQLExpress;AttachDbFileName=|DataDirectory|MyDatabase.mdf;Integrated Security=true;User Instance=true" ==== SqlDataSource ==== El control SqlDataSource representa una conexión a DB que puede ser utilizada por los demás controles en la página. \\ Por ejemplo, en el siguiente código, un GridView sse enlaza a un SqlDataSource:
Propiedades útiles: * **ConnectionString**: Especifica la cadena de conexión a la base de datos. La cadena de conexión se puede especificar directamente en la página, pero en este caso hemos asignado dicha propiedad obteniendo el valor desde el fichero Web.config. * **SelectCommand**: Especifica la consulta a ejecutar para obtener los datos. * **SqlDataSourceMode**: Por defecto devuelve un objeto DataView desde un objeto DataSet que contiene los resultados de la consulta. Mediante esta propiedad podemos configurarlo para devolver los datos como un objeto DataReader. * **UpdateCommand** define la operación de actualización. * **DeleteCommand** define la operación de borrado. UpdateCommand o DeleteCommand deben contener parámetros de substitución para cada valor que pasarán otros controles enlazados a él, podemos especificar también una colección de UpdateParameters o DeleteParameters para establecer las propiedades de cada parámetro, tales como el tipo de parámetro, la dirección de entrada/salida o el valor por defecto. Para realizar un filtrado de datos necesitamos agregar objetos ''DataParameter'', estos permiten asociar valores externos a operaciones, variables o porpiedades. ... Los tipos que podemos asignar como parámetros son: * ''Parameter'': Clase base. * ''QueryStringParameter'': Parámetro de la query string. * ''ControlParameter'': Parámetro en un control. * ''SessionParameter'': Parámetro en una variable de sesión. * ''FormParameter'': Parámetro en un control HTML. * ''CookieParameter'': Parámetro en una cookie. * ''ProfileParameter'': Parámetro en el perfil. ==== ObjectDataSource ==== El ''ObjectDataSource'' enlaza objetos de forma similar a que el ''SqlDataSource'', tiene una propiedad ''TypeName'' que indica el tipo de objeto que realizará las operaciones y a la vez tiene propiedades como el ''SelectMethod'', el ''UpdateMethod'', el ''InsertMethod'' y el ''DeteMethod'' para realizar las tareas que describen. Es decir, una clase como la siguiente: public class MyProductsLayer { public DataView GetProducts(); public DataView GetProductsByCategory(String categoryName); public DataView GetProductsByID(int recordID); public int UpdateProduct(int recordID, String recordData); public int DeleteProduct(int recordID); public int InsertProduct(int recordID, String recordData); public UpdateProduct (Product p); } Puede enlazarse con un ''ObjectDataSource'' de la siguiente forma: ==== XmlDataSource ==== ==== Enlace de datos, Eval & Bind ==== Podemos utilizar los ''<%# %> '' para enlazar datos en una página .aspx, todas las expresiones de enlace de datos deben ir incluidas entro de estos carácteres. Ejemplos: * Propiedad Simple (sintaxis para un cliente): ''<%# custID %>'' * Colección (sintaxis para un pedido): '''' * Expresión (sintaxis para un contacto): ''<%# ( customer.First Name + " " + customer.LastName ) %>'' * Resultado del método (sintaxis para el balance pendiente): ''<%# GetBalance(custID) %>'' === Eval === ''Eval'' es una función estática de ''System.Web.UI.DataBinder'' que puede ser utilizada en archivos de code-inline dentro de un entorno de enlace de datos. Por ejemplo en un GridView, en un ''TemplateField'' añadimos el nombre completo de una persona concatenando distintos campos de un registro: <%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %> <%# Eval("LastName") %> ''Eval'' también permite formatear el resultado que va a mostrar: <%# Eval("BirthDate", "{0:MM/dd/yy}") %> ...

... <%# Eval("Price", "Special Offer {0:C} for Today Only!") %>
''Eval'' equivale a utilizar: <%# DataBinder.Eval(Container.DataItem, "field_name") %> El cual también puede ser usuado como (El método ''getEstatName'' devuelve un ''string''): <%# codegest.app_incidencies.incidencies.getEstatName(((ATMDB.ATM_INCIDENCIES_HISTORIC)Container.DataItem).estat) %> === Bind === ''Bind'' realiza la misma función que ''Eval'', pero se diferencia con este en que no solo es de lectura sino también de actualización, es decir, muestra los datos y si es necesario también los actualiza en el servidor. Si sólo queremos mostrar los datos utilizaremos ''Eval'' pero si también queremos que se actualicen ''Bind'': Sólo puede utilizarse con los controles ''GridView'', ''DetailsView'' y ''FormView''. ===== Controles de gestión de acceso a datos ===== ==== Masters details y el Details View ==== La táctica del master-detail consiste en selección de un control como "master" que indicará qué detalles se mostrarán en el\los details. \\ El GridView contiene la propiedad SelectedValue que indica la fila seleccionada, está contiene el primer valor del campo especificado en la propiedad DataKeyNames. Para permitir la selección la propiedad ''AutoGenerateSelectButton'' ha de estar a true o la ShowSelectButton del CommandField también como true. Una vez hecho esto la propiedad SelectedValue puede ser asociada a un ControlParameter y mostar los detalles en el control Details View, que presenta los valores de un registro de forma tabulada. \\ \\ ==== DetailsView ==== El control ''DetailsView'' también soporta la edición, para ello se utilizan las propiedades ''AutoGenerateEditButton'' o ''CommandField.ShowEditButton'' y la fuente de datos tendrá que soportar la actualización. \\ \\ Para que, por ejemplo, el ''DetailsView'' se enlace a un hipervínculo (''QueryStringParameter'') añadiremos un objeto ''HyperLinkField'' al grupo de columnas del ''GridView'', la propiedad ''Text'' de este será el texto a mostrar y la ''NavigateUrl'' la dirección a la que se irá tras clickar en el enlace. Esto generaría una url estática comú para todos los campos, por lo que mejor sería especificar un ''NavigateUrlFields'', su propiedad ''NavigateUrlFormatString'' espcifica el formato de la url donde {0} y {1} se sustituyen por los valores del campo. ... Una propiedad que tiene el DetailsView que no tiene el GridView es que puede insertar, para ello el DataSource enlazado debe permitir la inserción, la propiedad ''AutoGenerateInsertButton'' a true o añadir al grupo de campos del DetailsView un CommandField con el ''ShowInsertButton'' establecido a true. Para excluir un campo en el modo de Inserción, tendremos que establecer la propiedad ''InsertVisible'' del campo a false. ==== Control GridView ==== === Propiedades === * ''AutoGenerateColumns'' si se pone a false impedirá que se agreguen columnas automáticamente. * Para permitir la ordenación de datos a partir de una columna asignando la propiedad ''AllowSorting'' a true. Ésto provoca que el control GridView cree botones de enlazado para las cabeceras de sus columnas. El control GridView pasa la expresión ''SortExpression'' asociada con el campo de la columna al control de la fuente de datos para ello. * También podemos permitir la paginación poniendo la propiedad ''AllowPaging'' a true. * Podemos personalizar el estilo y la configuración del paginador configurando las propiedades ''PagerStyle'' y ''PagerSettings'' respectivamente. ''PagerStyle'' determina el aspecto del paginador, mientras que PagerSettings determina el tipo de paginación a usar (numérica o con botones Siguiente/anterior), la posición y otras opciones. * Para permitir la Actualización y el Borrado GridView, podemos establecer las propiedades ''AutoGenerateEditButton'' y ''AutoGenerateDeleteButton'' a true, o añadir un ''CommandField'' al control GridView y habilitar sus propiedades ''ShowEditButton'' y ''ShowDeleteButton''. * Cuando colocamos la propiedad ''ReadOnly'' de una columna a true estamos indicando que ese campo no podrá ser editable. === Tipos de columnas === * Podemos asignar tipos a las columnas agregando a la colección los siguientes tipos (la mayoría de los cuales hereda de ''DataControlField''): * ''BoundField'', columna enlazada a datos. * ''CheckBoxField'', columna enlazada a datos true\false. * ''ImageField'', columna enlazada a imágen. * ''HyperLinkField'', mediante la cual aparece un link. Podemos indicar: * El campo que indicará el texto con la propiedad: ''DataTextField''. * Hacia donde apuntará el link, con la propiedad ''DataNavigateUrlFormaString'' y si esta dependerá de un cambo de la DB también con la propiedad ''DataNavigateUrlFields''. * ''CommandField''. * ''ButtonField''. * ''TemplateField'', columna configurable. === Ejemplos === Ejemplo de ''GridView'' enlazado a datos: Ejemplo de ''GridView'' que se enlazará a una lista de objetos con una propiedad ''nombre'' y que mostrará también un combo. Este ''DropDownList'' no cambia, pero podría hacerlo si se escribe el evento ''OnRowCreated'' y cada vez que se ejecute se coge este control ''DropDownList'' y se cambia en código. Activo Inactivo ==== Como: GridView ==== === Coger el identificador de una fila en un campo no visible === Para ello deberemos utilizar la pripiedad ''DataKeyNames'' del GridView para indicar los campos clave: int id = GridView1.DataKeys["ID"].Value; * Por el índice: int id = GridView1.DataKeys[0].Value; * O al utilizar ''RowUdating'' o ''RowDataBound'': int id = GridView1.DataKeys[e.RowIndex].Value; === Llenar un GridView con objetos de tipo anónimo === Es decir, teniendo un código del estilo: GridView1.DataSource = from c in customers, o in c.Orders, total = o.Total where total >= 2000 select new {id = c.CustomerID, order = o.OrderID, total = total}; GridView1.DataBind(); En el ''RowCreated'' __no__ podremos hacer algo así: object element = e.Row.DataItem; int id = element.id; Lo que deberíamos hacer sería crear una clase, aunque sea interna: public class GridType { public int Id {get; set;} ... } Al agregarlo, en este caso con Linq: ... select new GridType {id = ...} Y en el ''RowCreated'': GridType element = e.Row.DataItem as GridType; int id = element.Id; === Indicar estilos === El siguiente grid tiene: * Definición de estilos para ''hyperlinkfields'', mediante ''ControlStyle-CssClass''. * Definición de estilos para headers, mediante ''HeaderStyle-CssClass''. * Definición de estilos para un BoundField, con ''ItemStyle-CssClass''. * Definición de estilos para headers con ordenación, con código CSS ''gridHeader a { color: white; text-decoration: none; } ''. === Pequeños === * **Alinear horizontalmente una columna**: ''this.grid.Columns[3].ItemStyle.HorizontalAlign = System.Web.UI.WebControls.HorizontalAlign.Right;'' * **Hacer que aparezcan las tags ''THEAD'' y ''TBODY''**: ''this.GridView1.HeaderRow.TableSection = TableRowSection.TableHeader;'' * **Formatear una fecha**: '''' ==== Repeater ==== Es un control al cual se le pasa como DataSource una lista de elementos y este las muestra, no en formato tabla como el grid sino en el formato que elija el programador. Para ello hay que rellenarlo con objetos ''Template'' siguientes: * ''ItemTemplate'', para cada línea. * ''AlternatingItemTemplate'', como el ''ItemTemplate'' pero alternando las líneas. * ''HeaderTemplate'', antes de la lista de ''ItemTemplate''. * ''FooterTemplate'', después de la lista de ''ItemTemplate''. * ''SeperatorTemplate'', elementos entre cada línea (entre ''ItemTemplates'' y ''AlternatingItemTemplate''). Por ejemplo un repeater que muestra en una tabla los elementos de una lista asignada como DataBound:
<%#DataBinder.Eval(Container.DataItem, "nombre") %>
Para crear Repeaters anidados, es decir, uno dentro del ItemTemplate de otro. En el primero definiríamos el evento ''OnItemDataBound'' o el ''OnItemCreated'' y en su método buscaríamos el Repeater anidado dentro del objeto creado: private void rCanciones_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e) { if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) { string disco = (string)e.Item.DataItem; string[] canciones = new string[]{"canción 1","canción 2","canción 3"}; Repeater rCanciones = (Repeater)e.Item.FindControl("rCanciones"); rCanciones.DataSource = canciones; rCanciones.DataBind(); } } Si quisieramos recoger los datos que se han mostrado en un control repeater podremos acceder a su colección de Items: foreach (var item in this.Repeater1.Items) { AtmUsers.AtmUserAppConf conf = (AtmUsers.AtmUserAppConf)((RepeaterItem)item).DataItem; DropDownList lPags = (DropDownList)((RepeaterItem)item).FindControl("pagList"); HiddenField id = (HiddenField)((RepeaterItem)item).FindControl("idPag"); } ===== Controles ===== ==== General ==== === Propiedad AutoPostBack === Es la propiedad que permite al control lanzar eventos mediante PostBack (recargando la página). === Propiedad Command === Podemos enviar un comando desde un control anidadado y que después será interceptado por el método ''OnCommand''; por ejemplo en un Repeater, desde un LinkButton anidado que en vez de cargar una página utilizando sus propiedades ''CommandName'' y la ''CommandArgument'' se indicará que se ha elegido, la primera propiedad es para indicar un string identificador del comando y la segunda para el argumento. \\ En el siguiente ejemplo se envia el comando ''LineEdition'' con el identificador del ''DataItem'' cargado dentro del Repeater . ... <%# DataBinder.Eval(Container.DataItem, "linia") %> Editar linia Luego, desde el método ''Repeater1_ItemCommand'' del code-behind: if (e.CommandName == "LineEdition") { int codi = Int32.Parse(e.CommandArgument.ToString()); ... ==== DropDownList==== Si vamos a agregar elementos de la base de datos a nuestro control DropDownList, a parte de los introducidos manualmente, tendremos que utilizar la propiedad ''AppendDataBoundItems'', sino estos se eliminarán. ==== Controles Web de usuario ==== Un Web User Control es un control que crea el programador y que se puede reutilzar en las páginas asp.net, son clases que heredan de ''System.Web.UI.UserControl'' que a su vez hereda de ''System.Web.UI.TemplateParser'' y se guardan en archivos .ascx. \\ Para utilizar el control en nuestro formulario web de usuario primero tendremos que registrarlo a partir de la tag ''Register''. La propiedad ''TagPrefix'' de esta indica un espacio de nombres único para el control dentro del .aspx, la ''TagName'' es el nombre por el cual nos referiremos a él y la ''Src'' es la página donde se encuentre el fichero. <%@ Register TagPrefix="Acme" TagName="Message" Src="pagelet1.ascx" %> Luego podremos utilizar el control haciendo: '''': Al igual que los web forms los user controls pueden contener code-behind. Si en este ponemos propiedades públicas podremos acceder a ellas a partir de las tags en el .aspx: \\ Código en el .ascx: <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="WebApplication7.WebUserControl1" %> Código en el .ascx.cs: public partial class WebUserControl1 : System.Web.UI.UserControl { public String texto = ""; public String Text { set { this.text = value; } get { return this.text; } } protected void Page_Load(object sender, EventArgs e) { this.lbl.Text = this.texto; } } Código en el .aspx: ... %@ Register TagPrefix="ctrl" TagName="webcontrol" Src="WebUserControl1.ascx" %> ... ... Luego también podremos agregarlo a partir del código: Control c1 = LoadControl("userctrl7.ascx"); ((UserCtrl7)c1).Category = "business"; Page.Controls.Add(c1); El tipo El nombre de la clase generada por el control lo podemos definir a partir de la propiedad ClassName de la tag Control: <%@ Control ClassName="UserCtrl7" %> ===== Como... ===== ==== Pequeños 'howto' ==== **Construir una url relativa**: ==== Descarga dinámica de ficheros ==== :!: Podemos crear un fichero a tiempo real y hacer que el usuario lo reciba: Response.Clear(); Response.Buffer = true; Response.AddHeader("content-disposition", "attachment;filename=" + nameFile + ".xls"); Response.Charset = ""; Response.ContentType = "application/vnd.ms-excel"; StringWriter sw = new StringWriter(); HtmlTextWriter hw = new HtmlTextWriter(sw); gv.RenderControl(hw); string style = @""; Response.Write(style); Response.Output.Write(sw.ToString()); Response.Flush(); Response.End(); Response.Clear(); Response.ContentType = "application/vnd.ms-excel"; Response.WriteFile("c:\\a.xls"); Response.Flush(); File.Delete("c:\\a.xls"); Response.End(); Ejemplos de cabeceras: Response.ContentType = "application/vnd.ms-excel"; Response.AddHeader("Content-Length", file.Length.ToString()); Response.AddHeader("Content-Disposition", "inline;filename=temp.txt"); Response.AddHeader("content-disposition", "attachment;filename=" + nameFile + ".xls"); ===== Combinar JavaScript ===== Aunque ASP.NET permite el uso de HTML standard, utilizarlo no aprovecha al máximo las capacidades de la tecnología de MS pero sin él nos es más complejo agregar código JavaScript. ==== Enviar o no un formulario ==== Tenemos la siguiente función JavaScript que valida si el campo firtName tiene algún valor introducido. Si es así retornará ''true'' si no mostrará un mensaje y retornará ''false''. ... El siguiente código agrega código a la función OnClick que devuelve lo que la función ''validate'' que acabamos de ver retorna: protected void Page_Load(object sender, EventArgs e) { this.Button1.OnClientClick = "return validate()"; } Otra forma de hacer lo mismo desde el ''code behind'': this.Button1.Attributes.Add("onClick", "return validate()"); ==== Un textbox que se escribe a la vez que otro ==== Ejemplo parecido al anterior. Tenemos una función JavaScript que copia el contenido de un textbox a otro: function copy() { var doc = document.forms[0]; doc.lastName.value = doc.firstName.value; } El código html\asp: Y el código que agrega el keypress: this.firstName.Attributes.Add("onkeyup", "copy()"); === Agregar el evento desde JavaScript === Y esa función sería: function load_body() { var doc = document.forms[0]; doc.firstName.onkeypress = copy; } O... function load_body() { var doc = document.forms[0]; doc.firstName.onkeypress = function copy2() { var doc = document.forms[0]; doc.lastName.value = doc.firstName.value; } } === Con jQuery === ==== PostBack desde JavaScript ==== ASP.NET crea código en JavaScript que realiza la acción de PostBack, de esta forma los controles web pueden llamarlo. Desde código no gestionado por ASP.NET podemos llamarlo de la siguiente forma: el nombre de la función que hace el PostBack es ''__doPostBack'' y se le pasan dos argumentos: quien hace el PostBack y con qué argumento: __doPostBack('__Page', 'MyCustomArgument'); Aún así no es recomendable llamar a este método directamente, lo mejor es hacer que ASP.NET genere su llamada, para ello podríamos crear en el code behind una variable protegida: protected string strPostBack; Y en el mismo método ''PageLoad'' podremos crear la llamada dinámicamente a partir del método: ''Page.ClientScript.GetPostBackEventReference''. this.strPostBack = Page.ClientScript.GetPostBackEventReference(this, "argumentoTest"); Y en el código JavaScript\jQuery: Luego podremos capturar este argumento de la siguiente forma: if ((Page.IsPostBack) && (Request["__EVENTARGUMENT"] == "argumentoTest")) this.clickmeupdate.InnerText = "Se ha hecho PostBack"; ==== Id's y páginas maestras ==== Para saber la id de un control posicionado en una página que utiliza master pages usaremos la propiedad ''ClientID'' del control: var edited = $('#<%= fieldEddited.ClientID %>').val(); ===== Otros ===== ==== Code Snippets ==== === LinkButton de eliminar con confirmación en cliente === === Clase HttpHandler que devuelva una imágen dinámicamente === public class Handler1 : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/jpeg"; System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(100, 100); System.Drawing.Brush b = new System.Drawing.SolidBrush (System.Drawing.Color.FromArgb(255, 0,0)); System.Drawing.Graphics.FromImage(bmp).FillRectangle(b, new System.Drawing.Rectangle(0, 0, 100, 100)); bmp.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg); } public bool IsReusable { get { return false; } } } Aunque también lo podrías poner en una página: <%@ Page ContentType = "image/jpeg"%> <%@ Import Namespace = "System.Drawing" %> <%@ Import Namespace = "System.Drawing.Imaging" %> ==== Notas ==== * Si un formulario retorna tags xml ASP.NET se quejará por inseguridad, para evitarle agregaremos en la directiva ''Page'' inicial del aspx: ''ValidateRequest="false"''. * Para no liarse con los estados de página de ASP.NET y con los LinqSources y los ''DetailsView'' puedes intentar trabajar como si dichos estados no existiesen, es decir, utilizar el código necesario en los eventos ''OnInserting'', ''OnInserted'', ''OnUpdating''... Olvidarse de si es //callback//, y mediante los ''Response.Redirect'' indicar en la QueryString si se llama a la página para editar, insertar... Y configurar el ''DetailsView'' mediante el método ''ChangeMode'' en la sobreescritura del método ''OnPreRender''. * Si queremos forzar a un ''GridView'' (o ''Repeater'') para que se muestre sin que este tenga resultados le asignaremos una ''DataTable'' vacía: System.Data.DataTable table = new System.Data.DataTable(); table.Rows.Add(table.NewRow()); this.Repeater1.DataSource = table; this.Repeater1.DataBind();