ReactiveUI และรูปแบบ MVVM ในแอปพลิเคชัน WPF

เผยแพร่แล้ว: 2022-03-11

Reactive Programming เป็นกระบวนทัศน์การเขียนโปรแกรมแบบอะซิงโครนัสที่เกี่ยวข้องกับสตรีมข้อมูลและการแพร่กระจายของการเปลี่ยนแปลง – วิกิพีเดีย

เมื่อคุณอ่านประโยคนั้นแล้ว คุณอาจยังคงจบลงแบบเดียวกับที่ฉันอ่านเมื่อครั้งแรก: ไม่มีที่ไหนเลยที่ใกล้จะเข้าใจความเกี่ยวข้องของประโยคนั้น อุทิศให้กับแนวคิดพื้นฐานอีกเล็กน้อยและคุณจะเข้าใจถึงความสำคัญของแนวคิดนี้ได้อย่างรวดเร็ว โดยพื้นฐานแล้ว คุณอาจนึกถึงการเขียนโปรแกรมเชิงโต้ตอบในตอนแรกว่า: “การเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์บนสเตียรอยด์” ลองนึกภาพตัวจัดการเหตุการณ์เป็นสตรีม และคิดว่าการไล่ตัวจัดการแต่ละครั้งเป็นข้อมูลใหม่ในสตรีม โดยสรุป สิ่งที่คุณลงเอยด้วยการเขียนโปรแกรมเชิงโต้ตอบ

มีแนวคิดบางอย่างที่คุณอาจต้องการทำความเข้าใจก่อนที่จะเจาะลึกในการเขียนโปรแกรมเชิงโต้ตอบเพิ่มเติมอีกเล็กน้อย สิ่ง ที่สังเกตได้ คือวัตถุที่ให้คุณเข้าถึงสตรีมที่เราพูดถึง จุดประสงค์ของพวกเขาคือเพื่อให้คุณมีหน้าต่างเข้าสู่ข้อมูลในสตรีม เมื่อเปิดหน้าต่างนั้นแล้ว คุณสามารถ ดู ข้อมูลด้วยวิธีใดก็ได้ที่คุณเลือกโดยใช้ Operators และตัดสินใจว่าแอปพลิเคชันของคุณ ตอบสนอง ต่อสตรีมเมื่อใดและอย่างไร สุดท้าย คุณกำหนดผู้ สังเกตการณ์ บนสตรีมผลลัพธ์เพื่อกำหนดการดำเนินการที่จะเกิดขึ้นทุกครั้งที่สตรีมปล่อยจุดอ้างใหม่

ในทางปฏิบัติ นั่นหมายถึงว่าคุณจะควบคุมวิธีที่แอปพลิเคชันตอบสนองต่อสิ่งที่เกิดขึ้นได้มากขึ้น ไม่ว่าจะเป็นผู้ใช้ที่คลิกปุ่ม แอปของคุณได้รับการตอบกลับ HTTP หรือการกู้คืนจากข้อยกเว้น เมื่อคุณเริ่มเห็นประโยชน์ของการใช้ Reactive Programming (ซึ่งมีมากมาย) คุณจะย้อนกลับไปไม่ได้ นั่นเป็นเพียงเพราะสิ่งที่แอปทำส่วนใหญ่มีปฏิกิริยาตอบสนองต่อเหตุการณ์ที่เกิดขึ้น

นี่ไม่ได้หมายความว่าไม่มีข้อเสียสำหรับแนวทางใหม่นี้ ประการแรก เส้นโค้งการเรียนรู้นั้นค่อนข้างสูงชัน ฉันได้เห็นโดยตรงว่านักพัฒนา (รุ่นน้อง รุ่นพี่ สถาปนิก และอื่นๆ) พยายามดิ้นรนเพื่อหาสิ่งที่พวกเขาควรจะเขียนเป็นอันดับแรก โดยเรียงลำดับโค้ดของพวกเขาอย่างไร หรือจะแก้ไขข้อผิดพลาดได้อย่างไร คำแนะนำของฉันเมื่อแนะนำแนวคิดเหล่านี้ครั้งแรกคือการแสดงตัวอย่างมากมาย เมื่อนักพัฒนาเริ่มเห็นว่าสิ่งต่าง ๆ ควรจะทำงานและใช้งานอย่างไร พวกเขาจะชินกับมัน

ฉันทำงานกับแอพเดสก์ท็อปมานานกว่า 10 ปี (ส่วนใหญ่เป็น Visual Basic 6, Java Swing และ Windows Forms) ก่อนที่ฉันจะได้ใช้ Windows Presentation Foundation (WPF) เป็นครั้งแรกในปี 2010 โดยพื้นฐานแล้ว WPF ถูกสร้างขึ้นเพื่อ แทนที่ Windows Forms ซึ่งเป็นเฟรมเวิร์กการพัฒนาเดสก์ท็อปตัวแรกของ .NET

ความแตกต่างที่สำคัญระหว่าง WPF และ Windows Forms นั้นมีมากมาย แต่สิ่งที่สำคัญที่สุดคือ:

  • WPF ใช้กระบวนทัศน์การพัฒนาใหม่ซึ่งแข็งแกร่งกว่าและผ่านการทดสอบอย่างละเอียดถี่ถ้วนแล้ว
  • ด้วย WPF คุณสามารถแยกการออกแบบและการเข้ารหัสที่แข็งแกร่งสำหรับ UI ได้
  • WPF อนุญาตให้ปรับแต่งและควบคุม UI ของคุณได้มากมาย

เมื่อฉันเริ่มเรียนรู้ WPF และความสามารถของ WPF แล้ว ฉันก็ชอบมันมาก! ฉันไม่อยากจะเชื่อเลยว่ารูปแบบ MVVM นั้นใช้งานง่ายเพียงใดและการผูกคุณสมบัติทำงานได้ดีเพียงใด ฉันไม่คิดว่าจะพบสิ่งใดที่จะปรับปรุงวิธีการทำงานนั้นได้ จนกว่าฉันจะสะดุดกับ Reactive Programming และการใช้งานกับ WPF:

ในโพสต์นี้ ฉันหวังว่าจะสามารถแสดงการใช้งานแอป WPF อย่างง่าย ๆ โดยใช้ Reactive Programming ด้วยรูปแบบ MVVM และเพื่อเข้าถึง REST API

แอปพลิเคชันจะสามารถ:

  • ติดตามรถและสถานที่ของพวกเขา
  • ดึงข้อมูลมาจากแหล่งจำลอง
  • แสดงข้อมูลนี้แก่ผู้ใช้ใน Bing Maps WPF Control

สถาปัตยกรรม

คุณจะสร้างไคลเอนต์ WPF ที่ใช้บริการ RESTful Web API Core 2

ฝั่งไคลเอ็นต์:

  • WPF
  • ปฏิกิริยาUI
  • การฉีดพึ่งพา
  • รูปแบบ MVVM
  • ปรับโฉม
  • การควบคุม Bing Maps WPF
  • เพื่อการทดสอบเท่านั้น เราจะใช้ Postman

ฝั่งเซิร์ฟเวอร์:

  • .NET C# เว็บ API Core 2
  • การฉีดพึ่งพา
  • การรับรองความถูกต้อง JWT

สิ่งที่คุณต้องการ:

  • ชุมชน Visual Studio 2017 (หรือรุ่นใด ๆ ที่คุณอาจมี)

แบ็กเอนด์

เริ่มต้นอย่างรวดเร็ว

เริ่มโซลูชัน Visual Studio ใหม่ด้วยเว็บแอปพลิเคชัน ASP.NET Core

wpf reactiveui: เว็บแอปพลิเคชั่น ASP.NET Core แบบวิชวลสตูดิโอใหม่

กำหนดค่าให้เป็น API เนื่องจากเราจะใช้เป็นส่วนหลังสำหรับแอป WPF ของเราเท่านั้น

wpf reactiveui: กำหนดค่าเป็น API

เราควรลงเอยด้วยโซลูชัน VS ที่มีโครงสร้างคล้ายกับสิ่งนี้:

wpf reactiveui: VS ตัวอย่างโซลูชัน

จนถึงตอนนี้ เรามีทุกสิ่งที่จำเป็นในการเริ่มต้น REST API แบ็กเอนด์ หากเราดำเนินโครงการ มันจะโหลดเว็บเบราว์เซอร์ (ที่เราตั้งค่าไว้บน Visual Studio) ชี้ไปที่เว็บไซต์ที่โฮสต์บน IIS Express ซึ่งจะแสดงการตอบสนองต่อการเรียก REST ด้วยวัตถุ JSON

ตอนนี้ เราจะตั้งค่าการตรวจสอบสิทธิ์ JWT สำหรับบริการ REST ของเรา

ที่ส่วนท้ายของไฟล์ startup.cs ให้เพิ่มบรรทัดต่อไปนี้

 static readonly byte[] JwtKey = Encoding.ASCII.GetBytes(@"this is a test key"); private void LoadJwtAuthorization(IServiceCollection services) { services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(x => { x.Events = new JwtBearerEvents { OnTokenValidated = context => { var userId = int.Parse(context.Principal.Identity.Name); if (userId == 0) { //Handle user validation against DB context.Fail("Unauthorized"); } return Task.CompletedTask; } }; x.RequireHttpsMetadata = false; x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(JwtKey), ValidateIssuer = false, ValidateAudience = false }; }); }

นอกจากนี้ ภายในเมธอด ConfigureServices ให้เรียกเมธอดที่เราเพิ่งสร้างขึ้นก่อนที่จะเรียกเมธอด AddMvc

 public void ConfigureServices(IServiceCollection services) { LoadJwtAuthorization(services); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }

สุดท้าย ปรับวิธี Configure ให้มีลักษณะดังนี้:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); }

จนถึงตอนนี้ เราได้สร้าง JWT Authentication เพื่อใช้กับคอนโทรลเลอร์ของเรา หากมีการกำหนดไว้ ต่อไป เราจะปรับตัวควบคุมเพื่อให้ใช้การรับรองความถูกต้องที่เราอธิบายไว้

ใน ValuesController เราจะเพิ่ม AuthorizeAttribute เพื่อให้มีลักษณะดังนี้:

 [Route("api/[controller]")] [ApiController] [Authorize] public class ValuesController : ControllerBase { ... }

ตอนนี้ หากเราพยายามเรียกใช้บริการ เราจะได้รับข้อผิดพลาด 401 Unauthorized ดังนี้:

ข้อผิดพลาดที่ไม่ได้รับอนุญาตในบุรุษไปรษณีย์

ดังนั้น เราจะต้องเพิ่มวิธีการตรวจสอบผู้ใช้ของเรา เพื่อความง่ายในที่นี้ เราจะทำในคลาส ValuesController เดียวกัน

 [AllowAnonymous] [HttpPost("authenticate")] public IActionResult Authenticate([FromBody]JObject userInfo) { var username = userInfo["username"].ToString(); var password = userInfo["password"].ToString(); //We would validate against the DB if (username != "user" || password != "123") { return BadRequest(new { message = "Username or password is incorrect" }); } // return basic user info (without password) and token to store on the front-end return Ok(CreateUserToken(1)); } private string CreateUserToken(int userId) { var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, userId.ToString()) }), Expires = DateTime.UtcNow.AddDays(7), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Startup.JwtKey), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); }

ตอนนี้ เราได้สร้างวิธีการด้วยการเข้าถึงแบบไม่ระบุชื่อ ซึ่งหมายความว่าลูกค้าทั้งหมด แม้จะไม่ได้รับการพิสูจน์ตัวตน จะสามารถเรียกมันโดยใช้ข้อความ POST ที่มีอ็อบเจ็กต์ JSON และส่งสตริงสำหรับชื่อผู้ใช้และสตริงสำหรับรหัสผ่าน

เมื่อเราตรวจสอบกับบุรุษไปรษณีย์ เราได้รับสิ่งนี้:

ปฏิกิริยา WPF: การตรวจสอบสิทธิ์

ดังที่เราเห็น ผลลัพธ์ของวิธีการตรวจสอบสิทธิ์คือสตริงที่เราจำเป็นต้องใช้เป็นโทเค็นสำหรับการโทรทุกครั้งที่เราต้องการทำกับ API

เมื่อโทเค็นถูกรวมไว้ในส่วนหัวของข้อความแล้ว การตรวจสอบความถูกต้องจะเกิดขึ้น และหากผ่านพารามิเตอร์ที่ถูกต้อง บริการจะรันเมธอดและคืนค่าของเมธอด ตัวอย่างเช่น หากตอนนี้เราเรียกตัวควบคุมค่าและส่งโทเค็น เราจะได้ผลลัพธ์เหมือนเดิม:

ปฏิกิริยา WPF: การตรวจสอบสิทธิ์ 2

ตอนนี้ เราจะสร้างวิธีการหาค่าละติจูดและลองจิจูดของรถปัจจุบันที่เรากำลังติดตาม อีกครั้ง เพื่อความเรียบง่าย มันจะเป็นเพียงวิธีการจำลองที่จะกลับในตำแหน่งแบบสุ่มในตอนแรก และเริ่มเคลื่อนรถเป็นระยะทางคงที่ทุกครั้งที่มีการเรียกวิธีการ

ขั้นแรก เราปรับเมธอด Get(int id) ในคลาส ValuesController เพื่อให้มีลักษณะดังนี้:

 [HttpGet("{id}")] public ActionResult<string> Get(int id) { var location = LocationHelper.GetCurrentLocation(id); dynamic jsonObject = new JObject(); jsonObject.Latitude = location.latitude; jsonObject.Longitude = location.longitude; return jsonObject.ToString(); }

จากนั้น เราเพิ่มคลาส LocationHelper ใหม่ที่จะจัดการตำแหน่งปัจจุบันและอนาคตของรถยนต์ที่จะถูกติดตาม

 public static class LocationHelper { private static readonly Random Randomizer = new Random(); private const double PositionDelta = 0.0001d; internal static (double latitude, double longitude) GetCurrentLocation(int id) { if (!Locations.ContainsKey(id)) { Locations.Add(id, default((double latitude, double longitude))); } //This method updates the last known location for the car and simulates its movement UpdateLocation(id); return Locations[id]; } private static void UpdateLocation(int id) { (double latitude, double longitude)loc = Locations[id]; //If the default value is found, randomly assign a starting point. if (loc.latitude == default(double) && loc.longitude == default(double)) { loc = Locations[id] = GetRandomStartingPoint(); } if (Randomizer.Next(2) > 0) { //In this scenario we simulate an updated latitude loc.latitude = loc.latitude + PositionDelta; } else { //Simulated longitude change loc.longitude = loc.longitude + PositionDelta; } Locations[id] = loc; } private static (double latitude, double longitude) GetRandomStartingPoint() { //Set inside the continental US return (Randomizer.Next(31, 49), Randomizer.Next(-121, -75)); } private static readonly Dictionary<int, (double latitude, double longitude)> Locations = new Dictionary<int, (double latitude, double longitude)>(); }

นั่นแหละสำหรับแบ็กเอนด์

ส่วนหน้า:

ตอนนี้เราจะสร้างแอป WPF ใหม่ เมื่อเราสร้างเสร็จแล้ว Visual Studio จะเพิ่มโครงการใหม่ที่มีโครงสร้างต่อไปนี้ในโซลูชันของเรา

โครงสร้างแอป WPF

การควบคุม Bing Maps:

ในการใช้การควบคุม WPF สำหรับ Bing Maps เราจำเป็นต้องติดตั้ง SDK (ที่อ้างอิงด้านบน) และเพิ่มเป็นข้อมูลอ้างอิงไปยังแอปพลิเคชัน WPF ของเรา DLL อาจอยู่บนเส้นทางอื่น ทั้งนี้ขึ้นอยู่กับตำแหน่งที่คุณติดตั้ง ฉันติดตั้งไว้ที่ตำแหน่งเริ่มต้นและเพิ่มดังนี้:

ขั้นตอนที่ 1: คลิกขวาที่ส่วนอ้างอิงสำหรับโครงการ WPF ของคุณแล้วคลิก
ขั้นตอนที่ 1: คลิกขวาที่ส่วนข้อมูลอ้างอิงสำหรับโครงการ WPF ของคุณแล้วคลิก "เพิ่มข้อมูลอ้างอิง"

ขั้นตอนที่ 2: เรียกดูเส้นทางของการติดตั้ง Bing Maps WPF Control ของคุณ
ขั้นตอนที่ 2: เรียกดูเส้นทางของการติดตั้ง Bing Maps WPF Control ของคุณ

ขั้นตอนที่ 3: คลิกที่ ตกลง เพื่อเพิ่มลงในโครงการ
ขั้นตอนที่ 3: คลิกที่ ตกลง เพื่อเพิ่มลงในโครงการ

ต่อไป เราจะเพิ่มแพ็คเกจ nuget สำหรับ reactiveui , reactiveui-wpf และ refit ให้เข้ากับโปรเจ็กต์ WPF ของเรา ซึ่งจะทำให้เราสร้างโมเดลการดูโดยใช้ Reactive Programming รวมถึงใช้ REST API ของเรา

ขั้นตอนที่ 1: คลิกขวาที่ส่วนอ้างอิงของโครงการ WPF ของคุณแล้วคลิกจัดการแพ็คเกจ NuGet
ขั้นตอนที่ 1: คลิกขวาที่ส่วนอ้างอิงของโครงการ WPF ของคุณแล้วคลิกจัดการแพ็คเกจ NuGet

ขั้นตอนที่ 2: บนแท็บเรียกดู ค้นหา 'reactiveui' คลิกติดตั้ง ค้นหา 'reactiveui-wpf' คลิกติดตั้ง และสุดท้าย ค้นหา 'refit' และคลิกติดตั้ง
ขั้นตอนที่ 2: บนแท็บเรียกดู ค้นหา 'reactiveui' คลิกติดตั้ง ค้นหา 'reactiveui-wpf' คลิกติดตั้ง และสุดท้าย ค้นหา 'refit' และคลิกติดตั้ง

ตอนนี้เราจะสร้าง ViewModel ของเรา เพิ่มคลาสใหม่ชื่อ MainViewModel.cs และทำให้มีลักษณะดังนี้:

 public class MainViewModel : ReactiveObject { #region Private Members private readonly ITrackingService _service; private readonly ISubject<(double latitude, double longitude)> _locationUpdate; #endregion #region Methods public MainViewModel() { _service = Locator.Current.GetService<ITrackingService>(); _locationUpdate = new Subject<(double latitude, double longitude)>(); UpdateCar = ReactiveCommand.Create(() => { var parsedCorrectly = int.TryParse(NewCarToFollow, out int newCar); NewCarToFollow = null; if (!parsedCorrectly) { MessageBox.Show("There was an error reading the number of the car to follow. Please, review it.", "Car Tracking Service", MessageBoxButton.OK, MessageBoxImage.Warning); return; } FollowedCar = newCar; }, canExecute: this.WhenAnyValue(x => x.NewCarToFollow).Select(x => !string.IsNullOrWhiteSpace(x))); /*This Scheduled method is where we get the location for the car being followed every 500 ms. We call the service with the car id, our JWT Token, and transform the result to a ValueTuple (double latitude, double longitude) to pass to our Subject's OnNext method so it can be received by the view */ Scheduler.Default.SchedulePeriodic(TimeSpan.FromMilliseconds(500), () => _service.GetLocation(FollowedCar, App.GetToken()) .Select(jo => ( latitude: double.Parse(jo["Latitude"].ToString()), longitude: double.Parse(jo["Longitude"].ToString()) )).Subscribe(newLocation => _locationUpdate.OnNext(newLocation))); } #endregion #region Properties private string _newCarToFollow; public string NewCarToFollow { get => _newCarToFollow; set => this.RaiseAndSetIfChanged(ref _newCarToFollow, value); } private int _followedCar = 1; public int FollowedCar { get => _followedCar; set => this.RaiseAndSetIfChanged(ref _followedCar, value); } public IObservable<(double latitude, double longitude)> LocationUpdate => _locationUpdate; private ReactiveCommand _updateCar; public ReactiveCommand UpdateCar { get => _updateCar; set => this.RaiseAndSetIfChanged(ref _updateCar, value); } #endregion }

เพื่อให้มุมมองทราบว่ามี ViewModel แนบอยู่และสามารถใช้งานได้ เราจำเป็นต้องทำการเปลี่ยนแปลงเล็กน้อยในไฟล์ MainView.xaml.cs

 public partial class MainWindow : IViewFor<MainViewModel> { public MainWindow() { InitializeComponent(); ViewModel = Locator.CurrentMutable.GetService<MainViewModel>(); /*Our ViewModel exposes an IObservable with a parameter of type ValueTuple (double latitude, double longitude) and it gets called every time the ViewModel updates the car's location from the REST API.*/ ViewModel.LocationUpdate .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(SetLocation); } private void SetLocation((double latitude, double longitude) newLocation) { //New location for the tracked vehicle. var location = new Location(newLocation.latitude, newLocation.longitude); //Remove previous pin myMap.Children.Clear(); //Center pin and keep same Zoom Level myMap.SetView(location, myMap.ZoomLevel); var pin = new Pushpin { Location = location, Background = Brushes.Green }; //Add new pin to the map myMap.Children.Add(pin); } /// <summary> /// Allows the ViewModel to be used on the XAML via a dependency property /// </summary> public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MainViewModel), typeof(MainWindow), new PropertyMetadata(default(MainViewModel))); /// <summary> /// Implementation for the IViewFor interface /// </summary> object IViewFor.ViewModel { get => ViewModel; set => ViewModel = (MainViewModel)value; } /// <summary> /// Regular property to use the ViewModel from this class /// </summary> public MainViewModel ViewModel { get => (MainViewModel)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } }

จากนั้น เราจะแก้ไขไฟล์ MainWindow.xaml เพื่อให้มีลักษณะดังนี้:

 <Window x:Class="WpfApp.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:wpf="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF" xmlns:local="clr-namespace:WpfApp" DataContext="{Binding ViewModel,RelativeSource={RelativeSource Self}}" d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d" WindowStartupLocation="CenterScreen" Title="Car Tracker" Height="800" Width="1200"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <DockPanel> <StackPanel Orientation="Horizontal" Margin="10" DockPanel.Dock="Left"> <Label>Car to follow</Label> <TextBox Width="50" Text="{Binding NewCarToFollow, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/> <Button Margin="15,0,0,0" Content="Update Followed Car" Command="{Binding UpdateCar}"/> </StackPanel> <TextBlock Text="{Binding FollowedCar,StringFormat=Following Car: {0}}" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" DockPanel.Dock="Right"/> </DockPanel> <wpf:Map x:Name="myMap" ZoomLevel="15" Grid.Row="1" Margin="10" CredentialsProvider="ENTER-YOUR-BING-MAPS-CREDENTIAL-HERE"/> </Grid> </Window>

สิ่งสำคัญคือต้องปรับคุณสมบัติ CredentialsProvider ด้วยคีย์ Bing Maps ของคุณเอง

เพื่อให้สามารถเข้าถึง REST API ได้ เราจะใช้การปรับใหม่ สิ่งที่เราต้องทำคือสร้างอินเทอร์เฟซที่อธิบายวิธีการของ API ที่เราจะใช้ ดังนั้นเราจึงสร้างอินเทอร์เฟซใหม่ที่เรียกว่า ITrackingService โดยมีเนื้อหาดังต่อไปนี้:

 public interface ITrackingService { [Post("/api/values/authenticate")] IObservable<string> Authenticate([Body] JObject user); [Get("/api/values/{id}")] IObservable<JObject> GetLocation(int id, [Header("Authorization")] string authorization); }

สุดท้าย เราแก้ไขคลาส App เพื่อรวมการแทรกการพึ่งพา (โดยใช้ Splat ซึ่งถูกเพิ่มเมื่อเรารวมการอ้างอิงถึง reactiveui ) ตั้งค่า ServerUri (ซึ่งคุณควรเปลี่ยนเป็นพอร์ตใดก็ตามที่คุณได้รับเมื่อคุณเรียกใช้ REST API) และจำลองของเรา เข้าสู่ระบบเมื่อเริ่มต้นแอปพลิเคชัน

 public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); SetDependencyInjection(); LogIn(); } private void SetDependencyInjection() { Locator.CurrentMutable.RegisterLazySingleton(() => RestService.For<ITrackingService>(ServerUri), typeof(ITrackingService)); Locator.CurrentMutable.RegisterLazySingleton(() => new MainViewModel(), typeof(MainViewModel)); } private static string Token; private const string ServerUri = "http://localhost:54587"; private void LogIn() { try { var userInfo = new JObject { ["username"] = "user", ["password"] = "123" }; Token = Locator.Current.GetService<ITrackingService>() .Authenticate(userInfo) .Wait(); } catch { MessageBox.Show("There was an error validating the user. Is the service up?"); Shutdown(); } } internal static string GetToken() { return $"Bearer {Token}"; } }

สุดท้าย เมื่อเราเรียกใช้แอปพลิเคชัน เราจะสามารถเห็นการจำลองแบบเรียลไทม์ของรถที่กำลังเคลื่อนที่ โดยนำพิกัดมาจาก REST API ทุกๆ 500 มิลลิวินาที ผู้ใช้ยังสามารถเปลี่ยนรถที่กำลังติดตามเป็น ID อื่น ๆ และจะมีการสร้างข้อมูลชุดใหม่ขึ้น

ฉันหวังว่าตัวอย่างเล็กๆ นี้จะแสดงพื้นฐานของการจัดการ REST API ด้วย Reactive Programming ใน WPF ในลักษณะที่เข้าถึงได้

คุณสามารถดาวน์โหลดทั้งโครงการต้นทางจากที่เก็บข้อมูลนี้ได้ตลอดเวลา

มีบางพื้นที่ที่จะดำเนินการต่อกับตัวอย่างนี้อาจช่วยให้คุณเข้าใจมากขึ้น:

  • สร้างหน้าต่างเข้าสู่ระบบและอนุญาตให้ผู้ใช้เข้าสู่ระบบและออกจากระบบ
  • ตรวจสอบข้อมูลผู้ใช้จากฐานข้อมูล
  • สร้างบทบาทของผู้ใช้ที่แตกต่างกันและจำกัดวิธีการบางอย่างใน REST API เพื่อให้เฉพาะผู้ใช้ที่มีบทบาทบางอย่างเท่านั้นที่สามารถเข้าถึงได้
  • ทำความเข้าใจเกี่ยวกับ Reactive Programming ที่ดำเนินการผ่านโอเปอเรเตอร์ทั้งหมดและพฤติกรรมของพวกมันด้วย Rx Marbles Rx Marbles เป็นแอปพลิเคชั่นที่ใช้งานง่ายที่ให้คุณโต้ตอบกับสตรีมและปรับใช้โอเปอเรเตอร์กับจุดข้อมูลในนั้น

บทสรุป

การเขียนโปรแกรมเชิงโต้ตอบสามารถพิสูจน์ได้ว่ามีประโยชน์เมื่อพยายามบรรลุวิธีการควบคุมโดยใช้โปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์โดยไม่ประสบปัญหาตามปกติซึ่งมีอยู่ในกระบวนทัศน์นี้ การใช้งานสำหรับการพัฒนาใหม่นั้นง่ายพอๆ กับการเพิ่มข้อมูลอ้างอิงสองสามรายการไปยังไลบรารีโอเพ่นซอร์สที่ได้รับการสนับสนุนอย่างดี แต่ที่สำคัญที่สุด การรวมเข้ากับ codebases ที่มีอยู่สามารถก้าวหน้าได้ และไม่ควรทำลายความเข้ากันได้ย้อนหลังกับส่วนประกอบที่ไม่ได้ใช้งาน บทความนี้กล่าวถึง Reactive Programming สำหรับ WPF แต่มีพอร์ตสำหรับภาษาและเฟรมเวิร์กหลักๆ ส่วนใหญ่ ซึ่งทำให้ Reactive Programming เป็นการผจญภัยที่ดีสำหรับนักพัฒนาทุกประเภท

สำหรับการออกกำลังกาย ต่อไป คุณควร:

  • ขยายพฤติกรรมของโครงการโดย
    • การเพิ่มฐานข้อมูลสำหรับผู้ใช้ รถ และตำแหน่ง
    • รับตำแหน่งรถยนต์จากฐานข้อมูลและแสดงให้ผู้ใช้เห็น อนุญาตให้ผู้ใช้สำรวจการเคลื่อนไหวของรถในช่วงเวลาหนึ่ง
    • การเพิ่มสิทธิ์ผู้ใช้ ให้ผู้ดูแลระบบสร้างรถยนต์และผู้ใช้ใหม่ และให้สิทธิ์การเข้าถึงแบบอ่านอย่างเดียวแก่ผู้ใช้ทั่วไป เพิ่มบทบาทในการพิสูจน์ตัวตน JWT
  • ตรวจสอบซอร์สโค้ดสำหรับส่วนขยายปฏิกิริยา .NET ใน https://github.com/dotnet/reactive