After a fair amount of experimentation, I've written my first type provider. It is admittedly quite simple and isn't production quality, but it has been a fun exercise. The basic idea is to read the appSettings elements from a config file, parse the values appropriately, and interact with them in a type safe way.
The sample config file looks like this:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="test2" value="Some Test Value 5"/> <add key="TestInt" value="102"/> <add key="TestBool" value="True"/> <add key="TestDouble" value="10.01"/> </appSettings> </configuration>Here's the TypeProvider code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AppSettingsTypeProvider | |
open System | |
open System.IO | |
open System.Reflection | |
open Samples.FSharpPreviewRelease2011.ProvidedTypes | |
open Microsoft.FSharp.Core.CompilerServices | |
open System.Configuration | |
[<TypeProvider>] | |
type public AppSettingsProvider(config:TypeProviderConfig) as this = | |
inherit TypeProviderForNamespaces() | |
let namespaceName = "AppSettingsTypeProvider" | |
let currentAssembly = Assembly.GetExecutingAssembly() | |
let tySettings = ProvidedTypeDefinition(currentAssembly, namespaceName, "AppSettings", Some typeof<obj>) | |
let addSettings (configFileName:string) (tyDef:ProvidedTypeDefinition) = | |
tyDef.AddXmlDocDelayed(fun () -> sprintf "Provides a strongly typed representation of the appSettings in a provided config file.") | |
let filePath = Path.Combine(config.ResolutionFolder, configFileName) | |
let fileMap = ExeConfigurationFileMap(ExeConfigFilename=filePath) | |
let appSettings = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None).AppSettings.Settings | |
let tryParseWith func = func >> function | |
| true, value -> Some value | |
| false, _ -> None | |
let (|Bool|_|) = tryParseWith Boolean.TryParse | |
let (|Int|_|) = tryParseWith Int32.TryParse | |
let (|Double|_|) = tryParseWith Double.TryParse | |
do Seq.iter (fun (key) -> | |
let keyElement = match (appSettings.Item key).Value with | |
| Int fieldValue -> ProvidedLiteralField(key, typeof<int>, fieldValue) | |
| Bool fieldValue -> ProvidedLiteralField(key, typeof<bool>, fieldValue) | |
| Double fieldValue -> ProvidedLiteralField(key, typeof<Double>, fieldValue) | |
| fieldValue -> ProvidedLiteralField(key, typeof<string>, fieldValue.ToString()) | |
do keyElement.AddXmlDocDelayed(fun () -> sprintf "Returns the value from the appSetting with key %s" key) | |
do tyDef.AddMember keyElement ) appSettings.AllKeys | |
let staticParams = [ProvidedStaticParameter("configFilePath", typeof<string>)] | |
do tySettings.DefineStaticParameters(staticParams, | |
fun typeName args -> | |
match args with | |
| [| :? string as configFileName |] -> | |
let ty = ProvidedTypeDefinition(assembly = currentAssembly, | |
namespaceName = namespaceName, typeName = typeName, | |
baseType = Some typeof<obj>, HideObjectMethods = true) | |
addSettings configFileName ty | |
ty | |
| _ -> failwith "unexpected parameter values") | |
do this.AddNamespace(namespaceName, [tySettings]) | |
[<assembly:TypeProviderAssembly>] | |
do() |
#r "System" #r @"bin\debug\AppSettingsTypeProvider.dll" open System open AppSettingsTypeProvider type settings = AppSettings<"App.config"> printfn "Test2 String: %s" settings.test2 printfn "Test Int: %i" settings.TestInt printfn "Test Bool: %b" settings.TestBool printfn "Test Double: %f" settings.TestDouble