Como Aplicar Geofence no Windows Phone 8.1?

Renato Haddad

Dn859575.060DE5057573180CEC6D227C6D3E2207(pt-br,MSDN.10).png

Dezembro, 2014

O conceito de Geofence é criar uma cerca (virtual) a partir de um ponto de localização (latitude e longitute) com um raio (em metros) definido, de forma que no momento em que você entrar ou sair desta área (raio), seja disparado um evento para notificar o ocorrido.

Este é um dos melhores conceitos quando falo de geolocalização nos treinamentos, palestras e aplicativos. Portanto, o objetivo deste artigo é mostrar detalhadamente o que você precisa para explorar este conceito.

O pré-requisito para este artigo é o Visual Studio .NET 2013 com o Update 2 e todos os recursos de Windows Phone 8.1.

Um exemplo prático de Geofence é ter que buscar o seu filho na escola, onde a mesma está numa coordenada latitude e longitude. A sua App define que num raio de 200 metros do alvo (escola), será disparada uma notificação no celular para informar que está próximo. E, é claro, mostrar o mapa enquanto você se desloca para o alvo.

Nos bastidores do projeto, a sua App pode receber dados de uma tarefa que está rodando em Background, capturando as coordenadas e enviando à App. O Geofence é parte nativo do Windows Phone 8.1 e Windows 8.1. O namespace a ser usado é o ”Windows.Devices.Geolocation.Geofencing”, a aplicação requer obrigatoriamente a capacidade de localização, e é suportada tanto em modo foreground quanto background. Veja na figura 1 o fluxo de informações, onde o serviço de localização armazena diversas coordenadas (conforme você se desloca com o celular), e quando necessário, dispara uma notificação para a App, ou seja, assim que atingir o alvo avisa a App.

Dn859575.6CCD2A283ABEA71C949AEAD9952ED63C(pt-br,MSDN.10).png

Figura 1 – Fluxo do modelo de Geofence e notificação

Criar o Projeto

Nesta solução, farei o exemplo com o Windows Phone 8.1, portanto, abra o Visual Studio 2013 e crie uma nova solução em branco, chamada ArtigoGeofence. Em seguida, adicione um projeto na linguagem Visual C# / Store Apps / Windows Phone Apps / Class Library (Windows Phone) chamado File Library, conforme a figura 2.

Dn859575.67833E3AB9F023B6309D0F6DFCD02210(pt-br,MSDN.10).png

Figura 2 – Projeto Class Library

Este é um projeto de biblioteca que servirá de apoio à solução. Note que na lista de References existem as seguintes referências: .NET for Windows Store apps e Windows Phone 8.1, portanto, tenha certeza que o seu projeto esteja exatamente assim. Adicione uma nova classe chamada FileStatus, com o objetivo de ler ou gravar informações sobre o tracking em um arquivo localmente (ApplicationData.Current.LocalFolder) chamado status.txt.

Note que todos os 3 métodos (AddStatusEntry, ReadAllStatusEntries, OpenFileAsync) são assíncronos com os tipos Task, ou seja, executam uma determinada tarefa (Task) conforme o contexto. E, por ser async na assinatura do método, o truque é usar o await para executar a tarefa. Na verdade o async e await trabalham juntos e nem é possível compilar o projeto sem ter os dois declarados, um não trabalha sem o outro. Não vou explicar em detalhes esta classe por ser bem simples e de fácil entendimento.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Windows.Storage;

namespace FileLibrary
{
    public enum EntryType
    {
        EnteredZone,
        ExitedZone
    }

    public static class StatusFile
    {
        public static async Task AddStatusEntry(string fenceName, EntryType type)
        {
            StorageFile file = await OpenFileAsync(true);

            using (Stream stream = await file.OpenStreamForWriteAsync())
            {
                long position = stream.Seek(0, SeekOrigin.End);

                using (StreamWriter writer = new StreamWriter(stream))
                {
                    await writer.WriteAsync(
                        string.Format("{0}Fence {1} {2} at time {3}",
                        position == 0 ? string.Empty : SEPARATOR_STRING,
                        fenceName, type == EntryType.EnteredZone ? "entered" : "exited", DateTime.Now));

                    await writer.FlushAsync();
                }
            }
        }

