2017. június 24., szombat

XAMARIN app nem indul eszközön debugban

Visual Studio-val szerettem volna egy XAMARIN.FORMS alkalmazást Android telefonon tesztelni, de az alábbi hibaüzenet fogadott:

The application could not be started. Ensure that the application has been installed to the target device and has a launchable activity (MainLauncher = true).
Additionally, check Build->Configuration Manager to ensure this project is set to Deploy for this configuration.

Attól függetlenül, hogy sikeresen lefordult és a deploy során sem panaszkodott semmire, mégsem találta meg a telefonon az alkalmazást. Az eszköz főmenüjét megnézve, valóban nem volt indítóikon. A helyzetet tovább bonyolította, hogy nem mindegyik eszköz reagált így, volt, amelyiken ment. Ráadásul a problémások másik gépen jól működtek.

A vizsgálódás során kipróbáltam, hogy ha másik csomagazonosítót állítok be, akkor mit reagál. Az új azonosítóval gond nélkül elindult mindegyik eszközön.

A problémás eszközökön mindegyiknél megtaláltam az Alkalmazások listájában a régi csomagazonosítóval az appot, tehát valamilyen formában már telepítve volt egy verzió. Végül összeállt a kép: a Visual Studio-val történő telepítés során - számomra ismeretlen célból - valamilyen egyedi azonosítót is generál az alkalmazásba - talán a Fast Deploymenttel lehet összefüggésben -, ami megakadályozza, hogy egy másik VS rá tudjon frissíteni a már telepített verzióra.

A megoldás az volt, hogy kézzel el kellett távolítani a korábbi verziót, ami a másik VS-től származott és azt követően mindegyik készüléken működött.

2017. június 23., péntek

Entity Framework cache frissítése

Entity Framework 6-ban a DbContext automatikusan cache-eli az adatokat, ami az esetek jelentős részében teljesen jó dolognak számít. Azonban vannak olyan szituációk, amikor az elérni kívánt adatok az aktuális contexten kívül már megváltoztak (például másik felhasználó módosította vagy tárolt eljárás ért hozzá). 

Több lehetőség is létezik a helyzet kezelésére:
1. A követés kikapcsolása az AsNoTracking() utasítással
2. Régi DbContext példány eldobása és új létrehozása
3. ObjectQuery használata a MergeOptions beállításával a DBQuery helyett
4. Elem frissítése (Reload / Refresh)
5. Elem leválasztása (Detach)
6. GetDatabaseValues hívás, hogy egy adott Elem (Entity) frissítve legyen
7. A régi adatok használata

Ezen a linken részletesen körbejárják az egyes lehetőségeket, hogy melyiket mikor is éri meg használni. 

2017. június 15., csütörtök

Visual Studio 2015 Crossplatform - hiányzó Xaml Page template

Visual Studio 2015 és Xamarin páros telepítését követően találkoztam azzal a jelenséggel, hogy hiába adtam a projekthez bármilyen új XAML Page vezérlőt, minden esetben csak egy C# fájlt készített, de XAML-t sosem. A probléma feloldásához a hiányzó sablonfájlt át kellett másolni a Xamarin alól a VS ItemTemplates mappájába.

Innen kell átmásolni:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\Xamarin\Xamarin\
<verzió>\T\IT\Cross-Platform\Code\XamlPage.zip

Ide:
C:\Users\<felhasználó>\Documents\Visual Studio 2015\Templates\ItemTemplates\
Visual C#\XamlPage.zip

Ezt követően a "Cross-Platform" szekcióban lehetett kiválasztani a "Forms Xaml Page" elemet.

2017. június 12., hétfő

SSRS Riport nyomtatásakor mező elrejtése

Gyakorta felmerül az igény arra, hogy egy vagy több elemet (felirat, link, stb.) nem szeretnénk, hogy megjelenjen az SSRS riportok nyomtatásakor vagy exportáláskor. Ebbe a kategóriába tartozik szinte az összes interaktív vezérlő. Jó példa egy "Tovább a kapcsolódó elemekre" link, ami egy subreportot tölt be, viszont PDF-ben már csak zavaró tényezőnek számít a felirat.

Az SSRS-ben elérhető egy globális változó (RenderFormat.IsInteractive), ami megmondja, hogy éppen interaktív módban van-e betöltve az adott riport. Erre a tulajdonságra kell beállítani az elrejteni kívánt elemek láthatóságát:  =(Globals!RenderFormat.IsInteractive = False)


2017. június 7., szerda

