C# WPF Http Tool
By
jerryxjr1220
at 2023-10-10 • 0人收藏 • 706人看过
练手项目,涉及MVVM数据绑定,RelayCommand命令绑定,跨线程Messenger传参等,Html解析用了HtmlAgility库,递归读取DocumentNode后画到TreeView里。

using System;
using System.Net;
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPFHttpTool.Models;
public partial class HttpClientModel : ObservableObject
{
[ObservableProperty] private WebClient _client;
[Obsolete("Obsolete")]
public HttpClientModel()
{
Client = new WebClient();
}
}using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using WPFHttpTool.Models;
using WPFHttpTool.Views;
namespace WPFHttpTool.ViewModels;
public partial class MainViewModel : ObservableObject
{
[ObservableProperty] private HttpClientModel _clientModel;
[ObservableProperty] private string? _method;
[ObservableProperty] private BindingList<Tuple<string, string>> _requestHeaders;
[ObservableProperty] private BindingList<Tuple<string, string>> _requestParameters;
[ObservableProperty] private ResultPage _resultPage = new();
[ObservableProperty] private string? _url;
[Obsolete("Obsolete")]
public MainViewModel()
{
ClientModel = new HttpClientModel();
Url = string.Empty;
Method = "GET";
RequestHeaders = new BindingList<Tuple<string, string>>();
RequestParameters = new BindingList<Tuple<string, string>>();
RequestHeaders.Add(new Tuple<string, string>("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/"));
#region 注册并接受消息
WeakReferenceMessenger.Default.Register<AddRequestPage.CustomizedMessage>(this, (o, m) =>
{
RequestHeaders = m.viewmodel.RequestHeaders;
RequestParameters = m.viewmodel.RequestParameters;
});
#endregion
}
[RelayCommand]
public async Task RecievedAsString()
{
ClientModel.Client.Headers.Clear();
switch (Method)
{
case "GET":
foreach (var header in RequestHeaders) ClientModel.Client.Headers.Add(header.Item1, header.Item2);
string? parStr = null;
var sb = new StringBuilder();
foreach (var par in RequestParameters) sb.Append(par.Item1 + "=" + par.Item2 + "&");
if (sb.Length > 1) parStr = sb.ToString()[..^1];
var resp = await ClientModel.Client.DownloadStringTaskAsync(Url + "?" + parStr);
ResultPage = new ResultPage();
ResultPage.RenderPage(ClientModel, resp, null);
ResultPage.Show();
break;
case "POST":
foreach (var header in RequestHeaders) ClientModel.Client.Headers.Add(header.Item1, header.Item2);
var pars = new List<string>();
foreach (var par in RequestParameters) pars.Add(par.Item2);
var jObject = JsonContent.Create(pars);
var resp2 = await ClientModel.Client.UploadStringTaskAsync(Url!, jObject?.ToString() ?? string.Empty);
ResultPage = new ResultPage();
ResultPage.RenderPage(ClientModel, resp2, null);
ResultPage.Show();
break;
}
}
[RelayCommand]
public async Task RecievedAsBytes()
{
ClientModel.Client.Headers.Clear();
switch (Method)
{
case "GET":
foreach (var header in RequestHeaders) ClientModel.Client.Headers.Add(header.Item1, header.Item2);
string? parStr = null;
var sb = new StringBuilder();
foreach (var par in RequestParameters) sb.Append(par.Item1 + "=" + par.Item2 + "&");
if (sb.Length > 1) parStr = sb.ToString()[..^1];
var resp = await ClientModel.Client.DownloadDataTaskAsync(new Uri(Url + "?" + parStr));
ResultPage = new ResultPage();
ResultPage.RenderPage(ClientModel, null, resp);
ResultPage.Show();
break;
case "POST":
foreach (var header in RequestHeaders) ClientModel.Client.Headers.Add(header.Item1, header.Item2);
var pars = new List<string>();
foreach (var par in RequestParameters) pars.Add(par.Item2);
var jObject = JsonContent.Create(pars);
var resp2 = await ClientModel.Client.UploadStringTaskAsync(Url!, jObject?.ToString() ?? string.Empty);
ResultPage = new ResultPage();
ResultPage.RenderPage(ClientModel, resp2, null);
ResultPage.Show();
break;
}
}
}<hc:GlowWindow x:Class="WPFHttpTool.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WPFHttpTool.ViewModels"
xmlns:hc="https://handyorg.github.io/handycontrol"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
mc:Ignorable="d"
Title="
4 个回复 | 最后更新于 2023-10-11
2023-10-10
#2
<Window x:Class="WPFHttpTool.Views.AddRequestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:vm="clr-namespace:WPFHttpTool.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
Title="Add..." Height="280" Width="400" ResizeMode="NoResize">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox x:Name="tbKey" Width="300" Margin="10"
hc:InfoElement.Title="Key" hc:InfoElement.TitlePlacement="Left" />
<TextBox x:Name="tbValue" Width="300" Height="150" MaxLines="6" VerticalContentAlignment="Top"
hc:InfoElement.Title="Value" hc:InfoElement.TitlePlacement="Left" />
<Button Content="Submit" Style="{StaticResource ButtonPrimary}" Margin="5"
Click="ButtonSubmit_OnClick" />
</StackPanel>
</Window>using System;
using System.Windows;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using WPFHttpTool.ViewModels;
namespace WPFHttpTool.Views;
public partial class AddRequestPage : Window
{
private readonly string _buttonName;
private readonly MainViewModel _mainViewModel;
public AddRequestPage(MainViewModel _viewModel, string _name)
{
InitializeComponent();
_mainViewModel = _viewModel;
_buttonName = _name;
}
private void ButtonSubmit_OnClick(object sender, RoutedEventArgs e)
{
if (_buttonName == "ButtonAddHeader")
_mainViewModel.RequestHeaders.Add(new Tuple<string, string>(tbKey.Text, tbValue.Text));
else
_mainViewModel.RequestParameters.Add(new Tuple<string, string>(tbKey.Text, tbValue.Text));
var cm = new CustomizedMessage(_mainViewModel);
WeakReferenceMessenger.Default.Send(cm);
Close();
}
//定义消息
public class
CustomizedMessage : ValueChangedMessage<MainViewModel> // PropertyChangedMessage // RequestMessage ( Async + ..)
{
public MainViewModel viewmodel;
public CustomizedMessage(MainViewModel value) : base(value)
{
viewmodel = value;
}
}
}
2023-10-10
#3
<Window x:Class="WPFHttpTool.Views.ResultPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFHttpTool.Views"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:ResultPage}"
Title="Received Response" Height="850" Width="800" ResizeMode="NoResize">
<StackPanel>
<hc:Shield Status="Headers" Subject="Response" Color="Green" Margin="10,5" />
<DataGrid ItemsSource="{Binding RespHeaders}" AutoGenerateColumns="False" IsReadOnly="True"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Height="350" Width="780">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" Binding="{Binding Item1 }" />
<DataGridTextColumn Header="Value" Binding="{Binding Item2 }" />
</DataGrid.Columns>
</DataGrid>
<WrapPanel>
<hc:Shield Status="Html" Subject="Response" Color="Green" Margin="10,5" />
<Button Content="Parse" Style="{StaticResource ButtonSuccess.Small}"
Command="{Binding ParseHtmlCommand}" />
</WrapPanel>
<TextBox MaxLines="21" VerticalScrollBarVisibility="Auto" IsReadOnly="True" Text="{Binding ReceivedString}"
TextWrapping="Wrap" />
<hc:Shield Status="Bytes Length" Subject="Response" Color="Green" Margin="10,5" />
<TextBox IsReadOnly="True" Text="{Binding ReceivedBytesLength}" />
</StackPanel>
</Window>using System;
using System.Collections.ObjectModel;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HtmlAgilityPack;
using WPFHttpTool.Models;
namespace WPFHttpTool.Views;
[ObservableObject]
public partial class ResultPage : Window
{
[ObservableProperty] private HttpClientModel? _clientModel;
[ObservableProperty] private byte[] _receivedBytes = Array.Empty<byte>();
[ObservableProperty] private int _receivedBytesLength;
[ObservableProperty] private string? _receivedString;
[ObservableProperty] private ObservableCollection<Tuple<string, string>> _respHeaders;
public ResultPage()
{
InitializeComponent();
RespHeaders = new ObservableCollection<Tuple<string, string>>();
DataContext = this;
}
public void RenderPage(HttpClientModel _httpClientModel, string? receivedString, byte[]? receivedBytes)
{
ClientModel = _httpClientModel;
ReceivedString = receivedString;
if (receivedBytes is not null) ReceivedBytes = receivedBytes;
ReceivedBytesLength = ReceivedBytes.Length;
var keys = ClientModel.Client.ResponseHeaders?.AllKeys;
foreach (var key in keys)
{
var value = ClientModel.Client.ResponseHeaders?.GetValues(key)?[0];
if (value is not null) RespHeaders.Add(new Tuple<string, string>(key, value));
}
}
[RelayCommand]
public void ParseHtml()
{
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(ReceivedString);
var treeViewPage = new TreeViewPage(htmlDocument);
treeViewPage.Show();
}
}<Window x:Class="WPFHttpTool.Views.TreeViewPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" Title="TreeViewPage" Height="800" Width="800"> <Grid> <TreeView x:Name="_treeView" /> </Grid> </Window>
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using HtmlAgilityPack;
namespace WPFHttpTool.Views;
[ObservableObject]
public partial class TreeViewPage : Window
{
[ObservableProperty] private HtmlDocument _htmlDocument;
public TreeViewPage(HtmlDocument htmlDocument)
{
InitializeComponent();
_htmlDocument = htmlDocument;
MapHtmlToTreeView(_htmlDocument.DocumentNode);
}
public void MapHtmlToTreeView(HtmlNode htmlNode, TreeViewItem parentItem = null)
{
foreach (var childNode in htmlNode.ChildNodes)
if (childNode.NodeType == HtmlNodeType.Element)
{
var treeViewItem = new TreeViewItem();
treeViewItem.Header = childNode.Name;
foreach (var attr in childNode.Attributes) treeViewItem.Header += $" {attr.Name} : {attr.Value} ;";
if (parentItem != null)
parentItem.Items.Add(treeViewItem);
else
_treeView.Items.Add(treeViewItem);
MapHtmlToTreeView(childNode, treeViewItem);
}
else if (childNode.NodeType == HtmlNodeType.Text)
{
var text = childNode.InnerHtml.Trim();
if (!string.IsNullOrEmpty(text))
{
var textBlock = new TextBlock();
textBlock.Text = text;
if (parentItem != null)
parentItem.Items.Add(textBlock);
else
_treeView.Items.Add(textBlock);
}
}
}
}
2023-10-11
#4
增加了Json字符串解析功能,可以抓取Json API的返回值,直接解析,自动判断类型,并绘制TreeView展示。
用了NewtonSoft.Json库进行Json字符串的解析。

using System;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using HtmlAgilityPack;
using Newtonsoft.Json.Linq;
namespace WPFHttpTool.Views;
[ObservableObject]
public partial class TreeViewPage : Window
{
[ObservableProperty] private HtmlDocument? _htmlDocument;
public TreeViewPage(HtmlDocument htmlDocument)
{
InitializeComponent();
_htmlDocument = htmlDocument;
MapHtmlToTreeView(_htmlDocument.DocumentNode);
}
public TreeViewPage(JObject jObject)
{
InitializeComponent();
MapJsonToTreeView(jObject);
}
public TreeViewPage(JArray jArray)
{
InitializeComponent();
MapJsonToTreeView(jArray);
}
public void MapHtmlToTreeView(HtmlNode htmlNode, TreeViewItem parentItem = null)
{
foreach (var childNode in htmlNode.ChildNodes)
{
if (childNode.NodeType == HtmlNodeType.Element)
{
var treeViewItem = new TreeViewItem();
treeViewItem.Header = childNode.Name;
foreach (var attr in childNode.Attributes) treeViewItem.Header += $" {attr.Name} : {attr.Value} ;";
if (parentItem != null)
parentItem.Items.Add(treeViewItem);
else
_treeView.Items.Add(treeViewItem);
MapHtmlToTreeView(childNode, treeViewItem);
}
else if (childNode.NodeType == HtmlNodeType.Text)
{
var text = childNode.InnerHtml.Trim();
if (!string.IsNullOrEmpty(text))
{
var textBlock = new TextBlock();
textBlock.Text = text;
if (parentItem != null)
parentItem.Items.Add(textBlock);
else
_treeView.Items.Add(textBlock);
}
}
}
}
public void MapJsonToTreeView(JObject jObject, TreeViewItem parentItem = null)
{
foreach (var (key, value) in jObject)
{
var treeViewItem = new TreeViewItem();
treeViewItem.Header = key;
try
{
var children = value!.ToObject<JObject>();
MapJsonToTreeView(children!, treeViewItem);
}
catch (Exception ex)
{
try
{
var children = value!.ToObject<JArray>();
MapJsonToTreeView(children!, treeViewItem);
}
catch (Exception ex2)
{
var child = value!.ToObject<string>();
treeViewItem.Header += $" : {child}, ";
}
}
if (parentItem != null)
parentItem.Items.Add(treeViewItem);
else
_treeView.Items.Add(treeViewItem);
}
}
public void MapJsonToTreeView(JArray jArray, TreeViewItem parentItem = null)
{
foreach (var jtoken in jArray)
{
try
{
var children = jtoken.ToObject<JObject>();
MapJsonToTreeView(children!, parentItem);
}
catch (Exception ex)
{
try
{
var children = jtoken.ToObject<JArray>();
MapJsonToTreeView(children!, parentItem);
}
catch (Exception ex2)
{
var child = jtoken.ToObject<string>();
if (parentItem != null)
parentItem.Header += $" {child},";
else
_treeView.Items.Add(new TextBlock { Text = child });
}
}
}
}
}登录后方可回帖
<hc:GlowWindow x:Class="WPFHttpTool.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:WPFHttpTool.ViewModels" xmlns:hc="https://handyorg.github.io/handycontrol" d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" mc:Ignorable="d" Title="Http Tool" MinHeight="500" MinWidth="800" Width="900" Height="500" FontFamily="JetBrains Mono" FontSize="{StaticResource TextFontSize}"> <Window.Resources> </Window.Resources> <hc:SimplePanel> <ScrollViewer x:Name="scrollViewer" VerticalScrollBarVisibility="Auto"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="6*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <WrapPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center" Width="800"> <hc:ComboBox x:Name="cbMethod" AutoComplete="True" SelectedIndex="0" FontSize="20" Width="100" Style="{StaticResource ComboBoxExtend}" SelectionChanged="CbMethod_OnSelectionChanged"> <ComboBoxItem Content="GET" Style="{StaticResource ComboBoxItemCapsuleVerticalFirst}" /> <ComboBoxItem Content="POST" Style="{StaticResource ComboBoxItemCapsuleVerticalLast}" /> </hc:ComboBox> <hc:TextBox hc:InfoElement.Title="URL:" hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Necessary="True" hc:InfoElement.Placeholder="Http(s)://" Text="{Binding Url}" FontSize="20" Width="480" Margin="16,0" Style="{StaticResource TextBoxExtend}" /> <Button Content="As String" Style="{StaticResource ButtonPrimary}" Margin="5" Command="{Binding RecievedAsStringCommand}" /> <Button Content="As Bytes" Style="{StaticResource ButtonPrimary}" Margin="5" Command="{Binding RecievedAsBytesCommand}" /> </WrapPanel> <ScrollViewer Grid.Row="1" Grid.Column="0" VerticalScrollBarVisibility="Auto"> <WrapPanel> <TextBlock Text="请求头" VerticalAlignment="Center" Margin="10" Style="{StaticResource TextBlockSubTitle}" /> <Button Content="新增请求表头" Style="{StaticResource ButtonPrimary}" VerticalAlignment="Center" Click="ButtonAddRequest_OnClick" x:Name="ButtonAddHeader" /> <DataGrid Style="{StaticResource DataGridBaseStyle}" HorizontalContentAlignment="Center" MinWidth="450" ItemsSource="{Binding RequestHeaders}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Keys" Width="150" Binding="{Binding Item1}" IsReadOnly="True" /> <DataGridTextColumn Header="Values" Binding="{Binding Item2}" IsReadOnly="True" /> </DataGrid.Columns> </DataGrid> </WrapPanel> </ScrollViewer> <ScrollViewer Grid.Row="1" Grid.Column="1" VerticalScrollBarVisibility="Auto"> <WrapPanel> <TextBlock Text="请求参数" VerticalAlignment="Center" Margin="10" Style="{StaticResource TextBlockSubTitle}" /> <Button Content="新增请求参数" Style="{StaticResource ButtonPrimary}" VerticalAlignment="Center" Click="ButtonAddRequest_OnClick" x:Name="ButtonAddParameter" /> <DataGrid Style="{StaticResource DataGridBaseStyle}" HorizontalContentAlignment="Center" ItemsSource="{Binding RequestParameters}" MinWidth="450" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Key" Width="150" Binding="{Binding Item1}" IsReadOnly="True" /> <DataGridTextColumn Header="Value" Binding="{Binding Item2}" IsReadOnly="True" /> </DataGrid.Columns> </DataGrid> </WrapPanel> </ScrollViewer> </Grid> </ScrollViewer> </hc:SimplePanel> </hc:GlowWindow>using System; using System.Windows; using System.Windows.Controls; using HandyControl.Controls; using WPFHttpTool.ViewModels; namespace WPFHttpTool.Views; /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : GlowWindow { private readonly MainViewModel mainViewModel; public MainWindow() { InitializeComponent(); mainViewModel = new MainViewModel(); DataContext = mainViewModel; } private void ButtonAddRequest_OnClick(object sender, RoutedEventArgs e) { var b = sender as Button; var addRequestPage = new AddRequestPage(mainViewModel, b.Name); addRequestPage.Show(); } private void CbMethod_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { try { switch (cbMethod.SelectedIndex) { case 0: mainViewModel.Method = "GET"; break; case 1: mainViewModel.Method = "POST"; break; } } catch (Exception ex) { //ignored } } }