        public static async Task<string[]> ReadAllStatusEntries()
        {
            string[] entries = null;

            StorageFile file = await OpenFileAsync();

            if (file != null)
            {
                using (Stream stream = await file.OpenStreamForReadAsync())
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        string lines = await reader.ReadToEndAsync();
                        entries = lines.Split(SEPARATOR_CHAR);
                    }
                }
            }
            return (entries);
        }

        static async Task<StorageFile> OpenFileAsync(bool write = false)
        {
            StorageFile file = null;

            if (!write)
            {
                try
                {
                    file = await ApplicationData.Current.LocalFolder.GetFileAsync(FILENAME);
                }
                catch (FileNotFoundException)
                {
                }
            }
            else
            {
                file = await ApplicationData.Current.LocalFolder.CreateFileAsync(
                    FILENAME, CreationCollisionOption.OpenIfExists);
            }
            return (file);
        }

        static readonly char SEPARATOR_CHAR = '|';
        static readonly string SEPARATOR_STRING = "|";
        static readonly string FILENAME = "status.txt";
    }
}

Projeto de Background

A seguir, adicione um novo projeto chamado MyBackgroundTask que rodará em Background, portanto, o tipo de projeto a ser selecionado é Visual C# / Store Apps / Windows Phone Apps / Windows Runtime Component (Windows Phone). Em References deste projeto já estão .NET for Windows Store apps e Windows Phone 8.1, sendo assim, adicione mais uma referência ao projeto criado anteriormente, o FileLibrary.

Crie uma classe chamada NotificationTemplateHelper, que é um helper para montar a estrutura de uma notificação. Toda notificação no Universal Apps (Windows e Windows Phone) é feita a partir de uma estrutura XML, portanto, esta classe contém dois métodos que montarão dinamicamente o XML para enviar para notificação ao usuário. Aqui também não há nenhum mistério porque basta manipular o XML, selecionar os nós e elementos do XML para adicionar dados.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Data.Xml.Dom;

namespace mtaulty.Utility
{
  static class NotificationTemplateHelper
  {
    public static void CompleteBadgeTemplate(XmlDocument xml, int badgeValue)
    {
      xml.DocumentElement.SetAttribute("value", badgeValue.ToString());
    }

    public static void CompleteToastOrTileTemplate(XmlDocument xml, string[] text, string[] images)
    {
      XmlNodeList slots = xml.SelectNodes("descendant-or-self::image");
      int index = 0;

      if (images != null)
      {
        while ((index < images.Length) && (index < slots.Length))
        {
          ((XmlElement)slots[index]).SetAttribute("src", images[index]);
          index++;
        }
      }

      if (text != null)
      {
        slots = xml.SelectNodes("descendant-or-self::text");
        index = 0;

        while ((index < text.Length) && (index < slots.Length))
        {
          slots[index].AppendChild(xml.CreateTextNode(text[index]));
          index++;
        }
      }
    }
  }
}

Agora precisamos criar uma nova classe para a tarefa de Background, portanto, adicione uma nova classe chamada TheTask (veja que o tipo é sealed) que herda de IBackgroundTask. Implemente (CTRL + . (ponto)) esta interface com o método Run, que é assíncrono. Adicione os devidos usings para os namespaces (note que o mtaulty.Utility pertence à classe NotificationTemplateHelper criada anteriormente).

O objetivo desta classe é notificar a App através do ToastNotifier, o qual envia o arquivo XML com a estrutura pronta, ou seja, será exibida na tela uma mensagem quando atingir o alvo da cerca através do CreateToastNotifier.

using FileLibrary;
using mtaulty.Utility;
using System.Collections.Generic;
using Windows.ApplicationModel.Background;
using Windows.Data.Xml.Dom;
using Windows.Devices.Geolocation.Geofencing;
using Windows.UI.Notifications;

