برنامهی آنلاینی جهت تولید یک theme سفارشی جدید برای ویژوال استودیو ایجاد شده است که در آدرس زیر قابل استفاده است:
ابتدا تنظیمات رنگ خود را انتخاب کنید، سپس بر روی دکمه refresh کلیک نمائید تا نمونهای از نتیجهی کار را بتوان مشاهده کرد و سپس بر روی دکمه create کلیک کنید.
و برای استفاده از فایل تولید شده از طریق منوی tools گزینهی Import and Export Settings اقدام کنید.
{ "manifest_version": 2, "name": "Dotnettips Updater", "description": "This extension keeps you updated on current activities on dotnettips.info", "version": "1.0", "icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }, "browser_action": { "default_icon": "icon.png", "default_popup": "popup.html" }, "permissions": [ "activeTab", "https://www.dntips.ir" ] }
"default_icon": { // optional "19": "images/icon19.png", // optional "38": "images/icon38.png" // optional }
صفحات پسزمینه
- صفحات مصر یا Persistent Page
- صفحات رویدادگرا یا Events Pages
"background": { "scripts": ["background.js"], "persistent": false }
Content Script یا اسکریپت محتوا
"content_scripts": [ { "matches": ["http://*/*", "https://*/*"], "js": ["content.js"] } ]
آغاز کدنویسی (رابطهای کاربری)
اجازه دهید بقیه موارد را در حین کدنویسی تجربه کنیم و هر آنچه ماند را بعدا توضیح خواهیم داد. در اینجا من از یک صفحه با کد HTML زیر بهره برده ام که یک فرم دارد به همراه چهار چک باکس و در نهایت یک دکمه جهت ذخیره مقادیر. نام صفحه را popup.htm گذاشته ام و یک فایل popup.js هم دارم که در آن کد jquery نوشتم. قصد من این است که بتوان یک action browser به شکل زیر درست کنم:
کد html آن به شرح زیر است:
<html> <head> <meta charset="utf-8"/> <script src="jquery.min.js"></script> <!-- Including jQuery --> <script type="text/javascript" src="popup.js"></script> </head> <body style="direction:rtl;width:250px;"> <form > <input type="checkbox" id="chkarticles" value="" checked="true">آخرین مطالب سایت</input><br/> <input type="checkbox" id="chkarticlescomments" value="" >آخرین نظرات مطالب سایت</input><br/> <input type="checkbox" id="chkshares" value="" >آخرین اشتراکهای سایت</input><br/> <input type="checkbox" id="chksharescomments" value="" >آخرین نظرات اشتراکهای سایت</input><br/> <input id="btnsave" type="button" value="ذخیره تغییرات" /> <div id="messageboard" style="color:green;"></div> </form> </body> </html>
$(document).ready(function () { $("#btnsave").click(function() { var articles = $("#chkarticles").is(':checked'); var articlesComments = $("#chkarticlescomments").is(':checked'); var shares = $("#chkshares").is(':checked'); var sharesComments = $("#chksharescomments").is(':checked'); chrome.storage.local.set({ 'articles': articles, 'articlesComments': articlesComments, 'shares': shares, 'sharesComments': sharesComments }, function() { $("#messageboard").text( 'تنظیمات جدید اعمال شد'); }); }); });
"permissions": [ "storage" ]
chrome.storage.sync.set
{ "manifest_version": 2, "name": "Dotnettips Updater", "description": "This extension keeps you updated on current activities on dotnettips.info", "version": "1.0", "browser_action": { "default_icon": "icon.png", "default_popup": "popup.html" }, "permissions": [ "storage" ] }
chrome://extensions
chrome.storage.local.get(['articles', 'articlesComments', 'shares', 'sharesComments'], function ( items) { console.log(items[0]); $("#chkarticles").attr("checked", items["articles"]); $("#chkarticlescomments").attr("checked", items["articlesComments"]); $("#chkshares").attr("checked", items["shares"]); $("#chksharescomments").attr("checked", items["sharesComments"]); });
"page_action": { "default_icon": { "19": "images/icon19.png", "38": "images/icon38.png" }, "default_popup": "popup.html"
"background": { "scripts": ["page_action_validator.js"] }
function UrlValidation(tabId, changeInfo, tab) { if (tab.url.indexOf('dotnettips.info') >-1) { chrome.pageAction.show(tabId); } }; chrome.tabs.onUpdated.addListener(UrlValidation);
"omnibox": { "keyword" : ".net" }
"background": { "scripts": ["omnibox.js"]
chrome.omnibox.onInputChanged.addListener(function(text, suggest) { suggest([ {content: ".net tips Home Page", description: "صفحه اصلی"}, {content: ".net tips Posts", description: "آخرین مطالب"}, {content: ".net tips News", description: "آخرین نظرات مطالب"}, {content: ".net tips Post Comments", description: "آخرین اشتراک ها"}, {content: ".net tips News Comments", description: "آخرین نظرات اشتراک ها"} ]); });
onInputStarted | بعد از اینکه کاربر کلمه کلیدی را وارد کرد اجرا میشود |
onInputChanged | بعد از وارد کردن کلمه کلیدی هربار که کاربر تغییری در ورودی نوارد آدرس میدهد اجرا میشود. |
onInputEntered | کاربر ورودی خود را تایید میکند. مثلا بعد از وارد کردن، کلید enter را میفشارد |
onInputCancelled | کاربر از وارد کردن ورودی منصرف شده است؛ مثلا کلید ESC را فشرده است. |
chrome.omnibox.onInputEntered.addListener(function (text) { var location=""; switch(text) { case ".net tips Posts": location="https://www.dntips.ir/postsarchive"; break; case ".net tips News": location="https://www.dntips.ir/newsarchive"; break; case ".net tips Post Comments": location="https://www.dntips.ir/commentsarchive"; break; case".net tips News Comments": location="https://www.dntips.ir/newsarchive/comments"; break; default: location="https://www.dntips.ir/"; } chrome.tabs.getSelected(null, function (tab) { chrome.tabs.update(tab.id, { url: location }); }); });
var root = chrome.contextMenus.create({ title: 'Open .net tips', contexts: ['page'] }, function () { var Home= chrome.contextMenus.create({ title: 'Home', contexts: ['page'], parentId: root, onclick: function (evt) { chrome.tabs.create({ url: 'https://www.dntips.ir' }) } }); var Posts = chrome.contextMenus.create({ title: 'Posts', contexts: ['page'], parentId: root, onclick: function (evt) { chrome.tabs.create({ url: 'https://www.dntips.ir/postsarchive/' }) } }); });
کتابخانه iTextSharp دارای کلاسی است به نام HTMLWorker که کار تبدیل عناصر HTML را به عناصر متناظر خودش، انجام میدهد. این کلاس در حال حاضر منسوخ شده درنظر گرفته میشود (اینطور توسط نویسندگان آن اعلام شده) و دیگر توسعه نخواهد یافت. بنابراین اگر از HTMLWorker استفاده میکنید با یک کلاس قدیمی که دارای HTML Parser ایی بسیار بدوی است طرف هستید و در کل برای تبدیل محتوای HTML ایی با ساختار بسیار ساده بد نیست؛ اما انتظار زیادی از آن نداشته باشید.
جایگزین کلاس HTMLWorker در این کتابخانه در حال حاضر کتابخانه itextsharp.xmlworker است، که به صورت یک افزونه در کنار کتابخانه اصلی در حال توسعه میباشد. مشکل اصلی این کتابخانه، عدم پشتیبانی از UTF8 و راست به چپ است. بنابراین حداقل به درد کار ما نمیخورد.
راه حل بسیار بهتری برای موضوع اصلی بحث ما وجود دارد و آن هم استفاده از موتور WebKit (همان موتوری که برای مثال در Apple Safari استفاده میشود) برای HTML parsing و سپس تبدیل نتیجه نهایی به PDF است. پروژهای که این مقصود را میسر کرده، wkhtmltopdf نام دارد.
توسط آن به کمک موتور WebKit، کار HTML Parsing انجام شده و سپس برای تبدیل عناصر نهایی به PDF از امکانات کتابخانهای به نام QT استفاده میشود. کیفیت نهایی آن کپی مطابق اصل HTML قابل مشاهده در یک مرورگر است و با یونیکد و زبان فارسی هم مشکلی ندارد.
برای استفاده از این کتابخانهی native در دات نت، شخصی پروژهای را ایجاد کرده است به نام WkHtmlToXSharp که محصور کنندهی wkhtmltopdf میباشد. در ادامه به نحوه استفاده از آن خواهیم پرداخت:
الف) دریافت پروژه WkHtmlToXSharp
پروژه WkHtmlToXSharp را از آدرس زیر میتوانید دریافت کنید.
این پروژه به همراه فایلهای کامپایل شده نهایی wkhtmltopdf نیز میباشد و حجمی حدود 40 مگ دارد. به علاوه فعلا نسخه 32 بیتی آن در دسترس است. بنابراین باید دقت داشت که نباید تنظیمات پروژه دات نت خود را بر روی Any CPU قرار دهیم، زیرا در این حالت برنامه شما در یک سیستم 64 بیتی بلافاصله کرش خواهد کرد. تنظیمات target platform پروژه دات نتی ما حتما باید بر روی X86 تنظیم شود.
ب) پس از دریافت این پروژه و افزودن ارجاعی به اسمبلی WkHtmlToXSharp.dll، استفاده از آن به نحو زیر میباشد:
using System.IO; using WkHtmlToXSharp; using System; namespace Test2 { public class WkHtmlToXSharpTest { public static void ConvertHtmlStringToPdfTest() { using (var wk = new MultiplexingConverter()) { wk.Begin += (s, e) => Console.WriteLine("Conversion begin, phase count: {0}", e.Value); wk.Error += (s, e) => Console.WriteLine(e.Value); wk.Warning += (s, e) => Console.WriteLine(e.Value); wk.PhaseChanged += (s, e) => Console.WriteLine("PhaseChanged: {0} - {1}", e.Value, e.Value2); wk.ProgressChanged += (s, e) => Console.WriteLine("ProgressChanged: {0} - {1}", e.Value, e.Value2); wk.Finished += (s, e) => Console.WriteLine("Finished: {0}", e.Value ? "success" : "failed!"); wk.GlobalSettings.Margin.Top = "0cm"; wk.GlobalSettings.Margin.Bottom = "0cm"; wk.GlobalSettings.Margin.Left = "0cm"; wk.GlobalSettings.Margin.Right = "0cm"; wk.ObjectSettings.Web.EnablePlugins = false; wk.ObjectSettings.Web.EnableJavascript = false; wk.ObjectSettings.Load.Proxy = "none"; var htmlString = File.ReadAllText(@"c:\page.xhtml"); var tmp = wk.Convert(htmlString); File.WriteAllBytes(@"tst.pdf", tmp); } } } }
کار با وهله سازی از کلاس MultiplexingConverter شروع میشود. اگر علاقمند باشید که درصد پیشرفت کار به همراه خطاهای احتمالی پردازشی را ملاحظه کنید میتوان از رخدادگردانهایی مانند ProgressChanged و Error استفاده نمائید که نمونهای از آن در کد فوق بکارگرفته شده است.
تبدیل HTML به PDF آنچنان تنظیمات خاصی ندارد زیرا فرض بر این است که قرار است از همان تنظیمات اصلی HTML مورد نظر استفاده گردد. اما اگر نیاز به تنظیمات بیشتری وجود داشت، برای مثال به کمک GlobalSettings آن میتوان حاشیههای صفحات فایل نهایی تولیدی را تنظیم کرد.
موتور WebKit با توجه به اینکه موتور یک مرورگر است، امکان پردازش جاوا اسکریپت را هم دارد. بنابراین اگر قصد استفاده از آنرا نداشتید میتوان خاصیت ObjectSettings.Web.EnableJavascript را به false مقدار دهی کرد.
کار اصلی، در متد Convert انجام میشود. در اینجا میتوان یک رشته را که حاوی فایل HTML مورد نظر است به آن ارسال کرد و نتیجه نهایی، آرایهای از بایتها، حاوی فایل باینری PDF تولیدی است.
روش دیگر استفاده از این کتابخانه، مقدار دهی wk.ObjectSettings.Page میباشد. در اینجا میتوان Url یک صفحه اینترنتی را مشخص ساخت. در این حالت دیگر نیازی نیست تا به متد Convert پارامتری را ارسال کرد. میتوان از overload بدون پارامتر آن استفاده نمود.
یک نکته:
اگر میخواهید زبان فارسی را توسط این کتابخانه به درستی پردازش کنید، نیاز است حتما یک سطر زیر را به header فایل html خود اضافه نمائید:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
ما مطابق آموزشی که در این مقاله داده شده از یک اکشن متد برای ذخیره عکس ارسالی تو یک پوشه و سپس برگشت دادن مسیر عکس و از یک اکشن متد دیگه برای ذخیره اطلاعاتی که قراره همراه با فرم ارسال بشن (به همراه مسیر عکس برگشت داده شده)، استفاده میکنیم
مشکلی که ما موقع استفاده از این افزونه باهاش برخوردیم اینه که گاهی اوقات و همونطور که انتظار میره اکشن متد (AddAvatars) که وظیفه ذخیره عکس رو داره اول اجرا میشه و اکشن متد (Add) که وظیفه ذخیره اطلاعات رو داره دوم، ولی گاهی اوقات این ترتیب به هم میریزه و ابتدا اطلاعات ارسالی ذخیره میشه و بعد اکشن متد ذخیره عکس اجرا میشه.
سناریوی ما هم تا حدی شبیه به سناریویی هست که آقای احمدی مطرح کردند، ولی همونطور که گفتیم مشکل اصلی اینه که اکشن متدها هر بار با ترتیبهای متفاوت فراخوانی میشن
<div class="container-fluid"> @using (Ajax.BeginForm("Add", "Authors", new AjaxOptions { UpdateTargetId = "result", InsertionMode = InsertionMode.Replace, HttpMethod = "POST" }, new { @class = "form-horizontal", id = "UploadFile" })) { @Html.AntiForgeryToken() <div class="control-group"> <label class="control-label" for="AuhtorFirstNameAndLastName">نام نویسنده</label> <div class="controls"> @Html.TextBoxFor(author => author.AuhtorFirstNameAndLastName, new { placeholder = "نام نویسنده" }) </div> @Html.ValidationMessageFor(author => author.AuhtorFirstNameAndLastName) </div> <div class="control-group"> <label class="control-label" for="Status">ارسال عکس</label> <div class="controls"> <input type="file" name="avatarFile" id="avatarFile" /> </div> <div> @*<input type="submit" name="btn-submit" value="ارسال" class="btn btn-success" />*@ <img id="loading" alt="1" src="Images/loading83.gif" style="display: none;" /> </div> </div> <div id="result"></div> <input type="submit" name="btn-submit" value="افزودن نویسنده" class="btn btn-success" /> <input type="button" name="btn-colose" id="btn-close" value="انصراف" class="btn btn-danger" onclick="$dialog.dialog('close');" /> } </div> <script type="text/javascript"> $('#UploadFile').submit(function () { $("#loading").show(); $.ajaxFileUpload({ url: "@Url.Action("AddAvatar","Authors")", secureuri: false, fileElementId: 'avatarFile', dataType: 'json', data: {}, success: function (data, status) { $("#loading").hide(); }, error: function (data, status, e) { $("#loading").hide(); } }); }); </script>
معماری لایه بندی نرم افزار #1
برای استفاده از این مزایا باید سرویس Microsoft SharePoint Foundation User Code Service فعال باشد .
استفاده از Sandbox Solution نیازمند 3 چیز است :
1 - User Code Service یا SPUCHostService.exe : این سرویس وظیفه مدیریت کدهای Sandbox روی سرور و میزبانی درخواستهای Sandbox به سرور را دارد.
2 - Sandbox Worker Process یا SPUCWorkerProcess.exe : این سرویس برای مدیریت پردازش کدهای اجرایی Sandbox استفاده میشود خصوصا در مورارد امنیتی (فرض کنید درون کد حلقه بی نهایتی وجود داشته باشد. اگر بار اجرایی آن بر روی w3wp.exe باشد ، باعث متوقف شدن کارکرد تمام پروسه میشود. بار این مسئولیت بر دوش SPUCWorkerProcess.exe میباشد )
3 - Sandbox Worker Process Proxy یا SPUCWorkerProcessProxy.exe : این سرویس برای اجرای کدهای Client Object Model در sandbox استفاده میشود.
می توانید ارتباط پردازههای فوق را در نمودار زیر مشاهد کنید :
با توجه به این توضیحات به دلیل استفاده از Sandbox میپردازیم :
1 - sandbox Solutionها امن هستند.
2 - sandbox Solutionها میتوانند مانیتور شوند.
3 - sandbox Solutionها حداقل تاثیر را روی هم دارند.
4 - sandbox Solutionها عملیات I/O کمی روی فایلهای سیستمی دارند
5 - sandbox Solutionها امکان debugging دارند
6 - sandbox Solutionها امکان توسعه ، انتشار و به روز رسانی دارند
7 - sandbox Solutionها امکان تعریف Policy برای CAS ایجاد میکند (Code Access Security برای قابل اطمینان ساخت کد ها)
8 - sandbox Solutionها امکان اعتبار سنجی به کمک کلاس SPSolutionValidator را میدهد.
9 - sandbox Solutionها امکان تعریف SLA های مختلف را میدهد.
چه قسمت هایی توسط Sandbox Solution پشتیبانی میشود :
هنگام ایجاد یک پروژه از نوع sandbox در ویژوال استودیو ، یکسری امکانات از برنامه نویس سلب میشود و محدودیتهای برای استفاده از کلاسها برای وی اعمال میشود .موارد زیر ، از جمله مواردی هستند که توسعه دهنده میتواند از آنها در sandbox استفاده کند :
Add-SPSolution c:\code\SharePointProject2\bin\debug\SharePointProject2.wsp
stsadm –o addsolution –name SharePointProject2.wsp
internal class imageCopyrightUI : Module { protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo) { base.Initialize(serviceProvider, moduleInfo); IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel)); ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright",Resource1.Visual_Studio_2012,Resource1.Visual_Studio_2012); controlPanel.RegisterPage(modulePageInfo); } }
internal class imageCopyrightUI : Module { protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo) { base.Initialize(serviceProvider, moduleInfo); IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel)); ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright", Resource1.Visual_Studio_2012, Resource1.Visual_Studio_2012); controlPanel.RegisterPage(ControlPanelCategoryInfo.AspNet,modulePageInfo); } }
internal class imageCopyrightUI : Module { protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo) { base.Initialize(serviceProvider, moduleInfo); IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel)); ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright", Resource1.Visual_Studio_2012, Resource1.Visual_Studio_2012); ControlPanelCategorization areaCategorization = null; foreach (ControlPanelCategorization categorization in controlPanel.Categorizations) { if (categorization.Key == ControlPanelCategorization.AreaCategorization) { areaCategorization = categorization; break; } } ControlPanelCategoryInfo cat=new ControlPanelCategoryInfo("dotnettips","Dot Net Tips","This is a Tutorial",areaCategorization); controlPanel.RegisterCategory(cat); controlPanel.RegisterPage(cat.Name,modulePageInfo); } }
ControlPanelCategorization areaCategorization = null; ControlPanelCategorization CategoryCategorization = null; foreach (ControlPanelCategorization categorization in controlPanel.Categorizations) { if (categorization.Key == ControlPanelCategorization.AreaCategorization) { areaCategorization = categorization; } if (categorization.Key == ControlPanelCategorization.CategoryCategorization) { CategoryCategorization = categorization; } } ControlPanelCategoryInfo cat1=new ControlPanelCategoryInfo("dotnettipsarea","Dot Net Tips Area","This is a Tutorial",areaCategorization); controlPanel.RegisterCategory(cat1); controlPanel.RegisterPage(cat1.Name,modulePageInfo); ControlPanelCategoryInfo cat2 = new ControlPanelCategoryInfo("dotnettipscat", "Dot Net Tips Category", "This is a Tutorial", CategoryCategorization); controlPanel.RegisterCategory(cat2); controlPanel.RegisterPage(cat2.Name, modulePageInfo);
تا به اینجا این مبحث از سری آموزشی ما بسته میشود. در مقالات آینده موارد دیگری از IIS را مورد بررسی قرار خواهیم داد.
استفاده از Async و Await در برنامههای ASP.NET MVC
- «همزمان اجرا میشه»
خیر. متدهای Async واقعی مثل نمونه ارائه شده در EF غیرهمزمان اجرا میشوند. یعنی، ترد جاری را آزاد کرده و ASP.NET میتواند از آن ترد برای پاسخ دهی به یک درخواست رسیده دیگر استفاده کند.
- «باید منتظر پاسخ از db بمونه»
استفاده از await و async سبب بازنویسی بدنه متد توسط یک state machine در پشت صحنه میشوند. یعنی اینطور نیست که روش اجرای آن blocking است و تا رسیدن پاسخ از بانک اطلاعاتی، از این ترد دیگر نمیشود استفاده کرد. جایی که await فراخوانی میشود، ترد جاری برای استفاده بعدی آزاد خواهد شد. در ادامه مابقی کدها تبدیل به یک IEnumerator میشوند که هر دستور آن شامل یک yield return است. هر مرحله که تمام شد، MoveNext این IEnumerator فراخوانی میشود تا به مرحلهی بعدی برسد. به این روش استفاده از coroutines هم گفته میشود که در سی شارپ 5، کامپایلر کار تولید کدهای آنرا انجام میدهد. برای مطالعه بیشتر:
- انجام پی در پی اعمال Async به کمک Iterators - قسمت اول
- انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم
- «چون تا فایل آپلود نشه ذخیره آدرس تو db بی معنیه»
ذخیره آدرس هم یک قسمت از کار است و اتفاقا وابسته به سیستم جاری هم نیست. وابسته است به یک بانک اطلاعاتی که خارج از مرزهای سیستم، به صورت مستقل در حال فعالیت است (عموما البته؛ مثلا اگر از SQL Server استفاده میشود).
برای ذخیره فایلها در سیستم هم متدهای Async به کلاس Stream در دات نت 4.5 اضافه شدهاند؛ مثل WriteAsync . در این حالت هم میتوان از await WriteAsync برای ذخیره اطلاعات و بازهم آزاد کردن ترد جاری استفاده کرد.