使用OData擴充ASP.NET Web API

by vivid 6. 十一月 2013 01:47

.NET Magazine國際中文電子雜誌
者:許薰尹
稿:張智凱
文章編號:N131114201
出刊日期:2013/11/6

Open Data Protocol (OData) 是一個Web協定,以HTTP為基礎,整合Atom Publishing Protocol (AtomPub,XML格式) 和JSON格式,用來查詢與更新資料,由其是適用於設計CRUD(Create、Read、Update與Delete)的動作。

ASP.NET Web API內建OData的支援,若你建立一個ASP.NET Web API範本的專案,預設就可以使用OData相關類別。你也可以在Visual Studio 2012開發工具中,透過NuGet套件管理員,下載及安裝「Microsoft.AspNet.WebApi.OData」套件。本篇文章將介紹如何在ASP.NET Web API專案設計OData服務,以及用戶端如何叫用OData服務。

設計支援OData的ASP.NET Web API服務

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

clip_image002

圖 1:ASP.NET MVC 4 Web Application。

建立一個ASP.NET MVC 4應用程式「Web API」範本專案,請參考下圖所示:

clip_image004

圖 2:建立ASP.NET WEB API專案。

新增實體資料模型

我們所設計的OData服務,其後端資料資料庫存取部分採用Entity Framework Code First方式來設計,先定義一個Employee Entity,從「Solution Explorer」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「Class」,使用「Employee」當作名稱,按「Add」按鈕,請參考下圖所示:

clip_image006

圖 3:新增Employee類別。

為Employee類別加入Id、EmployeeName與Age三個屬性:

namespace MyWebAPI.Models {
  public class Employee {
    public int Id { get; set; }
    public string EmployeeName { get; set; }
    public int Age { get; set; }
  }
}

按相同方式,從「Solution Explorer」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「Class」,使用「EmployeesDB」當作名稱,按「Add」按鈕,請參考下圖所示:

clip_image008

圖 4:新增EmployeesDB類別。

加入以下程式碼,讓EmployeesDB繼承自DbContext類別( 定義在System.Data.Entity命名空間下),類別中包含一個Employees屬性,存放Entity物件:

namespace MyWebAPI.Models {
  public class EmployeesDB : DbContext {
    public DbSet<Employee> Employees { get; set; }
  }
}

接著再新增一個EmployeesInitializer類別,繼承DropCreateDatabaseAlways<T>類別,改寫Seed方法,此方法初始化EmployeessDB資料庫的Employees資料表,新增三筆員工資料,建立三個Employee物件,並將Employee物件新增到DbContext之中,並叫用SaveChanges方法寫回後端資料庫:

public class EmployeesInitializer : DropCreateDatabaseAlways<EmployeesDB> {
  protected override void Seed( EmployeesDB context ) {
    base.Seed( context );
    var emps = new List<Employee>{
        new Employee {          
         EmployeeName = "Mary",
        Age =30
       },
         new Employee {          
         EmployeeName = "Candy",
        Age =25
       },
         new Employee {          
         EmployeeName = "Lili",
        Age =45
       }
      };

    emps.ForEach( s => context.Employees.Add( s ) );
    context.SaveChanges( );
  }
}

最後修改專案根路徑下的Web.Config檔案,預設有一個connectionStrings組態區段:

<connectionStrings>

<add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MyWebAPI-20130924172807;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MyWebAPI-20130924172807.mdf" />

</connectionStrings>

指定name為EmployeesDB,此名稱需和繼承自DbContext類別的EmployeesDB類別名稱一致,這樣Enity Framework就會自動識別,讀取組態檔案中的連接字串連接到資料庫:

<connectionStrings>

<add name="EmployeesDB" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=EmployeesDB;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\EmployeesDB.mdf" />

</connectionStrings>

初始化資料庫

接著在Global.asax檔案內Application_Start方法中,加入程式碼,叫用Database類別SetInitializer方法來初始資料庫結構和資料表資料:

protected void Application_Start( ) {
      AreaRegistration.RegisterAllAreas( );

     Database.SetInitializer<MyWebAPI.Models.EmployeesDB>( new MyWebAPI.Models.EmployeesInitializer( ) );

      WebApiConfig.Register( GlobalConfiguration.Configuration );
      FilterConfig.RegisterGlobalFilters( GlobalFilters.Filters );
      RouteConfig.RegisterRoutes( RouteTable.Routes );
      BundleConfig.RegisterBundles( BundleTable.Bundles );
    }


建立支援OData的控制器

