|
- <%
- '=======================================================================================================================
- ' Jurisdiction Number Validation + Lookup
- '=======================================================================================================================
- ' Validates a submitted jurisdiction number against the JCode field of the jurisdictions
- ' 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<TAB>name1|code2<TAB>name2|...|"),
- ' 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)
- Dim codesText
- codesText = GetJurisdictionCodesText()
-
- jurisdictionNumber = Trim(jurisdictionNumber)
-
- If Len(codesText) = 0 Then
- ' No jurisdiction list available at all (first request after startup, API also
- ' down) - fail open rather than block every order request site-wide.
- IsValidJurisdictionNumber = True
- Else
- IsValidJurisdictionNumber = (InStr(codesText, "|" & jurisdictionNumber & Chr(9)) > 0)
- End If
- End Function
-
- ' 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 "|code1<TAB>name1|code2<TAB>name2|...|" 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
-
- cacheMinutes = GetAppSetting("JurisdictionCacheMinutes")
- If Not IsNumeric(cacheMinutes) Then cacheMinutes = 60
-
- cachedAt = Application("JurisdictionCodesFetchedAt")
- isStale = IsEmpty(cachedAt)
- If Not isStale Then isStale = (DateDiff("n", cachedAt, Now()) >= CInt(cacheMinutes))
-
- If isStale Then
- Dim fresh
- fresh = FetchJurisdictionCodesText()
- If Len(fresh) > 0 Then
- Application.Lock
- Application("JurisdictionCodesText") = fresh
- Application("JurisdictionCodesFetchedAt") = Now()
- Application.Unlock
- End If
- End If
-
- GetJurisdictionCodesText = Application("JurisdictionCodesText")
- End Function
-
- ' Fetches the jurisdictions list and extracts each JCode/Name pair. Returns a
- ' "|code1<TAB>name1|code2<TAB>name2|...|" 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. 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 = ""
-
- On Error Resume Next
-
- Dim http : Set http = Server.CreateObject("Msxml2.ServerXMLHTTP")
- http.setTimeouts 5000, 5000, 5000, 5000
- http.Open "GET", apiUrl, False
- http.Send ""
-
- If Err.Number = 0 And http.Status = 200 Then
- Dim re, matches, m, codes, code, name
- Set re = New RegExp
- re.Pattern = """JCode""\s*:\s*""([^""]*)""\s*,\s*""Name""\s*:\s*""([^""]*)"""
- re.Global = True
- re.IgnoreCase = True
-
- Set matches = re.Execute(http.responseText)
- If Err.Number = 0 And matches.Count > 0 Then
- codes = "|"
- For Each m In matches
- code = Trim(m.SubMatches(0))
- 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
- End If
-
- Err.Clear
- On Error GoTo 0
-
- FetchJurisdictionCodesText = result
- End Function
- %>
|