ASP.NET Identity簡介

by vivid 12. 三月 2014 04:19

.NET Magazine國際中文電子雜誌
者:許薰尹
稿:張智凱
文章編號:N140314601
出刊日期:2014/03/12
開發工具:Visual Studio 2013 Ultimate
版本:.NET Framework 4.5.1

NET Framework 4.5.x版與Visual Studio 2013開發工具問市之後,為ASP.NET平台帶來許多新的功能。其中安全性的部分大幅更改,當你利用Visual Studio 2013開發工具建立ASP.NET Web Site或Web Application範本專案之後,應該馬上就會發現,以往的ASP.NET 會員服務(ASP.NET Membership)、使用者設定檔(ProfileL等功能都不見了,取而代之的是一個全新的、使用ASP.NET Identity架構的安全性系統。本篇文章將介紹ASP.NET Identity的基本觀念,並探索與了解基本程式碼。

ASP.NET Identity主要是為了取代ASP.NET 2.0時所設計出的ASP.NET Membership服務,與一開始為WebMatrix開發工具所設計的Simple Membership服務用的,提供許多API程式,能夠快速讓你建立會員註冊網頁與使用者登入、登出等功能。ASP.NET Identity使用OWIN 驗證(OWIN Authentication)來設計使用者登入、登出功能,取代原ASP.NET Membership服務使用的FormsAuthentication 模組來產生驗證Cookie,而是改用OWIN CookieAuthentication來處理。

使用ASP.NET Identity的好處如下:

  • 它是以OWIN 為基礎,可以在所有ASP.NET Framework中使用,包含 ASP.NET MVC、 Web Forms、Web Pages、Web API,與SignalR等類型的應用程式,而且可以用在網站、手機…或混合式的系統。
  • 可由程式設計師完全掌控使用者設定檔(User Profile)資訊,以及資料庫的結構描述資訊(Schema),資料庫並不綁死於使用微軟的SQL Server。
  • 預設相關的會員資料是儲存在關聯式資料庫,使用Entity Framework Code First實作資料保存細節,只要自行實作ASP.NET Identity資料提供者(ASP.NET Identity providers )也很容易可以改用其它方式儲存。
  • 很容易整合使用其它社群網站帳號做登入,例如使用微軟帳號、Facebook、Twitter與Google…等等。

在這篇文章中,我們以ASP.NET Web Form範本網站專案,來了解一下ASP.NET Identity與OWIN Authentication設計的方式。

第一步先建立ASP.NET Web Forms專案,從Visual Studio 2013開發工具 -「File」-「New」-「Project」,在「New Project」對話盒中選取程式語言,例如本範例選擇「Visual C#」,從「Web」分類中,選取「ASP.NET Web Application」,然後按下「OK」鍵,為專案取一個名稱,再按下「OK」鍵,請參考下圖所示:

clip_image002[11]

圖 1:建立ASP.NET Web Application」。

選取「Web Forms」項目,一開始建立專案時可以選擇不同的驗證方式,只要按下畫面中的「Change Authentication」按鈕,就可以看到更多的驗證設定,請參考下圖所示:

clip_image004[6]

圖 2:驗證設定。

目前範本提供的驗證方式分為「No Authentication」、「Individual User Accounts」、「Organizational Accounts」與「Windows Authentication」這幾種,請參考下圖所示:

clip_image006[6]

圖 3:範本提供的驗證方式。

驗證方式分為以下幾種:

  • No Authentication:代表不驗證。
  • Individual User Accounts:使用ASP.NET Identity搭配OWIN驗證。預設範本專案內的程式碼使用SQL Server LocalDB來儲存會員資料,利用Entity Framework Code First來實做資料儲存細節。
  • Organizational Accounts :使用Windows Identity Foundation (WIF) 進行驗證,使用者帳號來自Windows Azure 目錄服務(Windows Azure Active Directory,WAAD,包含 Office 365)或 Windows Server目錄服務(Windows Server Active Directory)。
  • Windows Authentication:適用於企業內部網路(Intranet),使用IIS Windows Authentication 驗證。

目前只要使用預設的「Individual User Accounts」選項來建立專案即可。

ASP.NET Identity相關組件

範本專案預設引用許多ASP.NET Identity相關組件,包含:

· Microsoft.AspNet.Identity.EntityFramework:使用Entity Framework來實作ASP.NET Identity,將ASP.NET Identity 資料與結構描述資訊(Schema)儲存在SQL Server。

· Microsoft.AspNet.Identity.Core:ASP.NET Identity的核心介面,可用此介面將資料和結構描述資訊儲存在不同的儲存體,如Azure Table Storage、MySQL 資料庫。

· Microsoft.AspNet.Identity.Owin:用來讓ASP.NET Identity整合 OWIN 驗證,當你使用網站的登入功能,便使用OWIN Cookie Authentication模組來產生cookie。

探索網站

讓我們先來探索一下範本網站應用程式提供的功能,直接在Visual Studio 2013按下F5執行網站,預設網頁上方有一個「Register」超連結,請參考下圖所示:

clip_image008[6]

圖 4:範本網站。

點選「Register」超連結之後便轉向Register.aspx網頁,要求輸入使用者註冊資訊,包含使用者名稱與密碼,輸入適當資料之後,按下「Register」按鈕進行註冊,請參考下圖所示:

clip_image010[6]

圖 5:註冊使用者資訊。

註冊成功之後,會直接轉向Default.aspx網頁,並在網頁上方顯示使用者的名稱,請參考下圖所示:

clip_image012[6]

圖 6:Default.aspx網頁。

若按下Default.aspx網頁上方的「Log off」登出之後,可以再透過Login.aspx登入,請參考下圖所示:

clip_image014[6]

圖 7:透過Login.aspx登入。

我們來檢視一下資料庫,在「Solution Explorer」視窗,點選App_Data資料夾下的mdf檔案,然後按滑鼠右鍵,從快捷選單中選取「Open」項目,如下圖所示:

clip_image016[6]

圖 8:檢視資料庫。

Visual Studio會將資料庫開啟在「Server Explorer」視窗,其中包含一個AspNetUsers資料表,點選AspNetUsers資料表,然後按滑鼠右鍵,從快捷選單中選取「Show Table Data」項目,得到的執行結果如下圖所示,你可以看到資料表中包含一筆使用者資料:

clip_image018[6]

圖 9:AspNetUsers資料表。

了解ASP.NET Identity的分層架構

我們先了解一下ASP.NET Identity的分層架構,參考下圖,ASP.NET Identity是由Manager、Store、資料存取層、資料儲存體等組成

clip_image020[6]

圖 10:ASP.NET Identity的分層架構。

最底層是資料儲存體(Data Storage),預設使用LocalDB來儲存資料。當然ASP.NET Identity也可以改用其它資料儲存體來儲存會員資料,這部分暫不討論。

資料存取層(Data Access Layer)負責真正與資料庫存取的細節,例如包含連結到資料庫、新增、刪除、修改與查詢資料庫相關的使用者帳號資料之相關程式碼。Visual Studio建立的範本網站應用程式預設透過Entity Framework Code First技術來存取資料庫,當然此部分也可以加以擴充改寫,改用其它技術操作資料庫。

Store類別

預設範本專案使用存在於Microsoft.ASP.NET.Identity.EntityFramework組件之中的UserStore<TUser>類別,它是屬於較低階的類別,和資料存取層緊密地整合在一起,包含新增、刪除、修改、查詢使用者資料…等等的相關方法,以Entity Framework做為資料異動方式。下圖是UserStore<TUser>類別的部分定義程式碼,其中包含像是CreateAsync、DeleteAsync等方法:

clip_image022[6]

圖 11:Store類別。

Manager類別

Manager類別是比較高階的類別,讓程式設計師用來執行一些會員資料的操作,如建立使用者。Manager和Store 獨立不緊密相依,因此你可以很容易地換掉Store類別與底層的資料保存方式(資料存取層),而不影響整個系統,不必重新撰寫整個系統的所有程式碼。

Manager類別包含兩種:UserManager<TUser>與RoleManager<TRole>類別。UserManager<TUser>類別用來針對單一使用者的操作,項是建立、刪除使用者。RoleManager<TRole>類別則包含一些方法用來處理使用者群組(Role),例如建立Role(CreateAsync方法)、判斷Role是否存在(RoleExistsAsync方法)..等等。

下圖是UserManager<TUser>類別的部分定義,其中包含像是CreateAsync、FindByIdAsync等方法:

clip_image024[6]

圖 12:UserManager<TUser>類別。

對於程式設計師而言,只需要使用高階的UserManager<TUser>類別來執行新增、刪除、修改、查詢資料的程式碼;UserManager<TUser>類別可以和多種不一樣的Store類別溝通,你可以將Store代換掉,這樣就可以利用不同的Store將資料儲存到其它儲存體,例如MySQL或Windows Azure Storage,請參考下圖所示

clip_image026[6]

圖 13:UserManager<TUser>類別可以和多種不一樣的Store類別溝通。

Microsoft.AspNet.Identity.EntityFramework 套件

預設ASP.NET Identity使用Entity Framework Code First技術來保存資料到儲存體,相關的類別放在Microsoft.AspNet.Identity.EntityFramework套件之中,若在Visual Studio 開發工具內建立空白網站或專案,可以使用「NuGet Package Manager」手動下載,請參考下圖所示:

clip_image028[6]

圖 14:使用Microsoft.AspNet.Identity.EntityFramework 套件。

ApplicationUser類別

接下來讓我們來了解一下,範本網站應用程式中定義的幾個類別。在範本專案Models資料夾下,有一個IdentityModels.cs檔案,其中包含一個ApplicationUser類別,其定義如下:

 

public class ApplicationUser : IdentityUser

{

}

 

 

一個ApplicationUser類別的實體就代表一個使用者的資料。IdentityUser類別是存在於Microsoft.AspNet.Identity.EntityFramework命名空間之下,實作了Microsoft.AspNet.Identity空間下的IUser介面。IUser介面很簡單,定義使用者類別(IdentityUser)必需實作的Id與UserName屬性:

public interface IUser {
  string Id { get; }
  string UserName { get; set; }
}

使用者類別(IdentityUser)的定義如下,包含一些基本的屬性,像是UserName:

public class IdentityUser : IUser {
    public IdentityUser( ) {
      this.Id = Guid.NewGuid( ).ToString( );
      this.Claims = new List<IdentityUserClaim>( );
      this.Roles = new List<IdentityUserRole>( );
      this.Logins = new List<IdentityUserLogin>( );
    }

    public IdentityUser( string userName )
      : this( ) {
      this.UserName = userName;
    }

    public ICollection<IdentityUserClaim> Claims { virtual get; private set; }

    public virtual string Id { get; set; }

    public ICollection<IdentityUserLogin> Logins { virtual get; private set; }

    public virtual string PasswordHash { get; set; }

    public ICollection<IdentityUserRole> Roles { virtual get; private set; }

    public virtual string SecurityStamp { get; set; }

    public virtual string UserName { get; set; }
  }

 

未來若要額外為使用者記錄一些額外的資料,可以在ApplicationUser類別中定義多個屬性。

UserManager類別

IdentityModels.cs檔案中還包含UserManager類別的定義,它是比較高階的類別,讓應用程式設計師用來執行一些操作,如建立使用者,UserManager類別的定義如下:

public class UserManager : UserManager<ApplicationUser>
{
    public UserManager()
        : base(new UserStore<ApplicationUser>(new ApplicationDbContext()))
    {
    }
}

UserManager類別繼承Microsoft.AspNet.Identity 命名空間下的UserManager<TUser>類別,叫用父類別的建構函式,傳入UserStore<TUser>物件。UserStore<TUser>類別是比較低階的類別,指明Entity(如users、roles)該如何儲存。

ApplicationDbContext類別

IdentityModels.cs檔案中還包含一個ApplicationDbContext類別的定義。此類別繼承Microsoft.AspNet.Identity.EntityFramework命名空間下的IdentityDbContext<TUser>類別,它是一個DbContext類型的物件。ApplicationDbContext負責讀取組態檔案中的連接字串,利用Entity Framework,建立和資料庫的實體連線:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
}

