using System.Threading.Tasks; using Telegram.Bot; namespace Bot.Engine.Console { public class Program { Api bot; string botToken = "BOT_TOKEN"; public static void Main(string[] args) { Task.Run(() => RunBot(botToken)); System.Console.ReadLine(); } /// <summary> /// /// </summary> public static async Task RunBot(string botToken) { #region راه اندازی ربات bot = new Api(botToken); var me = await bot.GetMe(); if (me != null) { System.Console.WriteLine("bot started {0}", me.Username); } else { System.Console.WriteLine("get bot failed "); } #endregion #region شروع گوش دادن به درخواستها var whileCount = 0; var offset = 0; while (true) { System.Console.WriteLine("while no {0}", whileCount); whileCount += 1; try { var updates = await bot.GetUpdatesAsync(offset); var updatesCount = updates.Count(); System.Console.WriteLine("updates count is {0}", updatesCount); System.Console.WriteLine("================================================================"); if (updatesCount > 0) { foreach (var update in updates) { try { offset = update.Id + 1; if (update.Message.Text!=null) { //echo msg await bot.SendTextMessageAsync(update.Message.Chat.Id, update.Message.Text); } else { await bot.SendTextMessageAsync(update.Message.Chat.Id, "لطفا یک پیام متنی بفرستید"); } } catch (Exception ex) { bot.SendTextMessage(update.Message.Chat.Id, ex.ToString()); } } continue; } } catch (Exception ex) { System.Console.WriteLine("Error Msg = {0}",ex.Message); } } #endregion } } }
- event اجرا شده (مثلا push) و شخص فراخوان این رخداد
- جزئیات commit ایی که منجر به این event شده از جمله SHA hash کامیت، author و committer کامیت
- تعداد Issue ها، Forkها و Starهای ریپازیتوری در آن لحظه
- و بسیاری اطلاعات دیگر
steps: - name: Dump GitHub Context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT"
GITHUB_CONTEXT: { "token": "***", "ref": "refs/heads/master", "sha": "3251394ad66ae8419e606fbf78570906ff2f01d3", "repository": "mjebrahimi/github_actions_test", "repositoryUrl": "git://github.com/mjebrahimi/github_actions_test.git", "run_id": "66966756", "run_number": "17", "actor": "mjebrahimi", "workflow": ".NET Core", "head_ref": "", "base_ref": "", "event_name": "push", "event": { "after": "3251394ad66ae8419e606fbf78570906ff2f01d3", "base_ref": null, "before": "b6c382e8fe74916daf6821a5a71efe480bd98a13", "commits": [ { "author": { "email": "mj.ebrahimi72@gmail.com", "name": "Mohammad Javad Ebrahimi", "username": "mjebrahimi" }, "committer": { "email": "noreply@github.com", "name": "GitHub", "username": "web-flow" }, "distinct": true, "id": "3251394ad66ae8419e606fbf78570906ff2f01d3", "message": "Update dotnetcore.yml", "timestamp": "2020-03-31T04:48:53+04:30", "tree_id": "e27a05129010b3b2a7b18b92d91cd73a32babb8f", "url": "https://github.com/mjebrahimi/github_actions_test/commit/3251394ad66ae8419e606fbf78570906ff2f01d3" } ], "compare": "https://github.com/mjebrahimi/github_actions_test/compare/b6c382e8fe74...3251394ad66a", "created": false, "deleted": false, "forced": false, "head_commit": { "author": { "email": "mj.ebrahimi72@gmail.com", "name": "Mohammad Javad Ebrahimi", "username": "mjebrahimi" }, "committer": { "email": "noreply@github.com", "name": "GitHub", "username": "web-flow" }, "distinct": true, "id": "3251394ad66ae8419e606fbf78570906ff2f01d3", "message": "Update dotnetcore.yml", "timestamp": "2020-03-31T04:48:53+04:30", "tree_id": "e27a05129010b3b2a7b18b92d91cd73a32babb8f", "url": "https://github.com/mjebrahimi/github_actions_test/commit/3251394ad66ae8419e606fbf78570906ff2f01d3" }, "pusher": { "email": "mj.ebrahimi72@gmail.com", "name": "mjebrahimi" }, "ref": "refs/heads/master", "repository": { "archive_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/{archive_format}{/ref}", "archived": false, "assignees_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/assignees{/user}", "blobs_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/git/blobs{/sha}", "branches_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/branches{/branch}", "clone_url": "https://github.com/mjebrahimi/github_actions_test.git", "collaborators_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/collaborators{/collaborator}", "comments_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/comments{/number}", "commits_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/commits{/sha}", "compare_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/compare/{base}...{head}", "contents_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/contents/{+path}", "contributors_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/contributors", "created_at": 1585584602, "default_branch": "master", "deployments_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/deployments", "description": null, "disabled": false, "downloads_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/downloads", "events_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/events", "fork": false, "forks": 0, "forks_count": 0, "forks_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/forks", "full_name": "mjebrahimi/github_actions_test", "git_commits_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/git/commits{/sha}", "git_refs_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/git/refs{/sha}", "git_tags_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/git/tags{/sha}", "git_url": "git://github.com/mjebrahimi/github_actions_test.git", "has_downloads": true, "has_issues": true, "has_pages": false, "has_projects": true, "has_wiki": true, "homepage": null, "hooks_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/hooks", "html_url": "https://github.com/mjebrahimi/github_actions_test", "id": 251358686, "issue_comment_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/issues/comments{/number}", "issue_events_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/issues/events{/number}", "issues_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/issues{/number}", "keys_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/keys{/key_id}", "labels_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/labels{/name}", "language": "C#", "languages_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/languages", "license": null, "master_branch": "master", "merges_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/merges", "milestones_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/milestones{/number}", "mirror_url": null, "name": "github_actions_test", "node_id": "MDEwOlJlcG9zaXRvcnkyNTEzNTg2ODY=", "notifications_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/notifications{?since,all,participating}", "open_issues": 0, "open_issues_count": 0, "owner": { "avatar_url": "https://avatars1.githubusercontent.com/u/23256135?v=4", "email": "mj.ebrahimi72@gmail.com", "events_url": "https://api.github.com/users/mjebrahimi/events{/privacy}", "followers_url": "https://api.github.com/users/mjebrahimi/followers", "following_url": "https://api.github.com/users/mjebrahimi/following{/other_user}", "gists_url": "https://api.github.com/users/mjebrahimi/gists{/gist_id}", "gravatar_id": "", "html_url": "https://github.com/mjebrahimi", "id": 23256135, "login": "mjebrahimi", "name": "mjebrahimi", "node_id": "MDQ6VXNlcjIzMjU2MTM1", "organizations_url": "https://api.github.com/users/mjebrahimi/orgs", "received_events_url": "https://api.github.com/users/mjebrahimi/received_events", "repos_url": "https://api.github.com/users/mjebrahimi/repos", "site_admin": false, "starred_url": "https://api.github.com/users/mjebrahimi/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/mjebrahimi/subscriptions", "type": "User", "url": "https://api.github.com/users/mjebrahimi" }, "private": false, "pulls_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/pulls{/number}", "pushed_at": 1585613933, "releases_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/releases{/id}", "size": 40, "ssh_url": "git@github.com:mjebrahimi/github_actions_test.git", "stargazers": 0, "stargazers_count": 0, "stargazers_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/stargazers", "statuses_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/statuses/{sha}", "subscribers_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/subscribers", "subscription_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/subscription", "svn_url": "https://github.com/mjebrahimi/github_actions_test", "tags_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/tags", "teams_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/teams", "trees_url": "https://api.github.com/repos/mjebrahimi/github_actions_test/git/trees{/sha}", "updated_at": "2020-03-31T00:01:15Z", "url": "https://github.com/mjebrahimi/github_actions_test", "watchers": 0, "watchers_count": 0 }, "sender": { "avatar_url": "https://avatars1.githubusercontent.com/u/23256135?v=4", "events_url": "https://api.github.com/users/mjebrahimi/events{/privacy}", "followers_url": "https://api.github.com/users/mjebrahimi/followers", "following_url": "https://api.github.com/users/mjebrahimi/following{/other_user}", "gists_url": "https://api.github.com/users/mjebrahimi/gists{/gist_id}", "gravatar_id": "", "html_url": "https://github.com/mjebrahimi", "id": 23256135, "login": "mjebrahimi", "node_id": "MDQ6VXNlcjIzMjU2MTM1", "organizations_url": "https://api.github.com/users/mjebrahimi/orgs", "received_events_url": "https://api.github.com/users/mjebrahimi/received_events", "repos_url": "https://api.github.com/users/mjebrahimi/repos", "site_admin": false, "starred_url": "https://api.github.com/users/mjebrahimi/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/mjebrahimi/subscriptions", "type": "User", "url": "https://api.github.com/users/mjebrahimi" } }, "workspace": "/home/runner/work/github_actions_test/github_actions_test", "action": "run1" }
jobs: build: if: contains(toJson(github.event.commits), '[SKIP CI]') == false runs-on: ubuntu-latest ...
node -v npm -v
فایل پیشین angular-cli.json حذف و فایل جدید angular.json بجای آن معرفی شدهاست
یکی از مهمترین تغییرات CLI 6.0 نسبت به نگارشهای قبلی آن، پشتیبانی از چندین پروژه است و به همین منظور ساختار فایل تنظیمات آنرا به طور کامل تغییر دادهاند و اگر دستور ng new project1 را صادر کنید، دیگر از فایل پیشین angular-cli.json خبری نبوده و بجای آن فایل جدید angular.json قابل مشاهدهاست:
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "project1": {
مزیت مهم این قابلیت، امکان ایجاد libraries است که به صورت توکار از این نگارش پشتیبانی میشود و میتوان اجزایی مانند components ،directives ،pipes و services اشتراکی را در یک یا چندین کتابخانه قرار داد و سپس از آنها در پروژهی اصلی و یا چندین پروژهی متصل استفاده کرد.
نگارش 6 در پشت صحنه، پروژهی موفق ng-packagr را به مجموعهی CLI اضافه کردهاست و از این پس توسط خود CLI میتوان کتابخانههای استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانههای npm حاصل را نیز به همراه دارد؛ مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار، با بستههای فعلی npm مخصوص Angular وجود دارند. برای مثال بدون استفادهی از این ابزار، نیاز است مستندات 13 صفحهای ساخت کتابخانههای Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0، به صورت خودکار ایجاد و مدیریت میشود.
بنابراین اکنون سؤال مهمی که مطرح میشود این است: آیا باید فایل angular-cli.json پیشین را به صورت دستی به این فایل جدید به روز رسانی کرد و چگونه؟
به روز رسانی تمام بستههای سراسری سیستم
در ادامه پیش از هر کاری نیاز است تمام بستههای سراسری npm ایی را که هر از چندگاهی به سیستم خود اضافه کردهاید، به روز رسانی کنید. برای مشاهدهی لیست موارد تاریخ مصرف گذشتهی آنها، دستور زیر را صادر کنید:
npm outdated -g --depth=0
npm update -g
به روز رسانی خودکار ساختار فایل angular-cli.json
این به روز رسانی توسط CLI 6.0 به صورت خودکار پشتیبانی میشود و شامل این مراحل است:
ابتدا نیاز است بستهی سراسری آنرا به روز رسانی کرد:
npm i -g @angular/cli
npm install --save-dev @angular/cli@latest
ng update @angular/cli ng update @angular/core ng update rxjs
DELETE .angular-cli.json CREATE angular.json (4273 bytes) UPDATE karma.conf.js (1008 bytes) UPDATE src/tsconfig.spec.json (322 bytes) UPDATE package.json (2076 bytes) UPDATE tslint.json (3217 bytes)
لیست سایر بستههایی را که میتوان توسط این دستور به روز رسانی کرد، با اجرا دستور ng update میتوانید مشاهده کنید. برای مثال اگر از Angular Material نیز استفاده میکنید، دستور به روز رسانی آن به صورت زیر است:
ng update @angular/material
مشکل! دستور ng update کار نمیکند!
اگر پروژهی شما صرفا مبتنی بر بستههای اصلی Angular باشد، مراحل یاد شدهی فوق را با موفقیت به پایان خواهید رساند. اما اگر از کتابخانههای ثالثی استفاده کرده باشید، منهای دستور «ng update @angular/cli» که کار تولید فایل جدید angular.json را انجام میدهد، مابقی با خطاهایی مانند «incompatible peer dependency» و یا «Invalid range:>=2.3.1 <3.0.0||>=4.0.0» متوقف میشوند.
در یک چنین حالتی نیاز است ابتدا وابستگیهای محلی پروژه را به روز کرد و سپس دستورات ng update را تکرار نمود. برای این منظور ابتدا بستهی npm-check-updates را نصب کنید:
npm install npm-check-updates -g
ncu -u npm install
اکنون تمام وابستگیهای محلی پروژهی شما به صورت خودکار به آخرین نگارش آنها به روز رسانی شدهاند و اینبار اگر دستورات ذیل را اجرا کنید، با خطاهای یاد شده مواجه نخواهید شد:
ng update @angular/core ng update rxjs
البته اگر در این حالت برنامه را کامپایل کنید، کار نخواهد کرد. علت اصلی آن به بهروز رسانی rxjs به نگارش 6 آن مرتبط میشود که در مطلب بعدی پیگیری خواهد شد و این نگارش شامل حذفیات بسیاری است در جهت کاهش حجم آن، یکپارچکی و یک دست شدن syntax آن و همچنین بهبود قابل ملاحظهی کارآیی آن. البته پیشنیاز الزامی آن آشنایی با pipe-able operators است. علت دیگر کامپایل نشدن برنامه هم میتواند عدم استفاده از HttpClient نگارش 4.3 به بعد باشد.
خاموش کردن اخطار «TypeScript version mismatch»
اگر نگارش TypeScript نصب شدهی در سیستم به صورت سراسری، با نگارش محلی پروژهی شما یکی نباشد، اخطار «TypeScript version mismatch» را دریافت میکنید. روش خاموش کردن آن در CLI جدید با اجرای دستور زیر است:
ng config cli.warnings.typescriptMismatch false
{ "devDependencies": { "typescript": "~2.7.2" } }
تغییرات ng build در نگارش 6
در نگارش 6، مفهوم پیشین environments به configurations تغییر یافتهاست و اینبار در فایل جدید angular.json تنظیم میشوند:
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular-template-driven-forms-lab": { "configurations": { "production": { "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ] } } },
ng build --env staging
ng build --configuration staging
تغییرات نامهای نهایی تولیدی
در CLI 6.0، نامهای نهایی تولیدی دیگر به همراه bundle و یا chunk نیستند. برای مثال دستور ng build یک چنین خروجی را تولید میکند:
>ng build --watch Date: 2018-05-05T09:10:50.158Z Hash: a43eab94ff01539b8592 Time: 31733ms chunk {main} main.js, main.js.map (main) 9.38 kB [initial] [rendered] chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 226 kB [initial] [rendered] chunk {runtime} runtime.js, runtime.js.map (runtime) 5.4 kB [entry] [rendered] chunk {styles} styles.js, styles.js.map (styles) 15.6 kB [initial] [rendered] chunk {vendor} vendor.js, vendor.js.map (vendor) 3.05 MB [initial] [rendered]
و هشهای حالت prod به صورت زیر تولید و به نام فایل اضافه میشوند:
>ng build --prod --watch Date: 2018-05-05T09:17:01.803Z Hash: f25fd6788a4969c52b70 Time: 73279ms chunk {0} runtime.6afe30102d8fe7337431.js (runtime) 1.05 kB [entry] [rendered] chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered] chunk {2} polyfills.6c08419970f9e4781b69.js (polyfills) 59.4 kB [initial] [rendered]
ساده شدن افزودن وابستگیهای ثالث به پروژههای CLI
برای نصب یک کتابخانهی ثالث، پیشتر میبایستی ابتدا بستهی npm آن جداگانه نصب و سپس فایل config برنامه، جهت معرفی مداخل آن، ویرایش میشد. اکنون دستور جدید ng add تمام این مراحل را به صورت خودکار انجام میدهد:
ng add @angular/material
Cookie - قسمت سوم
Cookie - قسمت اول: مقدمه، تاریخچه، معرفی، و شرح کامل
Cookie - قسمت دوم: کوکی در جاوا اسکریپت
نکته مهم: خواندن قسمتهای قبلی این سری (مخصوصا قسمت اول) برای درک بهتر مطالب پیشنهاد میشود.
.
کوکی در ASP.NET - بخش اول
در قسمتهای قبلی مقدمات و مباحث کلی راجع به کوکیها و انواع آن، شرح کامل خواص، نحوه رفتار مرورگرها با انواع کوکیها و درنهایت نحوه کار کردن با کوکیها در سمت کلاینت با استفاده از زبان محبوب جاوا اسکریپت پرداخته شد.
در ادامه این سری مطالب به نحوه برخورد ASP.NET با کوکیها و چگونگی کار کردن با کوکی در سمت سرور آشنا خواهیم شد. در بخش اول این قسمت مباحث ابتدایی و اولیه برای کار با کوکیها در ASP.NET ارائه میشود. در بخش دوم مباحث پیشرفتهتر همچون SubCookieها در ASP.NET و نیز سایر نکات ریز کار با کوکیها در ASP.NET بحث خواهد شد.
.
.
Response و Request در ASP.NET
در قسمت اول این سری به مفاهیم Http Response و Http Request اشاره کوتاهی شده بود. بهصورت خلاصه، درخواستی که از سمت یک کلاینت به یک وب سرور ارسال میشود Request و پاسخی که وب سرور به آن درخواست میدهد Response نامیده میشود.
در ASP.NET، کلیه اطلاعات مرتبط با درخواست رسیده از سمت یک کلاینت در نمونهای منحصر به فرد از کلاس HttpRequest نگهداری میشود. محل اصلی نگهداری این نمونه در پراپرتی Request از نمونه جاری کلاس System.Web.HttpContext (قابل دسترسی ازطریق HttpContext.Current) است. البته کلاس Page هم یک پراپرتی با نام Request دارد که دقیقا از همین پراپرتی کلاس HttpContext استفاده میکند.
همچنین کلیه اطلاعات مرتبط با پاسخ ارسالی وب سرور به سمت کلاینت مربوطه در نمونهای از کلاس HttpResponse ذخیره میشود. محل اصلی نگهداری این نمونه نیز در پراپرتی Response از نمونه جاری کلاس HttpContext است. همانند Request، کلاس Page یک پراپرتی با نام Response برای نگهداری این نمونه دارد که این هم دقیقا از پراپرتی متناظر در کلاس HttpContext استفاده میکند.
.
.
کوکیها در Response و Request
هر دو کلاس HttpResponse و HttpRequest یک پراپرتی با عنوان Cookies (^ و ^) دارند که مخصوص نگهداری کوکیهای مربوطه هستند. این پراپرتی از نوع System.Web.HttpCookieCollection است که یک کالکشن مخصوص برای ذخیره کوکیهاست.
- این پراپرتی (Cookies) در کلاس HttpRequest محل نگهداری کوکیهای ارسالی توسط مرورگر در درخواست متناظر آن است. کوکیهایی که مرورگر با توجه به شرایط جاری و تنظیمات کوکیها اجازه ارسال به سمت سرور را به آنها داده و در درخواست ارسالی ضمیمه کرده است (با استفاده از هدر :Cookie که در قسمت اول شرح داده شد) و ASP.NET پس از پردازش و Parse دادهها، درون این پراپرتی اضافه کرده است.
- این پراپرتی (Cookies) در کلاس HttpResponse محل ذخیره کوکیهای ارسالی از وب سرور به سمت مرورگر کلاینت در پاسخ به درخواست متناظر است. کوکیهای درون این پراپرتی پس از بررسی و استخراج دادههای موردنیاز توسط ASP.NET در هدر پاسخ ارسالی ضمیمه خواهند شد (با استفاده از هدر :Set-Cookie که در قسمت اول توضیح داده شد).
.
.
ایجاد و بهروزرسانی کوکی در ASP.NET
برای ایجاد یک کوکی و ارسال آن به سمت کلاینت همانطور که در بالا نیز اشاره شد، باید از پراپرتی Response.Cookies از کلاس HttpContext استفاده کرد. برای ایجاد یک کوکی روشهای مختلفی وجود دارد.
- در روش اول با استفاده از ویژگی مخصوص ایندکسر کلاس HttpCookieCollection عملیات تولید کوکی انجام میشود. در این روش، ابتدا بررسی میشود که کوکی موردنظر در لیست کوکیهای جاری وجود دارد یا خیر. درصورتیکه با این نام قبلا یک کوکی ثبت شده باشد، مقدار کوکی موجود بروزرسانی خواهد شد. اما اگر این نام وجود نداشته باشد یک کوکی جدید با این نام به لیست افزوده شده و مقدار آن ثبت میشود. مثال:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myCookieValue";
- روش بعدی استفاده از متد Add در کلاس HttpCookieCollection است. در این روش ابتدا یک نمونه از کلاس HttpCookie ایجاد شده و سپس این نمونه به لیست کوکیها اضافه میشود. کد زیر چگونگی استفاده از این روش را نشان میدهد:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); HttpContext.Current.Response.Cookies.Add(myCookie);
- روش دیگر استفاده از متد Set کلاس HttpCookieCollection است. تفاوت این متد با متد Add در این است که متد Set ابتدا سعی میکند عملیات update انجام دهد. یعنی عملیات افزودن تنها وقتیکه نام کوکی موردنظر در لیست کوکیها یافته نشود انجام خواهد شد. برای مثال:
HttpContext.Current.Response.Cookies.Set(new HttpCookie("myCookie", "myCookieValue"));
نکته: باتوجه به توضیحات بالا، متد Set اجازه افزودن دو کوکی با یک نام را نمیدهد. برای اینکار باید از متد Add استفاده کرد. درباره این موضوع در قسمت بعدی بیشتر توضیح داده خواهد شد.
- روش دیگری که برای ایجاد یکی کوکی میتوان از آن استفاده کرد، بکارگیری متد AppnedCookie از کلاس HttpResponse است. در این روش نیز ابتدا باید یک نمونه از کلاس HttpCookie تولید شود. این روش همانند استفاده از متد Add از کلاس HttpCookieCollection است. کد زیر مثالی از این روش را نشان میدهد:
HttpContext.Current.Response.AppendCookie(new HttpCookie("myCookie", "myCookieValue"));
- روش بعدی استفاده از متد SetCookie از کلاس HttpResponse است. فرق این متد با متد AppendCookie در این است که در متد SetCookie ابتدا وجود یک کوکی با نام ارائه شده بررسی میشود و درصورت وجود، مقدار این کوکی بروزرسانی میشود. درصورتیکه قبلا یک کوکی با این نام وجود نداشته باشد، یک کوکی جدید به لیست کوکیها اضافه میشود. این روش همانند استفاده از متد Set از کلاس HttpCookieCollection است. نمونهای از نحوه استفاده از این متد در زیر آورده شده است:
HttpContext.Current.Response.SetCookie(new HttpCookie("myCookie", "myCookieValue"));
نکته: تمامی فرایندهای نشان داده شده در بالا تنها موجب تغییر محتویات کالکشن کوکیها درون HttpContext میشود و تا زمانیکه توسط وب سرور با استفاده از دستور Set-Cookie به سمت مرورگر ارسال نشوند تغییری در کلاینت بوجود نخواهند آورد.
برای آشنایی بیشتر با این روند کد زیر را برای تعریف یک کوکی جدید درنظر بگیرید:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myValue";
همانطور که مشاهده میکنید دستور ایجاد یک کوکی با نام و مقدار وارده در هدر پاسخ تولیدی توسط وب سرور گنجانیده شده است.
نکته: در ASP.NET به صورت پیش فرض از مقدار "/" برای پراپرتی Path استفاده میشود.
.
خواص کوکی در ASP.NET
برای تعیین یا تغییر خواص یک کوکی در ASP.NET باید به نمونه HttpCookie مربوطه دست یافت. سپس با استفاده از پراپرتیهای این کلاس میتوان خواص موردنظر را تعیین کرد. برای مثال:
var myCookie = new HttpCookie(string.Empty); myCookie.Name = "myCookie"; myCookie.Value = "myCookieValue"; myCookie.Domain = "dotnettip.info"; myCookie.Path = "/post"; myCookie.Expires = new DateTime(2015, 1, 1); myCookie.Secure = true; myCookie.HttpOnly = true;
نکته مهم: امکان تغییر خواص یک کوکی به صورت مستقیم در سمت سرور وجود ندارد. درواقع برای اعمال این تغییرات در سمت کلاینت باید به ازای هر کوکی موردنظر یک کوکی جدید با مقادیر جدید ایجاد و به کالکشن کوکیها در Http Response مربوطه اضافه شود تا پس از قرار دادن دستور Set-Cookie متناظر در هدر پاسخ ارسالی به سمت کلاینت و اجرای آن توسط مرورگر، مقادیر خواص مورنظر در سمت کلاینت بروزرسانی شوند. دقت کنید که تمامی نکات مرتبط با هویت یک کوکی که در قسمت اول شرح داده شد در اینجا نیز کاملا صادق است.
روش دیگری نیز برای تعیین برخی خواص کوکیها به صورت کلی در فایل وب کانفیگ وجود دارد. برای اینکار از تگ httpCookies در قسمت system.web استفاده میشود. برای مثال:
<httpCookies domain="www.example.com" httpOnlyCookies="true" requireSSL="true" />
این امکان از ASP.NET 2.0 به بعد اضافه شده است. با استفاده از این تگ، تنظیمات اعمال شده برای تمامی کوکیها درنظر گرفته میشود. البته درصورتیکه تنظیم موردنظر برای کوکی به صورت صریح آورده نشده باشد. برای نمونه به کد زیر دقت کنید:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); myCookie.Domain = "test.com"; HttpContext.Current.Response.Cookies.Add(myCookie); var myCookie2 = new HttpCookie("myCookie2", "myCookieValue2"); myCookie2.HttpOnly = false; myCookie2.Secure = false; HttpContext.Current.Response.Cookies.Add(myCookie2);
با استفاده از تنظیمات تگ httpCookies که در بالا نشان داده شده است، هدر پاسخ تولیدی توسط وب سرور به صورت زیر خواهد بود:
همانطور که میبینید تنها مقادیر پراپرتیهایی که صراحتا برای کوکی آورده نشده است از تنظیمات وب کانفیگ خوانده میشود.
.
.
حذف کوکی در ASP.NET
برای حذف یک کوکی در ASP.NET یک روش کلی وجود دارد که در قسمتهای قبلی نیز شرح داده شده است، یعنی تغییر خاصیت Expires کوکی به تاریخی در گذشته. برای نمونه داریم:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); myCookie.Expires = DateTime.Now.AddYears(-1);
نکته مهم: در کلاس HttpCookieCollection یک متد با نام Remove وجود دارد. از این متد برای حذف یک کوکی از لیست موجود در این کلاس استفاده میشود. دقت کنید که حذف یک کوکی از لیست کوکیها با استفاده از این متد تاثیری بر موجودیت آن کوکی در سمت کلاینت نخواهد گذاشت و تنها روش موجود برای حذف یک کوکی در سمت کلاینت همان تنظیم مقدار خاصیت Expires است.
.
خواندن کوکی در ASP.NET
برای خواندن مقدار یک کوکی ارسالی از مرورگر کلاینت در ASP.NET، باتوجه به توضیحات ابتدای این مطلب، طبیعی است که باید از پراپرتی Request.Cookies در نمونه جاری از کلاس HttpContext استفاده کرد. برای این کار نیز چند روش وجود دارد.
- روش اول استفاده از ایندکسر کلاس HttpCookieCollection است. برای اینکار نیاز به نام یا ایندکس کوکی موردنظر در لیست مربوطه داریم. برای مثال:
var myCookie = HttpContext.Current.Request.Cookies["myCookie"];
- یا این نمونه با استفاده از ایندکسر عددی:
var myCookie = HttpContext.Current.Request.Cookies[0];
- روش دیگری که برای خواند مقدار یک کوکی میتوان بکار برد، استفاده از متد Get از کلاس HttpCookieCollection است. این متد همانند ایندکسر این کلاس نیاز به نام یا ایندکس کوکی موردنظر دارد. برای نمونه:
var myCookie = HttpContext.Current.Request.Cookies.Get("myCookie");
- یا استفاده از ایندکس کوکی:
var myCookie = HttpContext.Current.Request.Cookies.Get(0);
.
.
بحث و نتیجه گیری
تا اینجا با مفاهیم اولیه درباره نحوه برخورد ASP.NET با کوکیها آشنا شدیم. روشهای مختلف ایجاد و یا بهروزرسانی کوکیها نشان داده شد. با تعیین انواع خواص کوکیها آشنا شدیم. نحوه حذف یک کوکی در ASP.NET را دیدیم. روشهای خواندن مقادیر کوکیها را نیز مشاهده کردیم.
باز هم تاکید میکنم که تمامی تغییرات اعمالی در سمت سرور تا زمانیکه بهصورت دستورات Set-Cookie در هدر پاسخ وب سرور قرار نگیرند هیچ کاری در سمت کلاینت انجام نمیدهند.
در قسمت بعدی این سری مطالب به مباحث پیشرفتهتری چون SubCookieها در ASP.NET و هویت منحصر به فرد کوکیها در سمت سرور پرداخته میشود.
.
.
منابع
معرفی Kendo UI
@section JavaScript { <!--KendoUI: Web--> <link href="@Url.Content("~/content/css/kendo/kendo.common.min.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/content/css/kendo/kendo.default.min.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/kendo/kendo.web.min.js")" type="text/javascript"></script> <!--KendoUI: DataViz--> <link href="@Url.Content("~/content/css/kendo/kendo.dataviz.min.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/kendo/kendo.dataviz.min.js")" type="text/javascript"></script> <!--KendoUI: Mobile--> <link href="@Url.Content("~/content/css/kendo/kendo.mobile.all.min.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/kendo/kendo.mobile.min.js")" type="text/javascript"></script> <script type="text/javascript"> $(function () { $("#pickDate").kendoDatePicker(); }); </script> }
جی کوئری یکبار صدا زده شده ولی متاسفانه خطای زیر رو میده:
TypeError: $(...).kendoDatePicker is not a function
چه مواردی رو باید برای رفع خطا بررسی کنم؟
برنامههای Vue.jsای از چندین کامپوننت برای بخش بندی هر قسمت تشکیل میشوند و این بخش بندی برای مدیریت بهتر تغییرات، خطایابی، نگهداری و استفاده مجدد (reusable) میباشد. فرض کنید تعدادی کامپوننت در برنامه داریم و اطلاعات این کامپوننتها بهم وابسته میباشند؛ بطور مثال یک کامپوننت انتخاب دسته بندی را داریم و به محض تغییر این مقدار، میخواهیم لیستی از محصولات پیشنهادی یا پرفروشِ آن دسته بندی، در کامپوننت پایین صفحه نمایش داده شود و یا خرید یک محصول را در نظر بگیرید که بلافاصله محتوای نمایش سبد خرید، بروزرسانی شود. در این مقاله ارتباط از نوع Parent و Child بین کامپوننتها بررسی میشود.
نکته: برای ارسال اطلاعات از کامپوننتِ Parent به Child، از Props استفاده میشود و برای ارتباط از Child به Parent، از emit$ استفاده میشود.
یک برنامه Vue.js با نام vue-communication-part-1 ایجاد نمایید. سپس دو کامپوننت را با نامهای Parent و Child، در پوشه components ایجاد کنید:
در کامپوننت Parent، یک تابع با نام increase وجود دارد که مقدار متغیر parentCounter را افزایش میدهد. چون قصد داریم مقدار متغیر parentCounter در کامپوننت Child نیز بروزرسانی شود، آن را به کامپوننت Child پاس میدهیم:
<Child :childCounter="parentCounter"/>
محتوای کامپوننت Parent:
<template> <div> <div> <h2>Parent Component</h2> <!-- را نمایش میدهید parentCounter مقدار --> <h1>{{ parentCounter }}</h1> <button @click="increase">Increase Parent</button> </div> <div> <!-- پاس میدهید Child در کامپوننت childCounter را به پراپرتی parentCounter مقدار --> <!--از طریق decreaseParent سبب اتصال و فراخوانی تابع @callDecreaseParent به decreaseParent با انتساب --> <!-- میشود Child در کامپوننت callDecreaseMethodInParent تابع --> <Child :childCounter="parentCounter" @callDecreaseParent="decreaseParent"/> </div> </div> </template> <script> //برای استفاده در کامپوننت جاری Child ایمپورت کردن کامپوننت import Child from "./Child.vue"; export default { // در این بخش متغیرهای مورد نیاز کامپوننت را تعریف میکنیم data() { return { parentCounter: 0 }; }, components: { // میتوان آرایه ای از کامپوننتها را در یک کامپوننت استفاده نمود // در این مثال فقط از یک کامپوننت استفاده شده Child }, methods: { //را یک واحد افزایش میدهد parentCounter این متد مقدار increase() { this.parentCounter++; }, decreaseParent() { this.parentCounter--; } } }; </script> <style> .parent-block, .child { text-align: center; margin: 20px; padding: 20px; border: 2px gray solid; } </style>
در کامپوننت Child قصد دریافت مقدار پراپرتیِ childCounter را داریم که از طریق کامپوننت Parent، مقدارش تنظیم و بروزرسانی میشود. به این منظور در قسمت props یک متغیر بنام childCounter را ایجاد میکنیم.
Data is the private memory of each component where you can store any variables you need. Props are how you pass this data from a parent component down to a child component
محتوای کامپوننت Child
<template> <div> <h3>Child Component</h3> <!-- را نمایش میدهید childCounter مقدار --> <h3>{{ childCounter }}</h3> <button @click="increase">Increase Me</button> <button @click="callDecreaseMethodInParent">Call Decrease Method In Parent</button> </div> </template> <script> export default { // استفاده میشود Child به Parent برای ارتباط بین کامپوننت props از props: { childCounter: Number }, data() { return {}; }, methods: { //را یک واحد افزایش میدهد childCounter این متد مقدار increase() { this.childCounter++; }, // فراخوانی میکند Parent را در کامپوننت decreaseParent تابع callDecreaseMethodInParent() { this.$emit("callDecreaseParent"); } } }; </script>
محتوای کامپوننت اصلی برنامه App.vue:
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <parent></parent> </div> </template> <script> import Parent from "./components/Parent.vue"; export default { name: "app", components: { parent: Parent } }; </script> <style> #app { width: 50%; margin: 0 auto; text-align: center; } </style>
اکنون برنامه را با دستور زیر اجرا کنید:
npm run serve
بعد از اجرای دستور فوق، روی گزینه زیر ctrl+click میکنیم تا نتیجه کار در مرورگر قابل رویت باشد:
نمایش صفحه زیر نشان دهندهی درستی انجام کار تا اینجا است:
اکنون روی دکمهی Increase Parent کلیک میکنیم. همزمان مقدار شمارشگر، در هر دو کامپوننت Parent و Child افزایش مییابد و این بدین معناست که با استفاده از Props میتوانیم دادههای دلخواهی را در کامپوننت Child بروز رسانی کنیم. هر زمانی روی دکمهی Increase Me در کامپوننت Child کلیک کنیم، فقط به مقدار شمارشگر درون خودش اضافه میشود و تاثیری را بر شمارشگر Parent ندارد. در واقع یک کپی از مقدار شمارشگر Parent را درون خود دارد.
در ادامه قصد داریم بروزرسانی داده را از Child به Parent انجام دهیم. برای انجام اینکار از emit$ استفاده میکنیم. در دیکشنری Cambridge Dictionary معنی emit به ارسال یک سیگنال ترجمه شدهاست. در واقع بااستفاده از emit میتوانیم یک تابع را در کامپوننت Parent فراخوانی کنیم و در آن تابع، کد دلخواهی را برای دستکاری دادهها مینویسیم.
در تابع callDecreaseMethodInParent در کامپوننت Child، کد زیر را قرار میدهیم:
this.$emit("callDecreaseParent");
هر زمانکه این تابع اجرا شود، یک سیگنال از طریق کد زیر برای کامپوننت Parent ارسال میشود:
<Child @callDecreaseParent="decreaseParent"/>
در کد فوق مشخص شده که با ارسال سیگنال callDecreaseParent، تابع decreaseParent در کامپوننت Parent فراخوانی شود.
نکته: برای اجرای برنامه و دریافت پکیجهای مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:
npm install
چند نکته
this.$emit //dispatches an event to its parent component
کد فوق سبب اجرای یک تابع در کامپوننتِ Parent خودش میشود.
this.$parent // gives you a reference to the parent component
ارجاعی به کامپوننت Parent خودش را فراهم میکند:
this.$root // gives you a reference to the root component
زمانیکه چندین کامپوننت تو در تو را داریم یا به اصطلاح nested component، سبب ارجاعی به بالاترین کامپوننت Parent میگردد.
this.$parent.$emit // will make the parent dispatch the event to its parent
سبب اجرای تابعِ Parent کامپوننتِ Parent جاری میشود. به بیان ساده اگر این کد در کامپوننت فرزند فراخوانی شود، سبب اجرای تابعی در کامپوننت پدربزرگِ خود میشود.
this.$root.$emit // will make the root dispatch the event to itself
سبب اجرای تابعی در کامپوننت root میشود (بالاترین کامپوننتِ پدرِ کامپوننت جاری).
تابع emit$ دارای آگومانهای دیگری برای پاس دادن اطلاعات از کامپوننت Child به Parent میباشد؛ مثل زمانیکه قصد دارید اطلاعاتی در مورد محصول خریداری شده را به سبد خرید پاس دهید. در مثال دیگری که در ادامه قرار میگیرد نحوه کارکرد ارتباط کامپوننت Parent و Child را در یک برنامه بهتر تجربه میکنیم.
پیاده سازی یک سبد خرید ساده با روش مقالهی جاری
نکته: برای اجرای برنامه و دریافت پکیجهای مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:
npm install
همچنین نیاز هست تا پکیچ node-sass را با دستور زیر برای این مثال نصب کنید.
npm install node-sass
کتابخانه perfect-scrollbar
Minimalistic but perfect custom scrollbar plugin Demo
perfect means...
- There should be no css change on any original element.
- The scrollbar should not affect the original design layout.
- The design of the scrollbar should be (nearly) fully customizable.
- If the size of the container or the content changes, the scrollbar size and position should be able to change.
- New! It should work with vanilla JavaScript and major tools like NPM or Browserify.
برپایی تنظیمات اولیهی سیستم مسیریابی در AngularJS 2.0
برای کار با سیستم مسیریابی AngularJS 2.0، ابتدا باید اسکریپتهای آن به صفحه اضافه شوند. در ادامه المان پایهای تعریف شده و سپس باید سرویس پروایدر مسیریابی را رجیستر کرد. جزئیات این موارد را در ادامه بررسی میکنیم:
الف) سرویس مسیریابی، جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن باید به صفحهی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفتهاست:
<!-- Required for routing --> <script src="~/node_modules/angular2/bundles/router.dev.js"></script>
ب) افزودن المان base به ابتدای صفحه:
<!DOCTYPE html> <html> <head> <base href="/">
از آنجائیکه فایل index.html در ریشهی سایت قرار گرفتهاست، مقدار آغازین href آن به / تنظیم شدهاست.
ج) شبیه به حالت ثبت پروایدر HTTP در قسمت قبل، برای ثبت پروایدر مسیریابی نیز به فایل App\app.component.ts مراجعه میکنیم:
//same as before ... import { ROUTER_PROVIDERS } from 'angular2/router'; //same as before ... @Component({ //same as before … providers: [ ProductService, HTTP_PROVIDERS, ROUTER_PROVIDERS ] }) //same as before ...
علت ختم شدن نام این سرویسها به PROVIDERS این است که این تعاریف، امکان استفادهی از چندین سرویس زیر مجموعهی آنها را فراهم میکنند و صرفا یک سرویس نیستند.
ساخت کامپوننت نمایش جزئیات محصولات
در ادامه میخواهیم جزئیات هر محصول را با کلیک بر روی نام آن در لیست محصولات، در آدرسی دیگر به صورتی مجزا مشاهده کنیم. به همین منظور به پوشهی products برنامه مراجعه کرده و دو فایل جدید product-detail.component.ts و product-detail.component.html را ایجاد کنید؛ با این محتوا:
الف) محتوای فایل product-detail.component.html
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> </div>
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; }
اگر دقت کنید، این کامپوننت ویژه دارای خاصیت selector نیست. ذکر selector تنها زمانی اجباری است که بخواهیم این کامپوننت را داخل کامپوننتی دیگر قرار دهیم. در اینجا قصد داریم این کامپوننت را به صورت یک View جدید، توسط سیستم مسیریابی نمایش دهیم و نه به صورت جزئی از یک کامپوننت دیگر.
افزودن تنظیمات مسیریابی به برنامه
مسیریابی در AngularJS 2.0، مبتنی بر کامپوننتها است. به همین جهت، ابتدای کار مسیریابی، مشخص سازی تعدادی از کامپوننتها هستند که قرار است به عنوان مقصد سیستم راهبری (navigation) مورد استفاده قرار گیرند و به ازای هر کدام، یک مسیریابی و Route جدید را تعریف میکنیم. تعریف هر Route جدید شامل انتساب نامی به آن، تعیین مسیر مدنظر و مشخص سازی کامپوننت مرتبط است:
{ path: '/products', name: 'Products', component: ProductListComponent },
این تنظیمات به عنوان یک متادیتای جدید دیگر به کلاس AppComponent، در فایل app.component.ts اضافه میشوند:
//same as before … import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router'; //same as before … @Component({ //same as before … }) @RouteConfig([ { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true }, { path: '/products', name: 'Products', component: ProductListComponent } ]) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
همانطور که ملاحظه میکنید، یک کلاس میتواند بیش از یک decorator داشته باشد.
()RouteConfig@ را به کامپوننتی الصاق میکنیم که قصد میزبانی مسیریابی را دارد (Host component). این مزین کننده، آرایهای از اشیاء را قبول میکند و هر شیء آن دارای خواصی مانند مسیر، نام و کامپوننت است. باید دقت داشت که نام هر مسیریابی تعریف شده باید pascal case باشد. در غیراینصورت مسیریاب ممکن است این نام را با path اشتباه کند.
همچنین امکان تعریف خاصیت دیگری به نام useAsDefault نیز در اینجا میسر است. از آن جهت تعریف مسیریابی پیش فرض سیستم، در اولین بار نمایش آن، استفاده میشود.
در اینجا نام کامپوننت، رشتهای ذکر نمیشود و دقیقا اشاره دارد به نام کلاس متناظر. بنابراین هر نام کلاسی که در اینجا اضافه میشود، باید به همراه import ماژول آن نیز در ابتدای فایل جاری باشد. به همین جهت اگر تنظیمات فوق را اضافه کنید، ذیل کلمهی WelcomeComponent یک خط قرمز مبتنی بر عدم تعریف آن کشیده میشود. برای تعریف آن، پوشهی جدیدی را به ریشهی سایت به نام home اضافه کنید و به آن دو فایل ذیل را اضافه نمائید:
الف) محتوای فایل welcome.component.ts
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/home/welcome.component.html' }) export class WelcomeComponent { public pageTitle: string = "Welcome"; }
<div class="panel panel-primary"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <h3 class="text-center">Default page</h3> </div> </div>
پس از تعریف این کامپوننت، اکنون باید import ماژول آنرا به ابتدای فایل app.component.ts اضافه کنیم، تا مشکل عدم شناسایی نام کلاس WelcomeComponent برطرف شود:
import { WelcomeComponent } from './home/welcome.component';
فعال سازی مسیریابیهای تعریف شده
روشهای مختلفی برای دسترسی به اجزای یک برنامه وجود دارند؛ برای مثال کلیک بر روی یک لینک، دکمه و یا تصویر و سپس فعال سازی مسیریابی متناظر با آن. همچنین کاربر میتواند آدرس صفحهای را مستقیما در نوار آدرسهای مرورگر وارد کند. به علاوه امکان کلیک بر روی دکمههای back و forward مرورگر نیز همواره وجود دارند. تنظیمات مسیریابیهای انجام شده، دو مورد آخر را به صورت خودکار مدیریت میکنند. در اینجا تنها باید مدیریت اولین حالت ذکر شده را با اتصال مسیریابیها به اعمال کاربران، انجام داد.
به همین جهت منویی را به بالای صفحهی برنامه اضافه میکنیم. برای این منظور، فایل app.component.ts را گشوده و خاصیت template کامپوننت AppComponent را به نحو ذیل تغییر میدهیم:
@Component({ //same as before … template: ` <div> <nav class='navbar navbar-default'> <div class='container-fluid'> <a class='navbar-brand'>{{pageTitle}}</a> <ul class='nav navbar-nav'> <li><a [routerLink]="['Welcome']">Home</a></li> <li><a [routerLink]="['Products']">Product List</a></li> </ul> </div> </nav> <div class='container'> <router-outlet></router-outlet> </div> </div> `, //same as before … })
سپس جهت تعریف لینکهای هر آیتم، از یک دایرکتیو توکار AngularJS 2.0 به نام routerLink استفاده میکنیم. هر routerLink به یکی از آیتمهای تنظیم شدهی در RouteConfig بایند میشود. بنابراین نامهایی که در اینجا قید شدهاند، دقیقا نامهایی هستند که در خاصیت name هر کدام از اشیاء تشکیل دهندهی RouteConfig، تعریف و مقدار دهی گردیدهاند.
اکنون اگر کاربر بر روی یکی از لینکهای Home و یا Product List کلیک کند، مسیریابی متناظر با آن فعال میشود (بر اساس این نام، در لیست عناصر RouteConfig جستجویی صورت گرفته و عنصر معادلی بازگشت داده میشود) و سپس View آن کامپوننت نمایش داده خواهد شد.
تا اینجا دایرکتیو جدید routerLink به قالب کامپوننت اضافه شدهاست؛ اما AngularJS 2.0 نمیداند که باید آنرا از کجا دریافت کند. به همین جهت ابتدا import آنرا (ROUTER_DIRECTIVES) به ابتدای ماژول جاری اضافه خواهیم کرد:
import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
directives: [ROUTER_DIRECTIVES],
تا اینجا اگر دقت کرده باشید، کامپوننت نمایش لیست محصولات را از کامپوننت ریشهی سایت حذف کردهایم و بجای آن منوی بالای سایت را نمایش میدهیم که توسط آن میتوان به صفحهی آغازین و یا صفحهی نمایش لیست محصولات، رسید. به همین جهت خاصیت directives دیگر شامل ذکر کلاس کامپوننت لیست محصولات نیست.
در انتهای قالب کامپوننت ریشهی سایت، یک دایرکتیو جدید به نام router-outlet نیز تعریف شدهاست. وقتی یک کامپوننت فعال میشود، نیاز است View مرتبط با آن نیز نمایش داده شود. دایرکتیو router-outlet محل نمایش این View را مشخص میکند.
اکنون اگر برنامه را اجرا کنیم، به این شکل خواهیم رسید:
اگر دقت کنید، آدرس بالای صفحه، در اولین بار نمایش آن به http://localhost:2222/welcome تنظیم شده و این مقدار دهی بر اساس خاصیت useAsDefault تنظیمات مسیریابی سایت انجام شدهاست (نمایش welcome به عنوان صفحهی اصلی و پیش فرض).
همچنین با کلیک بر روی لینک لیست محصولات، کامپوننت آن فعال شده و نمایش داده میشود. محل قرارگیری این کامپوننتها، دقیقا در محل قرارگیری دایرکتیو router-outlet است.
ارسال پارامترها به سیستم مسیریابی
در ابتدا بحث، مقدمات کامپوننت نمایش جزئیات یک محصول انتخابی را تهیه کردیم. برای فعال سازی این کامپوننت و مسیریابی آن، نیاز است بتوان پارامتری را به سیستم مسیریابی ارسال کرد. برای مثال با انتخاب آدرس product/5، جزئیات محصول با ID مساوی 5 نمایش داده شود.
برای این منظور:
الف) اولین قدم، تعریف مسیریابی آن است. به همین جهت به فایل app.component.ts مراجعه و دو تغییر ذیل را به آن اعمال کنید:
//same as before … import { ProductDetailComponent } from './products/product-detail.component'; @Component({ //same as before … }) @RouteConfig([ //same as before … { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent } ]) //same as before …
تفاوت این مسیریابی با نمونههای قبلی در تعریف id:/ است. پس از ذکر :/، نام یک متغیر عنوان میشود و اگر نیاز به چندین متغیر بود، همین الگو را تکرار خواهیم کرد.
ب) سپس نحوهی فعال سازی این مسیریابی را توسط تعریف لینکی جدید، معرفی میکنیم. بنابراین فایل قالب product-list.component.html را گشوده و سپس بجای نمایش عنوان محصول:
<td>{{ product.productName }}</td>
<td> <a [routerLink]="['ProductDetail', {id: product.productId}]"> {{product.productName}} </a> </td>
اکنون که از دایرکتیو جدید routerLink در این قالب استفاده شدهاست، نیاز است تعریف دایرکتیو آنرا به متادیتای کلاس کامپوننت لیست محصولات نیز اضافه کنیم تا AngularJS 2.0 بداند آنرا از کجا باید تامین کند:
import { Component, OnInit } from 'angular2/core'; import { ROUTER_DIRECTIVES } from 'angular2/router'; //same as before … @Component({ //same as before … directives: [StarComponent, ROUTER_DIRECTIVES] })
در ادامه اگر برنامه را اجرا کنید، عنوانهای محصولات، به آدرس نمایش جزئیات آنها لینک شدهاند:
ج) در آخر زمانیکه View نمایش جزئیات محصول فعال میشود، نیاز است این id را از url جاری دریافت کند. به همین جهت فایل product-detail.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core'; import { RouteParams } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } }
در این حالت، id دریافتی، به متغیر pageTitle اضافه شده و در قالب مربوطه به صورت خودکار نمایش داده میشود.
تا اینجا اگر برنامه را اجرا کنید، صفحهی نمایش جزئیات یک محصول، با کلیک بر روی عناوین آنها به صورت زیر نمایش داده میشود:
افزودن دکمهی back با کدنویسی
اکنون برای بازگشت مجدد به لیست محصولات، میتوان از دکمهی back مرورگر استفاده کرد، اما امکان طراحی این دکمه در قالبها نیز پیش بینی شدهاست.
برای این منظور قالب product-detail.component.html را به نحو ذیل بازنویسی میکنیم:
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> <div class='panel-footer'> <a class='btn btn-default' (click)='onBack()' style='width:80px'> <i class='glyphicon glyphicon-chevron-left'></i> Back </a> </div> </div>
سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
import { Component } from 'angular2/core'; import { RouteParams, Router } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams, private _router: Router) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } onBack(): void { this._router.navigate(['Products']); } }
رفع تداخل مسیریابیهای ASP.NET MVC با مسیریابیهای AngularJS 2.0
در طی بحث جاری عنوان شد که اگر کاربر مسیر http://localhost:2222/product/2 را جایی ثبت کرده یا bookmark کند، پس از فراخوانی مستقیم آن در نوار آدرسهای مرورگر، بلافاصله به این آدرس هدایت خواهد شد. این مورد صحیح است اگر از index.html بجای بکارگیری ASP.NET MVC، جهت هاست برنامه استفاده شود. اگر چنین آدرسی را در یک برنامهی ASP.NET MVC فراخوانی کنیم، ابتدا به دنبال کنترلری به نام product میگردد (ابتدا وارد موتور ASP.NET MVC میشود) و چون این کنترلر در سمت سرور تعریف نشدهاست، پیام 404 و یا یافت نشد را مشاهده خواهید کرد و فرصت به اجرای برنامهی AngularJS نخواهد رسید.
برای حل این مشکل نیاز است یک route جدید را به نام catch all، در انتهای مسیریابیهای فعلی اضافه کنید؛ تا سایر درخواستهای رسیده را به صفحهی نمایش برنامهی AngularJS هدایت کند:
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName ); // Route override to work with Angularjs and HTML5 routing routes.MapRoute( name: "NotFound", url: "{*catchall}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part9.zip
خلاصهی بحث
حین ایجاد کامپوننتها باید به نحوهی نمایش آنها نیز فکر کرد. اگر کامپوننتی قرار است داخل یک کامپوننت دیگر نمایش یابد، باید دارای selector باشد. یک چنین کامپوننتی نیاز به تعریف مسیریابی ندارد. برای کامپوننتهایی که به عنوان یک View مستقل طراحی میشوند و قرار است در یک صفحهی مجزا نمایش داده شوند، نیازی به تعریف selector نیست؛ اما باید برای آنها مسیریابیهای ویژهای را تعریف کرد. همچنین نیاز است مدیریت اعمال کاربران را جهت فعال سازی آنها نیز مدنظر داشت. برای استفاده از امکانات مسیریابی توکار AngularJS 2.0 نیاز است اسکریپت آنرا به صفحهی اصلی اضافه کرد. سپس باید المان base را جهت نحوهی تشکیل آدرسهای مسیریابی، به صفحه اضافه کرد. در ادامه کار ثبت ROUTER_PROVIDERS در بالاترین سطح سلسه مراتب کامپوننتهای سایت انجام میشود. با استفاده از RouteConfig کار تنظیمات ابتدایی مسیریابی صورت خواهد گرفت. این decorator به کامپوننتی که قرار است کار میزبانی مسیریابی را انجام دهد، متصل میشود. پس از تعریف مسیریابیها با ذکر یک نام منحصربفرد، مسیری که باید توسط کاربر وارد شود و نام کامپوننت مدنظر، با استفاده از دایرکتیو routerLink کار تعریف این آدرسها، در رابط کاربری برنامه انجام میشود. این دایرکتیو جدید، جزئی از مجموعهی ROUTER_DIRECTIVES است که باید به لیست دایرکتیوهای کامپوننت ریشههای سایت و هر کامپوننتی که از routeLink استفاده میکند، اضافه شود. برای بایند این دایرکتیو به مسیریابیهای تعریف شده، سمت راست این اتصال باید به آرایهای از مقادیر مقدار دهی شود که اولین عنصر آن، نام یکی از عناصر مسیریابی تعریف شدهی در RouteConfig است. از دومین عنصر آن برای مقدار دهی پارامترهای ارسالی به سیستم مسیریابی استفاده میشود. کار دایرکتیو router-outlet، مشخص سازی محل نمایش یک View است که عموما در قالب میزبان مسیریابی قرار میگیرد. برای تعیین پارامترهای مسیریابی، از الگوی paramName:/ استفاده میشود. برای دسترسی به این مقادیر در یک کامپوننت، میتوان از سرویس RouteParams استفاده کرد. برای فعال سازی یک مسیریابی با کدنویسی، از سرویس Router و متد navigate آن کمک میگیریم.
- جداول تعاریف Definition Table
- جداول ارجاع References Table
- جداول manifest
جداول تعریف
جدول زیر تعدادی از جداول تعریفها را توضیح میدهد:
ModuleDef | شامل آدرس یا مدخلی است که ماژول در آن تعریف شده است. این آدرس شامل نام ماژول به همراه پسوند آن است؛ بدون ذکر مسیر. در صورتی که کامپایل به صورت GUID انجام گرفته باشد، Version ID ماژول هم همراه آنها خواهد بود. در صورتیکه نام فایل تغییر کند، این جدول باز نام اصلی ماژول را به همراه خواهد داشت. هر چند تغییر نام فایل به شدت رد شده و ممکن است باعث شود CLR نتواند در زمان اجرا آن را پیدا کند. |
TypeDef | شامل یک مدخل ورودی برای هر نوعی است که تعریف شده است. هر آدرس ورودی شامل نام نوع ، پرچمها (همان مجوزهای public و private و ...) میباشد. همچنین شامل اندیس هایی به متدها است که شامل جدول MethodDef میباشند یا فیلدهایی که شامل جدول FieldDef میباشند و الی آخر... |
MethodDef | شامل آدرسی برای هر متد تعریف شده در ماژول است که شامل نام متد و پرچم هاست. همچنین شامل امضای متد و نقطهی آغاز کد IL آن در ماژول هم میشود و آن آدرس هم میتواند ارجاعی به جدول ParamDef جهت شناسایی پارامترها باشد. |
FieldDef | شامل اطلاعاتی در مورد فیلدهاست که این اطلاعات ، پرچم، نام و نوع فیلد را مشخص میکنند. |
ParamDef | حاوی اطلاعات پارامتر متدهاست که این اطلاعات شامل پرچمها (in , out ,retval) ، نوع و نام است. |
PropertyDef | برای هر پراپرتی یا خصوصیت، شامل یک آدرس است که شامل نام، نوع و پرچم میشود. |
EventDef | برای هر رویداد شامل یک آدرس است که این آدرس شامل نام و نوع است. |
جداول ارجاعی
AssemblyRef | شامل آدرس اسمبلی است که ماژولی به آن ارجاع داده است و این آدرس شامل اطلاعات ضروری جهت اتصال به اسمبلی میشود و این اطلاعات شامل نام اسمبلی (بدون ذکر پسوند و مسیر)، شماره نسخه اسمبلی، سیستم فرهنگی و منطقهای تعیین شده اسمبلی culture و یک کلید عمومی که عموما توسط ناشر ایجاد میگردد که هویت ناشر آن اسمبلی را مشخص میکند. هر آدرس شامل یک پرچم و یک کد هش هست که بری ارزیابی از صحت و بی خطا بودن بیتهای اسمبلی ارجاع شده Checksum استفاده میشود. |
ModuleRef | شامل یک آدرس ورودی به هدر PE ماژول است به نوعهای پیاده سازی شده آن ماژول در آن اسمبلی. هر آدرس شامل نام فایل و پسوند آن بدون ذکر مسیر است. این جدول برای اتصال به نوعهایی استفاده میشود که در یک ماژول متفاوت از ماژول اسمبلی صدا زده شده پیاده سازی شده است. |
TypeRef | شامل یک آدرس یا ورودی برای هر نوعی است که توسط ماژول ارجاع داده شده است. هر آدرس شامل نام نوع و آدرسی است که نوع در آن جا قرار دارد. اگر این نوع داخل نوع دیگری پیاده سازی شود، ارجاعات به سمت یک جدول TypeDef خواهد بود. اگر نوع داخل همان ماژول تعریف شده باشد، ارجاع به سمت جدول ModuleDef خواهد بود و اگر نوع در ماژول دیگری از آن اسمبلی پیاده سازی شده باشد، ارجاع به سمت یک جدول ModuleRef خواهد بود و اگر نوع در یک اسمبلی جداگانه تعریف شده باشد، ارجاع به جدول AssemblyRef خواهد بود. |
MemberRef | شامل یک آدرس ورودی برای هر عضو (فیلد و متدها و حتی پراپرتی و رویدادها) است که توسط آن آن ماژول ارجاع شده باشد. هر آدرس شامل نام عضو، امضاء و یک اشارهگر به جدول TypeRef است، برای نوعهایی که به تعریف عضو پرداختهاند. |
ILDasm Program.exe
View/MetaInfo/Show
=========================================================== ScopeName : Program.exe MVID : {CA73FFE80D424610A8D39276195C35AA} =========================================================== Global functions Global fields Global MemberRefs TypeDef #1 (02000002) TypDefName: Program (02000002) Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit] (00100101) Extends : 01000001 [TypeRef] System.Object Method #1 (06000001) [ENTRYPOINT] MethodName: Main (06000001) Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096) RVA : 0x00002050 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] ReturnType: Void No arguments. Method #2 (06000002) MethodName: .ctor (06000002) Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886) RVA : 0x0000205c ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #1 (01000001) Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object MemberRef #1 (0a000004) Member: (0a000004) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #2 (01000002) Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute MemberRef #1 (0a000001) Member: (0a000001) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: I4 TypeRef #3 (01000003) Token: 0x01000003 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute MemberRef #1 (0a000002) Member: (0a000002) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #4 (01000004) Token: 0x01000004 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1 (0a000003) Member: (0a000003) WriteLine: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String Assembly Token: 0x20000001 Name : Program Public Key : Hash Algorithm : 0x00008004 Version: 0.0.0.0 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [none] (00000000) CustomAttribute #1 (0c000001) CustomAttribute Type: 0a000001 CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32) Length: 8 Value : 01 00 08 00 00 00 00 00 > < ctor args: (8) CustomAttribute #2 (0c000002) CustomAttribute Type: 0a000002 CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor() Length: 30 Value : 01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78 > T WrapNonEx< : 63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01 >ceptionThrows < ctor args: () AssemblyRef #1 (23000001) Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Version: 4.0.0.0 Major Version: 0x00000004 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) User Strings 70000001 : ( 2) L"Hi" Coff symbol name overhead: 0
View/statistics
File size : 3584 PE header size : 512 (496 used) (14.29%) PE additional info : 1411 (39.37%) Num.of PE sections : 3 CLR header size : 72 ( 2.01%) CLR metadata size : 612 (17.08%) CLR additional info : 0 ( 0.00%) CLR method headers : 2 ( 0.06%) Managed code : 20 ( 0.56%) Data : 2048 (57.14%) Unaccounted : 1093 (30.50%) Num.of PE sections : 3 .text 1024 .rsrc 1536 .reloc 512 CLR metadata size : 612 Module 1 (10 bytes) TypeDef 2 (28 bytes) 0 interfaces, 0 explicit layout TypeRef 4 (24 bytes) MethodDef 2 (28 bytes) 0 abstract, 0 native, 2 bodies MemberRef 4 (24 bytes) CustomAttribute 2 (12 bytes) Assembly 1 (22 bytes) AssemblyRef 1 (20 bytes) Strings 184 bytes Blobs 68 bytes UserStrings 8 bytes Guids 16 bytes Uncategorized 168 bytes CLR method headers : 2 Num.of method bodies 2 Num.of fat headers 0 Num.of tiny headers 2 Managed code : 20 Ave method size 10
public abstract class ResourceProviderFactory { public abstract IResourceProvider CreateGlobalResourceProvider(string classKey); public abstract IResourceProvider CreateLocalResourceProvider(string virtualPath); }
public interface IResourceProvider { IResourceReader ResourceReader { get; } object GetObject(string resourceKey, CultureInfo culture); }
// Type: System.Web.Compilation.ResXResourceProviderFactory // Assembly: System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a // Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_32\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll using System.Runtime; using System.Web; namespace System.Web.Compilation { internal class ResXResourceProviderFactory : ResourceProviderFactory { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public ResXResourceProviderFactory() { } public override IResourceProvider CreateGlobalResourceProvider(string classKey) { return (IResourceProvider) new GlobalResXResourceProvider(classKey); } public override IResourceProvider CreateLocalResourceProvider(string virtualPath) { return (IResourceProvider) new LocalResXResourceProvider(VirtualPath.Create(virtualPath)); } } }
internal class GlobalResXResourceProvider : BaseResXResourceProvider { private string _classKey; internal GlobalResXResourceProvider(string classKey) { _classKey = classKey; } protected override ResourceManager CreateResourceManager() { string fullClassName = BaseResourcesBuildProvider.DefaultResourcesNamespace + "." + _classKey; // If there is no app resource assembly, return null if (BuildManager.AppResourcesAssembly == null) return null; ResourceManager resourceManager = new ResourceManager(fullClassName, BuildManager.AppResourcesAssembly); resourceManager.IgnoreCase = true; return resourceManager; } public override IResourceReader ResourceReader { get { // App resources don't support implicit resources, so the IResourceReader should never be needed throw new NotSupportedException(); } } }
internal const string DefaultResourcesNamespace = "Resources";
internal class LocalResXResourceProvider : BaseResXResourceProvider { private VirtualPath _virtualPath; internal LocalResXResourceProvider(VirtualPath virtualPath) { _virtualPath = virtualPath; } protected override ResourceManager CreateResourceManager() { ResourceManager resourceManager = null; Assembly pageResAssembly = GetLocalResourceAssembly(); if (pageResAssembly != null) { string fileName = _virtualPath.FileName; resourceManager = new ResourceManager(fileName, pageResAssembly); resourceManager.IgnoreCase = true; } else { throw new InvalidOperationException(SR.GetString(SR.ResourceExpresionBuilder_PageResourceNotFound)); } return resourceManager; } public override IResourceReader ResourceReader { get { // Get the local resource assembly for this page Assembly pageResAssembly = GetLocalResourceAssembly(); if (pageResAssembly == null) return null; // Get the name of the embedded .resource file for this page string resourceFileName = _virtualPath.FileName + ".resources"; // Make it lower case, since GetManifestResourceStream is case sensitive resourceFileName = resourceFileName.ToLower(CultureInfo.InvariantCulture); // Get the resource stream from the resource assembly Stream resourceStream = pageResAssembly.GetManifestResourceStream(resourceFileName); // If this page has no resources, return null if (resourceStream == null) return null; return new ResourceReader(resourceStream); } } [PermissionSet(SecurityAction.Assert, Unrestricted = true)] private Assembly GetLocalResourceAssembly() { // Remove the page file name to get its directory VirtualPath virtualDir = _virtualPath.Parent; // Get the name of the local resource assembly string cacheKey = BuildManager.GetLocalResourcesAssemblyName(virtualDir); BuildResult result = BuildManager.GetBuildResultFromCache(cacheKey); if (result != null) { return ((BuildResultCompiledAssembly)result).ResultAssembly; } return null; } }
نکته: باتوجه به استفاده از عبارات بومیسازی ضمنی در استفاده از ورودیهای منابع محلی، خاصیت ResourceReader در این کلاس نمونهای متناظر برای درخواست جاری از کلاس ResourceReader با استفاده از Stream استخراج شده از اسمبلی یافته شده، تولید میکند.
internal abstract class BaseResXResourceProvider : IResourceProvider { private ResourceManager _resourceManager; ///// IResourceProvider implementation public virtual object GetObject(string resourceKey, CultureInfo culture) { // Attempt to get the resource manager EnsureResourceManager(); // If we couldn't get a resource manager, return null if (_resourceManager == null) return null; if (culture == null) culture = CultureInfo.CurrentUICulture; return _resourceManager.GetObject(resourceKey, culture); } public virtual IResourceReader ResourceReader { get { return null; } } ///// End of IResourceProvider implementation protected abstract ResourceManager CreateResourceManager(); private void EnsureResourceManager() { if (_resourceManager != null) return; _resourceManager = CreateResourceManager(); } }
System.Resources.ResourceManager(string baseName, Assembly assemblyName)
var manager = new System.Resources.ResourceManager("Resources.Resource1", typeof(Resource1).Assembly)
var manager = new System.Resources.ResourceManager("Resources.Resource1", Assembly.LoadFile(@"c:\MyResources\MyGlobalResources.dll"))
public static ResourceManager CreateFileBasedResourceManager(string baseName, string resourceDir, Type usingResourceSet)
resgen d:\MyResources\MyResource.fa.resx
private ResourceManager CreateGlobalResourceManager(string classKey) { var baseName = "Resources." + classKey; var buildManagerType = typeof(BuildManager); var property = buildManagerType.GetProperty("AppResourcesAssembly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField); var appResourcesAssembly = (Assembly)property.GetValue(null, null); return new ResourceManager(baseName, appResourcesAssembly) { IgnoreCase = true }; }
var manager = CreateGlobalResourceManager("Resource1"); Label1.Text = manager.GetString("String1");
private ResourceManager CreateLocalResourceManager(string virtualPath) { var virtualPathType = typeof(BuildManager).Assembly.GetType("System.Web.VirtualPath", true); var virtualPathInstance = Activator.CreateInstance(virtualPathType, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { virtualPath }, CultureInfo.InvariantCulture); var buildResultCompiledAssemblyType = typeof(BuildManager).Assembly.GetType("System.Web.Compilation.BuildResultCompiledAssembly", true); var propertyResultAssembly = buildResultCompiledAssemblyType.GetProperty("ResultAssembly", BindingFlags.NonPublic | BindingFlags.Instance); var methodGetLocalResourcesAssemblyName = typeof(BuildManager).GetMethod("GetLocalResourcesAssemblyName", BindingFlags.NonPublic | BindingFlags.Static); var methodGetBuildResultFromCache = typeof(BuildManager).GetMethod("GetBuildResultFromCache", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); var fileNameProperty = virtualPathType.GetProperty("FileName"); var virtualPathFileName = (string)fileNameProperty.GetValue(virtualPathInstance, null); var parentProperty = virtualPathType.GetProperty("Parent"); var virtualPathParent = parentProperty.GetValue(virtualPathInstance, null); var localResourceAssemblyName = (string)methodGetLocalResourcesAssemblyName.Invoke(null, new object[] { virtualPathParent }); var buildResultFromCache = methodGetBuildResultFromCache.Invoke(null, new object[] { localResourceAssemblyName }); Assembly localResourceAssembly = null; if (buildResultFromCache != null) localResourceAssembly = (Assembly)propertyResultAssembly.GetValue(buildResultFromCache, null); if (localResourceAssembly == null) throw new InvalidOperationException("Unable to find the matching resource file."); return new ResourceManager(virtualPathFileName, localResourceAssembly) { IgnoreCase = true }; }
نحوه استفاده از متد فوق نیز به صورت زیر است:
var manager = CreateLocalResourceManager("~/Default.aspx"); Label1.Text = manager.GetString("Label1.Text");