//Server model
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public string ISBN { get; set; }
public string Author { get; set; }
public string Comment { get; set; }
public string Content { get; set; }
public string Summary { get; set; }
public string Title { get; set; }
public string Topic { get; set; }
}
//Client model
public class BookSlim
{
public int Id { get; set; }
public string Name { get; set; }
public string ISBN { get; set; }
public string Author { get; set; }
}
JsonSerializerSettings setting = new JsonSerializerSettings()
{
MissingMemberHandling = MissingMemberHandling.Ignore
};
List<BookSlim>? bksList = JsonConvert.DeserializeObject<List<BookSlim>>(jsonStr, setting);
//Consumer
<Window x:Class="WpfApp5.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:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="{Binding MainTitle}" WindowState="Maximized">
<Window.DataContext>
<local:MainVM/>
</Window.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding BooksCollection}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.CacheLength="5,5"
VirtualizingPanel.CacheLengthUnit="Item"
ScrollViewer.IsDeferredScrollingEnabled="True"
ScrollViewer.CanContentScroll="True"
UseLayoutRounding="True"
SnapsToDevicePixels="True"
EnableRowVirtualization="True"
EnableColumnVirtualization="True"
AutoGenerateColumns="True"
CanUserAddRows="False">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Setter Property="FontSize" Value="30"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontSize" Value="50"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
using Newtonsoft.Json;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainVM : INotifyPropertyChanged
{
HttpClient client = null;
string originalUrl = "http://localhost:8080/getbookslist?count=";
private bool isLoading = false;
public MainVM()
{
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
client = new HttpClient();
Task.Run(async () =>
{
await LoadDataFromServerWithoutRest();
});
}
}
private async Task LoadDataFromServerWithoutRest()
{
while (true)
{
await InitBooksCollectionAsync();
await Task.Delay(20000);
}
}
private SemaphoreSlim concurrentLimit = new SemaphoreSlim(10,10);
private async Task InitBooksCollectionAsync(int cnt = 1000000)
{
if (isLoading)
{
return;
}
bool acquired = false;
try
{
acquired = await concurrentLimit.WaitAsync(30000);
if (!acquired)
{
PrintMsgInOutput("Acquire concurrent signal failed");
return;
}
isLoading = true;
await Application.Current?.Dispatcher?.InvokeAsync(() =>
{
MainTitle = $"{DateTime.Now},loading...";
BooksCollection?.Clear();
},System.Windows.Threading.DispatcherPriority.Background);
string requestUrl = $"{originalUrl}{cnt}";
string jsonStr = await client.GetStringAsync(requestUrl);
if (!string.IsNullOrWhiteSpace(jsonStr))
{
JsonSerializerSettings setting = new JsonSerializerSettings()
{
MissingMemberHandling = MissingMemberHandling.Ignore
};
List<BookSlim>? bksList = JsonConvert.DeserializeObject<List<BookSlim>>(jsonStr, setting);
if (bksList != null && bksList.Any())
{
await Application.Current?.Dispatcher?.InvokeAsync(() =>
{
BooksCollection = new ObservableCollection<BookSlim>(bksList);
MainTitle = $"{DateTime.Now},FirstId:{BooksCollection[0]?.Id},LastId:{BooksCollection[^1]?.Id}";
}, System.Windows.Threading.DispatcherPriority.Background);
}
}
}
catch (Exception ex)
{
PrintMsgInOutput(ex?.Message);
}
finally
{
if(acquired)
{
concurrentLimit.Release();
}
isLoading = false;
}
}
private void PrintMsgInOutput(string msg)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"{DateTime.Now},{msg}");
#else
System.Diagnostics.Trace.WriteLine($"{DateTime.Now},{msg}");
#endif
}
private string mainTitle = $"{DateTime.Now}";
public string MainTitle
{
get
{
return mainTitle;
}
set
{
if (value != mainTitle)
{
mainTitle = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<BookSlim> booksCollection;
public ObservableCollection<BookSlim> BooksCollection
{
get
{
return booksCollection;
}
set
{
if (value != booksCollection)
{
booksCollection = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propName = "")
{
var handler = Volatile.Read(ref PropertyChanged);
if (handler == null)
{
return;
}
handler?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class BookSlim
{
public int Id { get; set; }
public string Name { get; set; }
public string ISBN { get; set; }
public string Author { get; set; }
}
}
//Server
using Newtonsoft.Json;
using System.Net;
using System.Text;
using System.Xml;
namespace ConsoleApp1
{
internal class Program
{
static async Task Main(string[] args)
{
await ListenHttpRequestAsync();
}
static async Task ListenHttpRequestAsync()
{
HttpListener httpListener = new HttpListener();
string prefix = "http://*:8080/";
httpListener.Prefixes.Add(prefix);
try
{
httpListener.Start();
Console.WriteLine($"{DateTime.Now},Http service started,address:{prefix}");
Console.WriteLine($"{DateTime.Now},sample:http://localhost:8080/getbookslist?count=1000");
Console.WriteLine($"{DateTime.Now},press Ctrl+C to stop service!");
while (true)
{
HttpListenerContext context = await httpListener.GetContextAsync();
await ProcessRequestAsync(context);
}
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now},start service failed:{ex.Message}");
Console.WriteLine($"If no permission.please run as administrator or update the port as greater than 1024");
}
finally
{
httpListener?.Close();
}
}
private static SemaphoreSlim concurrentLimit = new SemaphoreSlim(10,10);
private static async Task ProcessRequestAsync(HttpListenerContext context)
{
bool isAcquired = await concurrentLimit.WaitAsync(30000);
if (!isAcquired)
{
context.Response.StatusCode = 503;
string errorMsg = "{\"error\":\"Server busy, please retry later\"}";
byte[] buffer = Encoding.UTF8.GetBytes(errorMsg);
context.Response.ContentType = "application/json";
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
return;
}
if (context?.Request?.Url?.AbsolutePath?.Contains("/favicon.ico") == true)
{
return;
}
try
{
int count = ParseCountFromRequest(context.Request);
if (count <= 0)
{
return;
}
string jsonStr = await GetJsonStrAsync(count);
context.Response.ContentType = "application/json;charset=utf-8";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.StatusCode = 200;
byte[] buffer = Encoding.UTF8.GetBytes(jsonStr);
context.Response.ContentLength64 = buffer.Length;
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
Console.WriteLine($"{DateTime.Now},request from:{context?.Request?.Url?.ToString()}," +
$"Thread Id:{Thread.CurrentThread.ManagedThreadId},length:{jsonStr.Length}\n\n");
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now},Process request failed:{ex.Message}");
context.Response.StatusCode = 500;
string errorMsg = $"{{\"error\":\"{ex.Message}\"}}";
byte[] buffer = Encoding.UTF8.GetBytes(errorMsg);
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
}
finally
{
if(isAcquired)
{
concurrentLimit.Release();
}
context.Response.OutputStream?.Close();
}
}
private static int ParseCountFromRequest(HttpListenerRequest request)
{
int cnt = 0;
string path = request.Url.AbsolutePath;
Console.WriteLine($"{DateTime.Now},request path:{request.Url.ToString()}");
if (path == "/getbookslist")
{
if (request.QueryString != null)
{
string countParam = request.QueryString["count"];
if (!string.IsNullOrWhiteSpace(countParam))
{
if (!int.TryParse(countParam, out cnt) || cnt < 1)
{
cnt = 0;
}
}
}
}
else if (path == "/getbook")
{
cnt = 100;
}
return cnt;
}
private static int id = 1;
private static (int, int) GetStartEnd(int interval)
{
int end = Interlocked.Add(ref id, interval);
int start = end - interval;
return (start, end);
}
static async Task<string> GetJsonStrAsync(int cnt = 1000000)
{
return await Task.Run(() =>
{
List<Book> bksList = new List<Book>();
var (start, end) = GetStartEnd(cnt);
for (int i = start; i < end; i++)
{
bksList.Add(new Book()
{
Id = i,
Name = $"Name_{i}",
ISBN = $"ISBN_{i}",
Author = $"Author_{i}",
Comment = $"Comment_{i}",
Content = $"Content_{i}",
Summary = $"Summary_{i}",
Title = $"Title_{i}",
Topic = $"Topic_{i}"
});
}
Console.WriteLine($"{DateTime.Now},Id bwtween:[{bksList.FirstOrDefault()?.Id}] and [{bksList.LastOrDefault()?.Id}]");
string jsonStr = JsonConvert.SerializeObject(bksList,Newtonsoft.Json.Formatting.None);
return jsonStr;
});
}
}
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public string ISBN { get; set; }
public string Author { get; set; }
public string Comment { get; set; }
public string Content { get; set; }
public string Summary { get; set; }
public string Title { get; set; }
public string Topic { get; set; }
}
}
![image]()
![image]()