範本應用程式組態檔案中(Web.config)的連線字串設定如下,預設使用LocalDB來儲存資料:

<connectionStrings>

<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-IdentityDemo-20140225042005.mdf;Initial Catalog=aspnet-IdentityDemo-20140225042005;Integrated Security=True"

providerName="System.Data.SqlClient" />

</connectionStrings>

會員註冊程式

我們以範本網站應用程式的註冊會員程式看起,在網站應用程式的Account資料夾下包含一個Register.aspx檔案,用來註冊會員,當使用者按下網頁的註冊按鈕,便利用以下程式碼,將使用者輸入的資料加入資料庫:

public partial class Register : Page {
    protected void CreateUser_Click( object sender , EventArgs e ) {
      var manager = new UserManager( );
      var user = new ApplicationUser( ) { UserName = UserName.Text };
      IdentityResult result = manager.Create( user , Password.Text );
      if ( result.Succeeded ) {
        IdentityHelper.SignIn( manager , user , isPersistent: false );
        IdentityHelper.RedirectToReturnUrl( Request.QueryString[ "ReturnUrl" ] , Response );
      } else {
        ErrorMessage.Text = result.Errors.FirstOrDefault( );
      }
    }
  }

 

範例中建立UserManager類別,透過Create方法來建立使用者帳號資料。ApplicationUser預設會有Id與UserName兩個屬性。檢視IdentityResult的Succeeded屬性,若帳號建立成功,則叫用IdentityHelper.SignIn方法登入。

