Globalization در ASP.NET MVC
معرفی Blazor Hybrid
Blazor Hybrid یا همون NET MAUI Blazor App راهکار Blazor هست که HTML/CSS برای UI استفاده میشه، ولی C# .NET اش دسترسی کامل به سیستم عامل داره (بر خلاف Blazor Web Assembly که محدود به Sandbox مرورگر هست)
یا Blazor Native شما دیگه UI تون HTML/CSS نیست و برای داشتن TextBox به جای input type=text، از Entry استفاده میکنید برای مثال که پشت صحنه مپ میشه به کنترلهای Native در Android / iOS / Windows
توصیه من این هست که Blazor رو به صورت Multi Mode تنظیم کنید، به صورتی که UI رو با HTML / CSS بزنید، و هم خروجی Android بگیرید و هم iOS و Web و ویندوز
برای درک بهتر این مسئله میتونید وبینارم رو در رابطه با what's new in dotnet 6 ببینید
OpenCVSharp #6
- استفاده از متد capture.QueryFrame().ToBitmap اشتباه هست. از این جهت که خروجی capture.QueryFrame میتواند نال باشد. بنابراین این تبدیل را باید در داخل حلقه انجام دهید و نه در زمانیکه قصد دارید تصویری را دریافت کنید. شرط موجود در حلقه (مانند مثال اصلی مطلب)، بررسی نال نبودن این فریم دریافتی است. بنابراین اگر نال باشد، حلقه پایان خواهد یافت.
- همانطور که در متن عنوان شد، متد workerProgressChanged در ترد اصلی یا همان ترد UI اجرا میشود. بنابراین فراخوانی pictureBoxIpl1.Invoke غیر ضروری است و سربار بیجهتی را به سیستم تحمیل میکند.
به صورت خلاصه در حین استفادهی از BackgroundWorker:
- متد رخداد گردان DoWork بر روی ThreadPool اجرا میشود (ترد آن با ترد UI یکی نیست)
- متدهای رخدادگردان گزارش پیشرفت کار و اتمام کار، بر روی ترد UI اجرا میشوند. بنابراین امکان دسترسی به عناصر UI در این متدها، بدون مشکلی وجود دارد.
استفاده از منابع داده محلی
در ادامه مثالی را از نحوهی استفاده از یک منبع داده محلی جاوا اسکریپتی، مشاهده میکنید:
<script type="text/javascript"> $(function () { var cars = [ { "Year": 2000, "Make": "Hyundai", "Model": "Elantra" }, { "Year": 2001, "Make": "Hyundai", "Model": "Sonata" }, { "Year": 2002, "Make": "Toyota", "Model": "Corolla" }, { "Year": 2003, "Make": "Toyota", "Model": "Yaris" }, { "Year": 2004, "Make": "Honda", "Model": "CRV" }, { "Year": 2005, "Make": "Honda", "Model": "Accord" }, { "Year": 2000, "Make": "Honda", "Model": "Accord" }, { "Year": 2002, "Make": "Kia", "Model": "Sedona" }, { "Year": 2004, "Make": "Fiat", "Model": "One" }, { "Year": 2005, "Make": "BMW", "Model": "M3" }, { "Year": 2008, "Make": "BMW", "Model": "X5" } ]; var carsDataSource = new kendo.data.DataSource({ data: cars }); carsDataSource.read(); alert(carsDataSource.total()); }); </script>
ذکر new kendo.data.DataSource به تنهایی به معنای مقدار دهی اولیه است و در این حالت منبع داده مورد نظر، استفاده نخواهد شد. برای مثال اگر متد total آنرا جهت یافتن تعداد عناصر موجود در آن فراخوانی کنید، صفر را بازگشت میدهد. برای شروع به کار با آن، نیاز است ابتدا متد read را بر روی این منبع داده مقدار دهی شده، فراخوانی کرد.
استفاده از منابع داده راه دور
در برنامههای کاربردی، عموما نیاز است تا منبع داده را از یک وب سرور تامین کرد. در اینجا نحوهی خواندن اطلاعات JSON بازگشت داده شده از جستجوی توئیتر را مشاهده میکنید:
<script type="text/javascript"> $(function () { var twitterDataSource = new kendo.data.DataSource({ transport: { read: { url: "http://search.twitter.com/search.json", dataType: "jsonp", contentType: 'application/json; charset=utf-8', type: 'GET', data: { q: "#kendoui" } }, schema: { data: "results" } }, error: function (e) { alert(e.errorThrown.stack); } }); }); </script>
در قسمت schema مشخص میکنیم که اطلاعات JSON بازگشت داده شده توسط توئیتر، در فیلد results آن قرار دارد.
کار با منابع داده OData
علاوه بر فرمتهای یاد شده، Kendo UI DataSource امکان کار با اطلاعاتی از نوع OData را نیز دارا است که تنظیمات ابتدایی آن به صورت ذیل است:
<script type="text/javascript"> var moviesDataSource = new kendo.data.DataSource({ type: "odata", transport: { read: "http://demos.kendoui.com/service/Northwind.svc/Orders" }, error: function (e) { alert(e.errorThrown.stack); } }); }); </script>
یک مثال: دریافت اطلاعات از ASP.NET Web API
یک پروژهی جدید ASP.NET را آغاز کنید. تفاوتی نمیکند که Web forms باشد یا MVC؛ از این جهت که مباحث Web API در هر دو یکسان است.
سپس یک کنترلر جدید Web API را به نام ProductsController با محتوای زیر ایجاد کنید:
using System.Collections.Generic; using System.Web.Http; namespace KendoUI02 { public class Product { public int Id { set; get; } public string Name { set; get; } } public class ProductsController : ApiController { public IEnumerable<Product> Get() { var products = new List<Product>(); for (var i = 1; i <= 100; i++) { products.Add(new Product { Id = i, Name = "Product " + i }); } return products; } } }
در ادامه نیاز است تعریف مسیریابی ذیل نیز به فایل Global.asax.cs برنامه اضافه شود تا بتوان به آدرس api/products در سایت، دسترسی یافت:
using System; using System.Web.Http; using System.Web.Routing; namespace KendoUI02 { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
در ادامه فایلی را به نام Index.html (یا در یک View و یا یک فایل aspx دلخواه)، محتوای ذیل را اضافه کنید:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>Kendo UI: Implemeting the Grid</title> <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.all.min.js" type="text/javascript"></script> </head> <body> <div id="report-grid"></div> <script type="text/javascript"> $(function () { var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, error: function (e) { alert(e.errorThrown.stack); }, pageSize: 5, sort: { field: "Id", dir: "desc" } }); $("#report-grid").kendoGrid({ dataSource: productsDataSource, autoBind: true, scrollable: false, pageable: true, sortable: true, columns: [ { field: "Id", title: "#" }, { field: "Name", title: "Product" } ] }); }); </script> </body> </html>
- گرید صفحه، در محل div ایی با id مساوی report-grid تشکیل خواهد شد.
- سپس DataSource ایی که به آدرس api/products اشاره میکند، تعریف شده و در آخر productsDataSource را توسط یک kendoGrid نمایش دادهایم.
- نحوهی تعریف productsDataSource، در قسمت استفاده از منابع داده راه دور ابتدای بحث توضیح داده شد. در اینجا فقط دو خاصیت pageSize و sort نیز به آن اضافه شدهاند. این دو خاصیت بر روی نحوهی نمایش گرید نهایی تاثیر گذار هستند. pageSize تعداد رکورد هر صفحه را مشخص میکند و sort نحوهی مرتب سازی را بر اساس فیلد Id و در حالت نزولی قرار میدهد.
- در ادامه، ابتداییترین حالت کار با kendoGrid را ملاحظه میکنید.
- تنظیم dataSource و autoBind: true (حالت پیش فرض)، سبب خواهند شد تا به صورت خودکار، اطلاعات JSON از مسیر api/products خوانده شوند.
- سه خاصیت بعدی صفحه بندی و مرتب سازی خودکار ستونها را فعال میکنند.
- در آخر هم دو ستون گرید، بر اساس نامهای خواص کلاس Product تعریف شدهاند.
سورس کامل این قسمت را از اینجا میتوانید دریافت کنید:
KendoUI02.zip
یکی از این کیتها، فوتون نام دارد. فوتون شامل یک سری css ,sass، فونت و قالبهای html است که به شما اجازه میدهد تا یک برنامه با ظاهری شبیه به برنامههای مک را داشته باشید. کامپوننتهای فوتون شامل tab ها، لیستها، منوی کناری، دکمههای معمولی یا جعبه ابزاری و کنترلهای فرمها میشود. با این حال اگر هم دوست ندارید که از این کامپوننتها استفاده کنید، میتوانید از layout های آن استفاده کند که پنجرهی شما را تقسیم بندی میکنند.
برای استفاده از فوتون لازم است آن را دانلود کرده و فایلهای آن را در پروژهی خود کپی کنید. دایرکتوری dist آن شامل یک مثال میشود که میتوانید آن را به داخل دایرکتوری پروژه خود کپی کنید؛ یا خود این دایرکتوری را به عنوان دایرکتوری پروژه تعیین کنید. بعد از آن، فایلهای موجود در دایرکتوری template را به داخل دایرکتوری والد، یعنی dist انتقال دهید و داخل فایل html، مسیر فایل css را تصحیح نمایید. فقط میماند که الکترون را بر روی این محل نصب کنید، یا اینکه الکترون نصب شدهی به صورت عمومی (Global) را در اختیار آن قرار دهید.
بعد از آن ممکن است با خطا مواجه شوید و وقتی فایل اصلی را که در اینجا نام آن app.js است، باز کنید، خطوط زیر را میبینید:
var app=require('app'); var BrowserWindow=require('browser-window');
سپس برنامه را اجرا کنید تا رابط جدید کاربری را ببینید.
فقط یک مشکلی هست و آن هم این است که باید فریم یا پنجرهای را که خود الکترون تولید میکند، حذف کنیم برای حذف آن میتوانید از خصوصیت frame در شیء Browser Window استفاده کنید:
if(process.platform=='darwin') { mainWindow= new BrowserWindow({ width: 1000, height: 500, 'min-width': 1000, 'min-height': 500, 'accept-first-mouse': true, 'title-bar-style': 'hidden', titleBarStyle:'hidden' }); } else { new BrowserWindow({ width: 1000, height: 500, 'min-width': 1000, 'min-height': 500, frame:false }); }
حالا اگر برنامه را مجددا اجرا کنید، میبینید که قابهای دور آن حذف شدهاند، ولی با چند ثانیه کار کردن متوجه این ایراد میشوید که پنجره قابل درگ کردن و جابجایی نمیباشد. برای حل آن باید از css کمک بگیریم:
-webkit-app-region: drag
-webkit-app-region: no-drag;
<header class="toolbar toolbar-header" >
<header class="toolbar toolbar-header" style="-webkit-app-region: drag">
همانطور که میبینید با کمترین زحمت، به چنین رابط کاربری رسیدید. تصویر زیر متعلق به برنامهای است که در دو قسمت قبلی (+ + ) ساختیم و حالا با استفاده از این پکیج، ظاهر آن را تغییر دادهایم:
Electron UI Kit
دومین رابط کاربری که معرفی میکنیم در واقع یک کیت از یک سری کامپوننت است که بسیار شبیه به برنامههای دسکتاپ طراحی شده و شامل لیستها، گریدها، کنترلها و ... است که در دو فایل استایل، برای ویندوز و مک، مجزا شدهاند.
Maverix
یک استایل تحت وب به نام Maverix است که البته در مورد برنامههای دسکتاپ و الکترون حرفی نزده و خود را فریمورکی برای استفاده در برنامههای تحت وب معرفی کرده است. ولی از آنجا که کنترلهای موجود آن بر اساس سیستم عامل مک ایجاد شدهاند، به راحتی میتوانند خود را بجای برنامههای دسکتاپ جا بزنند. میتوانید دموی آن را نیز ببینید.
تنظیمات ابتدایی Kendo UI Editor
در ذیل کدهای سمت کاربر فعال سازی مقدماتی Kendo UI را مشاهده میکنید. در قسمت tools آن، لیست امکانات و نوار ابزار مهیای آن درج شدهاند.
دو مورد insertImage و insertFile آن نیاز به تنظیمات سمت کاربر و سرور بیشتری دارند.
<!--نحوهی راست به چپ سازی --> <div class="k-rtl"> <textarea id="editor" rows="10" cols="30" style="height: 440px"></textarea> </div> @section JavaScript { <script type="text/javascript"> $(function () { $("#editor").kendoEditor({ tools: [ "bold", "italic", "underline", "strikethrough", "justifyLeft", "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList", "insertOrderedList", "indent", "outdent", "createLink", "unlink", "insertImage", "insertFile", "subscript", "superscript", "createTable", "addRowAbove", "addRowBelow", "addColumnLeft", "addColumnRight", "deleteRow", "deleteColumn", "viewHtml", "formatting", "cleanFormatting", "fontName", "fontSize", "foreColor", "backColor", "print" ], imageBrowser: { messages: { dropFilesHere: "فایلهای خود را به اینجا کشیده و رها کنید" }, transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorImages")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false }, destroy: { url: "@Url.Action("DestroyFile", "KendoEditorImages")", type: "POST" }, create: { url: "@Url.Action("CreateFolder", "KendoEditorImages")", type: "POST" }, thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")", uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")", imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}" } }, fileBrowser: { messages: { dropFilesHere: "فایلهای خود را به اینجا کشیده و رها کنید" }, transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorFiles")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false }, destroy: { url: "@Url.Action("DestroyFile", "KendoEditorFiles")", type: "POST" }, create: { url: "@Url.Action("CreateFolder", "KendoEditorFiles")", type: "POST" }, uploadUrl: "@Url.Action("UploadFile", "KendoEditorFiles")", fileUrl: "@Url.Action("GetFile", "KendoEditorFiles")?path={0}" } } }); }); </script> }
منهای قسمت thumbnailUrl، عملکرد قسمتهای مختلف افزودن فایل و تصویر این ادیتور یکسان هستند. به همین جهت میتوان برای مثال کنترلی مانند KendoEditorFilesController را ایجاد و سپس در کنترلر KendoEditorImagesController از آن ارث بری کرد و متد دریافت و نمایش بند انگشتی تصاویر را افزود. به این ترتیب دیگر نیازی به تکرار کدهای مشترک بین این دو قسمت نخواهد بود.
نمایش لیست پوشهها و تصویر در ابتدای باز شدن صفحهی درج تصویر
با کلیک بر روی دکمهی نمایش لیست تصاویر، صفحه دیالوگی مانند شکل زیر ظاهر خواهد شد:
تنظیمات خواندن این فایلها، از قسمت read مربوط به imageBrowser دریافت میشود که cache آن نیز به false تنظیم شدهاست تا در این بین مرورگر اطلاعات را کش نکند. این مورد در حین حذف فایلها و پوشهها مهم است. زیرا اگر cache:false تنظیم نشده باشد، حذف یک فایل یا پوشه در سمت کاربر تاثیری نخواهد داشت.
imageBrowser: { transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorImages")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpGet] public ActionResult GetFilesList(string path) { path = GetSafeDirPath(path); var imagesList = new DirectoryInfo(path) .GetFiles() .Select(fileInfo => new KendoFile { Name = fileInfo.Name, Size = fileInfo.Length, Type = KendoFileType }).ToList(); var foldersList = new DirectoryInfo(path) .GetDirectories() .Select(directoryInfo => new KendoFile { Name = directoryInfo.Name, Type = KendoDirType }).ToList(); return new ContentResult { Content = JsonConvert.SerializeObject(imagesList.Union(foldersList), new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } protected string GetSafeDirPath(string path) { // path = مسیر زیر پوشهی وارد شده if (string.IsNullOrWhiteSpace(path)) { return Server.MapPath(FilesFolder); } //تمیز سازی امنیتی path = Path.GetDirectoryName(path); path = Path.Combine(Server.MapPath(FilesFolder), path); return path; } } }
namespace KendoUI13.Models { public class KendoFile { public string Name { set; get; } public string Type { set; get; } public long Size { set; get; } } }
- علت استفاده از CamelCasePropertyNamesContractResolver در حین بازگشت JSON نهایی، تبدیل خواص دات نتی، به نامهای سازگار با JavaScript است. برای مثال به صورت خودکار Name را تبدیل به name میکند.
- پارامتر path در ابتدای کار خالی است. اما کاربر میتواند در بین پوشههای باز شدهی توسط مرورگر تصاویر Kendo UI حرکت کند. به همین جهت مقدار آن باید هربار بررسی شده و بر این اساس لیست فایلها و پوشههای جاری بازگشت داده شوند.
مدیریت حذف تصاویر و پوشهها
همانطور که در شکل فوق نیز مشخص است، با انتخاب یک پوشه یا فایل، دکمهای با آیکن ضربدر جهت فراهم آوردن امکان حذف، ظاهر میشود. این دکمه متصل است به قسمت destroy تنظیمات ادیتور:
imageBrowser: { transport: { destroy: { url: "@Url.Action("DestroyFile", "KendoEditorImages")", type: "POST" } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult DestroyFile(string name, string path) { //تمیز سازی امنیتی name = Path.GetFileName(name); path = GetSafeDirPath(path); var pathToDelete = Path.Combine(path, name); var attr = System.IO.File.GetAttributes(pathToDelete); if ((attr & FileAttributes.Directory) == FileAttributes.Directory) { Directory.Delete(pathToDelete, recursive: true); } else { System.IO.File.Delete(pathToDelete); } return Json(new object[0]); } } }
- پارامتر name دریافتی مساوی است با نام فایل انتخاب شده و path مشخص میکند که در کدام پوشه قرار داریم.
- چون در اینجا امکان حذف یک پوشه یا فایل وجود دارد، حتما نیاز است بررسی کنیم، مسیر دریافتی پوشهاست یا فایل و سپس بر این اساس جهت حذف آنها اقدام صورت گیرد.
مدیریت ایجاد یک پوشهی جدید
تنظیمات قسمت create مرورگر تصاویر، مرتبط است به زمانیکه کاربر با کلیک بر روی دکمهی +، درخواست ایجاد یک پوشهی جدید را کردهاست:
imageBrowser: { transport: { create: { url: "@Url.Action("CreateFolder", "KendoEditorImages")", type: "POST" } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult CreateFolder(string name, string path) { //تمیز سازی امنیتی name = Path.GetFileName(name); path = GetSafeDirPath(path); var dirToCreate = Path.Combine(path, name); Directory.CreateDirectory(dirToCreate); return KendoFile(new KendoFile { Name = name, Type = KendoDirType }); } protected ActionResult KendoFile(KendoFile file) { return new ContentResult { Content = JsonConvert.SerializeObject(file, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } } }
- پس از ایجاد پوشه، باید نام آنرا با فرمت KendoFile به صورت JSON بازگشت داد. همچنین در اینجا Type را نیز باید به d (پوشه) تنظیم کرد.
مدیریت قسمت ارسال فایل و تصویر
زمانیکه کاربر بر روی دکمهی upload file یا بارگذاری تصاویر در اینجا کلیک میکند، اطلاعات فایل آپلودی به مسیر uploadUrl ارسال میگردد.
imageBrowser: { transport: { thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")", uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")", imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}" } },
در ادامه کدهای مدیریت سمت سرور قسمت آپلود این ادیتور را مشاهده میکنید:
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult UploadFile(HttpPostedFileBase file, string path) { //تمیز سازی امنیتی var name = Path.GetFileName(file.FileName); path = GetSafeDirPath(path); var pathToSave = Path.Combine(path, name); file.SaveAs(pathToSave); return KendoFile(new KendoFile { Name = name, Size = file.ContentLength, Type = KendoFileType }); } } }
- پس از ذخیره سازی اطلاعات فایل، نیاز است اطلاعات فایل نهایی را با فرمت KendoFile به صورت JSON بازگشت دهیم.
ارث بری از KendoEditorFilesController جهت تکمیل قسمت مدیریت تصاویر
تا اینجا کدهایی را که ملاحظه کردید، برای هر دو قسمت ارسال تصویر و فایل کاربرد دارند. قسمت ارسال تصاویر برای تکمیل نیاز به متد دریافت تصاویر به صورت بند انگشتی نیز دارد که به صورت ذیل قابل تعریف است و چون از کلاس پایه KendoEditorFilesController ارث بری کردهاست، این کنترلر به صورت خودکار حاوی اکشن متدهای کلاس پایه نیز خواهد بود.
using System.Web.Mvc; namespace KendoUI13.Controllers { public class KendoEditorImagesController : KendoEditorFilesController { public KendoEditorImagesController() { // بازنویسی مسیر پوشهی فایلها FilesFolder = "~/images"; } [HttpGet] [OutputCache(Duration = 3600, VaryByParam = "path")] public ActionResult GetThumbnail(string path) { //todo: create thumb/ resize image path = GetSafeFileAndDirPath(path); return File(path, "image/png"); } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
<script src="cordova.js"></script>
مثال برای اندروید
(function () { "use strict"; document.addEventListener( 'deviceready', onDeviceReady.bind( this ), false ); function onDeviceReady() { // Handle the Cordova pause and resume events document.addEventListener( 'pause', onPause.bind( this ), false ); document.addEventListener('resume', onResume.bind(this), false); document.addEventListener('menubutton', onMenuButton.bind(this), false); document.addEventListener('backbutton', onBackButton.bind(this), false); //document.addEventListener('searchbutton', onResume.bind(this), false); //document.addEventListener('endcallbutton', onResume.bind(this), false); //document.addEventListener('offline', onResume.bind(this), false); //document.addEventListener('online', onResume.bind(this), false); //document.addEventListener('startcallbutton', onResume.bind(this), false); //document.addEventListener('volumedownbutton', onResume.bind(this), false); //document.addEventListener('volumeupbutton', onResume.bind(this), false); // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here. }; function onPause() { // TODO: This application has been suspended. Save application state here. alert("paused"); }; function onResume() { alert("resume"); }; function onMenuButton() { alert("menu"); }; function onBackButton() { alert("back button"); }; } )();
.در مقالات آینده از افزونههای موجود، برای مدیریت رخدادهای باتری سیستم استفاده خواهیم کرد
jQuery Mobile
Phones/Tablets
Android 1.6+
BlackBerry 5+
iOS 3+
Windows Phone 7
WebOS 1.4+
Symbian (Nokia S60)
Firefox Mobile Opera Mobile 11+
Opera Mini 5+
Desktop browsers
Chrome 11+
Firefox 3.6+
Internet Explorer 7+
Safari
برای نصب jQuery Mobile کافی است دستورات زیر را در package manager console ویژوال استودیو استفاده کنید:
PM>install-package jquery
PM>install-package jquery.mobile.rtl
بعد از دانلود فایلهای مورد نظر خود، فولدری بنام jquery.mobile.rtl در ریشه پروژه ایجاد خواهد شد. به ترتیب فایل های rtl.jquery.mobile-1.4.0.css و rtl.jquery.mobile-1.4.0.js موجود در زیر شاخههای فلدر مذکور را به head و آخر body فایل index.html اضافه کنید.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>CordovaApp01</title> <!-- CordovaApp01 references --> <link href="css/index.css" rel="stylesheet" /> <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" /> </head> <body> <div data-role="page" id="page1"> <div data-role="header"> <h1>اولین برنامه</h1> </div> <div data-role="content"> <p>سلام من محتوای اولین برنامه هستم</p> </div> <div data-role="footer"> <h1>من فوتر هستم</h1> </div> </div> <!-- Cordova reference, this is added to your app when it's built. --> <script src="scripts/jquery-2.1.3.min.js"></script> <script src="cordova.js"></script> <script src="scripts/platformOverrides.js"></script> <script src="scripts/index.js"></script> <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script> </body> </html>
نتیجهی نهایی به شکل زیر خواهد بود:
در مقالهی بعد به استفاده از pluginها خواهیم پرداخت.
ادامه دارد...
در ادامه قصد داریم تمام این موارد را مانند Minimal API's و معماری برشهای عمودی به همراه CQRS، در طی یک سری و یک پروژهی عملی ساخت یک Blog به نام MinimalBlog، بررسی کنیم. البته هدف ما در اینجا صرفا ساخت backend ساختار یافتهی این برنامهاست؛ منهای UI آن. هدف اصلی ما از این سری، ارائهی یک معماری، جهت کار با Minimal API's است.
دریافت کدهای کامل این سری
جهت مرور سریعتر و سادهتر این سری، کدهای کامل آنرا از اینجا میتوانید دریافت کنید: MinimalBlog.zip
پروژههایی که برنامهی MinimalBlog را تشکیل میدهند
برنامهی MinimalBlog، تنها از سه پروژهی زیر تشکیل میشود:
MinimalBlog.Api: این پروژه از نوع minimal API's است که توسط دستور جدید «dotnet new webapi --use-minimal-apis» آغاز خواهد شد و به صورت پیشفرض به همراه پشتیبانی از OpenAPI نیز هست. البته اگر از VS2022 استفاده میکنید، در حین آغاز یک پروژهی Web API جدید، تیک مربوط به use controllers را در UI بردارید تا از Minimal API's استفاده شود.
MinimalBlog.Dal: که Dal در اینجا مخفف data access layer است و یک class library میباشد و با دستور dotnet new classlib آغاز میشود.
MinimalBlog.Domain: نیز یک class library است و با دستور dotnet new classlib آغاز میشود.
همانطور که مشاهده میکنید، این طراحی جدید، بدون وجود لایهی متداول سرویسها و یا مخازن است.
بررسی ساختار ابتدایی پروژهی MinimalBlog.Api
در اینجا تنها تک فایل Program.cs، به همراه تنظیمات برنامه قابل مشاهدهاست و فایل Starup.cs از آن حذف شدهاست (اطلاعات بیشتر). این فایل نیز بر مبنای مفهوم top level programs طراحی شدهاست و به همراه تعریف class و یا فضای نامی نیست:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen();
پس از آن به تعاریف زیر میرسیم؛ تعاریف میان افزارهایی که پیشتر در متد Configure کلاس Startup انجام میشدند، الان همگی در تک فایل Program.cs قرار دارند:
var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection();
در انتهای این فایل نیز تعاریف پیشفرض زیر قرار دارند:
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet("/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("GetWeatherForecast"); app.Run(); record WeatherForecast(DateTime Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); }
در همین حال اگر برنامهی Api را اجرا کنیم، به تصویر زیر خواهیم رسید:
در ادامه کدهای موجود در این فایل را Refactor کرده و به کلاسهای دیگری منتقل میکنیم؛ چون اگر قرار باشد در طول زمان تمام endpoints مدنظر را در همینجا تعریف کنیم، کنترل برنامه از دست خارج خواهد شد.
غنی سازی Solution و کامپایلر #C با استفاده از فایلهای editorconfig. و Directory.Build.props
در مورد این دو فایل در مطلب «غنی سازی کامپایلر C# 9.0 با افزونهها » بیشتر بحث شدهاست. هدف از آنها، اعمال یکسری تنظیمات سراسری، به تمام پروژههای یک solution به صورت یکدست است؛ مانند تنظیمات کامپایلر جهت نمایش اخطارها به صورت خطاها، تعریف usingهای سراسری سیشارپ 10 و یا اعمال Roslyn analyzers به تمام پروژهها. این دو فایل را به همراه پروژهی پیوست میتوانید دریافت کنید و ... باید جزء استاندارد تمام پروژههای جدید باشند. چون وجود آنها سبب خواهد شد که به شدت کیفیت کدهای نهایی افزایش یابند و مبتنی بر یکسری best practices شوند.
گپ و گفتی با مهندسان طراح دات نت در مورد آینده این فریم ورک
Q: What's the near-term roadmap for .NET tooling?
A: While tentative, here are some short term plans:
- Sep 2016 – Preview 3 of VS 2015 with early .CSProj support for .NET Core
- Nov 2016 – .NET Core 1.2, ASP.NET Core 1.2, Entity Framework Core 1.2, SignalR, .NET Standard 2.0, etc.