0条留言

显示圣诞礼物的照片

因此,至少可以说,由于大流行,今年的圣诞节很有趣。遵循COVID-19准则,今年我们可以交换我们的秘密圣诞老人礼物。只有一个问题。通常,我们会分配一些纸质彩票来分配秘密圣诞老人。我们倾向于在同一天晚上这样做。但是,随着COVID-19的开展,今年就有了FaceTime以及类似的活动。 FaceTime很棒,但在彩票方面却不是很好,尤其是因为没人知道谁是谁。

那么,在没有人知道选谁的情况下,成功完成《 Secret Santa 2021》该怎么办?为什么不自己做,爆发 Visual Studio程式码 并编写一个小应用程序来为我们做这件事。因此,我将该过程分为以下几个步骤:

  • 读取输入
  • 随机分配
  • 发送短信

免责声明: 使用Twilio没有得到任何补偿。我这样做是因为这似乎是一个有趣的项目,而SMS似乎是所有参与人员最友好的方法。

现在,为了阅读输入,我使用了或多或少的标准CSV文件格式,如下所示:

Person1;+15551234
Person2;+15551235

我认为这将带来一定的灵活性,并允许我共享代码而不共享任何私人联系信息。 ðŸ〜‰

Reading the input can be done with a few helpers 从 the .Net Framework living in the System and System.IO namespace.

type Person = { Name : string; MobileNumber : string }

let parseToPerson (inputString:string) =
    let csvItems = inputString.Split(";")
    {Name = csvItems.[0]; MobileNumber = csvItems.[1]}

现在,在分配秘密圣诞老人的算法旁边。在我们家里,规则很简单。每个人都是别人的秘密圣诞老人,没有人是他或她自己的秘密圣诞老人。可以通过对列表使用随机数进行排序,然后将其压缩为CSV文件的顺序来实现随机分配:

let random = Random()
let rec assignSecretSantas (people : Person seq) =
    let secretSantasAssignments =
        people
        |> Seq.sortBy(fun x -> random.Next())
    
    let assignments = 
        secretSantasAssignments
        |> Seq.zip people
        |> Seq.toList
    
    if assignments |> Seq.exists (fun a -> fst a = snd a) then
        assignSecretSantas people
    else
        assignments

最后,检查分配是否没有人分配给自己。如果存在非法分配,则该方法将递归执行。

现在到对我来说新的部分。通过代码发送短信。我听说过 特威里奥 之前,但从未有机会试用他们的服务。设置帐户是您的工厂运行创建帐户方案。他们甚至为您提供了几美元来试用他们的教程。因此,有了我的试用帐户和手头的钱,我实现了SMS部分。

第一步是添加 NuGet包。开发此代码时,我使用了F#脚本文件。我为什么要提这件事,因为 F# 5 在脚本中添加/使用NuGet包就像在脚本文件顶部添加以下几行一样简单:

#r "nuget: 特威里奥"

open 特威里奥
open 特威里奥.Rest.Api.V2010.Account

But in the final version, I opted for a console app, which uses the standard dotnet add package 特威里奥 to add the package to your project.

在能够发送消息之前,需要初始化客户端。

let initTwilio =
    let accountSid = configuration.GetSection("特威里奥").["Sid"]
    let authToken = configuration.GetSection("特威里奥").["AuthToken"]
    特威里奥Client.Init(accountSid, authToken)

我必须说 docs 来自Twilio的帮助对您有很大帮助。如果您正在寻找更高级的方案,请务必将它们签出。

初始化客户端后,我编写了代码来发送消息:

let secretSantaPhoneNumber = "+15551236"
let secretSantaMessage = "Testmessage"
MessageResource.Create(
    特威里奥.Types.PhoneNumber(secretSantaPhoneNumber), 
    body=secretSantaMessage, 
    从=Twilio.Types.PhoneNumber("特威里奥-Sender-Phone-Number"))

发送消息比最初预期的要复杂一些。相比之下,一切都与我在注册时必须提供的电话号码配合使用。当尝试向家庭中的其他人发送测试消息时,我收到了错误消息,以使用以前在Twilio的试用版中注册的号码。解决方法是使用付费帐户-但此后,我可以实施Secret Santas的发送:

let rec informSecretSantas dryRun (santas: (Person * Person) list) =
    match santas with
    | [] -> ()
    | head::tail ->
        let santaName = (fst head).Name
        let giftReceiver = (snd head).Name
        let phoneNumber = (fst head).MobileNumber
        let body = $"的 secret santa message"
        if dryRun then
            printfn "%s" body
            printfn "Sending to %s" phoneNumber
        else
            let msg = MessageResource.Create(Twilio.Types.PhoneNumber(phoneNumber), body=body, 从=Twilio.Types.PhoneNumber("特威里奥-Phone-Number"))
            printfn "%A" msg.Status
        informSecretSantas dryRun tail

随着2021年圣诞节的到来,人们得到了拯救。现在这是最好的选择吗?嗯,那里有很多服务-有些甚至支持SMS,而且不需要花钱。但是其中之一会带来什么乐趣呢? 🙃

对我来说,使用F#脚本文件划分“应用程序”的不同部分的经验非常好,而且非常直观。在F#中运行代码 替换 / FSI 允许快速迭代和尝试。一个额外的好处是使用 离子化物 -使用VS Code开发F#时要安装的插件。 离子化物刚刚进行了更新,以支持F#5.0。 🥳

哦,还有另一个建议,如果您按住按钮以执行FSI中的代码,它将运行多次。询问我的家人在第一次“测试”运行中收到一条秘密圣诞老人短信后收到了四个秘密短信后如何知道。 🙈

您可以在上找到整个代码 的GitHub.


标题照片作者: 露西·丽兹(Lucie Liz)像素

0条留言

00_Title

这篇博客文章是 F#日历2020。非常感谢 谢尔盖 在今年组织起来的时候,一定要在阅读这篇博客之后阅读其他博客帖子。

极好框架基于模型视图更新(MVU)模式,提供了编写功能性第一移动(和桌面)客户端的方法。如果您不熟悉Fabulous,建议您检查一下 发布 通过 提姆 (Fabulous的维护者)或官员 docs.

后端实现为已启用SignalR的ASP.NET Core Web应用程序。你可以看看如何 这里.

为了展示如何将SignalR与Fabulous结合使用,我们将实现一个聊天应用程序。而且我觉得这叫“很棒的聊天”-看看用这个框架做很棒的事情有多么容易?因此,让我们忽略以下问题:“这个世界真的需要另一个聊天应用程序吗?!”在我们的脑海中,让我们看看我们将在此应用程序中实现什么,以突出显示如何在功能齐全的第一个移动应用程序中使用SignalR。

假设我们有以下要求:

  • 用户应通过其姓名来识别自己。
  • 用户应该能够发送消息。
  • 该应用程序处于活动状态时,用户应该能够阅读消息。

换句话说,两个操作完全由UI驱动,用户输入他的用户名,然后进入聊天。用户键入一条消息,然后按一下按钮将其发送。但是,当涉及到能够阅读消息时,该事件将从后台触发。此外,SignalR连接通常建立一次,然后用于会话的其余部分。因此,让我们在应用中创建一个模块,其中将包含与SignalR交互有关的所有操作。

module SignalR =
    let connectToServer =
        // connect to SignalR service

    let startListeningToChatMessages (connection: HubConnection) dispatch =
        // receive messages

    let sendMessage (connection: HubConnection) (message: ChatMessage) =
        // send message

用户提供其姓名后,我们将连接到该服务。不是因为它本身是必需的。但是,如果稍后我们决定添加一些适当的身份验证,则不会改变应用程序的流程。事不宜迟,让我们实现登录视图。

该图显示了登录视图

我们用于此部分的视图如下所述。