IdentityHelper類別同樣是定義在IdentityModels.cs檔案之中,程式碼如下:

public static void SignIn(UserManager manager, ApplicationUser user, bool isPersistent)
{
    IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
    authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    var identity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
    authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

 

利用HttpContext.Current.GetOwinContext().Authentication取得實作IAuthenticationManager的物件,先叫用SignOut方法,清除Cookie,然後重新叫用SignIn方法,以目前註冊的新帳號,透過OWIN驗證進行登入。

IAuthenticationManager介面則是定義在Microsoft.Owin.Security命名空間之下。SignIn方法可以傳入AuthenticationProperties物件,設定驗證相關資訊,例如範例中的IsPersistent屬性用來決定是否記住驗證Cookie。

OWIN驗證

OWIN (Open Web Interface for .NET)是一個Web 伺服器與Framework元件之間的抽象層,更容易開發、使用新元件;應用程式容易移轉到不同裝載程式,或移轉到不同平台或作業系統。預設範本專案使用OWIN驗證,將OWIN驗證裝載在IIS上執行,因此Web.Config檔案之中authentication的mode是設定為None,而非以往大家熟知的Forms驗證:

<system.web>

<authentication mode="None" />

</system.web>

每一個使用OWIN的應用程式或網站都需要一個啟動類別(Startup),若將OWIN裝載在AS.PENT Web Site或Web 應用程式之中,需要在專案安裝Microsoft.Owin.Host.SystemWeb套件,若從一個空白專案開始寫起,可以利用NuGet Package Manger安裝。

你可以檢視網站應用程式根目錄下的Startup.cs檔案,其中包含裝載OWIN的相關程式碼,其中的OwinStartupAttribute指明要使用組件中的哪一個類別當做起動類別,Startup類別的Configuration方法叫用ConfigureAuth方法設定驗證,參考以下程式碼:

using Microsoft.Owin;
using Owin;

[assembly: OwinStartupAttribute( typeof( IdentityDemo.Startup ) )]
namespace IdentityDemo {
  public partial class Startup {
    public void Configuration( IAppBuilder app ) {
      ConfigureAuth( app );
    }
  }
}


Startup類別是一個部分類別(Partial Class),另一部分的定義是放在App_Start資料夾下的Startup.Auth.cs檔案之中,其中的程式碼如下,在ConfigureAuth方法之中,預設叫用UseCookieAuthentication 方法,新增OWIN的CookieAuthentication到ASP.NET pipeline之中,並利用CookieAuthenticationOptions物件指明登入的網頁為/Account/Login.aspx檔案(範本程式有啟用友善URL功能,因此不必加上附檔名ASPX)。最後利用UseExternalSignInCookie方法設定網站要使用以Cookie為基礎的OWIN中間層(middleware)進行驗證:

public partial class Startup {

    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301883
    public void ConfigureAuth( IAppBuilder app ) {
      // Enable the application to use a cookie to store information for the signed in user
      // and also store information about a user logging in with a third party login provider.
      // This is required if your application allows users to login
      app.UseCookieAuthentication( new CookieAuthenticationOptions {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie ,
        LoginPath = new PathString( "/Account/Login" )
      } );
      app.UseExternalSignInCookie( DefaultAuthenticationTypes.ExternalCookie );

      // Uncomment the following lines to enable logging in with third party login providers
      //app.UseMicrosoftAccountAuthentication(
      //    clientId: "",
      //    clientSecret: "");

      //app.UseTwitterAuthentication(
      //   consumerKey: "",
      //   consumerSecret: "");

      //app.UseFacebookAuthentication(
      //   appId: "",
      //   appSecret: "");

      //app.UseGoogleAuthentication();
    }
  }

 

從上述註解的程式中不難猜出,未來若要使用其它社群網站的帳號來進行驗證,只要在此類別中叫用適當的方法就可以達成,例如UseMicrosoftAccountAuthentication方法就是使用微軟的帳號來進行驗證;UseFacebookAuthentication則是使用Facebook的帳號來驗證。

存取使用者資訊

範本網站在登入成功之後,會顯示使用者的名稱,它是使用一個LoginView控制項來切換要顯示登入、登出的文字,並利用Microsoft.AspNet.Identity命名空間下IdentityExtensions類別的GetUserName()方法來讀取會員資料。參考範本網站~/Site.Master檔案中的程式碼:

 

<asp:LoginView runat="server" ViewStateMode="Disabled">
    <AnonymousTemplate>
        <ul class="nav navbar-nav navbar-right">
            <li><a runat="server" href="~/Account/Register">Register</a></li>
            <li><a runat="server" href="~/Account/Login">Log in</a></li>
        </ul>
    </AnonymousTemplate>
    <LoggedInTemplate>
        <ul class="nav navbar-nav navbar-right">
            <li><a runat="server" href="~/Account/Manage" title="Manage your account">Hello, <%: Context.User.Identity.GetUserName()  %> !</a></li>
            <li>
                <asp:LoginStatus runat="server" LogoutAction="Redirect" LogoutText="Log off" LogoutPageUrl="~/" OnLoggingOut="Unnamed_LoggingOut" />
            </li>
        </ul>
    </LoggedInTemplate>
</asp:LoginView>

 

Site.Master.cs中包含登出程式碼,透過IAuthenticationManager的SignOut( );方法登出:

protected void Unnamed_LoggingOut( object sender , LoginCancelEventArgs e ) {

  Context.GetOwinContext( ).Authentication.SignOut( );

}

自訂屬性

若想要額外儲存使用者的其它資料,則可以修改ApplicationUser類別的程式碼,例如修改ApplicationUser類別,自訂一個Country屬性:

public class ApplicationUser : IdentityUser {

  public string Country { get; set; }

}

此時若重新執行網站,試圖進行會員註冊動作,可能會遇到一個錯誤訊息:

The model backing the 'ApplicationDbContext' context has changed since the database was created. Consider using Code First Migrations to update the database

這是因為Entity Framework Code First偵測到資料庫結構異動產生的錯誤訊息,請參考下圖所示:

clip_image030[6]

圖 15:異動資料庫結構產生的錯誤訊息。

我們先利用「Package Manager Console」啟用遷移功能。從Visual Studio 「TOOLS」-「Library Package Manager」,選取「Package Manager Console」,然後輸入以下指令:

Enable-Migrations

請參考下圖所示:

clip_image032[6]

圖 16:啟用遷移功能。

接著專案之中便會產生一個Migrations資料夾,其中有一個Configuration.cs檔案。修改Configuration 建構函式中的第一行程式碼,將AutomaticMigrationsEnabled 設定為「true」:

internal sealed class Configuration : DbMigrationsConfiguration<IdentityDemo.Models.ApplicationDbContext> {
    public Configuration( ) {
      AutomaticMigrationsEnabled = true;
      ContextKey = "IdentityDemo.Models.ApplicationDbContext";
    }

    protected override void Seed( IdentityDemo.Models.ApplicationDbContext context ) {
      //  This method will be called after migrating to the latest version.

      //  You can use the DbSet<T>.AddOrUpdate() helper extension method
      //  to avoid creating duplicate seed data. E.g.
      //
      //    context.People.AddOrUpdate(
      //      p => p.FullName,
      //      new Person { FullName = "Andrew Peters" },
      //      new Person { FullName = "Brice Lambson" },
      //      new Person { FullName = "Rowan Miller" }
      //    );
      //
    }
  }


 

接下來在「Package Manager Console」輸入以下指令,更新資料庫:

Update-Database

請參考下圖所示:

clip_image034[6]

圖 17:更新資料庫。

下一步我們希望在註冊會員資料時,可以額外輸入Country 資訊,修改Register.aspx網頁,在註冊按鈕上方加入一個文字方塊輸入Country:

 

<div class="form-group">
    <asp:Label runat="server" AssociatedControlID="txtCountry" CssClass="col-md-2 control-label">Country</asp:Label>
    <div class="col-md-10">
        <asp:TextBox runat="server" ID="txtCountry" TextMode="SingleLine" CssClass="form-control" />
        <asp:RequiredFieldValidator runat="server" ControlToValidate="txtCountry"
            CssClass="text-danger" ErrorMessage="The Country field is required." />
    </div>
</div>

 

 

修改Register.aspx.cs檔案中的程式碼,建立ApplicationUser類別時,順帶初始化Country:

public partial class Register : Page {
    protected void CreateUser_Click( object sender , EventArgs e ) {
      var manager = new UserManager( );
      var user = new ApplicationUser( ) { UserName = UserName.Text , Country = txtCountry.Text };
      IdentityResult result = manager.Create( user , Password.Text );

      if ( result.Succeeded ) {
        IdentityHelper.SignIn( manager , user , isPersistent: false );
        IdentityHelper.RedirectToReturnUrl( Request.QueryString[ "ReturnUrl" ] , Response );
      } else {
        ErrorMessage.Text = result.Errors.FirstOrDefault( );
      }
    }
  }

按F5執行網站測試,先註冊一個會員資料,請參考下圖所示:

clip_image036[6]

圖 18:註冊一個會員資料。

登入成功,請參考下圖所示:

clip_image038[6]

圖 19:Default.aspx網頁。

檢視資料庫內容,在「Server Explorer」視窗,點選AspNetUsers資料表,然後按滑鼠右鍵,從快捷選單中選取「Show Table Data」項目:

clip_image040[6]

圖 20:檢視資料庫。

得到的執行結果如下圖所示,除了新註冊的會員資料之外,其它的兩筆資料是在未增加Country欄位之前新增的:

clip_image042[6]

圖 21:新增資料。

未來若要在程式中存取使用者的Country資料,可以使用程式碼:

var manager = new UserManager();

ApplicationUser user = manager.Find(UserName.Text, Password.Text);

Response.Write( user.Country );

參考資料

  • Introduction to ASP.NET Identity

l http://www.asp.net/identity/overview/getting-started/introduction-to-aspnet-identity

  • Implementing a Custom MySQL ASP.NET Identity Storage Provider

l http://www.asp.net/identity/overview/extensibility/implementing-a-custom-mysql-aspnet-identity-storage-provider

  • ·OWIN Startup Class Detection

l http://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET | 許薰尹Vivid Hsu

評論 (33) -

cours de theatre
cours de theatre United States
2017/9/30 上午 11:22:43 #

Thanks for sharing, this is a fantastic article post.Really looking forward to read more. Really Cool.

回覆

nghi duong vung tau
nghi duong vung tau United States
2017/10/7 上午 12:40:46 #

Great, thanks for sharing this blog article.Really looking forward to read more. Really Cool.

回覆

can ho bien vung tau
can ho bien vung tau United States
2017/10/9 下午 06:29:19 #

Very good article. Really Cool.

回覆

Nathan Coombe
Nathan Coombe United States
2017/10/10 下午 10:25:11 #

Very neat blog article.Much thanks again. Great.

回覆

buy hacklinks
buy hacklinks United States
2017/10/12 下午 09:08:08 #

Muchos Gracias for your post.

回覆

important source
important source United States
2017/10/14 下午 04:24:05 #

Thanks so much for the article post.Much thanks again. Keep writing.

回覆

dragon city hacks
dragon city hacks United States
2017/10/15 下午 03:53:18 #

Major thankies for the blog article.Really thank you! Want more.

回覆

like it
like it United States
2017/10/17 下午 03:11:13 #

Great blog article.Really thank you! Really Cool.

回覆

sletrokor
sletrokor United States
2017/10/17 下午 08:43:08 #

I really liked your blog article.Much thanks again. Keep writing.

回覆

sex pills
sex pills United States
2017/10/19 上午 07:47:30 #

Really appreciate you sharing this blog post.Much thanks again. Fantastic.

回覆

click here for more
click here for more United States
2017/10/19 下午 06:44:20 #

Great article.Thanks Again.

回覆

vung tau melody
vung tau melody United States
2017/10/21 上午 04:00:14 #

Thanks so much for the blog article.Really thank you! Great.

回覆

prix carte grise
prix carte grise United States
2017/10/21 上午 07:38:30 #

Really enjoyed this blog.Really looking forward to read more. Will read on...

回覆

can ho osimi
can ho osimi United States
2017/10/28 下午 12:05:42 #

I appreciate you sharing this article.Really thank you! Awesome.

回覆

EZ Battery Reconditioning Scam
EZ Battery Reconditioning Scam United States
2017/10/30 上午 11:00:57 #

I appreciate you sharing this blog article.Really thank you! Want more.

回覆

informacje plock
informacje plock United States
2017/10/30 下午 06:46:41 #

Fantastic article. Cool.

回覆

scam
scam United States
2017/11/1 上午 11:14:45 #

Looking forward to reading more. Great article.Really looking forward to read more. Keep writing.

回覆

phenocal
phenocal United States
2017/11/1 下午 06:43:49 #

Thanks for sharing, this is a fantastic article.Really thank you! Will read on...

回覆

phentaslim review
phentaslim review United States
2017/11/3 下午 03:10:09 #

Great post.Really thank you! Want more.

回覆

sciatica pain relief for leg
sciatica pain relief for leg United States
2017/11/15 上午 09:04:56 #

I really enjoy the blog.Really looking forward to read more. Will read on...

回覆

avocat criminel
avocat criminel United States
2017/11/16 下午 07:39:08 #

Thanks a lot for the blog.Really looking forward to read more. Fantastic.

回覆

fashion
fashion United States
2017/11/23 下午 11:10:31 #

Thanks-a-mundo for the blog post.Really looking forward to read more. Fantastic.

回覆

Search Engine Optimisation Nz
Search Engine Optimisation Nz United States
2017/11/25 下午 08:06:04 #

Thank you for your blog post.

回覆

Chad Boonswang and Jeffrey Goodman
Chad Boonswang and Jeffrey Goodman United States
2017/11/26 下午 07:16:07 #

Thanks-a-mundo for the article. Really Great.

回覆

Chad Boonswang SEO
Chad Boonswang SEO United States
2017/11/27 上午 01:24:04 #

Appreciate you sharing, great article.Really looking forward to read more. Awesome.

回覆

truck wreckers
truck wreckers United States
2017/11/29 下午 05:47:02 #

Im obliged for the article post.Thanks Again. Will read on...

回覆

can ho go vap
can ho go vap United States
2017/11/30 上午 12:25:00 #

Thank you ever so for you post.Much thanks again. Awesome.

回覆

small business loans
small business loans United States
2017/12/1 上午 02:11:34 #

I think this is a real great post.Much thanks again. Much obliged.

回覆

business trade lines
business trade lines United States
2017/12/3 上午 06:07:59 #

Thanks for the blog article.Really looking forward to read more. Keep writing.

回覆

free porn apps
free porn apps United States
2017/12/5 上午 10:56:51 #

Say, you got a nice article post.Really thank you! Will read on...

回覆

Say, you got a nice article post.Really looking forward to read more. Fantastic.

回覆

site legalize
site legalize United States
2017/12/10 下午 08:31:23 #

I think this is a real great blog.Much thanks again. Keep writing.

回覆

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>