-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathDesignTimeConnectionString.fs
More file actions
129 lines (119 loc) · 6.76 KB
/
DesignTimeConnectionString.fs
File metadata and controls
129 lines (119 loc) · 6.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
namespace FSharp.Data.SqlClient
open System.Configuration
open System.IO
open System
open System.Collections.Generic
open System.Diagnostics
open System.Text.RegularExpressions
[<CompilerMessageAttribute("This API supports the FSharp.Data.SqlClient infrastructure and is not intended to be used directly from your code.", 101, IsHidden = true)>]
type internal DesignTimeConnectionString =
| Literal of string
| NameInConfig of name: string * value: string * provider: string
static member Parse(s: string, resolutionFolder, fileName) =
match s.Trim().Split([|'='|], 2, StringSplitOptions.RemoveEmptyEntries) with
| [| "" |] -> invalidArg "ConnectionStringOrName" "Value is empty!"
| [| prefix; tail |] when prefix.Trim().ToLower() = "name" ->
let name = tail.Trim()
let value, provider = DesignTimeConnectionString.ReadFromConfig( name, resolutionFolder, fileName)
NameInConfig( name, value, provider)
| _ ->
Literal s
/// Reads a named connection string from an appsettings.json file.
/// Handles the standard ASP.NET Core ConnectionStrings format:
/// { "ConnectionStrings": { "name": "connection_string" } }
static member TryReadFromAppSettings(name: string, filePath: string) =
try
let json = File.ReadAllText(filePath)
// Locate the ConnectionStrings object; connection strings don't span multiple levels
let sectionMatch =
Regex.Match(json, @"""ConnectionStrings""\s*:\s*\{([^}]+)\}", RegexOptions.Singleline)
if not sectionMatch.Success then None
else
let section = sectionMatch.Groups.[1].Value
// Extract the named value (handles basic JSON escaping)
let valueMatch =
Regex.Match(section, sprintf @"""%s""\s*:\s*""((?:[^""\\]|\\.)*)""" (Regex.Escape(name)))
if not valueMatch.Success then None
else
// Unescape common JSON escape sequences in the connection string
let raw = valueMatch.Groups.[1].Value
let unescaped =
Regex.Replace(raw, @"\\(.)", fun m ->
match m.Groups.[1].Value with
| "\"" -> "\"" | "\\" -> "\\" | "/" -> "/"
| "n" -> "\n" | "r" -> "\r" | "t" -> "\t"
| c -> "\\" + c)
Some unescaped
with _ -> None
static member private ReadFromXmlConfig(name, configFilename) =
let map = ExeConfigurationFileMap()
map.ExeConfigFilename <- configFilename
let configSection = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None).ConnectionStrings.ConnectionStrings
match configSection, lazy configSection.[name] with
| null, _ | _, Lazy null ->
raise <| KeyNotFoundException(message = sprintf "Cannot find name %s in <connectionStrings> section of %s file." name configFilename)
| _, Lazy x ->
let providerName = if String.IsNullOrEmpty x.ProviderName then "System.Data.SqlClient" else x.ProviderName
x.ConnectionString, providerName
static member ReadFromConfig(name, resolutionFolder, fileName) =
if fileName <> "" then
let path = Path.Combine(resolutionFolder, fileName)
if not <| File.Exists path then
raise <| FileNotFoundException(sprintf "Could not find config file '%s'." path)
// Support appsettings.json as an explicit fileName
if path.EndsWith(".json", StringComparison.OrdinalIgnoreCase) then
match DesignTimeConnectionString.TryReadFromAppSettings(name, path) with
| Some value -> value, "System.Data.SqlClient"
| None ->
raise <| KeyNotFoundException(sprintf "Cannot find connection string '%s' in ConnectionStrings section of '%s'." name path)
else
DesignTimeConnectionString.ReadFromXmlConfig(name, path)
else
// Auto-discovery: try XML config files first (app.config / web.config),
// then appsettings.json for ASP.NET Core projects.
// note: these filenames are case sensitive on Linux
let xmlConfigFile =
seq { yield "app.config"; yield "App.config"; yield "web.config"; yield "Web.config" }
|> Seq.map (fun v -> Path.Combine(resolutionFolder, v))
|> Seq.tryFind File.Exists
match xmlConfigFile with
| Some configFilename ->
DesignTimeConnectionString.ReadFromXmlConfig(name, configFilename)
| None ->
let appsettingsFile =
seq { yield "appsettings.json"; yield "appsettings.Development.json" }
|> Seq.map (fun f -> Path.Combine(resolutionFolder, f))
|> Seq.tryFind File.Exists
match appsettingsFile with
| Some path ->
match DesignTimeConnectionString.TryReadFromAppSettings(name, path) with
| Some value -> value, "System.Data.SqlClient"
| None ->
failwithf "Cannot find connection string '%s' in ConnectionStrings section of '%s'." name path
| None ->
failwithf
"Cannot find app.config, web.config, or appsettings.json in '%s'. \
For ASP.NET Core projects, add a ConnectionStrings section to appsettings.json \
or specify the config file explicitly using the ConfigFile parameter."
resolutionFolder
member this.Value =
match this with
| Literal value -> value
| NameInConfig(_, value, _) -> value
member this.RunTimeValueExpr isHostedExecution =
match this with
| Literal value -> <@@ value @@>
| NameInConfig(name, value, _) ->
<@@
let hostProcess = Process.GetCurrentProcess().ProcessName.ToUpper()
if isHostedExecution
|| (Environment.Is64BitProcess && hostProcess = "FSIANYCPU")
|| (not Environment.Is64BitProcess && hostProcess = "FSI")
then
value
else
let section = ConfigurationManager.ConnectionStrings.[name]
if isNull section then raise <| KeyNotFoundException(message = sprintf "Cannot find name %s in <connectionStrings> section of config file." name)
else section.ConnectionString
@@>
member this.IsDefinedByLiteral = match this with | Literal _ -> true | _ -> false