WPF MVVM UI分離之《交互與數據分離》

来源:https://www.cnblogs.com/kybs0/archive/2018/05/18/9053683.html
-Advertisement-
Play Games

在我們使用WPF過程中,不可避免並且超級喜歡使用MVVM框架。 那麼,使用MVVM的出發點是視覺與業務邏輯分離,即UI與數據分離 諸如下麵的問題: 刪除操作,假如需要先執行一部分數據的處理,然後刪除界面列表中的子項,之後再執行其它數據的處理。請問此業務該放置於Xaml.cs文件,還是ViewMode ...


在我們使用WPF過程中,不可避免並且超級喜歡使用MVVM框架。

那麼,使用MVVM的出發點是視覺與業務邏輯分離,即UI與數據分離

諸如下麵的問題:

刪除操作,假如需要先執行一部分數據的處理,然後刪除界面列表中的子項,之後再執行其它數據的處理。請問此業務該放置於Xaml.cs文件,還是ViewModel中呢?

再如彈窗,提示框,設置列表的滾動等等。

此上一些操作,我們不應該把業務代碼直接挪到cs文件中,因為刪除操作絕大部分的代碼都是數據的處理。所以,數據的部分放置在ViewModel中,一些交互放在cs文件中,就是很合理及有必要了。

單元測試,UI與交互的那部分mock模擬有點難度,也沒必要去模擬。那麼,我們是應該把數據與交互拆開,減少之間的耦合性。這樣添加單元測試則更容易。

交互與數據分離 - 描述

首先MVVM,通過View與ViewModel的綁定,我們實現了UI與業務邏輯的分離。通俗一點,我們熟悉的Xaml與ViewModel文件中,代碼之間的隔離。在此不詳述~

而MVVM,不只是界面與邏輯,其實邏輯還可以拆分成交互與數據

即:Xaml 》Xaml.cs 》ViewModel

是的,按照上面的結構圖,我們分成三部分:

  • 界面 用於界面呈現 ---- 如頁面/控制項/樣式/模板等其它資源的初始化,動畫的觸發等。
  • 交互 用於與用戶確認的交互或者界面複雜邏輯的處理 ---- 如彈窗/提示框/複雜動畫的處理/設置列表的滾動等其它界面元素的視覺處理。
  • 數據 只是數據的處理 ---- 增刪改查導入導出保存等只針對數據的操作,界面狀態屬性的保存與觸發更改(綁定)。

交互與數據分離是怎樣的?比如刪除:

1. 界面刪除按鈕,綁定ViewModel中的DeleteCommand,當我們點擊刪除時,觸發DeleteCommand.Execute

2. ViewModel中,先執行數據狀態的判斷,然後執行交互通知ShowDeleteWaringAction,調用xaml.cs文件中的確認提示框

3. 在Xaml.cs中添加依賴屬性ShowDeleteWaring,綁定ViewModel中的ShowDeleteWaringAction.Progress。在屬性更改中,處理提示框確認邏輯。

4. ViewModel中,等待ShowDeleteWaring彈框完成後,繼續執行下麵的業務。

5. 還有類似上面步驟的刪除動畫。。。

 

交互與數據分離 - 實現

使用場景:在WPF框架下開發時,一種基於MVVM的UI分離方案

解決方案:在業務邏輯處理過程中,新建一個交互處理線程,通知界面完成交互處理,同時後臺邏輯保持同步等待。界面完成交互處理後,回調並執行後續的業務邏輯。

實現方案:

  • View中的依賴屬性DependencyProperty,綁定ViewModel中屬性“UIDelegateOperation”中的交互處理進度“UIDelegateProress”
  • 每次在ViewModel執行業務邏輯需要調用交互處理時,由UIDelegateOperation創建一個新的交互進度“UIDelegateProress”,觸發屬性變更,並設置“UIDelegateOperation”同步等待。
  • 當View中的屬性變更事件執行完成後,回調並喚醒”UIDelegateOperation“,繼續完成後面的業務邏輯。

1. 界面