接下來你需要設計一個支援OData的控制器,此控制器需要繼承ODataController或EntitySetController<TEntity, TKey>類別,控制器的名稱要對應到EntitySet。從Visual Studio 2012開發工具-「Solution Explorer」-專案-「Controllers」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」-「Controller」。設定控制器名稱為「EmployeesController」;在這個階段Template設定為「Empty API controller」,然後按下「Add 」按鈕,請參考下圖所示:

clip_image010

圖 5:新增控制器。

修改產生出的程式碼,讓EmployeesController類別繼承ODataController類別,然後在類別之中設計Queryable Action。Queryable Action就是一個動作方法(Action Method),回傳IQueryable<T>型別,比較需要注意的是,需要在方法上方套用[Queryable] Attribute,以開放用戶端進行查詢的動作。參考以下EmployeesController類別Get方法程式碼:

using MyWebAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.OData;

namespace MyWebAPI.Controllers {
  public class EmployeesController : ODataController {
    EmployeesDB ctx = new EmployeesDB( );
    [Queryable]
    public IQueryable<Employee> Get( ) {
      return ctx.Employees;
    }
  }
}


 

設定OData Endpoint

OData使用Service Metadata Document開放其資料模型的結構,Service Metadata Document是一份XML格式的文件,描述EDM中的概念資料模型(conceptual data model)。在ASP.NET Web API中只要建立一個實作IEdmModel介面的實體資料模型類別,就會產生Service Metadata Document。

修改專案中的WebApiConfig.cs檔案,在WebApiConfig類別中Register方法中叫新增一個InitializeOData方法,此方法利用ODataConventionModelBuilder類別自動建立CLR類別與EDM模型之間的對應,設定EntitySet<Employee>的名稱為Employees,然後叫用GetEdmModel( )方法來取得實體資料模型。

最後,還需要設定OData Endpoint,叫用GlobalConfiguration.Configuration.Routes.MapODataRoute方法指定路由名稱為「OData」;使用 routePrefix指定OData Endpoint的前置字串為「odata」,並傳入GetEdmModel( )方法取得的實體資料模型:

namespace MyWebAPI {
  public static class WebApiConfig {
    public static void Register( HttpConfiguration config ) {
      config.Routes.MapHttpRoute(
          name: "DefaultApi" ,
          routeTemplate: "api/{controller}/{id}" ,
          defaults: new { id = RouteParameter.Optional }
      );
      InitializeOData( );
      // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
      // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
      // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
      //config.EnableQuerySupport();

      // To disable tracing in your application, please comment out or remove the following line of code
      // For more information, refer to: http://www.asp.net/web-api
      config.EnableSystemDiagnosticsTracing( );
    }
    private static void InitializeOData( ) {
      ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder( );
      modelBuilder.EntitySet<Employee>( "Employees" );

      IEdmModel model = modelBuilder.GetEdmModel( );

      GlobalConfiguration.Configuration.Routes.MapODataRoute( routeName: "OData" ,
                                                             routePrefix: "odata" ,
                                                             model: model );
    }
  }
}

 

OData服務測試

OData提供許多OData Query String Options來查詢資料。OData Query String Options都是以「$」號開始,附加在URL後方,為查詢字串的一部分。常用的OData Query Options包含:

  • $orderby:排序。
  • $top:取前幾筆。
  • $skip:跳過前幾筆。

開啟瀏覽器,輸入OData服務所在Endpoint:「http://localhost:25723/OData」,可以看到以下描述服務的XML,請參考下圖所示:

clip_image012

圖 6:描述服務的XML。

我們也可以利用OData Query String Options「$metadata」來查詢Service Metadata Document,例如輸入以下URL:

http://localhost:25723/odata/$metadata

可取得服務所使用的實體物件模型的定義,請參考下圖所示:

clip_image014

圖 7:實體物件模型的定義。

修改瀏覽器URL,附加Employees字串到URL後方:「http://localhost:25723/OData/Employees」,從OData查詢資料庫Employees資料表資料(OData可以回傳XML或JSON格式資料,預設為JOSN格式) ,請參考下圖所示:

clip_image016

圖 8:OData服務查詢結果。

回傳的資料包含兩個部分,odata.metadata指明服務Metadata所在的URI為「http://localhost:25723/odata/$metadata#Employees」;value則是查詢回傳的Employee物件。

設計使用OData 的用戶端程式

OData以HTTP為基礎,任何HTTP的用戶端應用程式皆可以進行存取。我們可以利用Visual Studio 2012的「Add Service Reference」功能,在用戶端產生一個本機類別來呼叫ASP.NET Web API。這個本機類別的名稱為Container,繼承自DataServiceContext類別,開放OData服務的資料模型。

