domingo, 28 de octubre de 2007

(SQLTableProfileProvider) Uso de un proveedor de perfiles personalizado

Todos sabemos lo fácil de implementar que son los controles en ASP.Net, y en parte es eso lo que mas nos atrae(hasta cierto punto), con la promesa de comenzar a producir de manera casi inmediata, con una mínima inversión de tiempo en aprendizaje; desgraciadamente, no siempre se puede adecuar a las necesidades particulares de todos.

Es así como es muy común verse en la necesidad de "ampliarlos" o "extenderlos" para que se ajusten a nuestras necesidades, y sobre eso voy a hablar en las próximas lineas.

Como base en la creacion de sitios u aplicaciones web, es indispensable(generalmente) mantener un registro de usuarios y limitar su acceso al sitio en relación a sus credenciales, y en esto ASP.net nos facilita la vida mediante el uso de roles y membresías.

Por otra parte, muchas veces nos encontramos con la necesidad de almacenar datos de usuarios diferentes a aquellos que vienen por defecto en el control CreateUserWizard de ASP.net. La solución? extenderlo para que se adecue a nuestras necesidades.

Antes de embarcarnos en como alterar nuestra configuración para poder almacenar datos adicionales a los más comunes y ya incluidos por defecto(nombre,pass,mail,pregunta y respuesta secreta) es imperativo que veamos que tipo de tratamiento vamos a necesitar aplicar sobre ellos.

El proveedor de perfiles que ASP.net posee por defecto es fácilmente ampliable, solo con un par de líneas de código en el web.config y un par más en el code behind tenemos un proveedor de perfiles que puede almacenar toda la información que queramos. El problema con esto es la manera en la que se almacena dicha información en la base de datos, y si luego necesitamos hacer una consulta a la tabla aspnet_profiles nos daremos contra una verdadera pared.

El proveedor guarda la información de perfil de la siguiente manera:

Field Name Values
PropertyNames FirstName:S:0:6:LastName:S:7:6:
PropertyValuesString Andrew Rimmer
PropertyValuesBool Null

Donde, PropertyNames son los nombres de las "propiedades" o información extra que estamos agregando, PropertyValuesString almacena los valores de los strings y PropertyValuesBool los valores booleanos de la propiedad.


En PropertyNames, tengo el valor del primer campo "FirstName", seguido por el tipo "S" de String(Andrew es un string), cuyo valor inicia en la posición 0 y va hasta la 6(Andrew tiene 6 caracteres), seguido por LastName que se analiza de manera análoga. En la columna PropertyvaluesString se almacena el valor propiamente dicho de los strings (Andrew para FirstName), y en la PropertyValuesBool se almacena el valor de las propiedades booleanas si las tuviera. Si esto en un embrollo con solo 2 campos adicionales agregados, imagínense lo que sería si necesitamos guardar 10.

Por lo anterior, o hacemos un complejo tratamiento de strings para obtener la información, o implementamos nuestro propio proveedor de perfiles.

A continuación voy a mostrarles como hacer ambas cosas, por un lado voy a extender el proveedor de perfiles que ya viene con ASP.net, y luego voy a mostrarles como implementar uno propio, que almacena los valores de las propiedades en una tabla personalizada que nosotros mismos creamos y cuya disposición determinamos nosotros.

Extensión del Proveedor de datos por defecto de ASP.net:

Primero modificamos nuestro web.config de la siguiente manera:

Dentro de la declaración de profile, y luego de el cierre del tag providers, agregamos properties y procedemos a añadir allí cada uno de los campos de información extra que necesitamos de acuerdo a nuestras necesidades:
<profile defaultProvider="SqlProvider">
<providers>
<clear />
<add name="SqlProvider" type="System.Web.Profile.SqlProfileProvider"
connectionStringName="SqlServices"
applicationName="MiAplicacion"
description="SqlProfileProvider"/>
</providers>
<properties>
<add name="Firstname" type="string"/>
<add name="Lastname" type="string"/>
</properties>
</profile>

Una vez hecho esto agregamos un poco de codigo en el Code behind:
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
p.Firstname = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Firstname")).Text;
p.Lastname = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Lastname")).Text;
p.Save();
}

Dentro del evento created User del CreateUserWizard creamos un profileCommon p, el cual ya tendrá las propiedades agregadas al webconfig, por lo que lo único que debemos hacer es localizar aquellos controles que agregamos a nuestro CreateUserWizard (en este caso textboxes) y asignar los valores correspondientes. Luego de esto doy la indicación de que el perfil se salve, y voila tengo almacenada información extra sobre mi usuario.