Visual Studio class template módosítása

Sokunkban felmerülhetett már a gondolat, amikor egy új osztályt adtunk egy projekthez Visual Studioban, hogy miért lesz privát és miért kell kézzel átállítani publikusra? Az esetek jelentős részében úgyis publikus szükséges.

Könnyedén megoldható ez a probléma, mivel minden, amit hozzá lehet adni új elemként a VS-ben, az egy sablonfájlból ered. Ebből adódóan az új osztályhoz is tartozik egy ilyen sablon, amelyben át lehet írni a láthatóságot. A fájlt rendszergazdai jogosultsággal kell megnyitni szerkesztésre.

Visual Studio 2010 és előtte: 
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\
Code\1033\Class.zip

A Class.zip-ben szereplő fájlba kell beleírni a public kulcsszót. Ezt követően egyszer ezzel paraméterrel kell indítani a Visual Studio-t, hogy frissüljön a cache: /installvstemplates

Visual Studio 2012 és felfelé: 
C:\Program Files (x86)\Microsoft Visual Studio <verzió>\Common7\IDE\ItemTemplates\CSharp\
Code\1033\Class.cs

Az újabb verzióknál elegendő csupán módosítani a C# fájlt, nincs szükség tömörítésre illetve a cache frissítésére.

2016. szeptember 11., vasárnap

Nem publikus metódusok/osztályok elérése külön Unit test projektben

Jó ötlet és helyes dolog, ha a unit testek egy (vagy esetleg több) külön projektbe kerülnek, ezzel szeparálva az éles kódtól. Másfelől nézve viszont egy külső projektből hivatkozva alapjáraton csak a publikus dolgokat tudjuk elérni, így könnyen támadhatna az az ötlet (hibásan), hogy fájó szívvel megszegjük elveinket és publikussá tegyünk bizonyos belső adatszerkezeteket, metódusokat, változókat pusztán azért, hogy tesztelhessük azokat. 

A .NET C#-ban a láthatósági szintek között szerepel egy olyan, hogy internal,  ami azt jelenti, hogy csak az adott assembly-n belül lehetséges hivatkozni rá, csak azon belül látható. Amennyiben mégis megpróbálnánk kívülről elérni (például egy szeparált unit test projektből), akkor fordítási idejű hibát dobna, mondván, hogy nem fér hozzá.

Rossz megoldás: mindent publikussá
Egyik lehetséges megoldás erre, hogy ha a kérdéses osztályt, metódust, stb., publikussá tesszük. Ez maximum csak első látszatra tűnhet megoldásnak, mivel hamar előjön, hogy szinte mindent publikussá kéne tennünk, amivel pedig éppen csak az osztály egyik célját, a hozzáférés-szabályozást veszítjük el. Létezik ennél egy hatékonyabb megoldás is.

Helyes megoldás: assembly-k összebarátkoztatása (Friend Assemblies)
Egy barátnak jelölt assembly ugyanúgy hozzá tud férni egy másik assembly-ben lévő internal típusaihoz és egyéb tagjaihoz, mintha azok publikusak lennének. Az InternalsVisibleTo attribútum használatával lehet megjelölni egy vagy akár több assembly-t barátként.

Ezt az attribútumot annak a projektnek az AssemblyInfo.cs fájljába kell tenni, amelyiket szeretnénk elérhetővé tenni:

[assembly: InternalsVisibleTo("UnitTestProject")]

Ennek hatására projektszinten minden, ami internal láthatóságú, elérhetővé válik a barát számára.

2016. augusztus 31., szerda

RaisePropertyChanged attribútum property-re PostSharppal

MVVM patternt használva WPF fejlesztés során elkerülhetetlen, hogy implementáljuk az INotifyPropertyChanged interfészt. Amikor egy olyan tulajdonság változik a ViewModelben, amire a felületen valami rá van kötve, akkor egy PropertyChanged eseményt kell dobni, hogy értesüljön a UI is. Ehhez tartozóan egy property tipikusan így szokott kinézni:

public class MyViewModel : ViewModelBase
{
    private string demoProp;

    public string DemoProp
    {
        get { return demoProp; }
        set
        {
            if (demoProp == value) return;
            demoProp = value;
            OnPropertyChanged("DemoProp");
        }
    }
}

Ebben a példában a ViewModelBase ősosztály implementálja az interfészt:

public class ViewModelBase : INotifyPropertyChanged
{
    private event PropertyChangedEventHandler propertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (!string.IsNullOrWhiteSpace(propertyName) &&
           propertyChanged != null)
 {
       propertyChanged(this, new PropertyChangedEventArgs(propertyName));
 }
    }
}



PostSharp nagy mértékben meg tudja könnyíteni az életet úgy, hogy átláthatóbb, karbantarthatóbb kódot eredményez az aspektus orientáltság révén. A fenti DemoProp kódját többféle módon is képes egyszerűsíteni, ebből két lehetséges:

1. Osztályszintű attribútum és nincs manuális kódolás
Ekkor elegendő egy attribútumot felhelyezni az osztályra és fordításkor minden publikus autoproperty megváltoztatásáról ugyanúgy értesülni fog a rendszer, mintha saját magunk implementáltuk volna az INotifyPropertyChanged interfészt, illetve kötöttük volna be a property-k set ágába az esemény kiváltását. Ennek részletes működéséről itt lehet olvasni.

Ennek a módszernek egyszerre előnye és hátránya is, hogy csak osztályra lehet feltenni ezt az attribútumot. Ebből adódóan nincs lehetőség arra, hogy ki lehessen választani, hogy melyik property-t figyelje és melyiket ne. 

2. Saját készítésű attribútum property-re
Előfordulnak olyan esetek, amikor nem jó az 1. módszer, mivel nem akarjuk az összes property-re ráakasztani. Például ha saját magunk már implementáltuk az INotifyPropertyChanged interfészt, viszont vannak olyan property-k, amikhez nem tartozik extra logika  az érték megváltozásához a felület értesítését leszámítva. Emiatt felesleges is lenne kézzel megcsinálni hozzá a backfieldet és a get/set metódusokat. Egyszerűbb lenne, ha csak egy attribútumot kéne feltenni egy autoproperty-re.

A fenti MyViewModel átírva így nézne ki. A különbség szerintem önmagáért beszél:

public class MyViewModel : ViewModelBase
{
    [RaisePropertyChanged]
    public string DemoProp get; set}
}


A példában használt RaisePropertyChanged PostSharp attribútum pedig alább látható:

using System;
using System.Reflection;
using PostSharp.Aspects;

[AttributeUsage(AttributeTargets.Property)]
[Serializable]
public class RaisePropertyChangedAttribute : OnMethodBoundaryAspect
{
    /// <summary>
    /// Property neve
    /// </summary>
    private string propertyName;
    /// <summary>
    /// történt-e változás, kell-e értesítés
    /// </summary>
    private bool hasChanged;

    /// <summary>
    /// fordítási időben lekérdezett mezőinformáció
    /// </summary>
    private PropertyInfo propertyInfo;

    public override void OnEntry(MethodExecutionArgs args)
    {
        //A CompileTimeValidate biztosítja, hogy ez a kódrészlet
        //csak ViewModelBase-ben lévő property-khez tartozó Sethez legyen hozzáfűzve

        //mivel ez egy Set metódusba való belépés elején fut, 
        //így biztosan van egy paramétere (value)

        object value = propertyInfo.GetValue(args.Instance);
        object param = args.Arguments[0];

        hasChanged = (value != null && param == null) ||
                        (value == null && param != null) ||
                        (value != null && !value.Equals(param));  

        if (!hasChanged)
        {
            args.FlowBehavior = FlowBehavior.Return;
            return;
        }

        base.OnEntry(args);
           
    }

    public override void OnSuccess(MethodExecutionArgs args)
    {
        //siker esetén kell csak dobni az értesítést, 
        //feltéve, hogy történt egyáltalán változás

        if (!hasChanged) return;

        ((ViewModelBase) args.Instance).ReportPropertyChanged(propertyName);
    }

    public override bool CompileTimeValidate(MethodBase method)
    {
        var type = method.ReflectedType;
        if (type == null) return false;

        //ha nem ő maga egy ViewModelBase és ráadásul nem is leszármazottja
        if (type != typeof(ViewModelBase) &&
            !type.IsSubclassOf(typeof(ViewModelBase))) return false;

        //ha nem Set metódus
        if (!IsPropertySetter(method)) return false;
           

        //fordítási időben eltároljuk a property nevét és a leíróját
        propertyName = GetPropertyName(method);
        propertyInfo = type.GetProperty(propertyName);
           
        return true;
    }

    private static string GetPropertyName(MethodBase method)
    {
        return method.Name.Replace("set_", "");
    }

    private static bool IsPropertySetter(MethodBase method)
    {
        return method.Name.StartsWith("set_");
    }
}