namespace MyBackgroundTask
{
    public sealed class TheTask : IBackgroundTask
    {
        public async void Run(IBackgroundTaskInstance taskInstance)
        {
            var deferral = taskInstance.GetDeferral();

            IReadOnlyList<GeofenceStateChangeReport> reports = GeofenceMonitor.Current.ReadReports();

            foreach (var report in reports)
            {
                if ((report.NewState != GeofenceState.None) &&
                    (report.NewState != GeofenceState.Removed))
                {
                    await StatusFile.AddStatusEntry(
                        report.Geofence.Id, 
                        report.NewState == GeofenceState.Entered ? EntryType.EnteredZone : EntryType.ExitedZone);
                }
            }

            XmlDocument template = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01);
            
            NotificationTemplateHelper.CompleteToastOrTileTemplate(
                template,
                new string[] 
                {
                    "Uma ou mais cercas foram ultrapassadas."
                },
                null);

            ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();

            notifier.Show(new ToastNotification(template));

            deferral.Complete();
        }
    }
}

Para um melhor entendimento, pressione F12 em GeofenceStateChangeReport, o qual contém os métodos Geofence, Geoposition, NewState e RemovalReason. Faça o mesmo com o GeofenceMonitor, o qual conseguimos monitorar a posição atual (Current), a lista de pontos da cerca (IList<Geofence>), a última posição conhecida (LastKnowGeoposition) e o Status. Há ainda dois Handlers para controlar a mudança do estado (GeofenceStateChanged) e do Status (StatusChanged).

Já o looping foreach se encarrega de escrever no arquivo status.txt (através do método AddStatusEntry) um texto dizendo que entrou ou saiu da cerca. Isto também serve para efeito de log do aplicativo. O GeofenceState é um Enum contendo o estado do Geofence da cerca, sendo: None (nenhum), Entered (entrou), Exited (saiu) ou Removed (removido).

Em seguida, é definido o tipo de notificação a ser exibida (ToastTemplateType.ToastText01), pois há muitos tipos e de acordo com a situação, você pode mudar para um Tile (com ou sem imagem) ou para uma mensagem com somente texto.

E, para finalizar a notificação é criado o objeto de notificação com o CreateToastNotifier e exibido com o método Show.

Compile toda a solução e veja se há algum ajuste a ser feito, pois até aqui o projeto deve estar compilado com sucesso.

Projeto Windows Phone 8.1

Até aqui já temos todas as classes necessárias para disparar a notificação. No entanto, precisamos de uma interface UI que será no Windows Phone. Sendo assim, adicione um novo projeto do tipo Visual C# / Store Apps / Windows Phone Apps / Blank App (Windows Phone) chamado GeofenceDemo.

A primeira coisa a fazer é adicionar duas referências aos outros dois projetos que criamos, o FileLibrary e o MyBackgroundTask. Em seguida, abra o manifesto do projeto, dê um duplo clique no arquivo Package.appmanifest. Na guia Application, localize o dropdown Toast capable e selecione Yes. Isto serve para tornar a App passível de notificação. Logo abaixo, no dropdown Lock screen notifications, selecione Badge, que é o tipo de notificação que será exibida.

Ainda no manifesto da App, como esta App necessita ativar os recursos de localização, na guia Capabilities, selecione o checkbox Location.

Aqui vale um ajuste a ser feito na guia Visual Assets, afinal o VS está informando que o nome da imagem (PNG) do Badge não foi definida. Selecione a categoria Badge Logo e defina a imagem Assets\badgeLogo.png. Cabe dizer que esta imagem é do tamanho 24 x 24 e no formato PNG, portanto, você pode criar ou pegar uma imagem na internet.

E como associar esta App a uma tarefa de Background? É simples, no manifesto clique na guia Declarations. No dropdown Available Declarations (declarações disponíveis), selecione Background Tasks. Clique no botão Add, e em propriedades, selecione os checkboxes Location e System Event. E, para finalizar é preciso dizer qual o Entry point, neste caso é MyBackgroundTask.TheTask (nome do projeto + nome da classe que herda de IBackgroundTask), conforme a figura 3.

Dn859575.60607F3CA016FD451146CFDCE2366989(pt-br,MSDN.10).png

Figura 3 – Associar o Background Task

Salve o projeto e vamos para a parte de interface de usuário. Abra o arquivo MainPage.xaml e deixe a UI parecida com o seguinte código.