Implementación de un proveedor de perfiles personalizado:

Existe un trabajo por parte de un mienbro del ASP.net team de microsoft que nos permite implementar un proveedor personalizado de perfiles (AQUI podrán descargar los archivos necesarios así como los papers para ver como instalarlo).

Este hombrecito nos provee de 2 proveedores el SQLTableProfileProvider y el SQLStoreProcedureProfileProvider. Para nuestros fines utilizaremos el primero (sientanse libres de explorar sobre el segundo) que básicamente me permite almacenar los datos del perfil en una tabla creada personalmente, con la disposición de campos que desee, con la única condición necesaria de incluir UserID y LastUpdatedDate, necesarios para la gestión interna de ASP.net.

OK, lo primero que hacemos es crear la tabla en la base de datos, la cual podemos llamar como queramos, en nuestro ejemplo se llamará "Perfil", recuerden colocar los permisos de la tabla de manera correcta

grant SELECT,INSERT,UPDATE,DELETE on dbo.Perfil to [SuNombreDeMaquina\ASPNET]

go

Como el SqlTableProfileProvider accede a la tabla directamente, deben asegurarse de permitir los derechos de SELECT, INSERT, UPDATE y DELETE a la cuenta correspondiente del web server. En el ejemplo se usa la cuenta ASPNET puesto que es ésta la cuenta de procesos ASP.net en una PC con Windows XP. Con seguridad integrada se utiliza el proceso identity para acceder a la Base de Datos. Si estan usando Windows Server 2003 e IIS6 entonces NETWORK SERVICE es la cuenta a utilizar. Finalmente si solo usan seguridad standar SQL, deben conceder los derechos a la cuenta de usuario SQL correspondiente.

La última parte de configuración a nivel de Base de datos es la de conceder derechos a algunos procedimientos almacenados de ASP.net que son automticamente instalados en su BD cuando se instalan los esquemas para Membresias, Roles, etc.

grant EXECUTE on dbo.aspnet_Applications_CreateApplication to [YOURMACHINENAME\ASPNET]

grant EXECUTE on dbo.aspnet_Users_CreateUser to [YOURMACHINENAME\ASPNET]

grant SELECT on dbo.aspnet_Users to [YOURMACHINENAME\ASPNET]

grant UPDATE on dbo.aspnet_Users(LastActivityDate) to [YOURMACHINENAME\ASPNET]

go

De igual manera a lo anterior tambien deberan conceder los derechos de los procedimientos almacenados a la cuenta correspondiente.

Una vez hecho lo anterior procedemos a modificar nuestro web.config de la siguiente manera:

Es imperativo tener la connectionstring en el web.config para que esto funcione.

cada linea agregada al web.config es "self explanatory":

<profile defaultProvider="TableProfileProvider">
<providers>
<clear />
<add name="TableProfileProvider"
type="Microsoft.Samples.SqlTableProfileProvider"
connectionStringName="ConnectionString"
table="Perfil"
applicationName="MiAplicacion"/>
</providers>
<properties>
<add name="Firstname" defaultValue="[null]"
customProviderData="Firstname;varchar"
provider="TableProfileProvider"/>
<add name="Lastname" defaultValue="[null]"
customProviderData="Lastname;varchar"
provider="TableProfileProvider"/>
</properties>
</profile>

/clear para indicar que no quiero usar el proveedor por defecto(almacenado en el machine.config) y que voy a definir el propio.

add para indicar que voy a agregar un proveedor, name y type son entendibles, luego el nombre de el connectionstring.

Y aqui la informacion clave: table="Perfil" aquí coloco el nombre de la tabla en la que deseo guardar la informacion adicional, applicationName="NombreDeMiAplicacion"(indispensable haberlo definido previamente, para mayores referencias lean mi post anterior).

Una vez definido todo esto, lo único que me resta es definir las properties de una manera similar a la que especifique anteriormente, observen que no solo coloco el nombre de la propiedad, sino tambien el campo de la Base de Datos a la que este se va a bindear.

