Skip to content

Instantly share code, notes, and snippets.

@Thorium
Created July 16, 2015 13:29
Show Gist options
  • Save Thorium/1bc4e3c925d17850136a to your computer and use it in GitHub Desktop.
Save Thorium/1bc4e3c925d17850136a to your computer and use it in GitHub Desktop.
Simple CRUD -application. What you will need: 1) a Mac, Windows or Linux computer with FSharp (F#) installed. 2) MariaDB or MySQL installed. 3) Paket package manager. Run "paket.exe install" or "mono paket.exe install". 4) If on Mono, you have to add Program.fs to a project to compile it (or SignalR won't work).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My CRUD-Form</title>
<script type='text/javascript' src='paket-files/ajax.aspnetcdn.com/jquery.min.js'></script>
<script type='text/javascript' src='paket-files/SignalR/bower-signalr/jquery.signalR.js'></script>
<script type='text/javascript' src='paket-files/underscorejs.org/underscore-min.js'></script>
<script type='text/javascript' src='/signalr/hubs'></script> <!-- Generated SignalR Hub! -->
<script>
//UrlParameters: /index.html?customerId=1
// In production, replace this with client side router like Crossroads.js:
var customerId = location.search.replace(/^.*?\=/, '');
$(document).ready(function () {
$.connection.hub.url = "/signalr";
myHub = $.connection.MyHub;
var connection = !myHub ? $.hubConnection() : $.connection.hub;
var conn = connection.start({ transport: 'longPolling' });
conn.done(function () {
function readForm() {
var nonbuttons = _.filter($(":input"), function (i) { return i.type != "button"; });
return _.map(nonbuttons, function (k) { return { Item1: k.id, Item2: k.value }; });
}
if (customerId == "") {
$("#updatebtn").css({ display: "none", visibility: "hidden" });
$("#deletebtn").css({ display: "none", visibility: "hidden" });
$("#createbtn").click(function () {
myHub.server.create(readForm()).done(
function(data){
alert("Created!");
var id = _.filter(data, function(i){return i.Item1=="Id";});
var idval = _.map(id, function(i){return i.Item2;});
document.location.href = "index.html?customerId=" + idval[0];
});
return false;
});
}else {
function setData(data) {
_.each(data, function (c) { $('#' + c.Item1).val(c.Item2); });
}
myHub.server.read(customerId).done(setData);
$("#updatebtn").click(function () {
myHub.server.update(customerId, readForm()).done(function (data) {
alert("Updated!"); setData(data);
});
return false;
});
$("#deletebtn").click(function () {
if(confirm("Are you sure?")){
myHub.server.delete(customerId).done(function (d) { alert("Deleted!"); document.location.href = "index.html"; });
}
return false;
});
}
});
});
</script>
<style>
.bgFrame {
background-color: #ffffff;
border: thin solid #000000;
padding: 30px;
margin: 30px;
width: 600px;
height: 200px;
vertical-align: middle;
text-align: center;
}
</style>
</head>
<body style="background-color: #688856">
<div class="bgFrame"><h2>Customer management</h2>
<form>
Customer login: <input type="text" id="Login" /><br />
Customer password: <input type="password" id="Password" /><br />
Customer name: <input type="text" id="Name" /><br />
Customer birthdate: <input type="text" id="BirthDate" /><br />
<input type="button" class = "button" id="createbtn" value="Create new" />
<input type="button" class = "button" id="updatebtn" value="Update current" />
<input type="button" class = "button" id="deletebtn" value="Delete current" />
</form>
</div>
</body>
</html>
// This shows NuGet references. You may also get paket.exe
// and run "paket install" or "mono paket.exe install" with Mono
source https://www.nuget.org/api/v2
// Server side, F# .NET:
nuget FSharp.Data
nuget Microsoft.AspNet.SignalR.Core
nuget Microsoft.AspNet.WebApi.OwinSelfHost
nuget Microsoft.Owin.StaticFiles
nuget Microsoft.Owin.Diagnostics
nuget Microsoft.Owin.Security
nuget MySql.Data
nuget SQLProvider
// Client side, Javascripts, could be refereced through paket-files folder...
github SignalR/bower-signalr jquery.signalR.js
http http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.4.min.js jquery.min.js
http http://underscorejs.org/underscore-min.js
// In MONO, won't work in interactive, you need to make a project and compile a dll.
#if INTERACTIVE
#r @"./packages/FSharp.Data/lib/net40/FSharp.Data.dll"
#r @"./packages/SQLProvider/lib/net40/FSharp.Data.SqlProvider.dll"
// OWIN and SignalR-packages:
#I @"./packages/Microsoft.AspNet.SignalR.Core/lib/net45"
#r @"./packages/Microsoft.AspNet.SignalR.Core/lib/net45/Microsoft.AspNet.SignalR.Core.dll"
#I @"./packages/Microsoft.Owin/lib/net45"
#r @"./packages/Microsoft.Owin/lib/net45/Microsoft.Owin.dll"
#I @"./packages/Microsoft.Owin.Security/lib/net45"
#r @"./packages/Microsoft.Owin.Security/lib/net45/Microsoft.Owin.Security.dll"
#r @"./packages/Microsoft.AspNet.WebApi.Core/lib/net45/System.Web.Http.dll"
#r @"./packages/Microsoft.AspNet.WebApi.Owin/lib/net45/System.Web.Http.Owin.dll"
#r @"./packages/Microsoft.Net.Http/lib/net40/System.Net.Http.dll"
#I @"./packages/Microsoft.Owin.Hosting/lib/net45"
#r @"./packages/Microsoft.Owin.Hosting/lib/net45/Microsoft.Owin.Hosting.dll"
#I @"./packages/Microsoft.Owin.Host.HttpListener/lib/net45"
#r @"./packages/Microsoft.Owin.Host.HttpListener/lib/net45/Microsoft.Owin.Host.HttpListener.dll"
#I @"./packages/Microsoft.Owin.StaticFiles/lib/net45"
#r @"./packages/Microsoft.Owin.StaticFiles/lib/net45/Microsoft.Owin.StaticFiles.dll"
#r @"./packages/Microsoft.Owin.FileSystems/lib/net45/Microsoft.Owin.FileSystems.dll"
#I @"./packages/Microsoft.Owin.Diagnostics/lib/net45/"
#r @"./packages/Microsoft.Owin.Diagnostics/lib/net45/Microsoft.Owin.Diagnostics.dll"
#r @"./packages/Microsoft.AspNet.WebApi.Client/lib/net45/System.Net.Http.Formatting.dll"
#I @"./packages/Newtonsoft.Json/lib/net45"
#r @"./packages/Newtonsoft.Json/lib/net45/Newtonsoft.Json.dll"
#I @"./packages/Owin/lib/net40"
#r @"./packages/Owin/lib/net40/Owin.dll"
#endif
open System
open FSharp.Data
open FSharp.Data.Sql
[<Literal>]
let mysqldatapath = __SOURCE_DIRECTORY__ + @"/packages/MySql.Data/lib/net45/"
type SqlConnection =
SqlDataProvider<
ConnectionString = @"server = localhost; database = mydatabase; uid = webuser; pwd = v3rySecret",
DatabaseVendor = Common.DatabaseProviderTypes.MYSQL,
IndividualsAmount=1000,
UseOptionTypes=true,
ResolutionPath=mysqldatapath>
//Runtime connection string, move to configuration file:
let cstr = @"server = localhost; database = mydatabase; uid = webuser; pwd = v3rySecret"
open Microsoft.AspNet.SignalR.Hubs
open System.Threading.Tasks
// -------- This is the SignalR-Hub for CRUD-operations (could be more)
[<HubName("MyHub")>]
type MyHub() = // as this =
inherit Microsoft.AspNet.SignalR.Hub()
let executeCrud itemId actionToEntity =
let context = SqlConnection.GetDataContext cstr
let entity =
query {
for u in context.``[mydatabase].[customer]`` do
where (u.Id = itemId)
head
}
entity |> actionToEntity
context.SubmitUpdates ()
entity.ColumnValues
let ``map data from form to database format`` (formData: seq<string*obj>) =
formData // Add missing fields
|> Seq.append [| "LastLogin", DateTime.Now |> box ;
// ...
|]
|> Seq.map (
fun (key, value) -> // Convert some fields
match key with
| "Password" ->
"PasswordHash",
value.ToString()
|> System.Text.Encoding.UTF8.GetBytes
|> System.Security.Cryptography.SHA384Managed.Create().ComputeHash
|> Convert.ToBase64String |> box
| "BirthDate" -> "BirthDate", value.ToString() |> DateTime.Parse |> box
| _ -> key,value)
member __.Create (data: seq<string*obj>) =
let context = SqlConnection.GetDataContext cstr
let entity = data
|> ``map data from form to database format``
|> context.``[mydatabase].[customer]``.Create
context.SubmitUpdates()
entity.ColumnValues
member __.Read itemId = executeCrud itemId (fun e -> ())
member __.Update itemId data = executeCrud itemId (fun e -> data |> ``map data from form to database format`` |> e.SetData)
member __.Delete itemId = executeCrud itemId (fun e -> e.Delete())
// -------- Just some infra code to setup a web-server:
let myHubConfig = Microsoft.AspNet.SignalR.HubConfiguration(EnableDetailedErrors = true, EnableJavaScriptProxies = true)
open System.Net.Http
open Microsoft.Owin.Diagnostics
open Owin
open System.Web.Http
open Microsoft.Owin
type MyWebStartup() =
member __.Configuration(ap:Owin.IAppBuilder) =
let fileServerOptions = Microsoft.Owin.StaticFiles.FileServerOptions()
fileServerOptions.DefaultFilesOptions.DefaultFileNames.Add "index.html"
fileServerOptions.FileSystem <- (Microsoft.Owin.FileSystems.PhysicalFileSystem (__SOURCE_DIRECTORY__ (* + "webroot/" *)))
ap.UseErrorPage(new ErrorPageOptions(ShowExceptionDetails = true))
|> fun app -> app.MapSignalR(myHubConfig)
|> fun app -> app.UseFileServer(fileServerOptions)
|> ignore
()
[<assembly: Microsoft.Owin.OwinStartup(typeof<MyWebStartup>)>]
do()
#if INTERACTIVE
#else
[<EntryPoint>]
#endif
let main args =
let url = "http://localhost:9090" // "http://*:8080"
let server = Microsoft.Owin.Hosting.WebApp.Start<MyWebStartup> url
Console.WriteLine ("Server started at: " + url)
Console.WriteLine "Press Enter to stop."
let key = Console.ReadLine()
server.Dispose()
0
#if INTERACTIVE
main [||] |> ignore
#endif
# execute like this:
# mysql -u root -p -e "source setupDatabase.sql"
# Create database and user:
CREATE DATABASE mydatabase;
CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'v3rySecret';
GRANT USAGE ON mydatabase.* TO 'webuser'@'localhost'WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO 'webuser'@'localhost'WITH GRANT OPTION;
USE mydatabase;
# table names: lower case for MySQL/MaridaDb compability (Win+Mac/Linux)
CREATE TABLE customer(
Id int auto_increment primary key NOT NULL,
Name nvarchar(255) NOT NULL,
Login nvarchar(255) NULL,
PasswordHash nvarchar(255) NULL,
BirthDate date NULL,
LastLogin datetime NULL);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment