Windows 8 Metro Style App, osa 2

16.4.2012, 19:10 in Metro Style App, Sovelluskehitys, Visual Studio 2012, Windows 8 by ahti.haukilehto

Osa 2 – WCF-palvelun käyttäminen Metro-sovelluksessa

Tässä artikkelisarjassa tutustumme Windows 8 Metro Style App-ohjelmointiin.

Edellisessä osassa tutustuimme käyttöliittymän tekemiseen. Tässä osassa laajennamme sovellusta siten, että se käyttää paikallista WCF (Windows Communication Foundation) palvelua. Seuraavissa osissa palataan jälleen käyttöliittymän ohjelmointiin.

Tämä artikkelin esimerkkikoodin saat täältä (osa2).

Projektin avaaminen

Avaa edellisessä osassa tehty projekti – tai lataa se täältä (osa 1).

Kokeile kääntää projekti. Metro-projekteissa, jotka on siirretty koneelta toiselle, tulee helposti seuraava virhe

Error : DEP0600 : The following unexpected error occurred during deployment:  Illegal characters in path.

Virhe on Package Name:ssa (tai enemmänkin Visual Studio 11 betassa, mutta sen voi kiertää antamalla uusi Package Name). Jos käännöksessä tuli tämä virhe, on vaihdettava Package Name. Se tehdään seuraavasti

  • avaa EventLogBrowser-projektin  Package.appxmanifest –tiedosto
  • Packagin-välilehdellä  on Package Name. Oletuksena se on generoitu GUID, mutta toki sille voi antaa oikean loogisen nimen. Esim. näin

Tämän jälkeen projektin pitäisi kääntyä.

WCF-palvelun tekeminen

Seuraavaksi tullaan tekemään WCF-palvelu, joka

  • palauttaa koneen EvetLog-nimet
  • ja halutun EventLog:n tapahtumat annetulla kellorajauksella.

Lisää ensin ratkaisuun (solution) uusi Solution Folder  03 Service

Lisää tähän hakemistoon uusi ASP.NET Empty Web Application –projekti nimeltään EventLogService

Muistathan, että palvelu tulee lukemaan event log:ia, joka edellyttää palvelun ajamista administrator-tunnuksella. Tarkistetaan ensin, millä web-palvelulla tätä palvelua tullaan ajamaan. Joten RClick (Right Click) EventLogService-projektinimen päällä > Properties. Web-välilehdellä tarkista, että palvelua ajetaan IIS Express –sovelluksella.

Tekemämme palvelu edellyttää, että Visual Studio on käynnistetty As Administrator. Ellei näin ole, sulje Visual Studio ja käynnistä As Administratorina.

Voit toki ajaa palvelua IIS-palvelimella, mutta muista, että silloin on käytettävä AppPoolia, jota ajetaan Administrator-tunnuksella (tai tunnuksella, jolla voidaan lukea Event Logia).

Datakontrahti

Seuraavaksi tee datakontrahti – siis luokka, jonka mukaisia olioita tullaan palauttamaan. Luokka on DTO-luokka (DTO – Data Transfer Objects tai TSO – Tiedonsiirto-olio). Periaatteessa voisimme käyttää System.Diagnostics.EventLogEntry-olioita (joita eventlogin kyselyt palauttavat), mutta tässä esimerkissä data siirretään itse tehtyyn oliorakenteeseen. Edellisessä artikkelissa (OSA 1) käytimme data-luokkaa EventItem, jota tulemme tässäkin käyttämään. Tämän luokan määrittely tulee siirtää EventLogService –projektiin. Tee siis seuraavaa

  • Lisää EventLogService-projektiin luokka EventItem (Add New Item… > valitse Class, ja anna nimeksi EventItem)
  • Kopioi 01 Data\EventLogaData-projektista EventItem-luokan sisältö (ei konstruktoria eikä ToString-metodia) tähän luokkaan.
  • Ja poista private-määreet.

Koodi on siis seuraava (käytämme .NET 4:n mukana tulevaa implisiittistä datakontrahtia – public-luokan public jäsenet ovat DataMembereitä eikä tätä attribuuttia tarvitse kirjoittaa):