我們先建立一個主控台應用程式當用戶端。從Visual Studio 2012開發工具 -「File」-「Add」-「New Project」,在「New Project」對話盒中選取「Visual C#」程式語言,從「Windows」分類中,選取「Console Application」,名稱設定為「ODataClient」;按下「OK」鍵,請參考下圖所示:

clip_image018

圖 9:新增用戶端專案。

從「Solution Explorer」視窗,在「MyWebAPI」專案上方按右鍵,選取「Set As Startup Project」,然後按CTRL + F5執行MyWebAPI專案(即Host)。回到Visual Studio 2012,「Solution Explorer」視窗,在「ODataClient」專案上方按右鍵,從快捷選單選取「Add Service Reference」。在Address輸入「

http://localhost:25723/OData/」,然後按下「Go」按鈕,請參考下圖所示:

clip_image020

圖 10:Add Service Reference。

接著在ODataClient的專案下,就會自動產生一個Reference.cs檔案,其中包含Container類別的定義;以及一個service.edmx檔案,描述服務的實體資料模型,請參考下圖所示:

clip_image022

圖 11:Reference.cs檔案與service.edmx檔案。

Container類別包含許多屬性,用來開放OData服務中的資料,例如以本範例而言,Container類別會有一個System.Data.Services.Client.DataServiceQuery<Employee>型別的Employees屬性,利用此屬性便可以查詢資料庫Employees資料表資料。

接著我們就可以利用Container類別來叫用OData服務,在ODataClient的Program類別Main方法加入以下程式碼,以LINQ語法查詢出所有Employees資料表EmployeeName欄位的內容:

namespace ODataClient {
  class Program {
    static void Main( string [ ] args ) {
      Console.WriteLine( "OData Client is running ... " );
      var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );

      var emps = ( from emp in container.Employees
                   orderby emp.EmployeeName ascending
                   select emp
                     );

      foreach ( var emp in emps ) {
        Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
      }

      Console.ReadLine( );
    }
  }
}


 

測試時可以設定兩個專案同時執行,從「Solution Explorer」視窗-Solution項目上方按滑鼠右鍵,從快捷選單選取「Properties」,點選「Multiple Startup Projects」,設定兩個專案啟動順序,請參考下圖所示:

clip_image024

圖 12:設定兩個專案同時執行。

按CTRL + F5執行,主控台應用程式將印出以下資料庫資料,請參考下圖所示:

clip_image026

圖 13:用戶端執行結果。

繼承EntitySetController類別

另一個比較簡單設計支援OData控制器的方式是,讓你的控制器繼承EntitySetController<TEntity, TKey>類別,然後改寫它的方法。EntitySetController<TEntity, TKey>類別繼承自ODataController類別,而ODataController類別又繼承自ApiController,請參考下圖所示:

clip_image028

圖 14:EntitySetController<TEntity, TKey>類別繼承關係。

EntitySetController<TEntity, TKey>類別需要兩個泛型參數:第一個參數為Entity (Employee);第二個參數為 Key的型別。修改專案中的EmployeesController類別程式如下,改寫Get方法,透過LINQ查詢出Employees資料表資料,Get方法需要回傳IQueryable,這樣用戶端程式就可以定義查詢,從資料庫查詢符合條件的資料,而不需要將所有Employee資料放到Cache,再從Cache中查詢,因此範例中叫用AsQueryable方法轉型後將查詢結果回傳:

public class EmployeesController : EntitySetController<Employee , int> {
    EmployeesDB ctx = new EmployeesDB( );
    public override IQueryable<Employee> Get( ) {
      return ctx.Employees.AsQueryable( );
    }

