Mitä on Geneerinen ohjelmointi (osa4)?

Mitä on Geneerinen ohjelmointi (osa4)?

Tässä blogissani kuvaan, miten luodaan Json ”sanoma” esitellyn metadatan avulla. Lisäksi perehdytään sen talletukseen Sql serveriin.

Seuraavassa vaiheessa luodaan dataluokan tietojen tarkastus ja käsittely. Luokka luo rakenteensa metadatan ohjeiden mukaan.

public class DataCollection
{
[JsonProperty(PropertyName = ”n”, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
private String _collectionName;
[JsonProperty(PropertyName = ”m”, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
private String _metadata;
[JsonProperty(PropertyName = ”d”, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
private Dictionary<String, String> _data = new Dictionary<String, String>();

private Dictionary<String, IVariable> _variables = new Dictionary<String, IVariable>();

public DataCollection(String metadatax)
{
_metadata = metadatax;
PurgeMetadata();
}

public void Validate()
{
PurgeMetadata();
Dictionary<String, String> updates = new Dictionary<string, string>();
foreach (var item in _data)
{
String modified = _variables[item.Key].Validate(item.Value);
if (item.Value != modified)
{
updates.Add(item.Key, modified);
}
}
foreach (var item in updates)
{
_data[item.Key] = item.Value;
}
}

private void PurgeMetadata()
{
if (_metadata != null && _variables.Count == 0)
{
String[] lines = _metadata.Split(new char[] { ’\n’, ’\r’ }, StringSplitOptions.RemoveEmptyEntries);
_collectionName = lines[0];
for (int i = 1; i < lines.Length; i++)
{
IVariable variable = (IVariable)Plugin.Load(lines[i].Trim());
_variables.Add(variable.Command(”name”), variable);
}
}
}

public String SaveData(String name, String value)
{
IVariable variable;
_variables.TryGetValue(name, out variable);
String result = variable.Validate(value);
String data;
_data.TryGetValue(name, out data);
if (data != null)
{
data = result;
}
else
{
_data.Add(name, result);
}
return result;
}

public string SqlTable()
{
StringBuilder builder = new StringBuilder();
builder.AppendLine(”DECLARE ” + _collectionName + ” TABLE”);
builder.AppendLine(”(”);
int index = 0;
foreach (IVariable column in _variables.Values)
{
if (index < _variables.Values.Count – 1)
{
builder.AppendLine(column.Command(”sqldeclare”) + ”,”);
}
else
{
builder.AppendLine(column.Command(”sqldeclare”));
}
index++;
}
builder.AppendLine(”)”);
return builder.ToString();
}
}

Seuraavaksi esittelen koodin, johon tällä kaikella pyrittiin. Se on geneerinen ”sanoma”-luokka.

Tässä tapauksessa metadata on clientissa ja se lähtee ”sanoman” mukana palvelimelle. Jos palvelin on geneerinen, niin sen ei tarvitse etukäteen tietää, millaista dataa on tulossa.

Talletus voidaan tehdä esimerkiksi Azure Table Storageen, DocumentDB:n tai Sql Serverin kantaan. Tämä esimerkki tukee Sql-toteutusta, mutta se voisi tukea kaikkia edellisiä. Metadataan lisättäisiin uusi talletustapatieto.

String metaData = @”
Customer
Name,1,80,0,Bloki.Characters
City,1,11,l,Bloki.Characters
Account,0,50,u,Bloki.Characters”;

// Client code
DataCollection clientData = new DataCollection(metaData);
clientData.SaveData(”Name”, ”Jarmo”);
clientData.SaveData(”City”, ”Helsinki”);
clientData.SaveData(”Account”, ”abcdefg”);
String jsonToSend = JsonConvert.SerializeObject(clientData);

// Server code
DataCollection serverData = JsonConvert.DeserializeObject(jsonToSend);
serverData.Validate();
String table = clientData.SqlTable();

Yllä clientilla ensin luodaan DataCollection metaData merkkijonon avulla. Sitten asetetaan ja tarkistetaan arvot. Seuraavaksi serialisoidaan data merkkijonoksi, joka lähetetään serverille (tietenkin geneerisellä kutsulla). Alla jsonToSend sisältö.

{”n”:”Customer”,”m”:”\r\nCustomer\r\nName,1,80,0,Bloki.Characters\r\nCity,1,11,l,Bloki.Characters\r\nAccount,0,50,u,Bloki.Characters”,”d”:{”Name”:”Jarmo”,”City”:”helsinki”,”Account”:”ABCDEFG”}}

Serverillä sitten deserialisoidaan saatu merkkijono ja suoritetaan tarkastus. Samaa koodia siis ajetaan molemmissa päissä.

Clienttiin ei luoteta, siksi tarkastus suoritetaan myös serverillä. Samalla estetään Sql injection -tyyppiset hyökkäykset.

Eräänä esimerkkinä geneerisen mallin hyödyllisyydesta voi mainita, että Sql-taulun rakenne voidaan pyytää luokalta. Varsinkin sovelluksen kehityksen aikana on kätevää luoda ja korjata taulut automaattisesti. Tällöin luokkaan tehdään Sql insert -lauseen luonti. Jos tulee exception, niin tutkintaan, oliko taulu olemassa. Jos ei ollut, niin luodaan se ja sitten yritetään talletusta uudestaan. Samoin jos on tullut uusi kenttä, luodaan se jne. Alla table-muuttujan sisältö.

DECLARE Customer TABLE
(
Name NVARCHAR (80),
City NVARCHAR (11),
Account NVARCHAR (50)
)

Kun geneerisiä toteutuksia tekee useaan eri tarkoitukseen, niin uuden sovelluksen saa tehtyä aika vaivattomasti.

Mitä on Geneerinen ohjelmointi (osa3)?

Mitä on Geneerinen ohjelmointi (osa3)?

Talk is cheap, show me the code!

Edellisessä blogissani, geneerisen ohjelmoinnin kakkosblogissa, lupasin kertoa tarkemmin uusien ominaisuuksien antamisesta Variable-luokalle. Ensimmäisessä versiossa on tärkeää, että rajapinta toteutetaan siten, että uudet ominaisuudet eivät jatkossa riko vanhoja versioita.

Nyt metadatassa ja tarkastusluokissa on enemmän toiminnallisuutta. Julkaistaan versio 1.0 🙂

Customer
Name,1,80,0,Bloki.Characters
City,1,11,l,Bloki.Characters
Account,0,50,u,Bloki.Characters

Uuden parametrin avulla voidaan muuttaa tarkastettu arvo u-merkin avulla isoiksi kirjaimiksi eli suuraakkosiksi. Vastaavasti l-merkki muuttaa arvon pieniksi kirjaimiksi eli pienaakkosiksi. Kun parametri ei ole kumpikaan edellisistä ei muunnoksia tapahdu.

Rajapintaan olen lisännyt uuden metodin (Command). Sen avulla seuraavissa versioissa voi lisätä uusia ominaisuuksia.

interface IVariable
{
String Validate(String value);
String Command(String parameters, String data = null);
}

Tarkastuksen perusluokkaan lisäsin virheilmoituksen tuottamisen ja koodin uuden u/l -parametrin käyttöön.

public class VariableBase
{
protected String _name;
protected int _minLength;
protected int _maxLength;
protected String _case;

public VariableBase(String metadata)
{
String[] parts = metadata.Split(’,’);
_name = parts[0];
_minLength = Int32.Parse(parts[1]);
_maxLength = Int32.Parse(parts[2]);
_case = parts[3].ToLowerInvariant();
}

protected void LenghtCheck(String value)
{
if (value == null || value.Length _maxLength)
{
throw new ArgumentException(”Validate”);
}
}

protected String UpperCheck(String value)
{
if (_case == ”u”)
{
return value.ToUpperInvariant();
}
else if (_case == ”l”)
{
return value.ToLowerInvariant();
}
return value;
}

protected String LengthSetup()
{
return ”(” + _minLength + ”,” + _maxLength + ”)”;
}
}

Päivitin toteutetun merkkijonon tarkastusluokan tukemaan uusia toimintoja. Nyt saadaan siisti virheilmoitus ja mahdolliseen Sql serveriin tallentamiseen tietotyyppi. Version 1.0 tuetut ominaisuudet Command-metodissa ovat ”error”, ”sqldeclare” ja ”name”. Uusissa versioissa näitä sitten lisätään tarpeen mukaan.

public class Characters : VariableBase, IVariable
{
public Characters(String metadata) : base(metadata)
{
}

public String Validate(String value)
{
LenghtCheck(value);
foreach (char character in value)
{
if (char.IsLetter(character) == false)
{
throw new ArgumentException(”Validate”);
}
}
return UpperCheck(value);
}

public string Command(String parameters, String data)
{
if (parameters == ”error” && String.IsNullOrEmpty(data) == true)
{
return ”One word ” + LengthSetup();
}
else if (parameters == ”sqldeclare” && String.IsNullOrEmpty(data) == true)
{
return _name + ” NVARCHAR (” + _maxLength + ”)”;
}
else if (parameters == ”name” && String.IsNullOrEmpty(data) == true)
{
return _name;
}

return String.Empty;
}
}

Käytännössä olen oppinut, että ominaisuuksia kannattaa lisätä vain silloin, kun niitä tarvitaan. Ei kannata keksiä tarpeita etukäteen.

Geneerisyys ei saa vaikeuttaa normaalia hadkoodausta. Osa sovellusta voi olla kokonaan geneerinen, osa voi olla osin hardkoodattua ja osa kokonaan hardkoodattua. Monimutkaiset asiat ovat monimutkaisia ja ne kannattaa hardkoodata.

Näppituntuma on, että sovelluksesta noin 80 prosenttia on hyvä olla geneeristä ja loput hardkoodattua. Silloin osat ovat yleensä tasapainossa.

Geneerisellä mallilla perusasiat saadaan varsin vaivattomasti ja päästään suoraan haasteisiin kiinni. Projektissa tämä säästää paljon aikaa, koska vauhtiin päästään nopeasti ja resursseja jää enemmän monimutkaisten osien toteutukseen.

Seuraavassa blogissani kuvaan, miten luodaan Json ”sanoma” esitellyn metadatan avulla. Lisäksi perehdytään sen talletukseen Sql serveriin.

Mitä on geneerinen ohjelmointi (osa2)?

Mitä on geneerinen ohjelmointi (osa2)?

Edellisessä blogissani kerroin geneerisen ohjelmoinnin perusasioita. Siinä yhteydessä jäi mainitsematta, että Regex-tarkastusta ei kannata käyttää, jos halutaan palvelimelta ulos satoja sivuja sekunnissa.

Seuraavassa vaiheessa lisätään uusi luokka, jolla ladataan dynaamisesti arvojen tarkastusluokkia, joilla voi tsekata esimerkiksi postinumeroita tai sotu-tunnuksia. Arvojen tarkistusluokat voidaan vaihtoehtoisesti joko ladata aina tarvittaessa tai kerätä listalle, josta niitä voidaan käyttää uudelleen.

Kutsuvan koodin ei käännöksen aikana tarvitse tietää tarkistusluokkien toteutusta. Valmiin sovelluksen hakemistoon voidaan myöhemmin kopioida uusia assemblyjä ja metadatan avulla saada ne heti käyttöön.

Metadataan on seuraavassa esimerkissä lisätty luokan namespace, joka tässä esimerkissä on myös sama kuin assemblyn nimi.

Customer (kentän nimi, minimipituus, maksimipituus ja käsittelyluokan nimi)
– Name,1,80,Bloki.Characters
– SosId,1,10,Bloki.SosId
– Account,0,50,Bloki.Account

Alla oleva uusi Plugin-toteutus lataa dynaamisesti luokan metadatan ohjeiden mukaan.

public static class Plugin
{
public static object Load(String metadata)
{
String[] parts = metadata.Split(’,’);
String assemblyName = parts[3].Substring(0, parts[3].IndexOf(’.’));
return Load(assemblyName, parts[3], metadata);
}

public static object Load(String assemblyName, String className, object constructor)
{
Assembly assembly = Assembly.Load(new AssemblyName(assemblyName));
Type classType = assembly.GetType(className);
return Activator.CreateInstance(classType, constructor);
}
}

Sitten lopuksi aiemmin lupaamani testikoodin geneerinen toteutus.

String metadata = ”Name,1,80,Bloki.Characters”;
IVariable variable = (IVariable)Plugin.Load(metadata);
String generic1 = variable.Validate(”Jarmo”);

Muutamalla rivillä koodia on saatu yleinen ratkaisu tiedon tarkastukselle. Määrittelykoodia on niin vähän, että sen toiminnan ymmärtää helposti. Käytännössä alle parikymmentä luokkaa riittää isonkin sovelluksen tarpeisiin. Vanhat luokat voi käyttää uudelleen ja uudet luokat voi hyödyntää vanhojen sovellusten ylläpidossa.

Luokissa voidaan kutsua ulkoisia palveluita. Tästä esimerkkinä voi mainita rekisterinumeron tai postinumeron tarkastuksen.

Voidaan muun muassa hyväksyä erikoisia muotoja helpottamaan päivämäärän syöttöä.
– T = kuluva päivä
– T+2 = ylihuominen
– 3.4 = 3.4.2016 (oletuksena kuluva vuosi)

Seuraavassa blogissa käsittelen uusien ominaisuuksien antamista Variable-luokalle. Ensimmäisessä versiossa on tärkeää, että rajapinta toteutetaan siten, että uudet ominaisuudet eivät jatkossa riko vanhoja versioita.

Mitä on Geneerinen ohjelmointi (osa1)?

Mitä on Geneerinen ohjelmointi (osa1)?

Joka niemennotkossa ja saarelmassa koodataan. Usein samoja juttuja uudestaan ja uudestaan. Tämän huomaa, kun katsoo toimintaa vuorenhuipulta.

Geneerisesti toteutettavaksi valitaan mainittuja yhteisiä juttuja. Tällaista lähestymistä olen käyttänyt onnistuneesti monissa projekteissa yli 30 vuoden ohjelmointiurani aikana. Maailma on sinä aikana muuttunut paljon, mutta tämä geneerinen lähestymistapa toimii edelleen yhtä hyvin kuin aikaisemminkin.

Kyse ei ole patterneista, vaikka tämä ehkä on sukua niille. Patterneja lähemmin tarkasteltaessa minulle tulee epämiellyttävä olo. Ne voivat vaatia kolmannen osapuolen koodia tai olla liian monimutkaisia ylläpitää. Sovelluksen pitää olla sellainen, että peruskoodari – joka ei ole ollut projektissa mukana – voi sitä ylläpitää. Tämä on nykyään arkipäivää, kun sovelluksen ylläpito siirretään vaikka Intiaan.

Suurimman hyödyn uudelleenkäytöstä saa, kun perusjutut ovat valmiina ja testattuina alusta lähtien. Tällöin toteutetaan geneeriseen osaan vain uudet ominaisuudet, jotka ovat sitten jatkossa uusien ja vanhojen sovellusten käytössä. Sitten voi keskittyä vaikeampien osuuksien toteutukseen.

Koodia on syytä tehdä ylläpito ja pahin vaihtoehto mielessä. ”Kuvittele, että sovellusta tulee ylläpitämään psykopaatti, joka tietää missä asut”, eräs ystäväni kerran konkretisoi tilannetta. Tiivistetysti voi sanoa, että et kirjoita koodia itsellesi, vaan ylläpitäjälle.

Geneerisen ohjelma on parhaimmillaan
– helposti ymmärrettävä
– yksinkertainen
– siihen helppo lisätä uusia ominaisuuksia
– uudelleenkäytettävä
– vähän koodia
– vältetään hardkoodausta
– vähän riippuvuuksia
– päivitykset osina.

Nyt sitten esimerkki tälläisestä toteutuksesta.

Tehtävä on tehdä lomake, johon syötetään vaikka asiakkaan nimi, hetu ja tilinumero. Lomake lähettää tiedot palvelimelle ja palvelin tallettaa ne tietokantaan. Tiedot pitää tarkastaa lomakkeella ja palvelimella. Pitää varautua siihen että myöhemmin tulee lisää tietoja, lomakkeita, kenties kielituki ja kuvien talletus palvelimelle.

Helposti tulisi mieleen nopeasti hardkoodata koko juttu alusta loppuun. Mutta nyt tehdään toisin. Määritellään ensin asiakkaan tiedot. Pidetään mielessä että määrittely todennäköisesti tulee muuttumaan.

Customer (kentän nimi, minimipituus, maksimipituus ja käsittelyluokan nimi)
– Name,1,80,Characters
– SosId,1,10,SosId
– Account,0,50,Account

Ylläoleva on metadataa sovellukselle. Sitä muuttamalla muutetaan sovelluksen toimintaa. Esimerkkikoodit toimivat, mutta vain pakollinen koodi ilman poikkeustarkistuksia on toteutettu.

interface IVariable
{
String Validate(String value);
}

public class VariableBase
{
private int _minLength;
private int _maxLength;

public VariableBase(String metadata)
{
String[] parts = metadata.Split(’,’);
_minLength = Int32.Parse(parts[1]);
_maxLength = Int32.Parse(parts[2]);
}

protected void LenghtCheck(String value)
{
if (value == null || value.Length _maxLength)
{
throw new ArgumentException(”Validate”);
}
}
}

public class Characters : VariableBase, IVariable
{
public Characters(String metadata) : base(metadata)
{
}

public String Validate(String value)
{
LenghtCheck(value);
foreach (char character in value)
{
if (char.IsLetter(character) == false)
{
throw new ArgumentException(”Validate”);
}
}
return value;
}
}

String metadata = ”Name,1,80,Characters”;
Characters test = new Characters(metadata);
String result1 = test.Validate(”Jarmo”); //OK
String result2 = test.Validate(”Jarm0”); //ERROR

Yllä interface, perusluokka ja merkkijonon tarkastuksen toteutus geneerisyyden mahdollistamalla tavalla. Lopuksi sen testikoodi hardkoodattuna. Samoja luokkia ajetaan sitten käyttöliittymässä ja palvelimella. Seuraavassa osassa tulossa testikoodi geneerisellä tavalla.

Geneerisen projektin nosto Windows 10 -versioksi

Geneerisen projektin nosto Windows 10 -versioksi

Windows 10 -alustaan on pikku hiljaa saatu mielenkiintoisia, uusia osia. On ollut hienoa nähdä, miten ne toimivat.

Universal Windows Platform (UWP) tuottaa sovelluksia esimerksi seuraaville laitteille
– Puhelin (Myös Continuum)
– Tabletti
– PC
– Raspberry Pi ja muut pienet laitteet
– XBox (tulossa)
– HoloLens (tulossa).

Aikaisemmassa blogissa kerroin varhaisemmista sovelluksista ja nyt olen nostanut sovellukset UWP-versioksi. Monella eri tekniikalla toteutetut projektit on nyt yhdistetty. Samalla jotain jätettiin pois, mutta myös uutta syntyi.

Tekemäni järjestelmän tarkoitus on olla helppokäyttöinen datan talletus-, raportointi-, IOT-, viestinlähetys- ym. moottori. Kaikkea ohjataan tekstitemplateilla ja -säännöillä.

Säännöillä ohjataan esimerkiksi mailin lähetystä tai IOT-laitteiden toimintaa. Konfigurointia voi muuttaa niin sanotusti ”lennossa”.

Azureen jäi ainoastaan Web Api -rajapinta. Muut poistuivat tarpeettomina.

Azuressa käytetyt tekniikat ovat
– Web Api, johon lähetetään luokka ja palautetaan luokka. Kaikki data kulkee samalla tavalla (teksti ja binääri).
– Geneerinen Table Storage tallettaa tekstimuotoista dataa (lomakkeet, IOT-data ym.)
– Vaihtoehtoisesti voi käyttää Azuren DOcumentDB-varastoa tekstimuotoiselle datalle.
– Binäärit talletetaan Blob Storageen (kuvat ym.)
– SendGrid lähettää mailit (Esim. valokuvan lähetys, kun sääntö laukeaa.)
– SignalR lisättiin nopeuttamaan IOT-laitteiden toimintaa. (Laitteet keskustelevat nopeasti sääntöjen avulla.)

Tässä kohtaa huomasin UWP-mallin voiman. Eri tekniikat sulautuivat yhteen ja syntyi kolme eri sovellusta.

Oli upeaa nähdä, miten ne toimivat monilla eri laitteilla. Sovellukset toimivat siis samalla tavalla – ja samalla koodilla – eri laitteissa.

Käytän sovelluksia näissä laitteissa
– Puhelin ja sen Continuum-tila
– Tabletti
– PC
– Raspberry Pi.

Ainoa merkittävä ero on puhelimen normaalitilassa oleva kapeampi näyttö. Busineslogiikkakoodi on samaa clienteissä ja palvelimella. Tämän koodin jako on tehty vielä perinteisellä tavalla.

Clientin sovellukset
– Admin-sovellus, jolla luodaan uusi sovellus ja sen templatet.
– User-sovellus, jolla sitten syötetään dataa manuallisesti ja katsotaan businessgrafikkaa.
– IOT-sovellus, joka lähettää dataa ja hälytyksiä.

Esimerkiksi
– IOT-puhelimessa voi lähettä paikkatietoa.
– IOT-tabletissa voi otaa kuvia tai lähettää asentotietoa.
– IOT-Rasbberryssä voi lähettää säätietoja.

Toteutettuja IOT-ajureita ovat paikannus, nopeus, valoisuus, sää, puhe, wemo-valot ja -kytkimet.

Sovellus siis luodaan ja käytetään noilla clienteillä. Koodausta ei tarvita.

Uudelleenkäyttö on tässä kaiken ydin. Vain uudet ominaisuudet koodataan ja ne ovat sitten kaikkien vanhojen ja uusien sovellusten käytössä.

Tällä sovelluksella olen tehnyt paljon erilaisia toimivia demoja. Näitä tekniikoita olen käyttänyt onnistuneesti myös asiakasprojekteissa.

Parhaimmillaan sovellukseni on tilanteissa, joissa datakenttiä tulee lisää tai muutoksiin pitää varautua. Muutokset voi tehdä ’lennossa’. Lomakkeilla on lisäksi datan muototarkistukset ja virheilmoitukset.

Esimerkkejä
– Projektinseuranta
– Outlookissa huone-, laite- ja tarjoiluvaraukset
– Businessgrafiikka ja porautuminen
– Valojen sytytys, valokuvan ottaminen ja lähetys mailina sekä ”kiitos kuvasta” -puheena, kun liiketunnistin laukeaa.

Näen UWP-ohjelmointimallin mainiona yritysten kannalta. Sama sovellus toimii tarpeen mukaan eri laitteilla. Ei olla sidottuina laitteeseen tai paikkaan. Työtä voi jatkaa samalla ohjelmalla, mutta toisenlaisella laitteella.

Puhelimen Continuum-tila oli mukava yllätys. Sovellusta voi siinä ajaa täysikokoisena ulkoisella näytöllä. Uskonkin tämän menestykseen.

Lopuksi arvaus tulevasta kehityksestä työpaikoilla. Jatkossa töihin tullessa puhelin liitetään johdolla telakkaan, johon on liitetty näyttö, näppäimistö ja hiiri. Azure ja Office toimivat eri sovellusten palvelimena.

Geneerinen ohjelmointi helpottuu

Geneerinen ohjelmointi helpottuu

Olen aina ollut uudelleenkäytettävän koodin kannattaja. Tämä ei tarkoita open sourcea, vaan yrityksen sisällä tapahtuvaa uudelleenkäyttöä.

Olen tehnyt sovellusta, jolla voi helposti luoda lomakkeita ja bisnesgrafiikkaa eri käyttöliittymille. Tavoitteena on mahdollisimman vähän koodia, joka sitten jaetaan sovellusten kesken.

Käyttöliittymät ja palvelin voivat suorittaa komentoja. Komennoilla voidaan esimerkiksi kerätä dataa IOT-laitteista (IOT=internet of things). Komennoilla voidaan myös lähettää esimerkiksi meilejä, jos kulloinkin määritelty sääntö toteutuu.

Kaikki lomakkeet, grafiikat ja säännöt määritetään tekstipohjaisina ohjeina. Data tallettuu ohjeiden mukaan Azuren TableStorage-, Blob-, DocumentBD- tai muuhun varastoon.

Windows- ja web-sovellusten vaiherikas lähihistoria

Windows Presentation Foundation eli WPF on ollut käytössä kymmenkunta vuotta. Sen myötä saatiin uusi tapa tehdä sovelluksia. Käyttöliittymien määrittelyä voitiin tehdä XAML:n eli Extensible Application Markup Languagen avulla.

Seuraavaksi vuonna 2007 tuli uusi tapa tehdä websovelluksia selaimiin. SilverLight 1 haaroitettiin WPF 3 -versiosta.

Sen jälkeen WPF ja SilverLight kehittyivät erillisinä haaroina. Koodi ei enää ollut täysin yhteensopivaa niiden välillä.

Windows Phonen aika

Näiden vaiheiden jälkeen vuonna 2009 tuli Windows Phone 7 -versio. Se haaroitettiin SilverLight 3 -versiosta. Myöhemmin vuonna 2010 tuli WP 7.5, joka haaroitettiin SilverLight 4 versiosta.

Seuraavaksi saatiin Windows Runtime, josta viimeisin versio on 8.1. Tätä kirjoittaesa myös Windows 10 Beta on jo julkaistu.

Tämän monihaaraisen kehityksen tuloksena saatiin siis aikaiseksi viisi eri versiota XAML-pohjaisesta kehitysympäristöstä. Nämä olivat  WPF 4.5, SilverLight 5, Windows Phone 8.1 SilverLight, Windows Phone 8.1 Windows Runtime ja Windows 8.1 WinRT-tablettiversio. Versiot olivat enemmän tai vähemmän yhteensopivia.

Oma ”Universal App”

Siinä vaiheessa kun Windows Phone 7 -versio tuli, aloin tehdä  Proof of concept- eli POC-sovellusta silloin olemassa olleille erilaisille käyttöliittymille. Kyseessä oli siis ”Universal App” ennenkuin sellaista oli julkaistu.

Saamaa koodia uudelleenkäytettiin mahdollisimman paljon eri käyttöliittymissä ja palvelimella. Ensimmäinen versio oli toteutettu jakamalla lähdekoodeja eri projektien välillä.

Koodatessa projektit näyttivät normaaleilta, mutta lähdekooditiedostoja oli vain yksi. Erilaisuudet hoitettiin #ifdef-käännöslipuilla.

Tein monentyyppisiä sovelluksia samalla koodilla. Ensimmäinen versioni oli oheisen listan mukainen.
– WPF oli natiivi Windows käyttöliittymä sovellus. Asennus click once -tyyliin.
– Windows Phone 7 SilverLight -käyttöliittymä
– SilverLight Web -käyttöliittymä
– Käyttöliittymä ajettavaksi Outlookin sisällä
– Azure WCF/IIS -palvelin datan talletukseen.
– Erilaisia WCF client -projekteja
– Business logic -luokkakirjasto

Virallinen ”Universal app”

Sitten julkaistiin Portable Class Library eli PCL ja myöhemmin Universal Application. Ne olivat pieniä askelia oikeaan suuntaan.

Universal Application oli WP8.1- ja Win 8.1 -sovellukset yhdistävä uusi, projektin luomiseen tarkoitettu template. Se teki niitä samoja temppuja, joita olin tehnyt omalla sovelluksellani aiemminkin. Molemmissa oli WinRT pohjana, joten ne ovat melkein yhteensopivia.

Tässä on lista sovelluksistani kyseisessä vaiheessa.
– WPF natiivi Windows-käyttöliittymäsovellus. Asennus click once -tyyliin.
– Windows Phone 8.1 -käyttöliittymä
– SilverLight Web -käyttöliittymä
– Käyttöliittymä ajettavaksi Outlookin sisällä
– Windows 8.1 tablet -käyttöliittymä.
– Azure WCF/IIS -palvelin datan talletukseen ja websivujen ajamiseen.
– Erilaisia WCF client -projekteja
– Netduino client
– Business logic -luokkakirjasto PCL

Siirtymävaiheen ongelmia

Ongelmana tässä versiossa on tärkeiden osien yhteensopivuuden puute. Esimerkkinä voi mainita WCF:t, joista osa on perinteisiä kutsuja ja osa await-tyyppisiä. Puhelimessa vain silverlight-versio tukee WCF-kutsuja.

Bitmap-kuvien käsittely, File Picker jne. ovat erilaisia. Pienimmät erot ovat saman api-luokan erilaisissa nimiavaruuksissa. Suurin ero on siinä, että toteutus on täysin erilainen eri ympäristöissä.

On ymmärrettävää, että Microsoft haluaa hävittää eri SilverLight-versiot. Kuitenkin puhelimissa on paljon SilverLight-koodia ja sen tuki jatkuu kauan.

Selaimista tuki taitaa hävitä kokonaan. Se on sääli, sillä kehitysympäristönä XAML ja C# on paljon tuottavampi ja parempi kuin HTML ja JavaScript.

Puhelimissa ja tableteissa natiivisovellukset ovat kuitenkin parempia kuin webbipohjaiset sovellukset. Voikin otaksua, että jatkossa HTML- ja Javaskript-webbisovellusten tarve puhelimissa ja tableteissa vähenee.

Seuraava askel?

Windows 10 tuo mukanaan lupauksen todellisesta Universal App -tuesta. Mutta vain osalle toteuttamistani käyttöliittymistä.

Mikä voi muuttua Windows 10 -version myötä? Eräs kummallisuus on ollut SQL Compact -version puuttuminen WinRT-versiosta. Puhelimessa se on ollut käytettävissä. Toiveiden listalla onkin sen paluu.

WCF-tarina alkaa olemaan myös loppusuoralla.

Hahmotelma tulevasta

Millainen on sovellukseni seuraava versio, johon tulen porttaamaan koodini? Seuraavaa version hahmotan tässä vaiheessa näin.
– WPF-version voi pudottaa pois. Uudessa universal app -versiossa ikkunoiden koko on vapaasti muutettavissa.
– Windows runtime 10 -käyttöliittymä UAP
– Windows phone 10 -käyttöliittymä UAP
– Silverlight ehkä jää pois, vaikka se olisi hyvä esimerkiksi upotettuna SharePoint -sivulla.
– Outlookin kohtalosta ei vielä tietoa.
– Azure WCF/IIS -palvelin datan talletukseen.
– WCF-käyttö hiipuu ja korvaan sen web-apilla
– Netduino client todennäköisesti poistuu
– Bisneslogiikan jako clienttien ja Azuren välillä?
– XBox-käyttöliittymä
– Raspberry ym. pikkukoneet, jotka ajavat jatkossa Windows 10 -versiota (korvaa Netduinon).

Kirjoittelen lisää, kun uusi versio alkaa olemaan koossa. Odottelen innokkaasti Windows 10:n mukanaan tuomia mahdollisuuksia.

C# async constructors

Kun async/await julkaistiin C# koodiin siitä seurasi hyviä asioita ja joitakin ongelmia. Niiden avulla saadaan käyttöliittymä vastaamaan käyttäjän syötteisiin nopeammin. Kyse ei ole kuitenkaan oikeasta moniajosta, koska kaikki tapahtuu UI säikeessä. Vasta Task.Run() avulla saadaan oikean moniajon hyödyt irti. Tässä jutussa kerron miten itse olen ratkaissut await kutsun konstruktorissa. Normaalisti siellä suoritetaan koodia, minkä pitää olla valmis ennen kuin luokkaa käytetään. Ongelmaa ei ole jos valmistumista ei tarvitse odottaa ja kutsu ei palauta mitään. Seuraavassa esimerkissä ei odoteta eikä palauteta mitään. Koska kyseessä on void eikä Task metodin suoritus sitä ei odoteta.

public MainPage()
{
Doit()
}
private async void Doit()
{
await DoAsync();
}

DoAsync valmistuu joskus sen jälkeen kun konstruktori on valmis. Mutta monessa apissa nykyään tarvitaan await kutsuja. Kun paluuarvoja tarvitaan konstructorissa, pitää tuloksia odottaa ja tarvitaan hieman lisää koodia.

public MainPage()
{
var task = Task.Run(async () =>
{
return await DoAsync();
});
double result = Task.WhenAll(task).Result[0];
}

Nyt async koodi ajetaan konstruktorissa ja lopputulos saadaan talteen. Seuraavassa ajetaan monta async kutsua rinnakkaisesti ja otetaan lopputulokset talteen. Tämä on nopeampi tapa kuin UI säikeessä tapahtuva ajo.

List<Task> tasks = new List<Task>();
for (int i = 0; i
{
return await DoAsync();
}));
}
double all = 0.0;
foreach (var result in Task.WhenAll(tasks).Result)
{
all += result;
}

Tätä samaa tekniikkaa voi hyödyntää kun async alkaa ’kuplimaan’ luokissa ylöspäin. Sillä saa katkaistua nousun kun luokkakirjaston syövereissä joutuu käyttämään await kutsua. Await ei sitten näy kirjaston käyttäjälle. Tämäkään ei ole ”kultainen vasara” kaikkeen, varsinkin jos tällä tavoin ajettavassa koodissa esimerkiksi ajetaan Dispatcherin kanssa koodia UI säikeessä saadaan aikaiseksi deadlock.