namespace EventLogService

{

  public class EventItem

  {

    public string Provider { get; set; }

    public int EventId { get; set; }

    public int Level { get; set; }

    public int Task { get; set; }

    public DateTime TimeCreated { get; set; }

    public string Computer { get; set; }

    public string Message { get;     set; }

  }

}

Palvelukontrahti

Seuraavaksi lisää projektiin EventLogReadService-palvelu seuraavasti

  • Add New Item…
  • Search: WCF
  • ja valitse WCF Service
  • Anna nimeksi EventLogReadService

Muuta palvelun kontrahti seuraavaksi (IEventLogReadService-rajapinta)

[ServiceContract]

public
interface IEventLogReadService

{

  [OperationContract]

  List<string> GetLogNames();

  [OperationContract]

  List<EventItem> GetLogEntries(string logName, DateTime from, DateTime to);

}

Palvelun toteutus

Koodaa palvelun toteutuskoodi, joka on tiedostossa EventLogReadService.svc.cs.

  • Voit poistaa templatesta tulleen DoWork –metodin.
  • Ja koodaa toiminta. Tässä on ”lasten koodia”, eli luetaan event-log mahdollisimman yksinkertaisesti – siis kaikki tapahtumat. Tämän takia luetaan vain 100 ensimmäistä eventlog-tapahtumaa, ettei palvelusta tule turhan hidas.

public
class EventLogReadService : IEventLogReadService

{

  public List<string> GetLogNames()

  {

    try

    {

      return EventLog.GetEventLogs().Select(el => el.LogDisplayName).ToList();

    }

    catch (SecurityException se)

    {

      //throw new FaultException(”Security Exception: ” + se.Message);

      return
new List<string> { ”Security Exception – You should run this as admin! Error message:” + se.Message };

    }

  }

  public List<EventItem> GetLogEntries(string logName, DateTime from, DateTime to)

  {

    if (string.IsNullOrWhiteSpace(logName))

      throw
new FaultException(”logName is null or empty.”);

    if (!this.GetLogNames().Contains(logName))

      throw
new FaultException(”logName do not exists.”);

    try

    {

      EventLog eventLog = new EventLog(logName);

      var q = eventLog.Entries

        .Cast<EventLogEntry>()

        .Where(ele => ele.TimeGenerated >= from)

        .Where(ele => ele.TimeGenerated <= to)

        .Take(100)

        .Select(ele => new EventItem

        {

          Computer = ele.MachineName,

          EventId = (int)ele.InstanceId,

          Level = (int)ele.EntryType,

          Provider = ele.Source,

          Task = (int)ele.CategoryNumber,

          Message = ele.Message,

          TimeCreated = ele.TimeGenerated

        })

        ;

      return q.ToList();

    }

    catch (InvalidOperationException ex)

    {

      //throw new FaultException(”Can’t read that log.”);

      return
new List<EventItem>();

    }

  }

}

Palvelun testaaminen

Ensin testataan se, että palvelin metadatan voidaan lukea. Joten RClick (Right Click) EventLogReadService.svc –tiedoston päällä > View In Browser

Pitäisi tulla seuraava help-sivu (ja huomaa, että .NET 4.5:ssa on myös singleWSDL-mahdollisuus)

Huomaa, että tämä käynnisti IIS Express-sovelluksen. Mikäli suljet solutionin (tai Visual Studion), on tämä View In Browser tehtävä aina uudelleen (tai muutoin käynnistettävä IIS Express ja avattava tämä site).

Itse palvelun toiminnan voi testa Visual Studion mukana tulevalla geneerisellä Web Service testerillä. Tehdään testaus seuraavasti

  • kopioi leikekirjaan talteen edellisistä kohdasta (View In Browser) palvelun url. Se on esim.  http://localhost:3049/EventLogReadService.svc
  • käynnistä C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE –hakemistosta WcfTestClient.exe
  • Wcf Test Client-sovelluksessa File > Add Service ja syötä leikekirjaan kopioimasi osoite
  • Voit suorittaa vaikkapa GetLogNames-metodin tuplaklikkaamalla sitä ja painamalla Invoke

