雜物儲存室 Coding Blog

在 Angular 專案中使用 Google & Facebook 登入

直接上 code

Google Login Button

google-login-button.component.html

<button id="googleBtn" type="submit">
  透過Google登入
</button>

google-login-button.component.ts

import { Component, OnInit, ElementRef, AfterViewInit } from '@angular/core';

declare var gapi: any;
@Component({
  selector: 'app-google-login-button',
  templateUrl: './google-login-button.component.html',
  styleUrls: ['./google-login-button.component.scss']
})
export class GoogleLoginButtonComponent implements OnInit, AfterViewInit {
  clientId = 'clientId';

  scope = [
    'profile',
    'email'
  ].join(' ');

  auth2: any;

  constructor(private element: ElementRef) { }

  ngOnInit(): void {
    let script = document.getElementById('google-platform') as HTMLScriptElement;

    if (!script) {
      script = document.createElement('script');
      script.id = 'google-platform';
      script.src = `https://apis.google.com/js/platform.js`;
      script.async = true;
      script.defer = true;
      document.body.appendChild(script);
    }
  }

  ngAfterViewInit() {
    this.load();
  }

  load() {
    if (typeof gapi === undefined || typeof gapi === 'undefined') {
      this.checkLoaded();
      return;
    }
    if (typeof gapi.load !== 'function') {
      this.checkLoaded();
      return;
    }
    gapi.load('auth2', () => {
      this.auth2 = gapi.auth2.init({
        client_id: this.clientId,
        cookiepolicy: 'single_host_origin',
        scope: this.scope
      });
      this.attachSignin(this.element.nativeElement.firstChild);
    });
  }

  checkLoaded() {
    setTimeout(() => {
      this.load();
    }, 100);
  }

  attachSignin(element: any) {
    this.auth2.attachClickHandler(element, {},
      (googleUser) => {
        const profile = googleUser.getBasicProfile();
        const auth = googleUser.getAuthResponse();
        const user = {
          userId: profile.getId(),
          name: profile.getName(),
          email: profile.getEmail(),
          expiredTime: new Date(auth.expires_at),
          accessToken: auth.access_token,
        };
        console.log(user);
      }, (error: any) => {
        console.log(JSON.stringify(error, undefined, 2));
      });
  }
}

facebook-login-button.component.html

<button (click)="submit();">
  透過Facebook登入
</button>

facebook-login-button.component.ts

import { Component, AfterViewInit } from '@angular/core';

declare var FB: any;
@Component({
  selector: 'app-facebook-login-button',
  templateUrl: './facebook-login-button.component.html',
  styleUrls: ['./facebook-login-button.component.scss']
})
export class FacebookLoginButtonComponent implements AfterViewInit {
  appId = 'appId';
  version = 'v6.0';
  scope = 'public_profile,email';

  constructor() { }

  ngAfterViewInit() {
    let script = document.getElementById('facebook-platform') as HTMLScriptElement;

    if (!script) {
      script = document.createElement('script');
      script.id = 'facebook-platform';
      script.src = `https://connect.facebook.net/en_US/sdk.js`;
      script.async = true;
      script.defer = true;
      document.body.appendChild(script);
    }
    (window as any).fbAsyncInit = () => {
      FB.init({
        appId: this.appId,
        cookie: true,
        xfbml: true,
        version: this.version
      });
    };
  }

  submit() {
    FB.login(login => {
      if (login.status !== 'connected' || !login.authResponse) {
        console.log('User login failed');
        return;
      }

      FB.api('/me', 'GET', { fields: 'id,name,email,first_name,last_name,link,age_range,picture,birthday' }, userInfo => {
        const user = {
          userId: userInfo.id,
          name: userInfo.name,
          email: userInfo.email,
          expiredTime: new Date(login.authResponse.data_access_expiration_time * 1000),
          accessToken: login.authResponse.accessToken,
        };
        console.log(user);
      });
    }, { scope: this.scope });
  }
}

如何更新 Angular CLI

使用 npm 更新,首先移除 Angular CLI

npm uninstall -g angular-cli

再重新安裝

npm install -g @angular/cli@latest

ASP.NET core GeoJson Model Binding

直接上 code

專案加入 GeoAPI

dotnet add package GeoAPI

add GeoJsonConverter class

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using GeoAPI.Geometries;

