2021年3月18日 星期四

.NET Core 使用地址來查詢 GPS 經度緯度

.NET Core 使用地址來查詢 GPS 經度緯度

因為專案需求,需要使用地址來查詢出所在地點的經度與緯度數值,做為日後定位處理依據,經過上網搜尋,選用了 Generic C# Geocoding API 這裡的套件來幫忙設計這樣的需求。

這個套件支援了不同的地圖提供者的存取 API,在這裡將會使用 Google Map 這個提供者作為練習範例

建立測試用主控台應用程式專案

  • 開啟 Visual Studio 2019
  • 選擇右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗中
  • 從右上方的專案類型下拉按鈕中,找到並選擇 [主控台]
  • 從可用專案範本清單內,找到並選擇 [主控台應用程式]
  • 點選左下方 [下一步] 按鈕
  • 在 [設定新的專案] 對話窗中
  • 在 [專案名稱] 欄位中輸入 csAddressToGPS
  • 點選左下方 [下一步] 按鈕
  • 在 [其他資訊] 對話窗中
  • 在 [目標 Framework] 下拉選單中,選擇 [.NET 5.0 (目前)]
  • 點選左下方 [建立] 按鈕

加入所需要使用到的 NuGet 套件

  • 滑鼠右擊 [csAddressToGPS] 專案內的 [相依性] 節點
  • 從彈出功能表中,選擇 [管理 NuGet 套件]
  • 當 [NuGet: csAddressToGPS] 視窗出現後,切換到 [瀏覽] 標籤頁次
  • 搜尋 [Geocoding.Core] 並且安裝並且套件
  • 搜尋 [Geocoding.Google] 並且安裝並且套件

設計程式碼

請把 Main 方法改成底下的程式碼

記得要將 this-is-my-optional-google-api-key 這個文字,改成你申請到的 Google Map API Key

static async Task Main(string[] args)
{
    IGeocoder geocoder = new GoogleGeocoder() { ApiKey = "this-is-my-optional-google-api-key" };
    IEnumerable<Address> addresses = await geocoder.GeocodeAsync("高雄市鼓山區明倫路59號");
    Console.WriteLine("Formatted: " + addresses.First().FormattedAddress); 
    Console.WriteLine("Coordinates: " + addresses.First().Coordinates.Latitude + ", " + addresses.First().Coordinates.Longitude); 
}

執行結果

請按下 F5 開始執行這個專案,將會看到底下的執行結果

Formatted: No. 59, Minglun Road, Gushan District, Kaohsiung City, Taiwan 804 

Coordinates: 22.6676008, 120.2975978 




2021年3月15日 星期一

.NET 6 preview2 初體驗 - 執行 Maui 專案

.NET 6 preview2 初體驗 - 執行 Maui 專案

在 03月11日,微軟發表了 Announcing .NET 6 Preview 2 ,由於本身就已經從事於 Xamarin.Forms 多年教學與開發經驗,並且坐進也在使用 Blazor 進行各種專案開發,理所當然地對於 .NET 6 有著極高的興趣,所以,就在今天建立起一個 Windows 10 作業系統環境,一開始將 Hyper-V 相關服務開啟。

安裝 Visual Studio 2019 Preview

接著就到 Visual Studio Preview 網站下在 Visual Studio 2019 Preview IDE 到系統上。

安裝 .NET 6 SDK

最後,從 Download .NET 6.0 來下載 SDK 6.0.100-preview.2.21155.3 並且安裝到這台電腦上,下載下來的檔案名稱為 dotnet-sdk-6.0.100-preview.2.21155.3-win-x64.exe。

請執行這個安裝檔案,就會出現底下畫面

Microsoft.NET SDK 6.0.100-preview.2.21155.3

請點選 [安裝] 按鈕

安裝完成之後,請點選 [關閉] 按鈕

Microsoft.NET SDK 6.0.100-preview.2.21155.3

設定 Visual Studio 2019 可以使用 .NET 6 Preview

打開 Visual Studio 2019

點選 [工具] > [選項]

.NET 6 的 Maui 範例程式碼的安裝設定

根據 net6-mobile-samples 網頁說明,還需要額外安裝

Android:

Windows: Microsoft.NET.Workload.Android.11.0.200.148.msi

iOS:

Windows: Microsoft.NET.Workload.iOS.14.4.100-ci.main.1192.msi

執行 .NET 6 的 原生 Android 範例程式碼

這裡是現在 .NET6 Preview 2 的 Maui 範例 方案內容

執行 .NET6 Maui 範例程式碼

請下載這個專案 Repository net6-mobile-samples ,現階段似乎無法透過 Visual Studio 2019 來執行這些範例程式碼,因此,根據文件上的說明,打開命令提示字元視窗,切換到該專案的目錄下。

