.html` and substitutes `{{Key}}` placeholders from a `Scripting.Dictionary` built by `Application.wsc`'s handler subs (e.g. `RunHomeHello`) from a controller's plain scalar output — controllers (`HomeController.Hello(viewName, message)`) never build the Dictionary or touch rendering themselves, keeping them primitive-only like the rest of the request-data boundary. `viewsDir` is resolved by each `Default.asp` via `Server.MapPath("../Views")` (same parent-relative pattern as `logDir`) and threaded through `Application.Run`'s new `viewsDir` parameter — `ViewRenderer.wsc` itself never references ASP intrinsics.
+
+`Views/` is a sibling of `public/`, exactly like `Framework/`/`Controllers/`, so it is unreachable over HTTP by the same physical-separation guarantee (verified: `GET /Views/Home.html` → `404`) — no additional deny rule was needed.
+
+Substitution is a single deterministic left-to-right scan; template content is never executed as VBScript or evaluated as an expression (SPEC §8). Placeholder values are HTML-encoded (`&`, `<`, `>`, `"`, `'`) for text context only — **attribute/URL/JS-context encoding is not implemented**; this is a deliberate scope limit (SPEC §8 requires "separate, explicit handling" per context, and no current view needs anything but text context), not an oversight. An unterminated `{{`, a placeholder with no matching dictionary key, an invalid view name, or a missing template file are all safe errors (`Err.Raise` with a generic, path-free message) that flow into `Application.wsc`'s existing central 500 handling, which already discards `Err.Description` entirely.
+
+`Views/Home.html` is exactly `{{Message}}`; `HomeController` supplies `message = "Hello from WSC-MVC!"`, a string with no HTML-special characters, so `/hello`'s response body is byte-identical to the pre-M4 (M1-era) contract — the rendering pipeline was proven end-to-end without changing observable behavior. `Views/Layout.html` and a layout convention are deferred until a second view actually needs shared chrome (IMPLEMENTATION_PLAN M4 explicitly sequences "add layout after renderer works").
+
## Deferred to later milestones (do not implement early)
-Per SPEC §3 non-goals and IMPLEMENTATION_PLAN M4+: HTML views/templates, ADODB, auth. A more robust logging mechanism (if complete coverage under concurrent load is ever required) is deferred to M6 — see the concurrency finding in `docs/DECISIONS.md`.
+Per SPEC §3 non-goals and IMPLEMENTATION_PLAN M5+: ADODB, auth. A more robust logging mechanism (if complete coverage under concurrent load is ever required) is deferred to M6 — see the concurrency finding in `docs/DECISIONS.md`. A layout convention (`Views/Layout.html`) and non-text-context encoding are deferred until a concrete view needs them — see the Views section above.
diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md
index a226faa..1d8d6a3 100644
--- a/docs/DECISIONS.md
+++ b/docs/DECISIONS.md
@@ -117,3 +117,13 @@ Route path validation is intentionally conservative for M3. ASP/IIS has already
The response contract gained an `allowHeader` out parameter from `Application.Run`. `Default.asp` is still the only layer that touches ASP `Response`; it emits the `Allow` header only when the framework returns one. This keeps routing/framework code ASP-intrinsic-free while still allowing correct HTTP behavior for app-routed `405 Method Not Allowed` responses.
IIS/Classic ASP on the local Windows 11 test host intercepted `PUT`/`DELETE` requests before they reached the application, returning IIS's own `Allow: GET, HEAD, OPTIONS, TRACE`. Therefore HTTP integration tests assert framework 405 behavior using `POST /hello`, which Classic ASP does deliver to the app and which returns the framework's `Allow: GET`. The full Router/Application unsupported-method contract, including `DELETE /self-test -> Allow: GET, POST`, is covered by WSH component tests and the self-test controller's direct `Router.Match` checks. This matches the M3 scope: support GET and POST initially; distinguish unsupported methods where the request reaches the framework.
+
+## M4 — Views: ViewRenderer, controller/render split (2026-09-19)
+
+Added `Framework/ViewRenderer.wsc` (`WscMvc.ViewRenderer`) and `Views/Home.html`. Design choice: controllers (`HomeController.Hello`) stay primitive-only (return `viewName`, `message` as plain strings) and `Application.wsc`'s handler subs own building the `Scripting.Dictionary` and calling `ViewRenderer.Render` — matches the existing pattern of keeping business-logic components decoupled from any object more complex than what they strictly need to hand back, and avoids deciding a controller-to-Dictionary contract before a second controller actually needs one. `viewsDir` is threaded through as a new `Application.Run` parameter (not through `RequestContext`, which would have cascaded into every `ctx.Initialize` call site across both `Default.asp` files, `SelfTestController.wsc` (5 call sites), and the WSH tests, for a value that is pure Framework wiring rather than genuine per-request data) — smallest-footprint choice given `Application.Run`'s signature is already established as changeable pre-release (see the M2/M3 CLSID-stability note above).
+
+`/hello`'s existing exact-body contract (`"Hello from WSC-MVC!"`, fixed since M1/SPEC §4) was deliberately preserved rather than changed to prove the render pipeline: `Views/Home.html` is exactly `{{Message}}`, and the message string has no HTML-special characters, so HTML-encoding is a no-op and the response is byte-identical pre- and post-M4 — verified directly (`Content-Length: 19` unchanged) rather than assumed. This let the M1 acceptance test double as a live M4 regression check instead of requiring a new observable behavior to prove the slice works.
+
+No experimental surprises this milestone (unlike M0-M3, which each surfaced a real WSC/VBScript defect) — returning a newly-created object through a `Sub`'s `ByRef` out-parameter (`ctrl.Hello viewName, message`, both scalars in this case, but the same mechanism was also exercised for `Set data = CreateObject(...)` inside `RunHomeHello`) behaved exactly as ordinary VBScript `ByRef` semantics predict, and `Err.Raise` inside a WSC method, caught by the caller's existing `On Error Resume Next` + `Err.Number` pattern, worked identically to the naturally-thrown errors (`CreateObject` failures) already relied on elsewhere. Confirmed via the WSH suite's dedicated `ViewRenderer` contract tests (encoding, missing template, unresolved placeholder, invalid view name) before trusting it in `Application.wsc`.
+
+Confirmed `Views/` is unreachable over HTTP purely from being a sibling of `public/` (same guarantee already used for `Framework/`/`Controllers/`) — no new `web.config` rule was needed: `GET /Views/Home.html` → `404` on both sites.
diff --git a/docs/TEST-RESULTS.md b/docs/TEST-RESULTS.md
index 1ab7a7a..ecbb839 100644
--- a/docs/TEST-RESULTS.md
+++ b/docs/TEST-RESULTS.md
@@ -354,3 +354,68 @@ Result: **PASS** — `ok=true`, checks included `request_context_contract`, `cor
`tests/run-self-test.sh` local note: first failed under local bash because the Windows checkout had CRLF line endings in the `.sh` file (`set: pipefail\r: invalid option name`). Added `.gitattributes` (`*.sh text eol=lf`) and normalized the script. After that, local WSL/bash still could not reach Windows IIS on `localhost:8091`/gateway IP from this environment (`curl: (7) Failed to connect`), so the bash wrapper is **NOT RUN/PASS locally**. The same HTTP JSON contract was verified with PowerShell above; the bash wrapper remains intended for a CLI that can reach the test-app URL, as in earlier VM/tailnet evidence.
M3 gate status: **PASS** for framework and IIS GET/POST behavior. PUT/DELETE HTTP probes are not used as app-route assertions on this host because IIS intercepts them before Classic ASP; WSH/self-test Router checks cover the unsupported-method framework contract directly.
+
+## M4 — Views: separate templates, text-context encoding, default-deny (2026-09-19)
+
+Host under test: `DESKTOP-80D128R`, Microsoft Windows 11 Pro 10.0.26200, 64-bit — same local IIS sites as M3 (`WscMvc` on `:8090`, `WscMvcTests` on `:8091`), already running from this checkout.
+
+Added `Framework/ViewRenderer.wsc` (`WscMvc.ViewRenderer`, `{4948DF84-5DC6-448A-9F1B-EB596C28842B}`) and `Views/Home.html` (`{{Message}}`). `Controllers/HomeController.wsc`'s `Hello` now returns scalar view data (`viewName`, `message`) instead of a finished body string; `Framework/Application.wsc`'s `Run` gained a `viewsDir` parameter and `RunHomeHello` now builds a `Scripting.Dictionary` and calls `ViewRenderer.Render`. Both `Default.asp` files resolve `viewsDir = Server.MapPath("../Views")`, the same parent-relative pattern already used for `logDir`.
+
+Test-first check before implementation:
+
+```
+cscript //nologo tests\Test-Components.vbs
+```
+
+Result before M4 implementation: **FAIL**, as expected for the new contract:
+
+```
+FAIL: Application.Run raised error on /hello - Wrong number of arguments or invalid property assignment
+FAIL: could not create WscMvc.ViewRenderer - ActiveX component can't create object
+RESULT: FAILURE
+```
+
+Registration after implementation:
+
+```
+powershell -ExecutionPolicy Bypass -File tools\Register-Components.ps1
+```
+
+Result: **PASS** — registered `RequestContext`, `Router`, new `ViewRenderer`, `Application`, `HomeController`, `SelfTestController`.
+
+WSH component contract:
+
+```
+cscript //nologo tests\Test-Components.vbs
+```
+
+Result: **PASS** (full output, ViewRenderer-specific lines):
+
+```
+PASS: Application.Run(/hello) body [unchanged: "Hello from WSC-MVC!" — regression proof the render pipeline preserves M1's exact-body contract]
+PASS: ViewRenderer HTML-encodes placeholder value
+PASS: ViewRenderer passes through template with no placeholders (data=Nothing)
+PASS: ViewRenderer.Render on missing template raises a path-free error
+PASS: ViewRenderer.Render raises on unresolved placeholder
+PASS: ViewRenderer.Render rejects an invalid view name
+RESULT: ALL PASS
+```
+
+HTTP integration:
+
+```
+powershell -ExecutionPolicy Bypass -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl http://localhost:8091
+```
+
+Result: **PASS**, all 14 existing checks including `GET /hello body` (unchanged) and `GET /Framework/Application.wsc unreachable`.
+
+Direct verification that `Views/` is unreachable over HTTP (physical separation, same guarantee as `Framework/`/`Controllers/` — `Views/` is a sibling of `public/`, never inside it):
+
+```
+curl -i http://localhost:8090/hello -> 200, "Hello from WSC-MVC!" (Content-Length: 19, unchanged from pre-M4)
+curl -o /dev/null -w "%{http_code}" http://localhost:8090/Views/Home.html -> 404
+```
+
+`tests/run-self-test.sh http://localhost:8091`: **NOT RUN locally** — this host's local Python is a Microsoft Store execution-alias stub, so the script's `json.tool` pretty-print step cannot run. The same JSON contract was already verified directly (`curl http://localhost:8091/self-test` → `{"ok":true,...}`, all 5 checks passing) and via `Test-Http.ps1` above; this is a convenience-script limitation on this specific host, not a framework regression — matches the pre-existing NOT RUN note for this same script under M3.
+
+M4 gate status: **PASS** for the vertical slice implemented (safe separate template loading from a physically non-served directory; deterministic `{{Key}}` substitution; default HTML-text-context encoding verified against a real escaping case; missing-template and unresolved-placeholder failures verified path-free and safe; `/hello`'s existing exact-body contract verified unchanged end-to-end through IIS). `Views/Layout.html` and a layout convention are deferred (IMPLEMENTATION_PLAN M4 sequences "add layout after renderer works" as a second step) — not yet needed since there is still only one view. Attribute/URL/JS-context encoding remains explicitly out of scope until a view needs it (documented on `ViewRenderer.wsc` and in `docs/ARCHITECTURE.md`).
diff --git a/public/Default.asp b/public/Default.asp
index ab83b3b..0aa4e26 100644
--- a/public/Default.asp
+++ b/public/Default.asp
@@ -1,15 +1,16 @@
<%@ Language="VBScript" %>
<% Option Explicit %>
<%
-Dim route, httpMethod, logDir, ctx, app, applicationName, statusLine, contentType, body, allowHeader
+Dim route, httpMethod, logDir, viewsDir, ctx, app, applicationName, statusLine, contentType, body, allowHeader
route = Request.QueryString("route")
httpMethod = Request.ServerVariables("REQUEST_METHOD")
applicationName = "production"
-' logs/ deliberately lives outside the served "public" webroot (IIS site
-' physical path = public/), so a parent-relative MapPath is required here -
+' logs/ and Views/ deliberately live outside the served "public" webroot (IIS
+' site physical path = public/), so parent-relative MapPath is required here -
' requires enableParentPaths=true for this site. See docs/DECISIONS.md.
logDir = Server.MapPath("../logs")
+viewsDir = Server.MapPath("../Views")
Set ctx = Nothing
On Error Resume Next
@@ -57,7 +58,7 @@ body = ""
allowHeader = ""
On Error Resume Next
-app.Run ctx, applicationName, statusLine, contentType, body, allowHeader
+app.Run ctx, applicationName, viewsDir, statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
diff --git a/test-app/Controllers/SelfTestController.wsc b/test-app/Controllers/SelfTestController.wsc
index e7a37ad..0cc91a1 100644
--- a/test-app/Controllers/SelfTestController.wsc
+++ b/test-app/Controllers/SelfTestController.wsc
@@ -87,7 +87,7 @@ Sub RunSelfTest(logDir, body)
Set ctxUnknown = CreateObject("WscMvc.RequestContext")
ctxUnknown.Initialize "/hello", "GET", logDir
unkStatus = "" : unkType = "" : unkBody = "" : unkAllow = ""
- app.Run ctxUnknown, "tests", unkStatus, unkType, unkBody, unkAllow
+ app.Run ctxUnknown, "tests", "", unkStatus, unkType, unkBody, unkAllow
If Err.Number <> 0 Then
unkDetail = Err.Description
Err.Clear
diff --git a/test-app/public/Default.asp b/test-app/public/Default.asp
index a115e4a..dcc7d8a 100644
--- a/test-app/public/Default.asp
+++ b/test-app/public/Default.asp
@@ -5,15 +5,16 @@
' set in the shared framework. Production's bootstrap selects "production".
' Keeping this value inside each app prevents direct Default.asp?route=...
' requests from crossing the application boundary.
-Dim route, httpMethod, logDir, ctx, app, applicationName, statusLine, contentType, body, allowHeader
+Dim route, httpMethod, logDir, viewsDir, ctx, app, applicationName, statusLine, contentType, body, allowHeader
route = Request.QueryString("route")
httpMethod = Request.ServerVariables("REQUEST_METHOD")
applicationName = "tests"
-' logs/ deliberately lives outside the served "public" webroot (IIS site
-' physical path = public/), so a parent-relative MapPath is required here -
+' logs/ and Views/ deliberately live outside the served "public" webroot (IIS
+' site physical path = public/), so parent-relative MapPath is required here -
' requires enableParentPaths=true for this site. See docs/DECISIONS.md.
logDir = Server.MapPath("../logs")
+viewsDir = Server.MapPath("../Views")
Set ctx = Nothing
On Error Resume Next
@@ -61,7 +62,7 @@ body = ""
allowHeader = ""
On Error Resume Next
-app.Run ctx, applicationName, statusLine, contentType, body, allowHeader
+app.Run ctx, applicationName, viewsDir, statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
diff --git a/tests/Test-Components.vbs b/tests/Test-Components.vbs
index 128839b..ee9e93d 100644
--- a/tests/Test-Components.vbs
+++ b/tests/Test-Components.vbs
@@ -1,6 +1,6 @@
Option Explicit
-Dim fso, logDir, pass
+Dim fso, logDir, viewsDir, testViewsDir, pass
pass = True
Set fso = CreateObject("Scripting.FileSystemObject")
@@ -9,6 +9,17 @@ If fso.FolderExists(logDir) Then
fso.DeleteFolder logDir, True
End If
+' Real Views/ used for the /hello integration path (proves the end-to-end
+' rendering pipeline, not just the ViewRenderer component in isolation).
+viewsDir = fso.GetParentFolderName(WScript.ScriptFullName) & "\..\Views"
+
+' Disposable fixture directory for direct ViewRenderer contract tests below.
+testViewsDir = fso.GetParentFolderName(WScript.ScriptFullName) & "\test-views"
+If fso.FolderExists(testViewsDir) Then
+ fso.DeleteFolder testViewsDir, True
+End If
+fso.CreateFolder testViewsDir
+
Function NewContext(path, httpMethod)
Dim ctx
Set ctx = CreateObject("WscMvc.RequestContext")
@@ -20,10 +31,18 @@ Sub RunApplication(app, path, httpMethod, applicationName, statusLine, contentTy
Dim ctx
Set ctx = NewContext(path, httpMethod)
statusLine = "" : contentType = "" : body = "" : allowHeader = ""
- app.Run ctx, applicationName, statusLine, contentType, body, allowHeader
+ app.Run ctx, applicationName, viewsDir, statusLine, contentType, body, allowHeader
Set ctx = Nothing
End Sub
+Sub WriteFixture(fileName, content)
+ Dim stream
+ Set stream = fso.CreateTextFile(testViewsDir & "\" & fileName, True)
+ stream.Write content
+ stream.Close
+ Set stream = Nothing
+End Sub
+
Sub CheckEqual(actual, expected, label)
If actual = expected Then
WScript.Echo "PASS: " & label
@@ -177,6 +196,123 @@ If pass Then
CheckEqual allowHeader, "", "Application.Run(malformed path) Allow header"
End If
+' --- ViewRenderer contract: substitution + HTML-encoding ---
+Dim renderer
+On Error Resume Next
+Set renderer = CreateObject("WscMvc.ViewRenderer")
+If Err.Number <> 0 Then
+ WScript.Echo "FAIL: could not create WscMvc.ViewRenderer - " & Err.Description
+ pass = False
+ Err.Clear
+End If
+On Error Goto 0
+
+If pass Then
+ WriteFixture "Sample.html", "{{Name}}
"
+ Dim dataSample, outSample
+ Set dataSample = CreateObject("Scripting.Dictionary")
+ dataSample.Add "Name", ""
+ outSample = ""
+ On Error Resume Next
+ renderer.Render testViewsDir, "Sample", dataSample, outSample
+ If Err.Number <> 0 Then
+ WScript.Echo "FAIL: ViewRenderer.Render raised error on Sample - " & Err.Description
+ pass = False
+ Err.Clear
+ End If
+ On Error Goto 0
+ If pass Then
+ CheckEqual outSample, "<script>alert(1)</script>
", "ViewRenderer HTML-encodes placeholder value"
+ End If
+ Set dataSample = Nothing
+End If
+
+' --- ViewRenderer contract: template with no placeholders passes through unchanged, data = Nothing ---
+If pass Then
+ WriteFixture "Plain.html", "Plain text, no placeholders."
+ Dim outPlain
+ outPlain = ""
+ On Error Resume Next
+ renderer.Render testViewsDir, "Plain", Nothing, outPlain
+ If Err.Number <> 0 Then
+ WScript.Echo "FAIL: ViewRenderer.Render raised error on Plain (data=Nothing) - " & Err.Description
+ pass = False
+ Err.Clear
+ End If
+ On Error Goto 0
+ If pass Then
+ CheckEqual outPlain, "Plain text, no placeholders.", "ViewRenderer passes through template with no placeholders (data=Nothing)"
+ End If
+End If
+
+' --- ViewRenderer contract: missing template file is a safe, path-free error ---
+If pass Then
+ Dim outMissing, missingOk, missingDetail
+ missingOk = False
+ outMissing = ""
+ On Error Resume Next
+ renderer.Render testViewsDir, "DoesNotExist", Nothing, outMissing
+ If Err.Number <> 0 Then
+ missingDetail = Err.Description
+ If InStr(1, missingDetail, testViewsDir, 1) = 0 Then
+ missingOk = True
+ End If
+ Err.Clear
+ End If
+ On Error Goto 0
+ If missingOk Then
+ WScript.Echo "PASS: ViewRenderer.Render on missing template raises a path-free error"
+ Else
+ WScript.Echo "FAIL: ViewRenderer.Render on missing template -> Err.Number after call, detail=[" & missingDetail & "]"
+ pass = False
+ End If
+End If
+
+' --- ViewRenderer contract: unresolved placeholder (no matching dictionary key) is a safe error ---
+If pass Then
+ WriteFixture "Missing.html", "{{Unknown}}"
+ Dim dataEmpty, outMissingKey, unresolvedOk
+ Set dataEmpty = CreateObject("Scripting.Dictionary")
+ unresolvedOk = False
+ outMissingKey = ""
+ On Error Resume Next
+ renderer.Render testViewsDir, "Missing", dataEmpty, outMissingKey
+ If Err.Number <> 0 Then
+ unresolvedOk = True
+ Err.Clear
+ End If
+ On Error Goto 0
+ If unresolvedOk Then
+ WScript.Echo "PASS: ViewRenderer.Render raises on unresolved placeholder"
+ Else
+ WScript.Echo "FAIL: ViewRenderer.Render did not raise on unresolved placeholder -> got [" & outMissingKey & "]"
+ pass = False
+ End If
+ Set dataEmpty = Nothing
+End If
+
+' --- ViewRenderer contract: invalid view name (path traversal attempt) is rejected ---
+If pass Then
+ Dim outTraversal, traversalOk
+ traversalOk = False
+ outTraversal = ""
+ On Error Resume Next
+ renderer.Render testViewsDir, "../secret", Nothing, outTraversal
+ If Err.Number <> 0 Then
+ traversalOk = True
+ Err.Clear
+ End If
+ On Error Goto 0
+ If traversalOk Then
+ WScript.Echo "PASS: ViewRenderer.Render rejects an invalid view name"
+ Else
+ WScript.Echo "FAIL: ViewRenderer.Render did not reject invalid view name -> got [" & outTraversal & "]"
+ pass = False
+ End If
+End If
+
+Set renderer = Nothing
+
' --- Logging: best-effort log file was written with both outcomes ---
If pass Then
Dim logPath, logContent
@@ -205,6 +341,9 @@ Set ctx2 = Nothing
If fso.FolderExists(logDir) Then
fso.DeleteFolder logDir, True
End If
+If fso.FolderExists(testViewsDir) Then
+ fso.DeleteFolder testViewsDir, True
+End If
Set fso = Nothing
If pass Then
diff --git a/tools/Register-Components.ps1 b/tools/Register-Components.ps1
index 59df449..53594f9 100644
--- a/tools/Register-Components.ps1
+++ b/tools/Register-Components.ps1
@@ -14,6 +14,7 @@ $components = @(
# docs/ARCHITECTURE.md.
(Join-Path $ProjectRoot 'Framework\RequestContext.wsc'),
(Join-Path $ProjectRoot 'Framework\Router.wsc'),
+ (Join-Path $ProjectRoot 'Framework\ViewRenderer.wsc'),
(Join-Path $ProjectRoot 'Framework\Application.wsc'),
(Join-Path $ProjectRoot 'Controllers\HomeController.wsc'),
(Join-Path $ProjectRoot 'test-app\Controllers\SelfTestController.wsc')
diff --git a/tools/Unregister-Components.ps1 b/tools/Unregister-Components.ps1
index cfcbe2b..b623d29 100644
--- a/tools/Unregister-Components.ps1
+++ b/tools/Unregister-Components.ps1
@@ -18,6 +18,7 @@ $components = @(
@{ Path = (Join-Path $ProjectRoot 'test-app\Controllers\SelfTestController.wsc'); ProgId = 'WscMvc.SelfTestController'; ClassId = '{D2634944-4646-4C55-956E-4C05E7E10904}' },
@{ Path = (Join-Path $ProjectRoot 'Controllers\HomeController.wsc'); ProgId = 'WscMvc.HomeController'; ClassId = '{87488446-60BE-4068-8368-0B709BB68F3F}' },
@{ Path = (Join-Path $ProjectRoot 'Framework\Application.wsc'); ProgId = 'WscMvc.Application'; ClassId = '{851C7763-1638-42FE-A166-BF3DD3A96A88}' },
+ @{ Path = (Join-Path $ProjectRoot 'Framework\ViewRenderer.wsc'); ProgId = 'WscMvc.ViewRenderer'; ClassId = '{4948DF84-5DC6-448A-9F1B-EB596C28842B}' },
@{ Path = (Join-Path $ProjectRoot 'Framework\Router.wsc'); ProgId = 'WscMvc.Router'; ClassId = '{C92F9338-B478-4EAD-B865-892FFB1E1C51}' },
@{ Path = (Join-Path $ProjectRoot 'Framework\RequestContext.wsc'); ProgId = 'WscMvc.RequestContext'; ClassId = '{1C36FA55-34DF-4974-94B9-D657389362B2}' }
)