    protected override Employee GetEntityByKey( int key ) {
      Employee emp = ctx.Employees.Find( key );
      return emp;
    }

 

GetEntityByKey方法則可以傳入Employee的ID(key),根據key查詢特定員工資料後回傳。若有需要進行資料的新增、刪除或修改動作,則可以改寫Post、Delete與Put方法,在此先不討論。

修改組態

修改WebApiConfig.cs檔案中的Register方法,移除config.EnableQuerySupport() 方法的註解,啟用查詢功能:

public static void Register( HttpConfiguration config ) {
      config.Routes.MapHttpRoute(
          name: "DefaultApi" ,
          routeTemplate: "api/{controller}/{id}" ,
          defaults: new { id = RouteParameter.Optional }
      );
      InitializeOData( );
      // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
      // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
      // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.

      config.EnableQuerySupport();

      // To disable tracing in your application, please comment out or remove the following line of code
      // For more information, refer to: http://www.asp.net/web-api
      config.EnableSystemDiagnosticsTracing( );
    }

 

按CTRL+F5執行程式,開啟瀏覽器,輸入以下URL,叫用第一個GET方法:

http://localhost:25723/OData/Employees,執行結果請參考下圖所示:

clip_image016[1]

圖 15:用戶端測試結果。

因為服務有叫用config.EnableQuerySupport() 方法啟用查詢功能,因此你可以在URL後方附加Query String Options,例如以下使用$top叫用第一個GET方法取回滿足條件資料的前兩筆回傳:

http://localhost:25723/OData/Employees?$top=2

得到的執行結果如下圖所示:clip_image030

圖 16:用戶端測試結果。

你也可以使用以下語法,叫用GetEntityByKey方法,查詢出Employee Id為「1」的員工資料:

http://localhost:25723/OData/Employees(1),執行結果請參考下圖所示:

clip_image032

圖 17:用戶端測試結果。

OData Query String Options

若用戶端想要得知LINQ查詢使用的OData Query String Options,可以在LINQ查詢中叫用ToString()方法,例如修改ODataClient的Main方法如下:

 

namespace ODataClient {
  class Program {
    static void Main( string [ ] args ) {
      Console.WriteLine( "OData Client is running ... " );
      var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
    
      var emps = ( from emp in container.Employees
                   orderby emp.EmployeeName ascending                  
                   select emp
                     );

      Console.WriteLine( emps.ToString());

      foreach ( var emp in emps ) {
        Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
      }

      Console.ReadLine( );
    }
  }
}

 

按CTRL + F5執行,主控台應用程式將印出以下資料庫資料以及OData Query String Options,利用「$orderby」Query Options設定排序欄位為EmployeeName請參考下圖所示:

clip_image034

圖 18:用戶端測試結果。

修改用戶端程式碼,叫用LINQ的Take方法,找詢滿足條件的前兩筆:

 

namespace ODataClient {
  class Program {
    static void Main( string [ ] args ) {
      Console.WriteLine( "OData Client is running ... " );
      var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
    
      var emps = ( from emp in container.Employees
                   orderby emp.EmployeeName ascending                  
                   select emp
                     ).Take(2);

      Console.WriteLine( emps.ToString());

      foreach ( var emp in emps ) {
        Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
      }

      Console.ReadLine( );
    }
  }
}


 

按CTRL + F5執行,主控台應用程式將印出以下資料庫資料以及OData Query String Options,利用「$top」Query Options印出前兩筆資料,請參考下圖所示:

clip_image036

圖 19:用戶端測試結果。

此外我們也可以註冊SendingRequest2事件,來取得發出請求使用的HTTP Method與URL,參考以下程式碼:

 

static void Main( string [ ] args ) {
  Console.WriteLine( "OData Client is running ... " );
  var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
  container.SendingRequest2 += ( s , e ) => {
    Console.WriteLine( " Method : {0} \n URL: {1}" ,
        e.RequestMessage.Method , e.RequestMessage.Url );
  };
  var emps = ( from emp in container.Employees
               orderby emp.EmployeeName ascending
               select emp
                 ).Take( 2 );
  foreach ( var emp in emps ) {
    Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
  }

  Console.ReadLine( );
}

 

按CTRL + F5執行,得到的執行結果如下圖所示:

clip_image038

圖 20:用戶端測試結果。

總結

NET Framework 4.5的ASP.NET Web API雖然提供一個基礎架構來協助建立HTTP服務,但或多或少有客製化的需求,例如錯誤處理、整合OData,或稽核服務的執行等行為。ASP.NET Web API支援OData Query String Options,只要將Action設計成Queryable Action就可以達到這個目地。

Tags:

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

評論 (19) -

alljobspo security
alljobspo security United States
2018/9/15 下午 04:36:18 #

Simply wanna comment on few general things, The website style and design is perfect, the subject matter is rattling excellent. "I delight in men over seventy. They always offer one the devotion of a lifetime." by Oscar Fingall O'Flahertie Wills Wilde.

回覆

Cleaning Alljobspo
Cleaning Alljobspo United States
2018/9/15 下午 08:05:31 #

My wife and i ended up being very lucky that John could complete his basic research through your precious recommendations he gained from your own web site. It is now and again perplexing just to possibly be handing out guidance that some people may have been making money from. And we also acknowledge we've got the blog owner to give thanks to because of that. The most important explanations you've made, the straightforward web site menu, the relationships you will help to create - it is everything spectacular, and it's really letting our son and the family understand the situation is awesome, which is certainly pretty pressing. Thanks for all!

回覆

Cleaning Alljobspo
Cleaning Alljobspo United States
2019/2/13 上午 11:31:47 #

As soon as I  discovered  this  site I went on reddit to share some of the love with them.

回覆

kenya alljobspo
kenya alljobspo United States
2019/2/26 下午 08:23:03 #

I've learn some good stuff here. Definitely worth bookmarking for revisiting. I wonder how a lot attempt you place to create one of these wonderful informative website.

回覆

security jobs
security jobs United States
2019/3/18 上午 06:29:20 #

I together with my guys were actually taking note of the excellent secrets on your website and then before long developed a horrible suspicion I never expressed respect to the site owner for them. All of the men appeared to be consequently happy to see all of them and have quite simply been taking advantage of them. Thanks for really being so helpful and for choosing some amazing areas millions of individuals are really desirous to understand about. Our own sincere regret for not saying thanks to you earlier.

回覆

nelajobs Lagos
nelajobs Lagos United States
2019/3/21 下午 09:05:52 #

Its like you read my mind! You seem to know so much about this, like you wrote the book in it or something. I think that you could do with some pics to drive the message home a bit, but other than that, this is excellent blog. An excellent read. I will definitely be back.

回覆

zambian jobs
zambian jobs United States
2019/4/25 下午 04:52:41 #

A lot of thanks for all your hard work on this site. My niece really likes getting into investigations and it's easy to understand why. Most people know all regarding the powerful mode you deliver vital solutions on the web blog and therefore cause contribution from website visitors about this area then my princess has always been starting to learn a lot of things. Take pleasure in the remaining portion of the year. You're conducting a very good job.

回覆

zambian jobs
zambian jobs United States
2019/4/27 上午 08:16:20 #

I do trust all of the concepts you've introduced in your post. They're very convincing and will definitely work. Nonetheless, the posts are very quick for newbies. May just you please extend them a little from subsequent time? Thanks for the post.

回覆

govt jobs
govt jobs United States
2019/5/6 上午 02:22:08 #

But a smiling  visitant here to share the love (:, btw  outstanding  layout.

回覆

Volunteer Jobs
Volunteer Jobs United States
2019/5/10 下午 10:03:44 #

Wow, wonderful blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your website is wonderful, let alone the content!

回覆

sales jobs
sales jobs United States
2019/5/14 上午 06:00:28 #

I gotta  favorite this website  it seems  very helpful   handy

回覆

alljobspo maker
alljobspo maker United States
2019/5/24 下午 07:59:59 #

hi!,I like your writing so so much! percentage we keep in touch more approximately your post on AOL? I require an expert in this area to resolve my problem. Maybe that's you! Having a look forward to look you.

回覆

driver jobs
driver jobs United States
2019/5/29 下午 06:12:53 #

You have brought up a very  great  points ,  appreciate it for the post.

回覆

workabroad
workabroad United States
2019/6/5 下午 06:43:57 #

Pretty section of content. I just stumbled upon your blog and in accession capital to assert that I acquire actually enjoyed account your blog posts. Anyway I’ll be subscribing to your augment and even I achievement you access consistently quickly.

回覆

jobs web
jobs web United States
2019/7/19 上午 06:18:28 #

I got what you mean , thanks  for posting .Woh I am  glad  to find this website through google. "The test and use of a man's education is that he finds pleasure in the exercise of his mind." by Carl Barzun.

回覆

driver jobs
driver jobs United States
2019/8/1 上午 07:19:59 #

I dugg some of you post as I  cerebrated they were  very beneficial   handy

回覆

Nelajobs borno
Nelajobs borno United States
2019/9/8 下午 11:26:44 #

I am just commenting to let you be aware of what a outstanding experience my cousin's daughter encountered studying your web page. She noticed a lot of issues, including what it is like to possess a wonderful teaching style to have others easily know precisely several grueling matters. You undoubtedly surpassed her expectations. Many thanks for imparting those helpful, trusted, informative as well as fun thoughts on your topic to Sandra.

回覆

Karima Lanting
Karima Lanting United States
2019/10/4 下午 05:06:07 #

Thanks, I have just been searching for information approximately this topic for a long time and yours is the greatest I have came upon till now. However, what about the conclusion? Are you positive in regards to the source?

回覆

Makeda Greenblatt
Makeda Greenblatt United States
2019/10/12 上午 07:40:43 #

I dugg some of you post as I  cogitated  they were  very beneficial   handy

回覆

新增評論




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






NET Magazine國際中文電子雜誌

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

月分類Month List