Otra cosa que deberan hacer es bajarse el instalador del proveedor y compilar los SqlStoredProcedureProfileProvider.cs y SqlTableProfileProvider.cs que se encuentran en (por defecto) C:\Archivos de programa\Microsoft\ASP.NET 2.0 Table Profile Provider Samples\App_Code\, utilizen el Command Prompt del vs para hacerlo; una vez hecho esto(ver AVISO abajo) agregamos la dll al directorio bin/ de nuestra aplicación, y estamos listos para usarlo.

AVISO: Hay un pequeño inconveniente a solucionar cuando quieran hacer la compilación, en el caso del SQLStoreProcedure se va a compilar perfectamente, pero el SQLTable no, les dará un error como

"SqlTableProfileProvider.cs(75,36): error CS0103: The name 'SqlStoredProcedureProfileProvider' does not exist in the current context
SqlTableProfileProvider.cs(82,28): error CS0103: The name 'SqlStoredProcedureProfileProvider' does not exist in the current context"

Para solucionar esto lo que YO hice es agregar el codigo del SQLStoredProcedureProfileProvider AL INICIO del SQLTableProfileProvider, guardarlo, y luego compilarlo.

Realmente el uso de esta última opción nos presenta innumerables ventajas a la hora de intentar "editar" usuarios mediante nuestras aplicaciones en ASP.net, hasta ahora es la única forma en la que he logrado implementar la opción de "editar perfiles" desde dentro de la aplicación web propiamente dicha(desconozco si existe alguna manera más "facil", estuve buscando por un tiempo y no pude encontrar nada).

Como siempre el feedback está mas que agradecido, si descubren algun error(soy humano y los cometo) sientase libres de comunicármelo, cualquier crítica(siempre constructiva) será mas que bien recibida por mi parte.

Bueno, eso es todo por ahora, espero que les sirva de algo(a mi mismo me hizo recapacitar sobre ciertas cosillas que debo reveer y comunicar a mi Jefe :P).

Saludos y Happy Programming my little fellows.


Leandro

domingo, 21 de octubre de 2007

(Membership - ApplicationID) Actualizacion de usuarios y ApplicationID

Como probablemente ya saben, asp.net cuenta con un sistema de perfiles y roles bastante completo y sencillo de utilizar en el 97% de los casos, ( les dejo AQUI un link que encontre muy util para entender el sistema de roles y membresias de asp.net). Sin embargo uno puede llegar a encontrarse con algunos problemas cuando decide salir de ese esquema pre establecido y ajustar las soluciones de acuerdo a sus necesidades.

Me encontre en un aprieto recientemente mientras desarrollaba una aplicacion en asp.net, a la hora de realizar un ABM de usuarios que utilizaran la pagina web; pues si bien asp.net es muy flexible y sencillo a la hora de implementar funcionalidades tipicas; es necesario hilar fino en algunas cosas para que todo funcione correctamente, y hay informacion que no siempre esta a mano para advertirnos de comportamientos con los que nos encontramos que son más que peculiares.

El problema me llego cuando quise implementar un update (y delete )de usuarios registrados. Como sabran, asp.net maneja la creacion de usuarios de manera completamente transparente. Uno simplemente agrega un control de registro de usuarios, y ASP.net se encarga de todo lo demás. El problema con esto, es que uno tiende a pensar que todo esta solucionado automaticamente, y se enfoca en otras cuestiones, ignorando entonces parte del funcioanmiento escencial que se mueve detrás de las bambalinas en el sitio que UNO mismo está construyendo.

Como decia, el problema se me sucito cuando quise hacer un update de usuarios registrados(la unica funcionalidad de "update" que vi en asp.net por defecto fue la de cambiar la contraseña, mientras que si uno desea realmente personalizar su sitio y tener la posibilidad de cambiar UserNames, preguntas secretas, direccion de mail y demas, es necesario hacerlo a "mano"). Entonces mediante Membership.GetUser(Username).ProviderUserKey es posible conseguir el GUID UserID para el usuario "Username"; con esto en mano era cuestion de, mediante SQL, realizar las consultas necesarias a la BD y realizar las modificaciones pertinentes, en las tablas pertinentes (aspnet_Membership, aspnet_Users, aspnet_profile(uso un proveedor de perfiles personalizado que voy a explicar en otro momento)).

A todo esto yo ya habia leido el post AQUI sobre la importancia de definir el nombre de la aplicacion a la hora de realizar un proyecto que va a ser implementado en un servidor remoto,o en una carpeta diferente a aquella en la que fue desarrollado, por lo que me asegure de hacerlo en el web.config.

