F# Trying to access ConfigurationManager [duplicate] - f#

This question already has answers here:
The name 'ConfigurationManager' does not exist in the current context
(19 answers)
Closed 5 years ago.
I'm building a small project to understand F# better. As part of it, I want a function "myAppSettings" that uses System.Configuration.ConfigurationManager to read settings from the AppSettings.config file. I put this in a separate file in my solution called myAppSettings and it looks like this:
namespace foo.bar
open System
open System.Configuration
module myAppSettings =
let read key mandatory =
let appSettings = ConfigurationManager.AppSettings
let value = appSettings key
if mandatory && String.IsNullOrEmpty(value) then
failwith "bad hombres"
value
This does not compile however, I get the error:
Error 1 The namespace or module 'ConfigurationManager' is not defined
I know that that object is in System.Configuration (use it all the time from C#) so I must have syntax wrong somewhere, but where?
I also tried:
let appSettings = new ConfigurationManager.AppSettings
let value = appSettings key
which then found the ConfigurationManager (the new keyword helped) but then objected to the "let value":
Error 1 Incomplete structured construct at or before this point in
expression
I want to understand the two error messages and the right way to access the settings in the app.config file.

Your problem is with how you are accessing the ConfigurationManager:
namespace foo.bar
open System
open System.Configuration
module myAppSettings =
let read key mandatory =
//let appSettings = ConfigurationManager.AppSettings
//let value = appSettings key
let value = ConfigurationManager.AppSettings.Item(key)
if mandatory && String.IsNullOrEmpty(value) then
failwith "bad hombres"
value
If you want to keep the two-part access, try it like this:
let appSettings = ConfigurationManager.AppSettings
let value = appSettings.Item(key)

Related

F# non-static methods in modules

I am an absolute newbie to coding, but I need to modify a F# script. It always gives me the error "Method or object constructor 'x' is not static". I read that this might be due to the fact that I try to call a non-static method within a module, which is by default static. For example 'x' = Get.Axis():
module Primitives =
let axis1 = Zaber.Motion.Ascii.Device.GetAxis(1)
The manual only provides code in C#: var axis1 = device.GetAxis(1);
If I use static member instead of let, I'll get a 'unexpected keyword static in definition' error, although I checked the indentation as suggested in another question.
Assuming you're using the Zaber Motion Library, I think what you need to do is get an instance of a device first, instead of trying to access the class in a static context.
Their documentation includes an example of how to get a list of devices by opening a serial port:
open Zaber.Motion.Ascii
use connection = Connection.OpenSerialPort("COM3")
let deviceList = connection.DetectDevices()
match deviceList |> Seq.tryHead with // See if we got at least one device
| Some device ->
let axis = device.GetAxis(1)
// TODO: Do whatever you want with the axis here
| None ->
failwith "No Devices Found on COM3"

Can you run code before creating a type provider? (F#)

Say:
let x = // some operation
type t = SomeTypeProvider<x>
Is this valid?
No.
Since the types must be generated at compile-time, the parameter to the type provider needs to be a constant.
In other words, the code you marked // some operation can evaluate to a literal, but cannot be a value returned by a runnable function:
let arg = "foo"
type t = SomeTypeProvider<arg> // okay
let [<Literal>] arg = """{"name":"foo","value":42}"""
type t = SomeTypeProvider<arg> // okay
let arg = x.ToString()
type t = SomeTypeProvider<arg> // Boom! arg is not a Literal
It depends on your application, but one of the most common cases is the following:
You have a database-related Type Provider, and the connection string needs to be retrieved in runtime, from some sort of config file or something. So a developer mistakenly thinks they need a runnable code to retrieve the connection string first and then pass it to the Type Provider.
The correct approach is the following:
Keep two databases: one locally stored in a constant location (just for schema), and another one for the runtime purposes.
Pass the first one (a constant!) to your Type Provider. Don't worry about the hardcoded paths; it is only used for schema retrieval.
// Use a fixed sample file for schema generation only
type MyCSVData = CsvProvider<"dummy.csv">
// Load the actual data at runtime
let data = MyCSVData.Load(RetrieveFileNameFromConfig())

SqlDataConnection type provider - setting database connection string with script parameter

The normal way of using a SqlDataConnection type provider is as follows:
type dbSchema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;InitialCatalog=MyDatabase;Integrated Security=SSPI;">
let db = dbSchema.GetDataContext()
However we have a problem which is we want to use this type provider in an f# script where the connection string for the database is passed as a parameter. So what I would like to do is something like this:
let connectionString= Array.get args 1
type dbSchema = SqlDataConnection<connectionString>
However it gives the error "This is not a constant expression or valid custom attribute value"
Is there any way to do this?
Unfortunately there's no way to do this, the type provider requires a compile time literal string. This is so that when you're compiling the application, the type provider's able to connect and retrieve the metadata about the database and generate the types for the compiler. You can choose to extract out the connection string into a string literal by writing it in the form
[<Literal>] let connString = "Data Source=..."
type dbSchema = SqlDataConnection<connString>
Assuming your 2 databases have the same schema, it's then possible to supply your runtime connection string as a parameter to the GetDataContext method like
let connectionString = args.[1]
let dbContext = dbSchema.GetDataContext(connectionString)
The way i've been doing it is i have a hardcoded literal string (using the "Literal" attribute) for design time use and use a local string from the configuration when getting the data context. I use a local db schema (also hardcoded) to speed up intelli-sense during development.
type private settings = AppSettings<"app.config">
let connString = settings.ConnectionStrings.MyConnectionString
type dbSchema = Microsoft.FSharp.Data.TypeProviders.SqlDataConnection<initialConnectionString, Pluralize = true, LocalSchemaFile = localDbSchema , ForceUpdate = false, Timeout=timeout>
let indexDb = dbSchema.GetDataContext(connString);

Emitting Generated Types in F# type providers

I have created a simple generating type provider that takes the path to an assembly at reorganizes the types, to bring them under the type providers namespace, (sort of Internalising if you like).
The link to the code concerned is here
https://github.com/colinbull/Playground
Now the types seem to be provided correctly,
let[<Literal>]assemblyPath = #"D:\Appdev\Playground\SimpleLib\bin\Debug\SimpleLib.dll"
type T = Inno.InternalisingProvider<assemblyPath>
type C = T.Class1
[<EntryPoint>]
let main argv =
let c = new C()
printfn "Result: %A" c.X
System.Console.ReadLine() |> ignore
0
since the displays in VS without any reported errors. However when I compile this assembly the IL seems to get emitted incorrectly with the following error.
Error 1: A problem occurred writing the binary 'obj\Debug\TypeProviders.Tests.exe': Error in pass3 for type Program, error: Error in GetMethodRefAsMethodDefIdx for mref = ".ctor", error: Exception of type 'Microsoft.FSharp.Compiler.AbstractIL.ILBinaryWriter+MethodDefNotFound' was thrown. FSC 1 1 TypeProviders.Tests
Examples given for generated types given in samples pack doesn't seem to have any StaticParameters defined which requires a type with the provided type name to be returned. In this case how do I emit the types in the provided assembly? Currently I am doing the following
let provideAssembly (reqType:ProvidedTypeDefinition) assemblyPath =
let name = Path.GetFileName(assemblyPath)
let providedAssembly = ProvidedAssembly.RegisterGenerated(assemblyPath)
for t in providedAssembly.GetExportedTypes() do
let ty = createGeneratedType t
ty.SetAssembly(providedAssembly)
reqType.AddMember(ty)
reqType
Thanks in advance
Disclaimer: browser compiled solutions
I believe here you don't need to create generated types that will wrap exiting types => this should work
let provideAssembly (reqType:ProvidedTypeDefinition) assemblyPath =
let existingAssembly = Assembly.LoadFrom(assemblyPath)
for ty in providedAssembly.GetExportedTypes() do
reqType.AddMember(ty)
reqType
You can also try this one:
let provideAssembly (reqType:ProvidedTypeDefinition) assemblyPath =
reqType.AddAssemblyTypesAsNestedTypesDelayed(fun() -> Assembly.LoadFrom assemblyPath)
reqType
this one will preserve namespace so declaration of type C will look like
type C = T.SimpleLib.Class1

When do I need to call the "ConvertToGenerated" member to generate types using a type provider

I'm having a hard time deciphering the "Providing Generated Types" section of the Type Provider Tutorial. The tutorial provides the following specification.
"You must also call ConvertToGenerated on a root provided type whose nested types form a closed set of generated types. This call emits the given provided type definition and its nested type definitions into an assembly and adjusts the Assembly property of all provided type definitions to return that assembly. The assembly is emitted only when the Assembly property on the root type is accessed for the first time. The host F# compiler does access this property when it processes a generative type declaration for the type."
I do not know where to place the ConvertToGenerated call and I'm not sure on the requirements of the assembly file name parameter. Can someone provide an example? Thanks.
After some help from the F# team I solved my problem. This is what I did.
namespace Types
open System
open System.Data
open System.IO
open System.Linq
open System.Data.Linq
open Microsoft.FSharp.Data.TypeProviders
open Microsoft.FSharp.Linq
open Microsoft.FSharp.TypeProvider.Emit
open Microsoft.FSharp.Core.CompilerServices
type DatabaseSchema =
SqlDataConnection<"Data Source=(local);Initial Catalog=Test;Integrated Security=SSPI;">
[<TypeProvider>]
type public MeasureTypeProvider(cfg:TypeProviderConfig) as this =
inherit TypeProviderForNamespaces()
let assembly = System.Reflection.Assembly.GetExecutingAssembly()
let typesNamespace = "Types.Domain"
let providedTypeBuilder = ProvidedTypeBuilder.Default
let db = DatabaseSchema.GetDataContext()
let types =
query { for m in db.Table do select m }
|> Seq.map(fun dataEntity ->
let className:string = dataEntity.Identifier
let providedTypeDefinition =
ProvidedTypeDefinition(className = className,
baseType = Some typeof<obj>,
IsErased=false)
providedTypeDefinition.AddMember(
ProvidedConstructor([], InvokeCode = fun [] -> <## obj() ##>))
providedTypeDefinition
) |> Seq.toList
let rootType =
let providedTypeDefinition =
ProvidedTypeDefinition(assembly,
typeNamespace,
"DomainTypes",
Some typeof<obj>,
IsErased=false)
providedTypeDefinition.AddMembersDelayed(fun () -> types)
this.AddNamespace(typesNamespace, [providedTypeDefinition])
providedTypeDefinition
let path = Path.GetDirectoryName(assembly.Location) + #"\GeneratedTypes.dll"
do rootMeasureType.ConvertToGenerated(path)
[<assembly:TypeProviderAssembly>]
do()
The TypeProvider.Emit framework automatically cleans up the generated assembly. Comment out the following statement if you want it to stick around.
File.Delete assemblyFileName
One other gotcha I found is that while I was able to provide types that derive from value types (like decimal) when IsErased=true, I was not able to provide these derived types when IsErased=false. This is because value types are sealed so it is is not possible to generate a "real" type that derives from a value type.

Resources