let view model dispatch =
    View.ContentPage
        (title= "登录",
         content =
             View.StackLayout
                 (verticalOptions = LayoutOptions.Center,
                  horizontalOptions = LayoutOptions.Center,
                  children =
                      [ View.Label(text = "Please enter your Username")
                        View.Entry
                            (text = model.Username,
                             maxLength = 15,
                             placeholder = "Please enter your username",
                             completed = (fun _ -> (loginUser dispatch)),
                             textChanged = fun e -> dispatch (UsernameChanged e.NewTextValue))
                        View.Button(text = "登录", command = fun _ -> (loginUser dispatch))
                        ]))

一旦用户点击 登录 按钮,我们要建立SignalR连接。

let connectToServer =
    let connection =
        HubConnectionBuilder()
            .WithUrl(Config.SignalRUrl)
            .WithAutomaticReconnect()
            .Build()

    async {
        do! connection.StartAsync() |> Async.AwaitTask
        return connection
    }

Since we will want to hold on to the connection, we will invoke a Command which will be evaluated in the Update method.

let update msg (model: Model) =
    match msg with
    // ... other message handling
    | Connected connection ->
        { model with
              SignalRConnection = Some connection
              AppState = Ready },
        Cmd.none
    // ... more message handling

现在,在用户连接到聊天之后,我们将显示聊天视图。该视图允许用户键入消息,发送消息并阅读连接到该服务的其他用户的响应或问题。

该图显示了实际的聊天视图

Let's start with writing messages. Similar as with the username we again have an Entry and a button for sending the messages. Once the user sends a message, we invoke SendAsync in our SignalR module:

let sendMessage (connection: HubConnection) (message: ChatMessage) =
    async {
        let jsonMessage = JsonSerializer.Serialize(message)

        do! connection.SendAsync("SendMessage", jsonMessage)
            |> Async.AwaitTask
    }

So thus far, we have connected to the SignalR service, and we can send messages to the server. But we are still missing one essential part, and that is how we can receive messages 从 the backend service. What we need to do is register a listener to a specific SignalR-method (called NewMessage). We can implement our function as follows:

let startListeningToChatMessages (connection: HubConnection) dispatch =
    let handleReceivedMessage (msg: string) =
        printfn "Received message: %s" msg
        dispatch (Msg.MessageReceived(JsonSerializer.Deserialize<ChatMessage>(msg)))
        ()

    connection.On<string>("NewMessage", handleReceivedMessage)

现在,在处理程序方法中,每当从SignalR服务收到新消息时,我们将调度一个命令。因此,让我们从一开始就扩展登录功能,不仅可以创建连接,还可以注册接收者。

let loginUser dispatch =
    async {
        let! connection = SignalR.connectToServer
        dispatch (Msg.Connected connection)

        SignalR.startListeningToChatMessages connection dispatch
        |> ignore

        dispatch (Msg.LoggedIn)
    }
    |> Async.StartImmediate

We pass in the dispatcher 从 the view method when registering. This allows us to dispatch a command, i.e. invoke the update method and add the new message to our list of chat messages:

let update msg (model: Model) =
    match msg with
    // ... other message handling
    | MessageReceived chatMessage ->
        { model with
              Messages = chatMessage :: model.Messages },
        Cmd.none
    // ... more message handling

这样,用户将能够与当前正在使用聊天程序的任何人发送和接收聊天消息。

结论

这就是我们可以在Fabulous应用程序中使用SignalR并创建Fabulous Chat应用程序的方式。也许我们仍然需要仔细研究设计和安全性,才能真正赢得这个声誉。

您还看到了如何处理不是源自用户输入而是在后台发生的事件。每当您使用一些在后台执行其他代码的代码时,都可以使用此技术。

您可以在上找到该应用程序的完整示例 的GitHub.

标题照片作者: 泰勒·拉斯托维奇(Tyler Lastovich)像素

0条留言

标题图像显示转速计

构建目标不仅可以很好地区分Debug版本和Release版本。您还可以将它们用于定位应用程序的不同环境或配置。现在,我一直喜欢为用户提供最好的应用程序性能的想法-换句话说;我想启用 虚拟机

不幸的是,当使用 Visual Studio 2019 (在编写16.5.4时)禁用了启用LLVM的选项。

