[Xamarin] VS2017 cross platform android + web service

0

Siema.

Uczę się programować na androida. Bazując na tutorialach w VS2017 wybieram nowy Projekt -> Cross Platform (i odznaczam iOS oraz UWP), standard.Udało mi się opanować (po japońsku - w sensie, nie napisami) xamla by stworzyć interfejs.Za pomocą tutków dodałem bibliotekę Zxing i odczytuję kody kreskowe.Stworzyłem sobie prosty web service i postawiłem na IIS.Web service ma dwie metody:1. HelloWorld() - zwraca string "Witaj świecie"2. PobierzDane( string kod kreskowy) - zwraca string[] z danymi (zwykły switch z kilkoma przykładowymi wpisami - na potrzeby nauki)

W celach testowych stworzyłem apkę pod WinForms by przetestować WS (w VS2017 dodaję przez Connected Service połączenie do mojego WS) - wszystko działa mam dwie metody jw. Próbowałem tak samo dodać w apce pod andka (tej cross platform) i tu mam problem bo jak dodam przez Connected Service - to metody jakie mam to HelloWorldAsync() oraz PobierDaneAsync(). Jak chcę wywołać HelloWorldAsync() w funkcji synchronicznej to wali błąd, że nie jest asynchronicznie. Zmienię na async to wali błędem, że funkcja mam mieć jakiś "request", no to piszę HelloWorldAsync("jakiś tekst") - błąd, funkcja nie przyjmuje argumentów.Szukam na google rozwiązania - jest, przepisuję mójWSClient klient = new mójWSClient(EndPointConfiguration.Soap12) i jak na filmiku wywołuję moje label1.Text = HelloWorldAsync(); i dalej błąd "request". Szukam dalej i jest coraz gorzej (dla mnie) - ludzie tworzą jakieś klasy, modele i inne rzeczy by wykonać funkcję z WS - kurczę to nie dla mnie, jestem za zielony w te klocki.

Spróbowałem zrobić tylko pod andka czyli Wybrałem opcję XAMARIN->Android w VS2017. Z WS łączę się przez Connected service (jak w tutku z YT) i jasna ciasna mam obie moje metody, które działają (żadne Async).Oczywiście mogę zrobić pod "czystego" andka ale interfejsu nie mogę zrobić kopiuj-wklej (to największy problem bo pod xaml'em zajęło mi miesiąc zrobienie tego interfejsu - a i tak jest to tylko podstawowy bez żadnych opcji (np.: do Zxing) itp.).

Pytanie jak pod Cross platform połączyć się z WS by to chulało?

1

Zmienię na async to wali błędem, że funkcja mam mieć jakiś "request", no to piszę HelloWorldAsync("jakiś tekst") - błąd, funkcja nie przyjmuje argumentów.Szukam na google rozwiązania - jest, przepisuję mójWSClient klient = new mójWSClient(EndPointConfiguration.Soap12) i jak na filmiku wywołuję moje label1.Text = HelloWorldAsync(); i dalej błąd "request".

Nic nie rozumiem. Pokaż jakieś fragmenty swojego kodu - zwłaszcza tam, gdzie ci mówi, że wywołujesz kod asynchroniczny z synchronicznego.

(ale tak nawiasem to SOAP-owe WebService są coraz rzadziej używane)

0

Witam.
Załącznik ws.png pokazuje gdzie dodaję połączenie do mojego WebService'u.

namespace MagLok_XF
{
    
    public partial class MainPage : ContentPage
    {

        public MainPage()
        {
            InitializeComponent();

            Test();
        }
        public async void Test()
        {
            
            StringBuilder sb = new StringBuilder();

            WSTest.MagKodSoapClient klient = new  WSTest.MagKodSoapClient(WSTest.MagKodSoapClient.EndpointConfiguration.MagKodSoap12);
            var hw = await klient.HelloWorldAsync();

            sb.AppendLine(hw.ToString());
            //lb_TowarNazwa.Text = sb.ToString();//stest.Length.ToString();
            await DisplayAlert("Info", sb.ToString(), "OK");
            
        }
    }
}

Kod powyżej to to fragment, w którym chcę przetestować połączenie z WS.
Mój WS ma dwie metody:

  1. HelloWorld() - zwraca string "Witaj swiecie"
  2. Pobierz_Dane_Towaru(string kod_kreskowy) - zwraca string[] - funkcja nie ma połączenia do bazy danych tylko na sztywno kilka rzeczy wpisanych Switch->case
    Przykładowo: Pobierz_Dane_Towaru("20592318") zwraca
    Friki o smaku owocowym
    20591318
    0989999
    2/13C

Jednak gdy kompiluję na komórkę to otrzymuję błąd jak w załączniku blad1.png (na komórce mam tylko biały ekran).
Starałem się robić wg tego filmiku https://www.youtube.com/watch?v=JAmZByNhR8s

1

Przepraszam, nie mogę zreplikować twojego błędu.

Biorę sobie gotowy WCF Soapowy - https://github.com/johanv/WcfSoap, tam jest metoda Hello, która przyjmuje stringa, zwraca stringa. Uruchamiam sobie lokalnie. Tworzę rozwiązanie Xamarin.Forms. W części wspólnej dodaje Service Reference wygenerowane automatycznie (Next, Next, Finish) z linka http://localhost:49294/HelloService.svc?singleWsdl.

Tworzę przycisk. Po jego kliknięciu odpalam kod:

ServiceReference1.HelloServiceClient hc = new ServiceReference1.HelloServiceClient();
var response = await hc.HelloAsync("Marcin");

Wszystko działa (UWP, akurat nie mam Androida pod ręką do testu na szybko).

Jedyna różnica jaką widzę - używam VS2019.

Jeśli możesz udostępnić jakieś kawałki swojego rozwiązania (może być prywatnie) to możemy spróbować coś zdebugować - ale wydaje mi się, że coś poszło nie tak z generowaniem referencji do usługi.

0

Przepraszam, że dopiero teraz odpisuję ale w środę był młyn w robocie i nie miałem już ochoty pracować z tekstem.

  1. To kod mojego WS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
namespace MagWS
{
    /// <summary>
    /// Opis podsumowujący dla MagKod
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // Aby zezwalać na wywoływanie tej usługi sieci Web ze skryptu za pomocą kodu ASP.NET AJAX, usuń znaczniki komentarza z następującego wiersza. 
    // [System.Web.Script.Services.ScriptService]
    public class MagKod : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Witaj świecie";
        }

        [WebMethod]
        public string[] Pobierz_Dane_Towaru(string kod_towaru)
        {
            string[] dane = new string[4];  //nazwa, kod_1, kod_2, lokalizacja

            /*dane testowe do odczytu*/
            switch(kod_towaru)
            {
                case "20592318":
                    dane[0] = "Friki o smaku owocowym";
                    dane[1] = "20591318";
                    dane[2] = "0989999";
                    dane[3] = "2/13C";
                    break;
                case "20592301":
                    dane[0] = "Friki o smaku arbuzowym";
                    dane[1] = "85873663";
                    dane[2] = "T1268";
                    dane[3] = "6/413";
                    break;
            }

            return dane;
        }
    }
}