Sucede que, a la hora de realizar la logica SQL necesaria para actualizar cada uno de los campos que deseaba en las tablas, me encontre con un UserID en la tabla de membresias, diferente a aquel en la de perfiles, y dos entradas por cada usuario en la tabla aspnet_Users, una para cada uno de los dos UserId anteriores, cada una con un ApplicationID diferente; y al ir a ver la tabla aspnet_Applications, me encontre con 2 aplicaciones: siendo una la "/", y la otra aquella con el nombre que yo habia definido, la razon?, la siguiente:

No alcanza con cambiar el nombre de la aplicacion en el web.config unicamente, es NECESARIO redefinir en el web.config cada uno de los proveedores que voy a utilizar (para membresia, roles y perfil) con el ApplicationName correspondiente; de otra forma, se utiliza la configuracion definida en el machine.config, en la que, por defecto, el applicationId es "/".

Las modificaciones al web.config, deberian ser algo como esto:



<system.web>
<membership><!--proveedor para membresia-->
<providers>
<clear/>
<add name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

connectionStringName="LocalSqlServer" enablePasswordRetrieval="false"
enablePasswordReset="true" requiresQuestionAndAnswer="true"
applicationName="/MiAplicacionNombre" requiresUniqueEmail="false"
passwordFormat="Hashed" maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10" passwordStrengthRegularExpression="" />
</providers>
</membership>

<profile defaultProvider="TableProfileProvider"><!--proveedor para perfil-->
<providers>
<clear/>
<add name="TableProfileProvider"
type="Microsoft.Samples.SqlTableProfileProvider"
connectionStringName="LocalSQLServer"
table="Perfil"
applicationName="/MiAplicacionNombre"/>
</providers>
</profile>

<roleManager enabled="true"><!--proveedor para rol-->
<providers>
<clear/>
<add name="AspNetSqlRoleProvider" connectionStringName="LocalSqlServer"
applicationName="/MiAplicacionNombre"
type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
/>
<add name="AspNetWindowsTokenRoleProvider"
applicationName="/MiAplicacionNombre"
type="System.Web.Security.WindowsTokenRoleProvider, System.Web,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
/>
</providers>
</roleManager>
<system.web>



Cabe aclarar que mi proveedor de perfil es diferente al que viene por defecto para asp.net, esto es debido a que aquel por defecto tiene una manera de almacenar los datos que los vuelve practicamente "inqueryables", puesto que deberiamos hacer un tratamiento bastante complejo de manejo de strings para poder recuperar los datos(prometo un detalle de esto en mi proximo post durante la semana que viene).

Pero todo lo demas deberia ser bastante similar en el caso generico.

Espero que esto les ayude a solucionar este inconveniente a todos aquellos que lo estén padeciendo, de igual forma que lo padeciera yo en su momento.

Saludos a todos y happy programming.

Leandro

lunes, 15 de octubre de 2007

(ModalPopup)Uso de un Modal Popup para mostrar y actualizar el contenido de un gridview

Se me presento un problema recientemente a la hora de utilizar el control modal popup extender para contener algo diferente de un label que cambie de texto; resulto que, pese a lo que habia escuchado a través de las introducciones y propagandas, los
controles AJAX no eran tan sencillos como me habian prometido.
Sucede que este control, (y no solamente este del toolkit de ajax) utiliza webservices, y para poder llegar a sacarles el jugo completamente es necesario tener tambien varios conocimientos de javascript (conocimientos de los cuales en su momento no disponia). Pero, siendo que en la red no encontre otro ejemplo que se ajustara mejor a mis necesidades me puse manos a la obra con el que encontre AQUI, pero tuve que adaptarlo a un escenario un poco diferente y más comun, pues yo estaba utilizando MasterPages.
El control modalpopup posee la posibilidad de utilizar contenido dinámico, para lo que es necesario utilizar las propiedades DynamicContextKey(el parametro que se enviará al webservice), DynamicServiceMethod(El nombre del método del webservice) y DynamicServicePath(El path al webservice), DynamicControlID(el control destino que va a recibir la información del webservice para mostrarla),(pueden encontrar el detalle de varias de las otras propiedades del modal popup extender AQUI). El principal problema con el que me encontré fue que, al tratar de utilizar algun otro control como destino de la información diferente al clásico label, la cosa no andaba para nada(nullreference en el ModalPopupExtender).

Esto va a ser, básicamente, un paso a paso del ejemplo en el link anterior con aquella información que no esta en el artículo y que tuve que descubrir por mi mismo:

