Manejo de Excepciones en aplicaciones de varias capas

Cuando desarrollamos aplicaciones, es habitual dividir la lógica de nuestra aplicación en varias capas. Normalmente un mínimo de 3 capas (las conocidas como three-tier applications) que separan: Capa de presentación, capa de lógica de negocio y capa de acceso a datos.

Un tema importante en este tipo de implementaciones es, que approach utilizar para el manejo de excepciones, para ello hoy he tenido la oportunidad de leer un artículo muy interesante en c-sharpcorner que quiero compartir con vosotros en el que hablan de cómo tratar el manejo de excepciones en aplicaciones de este tipo y dan una serie “reglas” que hay que tener en cuenta. Este es el artículo en cuestión:

http://www.c-sharpcorner.com/Blogs/46991/standard-way-of-exception-handling-in-3-tier-or-n-tier-appli.aspx

En el artículo destacan los siguientes aspectos a tener en cuenta a la hora de implementar el manejo de excepciones en este tipo de aplicaciones:

  1. Encapsular la excepción que se ha lanzado en una excepción personalizada
  2. Almacenar en un log los detalles de la excepción (o hacerlo en la base de datos)
  3. Si la excepción proviene de una capa anterior, trasladar las excepción hasta la capa superior sin grabar la excepción, ya que esto se ha hecho en la capa de origen
  4. Si estamos en la capa visual mostrar un mensaje amigable al usuario

Además en el artículo comparten una imagen que ayuda a aclarar la implementación de excepciones que también quiero compartir.

Exception

Lo que quiero hacer a continuación es mostraros con un ejemplo de código cómo podríamos hacer la implementación que nos sugieren en el artículo anterior, o al menos tal y como yo lo he entendido. El ejemplo está hecho para .NET, aunque se puede aplicar en cualquier lenguaje de programación.

Ejemplo de implementación del manejo de errores en aplicaciones de varias capas

Para el ejemplo en cuestión, se tratará de una aplicación en tres capas, por lo que contaremos con una clase que representaría a cada una de las capas: Acceso a datos, Lógica de Negocio y Presentación, y otras tantas clases personalizadas de excepciones.

Vamos a empezar definiendo las Clases de Error Personalizadas

public class DataAccessException: Exception
{
    public DataAccessException(){}
    public DataAccessException(string message) : base(message){}
    public DataAccessException(string message, Exception inner) : base(message, inner){}
}

public class BusinessLogicException: Exception
{
    public BusinessLogicException(){}
    public BusinessLogicException(string message) : base(message){}
    public BusinessLogicException(string message, Exception inner) : base(message, inner){}
}

Podríamos tener en cada capa más clases de excepciones personalizadas en función del tipo de error, forma de almacenamiento del mismo y posibles acciones a llevar a cabo en cada caso, al pasar de una capa a otra encapsularíamos estas excepciones en la genérica de cada capa.

Vamos a ver ahora como se implementarían las distintas clases de cada capa.

public class ExampleDataAccess : DataAccess
{
    public function List<Data> GetSomeData()
    {
        try
        {
            //Do DataAccess Work
        }
        catch(Exception ex)
        {
            LogError(ex);
            throw new DataAccessException("Some friendly Message", ex);
        }
    }
}

public class ExampleBusinessLogic : BusinessLogic
{
    public function bool DoSomeBusinessProcess()
    {
        try
        {
            //Do Business Work
        }
        catch(DataAccessException dae)
        {
            throw dae;
        }
        catch(Exception ex)
        {
            LogError(ex);
            throw new BusinessLogicException("Some Friendly Message", ex);
        }
    }
}

public class ExampleView : View
{
    public function void InitializeView()
    {
        try
        {
            //Do View Work
        }
        catch(DataAccessException dae)
        {
            ShowFinalFriendlyMessageToUser();
        }
        catch(BusinessLogicException ble)
        {
            ShowFinalFriendlyMessageToUser();
        }
        catch(Exception ex)
        {
            LogError(ex);
            ShowFinalFriendlyMessageToUser();
        }
    }
}

Y esto es todo, aquí tenéis el ejemplo, yo lo estaba empezando a usar en cierta medida en mis últimos desarrollos, pero con esto creo que mi manejo de errores quedará mucho más completo y me permitirá tener una mejor traza y seguimiento de los mismos.

Espero que vosotros lo podáis utilizar igualmente.

Un saludo

Anuncios

Entendiendo el patrón de diseño MVVM (II)

Buenas a todos,

Hace unos días escribí un post hablando sobre lo que había aprendido del patrón MVVM y lo que me había parecido una vez que lo comprendí.

Entendiendo el patrón de diseño MVVM

