1
MiniPortal
Integración de SistemasIntegración de SistemasDiseño e implementación con .NET
MiniPortal
Implementación en .NET de la aplicación con el mismo nombre, vista en la primera parte de la asignatura
Casos de uso
Registrar usuario
Autenticarse
Recuperar información de registro
Actualizar información de registro
Cambiar contraseña
2
Estructura de paquetes del modelo
Es.UDC.DotNet.MiniPortal.Model
UserProfile
UserFacade
Exceptions
VO
Delegate
VO
DAO
Es.UDC.DotNet.MiniPortal.Model.UserProfile.VO
+FirstName : String+Surname : String+Email : String
UserProfileDetailsVO
+LoginName : String+EncryptedPassword : String
UserProfileVO
Ambas clases parametrizadas con el atributo [Serializable()]
+UserProfileDetailsVO()+ToString() : String
g+Language : String+Country : String+UserProfileVO()
+ToString()
yp g+UserProfileDetailsVO : UserProfileDetailsVO
1
3
Patrón Value Object
Representa estado/valor
El estado se almacena en PropiedadesEl estado se almacena en PropiedadesIncluyen métodos get y set implícitos
Sobrescribiremos (override) el método ToString()para imprimir el estado
VOs deben ser objetos serializablesS i di l t ib t [S i li bl ()] di d lSe indica con el atributo [Serializable()] precediendo a la declaración de la clase
Usaremos la notación xxxVO
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO
4
Patrón Data Access Object
Oculta cómo se hace el acceso a la BD
Sus métodos necesitan recibirSus métodos necesitan recibirLa conexión (DbConnection)
Para crear los comandos a partir de ella
command = connection.CreateCommand()
La transacción (DbTransaction)Si hay una transacción iniciada los comandos deben añadirse aSi hay una transacción iniciada, los comandos deben añadirse a ella (obligatoriamente)
/* If transaction exists, command will be added */if (transaction != null) {
command.Transaction = transaction;}
Patrón Data Access Object
IUserProfileDAO (Adapter)
Define una interfaz para insertar, borrar, actualizar y encontrar p , , yUserProfileVOs
Permite implementar adaptadores para distintas BD (dialectos de SQL)
UserProfileDAOFactory (Factory)
Permite obtener una instancia de un adaptador apropiado para la aplicaciónp
5
Patrón Data Access Object
IUserProfileDAO + UserProfileDAOFactoryEs posible hacer plug-n-play (sin recompilar) de adaptadoresEs posible hacer plug-n-play (sin recompilar) de adaptadores cuando se cambia de BD
Facilita la instalación/configuración de la aplicación
Usaremos la notación xxxDAOEl prefijo SQL en el entorno .NET se usa para hacer referencia a SQL Server, no para referirse a SQL estándarPara evitar confusiones, no utilizaremos el prefijo SQL (siempre , p j Q ( ptrabajaremos con SQL estándar) y utilizaremos SQLServer para referirnos a la BD de Microsoft
Carga Dinámica de Clases
Implementación de Factorías
Clases que se desean instanciar puedenPertenecer al assembly activo
Estar situadas en un assembly independiente (fichero .dll)
Namespaces
System.Reflection
System.Runtime.Remoting
6
Carga Dinámica de ClaseClase pertenece al assembly activo
// Nombre completo de la clase que se desea instanciarString daoClassName =
"Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.SQLServerUserProfileDAO";
// Recuperamos el assembly actualmente en ejecucionAssembly assembly = Assembly.GetExecutingAssembly();
// Creamos instancia del tipo especificado// Parametros: // * nombre del assembly en el que se define el tipo// * tipo que deseamos instanciarObject theObject = AppDomain CurrentDomainObject theObject = AppDomain.CurrentDomain.
CreateInstanceAndUnwrap(assembly.FullName, daoClassName);
return (IUserProfileDAO)theObject;
Carga Dinámica de ClaseClase pertenece al assembly independiente
// Nombre completo de la clase que se desea instanciarString className = "MySql.Data.MySqlClient.MySqlConnection";
// Nombre de la libreria en la que esta definida la claseString driverFile =
"c:\\MySQL Connector Net 1.0.9\\Binaries\\.NET 1.1\\MySql.Data.dll";
// Cargamos el assembly a partir de su nombreAssembly assembly = Assembly.LoadFrom(driverFile);
// Creamos instancia tipo especificado// Parametros: // * nombre del assembly en el que se define el tipo// nombre del assembly en el que se define el tipo// * tipo que deseamos instanciarObject obj = AppDomain.CurrentDomain.
CreateInstanceAndUnwrap(assembly.FullName, className);
return (DbConnection) obj;
7
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.UserProfileDAOFactory
public sealed class UserProfileDAOFactory{
/// <summary>/// Initializes a new instance of the /// <see cref="UserProfileDAOFactory"/> class./// </summary>/// <remarks>Class constructor must be private, so nobody will be /// allowed to instantiate it </remarks>private UserProfileDAOFactory() { }
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.UserProfileDAOFactory
/// <exception cref="ConfigurationParameterException"/> /// <exception cref="InternalErrorException"/> public static IUserProfileDAO GetDAO() {
try {
String daoClassName =Settings.Default.UserProfileDAOFactory_daoClassName;
Assembly assembly = Assembly.GetExecutingAssembly();
Object theObject = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assembly FullName daoClassName);CreateInstanceAndUnwrap(assembly.FullName, daoClassName);
return (IUserProfileDAO)theObject;
} catch (Exception e) {
throw new InternalErrorException(e);}
}}
8
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.UserProfileDAOFactory
Ejemplo de uso
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(providerInvariantName);
/* Create and open the connection */connection = dbFactory.CreateConnection();connection.ConnectionString = connectionString;connection.Open();
/* Get DAO */IUserProfileDAO dao = UserProfileDAOFactory.GetDAO();
/* Test DAO.Exists */Boolean userExist = dao.Exists(connection, transaction,
userProfileVO.LoginName);
Es.UDC.DotNet.Util
Es.UDC.DotNetEs.UDC.DotNet
Crypto
MiniPortal
Util
Exceptions
Log
9
Es.UDC.DotNet.Util.Exceptions
Es.UDC.DotNet.Util.Exceptions
InternalErrorException
Error grave durante la ejecución de un métodog jCaída de la BD, carga errónea de clases, etc.
Oculta el API empleada en la implementación
Encapsula la excepción real que se ha producidoÚtil para depuración
ModelException
Errores Lógicos en el modelo
10
Es.UDC.DotNet.Util.Exceptionsusing System;
namespace Es.UDC.DotNet.Util.Exceptions{
public class InternalErrorException : Exception{
private Exception encapsulatedException = null;
public InternalErrorException(Exception exception) {
encapsulatedException = exception;}}
public InternalErrorException(String msg) : base(msg) { }
Es.UDC.DotNet.Util.Exceptionspublic override String Message{
get{
if (encapsulatedException == null)return this.Message;
elseencapsulatedException.Message;
}}
public Exception EncapsulatedException{
get{{
return encapsulatedException; }
}}
}
11
Es.UDC.DotNet.Util.Exceptionsusing System;
namespace Es.UDC.DotNet.Util.Exceptions {
public class InstanceException : ModelException {
private Object key;private String className;
protected InstanceException(String specificMessage, Object key, String className): base(specificMessage + " (key = '" + key +: base(specificMessage + (key = + key +
"' - className = '" + className + "')") {
this.key = key;this.className = className;
}
Es.UDC.DotNet.Util.Exceptions
#region Properties
public Object Key {
get { return key; }}
public String ClassName {
get { return className; }}
#endregion
}
}
12
Es.UDC.DotNet.Util.LogProporciona log de mensajes a pantalla y/o fichero
Contenido:LogManager.cs
App.config: parámetros de configuraciónLogDestination: destino de los mensajes (SCREEN / FILE / ALL)SeverityLevel: tipo de mensajes de los que se hará log (INFO / WARNING / ERROR)FileLocation: ruta al fichero de logFilePartialName y FileExtension: nombre del fichero de log con la sintaxis:
{FilePartialName}_DATE.{FileExtension}
Ejemplo de uso:
LogManager.RecordMessage("Example Message", LogManager.MessageType.INFO, this);
Resultado:
[13/11/2006 17:26:56] Es.UDC.DotNet.MiniPortal.Model.UserProfile.VO: INFO: Example Message
Sustituto de Console.WriteLine("…")
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
13
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
AbstractUserProfileDAO
Implementación de IUserProfileDAO independiente del DataImplementación de IUserProfileDAO independiente del Data Provider
Deja sin implementar los métodos: String GetParameterSyntax(String parameterName)
String GetParameterName(String parameterName)
SQLServerUserProfileDAO, l fil filOracleUserProfileDAO, MySQLUserProfileDAO,
etc.Implementaciones para un Data Provider específico
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
public abstract class AbstractUserProfileDAO : IUserProfileDAO{
#region IUserProfileDAO Members
/// <summary>/// Create a new <code>UserProfile</code>/// </summary>/// <exception cref="DuplicateInstanceException"/> /// <exception cref="InternalErrorException"/> public void Create(DbConnection connection, DbTransaction transaction,
UserProfileVO userProfileVO) {
/* Check if the user already exists *// Check if the user already exists. /if (Exists(connection, transaction, userProfileVO.LoginName)) {
throw new DuplicateInstanceException(userProfileVO.LoginName, typeof(UserProfileVO).FullName);
}
14
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
try {
/* Create the command. */DbCommand command = connection.CreateCommand();
/* If transaction exists, command will be added */if (transaction != null) {
command.Transaction = transaction;}
command.CommandText ="INSERT INTO UserProfile (loginName, enPassword, " +
"firstName surname email language country) " +firstName, surname, email, language, country) +"VALUES (" + GetParameterSyntax("loginName") +
", " + GetParameterSyntax("enPassword") +", " + GetParameterSyntax("firstname") +", " + GetParameterSyntax("surname") +", " + GetParameterSyntax("email") +", " + GetParameterSyntax("language") +", " + GetParameterSyntax("country") + ")";
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
DbParameter loginNameParam = command.CreateParameter();loginNameParam.ParameterName = GetParameterName("loginName");loginNameParam.DbType = DbType.String;loginNameParam.Size = 30;loginNameParam.Value = userProfileVO.LoginName;command.Parameters.Add(loginNameParam);
DbParameter encryptedPasswordParam = command.CreateParameter();<< ... >>
DbParameter firstNameParam = command.CreateParameter();<< ... >>
DbParameter surnameParam = command CreateParameter();DbParameter surnameParam = command.CreateParameter();<< ... >>
DbParameter emailParam = command.CreateParameter();<< ... >>
DbParameter languageParam = command.CreateParameter();<< ... >>
DbParameter countryParam = command.CreateParameter();<< ... >>
15
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
command.Prepare();
int insertedRows = command.ExecuteNonQuery();
if (insertedRows == 0) {
throw new SQLException("Can not add row to table" +" 'UserProfile'");
}
if (insertedRows > 1) {
throw new SQLException("Duplicate row for login name = '" +throw new SQLException( Duplicate row for login name = +userProfileVO.LoginName + "' in table 'UserProfile'");
}
} catch (SQLException e) {
throw new InternalErrorException(e);}
} // Create
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
/// <summary>/// Looks if an <code>UserProfile</code> with/// this <paramref name="loginName"/> exists/// </summary>/// <returns>True if the <code>UserProfile</code> exists</returns>/// <exception cref="InternalErrorException"/> public Boolean Exists(DbConnection connection,
DbTransaction transaction, String loginName) {
DbDataReader dataReader = null;
try {{
/* Create the command. */DbCommand command = connection.CreateCommand();
/* If transaction exists, command will be added */
command.CommandText ="SELECT loginName FROM UserProfile " +"WHERE loginName = " + GetParameterSyntax("loginName");
16
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
DbParameter loginNameParam = command.CreateParameter();loginNameParam.ParameterName = GetParameterName("loginName");loginNameParam.DbType = DbType.String;loginNameParam.Value = loginName;loginNameParam.Size = 30;command.Parameters.Add(loginNameParam);
command.Prepare();
dataReader = command.ExecuteReader();
return (dataReader.Read());
}} catch (SQLException e) {
throw new InternalErrorException(e);
} finally{
dataReader.Close();}
} // Exists
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
/// <exception cref="InstanceNotFoundException"/> /// <exception cref="InternalErrorException"/> public UserProfileVO Find(DbConnection connection,
DbTransaction transaction, String loginName) {
DbDataReader dataReader = null;
try{
/* Create the command. */DbCommand command = connection.CreateCommand();
/* If transaction exists, command will be added */
command.CommandText ="SELECT enPassword, firstName, surname, email, " +
"language, country " +"FROM UserProfile " +"WHERE loginName = " + GetParameterSyntax("loginName");
17
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
DbParameter loginNameParam = command.CreateParameter();loginNameParam.ParameterName = GetParameterName("loginName");loginNameParam.DbType = DbType.String;loginNameParam.Size = 30;loginNameParam.Value = loginName;command.Parameters.Add(loginNameParam);
command.Prepare();
dataReader = command.ExecuteReader();
if (!dataReader.Read()) {{
throw new InstanceNotFoundException(loginName,typeof(UserProfileVO).FullName);
}
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
String encryptedPassword = dataReader.GetString(0);String firstname = dataReader.GetString(1);<< ... >>
UserProfileDetailsVO userProfileDetailsVO =new UserProfileDetailsVO(firstname,
surname, email, country, language);
/* Return the value object. */return new UserProfileVO(loginName, encryptedPassword,
userProfileDetailsVO);
} catch (DbException e)catch (DbException e) {
throw new InternalErrorException(e);
} finally{
dataReader.Close();}
} // Find
18
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
/// <exception cref="InstanceNotFoundException"/> /// <exception cref="InternalErrorException"/> public void Update(DbConnection connection, DbTransaction transaction,
UserProfileVO userProfileVO) {
try {
/* Create the command. */
/* If transaction exists, command will be added */
command CommandText =command.CommandText ="UPDATE UserProfile " +"SET enPassword = " + GetParameterSyntax("enPassword") +
", firstName = " + GetParameterSyntax("firstname") +", surname = " + GetParameterSyntax("surname") +", email = " + GetParameterSyntax("email") +", language = " + GetParameterSyntax("language") +", country = " + GetParameterSyntax("country") +
" WHERE loginName = " + GetParameterSyntax("loginName");
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
DbParameter loginNameParam = command.CreateParameter();loginNameParam.ParameterName = GetParameterName("loginName");loginNameParam.DbType = DbType.String;loginNameParam.Size = 30;loginNameParam.Value = userProfileVO.LoginName;command.Parameters.Add(loginNameParam);
/* Crear el resto de parametros:* encryptedPasswordParam, firstNameParam, surnameParam,* emailParam, languageParam y countryParam*/
command.Prepare();
19
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
int updatedRows = command.ExecuteNonQuery();
if (updatedRows == 0) {
throw new InstanceNotFoundException(userProfileVO.LoginName, typeof(UserProfileVO).FullName);
}
if (updatedRows > 1) {
throw new SQLException("Duplicate row for login name = '" +userProfileVO LoginName + "' in table 'UserProfile'");userProfileVO.LoginName + in table UserProfile );
}
} catch (DbException e) {
throw new InternalErrorException(e);}
} // Update
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
/// <exception cref="InstanceNotFoundException"/> /// <exception cref="InternalErrorException"/> public void Remove(DbConnection connection,
DbTransaction transaction, String loginName) {
try {
/* Create the command. */DbCommand command = connection.CreateCommand();/* If transaction exists, command will be added. */
command.CommandText ="DELETE FROM UserProfile " +DELETE FROM UserProfile +"WHERE loginName = " + GetParameterSyntax("loginName");
DbParameter loginNameParam = command.CreateParameter();loginNameParam.ParameterName = GetParameterName("loginName");loginNameParam.DbType = DbType.String;loginNameParam.Size = 30;loginNameParam.Value = loginName;command.Parameters.Add(loginNameParam);
20
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.AbstractUserProfileDAO
command.Prepare();
int removedRows = command.ExecuteNonQuery();
if (removedRows == 0) {
throw new InstanceNotFoundException(loginName,typeof(UserProfileVO).FullName);
}
} catch (DbException e) {
throw new InternalErrorException(e);throw new InternalErrorException(e);}
} // Remove
#endregion
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.SQLServerUserProfileDAO
21
Es.UDC.DotNet.MiniPortal.Model.UserProfile.DAO.SQLServerUserProfileDAO
/// <summary>/// Class which implements the abstract class AbstractUserProfileDAO to work/// with a SQLServer Provider./// </summary>public class SQLServerUserProfileDAO : AbstractUserProfileDAO{
private static readonly String PARAMETER_PREFIX = "@";
protected override String GetParameterSyntax(string parameterName) {
return PARAMETER_PREFIX + parameterName;
}}
protected override String GetParameterName(string parameterName) {
return PARAMETER_PREFIX + parameterName;}
}
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate
22
Session Facade y Business Delegate
Patrón Session FacadeNotación XXXFacade
Patrón Business DelegateNotación XXXDelegate
En este caso, el objeto Business Delegate y el Session Facade serán el mismo
No necesitamos hacer plug-n-play del modeloNo necesitamos hacer plug n play del modelo
El Delegate será una clase concreta
Session Facade y Business Delegate
Para facilitar la comprensión del código, las operaciones del Session Facade se han implementado directamente
En una aplicación real, las operaciones del Session Facade deberían implementarse en términos de acciones (implementar cada operación en una clase)
23
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
[Serializable()]public class UserFacadeDelegate{
private String loginName;
private static readonly String providerInvariantName =Settings.Default.UserProfileDAOFactory_providerInvariantName;
private static readonly String connectionString =Settings.Default.UserProfileDAOFactory_connectionString;
private static readonly DbProviderFactory dbProviderFactory =DbProviderFactories GetFactory(providerInvariantName);DbProviderFactories.GetFactory(providerInvariantName);
/// <summary>/// Initializes a new instance of the <see cref="UserFacadeDelegate"/> /// class and stablishes the facade state./// </summary>public UserFacadeDelegate() {
loginName = null;}
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
/// <exception cref="InstanceNotFoundException">/// This error occurs when this <paramref name="loginName"/> does/// not exist./// </exception>/// <exception cref="IncorrectPasswordException">/// This error occurs when the <paramref name="password"/> does not/// match with the user password stored./// </exception>/// <exception cref="InternalErrorException"/> public LoginResultVO Login(String loginName, String password,
Boolean passwordIsEncrypted) {
DbConnection connection = null;DbConnection connection = null;String encryptedPassword = null;
try {
/* Create the connection. */connection = dbProviderFactory.CreateConnection();connection.ConnectionString = connectionString;connection.Open();
24
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
IUserProfileDAO dao = UserProfileDAOFactory.GetDAO();
UserProfileVO userProfileVO = dao.Find(connection, null, loginName);
if (passwordIsEncrypted) {
encryptedPassword = password;}else{
encryptedPassword = Crypto.crypt(password);}
if (!userProfileVO.EncryptedPassword.Equals(encryptedPassword)) {
throw new IncorrectPasswordException(loginName);}
this.loginName = loginName;
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
return new LoginResultVO(userProfileVO.LoginName,userProfileVO.EncryptedPassword,userProfileVO.UserProfileDetailsVO.Language,userProfileVO.UserProfileDetailsVO.Country);
} catch (InstanceNotFoundException e) {
throw;}catch (InternalErrorException e) {
throw;}} catch (Exception e) {
throw new InternalErrorException(e);}
25
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
finally{
try {
if (connection != null) {
connection.Close();}
} catch (Exception e) {
throw new InternalErrorException(e);}
}}
} // Login
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
/// <exception cref="InternalErrorException"/> public UserProfileVO FindUserProfile() {
DbConnection connection = null;
try {
/* Create the connection. */<< ... >>
/* Get a DAO */<< ... >>
/* State Facade. loginName is stored. */return dao.Find(connection, null, this.loginName);
} catch (InstanceNotFoundException e) {
throw new InternalErrorException(e);}
26
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
catch (InternalErrorException e) {
throw;} catch (Exception e) {
throw new InternalErrorException(e);} finally{
try {
if (connection != null) {{
connection.Close();}
} catch (Exception e) {
throw new InternalErrorException(e);}
}} // FindUserProfile
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
/// <exception cref="DuplicateInstanceException">/// This error occurs when exists a previous instance with the same key./// </exception>/// <exception cref="InternalErrorException"/>public void RegisterUser(String loginName, String clearPassword,
UserProfileDetailsVO userProfileDetailsVO) {
DbConnection connection = null;DbTransaction transaction = null;Boolean rollback = false;
try {{
/* Create the connection. */<< ... >>
/* Starts new transaction. */transaction =
connection.BeginTransaction(IsolationLevel.Serializable);
27
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
IUserProfileDAO dao = UserProfileDAOFactory.GetDAO();
String encryptedPassword = Crypto.crypt(clearPassword);
UserProfileVO userProfileVO = new UserProfileVO(loginName,encryptedPassword, userProfileDetailsVO);
dao.Create(connection, transaction, userProfileVO);
this.loginName = loginName;
} catch (DuplicateInstanceException e)catch (DuplicateInstanceException e) {
rollback = true;throw;
} catch (InternalErrorException e) {
rollback = true;throw;
}
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
catch (Exception e) {
rollback = true;throw new InternalErrorException(e);
} finally{
try {
/* Commit or rollback, and finally, close connection. */if (connection != null) {
if (rollback) {{
transaction.Rollback();} else{
transaction.Commit();}connection.Close();
}}
28
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
catch (Exception e) {
throw new InternalErrorException(e);}
}
} // RegisterUser
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
/// <exception cref="InternalErrorException"/> public void UpdateUserProfileDetails(UserProfileDetailsVO
userProfileDetailsVO) {
<< ... >>
try {
/* Create the connection. */<< ... >>
/* Starts new transaction. */<< ... >>
/* Get a DAO *// Get a DAO /<< ... >>
UserProfileVO userProfileVO = dao.Find(connection,transaction, this.loginName);
userProfileVO.UserProfileDetailsVO = userProfileDetailsVO;
dao.Update(connection, transaction, userProfileVO);}
29
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
catch (InstanceNotFoundException e) {
rollback = true;throw new InternalErrorException(e);
} catch (InternalErrorException e) {
rollback = true;throw;
} catch (Exception e) {
rollback = true;rollback = true;throw new InternalErrorException(e);
}
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
finally{
try {
/* Commit or rollback, and finally, close connection. */<< ... >>
} catch (Exception e) {
throw new InternalErrorException(e);}
}
} // UpdateUserProfileDetails} // UpdateUserProfileDetails
30
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
/// <exception cref="IncorrectPasswordException">/// This error occurs when the <paramref name="oldClearPassword"/> does/// not match with the user's password stored./// </exception>/// <exception cref="InternalErrorException"/> public void ChangePassword(String oldClearPassword,
String newClearPassword) {
<< ... >>
try {
/* Create the connection. */<< ... >>
/* Starts new transaction. */<< ... >>
/* Get a DAO */<< ... >>
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
UserProfileVO userProfileVO = dao.Find(connection,transaction, this.loginName);
/* Recovery current password. */String storedPassword = userProfileVO.EncryptedPassword;
/* Compare current with oldClearPassword. */if (!storedPassword.Equals(Crypto.crypt(oldClearPassword))) {
throw new IncorrectPasswordException(this.loginName);}
/* Update current password. */storedPassword = Crypto crypt(newClearPassword);storedPassword = Crypto.crypt(newClearPassword);
userProfileVO.EncryptedPassword = storedPassword;
dao.Update(connection, transaction, userProfileVO);
31
Es.UDC.DotNet.MiniPortal.Model.UserFacade.Delegate.UserFacadeDelegate
} catch (InstanceNotFoundException e) {
rollback = true;throw new InternalErrorException(e);
} catch (InternalErrorException e) {
rollback = true;throw e;
} catch (Exception e) {
rollback = true;rollback = true;throw new InternalErrorException(e);
} finally{
<< ... >>}
} // ChangePassword
Pruebas de unidad
Se han incluido pruebas de unidad para validar clases individuales
Incluyen un método Main (comentado con //) con código de prueba dentro de
#region Test Code Region. Uncomment for testing.
#endregion
Las pruebas de unidad de los DAOs se han ubicado enLas pruebas de unidad de los DAOs se han ubicado en la factoría de DAOs
El Session Facade contiene código de prueba para cada caso de uso
32
Pruebas de unidad
VStudio Team Suite permite la creación de test project
P t T t Mi iP t lProyecto Test MiniPortalConstructores
Propiedades
MétodosPrueban únicamente código método
d dT t d j BD i t torderedTest dejan BD consistente
Web
Realización opcional en la práctica
Acceso a Parámetros Configurables
App.configFichero que permite almacenar propiedades configurablesFichero que permite almacenar propiedades configurables
Almacena pares (variable/valor)
Formato XML
Accesible en tiempo de ejecución
Acceso a parámetros no definidos implica que se lance ConfigurationErrorExceptionConfigurationErrorException
Aplicaciones Web ASP.NETLectura de propiedades desde un fichero Web.config
33
Acceso a Parámetros Configurables
Ejemplo de fichero App.config
<?xml version="1.0" encoding="utf-8" ?><configuration>
<appSettings>
<!-- ****** Parameters for DataBase Connection ******** --><!-- Data Provider --><add key="UserProfileDAOFactory/providerInvariantName"
value="System.Data.SqlClient"/>
<!-- Connection String --><add key="UserProfileDAOFactory/connectionString"<add key= UserProfileDAOFactory/connectionString
value="Data Source=localhost\SQLExpress; Initial Catalog=miniportal; User ID=user; Password=password"/>
</appSettings>
</configuration>
Acceso a Parámetros Configurables
Ejemplo de acceso
using System Configuration;using System.Configuration;
try {
// Gets the AppSettings section for the current applicattion’s// default configuration and access to the parameter by keyString connectionString =
ConfigurationSettings.AppSettings[“UserProfileDAOFactory/connectionString”];
<< ... >>
} catch(ConfigurationErrorException e) {
Debug.WriteLine(“Parameter not found”);}
34
Acceso a Parámetros Configurables
Framework 2.0 introduce nueva forma de acceso a las propiedades
Accesible a través de Propiedades Proyecto > Settingsp y g
FuncionamientoNamespace xxx.Properties
XXX es el namespace por defecto del proyecto
Encapsula app.config en la clase Settings.csSe accede como propiedades (no a elementos de un array)
Accesibles en tiempo de compilación a través del diseñadorEn tiempo de ejecución
Valores iniciales se modifican sobre fichero app.configPueden modificarse desde código
Settings.Default.Save();
Acceso a Parámetros Configurables
Compartición de propiedades entre diferentes proyectos posible
Por defecto se leen las propiedades definidas en el proyecto enlazadop p p y
SobreescrituraAñadir en el app.config de la aplicación principal la propiedad que desea sobreescribirse.
Ejemplo: Sobreescribir LogDestination de Es.UDC.Net.Util.Log
<?xml version="1.0" encoding="utf-8" ?><configuration>
<configSections><sectionGroup name="applicationSettings" ... ><section name="Es.UDC.DotNet.MiniPortal.Properties.Settings" ... /><section name="Es.UDC.DotNet.Util.Properties.Settings" ... />
</sectionGroup>
</configSections>
35
Acceso a Parámetros Configurables<applicationSettings>
<Es.UDC.DotNet.MiniPortal.Properties.Settings>
<setting name="UserProfileDAOFactory_providerInvariantName" serializeAs="String"><value>System.Data.SqlClient</value>
</setting>
<< ... >>
<Es.UDC.DotNet.MiniPortal.Properties.Settings>
<Es UDC DotNet Util Properties Settings><Es.UDC.DotNet.Util.Properties.Settings>
<setting name="LogManager_FileLocation" serializeAs="String"><value>s:\\MiniPortalLog</value>
</setting>
</Es.UDC.DotNet.Util.Properties.Settings>
</applicationSettings></configuration>
MiniBank
Integración de SistemasIntegración de SistemasDiseño e implementación con .NET
36
MiniBank
Un sencillo ejemplo de una aplicación bancaria con los siguientes casos de uso
Crear una cuenta bancariaCrear una cuenta bancariaDatos de una cuenta: identificador de la cuenta, identificador del usuario, balanceEl identificador de la cuenta se genera automáticamente
Buscar una cuenta a partir de su identificadorAñadir una cantidad de dinero a una cuentaRetirar una cantidad de dinero de una cuentaBuscar todas las cuentas de un usuarioEli i tEliminar una cuentaHacer una transferencia de dinero de una cuenta a otraBuscar todas las operaciones que se han hecho sobre una cuenta entre dos fechas
Datos de una operación: identificador de la operación, identificador de la cuenta, fecha, tipo (añadir/retirar), cantidad de dinero
Estructura de paquetes del modelo
Es.UDC.DotNet.MiniBank.Model
Account
AccountFacade
Exceptions
Plain
Delegate
DAO
VO
VO
Actions
AccountOperation
VO
DAO
37
Representación del estado de una cuenta
Representación del estado de una operación bancaria
38
Page-By-Page Iterator
Patrón Page-by-Page IteratorSolicitar objetos por bloquesSolicitar objetos por bloques
En JavaResultSet.TYPE_SCROLL_INSENSITIVE
Permite mover el cursor hacia adelante, atrás o saltar a una posición absoluta o relativaNo muestra los cambios que se estén haciendo a la BD mientras está abierto
En .NETNo se dispone de un equivalente al ResultSet.TYPE_SCROLL_INSENSITIVE
Es.UDC.DotNet.MiniBank.Model.Account.DAO.CCSqlServerAccountDAO
public override List<AccountVO> FindByUserIdentifier(DbConnection connection, DbTransaction transaction, long userIdentifier, int startIndex, int count)
{DbDataReader dataReader = null;
try{
// Creates the command and add the transaction (if exists)// ...
command.CommandText =String.Format(
"SELECT TOP {0} accId balance FROM Account " +SELECT TOP {0} accId, balance FROM Account +"WHERE usrId = {1} " +"AND accId NOT IN (SELECT TOP {2} accId FROM Account " +" WHERE usrId = {3} ORDER BY accID) " +"ORDER BY accId",count, GetParameterSyntax("usrId"), startIndex - 1,GetParameterSyntax("usrId"));
39
Es.UDC.DotNet.MiniBank.Model.Account.DAO.CCSqlServerAccountDAO
DbParameter userIdParam = command.CreateParameter();userIdParam.ParameterName = GetParameterName("usrId");userIdParam.DbType = DbType.Int32;userIdParam.Value = userIdentifier;command.Parameters.Add(userIdParam);
command.Prepare();
/* Execute the Query */dataReader = command.ExecuteReader();
Es.UDC.DotNet.MiniBank.Model.Account.DAO.CCSqlServerAccountDAO
/* Read objects. */List<AccountVO> accountVOs = new List<AccountVO>();
while (dataReader.Read()){
int i = 0;Int64 accId = dataReader.GetInt64(i++);Double balance = dataReader.GetDouble(i++);
AccountVO account =new AccountVO(accId, userIdentifier, balance);
accountVOs.Add(account);}}
/* Return value objects. */return accountVOs;
}
40
Es.UDC.DotNet.MiniBank.Model.Account.DAO.CCSqlServerAccountDAO
catch (DbException e){
throw new InternalErrorException(e);}finally{
if (dataReader != null){
dataReader.Close();}
}}
Un ejemplo de recorrido/* Get value objects in groups of 10. */int startIndex = 1;int groupCount = 10;List<AccountVO> accountVOs;
do{
accountVOs = dao.FindByUserIdentifier(connection,transaction, userId, startIndex, groupCount);
/* Print value objects. */foreach (AccountVO account in accountVOs){
LogManager RecordMessage(account ToString()LogManager.RecordMessage(account.ToString(),LogManager.MessageType.INFO);
}LogManager.RecordMessage("--------------------",
LogManager.MessageType.INFO);
startIndex = startIndex + groupCount;
} while (accountVOs.Count == groupCount);
41
Page-By-Page Iterator
Otras soluciones:
Usando DataSetDbDataAdapter – Método Fill(DataSet, Int32, Int32, String)
adapter.Fill(dataSet, startIndex, count, "Users");
InconvenienteEficiencia reducida
Usando DataReaderDr.Read()
Solución más eficiente que usando un DataSetSolución más eficiente que usando un DataSetInconveniente
Necesario recorrer todas las filas previas a startIndex
Generación de Identificadores
Basado en Columnas ContadorInserción de cada fila implica la creación automática de un valor numérico empleado como identificadorempleado como identificador
Una vez insertada la fila, puede consultarse el último identificador creado mediante una query
Query dependiente del SGBD
MS SQL Serverselect @@identity
MySQLselect LAST_INSERT_ID()
42
Generación Identificadores
<<implements>>
Generación IdentificadoresIEntityIdentifierRetrieverusing System;using System.Data.Common;
namespace Es.UDC.DotNet.Util.SQL.EntityIdentifierRetriever {
interface IEntityIdentifierRetriever {
/// <summary>/// Returns the last Entity Identifier generated/// </summary>/// <param name="connection">The connection.</param>/// <param name="transaction">The transaction.</param>/// <returns></returns>Int64 GetGeneratedIdentifier(DbConnection connectionInt64 GetGeneratedIdentifier(DbConnection connection,
DbTransaction transaction);}
}
43
Generación IdentificadoresGenericEntityIdentifierRetriever (I)using System;using System.Data;using System.Data.Common;using System.Configuration;
using Es.UDC.DotNet.Util.Exceptions;
namespace Es.UDC.DotNet.Util.SQL.EntityIdentifierRetriever {
public class GenericEntityIdentifierRetriever : IEntityIdentifierRetriever {
private static String queryString =private static String queryString =Settings.Default.GenericEntityIdentifierRetriever_query;
Generación IdentificadoresGenericEntityIdentifierRetriever (II)
/// <summary>/// Returns the last Entity Identifier generated/// </summary>/// <param name="connection">DataBase Connection</param>/// <exception cref="InternalErrorException"/> public Int64 GetGeneratedIdentifier(DbConnection connection,
DbTransaction transaction) {
DbCommand command = null;
try {
command = connection CreateCommand();command = connection.CreateCommand();command.CommandText = queryString;
/* If transaction exists, command will be added */if (transaction != null){
command.Transaction = transaction;}
44
Int64 identity = Convert.ToInt64(command.ExecuteScalar());return identity;
Generación IdentificadoresGenericEntityIdentifierRetriever (III)
} catch (Exception e) {
throw new InternalErrorException(e);}
}}
}
Generación IdentificadoresEntityIdentifierRetrieverFactoryclass EntityIdentifierRetrieverFactory {
private EntityIdentifierRetrieverFactory() { }
public static IEntityIdentifierRetriever GetRetriever() {
Object theObject = null;
try {
String retrieverClassName =Settings.Default.
EntityIdentifierRetrieverFactory_retrieverClassName;
Assembly assembly = Assembly.GetExecutingAssembly();
theObject = AppDomain.CurrentDomain.theObject AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assembly.FullName, retrieverClassName);
} catch (Exception e) {
throw new InternalErrorException(e);}
return (IEntityIdentifierRetriever)theObject;}
}
45
Generación IdentificadoresParámetros de configuración
Query para la obtención del último identificador insertado
<add key="GenericEntityIdentifierRetriever_query" serializeAs="String"
value="SELECT @@IDENTITY"/>
Clase instanciada por la factoría EntityIdentifierRetrieverFactory para la recuperación deEntityIdentifierRetrieverFactory para la recuperación de identificadores
<add key="EntityIdentifierRetrieverFactory_retrieverClassName“ serializeAs="String"
value="IdentifierGenerator.GenericEntityIdentifierRetriever"/>
Generación IdentificadoresEjemplo de uso: MiniBank (I)public class CCSqlServerAccountDAO : AbstractSQLAccountDAO
{
/// <summary>/// I l t th C t ti f A tDAO i C t C l h/// Implements the Create operation for AccountDAO using a Counter Columns approach/// </summary>/// <exception cref="InternalErrorException"/> public AccountVO Create(DbConnection connection, Transaction transaction, AccountVO accountVO) {
DbCommand command = null;
try {
command = connection.CreateCommand();command.CommandText = "INSERT INTO Account (userID, balance) " +
" values (" + GetParameterSyntax(userID) + ", " + GetParameterSyntax(balance) + ")";
// paramaters configuration <<…>>
command.Prepare();
/* Execute query. */int insertedRows = command.ExecuteNonQuery();
if (insertedRows == 0) throw new SQLException("Can not add row to table 'Account'");
if (insertedRows > 1)throw new SQLException("Duplicate row in table 'Account'");
46
Generación IdentificadoresEjemplo de uso: MiniBank (II)
/* Get account identifier. */IEntityIdentifierRetriever entityIdentifierRetriever =
EntityIdentifierRetrieverFactory.GetRetriever();
Int64 accountIdentifier = entityIdentifierRetriever.GetGeneratedIdentifier(connection, transaction);
/* Return the value object AccountVO(id, userID, balance) */return new AccountVO(accountIdentifier, accountVO.UserIdentifier,
accountVO.Balance);
} catch(Exception e){
throw new InternalErrorException(e);}
}}}
Procesador de acciones
En MiniBank, a diferencia de MiniPortal, se ha implementado el Session Facade en términos de acciones
Cada operación del Session Facade delega su implementación en la acción correspondiente
Existen acciones transaccionales (deben implementar ITransactionalPlainAction) y no transaccionales (deben implementar INonTransactionalPlainAction)
Implementación de un procesador de acciones en Es UDC DotNet Util SQL ActionProcessorEs.UDC.DotNet.Util.SQL.ActionProcessor
47
Es.UDC.DotNet.Util.SQL.ActionProcessor.PlainActionProcessor
public sealed class PlainActionProcessor{
private static readonly String connectionString =Settings.Default.PlainActionProcessor_connectionString;
private PlainActionProcessor() { }
/// <exception cref="ModelException"/>/// <exception cref="InternalErrorException"/>public static Object Process(DbProviderFactory dbProviderFactory,
INonTransactionalPlainAction action){
DbConnection connection = null;
try{
connection = dbProviderFactory.CreateConnection();connection.ConnectionString = connectionString;connection.Open();
return action.Execute(connection);}
Es.UDC.DotNet.Util.SQL.ActionProcessor.PlainActionProcessor
catch (DbException e){
throw new InternalErrorException(e);} finally{ try{
if (connection != null){
connection.Close();}
}catch (Exception e)catch (Exception e){
throw new InternalErrorException(e);}
}}
48
Es.UDC.DotNet.Util.SQL.ActionProcessor.PlainActionProcessor
/// <exception cref="ModelException"/>/// <exception cref="InternalErrorException"/>public static Object Process(DbProviderFactory dbProviderFactory,
ITransactionalPlainAction action){
DbConnection connection = null;DbTransaction transaction = null;Boolean rollback = false;
try{
connection = dbProviderFactory.CreateConnection();connection.ConnectionString = connectionString;connection Open();connection.Open();
transaction =connection.BeginTransaction(IsolationLevel.Serializable);
/* Execute action. */Object result = action.Execute(connection, transaction);
Es.UDC.DotNet.Util.SQL.ActionProcessor.PlainActionProcessor
/* Return "result". */return result;
}catch (InternalErrorException e){
rollback = true;throw e;
}catch (Exception e){
rollback = true;throw new InternalErrorException(e);
}finallyfinally{
49
Es.UDC.DotNet.Util.SQL.ActionProcessor.PlainActionProcessor
try{
/* Commits or rollbacks, and finally, closes connection. */if (connection != null){
if (rollback){
transaction.Rollback();}else{
transaction.Commit();}connection Close();connection.Close();
}}catch (Exception e){
throw new InternalErrorException(e);}
} // finally} // Process
Recommended