I am back again, this time with a question on writing service in F#. I cannot seem to install the service using installutil. It gives me the following error.
$ installutil atfwindowsservice.exe
Microsoft (R) .NET Framework Installation utility Version 4.0.30319.18408
Copyright (C) Microsoft Corporation. All rights reserved.
Running a transacted installation.
Beginning the Install phase of the installation.
See the contents of the log file for the C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe assembly's progress.
The file is located at C:\Dev\ATF\output\bin\Debug\atfwindowsservice.InstallLog.
Installing assembly 'C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe'.
Affected parameters are:
logtoconsole =
logfile = C:\Dev\ATF\output\bin\Debug\atfwindowsservice.InstallLog
assemblypath = C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe
No public installers with the RunInstallerAttribute.Yes attribute could be found in the C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe assembly.
The code is given below. Any help is appreciated and thanks in advance.
Ramesh
namespace service
open System.ServiceProcess
open System.Runtime.Remoting
open System.Runtime.Remoting.Channels
type atf() =
inherit ServiceBase(ServiceName = "atf win service")
override x.OnStart(args) = ()
override x.OnStop() = ()
The registering the service code:
// Learn more about F# at http://fsharp.net
// See the 'F# Tutorial' project for more help.=
open System
open System.ComponentModel
open System.Configuration.Install
open System.ServiceProcess
[<RunInstaller(true)>]
type FSharpServiceInstaller() =
inherit Installer()
do
// Specify properties of the hosting process
new ServiceProcessInstaller(Account = ServiceAccount.LocalSystem) |> base.Installers.Add |> ignore
// Specify properties of the service running inside the process
new ServiceInstaller( DisplayName = "F# ATF Service", ServiceName = "atf",StartType = ServiceStartMode.Automatic ) |> base.Installers.Add |> ignore
// Run the chat service when the process starts
module Main =
ServiceBase.Run [| new service.atf() :> ServiceBase |]
I had the same problem. I eventually added the following code which works nicely and has the added benefit of not requiring installutil.exe. The service is able to install/uninstall itself by passing in the correct command line param. Keep all your code and add the following:
module Program =
let getInstaller() =
let installer = new AssemblyInstaller(typedefof<atf>.Assembly, null);
installer.UseNewContext <- true
installer
let installService() =
let installer = getInstaller()
let dic = new System.Collections.Hashtable()
installer.Install(dic)
installer.Commit(dic)
let uninstallService() =
let installer = getInstaller()
let dic = new System.Collections.Hashtable()
installer.Uninstall(dic)
[<EntryPoint>]
let main (args:string[]) =
match (args |> Seq.length) with
|1 -> match (args.[0]) with
|"-install" -> installService()
|"-uninstall" -> uninstallService()
|_-> failwith "Unrecognized param %s" args.[0]
|_ -> ServiceBase.Run [| new atf() :> ServiceBase |]
0
To install you can execute the following from the command line:
atfwindowsservice.exe -install
I figured out how to write a self installing service using other examples on the web, especially this post on stack was useful:
http://pingfu.net/programming/2011/08/11/creating-a-self-installing-windows-service-with-csharp.html
open System
open System.ServiceProcess
open System.Windows
open System.Threading
open System.Windows.Forms
open System.ComponentModel
open System.Configuration.Install
open System.Reflection
open Microsoft.Win32
type ATFServiceInstaller() =
inherit Installer()
let spi_ = new ServiceProcessInstaller(Account = ServiceAccount.LocalSystem)
let si_ = new ServiceInstaller( DisplayName = "ATF Service", Description="ATF service", ServiceName = "atf",StartType = ServiceStartMode.Automatic )
let dic_ = new System.Collections.Hashtable()
let SVC_SERVICE_KET = #"SYSTEM\CurrentControlSet\Services"
member this.install () =
base.Installers.Add(spi_) |> ignore
let ret = base.Installers.Add(si_)
let apath = sprintf "/assemblypath=%s" (Assembly.GetExecutingAssembly().Location)
let ctx = [|apath; "/logToConsole=false"; "/showCallStack"|]
this.Context <- new InstallContext("atfserviceinstall.log", ctx)
base.Install(dic_)
base.Commit(dic_)
member this.uninstall() =
base.Installers.Add(spi_) |> ignore
let ret = base.Installers.Add(si_)
let apath = sprintf "/assemblypath=%s" (Assembly.GetExecutingAssembly().Location)
let ctx = [|apath; "/logToConsole=false"; "/showCallStack"|]
this.Context <- new InstallContext("atfserviceinstall.log", ctx)
base.Uninstall(null)
module Main =
try
let args = Environment.GetCommandLineArgs()
match (args |> Seq.length) with
| 2 -> match (args.[1]) with
| "-install" -> let installer = new ATFServiceInstaller()
installer.install()
installer.Dispose()
| "-uninstall" -> let installer = new ATFServiceInstaller()
installer.uninstall()
installer.Dispose()
| _ -> failwith "Unrecognized param %s" args.[0]
| _ -> ServiceBase.Run [| new atfservice.ATFService() :> ServiceBase |]
with
| _ as ex -> MessageBox.Show(ex.ToString()) |> ignore
I came across this question while having the same issue. I still needed to use InstallUtil.exe in the deployment and find out that the problem with your original code was a missing namespace in the main file.
I have found this framework for hosting .NET services http://topshelf-project.com/ which makes the development much easier and basically lets you create a console application which you can debug and also has a built-in Windows/Mono service installer. The only downside for me was a missing support for installation with InstallUtil.exe again but there is a solution for that too. Instead of adding ServiceProcessInstaller and ServiceInstaller to Installers in the class inherited from Installer override Install and Uninstall methods and make them run your executable with install/unistall parameter.
[<RunInstaller(true)>]
type public FSharpServiceInstaller() =
inherit Installer()
override __.Install(stateSaver : System.Collections.IDictionary) =
let assemblyPath = __.Context.Parameters.["assemblypath"]
stateSaver.Add(assemblyIdentifier, assemblyPath)
// runProcess assemblyPath "install"
base.Install(stateSaver)
override __.Uninstall(savedState : System.Collections.IDictionary) =
let assemblyPath = savedState.[assemblyIdentifier].ToString()
// runProcess assemblyPath "uninstall"
base.Uninstall(savedState)
Full code at: https://gist.github.com/jbezak/eda4cc5864059b717e71beaec47db2d9
Related
I am trying to use F# with .NET Core 2.1 to create a simple CRUD application but none of the controllers don't get registered. I see none of the controllers in Swagger and the controller themselves don't start.
I appreciate any help or hint.
Startup.fs
namespace SimpleCms
type Startup private () =
let mutable configuration : IConfigurationRoot = null
member this.Configuration
with get () = configuration
and private set (value) = configuration <- value
new(env : IHostingEnvironment) as this =
Startup()
then
let builder =
ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional = false, reloadOnChange = true)
.AddJsonFile(sprintf "appsettings.%s.json" env.EnvironmentName, optional = true)
.AddEnvironmentVariables()
this.Configuration <- builder.Build()
// This method gets called by the runtime. Use this method to add services to the container.
member this.ConfigureServices(services : IServiceCollection) =
services.AddLogging() |> ignore
services.AddSwaggerGen(fun x ->
x.SwaggerDoc("v1", new Info(Title = "SimpleCms", Version = "v1")) |> ignore)
|> ignore
services.AddMvcCore() |> ignore
services.AddMvc() |> ignore
let container =
new Container(fun opt ->
opt.Scan(fun x ->
x.AssemblyContainingType(typeof<Startup>)
x.Assembly("Dal")
x.Assembly("Logic")
x.WithDefaultConventions() |> ignore)
opt.For<LiteDatabase>().Use(new LiteDatabase("Filename=database.db")) |> ignore
opt.Populate(services) |> ignore)
container.GetInstance<IServiceProvider>()
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
member this.Configure(app : IApplicationBuilder, env : IHostingEnvironment) =
app.UseSwagger() |> ignore
app.UseSwaggerUI(fun x ->
x.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1") |> ignore) |> ignore
app.UseMvc(fun x ->
x.MapRoute("default", "{controller=Home}/{action=Index}") |> ignore) |> ignore
HomeController.fs
namespace SimpleCms.Controllers
open Microsoft.AspNetCore.Mvc
[<Route("")>]
type HomeController() =
inherit Controller()
[<Route("")>]
[<HttpGet>]
member this.Index() =
Ok("Hello World!")
The complete code
Repo URL
Your problem is the IPostController interface. Remove that and Swagger will pick up the PostController.
For example:
[<Route("api/[controller]")>]
[<ApiController>]
type PostController(logic : IPostLogic) =
inherit Controller()
member this.logic = logic
[<Route("")>]
[<HttpGet>]
member this.GetAll() =
this.logic.GetAll()
//etc
Which makes Swagger show this:
Side Note: For ASP.NET Core 2.1 and above, you shouldn't be specifying the version of the Microsoft.AspNetCore.All package, your fsproj file should contain this instead:
<PackageReference Include="Microsoft.AspNetCore.All" />
Which in turn means you should be using version 2.1.1 of Microsoft.Extensions.Logging.Debug.
Finally, you are using a really old version of Swashbuckle.AspNetCore. I suggest you upgrade that too.
I'm working through the Embedding F# Interactive example from here but like this post, I'm having an issue with the following line throwing an exception:
let fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream)
The exception thrown is:
"System.Exception: 'Error creating evaluation session: StopProcessingExn None'"
My project is being run from VS2017 Enterprise, setup as a simple F# console app, with the Target Framework as .NET Core 2.0. The version of FSharp.Compiler.Service downloaded from nuget is 17.0.1 and FSharp.Core is 4.2.0.
The Program.Fs file code I'm running is here (a direct port of the example):
open System
open System.IO
open System.Text
open Microsoft.FSharp.Compiler.Interactive.Shell
[<EntryPoint>]
let main argv =
let sbOut = new StringBuilder()
let sbErr = new StringBuilder()
let inStream = new StringReader("")
let outStream = new StringWriter(sbOut)
let errStream = new StringWriter(sbErr)
// Build command line arguments & start FSI session
let argv = [| "C:\\Program Files (x86)\\Microsoft SDKs\\F#\\4.1\\Framework\\v4.0\\fsi.exe" |]
let allArgs = Array.append argv [|"--noninteractive"|]
let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
let fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream)
/// Evaluate expression & return the result
let evalExpression text =
match fsiSession.EvalExpression(text) with
| Some value -> printfn "%A" value.ReflectionValue
| None -> printfn "Got no result!"
evalExpression "42+1" // prints '43'
Console.ReadLine() |> ignore
0 // return integer
I already tried to add the files FSharp.Core.optdata and FSharp.Core.sigdata in my bin folder (bin\Debug\netcoreapp2.0) as mentioned here and here, but without success. By the way, my bin folder does not contain the file FSharp.Core.dll.
I also tried to publish my app and add the .optdata and .sigdata files manually in the publish folder, but without success either.
Any thoughts would be appreciated.
my goal is to simply output a javascript file containing my translated F# library. Nothing more.
I have an empty solution to which I added two F# projects. One is a library called WSLib with a single file:
namespace WSLib
[<ReflectedDefinition>]
type Class1() =
member this.X = "F#"
[<ReflectedDefinition>]
module Foo =
let bar = 34
The other project is a console app and references the WebSharper and WebSharper.Compiler NuGet packages. It has a single file. I copied the first half of the code from http://www.fssnip.net/snippet/rP.
module Program
open Microsoft.FSharp.Quotations
open WebSharper
type AR = IntelliFactory.Core.AssemblyResolution.AssemblyResolver
module FE = WebSharper.Compiler.FrontEnd
let compile (expr: Expr) : string option =
let loader = FE.Loader.Create (AR.Create()) (eprintfn "%O")
let options =
{ FE.Options.Default with
References =
List.map loader.LoadFile [
// These contain the JavaScript implementation for most of the standard library
"WebSharper.Main.dll"
"WebSharper.Collections.dll"
"WebSharper.Control.dll"
"WSLib.dll"
// Add any other assemblies used in the quotation...
] }
let compiler = FE.Prepare options (sprintf "%A" >> System.Diagnostics.Debug.WriteLine)
compiler.Compile expr
|> Option.map (fun e -> e.ReadableJavaScript)
[<JavaScript>]
let main() =
let a = WSLib.Class1().X
let b = WSLib.Foo.bar
(a,b)
let code =
match (compile <# main() #>) with
|None -> failwith "parse failed"
|Some x -> x
open System.IO
let filePath = Path.Combine(System.Environment.CurrentDirectory, "index.js")
File.WriteAllText(filePath, code)
I get a couple of errors:
{Location = {ReadableLocation = "main";
SourceLocation = null;};
Priority = Error;
Text = "Failed to translate property access: X [WSLib.Class1].";}
{Location = {ReadableLocation = "main";
SourceLocation = null;};
Priority = Error;
Text = "Failed to translate property access: bar [WSLib.Foo].";}
What do I need to do to get the websharper compiler working with different projects? I get the same error if I include the WebSharper package on WSLib and replace ReflectedDefinition with JavaScript.
What happens here is that adding WSLib.dll to the compiler references will only make it look for WebSharper metadata in that assembly, if there is any; but WSLib needs to be WebSharper-compiled already. For this to happen, you need to reference WebSharper in WSLib (as you did) and add the following property to the project file:
<WebSharperProject>Library</WebSharperProject>
to instruct WebSharper that it does have to compile this assembly.
I am getting an "Insufficient Permissions" exception from BigQuery when trying to list the datasets in my project (via service.Datasets.List). What do I have to do to grant this permission? Full F# source code:
open System
open System.IO
open System.Threading
open Google.Apis.Auth.OAuth2
open Google.Apis.Bigquery.v2
open Google.Apis.Bigquery.v2.Data
open Google.Apis.Services
let private service =
let credential =
let secrets =
use stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read)
GoogleClientSecrets.Load(stream).Secrets
let task =
GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
[| BigqueryService.Scope.Bigquery |],
"user",
CancellationToken.None)
printfn "Authenticating"
task
|> Async.AwaitTask
|> Async.RunSynchronously
let initializer = new BaseClientService.Initializer(HttpClientInitializer = credential)
new BigqueryService(initializer)
[<EntryPoint>]
let main argv =
let projectId = "{MyProjectId}"
let list = service.Datasets.List(projectId).Execute()
for dataset in list.Datasets do
printfn "%A" dataset.FriendlyName
0
It turns out that I was missing a key line of boilerplate code:
GoogleWebAuthorizationBroker.Folder <- "Tasks.Auth.Store";
I don't really understand what this does (the documentation is woefully sparse), but adding this line solved the problem.
I am working with FluentCassandra in F# and attempting to convert a string to a UTF8Type in order to use the ExecuteNonQuery method. Has anyone been successful doing this?
Thanks,
Tom
Thank you Jack P. and Daniel for pointing me in the right direction.
To provide more examples so others can benefit, I am writing a wrapper on top of FluentCassandra in F# to make CRUD functionality much simpler by utilizing the succinctness of F#. I am using Nick Berardi's code as an example for this wrapper:
https://github.com/fluentcassandra/fluentcassandra/blob/master/test/FluentCassandra.Sandbox/Program.cs
For example, if you want to check if a keyspace exists, simply calling the KeySpaceExists(keyspaceName) would allow for checking if a keyspace exists, using CreateKeyspace(keyspaceName) would allow for creation of a keyspace, etc. An example of the library I am creating is here:
namespace Test
open System
open System.Collections.Generic
open System.Configuration
open System.Linq
open System.Text
open System.Windows
open FluentCassandra
open FluentCassandra.Connections
open FluentCassandra.Types
open FluentCassandra.Linq
module Cassandra =
let GetAppSettings (key : string) = ConfigurationManager.AppSettings.Item(key)
let KeyspaceExists keyspaceName =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let keyspaceNameExists = db.KeyspaceExists(keyspaceName)
db.Dispose()
keyspaceNameExists
let CreateKeyspace keyspaceName =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
if KeyspaceExists(keyspaceName)=false then keyspace.TryCreateSelf()
db.Dispose()
let DropKeyspace (keyspaceName : string ) =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
match db.KeyspaceExists(keyspaceName)=true with
// value has "ignore" to ignore the string returned from FluentCassandra
| true -> db.DropKeyspace(keyspaceName) |> ignore
| _ -> ()
db.Dispose()
let ColumnFamilyExists (keyspaceName, columnFamilyName : string) =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
let columnFamilyNameExists = db.ColumnFamilyExists(columnFamilyName)
db.Dispose()
columnFamilyNameExists
let CreateColumnFamily (keyspaceName, columnFamilyName: string) =
if ColumnFamilyExists(keyspaceName,columnFamilyName)=false then
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
if ColumnFamilyExists(keyspaceName,columnFamilyName)=false then
keyspace.TryCreateColumnFamily(new CassandraColumnFamilySchema(FamilyName = columnFamilyName, KeyValueType = CassandraType.AsciiType, ColumnNameType = CassandraType.IntegerType, DefaultColumnValueType = CassandraType.UTF8Type))
let ExecuteNonQuery(keyspaceName, query: string) =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
let queryUTF8 = FluentCassandra.Types.UTF8Type.op_Implicit query
try
db.ExecuteNonQuery(queryUTF8)
true
with
| _ -> false
This library allows for very easy one line commands to utilize the FluentCassandra functionality. Of course this is just the start and I plan on amending the above library further.
open System
open System.Linq
open System.Collections.Generic
open System.Configuration
open FluentCassandra.Connections
open FluentCassandra.Types
open FluentCassandra.Linq
open Test.Cassandra
[<EntryPoint>]
let main argv =
CreateKeyspace("test1")
printfn "%s" (ColumnFamilyExists("test1", "table1").ToString())
printfn "%s" (KeyspaceExists("test1").ToString())
CreateColumnFamily("test1","table1")
printfn "%s" (ColumnFamilyExists("test1", "table1").ToString())
let result = ExecuteNonQuery("test1", "CREATE TABLE table2 (id bigint primary key, name varchar)")
printfn "%s" (result.ToString())
let wait = System.Console.ReadLine()
0
Specifically with converting the query string to a UTF8Type, Daniel's approach of utilizing UTF8Type.op_Implicit str worked. You can see how I applied it in the ExecuteNonQuery function above. Thanks again for your help!