Consolidated ASP Classic MVC framework from best components
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

321 строка
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.