En el post, terminaba emplazándoos a una nueva ocasión en la que os mostraría un ejemplo sobre cómo aplicar este patrón en WPF. Y por fin hoy, después de unos días trabajando en él os lo puedo traer. A continuación os voy mostrar e intentar explicar lo máximo posible el ejemplo de la aplicación del patrón de diseño Model-View-ViewModel. Vamos a ir poco a poco avanzando en el ejemplo.

Lo primero que debemos hacer es dotar a nuestra aplicación de un pequeño “Core”, que nos proporcione las clases que necesitamos para trabajar con MVVM. En mi caso, y al estar empezando a trabajar con esto, he decidido utilizar unas clases que he encontrado en otro blog. A medida que vaya profundizando en el conocimiento de este patron de diseño y las clases crearé unas propias o si es necesario mejoraré las que ya he tomado como partida. Este es el blog donde se encuentran las clases que he cogido:

http://www.c-sharpcorner.com/UploadFile/1a81c5/a-simple-wpf-application-implementing-mvvm/

Necesitamos como partida dos clases básicamente: BaseViewModel y DelegateCommand. La primera nos servirá como clase base para todos los ViewModel que haya que definir mientras que la segunda nos permite crear los comandos que después serán utilizados por la vista.

El ejemplo que voy a crear a continuación es muy sencillo, lo que quiero hacer es crear una ventana que accede a dos modelos distintos. Para uno de ellos mostrará una lista a la que se podrán añadir nuevos elementos y para el otro permitirá indicar los valores del modelo y mostrarlos después en una ventana emergente. Para la funcionalidad se necesitarán dos comandos distintos, uno que añadirá elementos a la lista y otro que mostrará la ventana emergente.

Definiendo nuestros modelos

Para el ejemplo he creado dos modelos simples que os mostraré a continuación: Worker y Contact

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExampleApplication
{
    public class Worker
    {
        public string name { get; set; }
        public string department { get; set; }
        public double amount { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExampleApplication
{
    public class Contact
    {
        public string phone { get; set; }
        public string email { get; set; }
    }
}

Definiendo el ViewModel

El ViewModel, como definíamos en el artículo anterior, será el almacén que contendrá los modelos y comandos que usará la vista.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace ExampleApplication
{
    public class ViewModel : BaseViewModel
    {
        private Worker _worker;
        private ObservableCollection<Contact> _contacts;
        private Contact _contact;
        private ICommand _addContactCommand;
        private ICommand _showWorker;

        public Worker Worker
        {
            get
            {
                return _worker;
            }
            set
            {
                _worker = value;
                NotifyPropertyChanged("Worker");
            }
        }

        public ObservableCollection<Contact> Contacts
        {
            get
            {
                return _contacts;
            }
            set
            {
                _contacts = value;
                NotifyPropertyChanged("Contacts");
            }
        }
        
        public Contact Contact
        {
            get
            {
                return _contact;
            }
            set
            {
                _contact = value;
                NotifyPropertyChanged("Contact");
            }
        }

        public ICommand AddContactCommand
        {
            get 
            {
                if (_addContactCommand == null)
                    _addContactCommand = new DelegateCommand(param => this.AddContact(),null);

                return _addContactCommand;
            }
        }

        public ICommand ShowWorkerCommand
        {
            get
            {
                if (_showWorker == null)
                    _showWorker = new DelegateCommand(param => this.ShowWorker(), null);

                return _showWorker;
            }
        }

        public ViewModel()
        {
            Worker = new Worker();
            Contact = new Contact();
            Contacts = new ObservableCollection<Contact>()
            {
                new Contact(){phone = "950123123", email = "a@b.com"},
                new Contact(){phone = "951321321", email = "b@c.com"}
            };
            Contacts.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ContactCollectionChanged);      
        }

        void ContactCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Contacts");
        }

        private void AddContact()
        {
            Contacts.Add(Contact);
            Contact = new Contact();
        }

        private void ShowWorker()
        {
            MessageBox.Show(Worker.name + Worker.department + Worker.amount.ToString(),"WorkerData");
        }

    }
}

Vamos a ver algunos aspectos a tener en cuenta de esta clase:

  • En el constructor se inicializan los modelos que se van a utilizar
  • En la clase se definen como propiedades tanto los modelos, como los comandos que se van a utilizar, y sus descriptores de acceso respectivos. En el caso de los modelos, con el descriptor set se llama al método NotifyPropertyChanged para indicar que ha habido un cambio en dicho modelo.
  • Los comandos se asocian a un método privado a través de la clase de nuestro “core” que hemos creado previamente. La clase DelegateCommand

Definiendo la vista

Por último la vista, aquí es donde veremos las capacidades del binding, tanto de datos como de comandos que nos ofrece WPF, veremos como se conectan tanto los modelos como los comandos del ViewModel con la vista. Primero vamos a ver el código XAML

<Window x:Class="ExampleApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewmodel="clr-namespace:ExampleApplication"
        Title="MainWindow" Height="500" Width="525">
    <Window.Resources>
        <viewmodel:ViewModel x:Key="ViewModel"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel}}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition />
        </Grid.RowDefinitions>
        <DataGrid HorizontalAlignment="Center" VerticalAlignment="Center" ItemsSource="{Binding Contacts}" Grid.Row="0" Grid.Column="0"/>
        <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0" Margin="0 10">
            <Label Content="Phone"></Label>
            <TextBox Text="{Binding Contact.phone}" Width="100"></TextBox>
            <Label Content="Email"></Label>
            <TextBox Text="{Binding Contact.email}" Width="100"></TextBox>
            <Button Content="Add Contact" Command="{Binding AddContactCommand}" Margin="10 0 0 0"></Button>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Margin="0 10">
            <Label Content="Name"></Label>
            <TextBox Text="{Binding Worker.name}" Width="100"></TextBox>
            <Label Content="Department"></Label>
            <TextBox Text="{Binding Worker.department}"  Width="100"></TextBox>
            <Label Content="Amount"></Label>
            <TextBox Text="{Binding Worker.amount}"  Width="50"></TextBox>
            <Button Content="Show Worker" Command="{Binding ShowWorkerCommand}" Margin="5 0 0 0"></Button>
        </StackPanel>
    </Grid>