public class GeoJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IGeometry) || objectType.GetInterface("GeoAPI.Geometries.IGeometry") == typeof(IGeometry);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType != JsonToken.StartObject)
        {
            return null;
        }
        var jObj = JObject.Load(reader);
        return jObj.ToGeometry();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var geo = value as IGeometry;
        serializer.Serialize(writer, geo.ToJsonObject());
    }
}

in Startup.cs

public void ConfigureServices(IServiceCollection services)
{

    //...

    services.AddMvc(config =>
    {
        // ...
    }).AddJsonOptions(options =>
    {
        options.SerializerSettings.Converters.Add(new GeoJsonConverter()); // 加上GeoJsonConverter
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Update

For .NET Core 3.1

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using NetTopologySuite.Geometries;

public class GeoJsonConverter : JsonConverter<Geometry>
{
    public override Geometry Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        }
        if (!JsonDocument.TryParseValue(ref reader, out var doc))
        {
            throw new JsonException();
        }
        if (doc.RootElement.TryGetProperty("type", out var geoJsonType))
        {
            throw new JsonException();
        }
        var json = JsonSerializer.Serialize(doc);
        switch (geoJsonType.GetString())
        {
            case "Point":
                return ParseToGeometry<Point>(json);
            case "MultiPoint":
                return ParseToGeometry<MultiPoint>(json);
            case "LineString":
                return ParseToGeometry<LineString>(json);
            case "MultiLineString":
                return ParseToGeometry<MultiLineString>(json);
            case "Polygon":
                return ParseToGeometry<Polygon>(json);
            case "MultiPolygon":
                return ParseToGeometry<MultiPolygon>(json);
            case "GeometryCollection":
                return ParseToGeometry<GeometryCollection>(json);
        }
        throw new JsonException();
    }
    private static Geometry ParseToGeometry<T>(string json) where T : Geometry => new NetTopologySuite.IO.GeoJsonReader().Read<T>(json);
    public override void Write(Utf8JsonWriter writer, Geometry value, JsonSerializerOptions options)
    {
        var geoJsonWriter = new NetTopologySuite.IO.GeoJsonWriter();
        var json = geoJsonWriter.Write(value);
        var doc = JsonDocument.Parse(json);
        doc.WriteTo(writer);
    }
}

建立一個 .NET Core NuGet package

first in .csproj

<PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <Version>1.0.0</Version>
    <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <Company></Company>
    <Authors>David</Authors>
    <PackageProjectUrl>https://github.com/davidyujia/</PackageProjectUrl>
    <PackageIconUrl></PackageIconUrl>
    <PackageTags>generic repository</PackageTags>
    <Copyright></Copyright>
    <Description></Description>
    <PackageLicenseUrl></PackageLicenseUrl>
    <NeutralLanguage>en</NeutralLanguage>
    <RepositoryUrl>https://github.com/davidyujia/</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
</PropertyGroup>

then

dotnet pack -c Release

使用 ReactiveX 建立 Observer Pattern

直接上 code

Install

dotnet add package System.Reactive

using

using System.Reactive;
using System.Reactive.Subjects;

Observable

public class ObjectObservable : ObservableBase<object>
{
    private readonly Subject<object> _subject = new Subject<object>();

    public void Submit(object item)
    {
        _subject.OnNext(item);
    }

    protected override IDisposable SubscribeCore(IObserver<object> observer)
    {
        return _subject.Subscribe(observer);
    }
}

Observer

public class ObjectObserver : ObserverBase<object>, IDisposable
{
    private IDisposable _cancellation;

    public void Subscribe(IObservable<object> provider)
    {
        _cancellation = provider.Subscribe(this);
    }

    public void Dispose()
    {
        _cancellation.Dispose();
        _cancellation = null;
    }

    protected override void OnNextCore(object item)
    {
        //when call "OnNext"...
    }

    protected override void OnErrorCore(Exception error)
    {
        throw error;
    }

    protected override void OnCompletedCore()
    {
        //when call "OnCompleted" or "Dispose"...
    }
}

HOW TO

var observable = new ObjectObservable()

observable.Submit(new object());

var observer1 = new ObjectObserver();
var unsubscribe1 = observable.Subscribe(observer1);

observable.Submit(new object());

var observer2 = new ObjectObserver();
observable.Subscribe(observer2);

observable.Submit(new object());

//unsubscribe "observer1"
unsubscribe1.Dispose();