Adres tego to http://90.90.90.102:4440/MagKod.asmx (adres mojego lapka w mojej sieci lokalnej - postawione na IIS na Win7).

  1. Kod z MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using ZXing;
using System.Data;
using ZXing.Net.Mobile.Forms;
using System.Windows.Input;
using System.Web;



namespace MagLok_XF
{
    
    public partial class MainPage : ContentPage
    {
        private bool kasuj_tb;
        // int i = 0;
        // string s_kod_odczytany = "";
        //  lcpi.data.oledb.OleDbConnection db_con;
        public DateTime _lastKeyStroke = new DateTime(0);
        public List<char> _barcode = new List<char>(10);
        public int ilosc_kodow_odczytanych = 0;


        public MainPage()
        {
            InitializeComponent();
            /*   lb_OdczytanyKod.Text = "";
               lb_TowarNazwa.Text = "";
               lb_TowarKod1.Text = "";
               lb_TowarKod2.Text = "";
               lb_TowarLokalizacja.Text = "";
               lb_OdczytanaLokalizacja.Text = "";
               lb_LokalizacjaZgodna.Text = "";
               //s_kod_odczytany = "123456";
               */

            Test();
        }

        public async void Scan_Barcode(object sender, EventArgs e)
        {
            var skaner = new ZXingScannerPage();

            await Navigation.PushAsync(skaner);
            skaner.OnScanResult += (result) =>
            {
                Device.BeginInvokeOnMainThread(async () =>
                {
                    await Navigation.PopAsync();
                    lb_OdczytanyKod.Text = result.Text;
                    
                });
            };

            //BarcodeScanView.IsVisible = true;

            //BarcodeScanView.IsScanning = true;
        }

        public void OnToggled(object sender, ToggledEventArgs e)
        {
            if (e.Value == true)
            {
                bt_Czynnosc.Text = "Skanuj kod towaru";
                bt_Czynnosc.IsEnabled = false;
                bt_Czynnosc.IsVisible = true;
                en_Kod.IsEnabled = true;
                en_Kod.IsVisible = true;
                en_Kod.Focus();
            }
            else
            {
                bt_Czynnosc.Text = "Skanuj kod towaru";
                bt_Czynnosc.IsEnabled = true;
                bt_Czynnosc.IsVisible = true;
                en_Kod.IsVisible = false;
                en_Kod.IsEnabled = false;
            }
        }

