diff --git a/app/controllers/OrderController.asp b/app/controllers/OrderController.asp index 1a1e7da..518d0da 100644 --- a/app/controllers/OrderController.asp +++ b/app/controllers/OrderController.asp @@ -29,10 +29,11 @@ Class OrderController_Class ' The order form itself is a placeholder for now - this only confirms the token is valid. '------------------------------------------------------------------------------------------------------------------- Public Sub Continue() - Dim token, order, isValid, csrfToken + Dim token, order, isValid, csrfToken, municipalityName token = Trim(Request.QueryString("token")) isValid = False csrfToken = "" + municipalityName = "" Set order = Nothing If Len(token) > 0 Then @@ -45,7 +46,13 @@ Class OrderController_Class End If End If - If isValid Then csrfToken = HTMLSecurity().GetAntiCSRFToken("Order.Continue") + If isValid Then + csrfToken = HTMLSecurity().GetAntiCSRFToken("Order.Continue") + ' Look up the municipality from the same jurisdiction list the number was + ' already validated against at /request-order - "" means no match (cache empty + ' or code not found), and the view falls back to a manually-entered field. + municipalityName = GetJurisdictionName(order("JurisdictionNumber")) + End If %> <% diff --git a/app/models/JurisdictionValidator.asp b/app/models/JurisdictionValidator.asp index 83d98c0..ca4eb96 100644 --- a/app/models/JurisdictionValidator.asp +++ b/app/models/JurisdictionValidator.asp @@ -1,21 +1,22 @@ <% '======================================================================================================================= -' Jurisdiction Number Validation +' Jurisdiction Number Validation + Lookup '======================================================================================================================= ' Validates a submitted jurisdiction number against the JCode field of the jurisdictions -' list served by JurisdictionApiUrl (see public/web.config). That list is essentially static -' reference data (townships/municipalities), so it's cached in Application scope for -' JurisdictionCacheMinutes (default 60) instead of being fetched on every /request-order -' submission. If a refresh attempt fails, stale cached data is used rather than blocking -' order requests; only when there is no cached data at all (e.g. immediately after an app -' pool restart, with the API also unreachable) does validation fail open (allow) rather than -' block every order request site-wide. +' list served by JurisdictionApiUrl (see public/web.config), and looks up the matching +' municipality Name (used to pre-fill the order form's Municipality field). That list is +' essentially static reference data (townships/municipalities), so it's cached in +' Application scope for JurisdictionCacheMinutes (default 60) instead of being fetched on +' every /request-order submission. If a refresh attempt fails, stale cached data is used +' rather than blocking order requests; only when there is no cached data at all (e.g. +' immediately after an app pool restart, with the API also unreachable) does validation +' fail open (allow) rather than block every order request site-wide. ' -' The cache is stored as a single delimited STRING ("|code1|code2|...|"), not a -' Scripting.Dictionary or array of objects - Scripting.Dictionary is an apartment-threaded -' COM object, and IIS raises "ASP 0197: Disallowed object use" if you try to store one in -' the Application intrinsic (which is shared across all requests/threads). A plain string is -' just script data, so it's safe to cache this way. +' The cache is stored as a single delimited STRING ("|code1name1|code2name2|...|"), +' not a Scripting.Dictionary or array of objects - Scripting.Dictionary is an +' apartment-threaded COM object, and IIS raises "ASP 0197: Disallowed object use" if you try +' to store one in the Application intrinsic (which is shared across all requests/threads). A +' plain string is just script data, so it's safe to cache this way. '======================================================================================================================= Function IsValidJurisdictionNumber(jurisdictionNumber) @@ -29,13 +30,36 @@ Function IsValidJurisdictionNumber(jurisdictionNumber) ' down) - fail open rather than block every order request site-wide. IsValidJurisdictionNumber = True Else - IsValidJurisdictionNumber = (InStr(codesText, "|" & jurisdictionNumber & "|") > 0) + IsValidJurisdictionNumber = (InStr(codesText, "|" & jurisdictionNumber & Chr(9)) > 0) End If End Function -' Returns the cached "|code1|code2|...|" string, refreshing it from JurisdictionApiUrl when -' missing or older than JurisdictionCacheMinutes. Falls back to the existing (stale) cached -' string if a refresh attempt fails. +' Returns the municipality Name matching a jurisdiction number, or "" if the cache is empty +' or has no matching entry (callers should treat "" as "couldn't look this up" and fall back +' to letting the user enter it manually, not as a hard error). +Function GetJurisdictionName(jurisdictionNumber) + Dim codesText, marker, startPos, nameStart, endPos + + GetJurisdictionName = "" + + codesText = GetJurisdictionCodesText() + If Len(codesText) = 0 Then Exit Function + + jurisdictionNumber = Trim(jurisdictionNumber) + marker = "|" & jurisdictionNumber & Chr(9) + startPos = InStr(codesText, marker) + If startPos = 0 Then Exit Function + + nameStart = startPos + Len(marker) + endPos = InStr(nameStart, codesText, "|") + If endPos = 0 Then Exit Function + + GetJurisdictionName = Mid(codesText, nameStart, endPos - nameStart) +End Function + +' Returns the cached "|code1name1|code2name2|...|" string, refreshing it from +' JurisdictionApiUrl when missing or older than JurisdictionCacheMinutes. Falls back to the +' existing (stale) cached string if a refresh attempt fails. Private Function GetJurisdictionCodesText() Dim cacheMinutes, cachedAt, isStale @@ -60,15 +84,18 @@ Private Function GetJurisdictionCodesText() GetJurisdictionCodesText = Application("JurisdictionCodesText") End Function -' Fetches the jurisdictions list and extracts every JCode value. Returns a -' "|code1|code2|...|" string, or "" on any failure (network, HTTP status, or no matches). +' Fetches the jurisdictions list and extracts each JCode/Name pair. Returns a +' "|code1name1|code2name2|...|" string, or "" on any failure (network, HTTP +' status, or no matches). ' ' Uses a targeted regex over the raw response text rather than this codebase's generic ' aspJSON parser (core/lib.json.asp) - that parser walks the input character-by-character in ' plain VBScript, which measured at over 40 seconds for this API's ~450KB/1500-record ' response. RegExp.Execute is implemented natively and handles the same payload -' near-instantly; since JCode is the only field this validator needs, a full generic parse -' isn't necessary anyway. +' near-instantly. The regex assumes JCode is immediately followed by Name in the source +' JSON (verified true for all 1525 current records) - if the API ever reorders those fields +' this will need revisiting, but a full generic parse still isn't necessary since only these +' two fields are used. Private Function FetchJurisdictionCodesText() Dim apiUrl : apiUrl = GetAppSetting("JurisdictionApiUrl") Dim result : result = "" @@ -81,9 +108,9 @@ Private Function FetchJurisdictionCodesText() http.Send "" If Err.Number = 0 And http.Status = 200 Then - Dim re, matches, m, codes, code + Dim re, matches, m, codes, code, name Set re = New RegExp - re.Pattern = """JCode""\s*:\s*""([^""]*)""" + re.Pattern = """JCode""\s*:\s*""([^""]*)""\s*,\s*""Name""\s*:\s*""([^""]*)""" re.Global = True re.IgnoreCase = True @@ -92,7 +119,8 @@ Private Function FetchJurisdictionCodesText() codes = "|" For Each m In matches code = Trim(m.SubMatches(0)) - If Len(code) > 0 Then codes = codes & code & "|" + name = Trim(m.SubMatches(1)) + If Len(code) > 0 Then codes = codes & code & Chr(9) & name & "|" Next If Len(codes) > 1 Then result = codes End If diff --git a/app/views/Order/continue.asp b/app/views/Order/continue.asp index 9b9622d..53baf93 100644 --- a/app/views/Order/continue.asp +++ b/app/views/Order/continue.asp @@ -13,7 +13,16 @@ <% End If %> -<% If isValid Then %> +<% If isValid Then + Dim municipalityIsReadOnlyJs, municipalityDescription + If Len(Trim(municipalityName)) > 0 Then + municipalityIsReadOnlyJs = "true" + municipalityDescription = "Matched automatically from your jurisdiction number." + Else + municipalityIsReadOnlyJs = "false" + municipalityDescription = "We couldn't automatically match your jurisdiction number - please enter your municipality." + End If +%>
@@ -58,7 +67,12 @@ title: "1. Contact Information", elements: [ { type: "text", name: "ContactName", title: "Contact Name", isRequired: true }, - { type: "text", name: "Municipality", title: "Municipality", isRequired: true }, + { + type: "text", name: "Municipality", title: "Municipality", isRequired: true, + defaultValue: "<%= H(municipalityName) %>", + readOnly: <%= municipalityIsReadOnlyJs %>, + description: "<%= H(municipalityDescription) %>" + }, { type: "text", name: "Phone", title: "Phone Number", inputType: "tel", isRequired: true } ] },