輸入這個命令 dotnet build HelloAndroid

編譯 .NET6 Maui for Android 範例程式碼

接著輸入 dotnet build HelloAndroid -t:Run

執行 .NET6 原生 Android 範例程式碼

執行 .NET 6 的 Maui for Android 範例程式碼

請下載這個專案 Repository net6-mobile-samples ,現階段似乎無法透過 Visual Studio 2019 來執行這些範例程式碼,因此,根據文件上的說明,打開命令提示字元視窗,切換到該專案的目錄下。

輸入這個命令 dotnet build HelloMaui --no-restore -t:Run -f net6.0-android

執行 .NET6 Maui 範例程式碼 




2021年2月16日 星期二

使用 APM 非同步設計模式加上 callback 的各個觸發時間

使用 APM 非同步設計模式加上 callback 的各個觸發時間

當在進行 APM 非同步程式設計的時候,首先會先呼叫 BeginXXX 方法,啟動非同步作業,接著,當使用 callback 方式的話,當非同步作業完成之後,就會呼叫這個 callback 委派方法;不過,此時,若想要設計出在主執行緒中,使用封鎖 Block 等待的方法,等候這個方同步作業與 callback 完成,若不使用其他執行緒同步功能,是否可以做到呢?

也就是說,在主執行緒下,呼叫 IAsyncResult.AsyncWaitHandle.WaitOne() 方法的時候,究竟會在執行 callback 的當時解除封鎖 block,還是於 callback 方法之後呢? 為了要了解此一問題,設計了底下的程式碼來一探究竟。

首先,這裡使用 WebRequest.Create 建立起一個 HttpWebRequest 物件,而該 URL 會花費 5 秒鐘的時間,才會執行完畢,在主執行緒內,接著呼叫 myHttpWebRequest1.BeginGetResponse(callBack, myHttpWebRequest1) 方法,啟動非同步作業,這裡有宣告一個 callBack 委派方法,會於非同步作業完成之後,呼叫這個方法。最後,使用了 asyncResult.AsyncWaitHandle.WaitOne() 方法來等待非同步作業的完成。

不論在主執行緒內與委派 callback 方法內,都會使用 Console.WriteLine 方法輸出一些檢查點訊息,用來幫助確認這個問題。

static void Main(string[] args)
{
    try
    {
        string url = $"https://hyperfullstack.azurewebsites.net/api/HandOnLab/Add/1/2/5";
 
        // 針對非同步請求,產生委派方法,用於處理非同步工作執行完成後的結果
        AsyncCallback callBack = new AsyncCallback(ResponseCallback);
 
        // 使用 WebRequest.Create 工廠方法建立一個 HttpWebrequest 物件
        HttpWebRequest myHttpWebRequest1 = (HttpWebRequest)WebRequest.Create(url);
 
        // 呼叫 BeginXXX 啟動非同步工作
        Console.WriteLine("Main 1");
        IAsyncResult asyncResult =
          (IAsyncResult)myHttpWebRequest1.BeginGetResponse(callBack, myHttpWebRequest1);
 
        Console.WriteLine("Main 2");
        asyncResult.AsyncWaitHandle.WaitOne();
        Console.WriteLine("Main 3");
 
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine($"   處理其他事情");
            Thread.Sleep(1000);
        }
 
        // 主執行緒的工作已經完成
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    catch (WebException e)
    {
        Console.WriteLine("\nException raised!");
        Console.WriteLine("\nMessage:{0}", e.Message);
        Console.WriteLine("\nStatus:{0}", e.Status);
        Console.WriteLine("Press any key to continue..........");
    }
    catch (Exception e)
    {
        Console.WriteLine("\nException raised!");
        Console.WriteLine("Source :{0} ", e.Source);
        Console.WriteLine("Message :{0} ", e.Message);
        Console.WriteLine("Press any key to continue..........");
        Console.Read();
    }
}

private static void ResponseCallback(IAsyncResult ar)
{
    HttpWebRequest request = ar.AsyncState as HttpWebRequest;
    Console.WriteLine("Callback 1");
    Thread.Sleep(3000);
    Console.WriteLine("Callback 2");
    HttpWebResponse webResponse = request.EndGetResponse(ar) as HttpWebResponse;//取得資料
    Console.WriteLine("Callback 3");
 
    Stream ReceiveStream = webResponse.GetResponseStream();
    StreamReader reader = new StreamReader(ReceiveStream);
    string result = reader.ReadToEnd();
 
    Console.WriteLine("Callback 4");
}

這裡是檢查結果,從這裡可以看到,一旦非同步作業完成之後,就會使用訊號通知方式,解除封鎖 Block 等待,也就是說,住執行緒可以繼續往下執行,而此時,callback 委派方法會在另外一個執行緒下,繼續來執行。

Main 1
Main 2
Main 3
   處理其他事情