        private void En_Kod_TextChanged(object sender, TextChangedEventArgs e)
        {
            
            if (kasuj_tb == false)
            {
                kasuj_tb = true;
                int licznik = en_Kod.Text.Split('#').Length - 1;    //liczymy ilość wystąpień #
                //lb_TowarNazwa.Text = licznik.ToString();
                if (licznik == 2)
                {
                    string usunHasze = en_Kod.Text.Replace("#", "");  // usuwamy #
                    lb_OdczytanyKod.Text = usunHasze;
                    ilosc_kodow_odczytanych++;
                    if (ilosc_kodow_odczytanych == 0) bt_Czynnosc.Text = "SKANUJ KOD";
                    else bt_Czynnosc.Text = "SKANUJ LOKALIZACJĘ";
                    Przetwarzaj_Dane(usunHasze, ilosc_kodow_odczytanych);
                    lb_TowarKod2.Text = ilosc_kodow_odczytanych.ToString();
                    en_Kod.Text = "";
                }

                kasuj_tb = false;
            }
        }

        public void Przetwarzaj_Dane(string kod, int il_kod)
        {
            //pobieranie danych z bazy - bla bla bla



            lb_TowarNazwa.Text = "aaaaa";// test.Body.ToString();
            il_kod = 0;
        }

        public void _OnKeyPress(object sender, TextChangedEventArgs e)
        {
            TimeSpan elapsed = (DateTime.Now - _lastKeyStroke);
            if (elapsed.TotalMilliseconds > 100) _barcode.Clear();

            _barcode.Add(Convert.ToChar(e.NewTextValue));
            _lastKeyStroke = DateTime.Now;

            if (e.NewTextValue == "#" && _barcode.Count > 1)
            {
                string msg = new String(_barcode.ToArray());
                lb_OdczytanyKod.Text = msg.Replace("#", "");
                ilosc_kodow_odczytanych++;
                if (ilosc_kodow_odczytanych == 1) lb_OdczytanyKod.Text = "Skanuj lokalizację";
                else lb_OdczytanyKod.Text = "Skanuj kod towaru";
                //przetwarzaj dane
                _barcode.Clear();
            }
        }

        public async void Test()
        {
            
            StringBuilder sb = new StringBuilder();

            WSTest.MagKodSoapClient klient = new WSTest.MagKodSoapClient(WSTest.MagKodSoapClient.EndpointConfiguration.MagKodSoap12);
            
            var hw = klient.HelloWorldAsync();

            
                sb.AppendLine(hw.Result.Body.HelloWorldResult.ToString());
            //sb.AppendLine(hw.ToString());
            //lb_TowarNazwa.Text = sb.ToString();//stest.Length.ToString();
            await DisplayAlert("Info", sb.ToString(), "OK");
            
        }
    }
}

Funkcje co robią:
F1. Scan_Barcode - to funkcja do odczytu przez kamerkę komórki/tabletu (biblioteki ZXing)
F2. OnToggled - do switcha, przełącza mi z ZXinga na ręczny skaner kodów kreskowych
F3. En_Kod_TextChanged - zmiana tekstu w Entry, działa gdy switch jest na On - do odczytu z ręcznego czytnika kodów kreskowych
F4. Przetwarzaj_Dane - tu miała być cała obsługa WebService'u
F5. _OnKeyPress - tu miała być obsługa ręcznego czytnika kodów kreskowych ale android nie potrafi "czytać" z głównego ekranu *jak pod Win Forms)
F6. Test - tu testuję WS
Zawartością Test się nie przejmuj - to wynik kolejnych kombinacji próby rozwiązania problemu.
Załączniki "o1" - "o5" pokazują etapy dodawania WS do projektu, "kompilacja_na_komorke" pokazuje błąd jaki dostaję podczas kompilacji.

Na koniec "wisienka":
załączniki wisienka1 i wisienka2 to test mojego WS zrobiony jako Xamarin Native - po skompilowaniu działa bez pudła.

Pozdrawiam

PS. Edytowałem bo się kod rozjechał - część była jako zwykły tekst a część jako kod (enter przed namespace)

0

Okej, po kolei, bo jest ciekawie. Witamy we wspaniałym świecie Xamarina.

Różnica pomiędzy VS 2017 i VS 2019 tutaj ma znaczenie, chodzi o mechanizm generowania klienta dla usługi WCF/SOAP.