Bueno para empezar, necesitaremos un gridview y un método para cargarle los datos que necesitamos (pueden utilizar el bindeo sencillo de asignar la fuente de datos a través del asistente para cargar la grilla, pero si también quieren actualizar los datos utilizando el modal es necesario que, una vez que se modificaron en el modalpopup se "refresque" la grilla, y es más sencillo(y rápido, y.... estético, y... todo) que vuelvan a invocar el método de cargado de la grilla en lugar de tener que hacer un postback completo).
En este caso asumimos que estoy queriendo ver y modificar la lista de una tabla clientes :

private void LlenarGrillaClientes()
{
string sql = "Select * from Clientes";
SqlDataAdapter da = new SqlDataAdapter(sql, “ connection string”);
DataTable dt = new DataTable();
da.Fill(dt);
GridView1.DataSource = dt;
GridView1.DataBind();
}

Una vez definido el método, es necesario que lo invoquemos en el Page_Load de la página. Procedemos a agregar un templatefield al gridview, y dentro del mismo agregamos un hyperlink(porque un hyperlink y nop un boton? porque si utilizamos un boton y no configuramos un par de cosillas extra, por defecto cuando presionemos el boton se producira un postback que evitará que el modal se muestre).

El siguiente paso es colocar los campos que necesitemos "expuestos" en el gridview a través del uso de DataKeys. En este caso seria"ID_Cliente"; al hacer esto, podemos "tomar" el valor del campo que pusimos en el DataKey para el row seleccionado en el evento RowDataBound.
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
HyperLink HyperLink1 = (HyperLink)e.Row.FindControl("HyperLink1");
HyperLink1.Attributes.Add("onclick",
"MostrarPopup('"+GridView1.DataKeys[e.Row.RowIndex].Value+"')");
}
}

En el codigo anterior, lo que hago es, primero buscar el hyperlink que agregue a la grilla, y luego agregarle el metodo onclick, para que invoque al método de Javascript MostrarPopup, al que paso como parámetro el valor del campo que especifique en el DataKey para el row seleccionado.

Aclaro algo, en el caso de que deseen activar el modalpopup a traves de un único boton "Actualizar" en la pagina, y no a través del hyperlink dentro de el grid, lo anterior deberia cambiar un poco:
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
int index = Int32.Parse(e.CommandArgument.ToString());
BtnActualizarUsuario.Attributes.Add("onclick", "ShowMyModalActualizar('" + GrdVwListaAdministradores.DataKeys[index].Value+ "');return false;");

}

Es muy importante que agreguen el return false, para evitar que el botón con el cual estan queriendo invocar al modal cause un postback que refresque toda la pantalla y termine por hacer flashear la ventana modal para finalmente dejarla escondida, tambien noten que ya no se hace en el evento rowdatabound, sino en el command, esto es para tomar el valor seleccionado del gridview(utilizando el valor del index).


Bueno, hasta aqui lo sencillo, vayamos ahora a algo un poquito mas interesante, la parte webservice.
NO tuve buenas experiencias con el pseudo-webservice que me define por defecto el modal cuando le digo que voy a querer contenido dinamico, y después de muchas pruebas y errores decidi agregar el webservice yo mismo "Old Fashion Way", utilizando "Agregar Nuevo elemento" en ASP. (Nota al pie: No usen el nombre webservice que viene por defecto, porque es solo para confusiones, trust me en esto).
Asi que agregamos el webservice, pero es IMPORTANTE que agregue un par de cosas al ya definido webservice:
Ademas de las referencias que de por sí van a necesitar si pretenden utilizar sql (data, data.sqlclient) DEBEN agregar una referencia a System.Web.Script.Services, y agregar las clases [ScriptService] y [ScriptMethod] al servicio y al método del servicio respectivamente, sino no podrán referenciarlo desde el javascript.
Les va a quedar algo asi:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class CargarDatos : System.Web.Services.WebService {

public CargarDatos () {

//Uncomment the following line if using designed components
//InitializeComponent();
}

[WebMethod]
[ScriptMethod]
public string CargarDatosClientes(string ID_Cliente)
{

string sql = "select * from Clientes where ID_Cliente=" + ID_Cliente;
SqlDataAdapter da = new SqlDataAdapter(sql, “connection string”);
DataSet ds = new DataSet();

da.Fill(ds);

if (ds.Tables.Count > 0)
{
return ds.GetXml();
}
else
{
return null;
}

}
}

El siguiente paso es agregar las referencias pertinentes al webservice al proyecto, utilizando "...Add web reference", y otra cosa más que muy importante es agregar la referencia al webservice dentro del scriptmanager (en la MasterPage):

<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="CargarDatos.asmx" />;
</Services>
</asp:ScriptManager>



Si no siguen estos pasos no van a lograr "Encontrar" el webservice a la hora de llamarlo desde su codigo de Javascript.
Algo que cabe destacar en el codigo del webservice es que retorno el dataset en formato XML, lo que nos va a permitir "desmembrarlo" luego desde mi funcion en javascript, para obtener los datos necesarios.

Lo siguiente es agregar un panel (para estructura del modalpopup y contenedor de los controles), y el modalpopupextender a la página. Colocamos los controles que necesitemos dentro del panel, en este caso yo uso textboxes (para poder editar la información que recuperamos del webservice) y un hiddenfield (que me almacena el ID_Cliente que voy a necesitar a la hora de actualizar la informacion en la BD cuando presione el boton actualizar en el modalpopup con la información ya alterada en los textboxes).

<asp:Panel ID="Panel1" runat="server" BackColor="LightGray" Height="269px" Width="428px" style="display:none">
<asp:HiddenField ID="hidCusCode" runat="server" />
<asp:TextBox ID="TxBxNombre" runat="server"&lt;/asp:TextBox>
<asp:TextBox ID="TxBxCiudad" runat="server"></asp:TextBox>
<asp:TextBox ID="TxBxLocalidad" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Update" OnClick=" Button1_Click" />
<asp:Button ID="Button2" runat="server" Text="Cancel" />
</asp:Panel>;
<cc1:ModalPopupExtender ID="ModalPopupExtender1" runat="server" PopupControlID="Panel1" behaviorID="ModalPopupExtender" TargetControlID="Panel1" BackgroundCssClass="modalBackground" CancelControlID="Button2" OnCancelScript="HideModalPopup()"></cc1:ModalPopupExtender>


Observen que solo defino un boton cancel, que me va a esconder el modal despues, y que tengo un evento click asociado al boton actualizar, y como target control NO utilizo el hyperlink.

Okas hasta ahora todo mas o menos sencillo, ahora viene la parte... no tan pintoresca pero fundamental, el Javascript.

Defino 3 funciones en javascript en un bloque < type="text/javascript">< /script> EN la pagina contenido en la que tengo el panel, la grilla y el modal (probe de varias maneras esto, poniendolo en la masterpage, y en un archivo aparte de jscript, pero en ambos casos fui incapaz de poder referenciar a los controles a traves de su .ClientID, y tenia que usar el nombre que se le asigna cuando se arma el HTML, lo que francamente no es óptimo para nada y te quita escalabilidad, por lo que decidi ponerlo en la pagina contenido).
Observen que utilizo el behaviorID en el modal, hago esto para poder "encontrarlo" efectivamente, puesto que tampoco fui capaz de hacerlo de otra manera(esto tiene una explicacion de fondo que no voy a poner aca... pero sientanse libres de goooooglear your guts off :D).
function MostrarPopup(ID_Cliente)
{
var modal=$find('ModalPopupExtender1'); //haciendo referencia al behavior, y NO al id
modal.show();
CargarDatos.CargarDatosClientes(ID_Cliente,MostrarResultados);
}

//paso ID_Cliente al metodo del WS, y, si todo se desarrolla con exito en el WS, invoco a la funcion MostrarResultados (tambien hay bastante info sobre los parametros del WS cuando lo invoco desde javascript en internet, search my boys, search).

function MostrarResultados(result)
{
var doc;
if (window.ActiveXObject)
{
doc=new ActiveXObject("Microsoft.XMLDOM");
doc.async="false";
doc.loadXML(result);
}
else
{
var parser=new DOMParser();
var doc=parser.parseFromString(result,"text/xml");
}

var root=doc.documentElement.childNodes;
var tags;

for(var i=0;i<root.length;i++)
{
if (root[i].nodeType==1)
{
tags=root[i].childNodes;
}
}

for(var i=0;i<tags.length;i++)
{
if (tags[i].nodeType==1)
{
var xmlvalue=tags[i].childNodes[0].nodeValue;

switch (tags[i].nodeName)
{
case "ID_Cliente":
document.getElementById('<%=HiddenField1.ClientID%>').value=xmlvalue;
break;
case "Nombre":
document.getElementById('<%=TxBxNombre.ClientID%>').value=xmlvalue;
break;
case "Ciudad":
document.getElementById('<%=TxBxCiudad.ClientID%>').value=xmlvalue;
break;
case "Localidad":
document.getElementById('<%=TxBxCiudad.ClientID%>').value=xmlvalue;
break;
}
}
}
}