Callback 1
   處理其他事情
   處理其他事情
Callback 2
Callback 3
Callback 4 

Press any key for continuing... 





2021年1月11日 星期一

透過 Xamarin.Forms 來讀取 NFC 裝置 Tag

透過 Xamarin.Forms 來讀取 NFC 裝置 Tag

在這篇文章將會逐步說明,如何使用 Plugin.NFC 套件,進行讀取 NFC 標籤 Tag 內碼資訊功能。

這篇專案的原始碼可以在 Github XFNFC 找到

建立專案

  • 開啟 Visual Studio 2019
  • 在 [Visual Studio 2019] 對話窗中,選擇 [建立新的專案]
  • 在 [建立新專案] 對話窗,選擇 [Prism Blank App (Xamarin.Forms)]
  • 點選 [下一步] 按鈕
  • 在 [設定新的專案] 對話窗, [專案名稱] 欄位輸入 XFNFC
  • 點選 [建立] 按鈕
  • 在 [RPISM PROJECT WIZARD] 對話窗內,勾選 [ANDROID] & [iOS]
  • 點選 [CREATE PROJECT] 按鈕

加入新套件

安裝 PropertyChanged.Fody

  • 在 [XFNFC] 專案內
  • 滑鼠右擊 [相依性節點],點選 [管理 NuGet 套件]
  • 當 [NuGet: XFNFC] 視窗出現後,點選 [瀏覽] 標籤頁次
  • 在文字輸入盒內輸入 PropertyChanged.Fody ,搜尋出這個套件
  • 點選 [PropertyChanged.Fody] 這個套件,安裝到 Xamarin.Forms 專案內

安裝 Plugin.NFC

  • 在 [XFNFC] 專案內
  • 滑鼠右擊 [相依性節點],點選 [管理 NuGet 套件]
  • 當 [NuGet: XFNFC] 視窗出現後,點選 [瀏覽] 標籤頁次
  • 在文字輸入盒內輸入 Plugin.NFC ,搜尋出這個套件
  • 點選 [Plugin.NFC] 這個套件,安裝到 Xamarin.Forms 專案內

更新所有的套件(選擇性)

  • 滑鼠右擊 [方案總管] 最上方的節點 [解決方案 xfMediaPicker]
  • 點選 [管理方案的 NuGet 套件]
  • 當 [NuGet: 解決方案] 視窗出現後,點選 [更新] 標籤頁次
  • 點選 [選取所有封裝] ,接著點選 [更新按鈕]
  • 將選取套件更新到最新版本

Android 原生專案的設定

  • 在 [XFNFC.Android] 專案內的 [Properties] 節點內
  • 打開 [AssemblyInfo.cs] 檔案
  • 加入底下的敘述在該檔案的最後面
[assembly: UsesPermission(Android.Manifest.Permission.Nfc)]
[assembly: UsesFeature("android.hardware.nfc", Required = false)]
  • 打開 [MainActivity.cs] 檔案
  • 找到 [OnCreate] 方法
  • 在 [base.OnCreate] 方法呼叫之後,加入 CrossNFC.Init(this); 敘述
  • 完成後的結果如下
protected override void OnCreate(Bundle savedInstanceState)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;
 
    base.OnCreate(savedInstanceState);
 
    // https://github.com/franckbour/Plugin.NFC
    // Plugin NFC: Initialization
    CrossNFC.Init(this);
 
    Xamarin.Essentials.Platform.Init(this, savedInstanceState);
    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    LoadApplication(new App(new AndroidInitializer()));
}
  • 在 [MainActivity] 類別內
  • 加入底下的方法宣告
protected override void OnNewIntent(Intent intent)
{
    base.OnNewIntent(intent);
    CrossNFC.OnNewIntent(intent);
}
 
protected override void OnResume()
{
    base.OnResume();
 
    // Plugin NFC: Restart NFC listening on resume (needed for Android 10+) 
    CrossNFC.OnResume();
}
  • 在 [MainActivity] 類別外
  • 加入底下的 Attribute 屬性宣告

請注意,這裡的 [DataMimeType] 宣告為 com.companyname.MyNFC ,若要套用此套件功能到開發中的專案,在此,請修改為該 Android 專案用的套件名稱

    [IntentFilter(new[] { NfcAdapter.ActionNdefDiscovered }, 
        Categories = new[] { Intent.CategoryDefault }, 
        DataMimeType = "application/com.companyname.MyNFC")]

iOS 原生專案設定

  • 在 iOS 專案 [XFNFC.iOS]
  • 使用滑鼠右擊 [Entitlements.plist] 檔案
  • 選擇 [開啟方式] 選項
  • 從 [開啟方式 - Entitlements.plist] 對話窗內
  • 選擇 [XML (文字) 編輯器]
  • 點選 [確定] 按鈕
  • 在 [Entitlements.plist] 視窗內的 <dict> 與 </dict> 標籤區域內
  • 加入底下宣告
	<key>com.apple.developer.nfc.readersession.formats</key>
	<array>
		<string>NDEF</string>
		<string>TAG</string>
	</array>