<Page
    x:Class="GeofenceDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:GeofenceDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <StackPanel Margin="6,40,6,0">
            <StackPanel Margin="0,0,0,0">
                <TextBlock Text="Cerca existente" FontSize="32"/>
                <ListBox x:Name="listFences" Height="48"/>
            </StackPanel>
            <StackPanel>
                <TextBox x:Name="txtIdentifier" Header="Identifier" Text="MS Bldg10"/>
                <TextBox x:Name="txtLatitude" Header="Latitude" Text="47.6386"/>
                <TextBox x:Name="txtLongitude" Header="Longitude" Text="-122.1279"/>
                <Button Content="Adicione 0.5km Geofence" Click="OnAddGeofence"/>
            </StackPanel>
            <StackPanel x:Name="stackRegister">
                <Button Content="Registra Background Task" Click="OnRegisterBackgroundTask"/>
            </StackPanel>
            <StackPanel x:Name="stackNotifications" Visibility="Collapsed">
                <TextBlock Text="Fence Notifications" FontSize="32"/>
                <ListBox x:Name="listNotifications" Height="148"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

Veja na figura 4 o design da UI, contendo as informações em ListBox, TextBlock e nos dois Buttons há o evento Click que você deve implementar o código C#.

Dn859575.69402E969DD7D076400FD32B41EA70C8(pt-br,MSDN.10).png

Figura 4 – UI da App

Pressione F7 e vamos para a parte do código C#. Antes de mais nada, veja a lista de usings a serem referenciados.

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

using FileLibrary;
using MyBackgroundTask;
using System.Threading.Tasks;
using Windows.Devices.Geolocation.Geofencing;
using Windows.Devices.Geolocation;
using Windows.ApplicationModel.Background;
using Windows.UI.Core;

No construtor da classe MainPage(), adicione a declaração do handler Loaded, o qual será disparado todas as vezes em que esta página for carregada.

public MainPage()
{
    this.InitializeComponent();
    this.Loaded += OnLoaded;
    this.NavigationCacheMode = NavigationCacheMode.Required;
}

Veja o conteúdo do OnLoaded, declarado anteriormente. Note que deve ser assinado como async, afinal os códigos são executados de forma assíncrona. Todos estes métodos declarados não existem, ou seja, a cada declaração, já adicione o respectivo código, assim você pode acompanhar melhor a explicação.

async void OnLoaded(object sender, RoutedEventArgs e)
{
    this.RebuildListOfFences();
    await this.RebuildListOfNotifications();
    this.SyncBackgroundTaskCompletionEvent();
    this.SetVisibility();

}

Este evento cria uma lista de strings para varrer e mostrar todas as Geofences atuais no listbox chamado listFences. A cada iteração do looping, é montada uma expressão contendo o ID, a latitude, longitude e o alcance.

void RebuildListOfFences()
{
    List<string> items = new List<string>();
    foreach (var fence in GeofenceMonitor.Current.Geofences)
    {
        Geocircle circle = (Geocircle)fence.Geoshape;

        items.Add(
            string.Format("Cerca [{0}] na [{1},{2}] alcance [{3}km]",
            fence.Id,
            circle.Center.Latitude,
            circle.Center.Longitude,
            circle.Radius / 1000.0));
    }
    this.listFences.ItemsSource = items;
}

Já este método assíncrono mostrará no listbox listNotifications todas as notificações disparadas na App.

async Task RebuildListOfNotifications()
{
    string[] fileEntries = await StatusFile.ReadAllStatusEntries();

    this.listNotifications.ItemsSource = fileEntries;
}

Este código registra a tarefa em Background em si, é feito uma pesquisa através do FirstOrDefault para saber se há ou não uma tarefa registrada. Caso seja diferente de nula, é disparado o handler OnBackgroundTaskCompleted que registra a tarefa e dispara o delegate para o evento RebuildListOfNotifications.

void SyncBackgroundTaskCompletionEvent()
{
    IBackgroundTaskRegistration registration =
        BackgroundTaskRegistration.AllTasks.Values.FirstOrDefault();

    if (registration != null)
    {
        registration.Completed += OnBackgroundTaskCompleted;
    }
}

