Lukáš Grolig
- mnoho let SW developer a architekt
- v poslední době se věnuji hlavně masivně škálovatelným systémům využívajícím ML
méně bugů v aplikaci:
Bolero je postaveno nad Blazorem a přidává mnoho funkcí navržených speciálně pro práci ve F#:
dotnet new blazorwasm
dotnet new blazorserver
dotnet new -i Bolero.Templates
dotnet new bolero-app -o HelloWorld
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
}
}
type Startup() =
member this.ConfigureServices(services: IServiceCollection) =
services.AddMvc() |> ignore
services.AddServerSideBlazor() |> ignore
services.AddBoleroHost(server = false) |> ignore
module Program =
[<EntryPoint>]
let Main args =
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run()
0
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
// hello.html
<div id="${Id}">Hello, ${Who}!</div>
// hello.fs
type Hello = Template<"hello.html">
let hello =
Hello()
.Id("hello")
.Who("world")
.Elt()
let myElement name =
div [] [
h1 [] [text "My app"]
p [] [textf "Hello %s and welcome to my app!" name]
]
public class CounterClass : BlazorComponent
{
public int CurrentCount { get; set; }
[Parameter]
protected string SubTitle { get; set; }
public void IncrementCount()
{
CurrentCount += 5;
}
}
<Counter SubTitle="Subtitle from Index (Home) page"/>
<button class="btn btn-primary" @onclick="UpdateHeading">
Update heading
</button>
@code {
private async Task UpdateHeading(MouseEventArgs e)
{
await ...
}
}
type Model = { firstName: string; lastName: string }
let initModel = { firstName = ""; lastName = "" }
type Message = SetFirstName of string | SetLastName of string
let update message model =
match message with
| SetFirstName n -> { model with firstName = n } // <-- kopie objektu se zmenou property
| SetLastName n -> { model with lastName = n }
let view model dispatch =
div [] [
viewInput model.firstName (fun n -> dispatch (SetFirstName n))
viewInput model.lastName (fun n -> dispatch (SetLastName n))
text (sprintf "Hello, %s %s!" model.firstName model.lastName)
]
builder.Services.AddScoped(sp =>
new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
Napřed konfigurace HTTP klienta
private class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
Potom vytvoření modelu
@using System.Net.Http
@inject HttpClient Http
@code {
private TodoItem[] todoItems;
protected override async Task OnInitializedAsync() =>
todoItems = await Http.GetFromJsonAsync<TodoItem[]>(
"api/TodoItems");
}
A samotný request
type GitHub = JsonProvider<"https://api.github.com/.../issues">
let topRecentlyUpdatedIssues =
GitHub.GetSamples()
open Bolero.Remoting
type MyService =
{
getEntry : string -> Async<string option> // Served at /myService/getEntry
setEntry : string * string -> Async<unit> // Served at /myService/setEntry
deleteEntry : string -> Async<unit> // Served at /myService/deleteEntry
}
interface IRemoteService with member this.BasePath = "/myService"
let myService = this.Remote<MyService>()
myService.getEntry key
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject IProductRepository ProductRepository
@inject ILogger<ProductDetails> Logger
@if (details != null)
{
<h1>@details.ProductName</h1>
<p>@details.Description</p>
}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}
@code {
private ProductDetails details;
private bool loadFailed;
[Parameter]
public int ProductId { get; set; }
protected override async Task OnParametersSetAsync()
{
try
{
loadFailed = false;
details = await ProductRepository.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
}
}
}
type Model =
{ latestRetrievedEntry : string * string
latestError : exn option }
type Message =
// Trigger a `getEntry` request
| GetEntry of key: string
// Received response of a `getEntry` request
| GotEntry of key: string * value: string
// A request threw an error
| Error of exn
let update myService message model =
match message with
| GetEntry key ->
model,
Cmd.ofAsync
myService.getEntry key // async call and argument
(fun value -> GotEntry(key, value)) // message to dispatch on response
Error // message to dispatch on error
| GotEntry(key, value) ->
{ model with latestRetrievedEntry = (key, value) }, []
| Error exn ->
{ model with latestError = Some exn }, []
Například Telerik UI for Blazor
varianta server side:
varianta client side:
// Create a storage account with a container
let myStorageAccount = storageAccount {
name "myTestStorage"
add_public_container "myContainer"
}
// Create a web app with application insights that's connected to the storage account.
let myWebApp = webApp {
name "myTestWebApp"
setting "storageKey" myStorageAccount.Key
}
// Create an ARM template
let deployment = arm {
location Location.NorthEurope
add_resources [
myStorageAccount
myWebApp
]
}
// Deploy it to Azure!
deployment
|> Writer.quickDeploy "myResourceGroup" Deploy.NoParameters
Webová aplikace chovající se jako desktopová appka
Webová aplikace chovající se jako desktopová appka
Je třeba přidat app manifest (manifest.json)
a ideálně vyřešit podporu pro offline práci (service workers a storage)
Existuje F#
Doménové modelování přes ADT
Nepotřebujete design patterny
Nádstavba nad Blazorem je Bolero
Pro state management se využívá Elmish