上面的操作可以使用底下的做法來取代,會得到同樣的效果

滑鼠雙擊 [Entitlements.plist] 檔案

在 [Entitlements.plist] 視窗內

找到與點選 [近距離無線通訊標籤讀取] 項目

勾選 [啟用近距離無線通訊標籤讀取] 項目

  • 在 iOS 專案 [XFNFC.iOS]
  • 滑鼠右擊 [Info.plist] 檔案,選擇 [開啟方式]
  • 在 [開啟方式 - Info.plist] 對話窗出現後,選擇 [XML (文字) 編輯器]
  • 將底下內容輸入到 </dict> 文字前面
<key>NFCReaderUsageDescription</key>
<string>NFC tag to read NDEF messages into the application</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<string>com.apple.developer.nfc.readersession.iso7816.select-identifiers</string>

讀取 NFC 的操作摘要

Read a tag

  • Start listening with CrossNFC.Current.StartListening().
  • When a NDEF message is received, the event OnMessageReceived is raised.

Write a tag

  • To write a tag, call CrossNFC.Current.StartPublishing()
  • Then CrossNFC.Current.PublishMessage(ITagInfo) when OnTagDiscovered event is raised.
  • Do not forget to call CrossNFC.Current.StopPublishing() once the tag has been written.

Clear a tag

  • To clear a tag, call CrossNFC.Current.StartPublishing(clearMessage: true)
  • Then CrossNFC.Current.PublishMessage(ITagInfo) when OnTagDiscovered event is raised.
  • Do not forget to call CrossNFC.Current.StopPublishing() once the tag has been cleared.

Xamarin.Forms 專案修正 首頁頁面

  • 在 Xamarin.Forms 專案 [XFNFC] 內
  • 打開 MainPage.xaml 檔案
  • 將這個檔案內容替換為底下內容
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFNFC.Views.MainPage"
             Title="NFC 標籤讀取}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="{Binding Tag}"
               FontSize="30" TextColor="Red"/>
        <Button Text="感應 NFC" Command="{Binding ReadNFCCommand}"/>
        <Button Text="停止感應 NFC" Command="{Binding StopReadNFCCommand}"/>
    </StackLayout>

</ContentPage>

修正首頁 ViewModel

  • 打開 MainPageViewModel.cs 檔案
  • 將這個檔案內容替換為底下內容
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XFNFC.ViewModels
{
    using System.ComponentModel;
    using System.Threading.Tasks;
    using Plugin.NFC;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Forms;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private readonly INavigationService navigationService;
        public string Tag { get; set; }
        public DelegateCommand ReadNFCCommand { get; set; }
        public DelegateCommand StopReadNFCCommand { get; set; }
        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;
            ReadNFCCommand = new DelegateCommand(async () =>
            {
                if (CrossNFC.Current.IsAvailable == false)
                {
                    Tag = "你的裝置沒有 NFC 感應功能";
                    return;
                }
                if (CrossNFC.Current.IsEnabled == false)
                {
                    Tag = "裝置上 NFC 感應功能沒有啟用";
                    return;
                }

                SubscribeEvents();
                StartListen();
            });
            StopReadNFCCommand = new DelegateCommand(() =>
            {
                Tag = "";
                UnsubscribeEvents();
                CrossNFC.Current.StopListening();
            });
        }
        void SubscribeEvents()
        {
            CrossNFC.Current.OnTagDiscovered += OnTagDiscovered;
            CrossNFC.Current.OnMessageReceived += OnMessageReceived;
            CrossNFC.Current.OnMessagePublished += OnMessagePublished;
        }
        void UnsubscribeEvents()
        {
            CrossNFC.Current.OnTagDiscovered -= OnTagDiscovered;
            CrossNFC.Current.OnMessageReceived -= OnMessageReceived;
            CrossNFC.Current.OnMessagePublished -= OnMessagePublished;
        }
        void StartListen()
        {
            try
            {
                CrossNFC.Current.StartListening();
            }
            catch (Exception ex)
            {
                UnsubscribeEvents();
                Console.WriteLine(ex.Message);
            }
        }
        private void OnMessagePublished(ITagInfo tagInfo)
        {
            Tag = tagInfo.SerialNumber;
        }

        private void OnMessageReceived(ITagInfo tagInfo)
        {
            Tag = tagInfo.SerialNumber;
        }

        private void OnTagDiscovered(ITagInfo tagInfo, bool format)
        {
            Tag = tagInfo.SerialNumber;
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
        }
    }
}