Consolidated ASP Classic MVC framework from best components
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

321 lignes
11KB

  1. <?xml version="1.0"?>
  2. <!-- MVCDispatcher.wsc -->
  3. <component>
  4. <!-- COM registration -->
  5. <registration
  6. description = "Classic ASP MVC Dispatcher Component"
  7. progid = "App.MVCDispatcher"
  8. version = "1.0"
  9. classid = "{C3D4E5F6-7A8B-49C0-8D1E-2F3A4B5C6D7E}" />
  10. <!-- Public interface -->
  11. <public>
  12. <property name="Router">
  13. <put internalName="PutRouter"/>
  14. </property>
  15. <property name="ControllerRegistry">
  16. <put internalName="PutControllerRegistry"/>
  17. </property>
  18. <property name="ControllerFactory">
  19. <put internalName="PutControllerFactory"/>
  20. </property>
  21. <property name="Routes">
  22. <put internalName="PutRoutes"/>
  23. </property>
  24. <property name="ControllerName">
  25. <get internalName="GetControllerName"/>
  26. </property>
  27. <property name="CurrentController">
  28. <get internalName="GetCurrentController"/>
  29. </property>
  30. <property name="UseLayout">
  31. <get internalName="GetUseLayout"/>
  32. </property>
  33. <property name="LastError">
  34. <get internalName="GetLastError"/>
  35. </property>
  36. <method name="Resolve"/>
  37. <method name="ExecuteAction"/>
  38. <method name="DispatchRequest"/>
  39. <method name="RequirePost"/>
  40. <method name="RedirectToAction"/>
  41. <method name="RedirectTo"/>
  42. <method name="RedirectToExt"/>
  43. <method name="RedirectToActionExt"/>
  44. </public>
  45. <!-- Give the component ASP intrinsic objects (Request, Response, Server ...) -->
  46. <implements type="ASP"/>
  47. <!-- Implementation -->
  48. <script language="VBScript">
  49. <![CDATA[
  50. Option Explicit
  51. '------------------------------------------------------------
  52. ' Injected dependencies
  53. '
  54. ' A WSC runs in its own isolated script engine, so it cannot see the
  55. ' including ASP page's globals (GetAppSetting, ControllerRegistry(),
  56. ' Routes(), router, the per-controller singleton functions, etc.) the
  57. ' way mvc.asp used to. Per AGENTS.md section 69/70 ("Dependencies Must
  58. ' Be Injected", "Avoid Hidden Dependencies"), everything this component
  59. ' needs from the outside is passed in explicitly instead.
  60. '
  61. ' Properties here are wired to plain functions (PutX/GetX) via
  62. ' internalName in the <public> section above, rather than VBScript
  63. ' "Property Get/Let/Set" syntax - that syntax is only legal inside an
  64. ' explicit Class...End Class block (a general VBScript rule, not
  65. ' specific to WSC), and this component's members are declared directly
  66. ' in the script rather than inside a Class, matching router.wsc's
  67. ' existing convention in this project.
  68. '------------------------------------------------------------
  69. Private m_router
  70. Private m_controllerRegistry
  71. Private m_controllerFactory
  72. Private m_routes
  73. Private m_currentController
  74. Private m_controllerName ' "Controller" suffix stripped, e.g. "Home"
  75. Private m_actionName
  76. Private m_params
  77. Private m_lastError
  78. Sub PutRouter(value)
  79. Set m_router = value
  80. End Sub
  81. Sub PutControllerRegistry(value)
  82. Set m_controllerRegistry = value
  83. End Sub
  84. Sub PutControllerFactory(value)
  85. Set m_controllerFactory = value
  86. End Sub
  87. Sub PutRoutes(value)
  88. Set m_routes = value
  89. End Sub
  90. '------------------------------------------------------------
  91. ' Read-only state exposed to callers/views
  92. '------------------------------------------------------------
  93. ' Name of the controller most recently resolved by Resolve(), with the
  94. ' "Controller" suffix stripped (e.g. "Home", not "HomeController").
  95. Function GetControllerName()
  96. GetControllerName = m_controllerName
  97. End Function
  98. ' The controller object itself, e.g. so a layout can read CurrentController.Title.
  99. Function GetCurrentController()
  100. Set GetCurrentController = m_currentController
  101. End Function
  102. Function GetUseLayout()
  103. If IsObject(m_currentController) Then
  104. GetUseLayout = m_currentController.useLayout
  105. Else
  106. GetUseLayout = False
  107. End If
  108. End Function
  109. ' Nothing when the last Resolve()/ExecuteAction() succeeded. Otherwise a
  110. ' Dictionary with "Type" ("Security" or "Action") plus either "Message"
  111. ' (Security) or "ActionName"/"Number"/"Description" (Action). Rendering
  112. ' this is the caller's job - per AGENTS.md's "Controllers Coordinate;
  113. ' They Do Not Render", this component never calls Response.Write.
  114. Function GetLastError()
  115. Set GetLastError = m_lastError
  116. End Function
  117. '------------------------------------------------------------
  118. ' Resolve(method, path) -> Boolean
  119. '
  120. ' Resolves the route, validates the controller/action name format and
  121. ' whitelist, and creates the controller via the injected ControllerFactory.
  122. ' Returns False and sets LastError (Type = "Security") if any check fails.
  123. ' Must be called before ExecuteAction().
  124. '------------------------------------------------------------
  125. Public Function Resolve(method, path)
  126. Dim routeArray, controllerNameRaw, actionNameRaw
  127. Set m_lastError = Nothing
  128. Set m_currentController = Nothing
  129. m_controllerName = ""
  130. m_actionName = ""
  131. m_params = Empty
  132. routeArray = m_router.Resolve(method, path)
  133. controllerNameRaw = routeArray(0)
  134. actionNameRaw = routeArray(1)
  135. If Not m_controllerRegistry.IsValidControllerFormat(controllerNameRaw) Then
  136. SetSecurityError "Invalid controller name format."
  137. Resolve = False
  138. Exit Function
  139. End If
  140. If Not m_controllerRegistry.IsValidActionFormat(actionNameRaw) Then
  141. SetSecurityError "Invalid action name format."
  142. Resolve = False
  143. Exit Function
  144. End If
  145. If Not m_controllerRegistry.IsValidController(controllerNameRaw) Then
  146. SetSecurityError "Controller '" & controllerNameRaw & "' is not registered."
  147. Resolve = False
  148. Exit Function
  149. End If
  150. Set m_currentController = m_controllerFactory.Create(controllerNameRaw)
  151. m_controllerName = StripControllerSuffix(controllerNameRaw)
  152. m_actionName = actionNameRaw
  153. If UBound(routeArray) >= 2 Then
  154. m_params = SurroundParamsInArray(routeArray(2))
  155. End If
  156. Resolve = True
  157. End Function
  158. '------------------------------------------------------------
  159. ' ExecuteAction()
  160. '
  161. ' Invokes the resolved action on the resolved controller. Must follow a
  162. ' successful Resolve(). Populates LastError (Type = "Action") on failure
  163. ' instead of raising or writing HTML, so the caller decides how to show it.
  164. '
  165. ' Named ExecuteAction rather than Execute because VBScript's Execute
  166. ' STATEMENT (used below to invoke the action by name) is shadowed by any
  167. ' Sub/Function of the same name in scope - a Sub literally named "Execute"
  168. ' cannot call the Execute statement from inside itself.
  169. '
  170. ' The action name is still dispatched via a scoped Execute, since
  171. ' controllers do not (yet) implement the Invoke() reflection convention
  172. ' from AGENTS.md section 69 - that is a larger, separate change to every
  173. ' controller's contract. What this fixes is the CONTROLLER resolution:
  174. ' the original mvc.asp built "Set CurrentController = " & controllerName
  175. ' & "()" from a route-supplied string and Executed it, resolving an
  176. ' arbitrary global name at dispatch time. Here the controller always comes
  177. ' from the injected ControllerFactory (whitelisted, explicitly registered
  178. ' ahead of time), and this Execute only ever calls a method by name on
  179. ' that already-known object.
  180. '------------------------------------------------------------
  181. Public Sub ExecuteAction()
  182. Dim callString, errInfo
  183. If Not IsObject(m_currentController) Then
  184. Err.Raise vbObjectError + 1200, "MVCDispatcher.ExecuteAction", "ExecuteAction called before a successful Resolve."
  185. End If
  186. If IsArray(m_params) Then
  187. If UBound(m_params) >= 0 Then
  188. callString = "Call m_currentController." & m_actionName & "(" & Join(m_params, ",") & ")"
  189. Else
  190. callString = "Call m_currentController." & m_actionName & "()"
  191. End If
  192. Else
  193. callString = "Call m_currentController." & m_actionName & "()"
  194. End If
  195. On Error Resume Next
  196. Execute callString
  197. If Err.Number <> 0 Then
  198. Set errInfo = Server.CreateObject("Scripting.Dictionary")
  199. errInfo.Add "Type", "Action"
  200. errInfo.Add "ActionName", m_actionName
  201. errInfo.Add "Number", Err.Number
  202. errInfo.Add "Description", Err.Description
  203. Set m_lastError = errInfo
  204. Err.Clear
  205. End If
  206. On Error Goto 0
  207. End Sub
  208. '------------------------------------------------------------
  209. ' DispatchRequest(method, path)
  210. '
  211. ' Convenience wrapper for callers that do not need layout wrapping or
  212. ' custom error rendering (e.g. tests): Resolve then ExecuteAction, silently
  213. ' doing nothing further on a Resolve failure. Callers that need to wrap
  214. ' output in a layout or render errors (the real app's entry point) should
  215. ' call Resolve/UseLayout/ExecuteAction/LastError directly instead - see
  216. ' public/Default.asp.
  217. '------------------------------------------------------------
  218. Public Sub DispatchRequest(method, path)
  219. If Resolve(method, path) Then
  220. ExecuteAction()
  221. End If
  222. End Sub
  223. '------------------------------------------------------------
  224. Public Sub RequirePost()
  225. If Request.Form.Count = 0 Then
  226. RedirectToExt "NotValid", "", Empty
  227. End If
  228. End Sub
  229. ' Shortcut for RedirectToActionExt that does not require passing a parameters argument.
  230. Public Sub RedirectToAction(action_name)
  231. RedirectToActionExt action_name, Empty
  232. End Sub
  233. Public Sub RedirectTo(controller_name, action_name)
  234. RedirectToExt controller_name, action_name, Empty
  235. End Sub
  236. ' Redirects the browser to the specified action on the specified controller with the
  237. ' specified querystring parameters. params is a KVArray of querystring parameters.
  238. Public Sub RedirectToExt(controller_name, action_name, params)
  239. Response.Redirect m_routes.UrlTo(controller_name, action_name, params)
  240. End Sub
  241. Public Sub RedirectToActionExt(action_name, params)
  242. RedirectToExt m_controllerName, action_name, params
  243. End Sub
  244. '------------------------------------------------------------
  245. ' Private helpers
  246. '------------------------------------------------------------
  247. Private Sub SetSecurityError(message)
  248. Dim errInfo
  249. Set errInfo = Server.CreateObject("Scripting.Dictionary")
  250. errInfo.Add "Type", "Security"
  251. errInfo.Add "Message", message
  252. Set m_lastError = errInfo
  253. End Sub
  254. Private Function StripControllerSuffix(name)
  255. Const suffix = "Controller"
  256. If Len(name) > Len(suffix) And LCase(Right(name, Len(suffix))) = LCase(suffix) Then
  257. StripControllerSuffix = Left(name, Len(name) - Len(suffix))
  258. Else
  259. StripControllerSuffix = name
  260. End If
  261. End Function
  262. ' Wraps string route params in quotes so they can be spliced into the
  263. ' dynamically-built Execute call string in ExecuteAction() above. Equivalent
  264. ' to the app-side SurroundStringInArray() helper, duplicated here (rather
  265. ' than called) because this WSC cannot see that global function.
  266. Private Function SurroundParamsInArray(arr)
  267. Dim i, result
  268. result = arr
  269. For i = LBound(result) To UBound(result)
  270. If TypeName(result(i)) = "String" Then
  271. result(i) = """" & result(i) & """"
  272. End If
  273. Next
  274. SurroundParamsInArray = result
  275. End Function
  276. ]]>
  277. </script>
  278. </component>

Powered by TurnKey Linux.