顯示具有 Salesforce 標籤的文章。 顯示所有文章
顯示具有 Salesforce 標籤的文章。 顯示所有文章

2018-04-10

Salesforce: Record Type and View Model

先前 [1] 用物件導向的觀念分析了一下 Salesforce 中 object, record type, and master-detail object relationships 的特性;就 Salesforce 系統設計而言,是否能表達物件導向並不重要,讓使用者易於設定、儘快進入使用,才是這類 domain-specific service 的重點。

如果用三層式架構中 domain model, data model, and view model 的角度來看,Salesforce 在 data model 原則上採用類似 active record pattern 的作法,設定畫面中的 object 直接對應到 Apex programming 中的 object type 與 SOQL 中的 table,以 1-to-1 的方式呈現。

這時候 record type 很明顯就是為了 view model 而存在,只是多數 Web application development 中,有很多好用的 model mapping libraries 可供使用,view model 與 domain model 之間的關係可以很彈性地為 many-to-many。回到 Saleforce,如果要讓使用者在不需任何程式碼,也能保有 view model 的便利性:
同一個 domain model 可搭配不同的畫面,顯示不同的屬性組合。
這時候就可以透過 record type 的設定,讓 view model 與 domain model 之間達到 many-to-one 之間的關係。

see also:
[1] Salesforce: Objects Generalization and Specialization

2017-12-10

Salesforce SOSL, SOQL, and Tokenization

Salesforce 在登入後的畫面頂端提供了一個搜尋框,提供使用者搜尋各式資訊,例如:Account, Contact, Opportunity, Contract 等。

但由於預設的搜尋行為使用其內部的斷詞系統,因此針對一些專有名詞搜尋時,可能就不那麼盡如人意;例如:當資料為東京都時,可以用東京或完整的東京都搜尋到,但是無法用京都搜尋到該筆資料,或許這在很多時候是好用的,尤其當大家公認東京都跟京都是完全不同的 entity,但有時候當使用者只知道公司部分名稱時,就容易造成困擾。以下是 Salesforce 搜尋框的兩個主要限制:
  • 預設行為為 start with:一如前例,系統搜尋行為會找出以京都為起始字串的資料,如果想找到東京都,搜尋時必須輸入「*京都」。
  • 底層以 morphological tokenization 為基礎的索引:換言之,當專有名詞不常見時,可能因斷詞系統無法正確辨識而找不到。

上述兩個問題在斷詞系統剛從 bigram 轉換為 morphological [1] 時備受批評,但在 2017 年 4 月 [2] 跟 6 月 [3] 有修正得更精細些,例如:「横浜市中区」已經可以使用「中区」找到,關鍵不只是 start-with 的問題,更在於斷詞系統能識別「中区」一詞;然而對於廣大可能的中文專由名詞來說,這樣的解法仍然不夠。

其實英文也不見得就完全沒事,因為有時候在專有名詞欄位,使用者可能只輸入縮寫,甚至是自創的縮寫,這時候 morphological 一樣無法正確辨識並提供搜尋時所需的索引。

想要徹底解決,自己動手寫點程式可能免不了,就 Salesforce 提供的 SOSL 與 SOQL 而言,前者仍然以斷詞系統為基礎 [4],因此只能選用 SOQL 的 like-anywhere 完成,但相對效能上難免吃重些。

see also:
[1] Salesforce Release Notes (Winter'15): Users Searching in Chinese, Japanese, Korean, and Thai Find Optimized Results
[2] Salesforce Community Known Issues: New morphology tokenization for Japanese characters after new search framework can cause unexpected morphological tokenization result
[3] Salesforce Community Known Issues: Chinese - Morphology tokenization improvement for mainly proper name noun
[4] Force.com SOQL and SOSL Reference: Introduction to SOQL and SOSL
[5] Salesforce Community Known Issue: Wrong results for Japanese OR search for file content
[6] ROSETTE.com: Why Tokenization Matters

2017-11-05

Salesforce Secured Callout

在應用系統介接時,使用 Web API 是目前常見的手段;Salesforce 也提供這樣的模式,無論是扮演 service provider (使用內建的 SOAP Web Services,或是自訂 RESTful API),或是 service consumer,都有對應的支援;其中當 Salesforce 扮演 service consumer 時,最常見的是使用 Apex 自行呼叫外部 Web API,這種呼叫模式在 Saleforce 中稱為 callout。

在安全性方面,系統介接時一般會優先討論連線方面的安全性,更嚴謹的才會觸及資料面的安全性;這邊我們只討論前者,也就是透過 TLS 對 HTTP 加密,這樣的外部呼叫稱為 secured callout。

值得注意的是:Salesforce 在實作 secured callout 時,對簽發 target service 憑證的 CA 只認可正向表列中 root CA 簽發的憑證 [1] [2],然而這份列表有些陳舊 [3],許多常見的 root CA 都不在列表中,例如 TWCA Global Root CA,若 target service 不是自有的服務,無法變更憑證時,可能會被迫使用未加密的 HTTP。

另外在 protocol 版本上,Salesforce 支援 TLS 1.1 與 1.2 [4],TLS 1.0 由於可降階至 SSL 3.0 且 SSL 3.0 設計上有安全性缺陷,因此目前已停用 TLS 1.0 [5]

除了 secured callout 外,另一個會使用到憑證的場合是:使用 Salesforce 實作自訂的網站,並掛上自家的網域名稱;這時候對於憑證的限制比前者稍微放寬,允許不是由 root CA 直接簽發的憑證。管理者可以新增所需的 intermediate CA 到 Salesforce [6],但根源的 root CA 仍必須是列表中認可的。

至於新增 custom site 憑證到 Salesforce 時,由於這邊扮演的是 service provider,因此上傳的資訊中必須包括 private key;使用的檔案格式是 Java KeyStore,這方面的基礎知識可以參考 [7]

see also:
[1] Outbound Messaging SSL CA Certificates
[2] Know More about All the SSL Certificates That Are Supported by Salesforce
[2] Salesforce Community Idea: Manage List of Trusted Certificate Authorities 
[3] Summer '15 Release Notes: Test and Use Advanced Networking Protocols
[4] Salesforce Disabling TLS 1.0
[6] SSL Certificate Standards
[8] Making Authenticated Web Service Callouts Using Two-Way SSL

Salesforce Release Cycle and Certification

Salesforce 維持每年三次的更新節奏 [1],分別為:
  • Spring Release: February
  • Summer Release: June
  • Winter Release: October
其中 Winter release 會標示來年的年份,例如 2017/10/16 發表的 Winter release 會標示成 Winter'18。

此外,每次新版發表後,官方會給予所有證照持有人約 8 個月的時間,對更新內容進行測驗;起算時間從測驗提供的起始日起算,例如:Winter'18 的發表日是 2017/10/16,但是更新測驗的起始日是 2017/11/20,往後約 8 個月,到 2018/7/13 是證照持有人需完成測驗的期限。

費用方面,目前基本的 Salesforce Developer Certification 考試費用是 USD $200,之後針對新內容的測驗費是 USD $100/year,如果同時擁有多張證照,每年維持有效的測驗費會有些可觀,但目前上限是 USD $300。

see also:
[1] Salesforce Blog: Seasonal Release
[2] Salesforce Certification: Maintenance Exam
[3] Salesforce: Certification Mainenence Overview
[4] Salesforce: Cost to Maintain a Salesforce Credential

2017-10-30

Salesforce: Objects Generalization and Specialization

Salesforce 在設定畫面上的 object 名詞可能是個讓習於物件導向程式開發者困惑的地方:畫面上的 object 代表 type definition,而非 instance,真正描述 instance 的名詞是 record。

然而在處理 generalization & specialization 議題時,畫面上又給了一個新名詞,record type。系統管理者可以把所有可能用到的欄位都開在同一個 object 裡,透過 record type 的方式,限定各個 record type 所能看到的欄位。值得注意的是:這邊的縮限手段,是採用綁定 layout 的方式達成,如果使用 Apex 程式存取,則不在此限。

從 generalization & specialization 的抽象層次來看,record type 確實提供了一種 specialization 的途徑,與物件導向中的 subclass 類似,但很不一樣的是:record type 處理的是資料面的 specialization,讓特定 record type 只能看到原來眾多欄位的子集合,而 subclass 同時兼顧資料面與行為面的 specialization,各個 subclasses 能夠自己額外定義欄位並實作不同行為。

此外,record type 並不像物件導向的繼承關係將共同的部分獨立抽取至 super class,相對於 record types,object 等於是所有欄位的大雜燴,看不到哪些是共同的欄位,對於 generalization 有潔癖的人來說,似乎只能用類似物件導向 composition 的方式處理 [1],也就是 Salesforce 中的 master-detail object relationships [2]

使用 master-detail relationships 實現 generalization 難免有強加物件導向觀念的味道,但更不幸的是:即使採用這麼複雜的設定,仍然無法達成目的。原因在於:使用 master-detail relationships 實作 generalization 時,生命週期控制在 master 端,相當於是 specialized subclass;但在數量對應上 master 端屬於 one-side 而非 many-side,違背 generalization 的初衷。

see also:
[1] Salesforce Developers Forum: Custom Objects and Inheritance
[2] Salesforce Object Relationships

2017-10-24

Apex Serialize Objects to XML

先前的文章 [1] 中提到,Salesforce 的 REST 同時支援兩種輸出格式,XML 與 JSON;client 可以透過 HttpRequst header 中 Accept 屬性指定想要的 content type;這兩個 serialization 的過程對於 REST service 的實作者而言是完全 transparent 的,意即實作時 method 並不 return string 型別,而是直接 return sObject,由系統的 outbound messaging 機制直接處理掉。

然而對於 Apex 開發者而言,程式語言級別對 XML 與 JSON 在 serialization 的支援度就差很遠。以 JSON 為例,無論物件多複雜,要轉成 JSON string 只要一行 JSON.serialize() 就可以搞定 [2],但是轉成 XML string 時,無論使用 XmlStreamWriter [3] 或 DOM.XmlNode [4] [5] 都需要針對物件中各個 fields 作繁瑣地處理。

為了達到快速生成 XML 字串,我們可以透過 (1) 實作一 REST service;(2) 在 Apex 中呼叫該 service,並將 HttpRequest Accept header 設定為 'application/xml' 的方式,快速從 HttpResponse.getBody() 取得 outbound messaging 生成好的 XML 字串。

概念上是走 loopback invocation 的方式,利用既有的機制,寫最少的程式,完成任務。

see also:
[1] Salesforce RESTful Services: Response Content-Type
[2] JSON Support: Roundtrip Serialization and Deserialization
[3] XML Support: Writing XML Using Streams
[4] XML Support: Reading and Writing XML Using the DOM
[5] XmlNode Class
[6] Salesforce Trailblazer Community Idea: Convert Object to XML (Serialize)

2017-10-13

Salesforce RESTful Services: Publish

當使用 Salesforce 作為 API provider 時,預設採用 OAuth 2.0 作為認證機制,如果想簡化認證過程,則可透過將 API 發佈到公開站台的方式完成,此時需要針對 site profile 進行設定。
  • 首先要進到 site details page;在 Setup / Build / Develop / Sites,點選 Site Label 欄位 (不是 Edit 也不是 Site URL)。
  • Site Details page 上方有個 Public Access Settings button,點進去後可能會因為 Org 不同而出現不同畫面:
  • Personal Develop Org: 畫面為一個很長的表格,要設定的地方有:Enabled Apex Class Access, Field-Level Security 與 Object Permissions。
  • Production Sandbox Org: 分成 Apps 與 System 兩段,需設定的連結落在 Apps 裡頭,分別是 Apex Class Access 與 Object Settings。
設定的重點不外乎:service class 本身以及相關需要存取的物件欄位,因此只要分別在 Apex Class 與 Objects 中將相關權限打開即可。

see also:
[1] Public RESTful Web Services on Force.com Sites
[2] Public Access Settings for Force.com Sites
[3] Public Access Settings in Salesforce

Salesforce RESTful Services: Response Content-Type

Salesforce 實作 RESTful 時,不需額外的程式碼,系統會自動支援 2 種 response Content-Type:JSON 與 XML。

但由於回傳 XML 時,內部的 tag 名稱會代出自訂欄位與物件的名稱,並不存在於 W3C 預設 namespace 中,而 Salesforce 亦不會代為產生對應的 XML schema,因此對於某些 XML parser 來說會造成 parsing error,比較直覺的解法是繞過回傳 XML 改使用 JSON。

ps. 實際測試時,回傳的 XML 字串會設定 namespace 為 http://soap.sforce.com/schemas/class/ 開頭的 URL,但並未實際生成 XML schema。

前面說過 Salesforce 會自動支援 2 種 response Content-Type,切換的依據是根據 request header 中的 Accept 屬性。
  • 一般瀏覽器 (Chrome, Firefox, IE) 預設的 Accept Content-Type 為 text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8,此時 Salesforce 會優先取用 application/xml
  • 當未設定 Accept Content-Type,或指定 Accept Content-Type 為 application/json 或空白時,Salesforce 預設會以 JSON 作為 response Content-Type。
如果想直接在 server 端指定只支援 JSON,則必須將 method 的 return type 改為 void,並自行指定 HttpResponse,填入 JSON 字串。

RestContext.response.addHeader('Content-Type', 'application/json');
RestContext.response.responseBody = Blob.valueOf('{ "result": true}');

ps. 回傳的 charset 預設皆為 UTF-8,不需要額外設定 Accept-Charset。

see also:
[1] Apex REST Methods
[2] How can I return JSON object from REST service?

2017-10-06

Apex Describe Information

Salesforce 針對 SObject 與 field 分別提供相關的 describe information (a.k.a. metadata),有點類似 Java 中的 java.lang.reflect.Type 與 java.lang.reflect.Field;不過 Salesforce 進一部拆分成 token 與 describe result,前者為一個 compile-time generated & runtime immutable reference,沒有細部資料,但可用於比對 (==),而後者才是真正具備細部資料的結構。

  • Token 轉換至 describe result 的行為稱之為 describe,呼叫 getDescribe() method,可視為 dereference & data loading。
  • SObject 與 field 的 token 與 describe result 資料型別與相關取用方式分別為:
下圖為 4 種 describe information 的產生與轉換方式:
  • 除了 SObject 的 token 與 describe result 有機會從 instance 取得,其餘都必須在 Apex code 中指定 SObject 或 Field name 才能獲得。
  • 不只 SObject instance 有提供 getSObjectType() method,其它提供此 method 的還包括:SObject Describe Result, List 與 Map;其中 List 與 Map 呼叫 getSObjectType() 時等同於針對其 element instance 呼叫該 method。

see also:
[1] Apex Developer Guide: Schema Class
[2] Apex Developer Guide: Schame Namespace
[3] Apex Developer Guide: Dynamic Apex

2017-09-28

Salesforce Apex REST

REST 是目前實作 web API 時較為普遍的選擇,Salesforce 原生在 APEX 中也提供了這方面的支援,方便開發者自訂 API 作為系統整合使用。在 APEX 有以下 6 個 REST annotations:
  1. @RestResource
  2. @HttpDelete
  3. @HttpGet
  4. @HttpPatch
  5. @HttpPost
  6. @HttpPut
值得注意的是:APEX REST annotations 中只有 RestResource 才有 urlMapping 參數,且該 annotation 為 class level;換言之:
  • 每一個 service implementation class,針對各種 HTTP requests 類型只能有 1 個 method;不能同時宣告多個 @HttpGet methods 在 1 class 中;
  • Service implementation class 中的 class name 並不會浮現在 URL 中。
see also:
[1] Apex REST Annotations
[2] Creating REST APIs using Apex REST

2017-09-21

Salesforce: More about SOQL

上一篇整理了 Salesforce 中各式 object relationships 與相關 SOQL patterns;這邊想多聊一些 SOQL 撰寫時要注意的小地方。

Lazy Fetch

SOQL 算是把 lazy fetch 發揮得淋漓盡致;首先,它不像 SQL,SOQL 中並不提供 SELECT * 的語句,所有需要的 fields 除了 ID 都必須完整列舉。其次,即使只 SELECT 一個 field,回傳型別仍為 List<T> where T extends sObject,以下面這個針對 Account 物件搜尋的 SOQL 為例:
SELECT Name FROM Account
回傳的型別是 List<Account> 而不是 List<String>。

Query from One-side
如同操作 relational database,Salesforce 也提供了一些介面方便使用 SOQL 查詢系統中的資料,無論是 Workbench 或 Eclipse 中的 schema view,都會將結果以表格 (tabular) 的方式呈現。這跟 SOQL 以 object-oriented 的操作思維有些衝突,尤其是針對 one-to-many relationships 查詢時。以下是從 one-side 出發使用 outer JOIN 後在 Workbench 中得到的錯誤訊息。
看來 Salesforce 在這塊並不像 report 介面提供表格中再展開子表的能力,直接給了 parent relationships queries are disabled 的錯誤訊息。

2017-09-19

Salesforce Object Relationships

Salesforce 中有關 objects relationships 對新手往往是比較難懂的,主要原因包括:
  1. Object modeling 時採用的 schema builder 看似 ER model,但實際使用 SOQL 操作時,卻是以物件導向的觀點。
  2. SOQL 沒有 JOIN,只能透過 relationship traversal with path expression 完成各式的 implicit JOIN。
  3. Object 中記錄 relationship 的 field naming,有獨特的 convention,有時使用 __c,有時卻要使用 __r。
  4. Relationship 的 cardinality 固定為 one-to-many;使用 SOQL traverse relationship 時,從 one side 或 many side 出發會需要截然不同的 SOQL statement patterns。
另外還有幾點對撰寫 SOQL 關係不大,但也容易造成困擾:
  1. 設定 relationships 可能分為兩類:lookup 與 master-detail (a.k.a. MD)。
  2. 以物件導向的角度,上述分別是 aggregation 與 composition;前者是 whole-part 的關係,後者有一致的生命週期。
  3. 文件中的 parent 與 child,並非專屬於 master-detail relationship,純粹分別用於 relationship 中的 one side 與 many side。
[1] 是目前看過的參考資料中,最為完整的一篇,建議可以從這篇開始入門;相對其他文件,此處的名詞定義叫為明確,範例也相對清楚。這邊特別說明了如果 SOQL 以 one side (parent) 作為起點時,需使用 nested SELECT:
  • Both relationships are defined from the many-to-1 side, namely from a child to a parent. Note that we have utilized a NESTED select to obtain the records of related children from the parent. This kind of expression is very powerful in obtaining related records traversing from the 1 side in a 1-m relationship.
  • It is a useful pattern to obtain related information on a parent and all its children via traversing path expressions with relationship fields.
另外,當 SOQL 以 one side 出發並使用 nested SELECT 時,回傳結果的數量並非 Cartesian product,而是以 nested object 的方式呈現,意即每一筆 parent object 中含有多筆 child objects。

下表整理了 SOQL 針對不同 side 作為起點,如何透過 relationship traversal 達到各式 JOIN 的 patterns:
Outermost FROM Clause
One Side Many Side
Inner JOIN 在 WHERE clause 中使用 nested SELECT
SELECT Name
FROM Position_c
WHERE Id IN (SELECT Position__c FROM Job_Application__c)
在 WHERE clause 中直接取用 relationship
SELECT Name,Position__r.Name,
FROM Job_Application__c
WHERE Position__r.Department__c = ‘Sales’
Outer JOIN 在 SELECT clause 中使用 nested SELECT
SELECT Name, (SELECT Name FROM Job_Applications__r)
FROM Position__c
在 SELECT clause 中直接取用 relationship
SELECT Name, Position__r.Department__c
FROM Job_Application__c
ps. 上述 SOQL 中,只有在取用 relevant object 的欄位時才需要使用 __r notation,若只需該物件 id,則用 __c 即可。

[2] 說明了定義 relationship 時的命名原則:
  • 在 parent object 中定義 relationship 時:名稱須 unique to the parent,並且使用複數形的 child object name 命名。
  • 在 child object 中定義 relationship 時:名稱須 unique to the child,並且使用單數形的 parent object name 命名。
[3] 提供了一個從 child object (Daughter) 定義 lookup relationship 的範例
  • 按前述第 (2) 項原則,relationship filed 的命名應使用單數的 parent object name,此處命名為 Mother_of_Child (應可簡化為 Mother)。
  • Child Relationship Name 則以 child object 為主,命名為 Daughters。
    => Lookup relationship 中才談 Child Relationship Name,master-detail relationship 無此欄位。
  • 從 child object 為起點實作 outer JOIN:SELECT Id, FirstName__c, Mother_of_Child__r.FirstName__c FROM Daughter__c WHERE Mother_of_Child__r.LastName__c LIKE 'C%'
  • 從 parent object 為起點實作 outer JOIN:SELECT LastName__c, (SELECT LastName__c FROM Daughters__r) FROM Mother__c
see also:
[1] A Deeper Look at SOQL and Relationship Queries on Force.com
[2] Understanding Relationship Names
[3] Understanding Relationship Names, Custom Objects, and Custom Fields

2017-07-20

MVC, ASP.NET, and Salesforce VisualPage

表面上寫了好一陣 web application,骨子裡更多是寫 backend, libraries 與一些整合性的 service,UI 這塊一直非常薄弱。一開始想學 Struts2,看了些書,但一直沒落實到開發上;後來因為 Activiti BPM 接觸到 Vaadin 6,大概知道 component-based MVC 的樣貌,之後又碰了些 ASP.NET WebForm 這種以 view 為主的架構,算是有些粗略印象,該是要好好釐清的時候。

傳統的 MVC 源自單機的圖形化程式,model, view, controller 都在同一個 runtime 上,彼此溝通無礙;model 變化時可以順利更新 view,controller 只要負責處理 request 與 response 就好,簡單來說就是源自 UI control 與 model 的 events。其中 model 更新 view 這段連結又稱為 data binding。

這件事到了 web application 世界中有了大挑戰。

在 web application 中,view 屬於 client code,由 rendering engine 負責生成,而 controller 與 model 為 server code,server 端無法隨時連接 client 作畫面更新;因此出現了 MVC Model2。MVC Model2 並沒有解決問題,只是因應限制,釐清與傳統 MVC 的不同,MVC Model2 中,model 不負責將結果更新到 view,改由 controller 負責,至於如何實現一時還沒有答案。

這個時期出現幾種作法,最直覺得就是在 view 上頭掛一段 JavaScript,定期刷新頁面,包括畫面跟資料。微軟為了比較好的使用者體驗,在 1999 率先提出 XMLHttpRequest (XHR) 的概念,也就是說 client 刷新不再以整個 view 為單位,可以只針對需要的資料取得相關的 XML 即可。整個標準化的過程一直到 2002/6 Mozilla Gecko 發佈 1.0 版、2004/2 Safari 1.2 發佈之後才真正落實。

2004-2005 這兩年迎來巨變。

Gmail 跟 Google Maps 相繼於 2004/4 與 2005/2 發表,大家也發現 XMLHttpRequest 可以搭配 client-side JavaScript asynchronous 的行為,讓 JavaScript 負責處理 XMLHttpResponse 就好,不但可以刷新部份頁面,也可以部在一開始就載入完整頁面,當使用者觸發後再進一步抓資料,且不影響既有畫面瀏覽。AJAX 正式登場,並作為 2004/11 Firefox 1.0 發表時的主打特點。

某程度上來說 AJAX 算是解決了部份 data binding 的問題,但主動權仍落在 client 端,並不是真的由 controller 更新 view,屬於單向 (one-way) data binding。即便如此,AJAX 也讓更多人把目光從 data binding 轉移到另一個重點,model 的複用,衍生 component-based MVC frameworks。

對於初入前端的人來說,最棘手的就是各瀏覽器相容性問題,一種解法是使用 cross-browser JavaScript UI libraries,另一種就是交由 MVC frameworks 產出 cross-browser UI components。Component-based MVC 就是後者的產物。

這邊的 component 指的是 UI component;例如一個下拉選單,或 textbox;通常會帶一些資料跟檢核機制。相對於 MVC,component 比較像是 model 在 view 中的呈現,卻又帶一點 controller 對資料檢核的邏輯,但其呈現的不是一整的 page 而是各別 model。使用 component-based MVC 有個很大的好處,前面說的 one-way data binding 都在 framework 中處理好了,不需要額外程式碼,開發人員只要專注如何組裝、複用這些 UI components 即可。

但也因為 components 大大削弱了原本 MVC Model2 中 controller 的角色,處理 request 與更新 view;因此這類 component-based MVC framework 標榜的 architectural pattern 往往不是 MVC Model2,而是 MVP 或 MVVM 等。

ASP.NET 中的 code behind 算不算是 controller?
先回頭看看 controller 的 3 項主要職責:(1) 處理 request;(2) 根據執行結果選擇不同的 view;(3) 更新 view 中 model 的值。從功能性來看,code behind 完全能達到上面三者的要求,那究竟怪在哪裡?

首先 code behind class 裡頭的 member fields 多半宣告在 ASPX/ASPC 中,而 ASPX/ASPC 又只能綁定一個 code behind class,導致兩者 1:1 的強耦合關係,微軟更因此給了個很妙的名稱,page controller;這樣的模糊命名讓原本寫 ASP.NET Web Forms 的開發人員,瞬間也可以宣稱自己在寫 MVC;只是沒有以 ASP.NET MVC 為基礎,真正的 ASP.NET MVC 用的是 front controller pattern。

除了耦合性議題,另一個 page controller 讓人覺得詭異的地方是:在針對執行結果導向對應的 view 時,page controller 往往使用 HttpRequest.Redirect,依賴瀏覽器處理 HTTP redirect 行為,而不是走 server-side internal routing。

所以這個問題若使用微軟模糊的回答,答案是 YES;但面對多數的 MVC framework 是完全對應不起來。

Salesforce VisualPage 的 controller
相對於 ASP.NET ASPX/ASPC 與 code behind 的結構,VisualPage 也有個類似 code behind 的 class,稱為 controller。比 code behind 稍微好一點,VisualPage 所有欄位的宣告被放在 controller,換言之,controller 可脫離 VisualPage 獨立測試。此外在預載欄位資料上,ASP.NET 透過 code behind 塞入資料,而 VisualPage 的 controller 只提供 getter,把塞資料的主動權交給 view 本身,跟 AJAX data binding 的作法一致。

至於連動選單的更新,VisualPage 的作法就完全按照 AJAX data binding,不需要重載整個頁面,由瀏覽器送出 XMLHttpRequest 後刷新 UI component 完成。

See also:
[1] XMLHttpRequest (wikipedia; Mozilla.org)
[2] AJAX Programming (wikipedia)
[3] Ajax: A New Approach to Web Applications
[4] GUI Architectures (Martin Fowler)
[5] MVC是一個巨大誤會
[6] MVVM (wikipedia)
[7] MVP (wikipedia)
[8] Webforms vs MVC and Why MVC is better?
[9] MVC-structured Visualforce Example