void OnBackgroundTaskCompleted(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
    this.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        () =>
        {
            this.RebuildListOfNotifications();
        });
}

Este evento controla apenas os controles (2 stackPanels) da UI de acordo com a quantidade de tarefas registradas. Se for maior que 0, AllTasks.Count > 0, torna ou não visível os stackPanels stackRegister e stackNotifications.

void SetVisibility()
{
    bool haveTask = (BackgroundTaskRegistration.AllTasks.Count > 0);

    this.stackRegister.Visibility = haveTask ? Visibility.Collapsed : Visibility.Visible;
    this.stackNotifications.Visibility = haveTask ? Visibility.Visible : Visibility.Collapsed;
}

Agora vamos ao código do botão Registra Background Task, o qual é assinado como async. Primeiro é preciso requisitar acesso quando o device estiver em Lock Screen, afim de adicionar esta App na lista de tarefas em Background. Em seguida, basta registrar a tarefa, pois ela é um objeto (BackgroundTaskBuilder builder), então, atribui-se as propriedades Name, TaskEntryPoint (MyBackgroundTask.TheTask declarado no manifesto), o tipo de gatilho (trigger) a ser atribuído, e ao final é chamado o método Register.

async void OnRegisterBackgroundTask(object sender, RoutedEventArgs e)
{
    BackgroundExecutionManager.RemoveAccess();
    await BackgroundExecutionManager.RequestAccessAsync();

    BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
    builder.Name = "Location Task";
    builder.TaskEntryPoint = typeof(TheTask).FullName;
    builder.SetTrigger(new LocationTrigger(LocationTriggerType.Geofence));
    builder.Register();

    this.SyncBackgroundTaskCompletionEvent();
    this.SetVisibility();
}

Veja o código do botão Adiciona 0.5km Geofence, o qual captura da tela da App a latitude, a longitude e o ID. Em seguida, através do Geofence, cria um objeto com o ID (identifier), e com o Geocircle atribui a posição (latitude e longitude) e define o raio de 500 metros para alvo. Veja ainda que no MonitoredGeofenceStates serve apenas para a entrada ou saída da cerca. E, para finalizar, isto deverá ocorrer a cada 5 segundos. Na verdade, os parâmetros obrigatórios são o ID e a posição, os demais são opcionais. Ao final, é adicionar o objeto fence à lista de cercas.

void OnAddGeofence(object sender, RoutedEventArgs e)
{
    double lat = double.Parse(this.txtLatitude.Text);
    double lon = double.Parse(this.txtLongitude.Text);
    string identifier = this.txtIdentifier.Text;

    Geofence fence = new Geofence(
        identifier,
            new Geocircle(
                new BasicGeoposition()
                {
                    Latitude = lat,
                    Longitude = lon
                },
                500),
            MonitoredGeofenceStates.Entered | MonitoredGeofenceStates.Exited,
            false,
            TimeSpan.FromSeconds(5));

    GeofenceMonitor.Current.Geofences.Add(fence);

    RebuildListOfFences();
}

Pronto, este último projeto GeofenceDemo não está em negrito para ser executado quando pressionar F5. Então, no Solution Explorer, clique com o botão direito nele e selecione Set As Startup Project. Compile toda a aplicação e certifique-se que está 100% com sucesso e pronta para rodar.

Execução do Projeto

Pressione F5 para abrir o emulador do Windows Phone 8.1 e executar o projeto. É fundamental que você tenha acesso à internet, sem proxy, pois o emulador é uma máquina virtual. Com a App rodando no emulador, clique no último ícone da lista de ferramentas (>>) para abrir a janela de Additional Tools. Clique na guia Location e veja que o mapa já aparece para você navegar, conforme a figura 5.

Dn859575.4FDFF5687017A15E527B92BF2F322372(pt-br,MSDN.10).png

Figura 5 – Emulador com o Location

Para a simulação correta deste exemplo, siga estes passos detalhadamente.

1 – A simulação permite carregar um arquivo com diversas rotas pré-definidas, e para isto crie um arquivo XML chamado Locations.xml. Grave-o em qualquer lugar que possa ter acesso durante a execução do emulador. O conteúdo do arquivo deverá ser exatamente como a seguir.