在Xaml中添加附加屬性,刪除動畫DeleteCoursewaresAnimation,刪除確認框ShowDeleteWaring。並綁定ViewModel中對應的屬性

1 <UserControl.Style>
2  <Style TargetType="editing:CloudListView">
3      <Setter Property="DeleteCoursewaresAnimation" Value="{Binding DeleteCoursewaresAnimation.DelegateProgress}" />
4      <Setter Property="ShowDeleteWaringShow" Value="{Binding ShowDeleteWaring.DelegateProgress}" />
5  </Style>
6 </UserControl.Style>

界面ListBox,列表子項ListBoxItemr的DataTemplate模板中,刪除按鈕綁定ViewModel中的DeleteCommand

1 <Button x:Name="DeleteButton" 
2         Command="{Binding ElementName=TheCloudDocsList,Path=DataContext.DeleteCommand}"
3         CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext }"
4         Content="刪除" Style="{StaticResource Style.Button}" />

 2. ViewModel

ViewModel調用UIDelegateOperation交互處理時,根據是否需要同步等待,調用不同的函數 Start(),StartAsync(),StartWithResult(),StartWithResultAsync();

刪除業務中,除了數據處理,還有倆個交互(刪除確認框,刪除元素動畫)。

通過在同步調用刪除確認框/刪除元素動畫後,再繼續往下執行業務。

屬性和欄位字義:

定義命令

自定義命令,可以詳細之前寫的博客:自定義Command

 1 private DelegateCommand<CoursewareListItem> _deleteCommand = null;
 2 /// <summary>
 3 /// 刪除
 4 /// </summary>
 5 public DelegateCommand<CoursewareListItem> DeleteCommand
 6 {
 7     get
 8     {
 9         if (_deleteCommand == null)
10         {
11             _deleteCommand = new DelegateCommand<CoursewareListItem>(DeleteCourseware_OnExecute);
12 
13         }
14         return _deleteCommand;
15     } 
16 }

提示框確認交互/刪除動畫交互

1 /// <summary>
2 /// 彈出刪除確認視窗 
3 /// </summary>
4 public IUIDelegateOperation<List<CoursewareListItem>, MessageResult> ShowDeleteWaring { get; set; } = new IUIDelegateOperation<List<CoursewareListItem>, MessageResult>();
5 
6 /// <summary>
7 /// 刪除動畫 
8 /// </summary>
9 public IUIDelegateOperation<List<CoursewareListItem>> DeleteCoursewaresAnimation { get; set; } = new IUIDelegateOperation<List<CoursewareListItem>>();

刪除邏輯:

 1 /// <summary>
 2 /// 刪除
 3 /// </summary>
 4 /// <param name="item"></param>
 5 /// <returns></returns>
 6 private async void DeleteCourseware_OnExecute(CoursewareListItem item)
 7 {
 8     await DeleteCoursewares(new List<CoursewareListItem>() { item });
 9 }
10 private async Task DeleteCoursewares(List<CoursewareListItem> items)
11 {
12     if (items.Count == 0)
13     {
14         return;
15     }
16 
17     //彈出刪除確認視窗
18     var messageResult = await ShowDeleteWaringShow.ExecuteWithResultAsync(items);
19     if (messageResult == MessageResult.Positive)
20     {
21         //刪除伺服器數據 
22         Response deleteResponse = await WebService.DeleteItemAsync(items);
23 
24         //刪除失敗
25         if (!deleteResponse.Success)
26         {
27             Notification.ShowInfo(deleteResponse.Message);
28             return;
29         }
30         //刪除動畫
31         await DeleteCoursewaresAnimation.ExecuteAsync(items);
32         
33         //界面刪除子項
34         items.ForEach(item => ItemsSource.Remove(item));
35 
36         //退出編輯模式
37         if (DocListState == EditStatus.Editing)
38         {
39             DocListState = EditStatus.Normal;
40         }
41     }
42 }

 