la parte de.ClientID es necesaria para poder encontrar el control cuando se arma el html, puesto que en el momento que la funcion va a ser llamada, cada control ya no se llama de la manera "TxBxNombre", sino ctl00$ContentPlaceHolder1$TxBxNombre; que es una identidficacion generada automaticamente; al usar CLientID obtenemos el ctl00$ContentPlaceHolder1$TxBxNombre en el momento en el que se genera el html sin la necesidad de ESPECIFICAMENTE escribirlo en tiempo de codificacion, con las desventajas que esto traeria aparejado.
Basicamente la funcion anterior lo que hace es tomar el xml y "desmembrarlo", cargando cada control en funcion al valor del mismo.
Ok, ahora la funcion que esconde el modal:
function HideModalPopup()
{
var modal = $find('ModalPopupExtender1');
modal.hide();
}

Perfecto, so far so good; con esto deberian ser capaces de mostrar el modalpopup con los datos cargados desde el xml(recuerden crear una clase css para modalpopup y modalbackground).
Ahora vamos a proceder a hacer "impactar" los cambios que formulen en los texboxes en la base de datos, para esto lo que hacemos es lo siguiente:
En Page_load agregamos:

Button1.OnClientClick=String.Format("fnClickUpdate('{0}','{1}')", Button1.UniqueID, "");


y dentro del
<script type="text/javascript"></script>
agregamos otra funcion mas:

function fnClickUpdate(sender, e)
{
__doPostBack(sender,e);
}


que basicamente lo que nos va a permitir es hacer un postback DESDE el modal para asi poder realizar la actualizacion de los datos.
Ahora lo que hacemos es agregar la logica sql de actualizacion dentro del evento click del boton 1(el de actualizar)

protected void Button1_Click(object sender, EventArgs e)
{
string sql = "Update Clientes Set Nombre=’”+TxBxNombre.Text+”’,
otros_controles=otros_campos Where ID_Cliente="
+HiddenField1.Value;
SqlConnection conn = new SqlConnection(“Connection String”);
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.ExecuteNonQuery();
conn.Close();
LlenarGrillaClientes();
}


Y voilá, tienen una linda ventanita modal que no solo muestra datos de una grilla, sino que ademas les permite actualizarlos.

Creo que no se me olvido nada, en caso de alguna duda pregunten nomas que para eso estamos...

Saludos y happy programming

P.D:Es mi primer post y es mas que factible que tenga algun error, el funcionamiento del ejemplo es completo, lo estoy usando actualmente, asi que si algo no funciona puede ser error mio al armar el post, o de ustedes al interpretarlo, asi que no duden en preguntar o avisarme si ven algo mal.

P.D.2: Grax Dario por el tip de formateo de codigo en el blog

Saludos

domingo, 14 de octubre de 2007

Bienvenido

Hola, bienvenidos al rincon de ASp.Net y AJAX, (y a mi primer blog dicho sea de paso), aqui voy a intentar darle una mano a todas aquellas personas que se estan iniciando en el uso de ASP.Net y AJAX en el desarrollo de sus sites; cabe aclarar que yo mismo estoy aun en fase de exploracion tambien, con lo que no siempre tendre todas las respuestas, pero aun asi podrán valerse de mis experiencias para poder utilizarlas de base en sus propios proyectos. Sientanse libres de tomar todo lo que necesiten de aqui, asi como hacer las referencias necesarias al sitio.
A medida que vaya solucionando diferentes problemas los ire subiendo aqui para que mi trabajo de busqueda pueda servirle a otras personas, y no tengan que perder tiempo solucionando problemas ya solucionados; con este pensamiento en mente podremos aprender de las experiencias de los demas y lograran avanzar mucho mas rapido.
Tengo en el tintero un par de soluciones a problemas bastante comunes cuando enfrentamos el desarrollo de un site promedio, con acceso a Base de datos y manejo de usuarios, que ire subiendo en la medida de lo posible en estos dias.


Saludos y happy programming.