Jos tulee tämä virhe (Security Exception), niin syynä on se, että palvelun käyttämällä tunnuksella ei ole oikeutta lukea event logia.

Käynnistä Visual Studio (ja sitä kautta IIS Express) As Administrator ja yritä uudelleen. Muista tehdä aina View In Browser, jotta IIS Express käynnistyisi.

EventLogData –projekti, palvelun proxy-koodi

Ja sitten palvelun clientin pariin – eli projekti 01 Data\EventLogData.

Tämä projekti on vastuussa sovelluksen datan hakemisesta. Nythän generoidaan vain testidata, joten EventDataSource-luokkaa tullaan muuttamaan siten, että se ei-design –tilassa lukee datan EventDataReadService -palvelua. Design-tilassa (siis silloin, kun Visual Studiolla tai Blend:llä editoidaan lomaketta) voidaan edelleen käyttää generoitua testidataa.

Ensiksi referoidaan palvelu. Tee siis seuraavaa

  • 01 Data\EventLogData –projektinnimen päällä RClick > Add Service Reference
  • Paina Discover-nappia, jolloin EventLogReadService pitäisi näkyä
  • Anna nimeksi EventLogService

Jos painat Advanced… -nappia, voi havaita, että

  • vain async operaatiot ovat mahdollisia
  • kokoelmat tulevat ObservableCollection –tyyppisinä

Paina OK.

Jos WCF on sinulle tuttua, voit myös tutustua generoituun proxy-koodiin. Voit todeta että

  • mitään konfigurointia ei tule – Metro-sovelluksilla ei todellakaan ole config-tiedostoa
  • client channel konfigurointi on kaikki generoidussa koodissa. Jos klikkaat ”Show All Files” nappia, niin voit nähdä Service References\EventLogService\Reference.svcmap\Reference.cs –tiedostossa proxyn koodin, endpoint on kovokoodattu!

EventLogData – koodimuutokset

Muuta EventLogData-projektissa määritelty EventItem partial-luokaksi. Luokan datarakenne tulee nyt palvelulta, joten tässä partial-luokassa lisäämme ainoastaan tarvitsemamme ToString-metodin ja konstruktorin.

Tee siis seuraavaa

  • avaa EventLogData-projektin EventItem.cs
  • Lisää partial -määre
  • muuta nimiavaruus EventLogItem.EventLogaService:ksi (siis samannimiseksi kuin on service reference)
  • Poista kaikki propertyt, jätä vain konstruktori ja ToString

Luokka on nyt tällainen

namespace EventLogData.EventLogService

{

  public
partial
class
EventItem

  {

    public EventItem(string provider, int eventId, int level, int task, DateTime timeCreated, string computer, string message)

    {

      this.Provider = provider;

      this.EventId = eventId;

      this.Level = level;

      this.Task = task;

      this.Computer = computer;

      this.Message = message;

    }

    public
override
string ToString()

    {

      return
string.Format(”Provider:{0} Id:{1} Level:{2} Task:{3} Created@{4} Computer:{5} Message:{6}”,

              Provider, EventId, Level, Task, TimeCreated, Computer, Message);

    }

  }

}

Muutetaan EventDataSource –luokkaa hieman. Tee seuraavaa

  • avaa EventLogData-projektin EventDataSource.cs
  • Lisää tiedoston alkuun

using EventLogData.EventLogService;

  • rename GetEventsFromCurrentDay-> GetTestEvents
    (vain tässä tiedostossa, älä siis käyteä refactor-työkalua)
  • Muuta konstruktoria siten, että kutsutaan tätä testimetodia design-tilassa

public EventDataSource()

{

  if (DesignMode.DesignModeEnabled)

    this.GetTestEvents();

}

  • Koodaa varsinainen datan hakumetodi, joka käyttää EventLogReadService-palvelua. Se on seuraavanlainen

public
void GetEventsFromCurrentDay()

{

  EventLogService.EventLogReadServiceClient proxy = new EventLogService.EventLogReadServiceClient();

  Task<ObservableCollection<string>> taskLogNames = proxy.GetLogNamesAsync();

  taskLogNames.Wait();

  var logNames = taskLogNames.Result;

  foreach (var log in logNames)

  {

    EventData eventData = new
EventData(log, DateTime.Now.Date, DateTime.Now.Date.AddDays(1).AddMilliseconds(-1));

    this._Events.Add(eventData);

    Task<ObservableCollection<EventLogService.EventItem>> tItems = proxy.GetLogEntriesAsync(log, eventData.From, eventData.To);

    tItems.Wait();

    foreach (var item in tItems.Result)

    {

      eventData.AddEventItem(item);

    }

  }

  proxy.CloseAsync();

}

EventDataSource:n testaaminen

Edellisessä artikkelissa olimme jo tehneetkin luokan yksikkötestauksen. Ajetaan testi nyt uudelleen (kyse on enemmänkin integrointitestistä, koska luokka käyttää palvelua). Tee seuraavaa

  • Valikkokomento Unit Test > Windows > Unit Test Explorer
  • Unit Test Explorerissa paina Run All

Seuraavaksi käsitellään kolme yleistä testissä tulevaa virhettä. Katso nämä kaikki läpi, mikäli testiajo en mennyt virheettä läpi (huom: ajaminen kestää kauan, jopa noin 60 sek.)

  1. Jos tulee virhe DEP0600: Illegal characters in path, niin kyse on samasta virheestä mitä käsiteltiin tämän artikkelin alussa. Ja korjaustoimenpiteetkin ovat samoja, eli niin muuta paketin nimeä (kuten tämän artikkelin alussa ohjeistettiin).
  2. Jos testin ajossa tulee virhe: There was no endpoint listening at http://localhost:1832/EventLogReadService.svc that could accept the message

niin kyse on siitä, ettei Metro-sovelluksella ole lupaa kutsua localhost:n palveluja. Testi-projektikin on Metro-sovellus. Tämä virhe piti olla korjattu Visual Studiossa, mutta ainakin minun käyttämissä ympäristöissä rajoitus on edelleen voimassa.

Jos siis tämä virhe tuli, tulee avata testi-projektille localhost –oikeus. Tämä tehdään seuraavasti

  • Kopioi testiprojektin paketin nimi leikekirjaan (EventLogData.Test –projektin Package.appxmanifest)

  • käynnistä Visual Studio –komentotulkki As Administrator
  • jossa aja CheckNetIsolation LoopbackExempt -a -n=”EventLogDataTest_cwqd0wzzg62a8″
  1. virhe System.FormatException: Input string was not in a correct format johtuu siitä, että loki-merkintöjen message-property on liian pitkä tai sisältää laittomia merkkejä unit-test tulostukseen.

Joten muuta tulostus siten, että ToString tulostaakin Message-property pituuden – ei sisältöä. Joten avaa EventLogData/EventItem.cs  ja muuta ToString seuraavaksi:

public override string ToString()

{

  return string.Format(”Provider:{0} Id:{1} Level:{2} Task:{3} Created@{4} Computer:{5} Message Length:{6}”, Provider, EventId, Level, Task, TimeCreated, Computer, Message!=null?Message.Length:0);

}

Nyt testin pitäisi mennä läpi. Mutta korjataan vielä testin nimi:

[TestMethod]

public
void GetEventsFromCurrentDay_SomeDataShouldReturn()

{

EvenLogBrowser

Ja sitten varsinaisen metro-sovelluksen pariin

Tai itse asiassa, mitään muutoksia siihen ei tarvitakaan. Kokeile ajaa sovellusta. Pitäisi pelittää

Sellainen haaste sovelluksessa on, että mikäli on valinnut jonkin eventin (oikea lista), ja klikkaa vasemmalta listan, jossa ei ole eventtejä, sovellus kaatuu. Tämä korjaantuu seuraavassa osassa.