3. Xaml.cs後臺

  • 添加依賴屬性後,通過屬性變更觸發,來完成彈出提示框/刪除動畫等交互。
  • 執行交互時,需要同步等待時,應將動畫執行等轉化為同步邏輯。

添加依賴屬性 - 刪除視窗

屬性變更觸發方法,應該是一個非同步方法,裡面的邏輯應該為同步執行。這樣ViewModel中才能同步等待交互的完成,並執行之後的邏輯。

 1 /// <summary>
 2 /// 刪除視窗
 3 /// </summary>
 4 public static readonly DependencyProperty ShowDeleteWaringShowProperty = DependencyProperty.Register(
 5     "ShowDeleteWaringShow", typeof(UIDelegateProgress<List<CoursewareListItem>, MessageResult>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress<List<CoursewareListItem>, MessageResult>),
 6         (d, e) => ((UIDelegateProgress<List<CoursewareListItem>, MessageResult>)e.NewValue)?.StartAsync(((CloudListView)d).ShowDeleteWaringShow)));
 7 
 8 private async Task<MessageResult> ShowDeleteWaringShow(List<CoursewareListItem> items)
 9 {
10     var cmd = await DeleteWaringShow(items);
11     return cmd.Result;
12 }
13 
14 public static void SetShowDeleteWaringShow(DependencyObject element, UIDelegateProgress<List<CoursewareListItem>, MessageResult> value)
15 {
16     element.SetValue(ShowDeleteWaringShowProperty, value);
17 }
18 
19 public static UIDelegateProgress<List<CoursewareListItem>, MessageResult> GetShowDeleteWaringShow(DependencyObject element)
20 {
21     return (UIDelegateProgress<List<CoursewareListItem>, MessageResult>)element.GetValue(ShowDeleteWaringShowProperty);
22 }

添加依賴屬性 - 刪除動畫

 1 public static readonly DependencyProperty DeleteCoursewaresAnimationProperty = DependencyProperty.Register(
 2     "DeleteCoursewaresAnimation", typeof(UIDelegateProgress<List<CoursewareListItem>>), typeof(CloudListView), new PropertyMetadata(default(UIDelegateProgress<List<CoursewareListItem>>),
 3         (d, e) => ((UIDelegateProgress<List<CoursewareListItem>>)e.NewValue)?.StartAsync(((CloudListView)d).ExecuteDeleteCoursewaresAnimation)));
 4 
 5 private async Task ExecuteDeleteCoursewaresAnimation(List<CoursewareListItem> coursewares)
 6 {
 7     List<Storyboard> storyboards = new List<Storyboard>();
 8     foreach (var courseware in coursewares)
 9     {
10         var listBoxItem = DocumentsControl.ItemContainerGenerator.ContainerFromItem(courseware) as ListBoxItem;
11         var border = listBoxItem?.VisualDescendant<Border>();
12         var storyboard = (Storyboard)border?.Resources["ItemRemovedStoryboard"];
13         if (storyboard == null)
14         {
15             //如果找不到storyBoard,則中斷動畫的執行。因為刪除多個Item,只執行一半的動畫,界面會閃現倆次。
16             return;
17         }
18         storyboards.Add(storyboard);
19     }
20     //刪除界面課件
21     await AsynchronousTransferHelper.ExecuteStoryboradAsync(storyboards);
22 }
23 
24 public static void SetDeleteCoursewaresAnimation(DependencyObject element, UIDelegateProgress<List<CoursewareListItem>> value)
25 {
26     element.SetValue(DeleteCoursewaresAnimationProperty, value);
27 }
28 
29 public static UIDelegateProgress<List<CoursewareListItem>> GetDeleteCoursewaresAnimation(DependencyObject element)
30 {
31     return (UIDelegateProgress<List<CoursewareListItem>>)element.GetValue(DeleteCoursewaresAnimationProperty);
32 }

動畫的執行,怎麼轉為有同步等待呢?動畫完成只有通過觸發事件Completed才能確定。

