فعال سازی عملیات CRUD در Kendo UI Grid
- زمانیکه که خطای Invalid JSON primitive را در سمت سرور دریافت میکنید، یعنی سمت کلاینت، اطلاعات را با فرمت نادرستی به سمت سرور ارسال میکند و این فرمت JSON نیست. یعنی در قسمت DataSource اطلاعات اضافی و یا نادرستی وجود دارند که با View ذکر شده هماهنگ نیستند.
همانطور که عنوان شد، قسمتهای سمت سرور و سمت کلاینت را با مثال ارسالی هماهنگ کنید و انطباق دهید. اگر وب API است این مثال و اگر MVC است، این مثال.
بازگرداندن Stream فایل از WCF
به نظرم در مثال سیلورلایت فایل PDF روی سرور ذخیره میشود و بعد به کاربر نمایش داده میشود
آیا لازم است که فایل روی سرور ذخیره شود یعنی آیا میتوان فایل را به صورت Stream تولید کرد
.Generate(data => data.AsPdfStream(new MemoryStream()));
و بعد PdfStreamOutput آن را بازگرداند؟
PdfRpt-1.6.zip
Generate(data => data.FlushInBrowser(fileName))
اگر علاقمند باشید که اسمبلی را با لهجهی غلیظ هندی انگلیسی فرا بگیرید، لینکهای زیر از سایت SecurityTube کمک شایانی خواهند کرد!
Part-2-Virtual-Memory-Organization
Part-3-GDB-Usage-Primer
Part-4-Hello-World
Part-5-Data-Types
Part-6-Working-with-Strings
Part-7-Moving-Data
Part-8-Unconditional-Branching
Part-9-Conditional-Branching
Part-10-Functions
Part-11-Functions-Stack
معماری لایه بندی شده، یک معماری بسیار همه گیر میباشد. به این خاطر که به راحتی SOC ، decoupling و قدرت درک کد را بسیار بالا میبرد. امروزه کمتر برنامه نویس و فعال حوضهی نرم افزاری است که با لایههای کلی و وظایف آنها آشنا نباشد ( UI layer آنچه که ما میبینیم، middle layer برای مقاصد منطق کاری، data access layer برای هندل کردن دسترسی به دادهها). اما مسئله ای که بیشتر برنامه نویسان و توسعه دهندگان نرم افزار با استانداردهای آن آشنا نیستند، راههای تبادل دادهها مابین layer ها میباشد. در این مقاله سعی داریم راههای تبادل دادهها را مابین لایهها، تشریح کنیم.
Layers و Tiers با هم متفاوت هستند
Layer با Tier متفاوت است. هنگامیکه در مورد مفهوم layer و Tier دچار شک شدید،
دیاگرام ذیل میتواند به شما بسیار کمک کند. layer به مجزاسازی
منطقی کد و Tier هم به مجزا سازی فیزیکی در ماشینهای مختلف اطلاق میشود. توجه داشته
باشید که این نکته یک شفاف سازی کلی در مورد یک مسئله مهم بود.
دادههای وارد شونده( incoming ) و خارج شونده( outgoing )
ما باید تبادل دادهها را از دو جنبه مورد بررسی قرار دهیم؛ اول اینکه دادهها چگونه به سمت لایه Data Access میروند، دوم اینکه دادهها چگونه به لایه UI پاس میشوند، در ادامه شما دلیل این مجزا سازی را درک خواهید کرد.
روش اول: Non-uniform
این روش اولین روش و احتمالا عمومیترین روش میباشد. خوب، اجازه دهید از لایهی UI به لایه DAL شروع کنیم. دادهها از لایه UI به Middle با استفاده از getter ها و setter ها ارسال خواهد شد. کد ذیل این مسئله را به روشنی نمایش میدهد.
Customer objCust = new Customer(); objCust.CustomerCode = "c001"; objCust.CustomerName = "Shivprasad";
بعد از آن، از لایه Middle به لایه Data Access دادهها با استفاده از مجزاسازی به وسیله comma و آرایهها و سایر روشهای non-uniform پاس داده میشوند. در کد ذیل به متد Add دقت کنید که چگونه فراخوانی به لایه Data Access را با استفاده از پارامترهای ورودی انجام میدهد.
public class Customer { private string _CustomerName = ""; private string _CustomerCode = ""; public string CustomerCode { get { return _CustomerCode; } set { _CustomerCode = value; } } public string CustomerName { get { return _CustomerName; } set { _CustomerName = value; } } public void Add() { CustomerDal obj = new CustomerDal(); obj.Add(_CustomerName,_CustomerCode); } }
کد ذیل، متد add در لایه Data Access را با استفاده از دو متد نمایش میدهد.
public class CustomerDal { public bool Add(string CustomerName,string CustomerCode) { // Insert data in to DB } }
بنابراین اگر بخواهیم به صورت خلاصه نحوه پاس دادن دادهها را در روش non-uniform بیان کنیم، شکل ذیل به زیبایی این مسئله را نشان میدهد.
· از لایه UI به لایه Middle با استفاده از setter و getter
· از لایه Middle به لایه data access با استفاده از comma ، input ، array
حال
نوبت این است بررسی کنیم که چگونه دادهها از DAL به UI در روش non-uniform پاس
خواهند شد. بنابراین اجازه دهید که اول از UI شروع
کنیم. از لایه UI دادهها با استفاده از object های لایه Middle واکشی میشوند.
Customer obj = new Customer(); List<Customer> oCustomers = obj.getCustomers();
از لایه Middle هم دادهها با استفاده از dataset ، datatable و xml پاس خواهند شد. مهمترین مسئله برای لایه middle ، loop بر روی dataset و تبدیل آن به strong type object ها میباشد. برای مثال میتوانید کد تابع getCustomers که بر روی dataset ، loop میزند و یک لیست از Customer ها را آماده میکند در ذیل مشاهده کنید. این تبدیل باید انجام شود، به این دلیل که UI به کلاسهای strongly typed دسترسی دارد.
public class Customer { private string _CustomerName = ""; private string _CustomerCode = ""; public string CustomerCode { get { return _CustomerCode; } set { _CustomerCode = value; } } public string CustomerName { get { return _CustomerName; } set { _CustomerName = value; } } public List<Customer> getCustomers() { CustomerDal obj = new CustomerDal(); DataSet ds = obj.getCustomers(); List<Customer> oCustomers = new List<Customer>(); foreach (DataRow orow in ds.Tables[0].Rows) { // Fill the list } return oCustomers; } }
با انجام این تبدیل به یکی از بزرگترین اهداف معماری لایه بندی شده میرسیم؛ یعنی اینکه « UI نمیتواند به طور مستقیم به کامپوننتهای لایه Data Access مانند ADO.NET ، OLEDB و غیره دستیابی داشته باشد. با این روش اگر ما در ادامه متدولوژی Data Access را تغییر دهیم تاثیری بر روی لایه UI نمیگذارد.» آخرین مسئله اینکه کلاس CustomerDal یک Dataset را با استفاده از ADO.NET بر میگرداند و Middle از آن استفاده میکند.
public class CustomerDal { public DataSet getCustomers() { // fetch customer records return new DataSet(); } }
حال اگر بخواهیم حرکت دادهها را به لایه UI، به صورت خلاصه بیان کنیم، شکل ذیل کامل این مسئله را نشان میدهد.
· دادهها از لایه DAL به لایه Middle با استفاده از Dataset ، Datareader ، XML ارسال خواهند شد.
· از لایه Middle به UI از strongly typed classes استفاده میشود.
مزایا و معایب روش non-uniform
یکی از مزایای non-uniform
· به راحتی قابل پیاده سازی میباشد، در مواردی که روش data access تغییر نمیکند این روش کارآیی لازم را دارد.
تعدادی از معایب این روش
· به خاطر اینکه یک ساختار uniform نداریم، بنابراین نیاز داریم که همیشه در هنگام عبور از یک لایه به یک لایهی دیگر از یک ساختار به یک ساختار دیگر تبدیل را انجام دهیم.
· برنامه نویسان از روشهای خودشان برای پاس دیتا استفاده میکنند؛ بنابراین این مسئله خود باعث پیچیدگی میشود.
· اگر برای مثال شما بخواهید متدولوژی Data Access خود را تغییر دهید، تغییرات بر تمام لایهها تاثیر میگذارد.
معرفی قالبهای جدید شروع پروژههای Blazor در دات نت 8
پس از نصب SDK دات نت 8، دیگر خبری از قالبهای قدیمی پروژههای blazor server و blazor wasm نیست! در اینجا در ابتدا باید مشخص کرد که سطح تعاملی برنامه در چه حدی است. در ادامه 4 روش شروع پروژههای Blazor 8x را مشاهده میکنید که توسط پرچم interactivity--، نوع رندر برنامه در آنها مشخص شدهاست:
اجرای قسمتهای تعاملی برنامه بر روی سرور:
dotnet new blazor --interactivity Server
اجرای قسمتهای تعاملی برنامه در مرورگر، توسط فناوری وباسمبلی:
dotnet new blazor --interactivity WebAssembly
برای اجرای قسمتهای تعاملی برنامه، ابتدا حالت Server فعالسازی میشود تا فایلهای WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده میکند:
dotnet new blazor --interactivity Auto
فقط از حالت SSR یا همان static server rendering استفاده میشود (این نوع برنامهها تعاملی نیستند):
dotnet new blazor --interactivity None
سایر گزینهها را با اجرای دستور dotnet new blazor --help میتوانید مشاهده کنید.
نکتهی مهم! در قالبهای آمادهی Blazor 8x، حالت SSR، پیشفرض است.
هرچند در تمام پروژههای فوق، انتخاب حالتهای مختلف رندر را مشاهده میکنید، اما این انتخابها صرفا دو مقصود مهم را دنبال میکنند:
الف) تنظیم فایل Program.cs برنامه جهت افزودن وابستگیهای مورد نیاز، به صورت خودکار.
ب) ایجاد پروژهی کلاینت (علاوه بر پروژهی سرور)، در صورت نیاز. برای مثال حالتهای وباسمبلی و Auto، هر دو به همراه یک پروژهی کلاینت وباسمبلی هم هستند؛ اما حالتهای Server و None، خیر.
در تمام این پروژهها هر صفحه و یا کامپوننتی که ایجاد میشود، به صورت پیشفرض بر اساس SSR رندر و نمایش داده خواهد شد؛ مگر اینکه به صورت صریحی این نحوهی رندر را بازنویسی کنیم. برای مثال مشخص کنیم که قرار است بر اساس Blazor Server اجرا شود و یا وباسمبلی و یا حالت Auto.
بررسی حالت Server side rendering
برای بررسی این حالت یک پوشهی جدید را ایجاد کرده و توسط خط فرمان، دستور dotnet new blazor --interactivity Server را در ریشهی آن اجرا میکنیم. پس از ایجاد ساختار ابتدایی پروژه بر اساس این قالب انتخابی، فایل Program.cs جدید آن، چنین شکلی را دارد:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>().AddInteractiveServerRenderMode(); app.Run();
server-side rendering به این معنا است که برنامهی سمت سرور، کل DOM و HTML نهایی را تولید کرده و به مرورگر کاربر ارائه میکند. مرورگر هم این DOM را نمایش میدهد. فقط همین! در اینجا هیچ خبری از اتصال دائم SignalR نیست و محتوای ارائه شده، یک محتوای استاتیک است. این حالت رندر، برای ارائهی محتواهای فقط خواندنی غیرتعاملی، فوق العادهاست؛ امکان از لحظهای که نیاز به کلیک بر روی دکمهای باشد، دیگر پاسخگو نیست. به همین جهت در اینجا امکان تعاملی کردن تعدادی از کامپوننتهای ویژه و مدنظر نیز پیشبینی شدهاند تا بتوان به ترکیبی از server-side rendering و client-side rendering رسید.
حالت پیشفرض در اینجا، ارائهی محتوای استاتیک است. بنابراین هر کامپوننتی در اینجا ابتدا بر روی سرور رندر شده (HTML ابتدایی آن آماده شده) و به سمت مرورگر کاربر ارسال میشود. اگر کامپوننتی نیاز به امکانات تعاملی داشت باید آنرا دقیقا توسط ویژگی InteractiveXYZ مشخص کند؛ مانند مثال زیر:
@page "/counter" @rendermode InteractiveServer <PageTitle>Counter</PageTitle> <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
در ادامه، مجددا سطر کامنت شده را به حالت عادی برگردانید و سپس برنامه را اجرا کنید. پیش از باز کردن صفحهی Counter، ابتدا developer tools مرورگر خود را گشوده و برگهی network آنرا انتخاب و سپس صفحهی Counter را باز کنید. در این لحظهاست که مشاهده میکنید یک اتصال وبسوکت برقرار شد. این اتصال است که قابلیتهای تعاملی صفحه را برقرار کرده و مدیریت میکند (این اتصال دائم SignalR است که این صفحه را همانند برنامههای Blazor Web Server پیشین مدیریت میکند).
یک نکته: در برنامههای Blazor Server سنتی، امکان فعالسازی قابلیتی به نام prerender نیز وجود دارد. یعنی سرور، ابتدا صفحه را رندر کرده و محتوای استاتیک آنرا به سمت مرورگر کاربر ارسال میکند و سپس اتصال SignalR برقرار میشود. در دات نت 8، این حالت، حالت پیشفرض است. اگر آنرا نمیخواهید باید به نحو زیر غیرفعالش کنید:
@rendermode InteractiveServerRenderModeWithoutPrerendering @code{ static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = new InteractiveServerRenderMode(false); }
روشی ساده برای تعاملی کردن کل برنامه
اگر میخواهید رفتار برنامه را همانند Blazor Server سابق کنید و نمیخواهید به ازای هر کامپوننت، نحوهی رندر آنرا به صورت سفارشی انتخاب کنید، فقط کافی است فایل App.razor را باز کرده:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="MyApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
<HeadOutlet @rendermode="@InteractiveServer" /> ... <Routes @rendermode="@InteractiveServer" />
در این حالت دیگر نیازی نیست تا به ازای هر کامپوننت و صفحه، نحوهی رندر را مشخص کنیم؛ چون این نحوه، از بالاترین سطح، به تمام زیرکامپوننتها به ارث میرسد (دربارهی این نکته در قسمت قبل، توضیحاتی ارائه شد).
بررسی حالت Streaming Rendering
در اینجا مثال پیشفرض Weather.razor قالب پیشفرض مورد استفادهی جاری را کمی تغییر دادهایم که کدهای نهایی آن به صورت زیر است (2 قسمت forecasts.AddRange_ را اضافهتر دارد):
@page "/weather" @attribute [StreamRendering(prerender: true)] <PageTitle>Weather</PageTitle> <h1>Weather</h1> <p>This component demonstrates showing data.</p> @if (_forecasts == null) { <p> <em>Loading...</em> </p> } else { <table class="table"> <thead> <tr> <th>Date</th> <th>Temp. (C)</th> <th>Temp. (F)</th> <th>Summary</th> </tr> </thead> <tbody> @foreach (var forecast in _forecasts) { <tr> <td>@forecast.Date.ToShortDateString()</td> <td>@forecast.TemperatureC</td> <td>@forecast.TemperatureF</td> <td>@forecast.Summary</td> </tr> } </tbody> </table> } @code { private List<WeatherForecast>? _forecasts; protected override async Task OnInitializedAsync() { // Simulate asynchronous loading to demonstrate streaming rendering await Task.Delay(500); var startDate = DateOnly.FromDateTime(DateTime.Now); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", }; _forecasts = GetWeatherForecasts(startDate, summaries).ToList(); StateHasChanged(); // Simulate asynchronous loading to demonstrate streaming rendering await Task.Delay(1000); _forecasts.AddRange(GetWeatherForecasts(startDate, summaries)); StateHasChanged(); await Task.Delay(1000); _forecasts.AddRange(GetWeatherForecasts(startDate, summaries)); } private static IEnumerable<WeatherForecast> GetWeatherForecasts(DateOnly startDate, string[] summaries) { return Enumerable.Range(1, 5) .Select(index => new WeatherForecast { Date = startDate.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = summaries[Random.Shared.Next(summaries.Length)], }); } private class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } public string? Summary { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } }
در ادامه مجددا سطر ویژگی StreamRendering را به حالت قبلی برگردانید و برنامه را اجرا کنید. در این حالت ابتدا قسمت loading ظاهر میشود و سپس در طی چند مرحله با توجه به Task.Delayهای قرار داده شده، صفحه رندر شده و تکمیل میشود.
اتفاقی که در اینجا رخ میدهد، استفاده از فناوری HTML Streaming است که مختص به مایکروسافت هم نیست. در حالت Streaming، هربار قطعهای از HTML ای که قرار است به کاربر ارائه شود، به صورت جریانی به سمت مرورگر ارسال میشود و مرورگر این قطعهی جدید را بلافاصله نمایش میدهد. نکتهی جالب این روش، عدم نیاز به اتصال SignalR و یا اجرای WASM درون مرورگر است.
Streaming rendering حالت بینابین رندر کامل در سمت سرور و رندر کامل در سمت کلاینت است. در حالت رندر سمت سرور، کل HTML صفحه ابتدا توسط سرور تهیه و بازگشت داده میشود و کاربر باید تا پایان عملیات تهیهی این HTML نهایی، منتظر باقی بماند و در این بین چیزی را مشاهده نخواهد کرد. در حالت Streaming rendering، هنوز هم همان حالت تهیهی HTML استاتیک سمت سرور برقرار است؛ به همراه تعدادی محل جایگذاری اطلاعات جدید. به محض پایان یک عمل async سمت سرور که سه نمونهی آن را در مثال فوق مشاهده میکنید، برنامه، جریان قطعهای از اطلاعات استاتیک را به سمت مرورگر کاربر ارسال میکند تا در مکانهایی از پیش تعیین شده، درج شوند.
در حالت SSR، فقط یکبار شانس ارسال کل اطلاعات به سمت مرورگر کاربر وجود دارد؛ اما در حالت Streaming rendering، ابتدا میتوان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آنرا به محض آماده شدن در طی چند مرحله بازگشت داد. در این حالت نمایش گزارشی از اطلاعاتی که ممکن است با تاخیر در سمت سرور تهیه شوند، سادهتر میشود. یعنی میتوان هربار قسمتی را که تهیه شده، برای نمایش بازگشت داد و کاربر تا مدت زیادی منتظر نمایش کل صفحه باقی نخواهد ماند.
روش نهایی معرفی نحوهی رندر صفحات
بجای استفاده از ویژگیهای RenderModeXyz جهت معرفی نحوهی رندر کامپوننتها و صفحات (که تا پیش از نگارش RTM معرفی شده بودند و چندبار هم تغییر کردند)، میتوان از دایرکتیو جدیدی به نام rendermode@ با سه مقدار InteractiveServer، InteractiveWebAssembly و InteractiveAuto استفاده کرد. برای سهولت تعریف این موارد باید سطر ذیل را به فایل Imports.razor_ اضافه نمود:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@attribute [RenderModeInteractiveServer]
@rendermode InteractiveServer
اگر هم قصد سفارشی سازی آنها را دارید، برای مثال میخواهید prerender را در آنها false کنید، روش کار به صورت زیر است:
@rendermode renderMode @code { static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false); }
در این آموزش هدف ما ایجاد برنامهای بر اساس TodoMVC است که میتواند خودش را با یک دیتابیس آنلاین همگام سازی کند.
مطمئن باشید بیشتر از 10 دقیقه وقت شمارا نخواهد گرفت !
نصب PouchDB
فایل index.html را باز کنید و فایلهای PouchDB را به آن اضافه کنید :
<script src="//cdn.jsdelivr.net/pouchdb/2.2.0/pouchdb.min.js"></script> <script src="js/base.js"></script> <script src="js/app.js"></script>
حالا PouchDB نصب شده و آماده به کار است . ( در نسخه نهایی بهتر است از فایلهای local استفاده کنید )
ایجاد بانک اطلاعاتی
بقیه کارها باید در فایل app.js انجام شود . برای شروع باید بانک اطلاعاتی جدیدی را برای درج اطلاعات خودمان ایجاد کنیم . برای ایجاد بانک اطلاعاتی خیلی ساده یک instance جدید از آبجکت PouchDB میسازیم .
var db = new PouchDB('todos'); var remoteCouch = false;
نیازی نیست که برای بانک خود، نما (Schema) ایجاد کنید! بعد از اینکه اسم را مشخص کنید، بانک آماده به کار است.
ثبت اطلاعات در بانک اطلاعاتی
اولین کاری که باید انجام دهیم این است که یک متد را ایجاد کنیم و توسط آن اطلاعات خودمان را در بانک اطلاعاتی ذخیره کنیم. نام متد را addTodo انتخاب میکنیم و کارش این است که وقتی کاربر کلید Enter را فشار داد، اطلاعات را داخل بانک اطلاعاتی ذخیره کند. متد مورد نظر به این صورت هست:
function addTodo(text) { var todo = { _id: new Date().toISOString(), title: text, completed: false }; db.put(todo, function callback(err, result) { if (!err) { console.log('Successfully posted a todo!'); } }); }
در PouchDB هر پرونده نیاز دارد تا یک فیلد unique با نام _id داشته باشد. اگر دادهای بخواهد در بانک اطلاعاتی ثبت شود و دارای _id مشابه باشد، با آن به صورت یک update رفتار خواهد شد. در اینجا ما از تاریخ با عنوان id استفاده کردیم که در این مورد خاص میباشد. شما میتواند از db.post() نیز استفاده کنید؛ اگر یک id را به صورت random میخواهید. تنها چیزی که اجباری است در هنگام ساختن یک پرونده، همین _id است و بقیه موارد کاملا اختیاری هستند.
نمایش اطلاعات
در اینجا ما از یک function کمکی به نام redrawTodosUI استفاده خواهیم کرد که وظیفهاش این است تا یک array را دریافت کرده و آن را هر طور که مشخص کردید نمایش دهد. البته آن را به سلیقه خودتان میتوانید آماده کنید.
تنها کاری که باید انجام دهیم این است که اطلاعات را از بانک اطلاعاتی استخراج کرده و به function مورد نظر پاس دهیم.
در اینجا میتوانیم به سادگی از db.allDocs برای خواندن اطلاعات از بانک اطلاعاتی استفاده کنیم.
خاصیت include_docs به PouchDB این دستور را میدهد که ما درخواست دریافت همه اطلاعات داخل پروندهها را داریم و descending هم نحوه مرتب سازی را که بر اساس _id هست، مشخص میکند تا بتوانیم جدیدترین اطلاعات را اول دریافت کنیم .
function showTodos() { db.allDocs({include_docs: true, descending: true}, function(err, doc) { redrawTodosUI(doc.rows); }); }
به روزرسانی خودکار
هر بار که ما اطلاعات جدیدی را در بانک اطلاعاتی وارد میکنیم، نیازمند این هستیم تا اطلاعات جدید را به صورت خودکار دریافت و نمایش دهیم. برای این منظور میتوانیم به روش زیر عمل کنیم :
var remoteCouch = false; db.info(function(err, info) { db.changes({ since: info.update_seq, live: true }).on('change', showTodos); });
حالا هر بار که اطلاعات جدیدی در بانک اطلاعات ثبت شود، به صورت خودکار نمایش داده خواهد شد. خاصیت live مشخص میکند که این کار میتواند بی نهایت بار انجام شود.
ویرایش اطلاعات
وقتی که کاربر یک آیتم Todo را کامل میکند، یک چک باکس را علامت میزند و اعلام میکند که این کار انجام شده است.
function checkboxChanged(todo, event) { todo.completed = event.target.checked; db.put(todo); }
این بخش شبیه به قسمت ثبت اطلاعات است، با این تفاوت که باید شامل یک فیلد _rev ( اضافه بر _id ) نیز باشد. در غیر اینصورت تغییرات ثبت نخواهد شد. این کار برای این است که اشتباهی، اطلاعاتی در بانک ثبت نشود.
حذف اطلاعات
برای حذف اطلاعات باید از متد db.remove به همراه آبجکت مورد نظر استفاده کنید .
function deleteButtonPressed(todo) { db.remove(todo); }
مانند بخش ویرایش نیز باید در اینجا هم _id و هم _rev مشخص شود.
باید توجه داشته باشید در اینجا همان آبجکتی را که از بانک فراخوانی کردهایم، به این متد پاس میدهیم.
البته شما میتونید آبجکت خودتان را نیز ایجاد کنید {_id: todo._id, _rev: todo._rev} اما خوب همان روش قبلی عاقلانهتر است و احتمال خطای کمتری دارد .
نصب CouchDB
حالا میخواهیم همگام سازی را اجرا کنیم و برای این کار نیاز هست یا CouchDB را به صورت Local نصب کنیم یا از سرویسهای آنلاین مثل IrisCouch استفاده کنید .
فعال سازی CORS
برای اینکه به صورت مستقیم با CouchDB در ارتباط باشید باید مطمئن شوید که CORS فعال هست.
در اینجا فقط نام کاربری و رمز ورود را مشخص کنید. به صورت پیش فرض CouchDB به صورت Admin Party نصب میشود و نیازی به User و password ندارد. مگر اینکه برایش مشخص کنید.
همچنین شما باید myname.iriscouch.com را با سرور خودتان جایگزین کنید که اگر به صورت local کار میکنید 127.0.0.1:5984 است.
$ export HOST=http://username:password@myname.iriscouch.com $ curl -X PUT $HOST/_config/httpd/enable_cors -d '"true"' $ curl -X PUT $HOST/_config/cors/origins -d '"*"' $ curl -X PUT $HOST/_config/cors/credentials -d '"true"' $ curl -X PUT $HOST/_config/cors/methods -d '"GET, PUT, POST, HEAD, DELETE"' $ curl -X PUT $HOST/_config/cors/headers -d \ '"accept, authorization, content-type, origin"'
راه اندازی ارتباط ساده دو طرفه
به فایل app.js برگردید . در اینجا باید آدرس بانک اطلاعاتی آنلاین را مشخص کنیم.
var db = new PouchDB('todos'); var remoteCouch = 'http://user:pass@mname.iriscouch.com/todos';
فراموش نکنید که نام کاربری و رمز ورود را بسته به نیاز خود تغییر دهید.
حالا میتونیم function که وظیفه همگام سازی اطلاعات را به عهده دارد بنویسیم :
function sync() { syncDom.setAttribute('data-sync-state', 'syncing'); var opts = {live: true}; db.replicate.to(remoteCouch, opts, syncError); db.replicate.from(remoteCouch, opts, syncError); }
db.replicate() به PouchDB میگوید که که همه اطلاعات را "به" یا "از" remoteCouch انتقال دهد.
ما دوبار این درخواست را دادیم. در بار اول اطلاعات به داخل سرور ریموت منتقل میشود و در بار دوم اطلاعات local جایگزین میشوند.
یک callback هم وقتی که این کار به پایان برسد اجرا خواهد شد .
میتوانید برنامه خود را در دو مرورگر مختلف اجرا کنید تا نتیجه کار را ببینید.
تبریک میگوییم !
شما توانستید اولین برنامه خود را توسط PouchDB ایجاد کنید. البته این یک برنامه ساده بود و در دنیای واقعی نیاز هست تا خیلی کارهای بیشتری را انجام دهید. اما باز هم اصول اولیه کار را یاد گرفتید و ادامه راه را میتوانید تنهایی ادامه دهید .
ListBox
در مورد لیست، ما قبلا نام کشورها را با استفاده از تگ ListBoxItem به طور دستی اضافه میکردیم و هر گونه ویرایش و اضافه کردن عکس و دیگر اشیاء را داخل این تگ برای هر آیتم جداگانه انجام میدادیم؛ مثل تصویر زیر که هر آیتم شامل یک تگ تصویر و دو تگ TextBlock است که یکی از آنها رنگی شده است. کد هر آیتم به طور جداگانه و دستی اضافه شده است.
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10" Height="80" > <ListBox.ItemTemplate> <DataTemplate> <WrapPanel> <Image Width="24" Height="24" Source="{Binding Flag}"></Image> <TextBlock Padding="5 5 0 0" Text="{Binding Name}"></TextBlock> </WrapPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
ارائه این نکته ضروری است که همه اشیاء خصوصیت DataContext را دارند و ما در مثال قبلی DataContext ریشه یا والد اشیاء را پر کردیم. اگر مقاله "ساختار سلسله مراتبی " را به یاد بیاورید، گفتیم که هر شیء در صورتیکه خصوصیت وابستهای برایش تعریف نشده باشد، به سمت اشیاء والد حرکت میکند، به این جهت بود که همهی کنترلها به منبع دادهها دسترسی داشتند. پس ما اگر DataContext لیست را پر کنیم، لیست دلیلی برای دسترسی به DataContext اشیاء والد ندارد و خصوصیت پر شدهی خودش را در نظر میگیرد. پس بیایید این مورد را امتحان کنیم:
من کلاس زیر را جهت ارسال لیستی از کشورها به همراه آدرس پرچمشان، بر میگردانم:
دلیل استفاده از کلاس ObservableCollection در کد زیر به جای استفاده از اشیایی چون Ilist و ... این بود که این کلاس به اینترفیس هایی چون INotifyPropertyChanged مزین گشته و هر گونه تغییری در این مجموعه، از قبیل حذف و اضافه را اطلاع رسانی کرده و مدل تغییر یافته را به سمت ویو هدایت میکند.
using System.Collections.ObjectModel; namespace test { public class Country { public string Flag { get { return "Images/flags/" + Name + ".png"; } } public string Name { get; set; } public int Id { get; set; } public ObservableCollection<Country> GetCountries() { var countries = new ObservableCollection<Country>(); countries.Add(new Country(){Id =1,Name = "Afghanistan"}); countries.Add(new Country() { Id = 2, Name = "Albania" }); countries.Add(new Country() { Id = 3, Name = "Angola" }); countries.Add(new Country() { Id = 4, Name = "Bahrain" }); countries.Add(new Country() { Id = 5, Name = "Bermuda" }); countries.Add(new Country() { Id =6, Name = "Iran" }); return countries; } } }
دلیل این مشکل این است که DataContext برای نمایش یک Object تهیه شده است و در مورد دادههای لیستی باید از خصوصیتی به نام ItemsSource استفاده کرد که برای دادههای لیستی IEnumerables، بهینه شده است.
پس به این ترتیب مینویسیم :
public MainWindow() { InitializeComponent(); person = Person.GetPerson(); DataContext = person; //خط جدید MyListBox.ItemsSource = new Country().GetCountries(); }
شکلهای زیر یک نمودار از ارتباط با Object برای واکشی داده هاست:
نمودار زیر هم دسترسی به مجموعه ای از دادههای لیستی است که از طریق ItemsSource خوانده میشوند:
کد زیر همچنین برای اتصال به کار میرود:
public MainWindow() { InitializeComponent(); person = Person.GetPerson(); DataContext = person; //خط جدید MyListBox.DataContext = new Country().GetCountries(); MyListBox.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()); }
پی نوشت : روشهای دیگر بایند کردن همچون استفاده از منابع یا ریسورسها یا استفاده از ViewModelها هم هستند که در آینده در مورد آنها بیشتر صحبت خواهیم کرد.
حال که توانستیم لیست را پر کنیم باید کشوری را که در رکورد واکشی شده آمده است، در لیست انتخاب کنیم.
توجه داشته باشید که باید لیست را از طریق خصوصیت ItemsSource پر کرده باشید و DataContext را دستکاری نکرده باشید.
خصوصیت Country در کلاس Person میتواند به دو صورت زیر باشد:
public int Country { get; set; } public Country Country { get; set; }
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10" Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country}" > <ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10" Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country.Id}" >
خصوصیتهای دیگر یک شیء لیستی چون ListBox و ComboBox و ... SelectedIndex است که اندیس یک آیتم انتخابی را بازگردانده یا جهت انتخاب یک آیتم، اندیس آن را دریافت میکند. SelectedItem و SelectedItems هم شیء یا شیءهایی از مدل را (در اینجا Country) که در لیست انتخاب شدهاند، بر میگرداند (فقط خواندنی).
چرا XML و چرا پشتیبانی توکار از آن در SQL Server
فیلدهای XML از سال 2005 به امکانات توکار SQL Server اضافه شدهاند و بسیاری از مزایای دنیای NoSQL را درون SQL Server رابطهای مهیا میسازند. برای مثال با تعریف یک فیلد به صورت XML، میتوان از هر ردیف به ردیفی دیگر، اطلاعات متفاوتی را ذخیره کرد؛ به این ترتیب امکان کار با یک فیلد که میتواند اطلاعات یک شیء را قبول کند و در حقیقت امکان تعریف اسکیمای پویا و متغیر را در کنار امکانات یک بانک اطلاعاتی رابطهای که از اسکیمای ثابت پشتیبانی میکند، میسر میشود.
همچنین SQL Server در این حالت قابلیتی را ارائه میدهد که در بسیاری از بانکهای اطلاعاتی NoSQL میسر نیست. در اینجا در صورت نیاز و لزوم میتوان اسکیمای کاملا مشخصی را به یک فیلد XML نیز انتساب داد؛ هر چند این مورد اختیاری است و میتوان یک un typed XML را نیز بکار برد. به علاوه امکانات کوئری گرفتن توکار از این اطلاعات را به کمک XPath ترکیب شده با T-SQL، نیز فراموش نکنید.
بنابراین اگر یکی از اهداف اصلی گرایش شما به سمت دنیای NoSQL، استفاده از امکان تعریف اطلاعاتی با اسکیمای متغیر و پویا است، فیلدهای نوع XML اس کیوال سرور را مدنظر داشته باشید.
یک مثال عملی: فناوری Azure Dev Fabric's Table Storage (نسخه Developer ویندوز Azure که روی ویندوزهای معمولی اجرا میشود؛ یک شبیه ساز خانگی) به کمک SQL Server و فیلدهای XML آن طراحی شده است.
چرا XML و چرا پشتیبانی توکار از آن در SQL Server
یک سند XML معمولا بیشتر از یک قطعه داده را در خود نگهداری میکند و نوع دادهی پیچیده محسوب میشود؛ برخلاف دادههایی مانند int یا varchar که نوعهایی ساده بوده و تنها یک قطعه از اطلاعات خاصی را در خود نگهداری میکنند. بنابراین شاید این سؤال مطرح شود که چرا از این نوع داده پیچیده در SQL Server پشتیبانی شدهاست؟
- از سالهای نسبتا دور، از XML برای انتقال دادهها بین سیستمها و سکوهای کاری مختلف استفاده شدهاست.
- استفادهی گستردهای در برنامههای تجاری دارد.
- بسیاری از فناوریهای موجود از آن پشتیبانی میکنند.
برای مثال اگر با فناوریهای مایکروسافتی کار کرده باشید، به طور قطع حداقل در یک یا چند قسمت از آنها، مستقیما از XML استفاده شدهاست.
بنابراین با توجه به اهمیت و گستردگی استفاده از آن، بهتر است پشتیبانی توکاری نیز از آن داخل موتور یک بانک اطلاعاتی، پیاده سازی شده باشد. این مساله سهولت تهیه پشتیبانهای خودکار، بازیابی آنها و امنیت یکپارچه با SQL Server را به همراه خواهد داشت؛ به همراه تمام زیرساختهای مهیای در SQL Server.
روشهای مختلف ذخیره سازی XML در بانکهای اطلاعاتی رابطهای
الف) ذخیره سازی متنی
این روش نیاز به نگارش خاصی از SQL Server یا بانک اطلاعاتی الزاما خاصی نداشته و با تمام بانکهای اطلاعاتی رابطهای سازگار است؛ مثلا از فیلدهای varchar برای ذخیره سازی آن استفاده شود. مشکلی که این روش به همراه خواهد داشت، از دست دادن ارزش یک سند XML و برخورد متنی با آن است. زیرا در این حالت برای تعیین اعتبار آن یا کوئری گرفتن از آنها نیاز است اطلاعات را از بانک اطلاعاتی خارج کرده و در لایهای دیگر از برنامه، کار جستجو پردازش آنها را انجام داد.
ب) تجزیه XML به چندین جدول رابطهای
برای مثال یک سند XML را درنظر بگیرید که دارای اطلاعات شخص و خریدهای او است. میتوان این سند را به چندین فیلد در چندین جدول مختلف رابطهای تجزیه کرد و سپس با روشهای متداول کار با بانکهای اطلاعاتی رابطهای از آنها استفاده نمود.
ج) ذخیره سازی آنها توسط فیلدهای خاص XML
در این حالت با استفاده از فیلدهای ویژه XML میتوان از فناوریهای مرتبط با XML تمام و کمال استفاده کرد. برای مثال تهیه کوئریهای پیچیده داخل همان بانک اطلاعاتی بدون نیاز به تجزیه سند به چندین جدول و یا خارج کردن آنها از بانک اطلاعاتی و جستجوی بر روی آنها در لایهای دیگر از برنامه.
موارد کاربرد XML در SQL Server
کاربردهای مناسب
- اطلاعات، سلسله مراتبی و تو در تو هستند. XQuery و XPath در این موارد بسیار خوب عمل میکند.
- ساختار قسمتی از اطلاعات ثابت است و قسمتی از آن خیر. برای نمونه، یک برنامهی فرم ساز را درنظر بگیرید که هر فرم آن هر چند دارای یک سری خواص ثابت مانند نام، گروه و امثال آن است، اما هر کدام دارای فیلدهای تشکیل دهنده متفاوتی نیز میباشد. به این ترتیب با استفاده از یک فیلد XML، دیگری نیازی به نگران بودن در مورد نحوه مدیریت اسکیمای متغیر مورد نیاز، نخواهد بود.
نمونهی دیگر آن ذخیره سازی خواص متغیر اشیاء است. هر شیء دارای یک سری خواص ثابت است اما خواص توصیف کنندهی آنها از هر رکورد به رکوردی دیگر متفاوت است.
کاربردهای نامناسب
- کل اطلاعات را داخل فیلد XML قرار دادن. هدف از فیلدهای XML قرار دادن یک دیتابیس داخل یک سلول نیست.
- ساختار تعریف شده کاملا مشخص بوده و به این زودیها هم قرار نیست تغییر کند. در این حالت استفاده از قابلیتهای رابطهای متداول SQL Server مناسبتر است.
- قرار دادن اطلاعات باینری بسیار حجیم در سلولهای XML ایی.
تاریخچهی پشتیبانی از XML در نگارشهای مختلف SQL Server
الف) SQL Server 2000
در SQL Server 2000 روش (ب) توضیح داده شده در قسمت قبل، پشتیبانی میشود. در آن برای تجزیه یک سند XML به معادل رابطهای آن، از تابعی به نام OpenXML استفاده میشود و برای تبدیل این اطلاعات به XML از روش Select … for XML میتوان کمک گرفت. همچنین تاحدودی مباحث XPath Queries نیز در آن گنجانده شدهاست.
ب) SQL Server 2005
در نگارش 2005 آن، برای اولین بار نوع دادهای ویژه XML معرفی گشت به همراه امکان تعریف اسکیمای XML و اعتبارسنجی آن و پشتیبانی از XQuery برای جستجوی سریع بر روی دادههای XML داخل همان بانک اطلاعاتی، بدون نیاز به استخراج اطلاعات XML و پردازش مجزای آنها در لایهای دیگر از برنامه.
ج) SQL Server 2008 به بعد
در اینجا فاز نگهداری این نوع داده خاص شروع شده و بیشتر شامل یک سری بهبودهای کوچک در کارآیی و نحوهی استفاده از آنها میشود.
استفاده از XML با کمک SQLCLR
از SQL Server 2005 به بعد، امکان استفاده از کلیهی امکانات موجود در فضای نام System.Xml دات نت، در SQL Server نیز به کمک SQL CLR مهیا شدهاست. همچنین از SQL Server 2008 به بعد، امکانات فضای نام System.Xml.Linq و مباحث LINQ to XML نیز توسط SQL CLR پشتیبانی میشوند.
البته این امکانات در SQL Server 2005 نیز قابل استفاده هستند، اما اسمبلی شما unsafe تلقی میشود. پس از آزمایشات و بررسی کافی، فضای نام مرتبط با LINQ to XML و امکانات آن، به عنوان اسمبلیهایی امن و قابل استفاده در SQL Server 2008 به بعد، معرفی شدهاند.
مزایای وجود فیلد ویژه XML در SQL Server
پس از اینکه فیلدهای XML به صورت یک نوع داده بومی بانک اطلاعاتی SQL Server معرفی شدند، مزایای ذیل بلافاصله در اختیار برنامه نویسها قرار گرفت:
- امکان تعریف آنها به صورت یک ستون جدولی خاصی
- استفاده از آنها به عنوان یک پارامتر رویههای ذخیره شده
- امکان تعریف خروجی توابع scalar سفارشی تعریف شده به صورت XML
- امکان تعریف متغیرهای T-SQL از نوع XML
برای مثال در اینجا نحوهی تعریف یک جدول جدید دارای فیلدی از نوع XML را مشاهده میکنید:
CREATE TABLE xml_tab ( id INT, xml_col XML )
- امکان تعریف ایندکسهای XML ایی اضافه شدهاست.
چه نوع XML ایی را میتوان در فیلدهای XML ذخیره کرد؟
فیلدهای XML امکان ذخیره سازی دادههای XML خوش فرم را مطابق استاندارد یک XML، دارند. حداکثر اندازه قابل ذخیره سازی در یک فیلد XML دو گیگابایت است.
البته امکانات مهیای در SQL Server در بسیاری از موارد فراتر از استاندارد یک XML هستند. به این معنا که در فیلدهای XML میتوان Documents و یا Fragments را ذخیره سازی کرد. یک سند XML یا Document حاوی تنها یک ریشه اصلی است؛ اما یک Fragment میتواند بیش از یک ریشه اصلی را در خود ذخیره کند. یک مثال:
DECLARE @xml_tab TABLE (xml_col XML) -- document INSERT @xml_tab VALUES ('<person/>') -- fragment INSERT @xml_tab VALUES ('<person/><person/>') SELECT * FROM @xml_tab
DECLARE @xml_tab TABLE (xml_col XML) -- text only INSERT @xml_tab VALUES ('data data data .....') -- empty string INSERT @xml_tab VALUES ('') -- null value INSERT @xml_tab VALUES (null) SELECT * FROM @xml_tab
به علاوه باید دقت داشت که در SQL Server نوع دادهای XML برای ذخیره سازی دادهها بکار گرفته میشود. به این معنا که در اینجا پیشوندهای فضاهای نام XML بیمعنا هستند.
DECLARE @xml_tab TABLE (xml_col XML) INSERT @xml_tab VALUES ('<doc/>') INSERT @xml_tab VALUES ('<doc xmlns="http://www.doctors.com"/>') -- این سه سطر در عمل یکی هستند INSERT @xml_tab VALUES ('<doc xmlns="http://www.documents.com"/>') INSERT @xml_tab VALUES ('<dd:doc xmlns:dd="http://www.documents.com"/>') INSERT @xml_tab VALUES ('<rr:doc xmlns:rr="http://www.documents.com"/>') SELECT * FROM @xml_tab
Encoding ذخیره سازی دادههای XML
SQL Server امکان ذخیره سازی اطلاعات متنی را به فرمت UFT8، اسکی و غیره، دارد. اما جهت پردازش فیلدهای XML و ذخیره سازی آنها از Collation پیش فرض بانک اطلاعاتی کمک خواهد گرفت. البته ذخیره سازی نهایی آن همیشه با فرمت UCS2 است (یونیکد دو بایتی).
DECLARE @xml_tab TABLE (id INT, xml_col XML) INSERT INTO @xml_tab VALUES ( 5, N'<?xml version="1.0" encoding="utf-8"?> <doc1> <row name="vahid"></row> </doc1> ')
XML parsing: line 1, character 38, unable to switch the encoding
برای حل این مشکل باید N ابتدای رشته را حذف کرد. روش دوم، معرفی و استفاده از utf-16 است بجای utf-8 در ویژگی encoding.
همچنین در این حالت اگر encoding را utf-16 معرفی کنیم و ابتدای رشته در حال ذخیره سازی N قرار نگیرد، باز با خطای unable to switch the encoding مواجه خواهیم شد.
نحوهی ذخیره سازی اطلاعات XML ایی در SQL Server
SQL Server فرمت اطلاعات XML وارد شده را حفظ نمیکند. برای مثال اگر قطعه کد زیر را اجرا کنید
DECLARE @xml_tab TABLE (id INT, xml_col XML) INSERT INTO @xml_tab VALUES ( 5, '<?xml version="1.0" encoding="utf-8"?><doc1><row name="vahid"></row></doc1>' ) SELECT * FROM @xml_tab
<doc1> <row name="vahid" /> </doc1>
ذخیره سازی دادههایی حاوی کاراکترهای غیرمجاز XML
اطلاعات دنیای واقعی همیشه به همراه اطلاعات تک کلمهای ساده نیست. ممکن است نیاز شود انواع و اقسام حروف و تگها نیز در این بین به عنوان داده ذخیره شوند. روش حل استاندارد آن بدون نیاز به دستکاری اطلاعات ورودی، استفاده از CDATA است:
DECLARE @xml_tab TABLE (id INT, xml_col XML) INSERT INTO @xml_tab VALUES ( 5, '<person><![CDATA[ 3 > 2 ]]></person>' ) SELECT * FROM @xml_tab
<person> 3 > 2 </person>
محدودیتهای فیلدهای XML
- امکان مقایسه مستقیم را ندارند؛ بجز مقایسه با نال. البته میتوان XML را تبدیل به مثلا varchar کرد و سپس این داده رشتهای را مقایسه نمود. برای مقایسه با null توابع isnull و coalesce نیز قابل بکارگیری هستند.
- order by و group by بر روی این فیلدها پشتیبانی نمیشود.
- به عنوان ستون کلید قابل تعریف نیست.
- به صورت منحصربفرد و unique نیز قابل علامتگذاری و تعریف نیست.
- فیلدهای XML نمیتوانند دارای collate باشند.