2018. május 27., vasárnap

XAMARIN és a WCF

A ritka kivételektől eltekintve szinte mindegyik mobil alkalmazás kommunikál valamilyen backend rendszerrel, most csupán a Windows  Communication Foundation (WCF) vonalra térnék ki.

Sok helyen lehet találkozni olyannal, hogy nem működik a WCF Xamarin alatt, pedig csupán néhány korlátozással kell együtt élni a cél elérése érdekében.

Ezen a linken elérhető egy követhető leírás Xamarinhoz arról, hogyan kell összekötni egy WCF-es kiszolgálóval. Ehhez tennék kiegészítést néhány speciális esetet megvizsgálva.

Korlátozások

  • Nincs XAML konfig
A "hagyományos" XML alapú konfigurációs fájlokkal tényleg nem lehet megvalósítani, mivel nem lehet csak úgy odatenni az indítófájl mellé. Másfelől .NET C# alatt, - hasonlóan a WPF UI vezérlőkhöz - ami konfig fájlban XAML-lel leírható, az C# kóddal is. Ezt használja ki a fentebb linkelt tutoriál is.

  • Nincs NetTCPBinding
Fontos megjegyezni, hogy mobilon nem létezik a NetTCPBinding, csak valamilyen *HttpBinding típust lehet használni.

  • Nincs EndpointBehaviors
PCL készítésekor egy WCF kliensben a Behavior helyett az EndpointBehaviort kell használni, ami Windows RT-n még elfut, de Android és iOS rendszereken a MONO alatt NotImplementedExceptionnel elszáll. Szerencsére reflectionnel kinyerhető a megoldás:
//client.Endpoint.EndpointBehaviors.Add(new EndpointBehavior());

//client is of type System.ServiceModel.ClientBase<T>
var prop = client.Endpoint.GetType()
                 .GetTypeInfo()
                 .GetDeclaredProperty("Behaviors");
var obj = (KeyedCollection<Type, IEndpointBehavior>)prop
                 .GetValue(client.Endpoint);
obj.Add(new EndpointBehavior());

Ugyanez a helyzet a MessageInspectorral is:
//clientRuntime.ClientMessageInspectors.Add(new MessageInspector());

var prop = clientRuntime.GetType()
                        .GetTypeInfo()
                        .GetDeclaredProperty("MessageInspectors");
var obj = (ICollection<IClientMessageInspector>)prop
                        .GetValue(clientRuntime);
obj.Add(new MessageInspector());

Wrapper a WCF hívás körül

Korábban már írtam arról, hogyan célszerű felkészülni arra, ha a WCF kliens hívása kezeletlen hibára fut. Ilyenkor nem szabad a Close()-t ráhívni, mint ahogy azt teszi alapértelmezetten a USING, helyette Abort() kell.

Async/await és a WCF

Xamarin alatt is használhatók az async/await kulcsszavak, amik tökéletesen illeszkednek az aszinkron WCF hívások köré. Bővebb leírás itt található.

public class WcfServiceWrapper:IWcfServiceWrapper
{
    private IWcfService _wcfService;

    public WcfServiceWrapper(IWcfService wcfService)
    {
        if (wcfService == null) throw new ArgumentNullException("wcfService");

        _wcfService = wcfService;
    }

    public async Task<string> GetDataAsync(int number)
    {
        return await new TaskFactory()
          .FromAsync<int,string>(
            _wcfService.BeginGetData, 
            _wcfService.EndGetData, 
            number, 
            null, 
            TaskCreationOptions.None
          );
    }
}

.NET Portable Assembly


Ha esetleg bármi miatt szeretnénk hozzáadni mobilon is használható .NET portable assembly-t, akkor azok itt találhatók:
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\

Generált proxy és az IExtensibleDataObject


A WCF által publikált WSDL alapján le lehet generálni a hálózaton keresztül utazó osztályokat. Amennyiben a DataContractSerializert használjuk szerver oldalon, akkor ráteszi az IExtensibleDataObject interfészt és a hozzátartozó ExtensionDataObject tulajdonságot ezen generált osztályokra. Önmagában nem lenne baj, hiszen a System.Runtime.Serialization.dll-ben elérhető ez az interfész és osztály, van belőle portable DLL is, gondolhatnánk, hogy majd hozzáadjuk a PCL projekthez és készen vagyunk. Nos, ez az út nem járható, nem engedi hozzáadni azzal az üzenettel, hogy már referálja a sajátját. A probléma az, hogy abban viszont nincsenek benne ezek. 

1) Egyik megoldás lehet, hogy a kiszolgáló oldalon a ServiceContract attribútumban vagy akár magában a DataContractSerializerben bekapcsoljuk, hogy IgnoreExtensionDataObject. Viszont ezek a nevüknek megfelelően csak annyit csinálnak, hogy figyelembe kell-e venni az ExtensionDataObjectet vagy sem, de továbbra is megmaradnak az osztályokban. Elvileg a platformspecifikus projekthez már hozzá lehet adni ezt a dll-t, és utána működhet valahogy a DependencyService-en keresztül, de ezt a vonalat nem próbáltam ki.

2) Ha biztosan tudjuk, hogy egyáltalán nem használjuk semmire az ExtensionDataObjectet, akkor egy-egy üres osztállyal és interfésszel könnyedén orvosolható a helyzet:
 public interface IExtensibleDataObject { ExtensionData { get; set; } }  public sealed class ExtensionDataObject {}