如何將動畫轉化為同步,可參考之前寫的博客:C# 非同步轉同步

 1 /// <summary>
 2 /// 執行動畫
 3 /// </summary>
 4 /// <param name="storyboard"></param>
 5 /// <returns></returns>
 6 public static async Task ExecuteStoryboradAsync([NotNull] Storyboard storyboard)
 7 {
 8     if (storyboard == null) throw new ArgumentNullException(nameof(storyboard));
 9 
10     AutoResetEvent autoResetEvent = new AutoResetEvent(false);
11 
12     storyboard.Completed += OnStoryboardCompleted;
13     storyboard.Begin();
14 
15     void OnStoryboardCompleted(object sender, EventArgs e)
16     {
17         storyboard.Completed -= OnStoryboardCompleted;
18         autoResetEvent.Set();
19     }
20 
21     await Task.Run(() => { autoResetEvent.WaitOne(); });
22 }

4. 交互處理輔助類 UIDelegateOperation 

在UIDelegateOperation內部,每次調用時,都會新建一個UIDelegateProgress(委托進度)。委托進度,是界面交互的處理~

UIDelegateOperation:

  1 /// <summary>
  2     /// UI交互處理-提供可調用UI交互的操作
  3     /// </summary>
  4     public class UIDelegateOperation : BindableObject, IUIDelegateAction
  5     {
  6         private UIDelegateProgress _delegateProgress;
  7 
  8         public UIDelegateProgress DelegateProgress
  9         {
 10             get => _delegateProgress;
 11             private set
 12             {
 13                 _delegateProgress = value;
 14                 OnPropertyChanged();
 15             }
 16         }
 17 
 18         /// <summary>
 19         /// 執行
 20         /// </summary>
 21         public void Execute()
 22         {
 23             var delegateProgress = new UIDelegateProgress();
 24             delegateProgress.ProgressCompleted += () =>
 25             {
 26                 _delegateProgress = null;
 27             };
 28             DelegateProgress = delegateProgress;
 29         }
 30 
 31         /// <summary>
 32         /// 非同步執行
 33         /// 交互處理完成並回調
 34         /// </summary>
 35         public async Task ExecuteAsync()
 36         {
 37             AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 38 
 39             var delegateProgress = new UIDelegateProgress();
 40             delegateProgress.ProgressCompleted += () =>
 41             {
 42                 _delegateProgress = null;
 43 
 44                 autoResetEvent.Set();
 45             };
 46             DelegateProgress = delegateProgress;
 47             await Task.Run(() => { autoResetEvent.WaitOne(); });
 48         }
 49     }
 50 
 51     /// <summary>
 52     /// UI交互處理-提供可同步調用UI交互的操作
 53     /// </summary>
 54     /// <typeparam name="T">輸入/輸出類型</typeparam>
 55     public class UIDelegateAction<T> : BindableObject, IUIDelegateAction<T>
 56     {
 57         private UIDelegateProgress<T> _delegateProgress;
 58 
 59         public UIDelegateProgress<T> DelegateProgress
 60         {
 61             get => _delegateProgress;
 62             private set
 63             {
 64                 _delegateProgress = value;
 65                 OnPropertyChanged();
 66             }
 67         }
 68         /// <summary>
 69         /// 執行
 70         /// </summary>
 71         public void Execute(T parameter)
 72         {
 73             var delegateProgress = new UIDelegateProgress<T>(parameter);
 74             delegateProgress.ProgressCompleted += () =>
 75             {
 76                 _delegateProgress = null;
 77             };
 78             DelegateProgress = delegateProgress;
 79         }
 80         /// <summary>
 81         /// 非同步執行
 82         /// 交互處理完成並回調
 83         /// </summary>
 84         public async Task ExecuteAsync(T parameter)
 85         {
 86             AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 87 
 88             var delegateProgress = new UIDelegateProgress<T>(parameter);
 89             delegateProgress.ProgressCompleted += () =>
 90             {
 91                 _delegateProgress = null;
 92 
 93                 autoResetEvent.Set();
 94             };
 95             DelegateProgress = delegateProgress;
 96 
 97             await Task.Run(() => { autoResetEvent.WaitOne(); });
 98         }
 99 
100         /// <summary>
101         /// 非同步執行並返回結果
102         /// </summary>
103         public async Task<T> ExecuteWithResultAsync()
104         {
105             AutoResetEvent autoResetEvent = new AutoResetEvent(false);
106 
107             var delegateProgress = new UIDelegateProgress<T>();
108             delegateProgress.ProgressCompleted += () =>
109             {
110                 _delegateProgress = null;
111 
112                 autoResetEvent.Set();
113             };
114             DelegateProgress = delegateProgress;
115 
116             await Task.Run(() => { autoResetEvent.WaitOne(); });
117 
118             return delegateProgress.Result;
119         }
120     }
121 
122     /// <summary>
123     /// UI交互處理-提供可同步調用UI交互的操作
124     /// </summary>
125     /// <typeparam name="TInput">輸入類型</typeparam>
126     /// <typeparam name="TOut">輸出類型</typeparam>
127     public class UIDelegateAction<TInput, TOut> : BindableObject, IUIDelegateAction<TInput, TOut>
128     {
129         private UIDelegateProgress<TInput, TOut> _delegateProgress;
130 
131         public UIDelegateProgress<TInput, TOut> DelegateProgress
132         {
133             get => _delegateProgress;
134             private set
135             {
136                 _delegateProgress = value;
137                 OnPropertyChanged();
138             }
139         }
140         /// <summary>
141         /// 執行
142         /// </summary>
143         public void Execute(TInput parameter)
144         {
145             var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
146             delegateProgress.ProgressCompleted += () =>
147             {
148                 _delegateProgress = null;
149             };
150             DelegateProgress = delegateProgress;
151         }
152 
153         /// <summary>
154         /// 執行並返回結果
155         /// </summary>
156         public TOut ExecuteWithResult(TInput parameter)
157         {
158             var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
159             delegateProgress.ProgressCompleted += () =>
160             {
161                 _delegateProgress = null;
162             };
163             DelegateProgress = delegateProgress;
164             return delegateProgress.Result;
165         }
166 
167         /// <summary>
168         /// 非同步執行並返回結果
169         /// </summary>
170         public async Task<TOut> ExecuteWithResultAsync(TInput parameter)
171         {
172             var delegateProgress = new UIDelegateProgress<TInput, TOut>(parameter);
173             await SetDelegateProgress(delegateProgress);
174             return delegateProgress.Result;
175         }
176         private async Task SetDelegateProgress(UIDelegateProgress<TInput, TOut> delegateProgress)
177         {
178             AutoResetEvent autoResetEvent = new AutoResetEvent(false);
179 
180             delegateProgress.ProgressCompleted += () =>
181             {
182                 _delegateProgress = null;
183                 autoResetEvent.Set();
184             };
185             DelegateProgress = delegateProgress;
186             await Task.Run(() => { autoResetEvent.WaitOne(); });
187         }
188     }
189 
190     /// <summary>
191     /// UI交互處理介面
192     /// </summary>
193     public interface IUIDelegateAction
194     {
195 
196         UIDelegateProgress DelegateProgress { get; }
197 
198         /// <summary>
199         /// 執行
200         /// </summary>
201         void Execute();
202 
203         /// <summary>
204         /// 非同步執行
205         /// </summary>
206         Task ExecuteAsync();
207     }
208 
209     /// <summary>
210     /// UI交互處理介面
211     /// </summary>
212     /// <typeparam name="T">輸入/輸出類型</typeparam>
213     public interface IUIDelegateAction<T>
214     {
215         UIDelegateProgress<T> DelegateProgress { get; }
216 
217         /// <summary>
218         /// 執行
219         /// </summary>
220         void Execute(T parameter);
221 
222         /// <summary>
223         /// 非同步執行
224         /// </summary>
225         Task ExecuteAsync(T parameter);
226 
227         /// <summary>
228         /// 非同步執行並返回結果
229         /// </summary>
230         Task<T> ExecuteWithResultAsync();
231     }
232 
233     /// <summary>
234     /// UI交互處理介面
235     /// </summary>
236     /// <typeparam name="TInput">輸入類型</typeparam>
237     /// <typeparam name="TOut">輸出類型</typeparam>
238     public interface IUIDelegateAction<TInput, TOut>
239     {
240         UIDelegateProgress<TInput, TOut> DelegateProgress { get; }
241 
242         /// <summary>
243         /// 執行
244         /// </summary>
245         void Execute(TInput parameter);
246 
247         /// <summary>
248         /// 執行並返回結果
249         /// </summary>
250         TOut ExecuteWithResult(TInput parameter);
251 
252         /// <summary>
253         /// 非同步執行並返回結果
254         /// </summary>
255         Task<TOut> ExecuteWithResultAsync(TInput parameter);
256     }
View Code

UIDelegateProgress:

  1     /// <summary>
  2     /// 委托進度
  3     /// </summary>
  4     public class UIDelegateProgress
  5     {
  6         public event Action ProgressCompleted;
  7 
  8         /// <summary>
  9         /// UI委托處理
 10         /// </summary>
 11         /// <param name="uiTask"></param>
 12         public async void StartAsync(Func<Task> uiTask)
 13         {
 14             try
 15             {
 16                 await uiTask.Invoke();
 17             }
 18             catch (InvalidOperationException e)
 19             {
 20                 Log.Error(	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 學習內容:C#;學習書籍:圖解教程(中文第四版)。 目錄:第二章 C#編程概述 > 2.7 從程式中輸出文本 > 2.7.5格式化數字字元串 學習感受: 我現在的工作中還沒有遇見這些的使用。 ...
  • ASP.NET程式中的web.config文件中,在appSettings這個配置節中能夠保存一些配置,比如, 但是這些配置都是單個字元串信息,在某些情況下,無法做到靈活配置。 針對這種情況,使用.Net Framework提供的自定義系統配置方式來進行改善。自定義系統配置方式主要使用到以下幾個類: ...
  • 在asp.net項目中,添加一個【一般處理程式】來處理請求是很自然的事,這樣會得到一個實現自IHttpHandler的類,然後只需在ProcessRequest方法中寫上處理邏輯就行了。但是這樣的一個請求處理程式(下稱ashx)是同步的,就是接待該次請求的線程會一直等待處理完才能解脫,後果就是,如果 ...
  • 小程式頁面代碼因為某些人力不可控的原因代碼丟失了,這裡簡單說明一下 調用小程式APIwx.chooseImage(OBJECT)選擇相冊或拍攝照片,會返回 tempFilePaths,之後通過wx.uploadFile(OBJECT)把照片傳至後臺,在伺服器後臺進行Base64編碼, 小程式目前不支 ...
  • 找了好多都有問題,這個可以分享給到家 轉自:https://www.cnblogs.com/kmust/p/4412228.html ...
  • 本文的重點主要是解決:List<T>對象集合的排序功能。 一、List<T>.Sort 方法 () MSDN對這個無參Sort()方法的介紹:使用預設比較器對整個List<T>中的元素進行排序。 從官方文檔的介紹,很難的看出詳細的解析,而且還要完整的知識結構去分析,上面提到了一個關鍵詞:比較器。大致 ...
  • 20180518更新內容 1、重構調整QQ登錄代碼,使用JObject,減少代碼,增加access_token自動續期(未測試)。 2、重構調整微信登錄代碼,使用JObject,減少代碼,增加access_token自動續期(未測試)。 3、重構微信公眾號登錄接入代碼,使用JObject,減少代碼。 ...
  • 編譯環境需求(3239版本) win7或更高,64位 vs2017 15.3.2+ 預設位置安裝 不需要安裝附帶的win10sdk,sdk單獨裝 Windows 10.0.15063.468 SDK 預設位置安裝 這裡下載相應版本sdk 8g ram 和 40g disk 實測完全不夠,最終編譯成功 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...