<?xml version="1.0" encoding="utf-8"?>
<WindowsPhoneEmulator xmlns="http://schemas.microsoft.com/WindowsPhoneEmulator/2009/08/SensorData">
    <SensorData>
        <Header version="1" mapmode="2" speedmode="2" profiletype="Urban" simulatorseed="562674963" />
        <GpsData latitude="47.6396568043767" longitude="-122.129993061113" />
        <GpsData latitude="47.6385264280303" longitude="-122.129228067289" />
        <GpsData latitude="47.6385716435537" longitude="-122.127953077583" />
    </SensorData>
</WindowsPhoneEmulator>

DICA: Cabe ressaltar que as coordenadas de latitude e longitude DEVEM usar o caracter PONTO (.) como separador de decimal. Sendo assim, ajuste o seu Windows para aceitar isto, senão você verá um erro ao carregar este arquivo no emulador.

2 – Clique no botão “Adicione 0.5km Geofence”. Note que o texto completo já aparece no listbox, conforme a figura 6.

Dn859575.79EB4BD8C8D13AEB08D7201763FCD2C6(pt-br,MSDN.10).png

Figura 6 – Ponto definido da cerca

3 – Ajuste a guia Location do emulador com as seguintes características:

- Troque o Accuracy Profile para Urban;

- No textbox de Search, digite “microsoft, redmond” e tecle Enter. Isto deverá mostrar em detalhes o mapa do local;

- Localize o dropdown que está como Live e troque para Route (rota);

- Antes deste dropdown há um botão com o hint “Load recorded location data from a file”, o qual permite escolher dados de uma rota a partir de um arquivo. Clique neste botão e selecione o arquivo chamado Locations.xml que você gerou anteriormente.

Veja na figura 7 como deverá estar a tela do Location com tudo atribuído até agora.

Dn859575.27BB3F244CC0813E67E9077BCA62E6E8(pt-br,MSDN.10).png

Figura 7 – Configurações do Location

4 – Na App, clique no botão “Registra Background Task”. Isto deverá mostrar um listbox com as notificações (em branco ainda).

5 – Retorne ao Location do emulador, certifique-se que o dropdown de tipo de percurso está selecionado para bicicleta (Biking). Logo a frente deste dropdown há um botão de Play. Clique nele e veja o resultado da simulação. Conforme a figura 8, você verá em ação a pedalada de bicicleta se deslocando pela rota, e ao atingir o alvo, ou seja, ao entrar na cerca de 0.5km, é disparada uma notificação que aparece na tela (Badge).

Dn859575.AE4E007AC433879B7EB93D459D9D5989(pt-br,MSDN.10).png

Figura 8 – Notificação disparada ao entrar na cerca

Recomendações para o uso de Geofence

- Use um raio de 50 metros ou mais

- Não use mais que 1000 cercas por App

- A cerca está limitada a 20.000 pontos por App

- Não use os modos foreground e background juntos

Conclusão

Esta funcionalidade abordada neste artigo vem se tornando cada vez mais usual em assuntos de geolocalização. E, como temos nativamente disponíveis todas as APIs do .NET que precisamos, basta estudar um pouco e colocar a mente para codificar a nosso favor. Faça um exercício mental rápido de quantas funcionalidades este tipo de Geofence se aplica? E o melhor de tudo é que este recurso está disponível tanto nos Windowns Phone 8.1 quanto no Windows 8.1, o mesmo código, a mesma API.

Agradeço a oportunidade de poder compartilhar o conhecimento deste artigo. Qualquer dúvida e preparação de times de desenvolvimento, por favor me contate.

Sobre o Autor

Renato Haddad (rehaddad@msn.comwww.renatohaddad.com ) é MVP, MCPD e MCTS, palestrante em eventos da Microsoft em diversos países, ministra treinamentos focados em produtividade com o VS.NET 2013/2015, ASP.NET 4/5, Entity Framework, Reporting Services, Windows Phone e Windows 8.1. Visite o blog http://weblogs.asp.net/renatohaddad.

Mostrar: