FieldInfo fieldInfo = _Culture.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
قابلیت چند زبانه و Localization در AngularJs- بخش چهارم و نهایی: Best Practiceهای angular-translate
ex7_load_static_files
<script src="Scripts/angular.js"></script> <script src="Scripts/angular-cookies.js"></script> <script src="Scripts/angular-translate.js"></script> <script src="Scripts/angular-translate-storage-cookie.js"></script> <!-- for override loader methods in angular translate --> <script src="/src/service/loader-static-files.js"></script>
// Register a loader for the static files // So, the module will search missing translation tables under the specified urls. // Those urls are [prefix][langKey][suffix]. $translateProvider.useStaticFilesLoader({ prefix: '/l10n/', suffix: '.json' });
حال ببینیم که این فرآیند در loader-static-files چگونه پیاده سازی شده است. در این فایل یک متد load نوشته شده است که فایلهای static را طبق یک الگوی مشتمل بر prefix و suffix از سرور میخواند. لزومی ندارد که شما فایلها را حتما به صورت JSON و با این پسوند ذخیره کنید. اما چیزی که قطعی است این است که فایلها حتما باید به صورت key value ذخیره شده باشند.
تکه کد زیر اطلاعات فایل loader-static-files را نمایش میدهد.
angular.module('pascalprecht.translate') .factory('$translateStaticFilesLoader', $translateStaticFilesLoader); function $translateStaticFilesLoader($q, $http) { 'use strict'; return function (options) { if (!options || (!angular.isArray(options.files) && (!angular.isString(options.prefix) || !angular.isString(options.suffix)))) { throw new Error('Couldn\'t load static files, no files and prefix or suffix specified!'); } if (!options.files) { options.files = [{ prefix: options.prefix, suffix: options.suffix }]; } var load = function (file) { if (!file || (!angular.isString(file.prefix) || !angular.isString(file.suffix))) { throw new Error('Couldn\'t load static file, no prefix or suffix specified!'); } var deferred = $q.defer(); $http(angular.extend({ url: [ file.prefix, options.key, file.suffix ].join(''), method: 'GET', params: '' }, options.$http)).success(function (data) { deferred.resolve(data); }).error(function () { deferred.reject(options.key); }); return deferred.promise; }; var deferred = $q.defer(), promises = [], length = options.files.length; for (var i = 0; i < length; i++) { promises.push(load({ prefix: options.files[i].prefix, key: options.key, suffix: options.files[i].suffix })); } $q.all(promises).then(function (data) { var length = data.length, mergedData = {}; for (var i = 0; i < length; i++) { for (var key in data[i]) { mergedData[key] = data[i][key]; } } deferred.resolve(mergedData); }, function (data) { deferred.reject(data); }); return deferred.promise; }; } $translateStaticFilesLoader.displayName = '$translateStaticFilesLoader';
همانطور که ملاحظه میکنید، کد فوق یک سرویس با نام $translateStaticFilesLoader را تعریف نموده است. در صورتیکه ما در کنترلر فایل ex7، اصلا نامی از آن نبردیم و تنها از $translateProvider.useStaticFilesLoader استفاده نمودیم! جواب در نحوهی نگارش کد angular-translate نهفته است. در خط 866 فایل angular-translate تکه کد زیر مربوط به تعریف translateStaticFileLoader میباشد. همانطور که ملاحظه میکنید سرویس translateStaticFilesLoader درون فضای نام سرویس translateTable قرار گرفته است. بنابراین ما تنها با تعریف سرویس translateStaticFilesLoader، در حقیقت آن را override نمودهایم. در کد نمونهای که در بخشهای قبلی قرار دادهام یک فایل translate.js نیز قرار دارد که در فولدر src/services قرار گرفته است. این فایل نیز برخی از امکانات و سرویسهای built-in درون angular-translate را سفارشی نموده است.
/** * @ngdoc function * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader * @methodOf pascalprecht.translate.$translateProvider * * @description * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader. * * @param {Object=} options Optional configuration object */ this.useStaticFilesLoader = function (options) { return this.useLoader('$translateStaticFilesLoader', options); };
در این 4 مجموعه سعی کردم تمامی آنچه را که برای ایجاد قابلیت چند زبانه و localization نیاز است و حیاتی بود، تشریح کنم. بنابراین تا کنون دانش خوبی دربارهی این کتابخانه کسب نمودهاید. باقی تمرینها را میتوانید بر حسب نیاز با استفاده از مستندات موجود در angular-translate مطالعه و استفاده نمایید.
Attack Surface Analyzer 1.0 منتشر شد
مقدمه ای بر Web Sql
In this post we will see some informations about Web SQL. I know you all are familiar with SQL, If not I strongly recommend you to read some basic informations here . As the name implies, Web SQL has so many similarities with SQL. So if you are good in SQL, you will love Web SQL too. Web SQL is an API which helps the developers to do some database operations in client side, like creating database, open the transaction, creating tables, inserting values to tables, deleting values, reading the data.
ساخت (Build) برنامههای Angular
Angular CLI کار ساخت و کامپایل برنامه را به صورت خودکار انجام داده و خروجی را در مسیری مشخص درج میکند. در اینجا میتوان گزینههایی را بر اساس نوع کامپایل مدنظر مانند کامپایل برای حالت توسعه و یا کامپایل برای حالت توزیع نهایی، انتخاب کرد. همچنین مباحث bundling و یکی کردن تعداد بالای ماژولهای برنامه در آن لحاظ میشوند تا برنامه در حالت توزیع نهایی، سبب 100ها رفت و برگشت به سرور برای دریافت ماژولهای مختلف آن نشود. به علاوه مباحث uglification (به نوعی obfuscation کدهای جاوا اسکریپتی نهایی) و tree-shaking (حذف کدهایی که در برنامه استفاده نشدهاند؛ یا کدهای مرده) نیز پیاده سازی میشوند. با انجام tree-shaking، نه تنها اندازهی توزیع نهایی به کاربر کاهش پیدا میکند، بلکه مرورگر نیز حجم کمتری از کدهای جاوااسکریپتی را باید تفسیر کند.
برای شروع میتوان از دستور ذیل برای مشاهدهی تمام گزینههای مهیای ساخت برنامه استفاده کرد:
> ng build --help
"apps": [ { "outDir": "dist",
فایل | توضیح |
inline.bundle.js | WebPack runtime از آن برای بارگذاری ماژولهای برنامه و چسباندن قسمتهای مختلف به یکدیگر استفاده میشود. |
main.bundle.js | شامل تمام کدهای ما است. |
polyfills.bundle.js | Polyfills - جهت پشتیبانی از مرورگرهای مختلف. |
styles.bundle.js | شامل بسته بندی تمام شیوه نامههای برنامه است |
vendor.bundle.js | کدهای کتابخانههای ثالث مورد استفاده و همچنین خود Angular، در اینجا بسته بندی میشوند. |
روشی برای بررسی محتوای bundleهای تولید شده
تولید bundleها در جهت کاهش رفت و برگشتهای به سرور و بالا بردن کارآیی برنامه ضروری هستند؛ اما دقیقا این بسته بندیها شامل چه اطلاعاتی میشوند؟ این اطلاعات را میتوان از فایلهای source map تولیدی استخراج کرد و برای این منظور میتوان از برنامهی source-map-explorer استفاده کرد.
روش نصب عمومی آن:
> npm install -g source-map-explorer
> source-map-explorer dist/main.bundle.js
یک مثال: ساخت برنامهی مثال قسمت چهارم - تنظیمات مسیریابی در حالت dev
در ادامه، کار Build همان مثالی را که در قسمت قبل توضیح داده شد، بررسی میکنیم. برای این منظور از طریق خط فرمان به ریشهی پوشهی اصلی پروژه وارد شده و دستور ng build را صادر کنید. یک چنین خروجی را مشاهده خواهید کرد:
D:\Prog\angular-routing>ng build Hash: 123cae8bd8e571f44c31 Time: 33862ms chunk {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.bundle.js, main.bundle.js.map (main) 14.7 kB {3} [initial] [rendered] chunk {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered] chunk {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.34 MB [initial] [rendered] chunk {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
<!doctype html> <html> <head> <meta charset="utf-8"> <title>AngularRouting</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root>Loading...</app-root> <script type="text/javascript" src="inline.bundle.js"> </script><script type="text/javascript" src="polyfills.bundle.js"> </script><script type="text/javascript" src="styles.bundle.js"> </script><script type="text/javascript" src="vendor.bundle.js"> </script><script type="text/javascript" src="main.bundle.js"></script> </body> </html>
یک نکته: زمانیکه دستور ng serve -o صادر میشود، در پشت صحنه دقیقا همین دستور ng build صادر شده و اطلاعات را درون حافظه تشکیل میدهد. اما اگر کار ng build را دستی انجام دهیم، اینبار ng serve -o اطلاعات را از پوشهی dist دریافت میکند. بنابراین در حین کار با ng serve -o نیازی به build دستی پروژه نیست.
سؤال: چرا حجم فایل endor.bundle.js اینقدر بالا است و شامل چه اجزایی میشود؟
نکتهای که در اینجا وجود دارد، حجم بالای فایل vendor.bundle.js آن است که 2.34 MB میباشد:
چون دستور ng build بدون پارامتری ذکر شدهاست، برنامه را برای حالت توسعه Build میکند و به همین جهت هیچگونه بهینه سازی در این مرحله صورت نخواهد گرفت. برای بررسی محتوای این فایل میتوان دستور ذیل را در ریشهی اصلی پروژه صادر کرد:
> source-map-explorer dist/vendor.bundle.js
همانطور که مشاهده میکنید، در حالت بهینه سازی نشده و Build برای توسعه، کامپایلر Angular حدود 41 درصد حجم فایل vendor.bundle.js را تشکیل میدهد. به علاوه ماژولها و قسمتهایی را ملاحظه میکنید که اساسا برنامهی فعلی مثال ما از آنها استفاده نمیکند؛ مانند http، فرمها و غیره.
سفارشی سازی Build برای محیطهای مختلف
اگر به پروژهی تولید شدهی توسط Angular CLI دقت کنید، حاوی پوشهای است به نام src\environments
هدف از فایلهای environment برای نمونه تغییر آدرس توزیع برنامه در حالت توسعه و ارائه نهایی است.
همچنین در اینجا میتوان نحوهی بهینه سازی فایلهای تولیدی را توسط Build Targets مشخص کرد و اینکار توسط ذکر پرچم prod-- (مخفف production) صورت میگیرد.
در ادامه، تفاوتهای دستورهای ng build و ng build --prod را ملاحظه میکنید:
- با اجرای ng build، از فایل environment.ts استفاده میشود؛ برخلاف حالت اجرای ng build --prod که از فایل environment.prod.ts استفاده میکند.
- Cache-busting در حالت ارائهی نهایی، به تمام اجزای پروژه اعمال میشود؛ اما در حالت توسعه فقط برای تصاویر قید شدهی در فایلهای css.
- فایلهای source map فقط برای حالت توسعه تولید میشوند.
- در حالت توسعه، cssها داخل فایلهای js تولیدی قرار میگیرند؛ اما در حالت ارائهی نهایی به صورت فایلهای css بسته بندی میشوند.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار uglification انجام نمیشود.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار tree-shaking یا حذف کدهای مرده و بدون ارجاع، انجام نمیشود.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار AOT انجام نمیشود. در اینجا AOT به معنای Ahead of time compilation است.
- در هر دو حالت توسعه و ارائهی نهایی کار bundling و دسته بندی فایلها انجام خواهد شد.
به همین جهت است که ng build سریع است؛ اما حجم بالاتری را هم تولید میکند. چون بسیاری از بهینه سازیهای حالت ارائهی نهایی را به همراه ندارد.
دستورات build برای حالت توسعه و ارائهی نهایی
برای حالت توسعه، هر 4 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build متداولتر است:
>ng build --target=development --environment=dev >ng build --dev -e=dev >ng build --dev >ng build
برای حالت ارائهی نهایی، هر 3 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build --prod متداولتر است:
>ng build --target=production --environment=prod >ng build --prod -e=prod >ng build --prod
همچنین هر کدام از این دستورات را توسط پرچمهای ذیل نیز میتوان سفارشی سازی کرد:
پرچم | مخفف | توضیح |
sourcemap-- | sm- | تولید سورسمپ |
aot-- | Ahead of Time compilation | |
watch-- | w- | تحت نظر قرار دادن فایلها و ساخت مجدد |
environment-- | e- | محیط ساخت |
target-- | t- | نوع ساخت |
dev-- | مخفف نوع ساخت جهت توسعه | |
prod-- | مخفف نوع ساخت جهت ارائه نهایی |
برای مثال در حالت prod، سورسمپها تولید نخواهند شد. اگر علاقمندید تا این فایلها نیز تولید شوند، پرچم souremap را نیز ذکر کنید.
و یا اگر برای حالت dev میخواهید AOT را فعالسازی کنید، پرچم aot-- را در آنجا قید کنید.
یک مثال: ساخت برنامهی مثال قسمت چهارم - تنظیمات مسیریابی در حالت prod
تا اینجا خروجی حالت dev ساخت برنامهی قسمت چهارم را بررسی کردیم. در ادامه دستور ng build --prod را در ریشهی پروژه صادر میکنیم:
D:\Prog\angular-routing>ng build --prod Hash: f5bd7fd555a85af8a86f Time: 39932ms chunk {0} polyfills.18173234f9641113b9fe.bundle.js (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.c6958def7c5f51c45261.bundle.js (main) 50.3 kB {3} [initial] [rendered] chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 69 bytes {4} [initial] [rendered] chunk {3} vendor.b426ba6883193375121e.bundle.js (vendor) 1.37 MB [initial] [rendered] chunk {4} inline.8cec210370dd3af5f1a0.bundle.js (inline) 0 bytes [entry] [rendered]
همانطور که ملاحظه میکنید، اینبار نه تنها حجم فایلها به میزان قابل ملاحظهای کاهش پیدا کردهاند، بلکه این نامها به همراه یک سری hash هم هستند که کار cache-busting (منقضی کردن کش مرورگر، با ارائهی نگارشی جدید) را انجام میدهند.
در ادامه اگر بخواهیم مجددا برنامهی source-map-explorer را جهت بررسی محتوای فایلهای js اجرا کنیم، به خطای عدم وجود sourcemapها خواهیم رسید (چون در حالت prod، به صورت پیش فرض غیرفعال هستند). به همینجهت برای این مقصود خاص نیاز است از پرچم فعالسازی موقت آن استفاده کرد:
> ng build --prod --sourcemap > source-map-explorer dist/vendor.b426ba6883193375121e.bundle.js
همانطور که در تصویر نیز مشخص است، اینبار کامپایلر Angular به همراه تمام ماژولهایی که در برنامه ارجاعی به آنها وجود نداشتهاست، حذف شدهاند و کل حجم بستهی Angular به 366 KB کاهش یافتهاست.
بررسی دستور ng serve
تا اینجا برای اجرای برنامه در حالت dev از دستور ng serve -o استفاده کردهایم. کار ارائهی برنامه توسط این دستور، از محتوای کامپایل شدهی درون حافظه با مدیریت webpack انجام میشود. به همین جهت بسیار سریع بوده و قابلیت live reload را ارائه میدهد (نمایش آنی تغییرات در مرورگر، با تغییر فایلها).
همانند تمام دستورات دیگر، اطلاعات بیشتری را در مورد این دستور، از طریق راهنمای آن میتوان به دست آورد:
> ng serve --help
که شامل این موارد هستند (علاوه بر تمام مواردی را که در حالت ng build میتوان مشخص کرد؛ مثلا ng serve --prod -o):
پرچم | مخفف | توضیح |
open-- | o- | بازکردن خودکار مرورگر پیش فرض. حالت پیش فرض آن گشودن مرورگر توسط خودتان است و سپس مراجعهی دستی به آدرس برنامه. |
port-- | p- | تغییر پورت پیش فرض مانند ng server -p 8626 |
live-reload-- | lr- |
فعال است مگر اینکه آنرا با false مقدار دهی کنید. |
ssl-- | ارائه به صورت HTTPS | |
proxy-config-- | pc- | Proxy configuration file |
استخراج فایل تنظیمات webpack از Angular CLI
Angular CLI برای مدیریت build، در پشت صحنه از webpack استفاده میکند. فایل تنظیمات آن نیز جزئی از فایلهای توکار این ابزار است و قرار نیست به صورت پیش فرض و مستقیم توسط پروژهی جاری ویرایش شود. به همین جهت آنرا در ساختار پروژهی تولید شده، مشاهده نمیکنید.
اگر علاقمند به سفارشی سازی بیشتر این تنظیمات پیش فرض باشید، ابتدا باید آنرا اصطلاحا eject کنید و سپس میتوان آنرا ویرایش کرد:
> ng eject Ejection was successful. To run your builds, you now need to do the following commands: - "npm run build" to build. - "npm run test" to run unit tests. - "npm start" to serve the app using webpack-dev-server. - "npm run e2e" to run protractor. Running the equivalent CLI commands will result in an error. ============================================ Some packages were added. Please run "npm install".
در این حالت است که فایل webpack.config.js به ریشهی پروژه جهت سفارشی سازی شما اضافه خواهد شد. همچنین فایلهای .angular-cli.json، package.json نیز جهت درج این تغییرات ویرایش میشوند.
و اگر در این لحظه پشیمان شدهاید (!) فقط کافی است تا این مرحلهی جدید commit شدهی به مخزن کد را لغو کنید و باز هم به همان Angular CLI قبلی میرسید.
Strong Name
System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
برای استفاده از این تکنولوژی ابتدا نیاز است تا یک جفتکلید عمومی/خصوصی (توسط ادمین، منبع گواهینامهها، یک بانک یا یک ابزار خاص) فراهم شود تا از آن برای اینکریپشن استفاده شود. سپس دادههای موردنظر (هر داده کلی که قصد ارسال و توزیع آن را داریم مثل یک اسمبلی) با استفاده از یک الگوریتم هشکردن (مثل MD5، SHA یا ترکیبی از آنها، هرچند MD5 توصیه نمیشود) پردازش شده و یک هشکد مخصوص تولید میشود. این هشکد با استفاده از کلید خصوصی دردسترس اینکریپت میشود و به عنوان یک امضای دیجیتال به همراه داده موردنظر ارسال یا توزیع میشود. در سمت مصرف کننده که با استفاده از یک روش خاص و امن به کلید عمومی دسترسی پیدا کرده است عملیات دیکریپت کردن این امضای دیجیتال با استفاده از کلید عمومی انجام شده و هشکد مربوطه بدست میآید. همچنین عملیات تولید هشکد با استفاده از دادهها در سمت مصرف کننده انجام شده و هشکد دادهها نیز دوباره با استفاده از همان الگوریتم استفاده شده در سمت توزیعکننده تولید میشود. سپس این دو مقدار محاسبه شده در سمت مصرفکننده با یکدیگر مقایسه شده و درصورت برابر بودن میتوان اطمینان حاصل کرد همان دادهای که توزیع کننده در اصل ارسال کرده بدون تغییر به دست مصرف کننده رسیده است. درواقع ویژگی اینکریپت/دیکریپت کردن دادهها توسط جفتکلید این است که بهصورت یکطرفه بوده و دادههای اینکریپت شده با استفاده از یک کلید خصوصی را تنها با استفاده از کلید عمومی همان کلید خصوصی میتوان بدرستی دیکریپت کرد.
1. تولید و مدیریت جفتکلیدهای قوی- نامگذاریشده (Strongly Named Key Pairs)
همانطور که در قسمت قبل اشاره شد برای نامگذاری قوی یک اسمبلی به یک کلید عمومی (public key) و یک کلید خصوصی (private key) که در مجموع به آن یک جفت کلید (key pair) میگویند، نیاز است.برای اینکار میتوان با استفاده از برنامه sn.exe (عنوان کامل آن Microsoft .Net Framework Strong Name Utility است) یک جفت کلید تولید کرده و آن را در یک فایل و یا در CSP (یا همان cryptographic service provider) ذخیره کرد. همچنین اینکار را میتوان توسط ویژوال استودیو نیز انجام داد. امکان موردنظر در فرم پراپرتی یک پروژه و در تب Signing آن وجود دارد.
نکته: یک CSP عنصری از API کریپتوگرافی ویندوز (Win32 CryptoAPI) است که سرویسهایی چون اینکریپشن، دیکریپشن، و تولید امضای دیجیتال را فراهم میکند. این پرووایدرها همچنین تسهیلاتی برای مخازن کلیدها فراهم میکنند که از اینکریپشنهای قوی و ساختار امنیتی سیستم عامل (سیستم امنیتی و دسترسی کاربران ویندوز) برای محافظت از تمام کلیدهای کریپتوگرافی ذخیره شده در مخزن استفاده میکند. بهطور خلاصه و مفید میشود اشاره کرد که میتوان کلیدهای کریپتوگرافی را درون یک مخزن کلید CSP ذخیره کرد و تقریبا مطمئن بود که تا زمانیکه هیچکس کلمه عبور سیستم عامل را نداند، این کلیدها امن خواهند ماند. برای کسب اطلاعات بیشتر به دادههای CryptoAPI در اسناد SDK سیستم عامل خود مراجعه کنید.
برنامه sn به همراه SDKهای ویندوز نصب میشود. البته با نصب ویژوال استودیو تمام SDKهای موردنیاز مطابق با نسخههای موجود، نصب خواهد شد. مسیر نسخه 4 و 32 بیتی این برنامه در سیستم عامل Windows 7 بهصورت زیر است:
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sn.exe
با استفاده از آرگومان k همانند دستور زیر یک جفتکلید جدید تولید شده و در فایل MyKeys.snk در ریشه درایو d: ذخیره میشود:
sn –k d:\MyKeys.snk
نکته: به بزرگی و کوچکی حروف سوییچهای دستورات برنامه sn دقت کنید!
این کار یک جفت کلید کریپتوگرافی 1024 بیتی بهصورت تصادفی تولید میکند. این دستور را باید در خط فرمانی (Command Prompt) اجرا نمود که مسیر فایل sn.exe را بداند. برای راحتی کار میتوان از خط فرمان ویژوال استودیو (Visual Studio Command Prompt) استفاده کرد.
نکته: اجرای عملیات فوق در یک شرکت یا قسمت توسعه یک شرکت، تنها یک بار نیاز است زیرا تمام اسمبلیهای تولیدی تا زمانیکه عناوین ساده متمایزی دارند میتوانند از یک جفت کلید مشترک استفاده کنند.
نکته: هرچند که میتوان از پسوندهای دیگری نیز برای نام فایل حاوی جفت کلید استفاده کرد، اما توصیه میشود از همین پسوند snk. استفاده شود.
فایل تولید شده حاوی هر دو کلید «عمومی» و «خصوصی» است. میتوان با استفاده از دستور زیر کلید عمومی موجود در فایل mykeys.snk را استخراج کرده و در فایل mypublickey.snk ذخیره کرد:
sn –p d:\mykeys.snk d:\mypublickey.snk
sn -tp MyPublicKey.snk
sn -i MyKeys.snk MyStrongNameKeys
sn –m n
sn –m y
sn -d MyStrongNameKeys
نکته: برای استفاده از این ویژگی در ویژوال استودیو، باید در تب Signing در تنظیمات پروژه گزینه Sign the Assembly را انتخاب کرد. سپس میتوان فایل حاوی جفت کلیدهای تولیدشده را انتخاب یا فایل جدیدی تولید کرد. البته ویژوال استودیو تا نسخه 2010 امکانی جهت استفاده از مخازن CSP را ندارد.
[assembly:AssemblyKeyFileAttribute("MyKeys.snk")]
sn –vf MyAsm.exe
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. Failed to verify assembly -- Strong name validation failed for assembly MyAsm.exe'.
sn –p d:\MyKeys.snk d:\MyPublicKey.snk sn –pc MyKeysContainer d:\MyPublicKey.snk
csc.exe /delaysign /keyfile:d:\MyPublicKey.snk /out:d:\MyAsm.exe d:\Class1.cs
al /out:<assembly name> <module name> /keyfile:<file name>
sn –Vr d:\MyAsm.exe
sn –R d:\MyAsm.exe MyKeys.snk sn –R d:\MyAsm.exe MyKeysContainer
sn –D assembly1 assembly2
sn –Vu d:\MyAsm.exe
sn –Vx
sn –Vl
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe
gacutil /i c:\MyAsm.dll
gacutil /u MyAsm
gacutil /u MyAsm,Version=1.3.0.5
gacutil /l
gacutil /l MyAsm
چرا هنوز برنامه نویسی میکنم؟
پس از نصب Git اطمینان حاصل کنید که NodeJs ، npm و jspm نیز بر روی سیستم شما نصب باشند. در این قسمت گفتهایم که چگونه از این «اطمینان» آگاه شوید.
حال نوبت به ساخت اولین پروژهی MVC ما میرسد. یک پروژه MVC جدید با نام دلخواه خودتان در مسیر دلخواه خودتان ایجاد کنید. با خط فرمان، در ریشهی پروژه دستور زیر را اجرا کنید:
jspm init
بدون هیچ تغییری، به هیچ کدام از سوالات پاسخ ندهید و از دکمهی enter استفاده کنید تا مقادیر پیشفرض اعمال شوند. اگر تصویر زیر را در خروجی مشاهده کردید یعنی تا بدین جای کار به درستی پیش رفتهاید :
حالا نوبت به نصب محتویات Aurelia میباشد. برای این کار دستورات زیر را اجرا کنید :
jspm install aurelia-framework jspm install aurelia-bootstrapper
توجه داشته باشید، اگر دستورات بالا به درستی اجرا و تکمیل شوند، باید پس از پایان هر دستور، پیام زیر را در انتهای خروجی مشاهده کنید:
پس از این کارها، فایل Layout را باز کنید و کدهای آن را به صورت زیر تغییر دهید:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Aurelia - www.dotnettips.info</title> </head> <body aurelia-app> <div> @RenderBody() </div> <script src="~/jspm_packages/system.js"></script> <script src="~/config.js"></script> <script> System.import("aurelia-bootstrapper"); </script> </body> </html>
export class App { }
<template> <h3>www.dotnettips.info</h3> </template>
در بخشهای بعدی در مورد کدهای فوق و همچنین به سایر مباحث دیگر Aurelia میپردازیم.
نکتهی تکمیلی
زمانیکه میخواهید از دستوارت jspm استفاده کنید، باید به ریشهی برنامه مراجعه کنید. حال اگر پوشههای تودرتوی زیادی داشته باشید، این رفت و آمدها زمانبر و خسته کننده خواهند شد. برای حل این مشکل کافیست روی پروژه، راست کلیک کنید و گزینهی Open Folder in File Explorer را انتخاب کنید تا ریشهی پروژه باز شود. حالا کافیست در فضای خالی Windows Explorer کلید Shift را گرفته و راست کلیک کنید. حالا یک آیتم جدید با نام Open command window here ظاهر شده است. کافیست روی آن کلیک کنید. CMD در ریشهی سایت باز خواهد شد.
دانلود پروژه جاری در مخزن گیت
/// <summary> /// /// </summary> public class CompanyModel { /// <summary> /// Table Identity /// </summary> public int Id { get; set; } /// <summary> /// Company Name /// </summary> [DisplayName("نام شرکت")] public string CompanyName { get; set; } /// <summary> /// Company Abbreviation /// </summary> [DisplayName("نام اختصاری شرکت")] public string CompanyAbbr { get; set; } }
@{ const string viewTitle = "شرکت ها"; ViewBag.Title = viewTitle; const string gridName = "companies-grid"; } <div class="col-md-12"> <div class="form-panel"> <header> <div class="title"> <i class="fa fa-book"></i> @viewTitle </div> </header> <div class="panel-body"> <div id="@gridName"> </div> </div> </div> </div> </div> @section scripts { <script type="text/javascript"> $(document).ready(function () { $("#@gridName").kendoGrid({ dataSource: { type: "json", transport: { read: { url: "@Html.Raw(Url.Action(MVC.Company.CompanyList()))", type: "POST", dataType: "json", contentType: "application/json" } }, schema: { data: "Data", total: "Total", errors: "Errors" } }, pageSize: 10, serverPaging: true, serverFiltering: true, serverSorting: true }, pageable: { refresh: true }, sortable: { mode: "multiple", allowUnsort: true }, editable: false, filterable: false, scrollable: false, columns: [ { field: "CompanyName", title: "نام شرکت", sortable: true, }, { field: "CompanyAbbr", title: "مخفف نام شرکت", sortable: true }] }); }); </script> }
مشکلی که در کد بالا وجود دارد این است که با تغییر نام هر یک از متغییر هایمان ، اطلاعات گرید در ستون مربوطه نمایش داده نمیشود.همچنین عناوین ستونها نیز از DisplayName مدل پیروی نمیکنند.توسط متدهای الحاقی زیر این مشکل برطرف شده است.
/// <summary> /// /// </summary> public static class PropertyExtensions { /// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expression"></param> /// <returns></returns> public static MemberInfo GetMember<T>(this Expression<Func<T, object>> expression) { var mbody = expression.Body as MemberExpression; if (mbody != null) return mbody.Member; //This will handle Nullable<T> properties. var ubody = expression.Body as UnaryExpression; if (ubody != null) { mbody = ubody.Operand as MemberExpression; } if (mbody == null) { throw new ArgumentException("Expression is not a MemberExpression", "expression"); } return mbody.Member; } /// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expression"></param> /// <returns></returns> public static string PropertyName<T>(this Expression<Func<T, object>> expression) { return GetMember(expression).Name; } /// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expression"></param> /// <returns></returns> public static string PropertyDisplay<T>(this Expression<Func<T, object>> expression) { var propertyMember = GetMember(expression); var displayAttributes = propertyMember.GetCustomAttributes(typeof(DisplayNameAttribute), true); return displayAttributes.Length == 1 ? ((DisplayNameAttribute)displayAttributes[0]).DisplayName : propertyMember.Name; } }
public static string PropertyName<T>(this Expression<Func<T, object>> expression)
public static string PropertyDisplay<T>(this Expression<Func<T, object>> expression)
بنابراین View مربوطه را اینگونه بازنویسی میکنیم:
@using Models @{ const string viewTitle = "شرکت ها"; ViewBag.Title = viewTitle; const string gridName = "companies-grid"; } <div class="col-md-12"> <div class="form-panel"> <header> <div class="title"> <i class="fa fa-book"></i> @viewTitle </div> </header> <div class="panel-body"> <div id="@gridName"> </div> </div> </div> </div> </div> @section scripts { <script type="text/javascript"> $(document).ready(function () { $("#@gridName").kendoGrid({ dataSource: { type: "json", transport: { read: { url: "@Html.Raw(Url.Action(MVC.Company.CompanyList()))", type: "POST", dataType: "json", contentType: "application/json" } }, schema: { data: "Data", total: "Total", errors: "Errors" } }, pageSize: 10, serverPaging: true, serverFiltering: true, serverSorting: true }, pageable: { refresh: true }, sortable: { mode: "multiple", allowUnsort: true }, editable: false, filterable: false, scrollable: false, columns: [ { field: "@(PropertyExtensions.PropertyName<CompanyModel>(a => a.CompanyName))", title: "@(PropertyExtensions.PropertyDisplay<CompanyModel>(a => a.CompanyName))", sortable: true, }, { field: "@(PropertyExtensions.PropertyName<CompanyModel>(a => a.CompanyAbbr))", title: "@(PropertyExtensions.PropertyDisplay<CompanyModel>(a => a.CompanyAbbr))", sortable: true }] }); }); </script> }
این افزونه با استفاده از ابزار Visual Studio Tools for Office که به VSTO مشهور شده است، تهیه شد. در بسته به روز رسانی سیستم که در ذیل (معرفی افزونه) نیز معرفی شد نگارش sp1 vsto3.0 آن به صورت خودکار نصب خواهد شد.
برای ایجاد این پروژه در VS.Net 2008 ، تنها کافی است یک پروژه جدید Word add-in را آغاز نمائیم. (شکل زیر)
قبل از ادامه بحث، بهتر است در مورد بانک اطلاعاتی مورد استفاده نیز توضیح داده شود. در اینجا از SQLite استفاده شد. (بسیار سبک، کم حجم و سریع است و اساسا یک کاربر نهایی برای تنظیمات آن نیازی نیست اطلاعاتی داشته باشد). بسته به روز رسانی سیستم (در مطلب قبلی)، این مورد را نیز به صورت خودکار نصب خواهد کرد (در GAC باید نصب شود وگرنه افزونه قادر به یافتن آن نخواهد شد).
برای ایجاد این بانک اطلاعاتی، از افزونه SQLite manager برای فایرفاکس استفاده شد. (این افزونه رایگان شما را از هر ابزار جانبی برای مدیریت یک بانک اطلاعاتی SQLite بینیاز میکند)
برای مثال فایل ErrorsBank.sqlite برنامه افزونه فارسی به پارسی را توسط افزونه SQLite manager فایرفاکس باز کنید (این فایل را در محل نصب افزونه میتوانید پیدا کنید). در اینجا میتوان جداول جدید را ایجاد کرد، کوئریهای دلخواه را اجرا نمود و یا اطلاعات را مرور کرده، حذف یا ویرایش کرد (شکل زیر).
و خوشبختانه این بانک اطلاعاتی و محصور کنندههای آن با اطلاعات یونیکد فارسی هیچ مشکلی ندارند و برای کارهایی با وسعت کم و تعداد رکورد پائین یکی از بهترین انتخابها بهشمار میروند.
نحوه استفاده از SQLite نیز در دات نت بسیار ساده است. اگر با ADO.Net کار کرده باشید، پس از افزودن ارجاعی از اسمبلی System.Data.SQLite.DLL به پروژه و معرفی فضای نام آن به پروژه، تنها کافی است در کدهای قبلی خود برای مثال SqlConnection را به SQLiteConnectionتغییر دهید و امثال آن. یعنی دانش ADO.Net شما در اینجا نیز کاملا قابل استفاده خواهد بود و نیازی نیست مدتی را صرف آشنا شدن با کلاسها و مفاهیم جدید نمائید (البته این تنها زمانی معنا خواهد داشت که به ویزاردها عادت نکرده باشید و کارهای خود را با کد نویسی انجام داده باشید).
تنها یک نکته را باید بهخاطر داشت و آن هم مربوط است به ساز و کار درونی SQLite . هنگام انجام عملیات update یا insert حتما از transaction استفاده کنید تا سرعت کوئریهای شما در SQLite به نحو شگفت انگیزی افزایش یابد. مثالی در این مورد را در فایل chm راهنمای SQLite.NET میتوانید پیدا کنید.
مطلب دیگری که پیش از پرداختن به کد نویسی افزونه باید با آن آشنا شویم، مفهوم smart tags در مجموعه آفیس است که در این پروژه از آن استفاده گردید.
smart tags در مجموعه آفیس برچسبهایی هستند که به صورت خودکار توسط یکی از محصولات آفیس مثلا ورد یا اکسل و امثال آن، پس از تشخیص یک کلمه خاص ایجاد میشوند و میتوان اعمالی را به این برچسب ایجاد شده انتساب داد. برای مثال در اینجا امکان جایگزین کردن کلمه فارسی با معادل پارسی در نظر گرفته شد.
ویدیویی در مورد نحوه ایجاد اسمارت تگها در VS.Net و یا مثالی پیشرفتهتر در مورد تشخیص دمای فارنهایت در یک متن و ایجاد smart tag مخصوص به آن برای تبدیل به سلسیوس. (از regular expressions جهت یافتن یک الگو در متن استفاده شده است)
در این پروژه، حدود 3800 واژه فارسی به یک smart tag انتساب داده میشود (در روال استاندارد ThisAddIn_Startup). سپس در هنگام نمایش آن، معادل پارسی کلمه نیز به منوی باز شده افزوده گشته و در روال رخداد کلیک آن، تعویض کلمه تشخیص داده شده با واژه پیدا شده صورت خواهد گرفت.
در ادامه فرض بر این است که یک پروژه جدید word add-in را در VS.Net ایجاد کردهاید و همچنین ارجاعی را به فایل System.Data.SQLite.DLL افزودهاید.
using System;
using System.Diagnostics;
using Microsoft.Office.Tools.Word;
using Action = Microsoft.Office.Tools.Word.Action;
private SmartTag _st;
private void init()
{
try
{
//Enable Smart Tags in Word
if (!Application.Options.LabelSmartTags)
{
//ممکن است اسمارت تگها در ورد غیرفعال باشند. به این صورت میشود آنها را فعال کرد
Application.Options.LabelSmartTags = true;
}
_st = new SmartTag(@"www.microsoft.com/Demo#FarsiSmartTag", @"فارسی به پارسی");
//دریافت واژههای فارسی از دیتابیس و افزودن خودکار آنها به اسمارت تگها
if (!DBhelper.AddSmartTagItems(_st, "select distinct farsi from tblFarsiToParsi")) return;
Action stActions = new Action("تبدیل");//تعریف یک اکشن جدید
stActions.Click += stActions_Click;//انتساب روالهای رخداد گردان
stActions.BeforeCaptionShow += stActions_BeforeCaptionShow;
_st.Actions = new[] { stActions };
VstoSmartTags.Add(_st);//افزودن اسمارت تگ به مجموعه
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}
private void ThisAddIn_Startup(object sender, EventArgs e)
{
init();
}
static void stActions_BeforeCaptionShow(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
string parsi = DBhelper.FindParsi(e.Text);//معادل پارسی از دیتابیس دریافت میشود
clickedAction.Caption = (parsi == string.Empty ? e.Text : parsi);
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}
static void stActions_Click(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
e.Range.Text = clickedAction.Caption;//جایگزینی متن موجود با عنوانی که پیشتر پارسی شده است
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}
همچنین باید دقت داشت که اگر متغیری در سطح کلاس تعریف نشود به احتمال زیاد تا دقایقی بعد توسط garbage collector به دیار باقی خواهد شتافت (تعریف st_ در اینجا). اینجاست که شاید ساعتها وقت صرف کنید که چرا روالهای رخداد گردان دیگر اجرا نمیشوند. چرا افزونه دیگر کار نمیکند.
همین! کل سورس این add-in منهای بحث دریافت اطلاعات از دیتابیس همین بود! وظیفهی تشخیص کلمات معرفی شده به ms-word بهعهدهی خود آن است و اینکار را نیز بهخوبی انجام میدهد. در گذشتههای نچندان دور ایجاد یک افزونه برای word واقعا مشکل بود که با این روش بسیاری از موانع برطرف شده است.
کلاس DBHelper که کار دریافت اطلاعات واژهها را از دیتابیس SQLite انجام میدهد به شرح زیر است:
using System;
using System.Data.SQLite;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Office.Tools.Word;
namespace Farsi2Parsi
{
class DBhelper
{
#region Methods (2)
// Public Methods (2)
public static bool AddSmartTagItems(SmartTag st, string strSQL)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
bool ret = false;
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();
if (myReader != null)
while (myReader.Read())
{
if (myReader.GetValue(0) != DBNull.Value)
st.Terms.Add(myReader.GetValue(0).ToString());
}
ret = true;
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 7);
}
finally
{
if (myReader != null)
myReader.Close();
if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}
public static string FindParsi(string farsi)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
string ret = string.Empty;
string strSQL = "select parsi from tblFarsiToParsi where farsi='" + farsi.Replace("'", "''") + "'";
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();
if (myReader != null)
{
myReader.Read(); //اولین مورد کافی است
if (myReader.GetValue(0) != DBNull.Value)
ret = myReader.GetValue(0).ToString();
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 8);
}
finally
{
if (myReader != null)
myReader.Close();
if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}
#endregion Methods
}
}
هنگام نصب برنامه، مسیر پوشه نصب در رجیستری ویندوز توسط نصاب نوشته خواهد شد. از همین مورد برای ایجاد رشته اتصالی به دیتابیس استفاده گردید.
class ConStr
{
public static string ConnectionString
{
get
{
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\FarsiToParsi").GetValue("folder") + "\\ErrorsBank.sqlite";
}
}
}
نصاب برنامه با استفاده از NSIS ایجاد شده که در روزی دیگر دربارهی آن توضیح خواهم داد.
اگر قصد داشته باشید از روشهای متداول استفاده کنید، مشاهده ویدیوی زیر توصیه میشود:
http://msdn.microsoft.com/en-us/office/bb851702.aspx
برای توزیع این نوع افزونهها علاوه بر دات نت فریم ورک، به چهار به روز رسانی دیگر نیز نیاز خواهد بود:
به روز رسانی نصاب ویندوز (که احتمالا نصب هست)
WindowsInstaller-KB893803-v2-x86.exe
Microsoft Office System Update: Redistributable Primary Interop Assemblies :
o2007pia.msi
نصب vsto و همچنین sp1 آن
vstor30.exe
vstor30sp1-KB949258-x86.exe
این موارد را من در بسته به روز رسانی سیستم قرار دادهام که به صورت خودکار و یکی پس از دیگری اجرا و نصب خواهند شد.
پس از آن با کلیک بر روی فایلی با پسوند vsto که در پوشه build برنامه موجود است، میتوان افزونه را نصب کرد (click once installation).
سایر اطلاعات در مورد پروژههای VSTO را میتوان از طریق وبلاگ رسمی آنها دنبال کرد:
http://blogs.msdn.com/vsto/
ایدههای دیگری را هم در همین رابطه میتوان پیاده سازی کرد. برای مثال درست کردن یک افزونه برای بررسی آئین نگارش فارسی در متون word. دقیقا با همین روش قابل پیاده سازی است و یا ایجاد غلط یاب بهتری نسبت به آنچه که هم اکنون برای آفیس 2003 توسط مایکروسافت ارائه شده است (این غلط یاب با صفحه کلید استاندارد تایپ ایران همخوانی ندارد، به همین جهت با استقبال نیز مواجه نشد).