Seguint la sèrie de posts dedicats al desenvolupament d'aplicacions web, ara toca processar una petició de nou registre que arriba des del formulari web. Aquesta part del ASHX òbviament crida a la deserialització per obtenir els valors dels camps que arriben al "request" que es col·loquen en un Dictionary (és un objecte que té una estructura clau-valor, que faig servir per posar nom_camp_BBDD-valor_camp) i posteriorment es crida a l'INS de la classe derivada corresponent (derivada de la clsDades). Aquest mecanisme de deserialització fa que no importi el nombre de camps que hi ha al formulari, 5 o 50 ens costarà el mateix de programar. La llista de camps a deserialitzar la genera un programa a partir dels camps a la BBDD i amb un "checkbox" (casella de verificació? Que malament sona), deia que amb un "checkbox" es decideix si es deserialitza o no. De forma similar al Update també es pot posar aquí verificacions de negoci, o es pot afegir al Dictionary algo com la data de creació del registre... La diferència principal respecte l'update és que es recull el valor del clau primària (PK, primary key) que es genera i s'envia al formulari web. Un cop el formulari web rep aquest PK, el formulari passa de mode insert a mode update, ja que posteriors gravacions no crearan registres nous si no que actualitzaran el mateix registre tota l'estona. La plantilla que genera ASHXs crea aquest mètode "Insertar", com sempre el codi comentat:
privatevoid Insertar()
{
Resp.ContentType = "plain/text";
Req.ContentEncoding = Encoding.GetEncoding("UTF-8");
try
{
//Clase derivada de clsDades que té informació sobre el camps a//Deserialitzar
clsClassificacio ClassificacioTA = new clsClassificacio(ConnStr);
//Objecte Dictionary on guardarem nom_camp-valor_campDictionary<string, string> Params = newDictionary<string, string>(); ;
//Verificació de la lògica de negocistring Errors = ComprovacionsDadesUpdateInsert();
if (Errors == "")
{
decimal PK_Classificacio;
//Deserialitzar propiament dit
Params = ClassificacioTA.Deserialize(Req);
//Obtenim la nova PK
PK_Classificacio = ClassificacioTA.INS(Params);
//S'envia la PK al formulari
Resp.Write(PK_Classificacio.ToString());
}
else
{
Resp.Write(Errors);
}
}
catch (Exception Err)
{
Resp.Write("ERROR: " + Err.Message);
}
}
#15/01/2014 10:26 Programació C# Autor: Alex Canalda
Fa anys, quan les fotos NO es veien directament a l'explorador de fitxers fèiem servir el ACDSee o similar, però el temps passa i cal trobar alternatives més econòmiques. El FastStone!
Quan disparo fotos amb la meva reflex digital m'agrada saber com vaig disparar la foto (per no tornar-ho a fer si ha quedat malament o repetir si ha quedat bé). També m'agrada que les fotos vagin passant soles (i no sigui un protector de pantalla). Posats a demanar si el programa de veure fotos permet renombrar-les fàcilment seria la bomba. I ja la repera si el programa permet girar les fotos (té l'opció de girat automàtic però si es veuen per web no, així que acostumo a girar-les) i aquest és un girat sense pèrdues (lossless). Per això a casa fem servir el FastStone Image Viewer que fa tot això i a sobre és gratis! Després de fer-lo servir força temps els he comprat varies llicències (solidaritat entre desenvolupadors).
#14/01/2014 23:45 Fotografia Software Autor: Alex Canalda
No sóc dissenyador, però a vegades faig veure que ho sóc amb més o menys encert. I ja se que es poden fer servir fonts als CSS desde fa "temps" (en termes informàtics el temps és relatiu).
L'altra dia em vaig animar a posar fonts diferents al blog. I he vist que no és sencill. El primer pas és aconseguir les fonts, m'agrada fer-me les coses jo mateix però a les fonts encara no arriba el meu masoquisme. Per això les he agafat de Google.
He fet servir la lobster pel títol del blog i la nunito pel títol dels posts.
La forma de posar al CSS també és curiosa. Quan descarregues les fonts tens un munt de fitxers amb la font "regular" (normal), "bold" (negreta), "italic", "thin"... Els navegadors si poses la regular però després dius que la vols negreta fan un "faux" bold (o una negreta falsa). I el mateix per la itàlica, tens la regular i li dius que la vols itàlica i et fa una itàlica falsa. El que cal fer és incloure el fitxer "bold" i el fitxer de la "italic". Però enlloc de fer una font NunitoRegular, una altra NunitoBold i una NunitoItalic en el CSS s'ha de fer amb el mateix nom però modificant les propietats "font-weight" (normal o bold) i "font-style" (normal o italic). Aquí trobareu un article que ho explica millor que jo. Jo no ho he tingut que fer per que només faig servir la "regular" de la font Nunito i la font "lobster" només té regular, però poso el codi com sempre:
Els ASHX han de permetre actualitzar dades i aquesta funció s'encarrega d'actualitzar el registre que està mostrant actualment un formulari. Es poden donar molts casos, per exemple que el formulari mostri dades de més d'una sola taula. No passa res, quan es cridi a les funcions de deserialització corresponents cadascuna agafarà els camps que li pertoca. La única condició es que els camps del formulari tinguin noms diferents. Igual que al serialitzar no importa que el formulari tingui 5 camps o 50, el cost de programació (en aquest punt) és el mateix. La informació sobre els camps a recuperar estarà en una classe derivada de la clsDades. També acostuma a passar que els camps que es mostren en un formulari no són tots els que es mostren al formulari, llavors el que es fa és carregar el registre amb un GET prèviament i després deserialitzar només els camps que s'actualitzen fent un UPD. L'exemple que es mostra a continuació és aquest cas. També en aquesta funció es sol afegir lògica de verificació que sigui de negoci (la típica de camps buits, números i dates es posa al navegador).
privatevoid UpdateData()
{
Resp.ContentType = "plain/text";
Req.ContentEncoding = Encoding.GetEncoding("UTF-8");
try
{
//Classe derivada de clsDades
clsClassificacio ClassificacioTA = new clsClassificacio(ConnStr);
DataTable TBL;
DataRow DR;
//Dictionary per recuperar dadesDictionary<string, string> Params = newDictionary<string, string>();
string pPK_Classificacio = Req["PK_Classificacio"];
//Es verifica la lógica de negocistring Errors = ComprovacionsDadesUpdateInsert();
if (Errors != "")
{
Resp.Write(Errors);
}
else
{
//Es recuperen les dades
Params.Add("PK_Classificacio", pPK_Classificacio);
TBL = ClassificacioTA.GET(Params);
DR = TBL.Rows[0];
//Es deserialitza
ClassificacioTA.Deserialize(DR, Req);
//Es fa el Update
ClassificacioTA.UPD(DR);
Resp.Write("Update Classificacio OK");
}
}
catch (Exception Err)
{
Resp.Write("ERROR: " + Err.Message);
}
}
#13/01/2014 16:08 Programació C# Autor: Alex Canalda
Una de les accions del ASHX és obtenir un registre de la BBDD (fent servir les "stored procedures" i la clsDades), serialitzar els camps que formen el registre, convertir-ho a JSON i enviar-ho al navegador. El millor d'aquest proces és que el seu cost no depèn del número de camps que té el formulari al que van destinades les dades. És a dir em resulta igual que el formulari tingui 5 camps o 50. La funció que s'encarrega de fer això és diu LoadData, normalment recupera la clau primària sobre la que ha de retornar un registre. El generador que fa ASHX a partir d'una plantilla genera aquesta funció automàticament. Com sempre el codi:
privatevoid LoadData()
{
//Es posa el content type que toca
Resp.ContentType = "application/json";
//Paràmetres per fer la cridaDictionary<string, string> Params = newDictionary<string, string>();
//Clase derivada de la clsDades que té la informació//dels camps a serialitzar
clsDestruccio TA = new clsDestruccio(ConnStr);
//Recollim la PKstring pPK_Destruccio = Req["PK_Destruccio"];
Params.Add("PK_Destruccio", pPK_Destruccio);
//Fem un GETDataTable TBL = TA.GET(Params);
//Es serialitza i s'envia el JSON//al fer la serialització no importa que siguin 5 camps o 50
Resp.Write(TA.Serialize(TBL.Rows[0]));
}
#13/01/2014 12:51 Programació C# Autor: Alex Canalda
Dins del desenvolupament d'aplicacions web cal una peça que enllaci la capa de BBDD (clsDades i "stored procedures" que estan sota) amb les pàgines web que llencen peticions. Per això faig servir els "manejadors generics" (que malament sonen traduïts), "generic handler" (millor). Són com pàgines web però a les que s'ha tret tota la part HTML de .NET. Tenen l'extensió ASHX i estan en .NET des del primer Visual Studio (bé jo només puc afirmar que estan des del VS 2005, el 2003 no estic segur).
Al no tenir HTML no tenen ViewState, no gestionen events, al no tenir aquests dos components el seu rendiment és millor que el d'una pàgina ASPX (concretament el pipeline (cadena de muntatge) és més curt), són força ràpids. El seu punt d'entrada és una funció:
publicvoid ProcessRequest(HttpContext context)
Dins de l'objecte HttpContext disposem dels objectes HttpResponse per enviar dades a la pàgina web que invoca al ASHX i l'objecte HttpRequest per recuperar informació que ens envien.
Normalment cada taula de la BBDD, o entitat que es vulgui té un ASHX associat. En aquest ASHX es fan les operacions bàsiques (típiques d'un CRUD). A més a més es pot afegir la lògica de negoci (les operacions específiques d'un objecte) que estigui relacionada amb l'objecte. Per exemple si tractem comandes, en el ASHX corresponent a les comandes hi haurà, alta, baixa, actualitzar, obtenir un conjunt de comandes (per muntar un grid), i a més a més també poden existir canvi d'estat de la comanda, tancar comanda, verificar comanda, enviar comanda a algú, autocompletes, etc... (això seria la lògica de negoci). El generador de codi munta un ASHX normal, amb les operacions CRUD ja definides, però no crea la lògica de negoci (no ho pot endevinar). Poso el codi d'un ASHX típic.
publicclass classificacions : IHttpHandler, IReadOnlySessionState
{
privateHttpResponse Resp;
privateHttpRequest Req;
privateHttpSessionState Ses;
string IdPersona = "";
private Connexions Conn = new Connexions();
privatestring ConnStr;
publicvoid ProcessRequest(HttpContext context)
{
ConnStr = Conn.GetConnStr();
Resp = context.Response;
Req = context.Request;
Ses = context.Session;
Resp.Cache.SetCacheability(HttpCacheability.NoCache);
Resp.ContentEncoding = Encoding.Default;
Resp.Charset = "ISO-8859-1";
//Verificació de seguretat, es pot fer d'altres formes //Normalment el login informa aquestes variables a sessió, BBDD...if (Ses["idPersona"] == null)
{
Resp.Write("ERROR: Sessió expirada, torni a validar-se al sistema.");
return;
}
else
{
IdPersona = Ses["idPersona"].ToString();
}
string Action = Req["action"];
switch (Action)
{
case"data":
LoadData();
break;
case"update":
UpdateData();
break;
case"insert":
Insertar();
break;
case"delete":
Delete();
break;
case"loadgrid":
LoadGrid();
break;
}
}
publicbool IsReusable
{
get
{
returnfalse;
}
}
}
En posts posteriors anirem desgranant les funcions que estan al switch:
Algunes d'aquestes funcions envien JSON a la part client, concretament LoadData i LoadGrid. Cal doncs una llibreria que converteixi a JSON el resultat. Jo faig servir aquesta.
#10/01/2014 09:17 Programació C# Autor: Alex Canalda
"La gran boda" és una pel·licula que té bons actors, Robert De Niro, Katherine Heigl, Diane Keaton, Susan Sarandon, Robin Williams però NO val res. En teoria és una comèdia que ha de fer riure, però no. Els personatges no són creïbles, tot està massa estereotipat, massa previsible (i mira que a vegades dono màniga ampla amb això), situacions massa forçades, resumint una pifia tremenda. Com diu el títol tracta d'un casament on hi ha embolics entre els pares del nubi, la ex-dóna i la mare del nubi, però lo dit no val la pena. De fet, a pesar del meu Diògenes digital, la he esborrat.
Dins de la clsDades hi ha el mètode UPD que actualitza un registre, però i si volem actualitzar una DataTable? Cap problema. Fent servir tots els mètodes de la clsDades és possible cridar al que correspon segons l'estat de cada registre de la DataTable. Al final es fa un AcceptChanges per resetejar l'estat dels registres i llestos. Com sempre el codi:
/// <summary>/// Actualitza una taula contra la BBDD, fa els inserts, updates i deletes segons l'estat de la fila./// Quan acaba deixa totes les files en DataRowState.Unchanged fent una crida a AcceptChanges/// </summary>/// <param name="TBL">La taula que s'ha d'actualitzar</param>publicvoid UPD(DataTable TBL)
{
foreach (DataRow DR in TBL.Rows)
{
switch (DR.RowState)
{
caseDataRowState.Added:
INS(DR);
break;
caseDataRowState.Deleted:
DEL(DR);
break;
caseDataRowState.Modified:
UPD(DR);
break;
}
}
TBL.AcceptChanges();
}
#09/01/2014 17:07 Programació C# Autor: Alex Canalda
Igual que el procés que deserialitza, també és important la part que envia les dades. I la part comuna està a la clsDades.
Es a dir recuperar els valors de la BBDD, normalment amb un GET, després empaquetar-los en alguna estructura que després sigui fàcil enviar per JSON al navegador (faig servir un Dictionary). Els Dictionary són objectes que guarden valors (en el cas de la clsDades cadenes de text (strings)) en forma de parells, clau-valor, això és ideal per convertir-los a JSON, ja que el JSON també són claus-valors. Un cop al navegador hi ha una llibreria que fa el procés contrari (passar del JSON als camps), aquesta llibreria es crida en la funció LoadData de Javascript.
El que fa el serialitzar es crear aquest Dictionary, amb les dades adequades. La funció fa servir la llista de camps que està a la variable "Serialitzar" que s'inicialitza amb els camps de la taula que toqui (una d'exemple). Hi ha tipus de dades que admeten un determinat format, per exemple les dates o els decimals, el string que defineix com és aquest format surt de la propietat "format" (valgui la redundància) de la clsCamp (en el post de clsDades està descrita la clsCamp) i s'informa en el programa generador de classes. Per exemple podria ser un "dd-mm-yyyy" per dates o un "D2" per números.
La funció Serialize existeix en dos sabors, una que retorna el Dictionary (a vegades es recuperen dades i es vol afegir algo) o directament el JSON. Com la signatura de les funcions és la mateixa vaig posar un paràmetre dummy per diferenciar-les. El codi a continuació.
En un projecte web, una part molt important i que acostuma a consumir molt temps de programació és recuperar els valors d'un formulari al servidor quan es rep un submit (ja sigui per Ajax o normal) per posar-los en una estructura que permeti guardar-los, normalment a una BBDD.
Per evitar-ho el que es fa és una llista dels camps a recuperar (variable "Deserialitzar" que és un array de clsCamp). Es a dir de tots els camps que pertanyen a una taula es marquen els que es volen deserialitzar (recuperar) i el software que genera la classe els inclou en aquesta llista (variable). Així el que s'acostuma a fer és invocar a la funció deserialitzar i després fer un UPD o un INS. Durant la deseriatlització es verifica el tipus dels valors que es reben des del formulari web.
La funció genèrica que realitza aquest procés està a la clsDades, però la llista de camps a deserialitzar està a cada classe especifica (derivada), la que correspon a cada taula. La funció Deserialitzar rep el Request (per obtenir les dades) i un DataRow o un Dictionary (acostuma a fer-se servir el DataRow per updates i l'altra per inserts).
Com sempre el codi associat, primer la versió Dictionary:
publicDictionary<string, string> Deserialize(HttpRequest Req)
{
Dictionary<string, string> Values = newDictionary<string, string>();
string ValorCamp;
decimal ValorDecimal;
Int64 ValorInt64;
int ValorInt;
DateTime ValorData;
foreach (clsCamp Camp in Deserialitzar)
{
ValorCamp = Req[Camp.NomCamp];
switch (Camp.Tipus)
{
caseTipus.bit:
if (string.IsNullOrEmpty(Req[Camp.NomCamp])) Values.Add(Camp.NomCamp, "False");
else Values.Add(Camp.NomCamp, "True");
break;
caseTipus.bigint:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (Int64.TryParse(ValorCamp, out ValorInt64)) Values.Add(Camp.NomCamp, ValorCamp);
elsethrownewException("El camp "
+ Camp.NomCamp + " no té un valor sencer de 64 bits vàlid");
}
break;
caseTipus.tint:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (int.TryParse(ValorCamp, out ValorInt)) Values.Add(Camp.NomCamp, ValorCamp);
elsethrownewException("El camp "
+ Camp.NomCamp + " no té un valor sencer vàlid");
}
break;
caseTipus.dec:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (decimal.TryParse(ValorCamp, out ValorDecimal)) Values.Add(Camp.NomCamp, ValorCamp);
elsethrownewException("El camp "
+ Camp.NomCamp + " no té un valor numèric vàlid");
}
break;
caseTipus.datetime:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (DateTime.TryParse(ValorCamp, out ValorData)) Values.Add(Camp.NomCamp, ValorCamp);
elsethrownewException("El camp "
+ Camp.NomCamp + " no té una data vàlida");
}
break;
caseTipus.varchar:
caseTipus.chr:
caseTipus.nchar:
caseTipus.nvarchar:
if (!string.IsNullOrEmpty(ValorCamp))
{ //Els varchar(max) i nvarchar(max) tenen longitud -1 i no s'ha de verificar la seva longitud.if (ValorCamp.Length > Camp.Longitud && Camp.Longitud != -1)
thrownewException("Camp " + Camp.NomCamp + " supera longitud permesa");
else Values.Add(Camp.NomCamp, ValorCamp);
}
break;
}
}
return Values;
}
Ara la versió DataRow:
publicvoid Deserialize(DataRow DR, HttpRequest Req)
{
string ValorCamp;
decimal ValorDecimal;
int ValorInt;
Int64 ValorInt64;
DateTime ValorData;
foreach (clsCamp Camp in Deserialitzar)
{
ValorCamp = Req[Camp.NomCamp];
switch (Camp.Tipus)
{
caseTipus.bit:
if (string.IsNullOrEmpty(Req[Camp.NomCamp])) DR[Camp.NomCamp] = false;
else DR[Camp.NomCamp] = true;
break;
caseTipus.bigint:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (Int64.TryParse(ValorCamp, out ValorInt64)) DR[Camp.NomCamp] = ValorCamp;
elsethrownewException("Camp "
+ Camp.NomCamp + " no té un valor sencer de 64 bits vàlid");
}
else
{
if (Camp.Nulable) DR[Camp.NomCamp] = DBNull.Value;
elsethrownewException("Camp "
+ Camp.NomCamp + " no permet valors nulls");
}
break;
caseTipus.tint:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (int.TryParse(ValorCamp, out ValorInt)) DR[Camp.NomCamp] = ValorCamp;
elsethrownewException("Camp "
+ Camp.NomCamp + " no té un valor sencer vàlid");
}
else
{
if (Camp.Nulable) DR[Camp.NomCamp] = DBNull.Value;
elsethrownewException("Camp "
+ Camp.NomCamp + " no permet valors nulls");
}
break;
caseTipus.dec:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (decimal.TryParse(ValorCamp, out ValorDecimal)) DR[Camp.NomCamp] = ValorCamp;
elsethrownewException("Camp "
+ Camp.NomCamp + " no té un valor numèric vàlid");
}
else
{
if (Camp.Nulable) DR[Camp.NomCamp] = DBNull.Value;
elsethrownewException("Camp "
+ Camp.NomCamp + " no permet valors nulls");
}
break;
caseTipus.time:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (string.IsNullOrEmpty(Camp.Format))
{
if (DateTime.TryParseExact(ValorCamp, "HH:mm",
System.Globalization.CultureInfo.CurrentCulture,
System.Globalization.DateTimeStyles.None,
out ValorData)) DR[Camp.NomCamp] = ValorCamp;
elsethrownewException("Camp "
+ Camp.NomCamp + " no té una hora vàlida");
}
else
{
if (DateTime.TryParseExact(ValorCamp,
Camp.Format,
System.Globalization.CultureInfo.CurrentCulture,
System.Globalization.DateTimeStyles.None,
out ValorData)) DR[Camp.NomCamp] = ValorCamp;
elsethrownewException("Camp "
+ Camp.NomCamp + " no té una hora vàlida");
}
}
else
{
if (Camp.Nulable) DR[Camp.NomCamp] = DBNull.Value;
elsethrownewException("Camp "
+ Camp.NomCamp + " no permet valors nulls");
}
break;
caseTipus.datetime:
caseTipus.date:
if (!string.IsNullOrEmpty(ValorCamp))
{
if (DateTime.TryParse(ValorCamp, out ValorData)) DR[Camp.NomCamp] = ValorCamp;
elsethrownewException("Camp "
+ Camp.NomCamp + " no té una data vàlida");
}
else
{
if (Camp.Nulable) DR[Camp.NomCamp] = DBNull.Value;
elsethrownewException("Camp " + Camp.NomCamp + " no permet valors nulls");
}
break;
caseTipus.varchar:
caseTipus.chr:
caseTipus.nchar:
caseTipus.nvarchar:
if (!string.IsNullOrEmpty(ValorCamp))
{ //Els varchar(max) i nvarchar(max) tenen longitud -1 i no s'ha de verificar la seva longitud.if (ValorCamp.Length > Camp.Longitud && Camp.Longitud != -1)
thrownewException("Camp " + Camp.NomCamp + " supera longitud permesa");
else DR[Camp.NomCamp] = ValorCamp;
}
else
{
if (Camp.Nulable) DR[Camp.NomCamp] = DBNull.Value;
elsethrownewException("Camp " + Camp.NomCamp + " no permet valors nulls");
}
break;
}
}
}
#08/01/2014 12:32 Programació C# Autor: Alex Canalda