</Window>
  • Inicialmente debemos de indicar el ViewModel al que vamos a hacer referencia en la vista. (Líneas 6 a 8)
  • En segundo lugar, debemos de indicar el DataContext del Grid de la vista, indicándolo como StaticResource y haciendo referencia al ViewModel, veremos a partir de ahora que la información de nuestro ViewModel aparece en el IntelliSense de la vista. (Línea 9)
  • Cuando queramos asociar una propiedad de alguno de los modelos del ViewModel a uno de los controles de la vista, tendremos que hacerlo por medio del Binding, indicando el modelo y su propiedad. (Línea 22)
  • Por último, podemos asociar también los botones de la vista a comandos del ViewModel por medio de la propiedad “Command” de los mismos. (Línea 25)

Y para terminar el code-behind de la vista

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ExampleApplication
{
    /// <summary>
    /// Lógica de interacción para MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

¿Y qué pasa con el code-behind?, pues como se puede ver, no hay nada. Con esto, cumplimos uno de los objetivos y beneficios de este patrón de diseño que os comentaba en el artículo anterior, separar el diseño, de la lógica y la funcionalidad. De esta forma un diseñador puede dedicarse exclusivamente al look&feel de la aplicación sin centrarse en nada de su funcionalidad, ni eventos de los botones ni nada, todo queda en el ViewModel. ¿Y el funcionamiento del mini ejemplo que hemos hecho?, pues os dejo algunos capturas a continuación, para que podáis verlo.

Captura de pantalla 2014-11-29 a las 23.07.52

Captura de pantalla 2014-11-29 a las 23.07.52

Captura de pantalla 2014-11-29 a las 23.08.12

Y nada más, espero que os sea de utilidad el código y os pueda servir como guía para aplicar este patrón de diseño en aplicaciones de escritorio basadas en WPF.

Saludos

Consultas CAML para filtrar listas de SharePoint

Muy buenas a todos.

Hace bastante tiempo, cuando empecé con el blog, que coincidió además con mis inicios en SharePoint, escribí un artículo en el que hablaba sobre consultas con CAML. Con el tiempo, he visto que esta entrada es la que más visitas del blog recibe, y creo que en el fondo es muy escasa para conocer, siquiera por encima, lo que se puede hacer con CAML. Aprovechando también que ya conozco mucho más sobre esto, quiero escribir una entrada, con un contenido mucho más extenso sobre este tema.

Esta es la entrada del blog sobre CAML que escribí:

Consultas sobre CAML en Sharepoint 2010

Vamos a empezar por ver la estructura completa de una consulta en CAML y que partes la componen

<View>
 <Query>
      <OrderBy>
          ...
      </OrderBy>
      <Where>
         ...
      </Where>
 </Query>
 <ViewFields>
      <FieldRef Name='Title'/>
      <FieldRef Name='ID'/>
 </ViewFields>
 <RowLimit>10</RowLimit>
</View>

En una consulta CAML, tenemos las siguientes secciones:

  • Query: Que contiene los apartados OrderBy y Where de la consulta.
  • ViewFields: Es similar a la parte de Select de una consulta SQL. Nos permite indicar que campos queremos devolver en la consulta que vamos a realizar.
  • RowLimit: Indica el máximo número de columnas que puede devolver la consulta

¿Como podemos usar la clausula Where?

Voy a hacer un resumen ahora de algunas de las situaciones más comunes que se pueden dar cuando hacemos consultas CAML y queremos filtrar los elementos de una lista determinada.

Filtrando por un campo de la lista
<Where>
    <Eq>
        <FieldRef Name='Title' />
        <Value Type='Text'>ejemplo</Value>
    </Eq>
</Where>
<Where>
    <BeginsWith >
        <FieldRef Name='Title' />
        <Value Type='Text'>ejemplo</Value>
    </BeginsWith>
</Where>
<Where>
    <Contains>
        <FieldRef Name='Title' />
        <Value Type='Text'>ejemplo</Value>
    </Contains>
</Where>
Filtrando por campos de tipo Búsqueda
<Where>
    <Eq>
        <FieldRef Name='LookupField1' LookupId='TRUE'/>
        <Value Type='Lookup'>3</Value>
    </Eq>
</Where>
Filtrar un campo de tipo User
Filtrar usando la ID de un usuario cualquiera:

<Where>
  <Eq>
    <FieldRef Name="User1" LookupId="True" />
    <Value Type="User">123</Value>
  </Eq>
</Where>

Filtrar por el usuario actual:

<Where>
  <Eq>
    <FieldRef Name="User1" />
    <Value Type="Integer">
      <UserID />
    </Value>
  </Eq>
</Where>
Concatenando varios filtros
<Where>
    <And>
        <Eq>
            <FieldRef Name='LookupField1' LookupId='TRUE'/>
            <Value Type='Lookup'>3</Value>
        </Eq>
        <Eq>
            <FieldRef Name='TextField1'/>
            <Value Type='Text'>ejemplo</Value>
        </Eq>
    </And>
</Where>
<Where>
    <Or>
    <Eq>
        <FieldRef Name="User1" />
        <Value Type="Integer">
            <UserID />
        </Value>    
    </Eq>
    <And>
        <Eq>
            <FieldRef Name='LookupField1' LookupId='TRUE'/>
            <Value Type='Lookup'>3</Value>
        </Eq>
        <Eq>
            <FieldRef Name='TextField1'/>
            <Value Type='Text'>ejemplo</Value>
        </Eq>
    </And>
    </Or>
</Where>

Usando la clausula OrderBy

Esta clausula va dentro de Query al igual que Where y su uso es muy sencillo.

Ordenando de forma ascendente por un campo concreto
<OrderBy>
    <FieldRef Name='ID' />
</OrderBy>
Ordenando de forma descendente por un campo concreto
<OrderBy>
    <FieldRef Name='ID' Ascending='FALSE' />
</OrderBy>
Ordenando por varios campos una consulta
<OrderBy>
    <FieldRef Name='Field1' Ascending='False' />
    <FieldRef Name='Field2' />
</OrderBy>

Usando CAML dentro del modelo de objetos

El modelo de objetos nos proporciona una clase para hacer consultas CAML en nuestro código. Esta clase es la clase SPQuery. Vamos a ver como deberíamos usar en nuestro código esta clase

SPQuery consulta = new SPQuery();
consulta.Query="<Where><Eq><FieldRef Name="Field1" /><Value Type="Text">ejemplo</Value></Eq></Where>"

SPListItemCollection resultadoConsulta = lista.GetItems(consulta);

De esta forma podremos hacer un filtrado de la lista en cuestión que habremos cargado en la variable lista definida previamente. Algunas consideraciones sobre esta clase.

  • Si queremos añadir la clausula OrderBy, podemos hacerlo a través de la propiedad Query de la clase, junto con el Where
  • No incluir las etiquetas Query dentro de la propiedad Query de la clase, esto provocará que el resultado no sea el esperado
  • Si queremos limitar el número de resultados que nos devuelva la consulta, podemos hacerlo con la propiedad RowLimit de la clase. Además es una buena práctica limitar el número de resultados de la consulta, para tenerlo controlado en todo momento. Si no lo indicamos, por defecto (podéis comprobarlo con el depurador), el valor que se establece es muy grande y esto puede provocar problemas de rendimiento.
  • Podéis establecer una cadena con la estructura completa de una consulta CAML a través del campo ViewXml

Y esto es todo amigos, espero que os sea útil y sirva también para completar el artículo inicial sobre esta temática que escribí hace un tiempo.

Un saludo y buen fin de semana a todos