https://forums.xamarin.com/discussion/comment/336701/#Comment_336701
https://github.com/dotnet/wcf/issues/1808

Ale u mnie zadziałało, prawda?

Kiedy wygenerowałem sobie klienta do takiego WebService, jakiego mi tutaj podałeś, to dla UWP wszystko zadziałało. I to w nowoczesny sposób, z async/await. Ale tylko pod UWP - bo pod Androidem nie działało, ale z kompletnie innego powodu niż twój - ogólnie rzecz biorąc w momencie kiedy ja robię HelloWorldAsync to on szuka "HelloWorldAsync" w odpowiedzi SOAP - a tam jest przecież "HelloWorld". I nie znajduje i się wykrzacza z błędem.

No i niestety. Implementacja klienta WCF dla Mono ma buga, i ten błąd nie jest w tym momencie naprawiony - https://github.com/dotnet/wcf/issues/2463.

Można zrobić workaround - trzeba sobie ręcznie wygenerować klienta:

"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\SvcUtil.exe" /async http://192.168.8.149:8000/WebService1.asmx /out:Reference.cs

(u mnie akurat svcutil był w tym miejscu)

On wygeneruje tutaj metody asynchroniczne "w starym stylu" - bez async/await. Ale wygeneruje też metody synchroniczne, więc można by ich użyć:

var address = new System.ServiceModel.EndpointAddress("http://192.168.8.149:8000/WebService1.asmx");
WSTest.MagKodSoapClient klient = new WSTest.MagKodSoapClient(new BasicHttpBinding(), address);

var x = klient.Pobierz_Dane_Towaru("20592318");

I takie coś działa - ale synchronicznie, tj. blokuje wątek UI. Można (a nawet powinno) sobie to opakować w Taski, aby puszczać w tle, albo trzeba używać "starego" systemu, gdzie masz metody w stylu klient.BeginPobierz_Dane_Towaru() i callbacki.

Ale zaznaczę jeszcze raz - jeżeli cokolwiek teraz robisz i robisz to od nowa, to stawianie na SOAP (i ten stary WebService i ASMX i tak dalej) to nie ta droga. Mamy 2020 rok, ASP.NET Core WebAPI, JSON i jedziesz - tak samo wygodne, a na pewno działa ;)

0

Dzięki za zainteresowanie problemem.

Z tego co piszesz wnioskuję, że lepiej przejść na ASP.NET Core WebAPI (to jest ten REST?) niż banglać się z SOAP.

Znasz może jakiś tutorial dla tego ASP.NET Core WebAPI (taki dla idiotów) i co działa na VS2017 (jak trzeba to przeinstaluję na VS2019) i Win7?

Najśmieszniejsze jest to, że specjalnie wybrałem SOAP i asmx bo jest stary i sprawdzony, i na pewno będzie działać.
Najgorsze, że straciłem kupę czasu na coś co i tak trzeba przekombinować by działało.
W miesiąc zrobiłem (za pomocą tutków neta) interfejs (na relative prawie wszystko) programu na andka (to mój pierwszy program na Androida), dodałem obsługę ZXing'a (za pomocą tutka z neta - podstawowy odczyt kodu bez ustawiania parametrów), ba dodałem nawet możliwość odczytu ze skanera ręcznego (fakt, że przez Entry ale cóż - nie znalazłem innej (łatwej) możliwości, pomijam przechwytywanie USB).

W razie problemów z nowym sposobem będę dalej dręczyć pytaniami.

Pozdrawiam

0

Możesz przejrzeć https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio, jest dość ogólne, ale zrozumiesz o co chodzi. Działa na VS2017, tylko musisz doinstalować workload dla ASP.NET Core. Jak potrzebujesz przykładu dla takiego bardzo prostego zabawkowego API to możesz obejrzeć https://github.com/csplb/CatApi.

Jako klient - polecam RestSharp, jest bardzo przyjemny do pracy. Największą wadą jest to, że nie da się łatwo (bez zabawy dodatkowej) wygenerować automatycznie klienta, tak jak to było z "Connected Services" - musisz sobie samemu napisać trochę kodu, ale RestSharp sam zapewni ci wszelkie konwertowanie do i z JSON na twoje obiekty.

Jeżeli chcesz, to mogę ci wysłać materiały, jakie robiłem dla moich studentów (po angielsku) - gdzie właśnie przez RestSharpa budowaliśmy aplikację Xamarin.Forms będącą prostym klientem tego wspomnianego Cat API.

0

Dzięki za odzew. Przejrzę to co podałeś, jak sobie nie poradzę to poproszę Cię o przesłanie tych materiałów dla studentów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1