在Visual Studio中显示禁用的LLVM选项-和尖叫的表情符号

问题 is under consideration 通过 the team, and for the time being, there is no way to enable 虚拟机 via the UI Wizard in Visual Studio. Now one way to solve this is to clone your solution on to a machine running macOS and then enabling it in Visual Studio for Mac. But under Windows, the only option is to open up the csproj file and enable 虚拟机 manually:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Stuff -->
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Gnabber|iPhone'">
    <OutputPath>bin\iPhone\Gnabber\</OutputPath>
    <DefineConstants>__IOS__;__MOBILE__;__UNIFIED__;</DefineConstants>
    <Optimize>true</Optimize>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <UseVSHostingProcess>false</UseVSHostingProcess>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
    <!-- Add the line bellow -->
    <MtouchUseLlvm>true</MtouchUseLlvm>
  </PropertyGroup>
  <!-- More Stuff -->
</Project>

谢谢, 维克多·加西亚·阿普里亚(Victor Garcia Aprea) 向我指出这一点,我希望这对任何遇到相同问题的人都能有所帮助。如果要检查

You can find a small sample Project with a custom build target Gnabber up on 的GitHub.

高温超导

2条留言

ShellLoginApp

自发布以来 Xamarin表格4.5,壳牌现在支持 模态导航。由于我排名最高的博客文章之一是 如何使用Xamarin Forms创建登录页面。我认为是时候重新讨论该主题,并看看如何使用命令行管理程序实现登录页面。

那么登录页面有何特别之处?好吧,为了说明这一点,用户只能在输入正确的登录名后才能退出它。此外,用户应该不能离开登录页面,即,导航回到前一页。最后,一旦成功通过身份验证,用户在导航时就不会找到登录页面。

因此,让我们看看如何捕获页面上的用户,然后确保使用Shell时该页面不再位于导航堆栈中。因此,让我们继续进行可能的登录体验的UI流程。我们的应用程序具有以下屏幕流程:

PageFlow

All of our pages have to be registered with the Shell. Note that the first ContentPage in the Shell.xaml file is the one getting displayed after start-up. So our Shell is structured accordingly:

<!-- Loading/Start Page -->
<ShellItem Route="loading">
    <ShellContent ContentTemplate="{DataTemplate local:LoadingPage}" />
</ShellItem>

<!-- 登录 and Registration Page -->
<ShellContent Route="login"
              ContentTemplate="{DataTemplate local:LoginPage}">
</ShellContent>

<!-- Main Page -->
<!-- We will get to this later -->

我们的加载屏幕主要模拟检查用户是否具有有效的凭证。如果应用不是业务逻辑方面的假货。它可能正在使用基于令牌的流;在此可以检查应用程序是否仍然具有有效的令牌并且可以直接进入主屏幕,或者用户必须登录。

LoadingPage

漂亮的加载动画,对不对? ðŸ〜‰这是视图模型中的苗条逻辑:

// Called 通过 the views OnAppearing method
public async void Init()
{
    var isAuthenticated = await this.identityService.VerifyRegistration();
    if (isAuthenticated)
    {
        await this.routingService.NavigateTo("///main");
    }
    else
    {
        await this.routingService.NavigateTo("///login");
    }
}

注意,我们只告诉命令行管理程序导航到登录屏幕。使用Shell时,可以在目标页面上定义导航的类型。因此,对于登录页面,我们将其设置如下:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             ...
             Shell.PresentationMode="ModalAnimated"
             ...>
    ...
</ContentPage>

而且无论何时进行模态导航,总是最好在目标页面后面的代码中覆盖后退按钮导航,如下所示:

protected override bool OnBackButtonPressed()
{
    return true;
}

为什么?好吧,因为否则所有您的Android用户都可以简单地按一下Android后退按钮,然后就从您精心设计的登录过程中退出。现在,让我们添加一个注册页面。在这里,我们定义了标准的推送导航:

?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             ...
             Shell.PresentationMode="Animated"
             ...>
    <Shell.BackButtonBehavior>
        <BackButtonBehavior Command="{Binding ExecuteBack}"
                            TextOverride="Back" />
    </Shell.BackButtonBehavior>
    ...
</ContentPage>

Before we can navigate to the registration page, we have to register the page with the Shell. We can do this in the code behind of the AppShell.xaml.cs file:

Routing.RegisterRoute("registration", typeof(RegistrationPage));

现在,我们可以通过以下方式从登录页面导航到注册页面:

Shell.Current.GoToAsync("//login/registration");

的 code above implements the default navigation behaviour, i.e. a back button on the top left 要么 通过 using the back button on Android. So as soon as the user has logged in, we display the main page of the app. Which is again defined in the AppShell.xaml as follows:

<!-- Main Page -->
<FlyoutItem Route="main"
            FlyoutDisplayOptions="AsMultipleItems" IsTabStop="False">
    <ShellContent Route="home"
                  IsTabStop="False"
                  ContentTemplate="{DataTemplate local:MainPage}"
                  Title="Home" />
</FlyoutItem>

由于它不是登录页面的一部分,因此命令行管理程序会自动从导航堆栈中删除登录页面。

So now that we are in the app. Sometimes you will want to present the user with the option to logout. Shell gives us an easy way to define a flyout menu. In Non-Shell apps, this is usually done with the MasterDetailPage. So a nice place to add our logout is as a new entry in the flyout right? Instead of defining a flyout item, it will be better to use a menu item. In general think of flyout items as areas of your app and menu items as actions in the menu. 的 logout is less an area and more an action. So for our logout, we define a menu item like this:

<MenuItem Text="Logout"
          Command="{Binding ExecuteLogout}" />

的 Command and Binding context get defined in the code behind, i.e. the App.xaml.cs:

public AppShell()
{
    InitializeComponent();
	// ... routes and stuff
    BindingContext = this;
}

public ICommand ExecuteLogout => new Command(async () => await GoToAsync("main/login"));

根据您的应用程序的要求,您可能要强制用户在不同时间登录。这可能是在启动,恢复过程中或在下雨的星期二。无论有什么要求,您都可以简单地调用与上述类似的导航,然后用户将被导航到登录屏幕。成功登录后,用户将返回到打开登录页面的页面。

结论

With the new 模态导航 mode of Shell, the implementation of a 登录 screen can be done quite nicely and with a lot less worrying about the state of the navigation stack. One of the main differences between using a NavigationPage compared to the Shell is how you configure the different parts within the AppShell.xaml. 的 Xamarin team has mentioned that more goodness should come to Shell, so be sure to stay tuned and watch out for news on the Xamarin博客 关于新的更新和功能。

您可以在上找到整个样本 的GitHub。并签出 大卫·奥蒂诺的示例启发了这篇文章。

高温超导

0条留言

Xamarin的图标&手机中的自动映射器

您可能已经听说过 自动贴图,该库可帮助您将“属性表单”对象复制到由编写的另一个 吉米·博加德(Jimmy Bogard)。每当您创建更大的Xamarin Forms应用程序时,通常都会得到代表相似数据但针对应用程序不同区域的不同模型。例如,您将从后端获得一个极简的数据传输对象(DTO),您可以将其复制到另一个应用程序内部模型中,或直接复制到表示视图中显示的数据的视图模型中。这就是AutoMapper可以帮助您并阻止您编写所有复制代码的地方。

我必须承认-一开始它总是有点像过分杀伤。在项目结束时,我仍然很高兴在开始时就做出了决定。因此,让我们看看如何开始。

进行设定

设置很简单,实际上添加了 NuGet包 of 自动贴图 to your Xamarin Forms project. That is until you start compiling for iOS, then it will all blow up due to some reflection 问题. Since iOS is compiled Ahead Of 提姆e (AOT), you can't do any runtime operations such as reflections. Now 自动贴图 does not use reflection when running on iOS. Still, due to some weird compilation thingy 问题 - I haven't understood the point in detail - the compiler ends up trying to add reflection which will not work on iOS. So to make things run under iOS, we have to add the following line to our iOS csproj file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  ...
  <ItemGroup>
    ...
    <PackageReference Include="System.Reflection.Emit" Version="4.7.0">
      <ExcludeAssets>all</ExcludeAssets>
      <IncludeAssets>none</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  ...
</Project>

的n there is still the Linker under iOS that tries to remove the System.Convert assembly. Which is required 通过 自动贴图. But luckily we can help the linker out 这里 通过 adding an XML file:

<linker>
  <assembly fullname="mscorlib">
    <type fullname="System.Convert" preserve="All" />
  </assembly>
</linker>

And setting the build property to LinkDescription:

显示Visual Studio属性窗格

配置和用法

好的,现在一切都已设置好,我们仍然必须告诉AutoMapper我们要将哪些对象从A复制到B并再次返回。在为这篇博客准备的小应用程序中,我们将把笔记DTO对象复制到它的View Model对应对象中。因此,要获取配置的AutoMapper实例,我们将在应用程序启动期间编写如下代码:

public static IMapper CreateMapper()
{
    var mapperConfiguration = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Note, NoteViewModel>();
        cfg.CreateMap<NoteViewModel, Note>();
    });

    return mapperConfiguration.CreateMapper();
}jjjjjjj

现在,我们可以通过如下调用AutoMapper实例将DTO转换为视图模型:

var viewModel = _mapper.Map<MainViewModel>(note);

But what I often end up doing is populating the View Model after creation 要么 when the Page/View it is being used in is getting created. For example, an Init method on the View Model might be invoked during the OnAppearing of the View. 的n we could call a service in the View Model which would return a DTO of that object. In this scenario, we will want to tell 自动贴图 to map the DTO directly on the View Model itself:

public async void Init()
{
    var note = (await _noteService.GetNotes()).First();
    _mapper.Map(note, this);
}

如果您有一个视图模型列表,即 集合视图 要么 列表显示,我们将使用 LINQ 与AutoMapper结合使用,可以从DTO快速转换为View Model:

Notes = (await _noteService.GetNotes())
            .Select(_mapper.Map<MainViewModel>)
            .ToList();

每当必须在应用程序中显示新的DTO时,就可以使用AutoMapper,您将添加新的配置,然后能够使用映射。因此,您需要的映射越多,使用AutoMapper的速度就越快。

但是,在我的项目中使用AutoMapper时,我真的很喜欢做一件事,那就是进行测试。

测试您的映射

“测试?你是认真的吗?” -是的,我实际上是。您可以使用一个简单的测试用例来检查您的配置:

[Fact]
public void AppBoostrapper_ValidateMapping_AssertCorrectness()
{
    var mapper = AppBootstrapper.CreateMapper();
    mapper.ConfigurationProvider.AssertConfigurationIsValid();
}

This test will tell you if 自动贴图 has all the information necessary to copy your data 从 one object to the other. So if we start adding Commands to the View Model, the mapping will fail with the information that it can not map the command into our DTO. And we obviously do not want to send an ICommand data field back to the server, so we ignore it:

cfg.CreateMap<Note, NoteViewModel>()
    .ForMember(n => n.ExecuteReset, opt => opt.Ignore())
    .ForMember(n => n.ExecuteStore, opt => opt.Ignore());
cfg.CreateMap<NoteViewModel, Note>();

But what when we add a data field WriterMood to the DTO and forget to add it to the View Model? Correct, the test will fail and inform us that we have forgotten to add the field.

自动贴图配置测试失败的屏幕截图

那项测试已将我从那么多被遗忘的数据字段中解救了出来-我想说的是它拯救了我的一个朋友... ðŸ〜 ...

往后看

我肯定会在以后的Xamarin Forms项目中使用AutoMapper,因为它们不仅方便复制属性,还可以共享DTO,内部模型和视图模型。但这也使我免于忘记为对象添加属性。

请务必查看官方 文件资料 因为这篇文章几乎没有涉及您可以使用AutoMapper配置的内容。

与往常一样,您可以在上找到完整的小样本应用程序 的GitHub.

高温超导