جلسه اول:
اولین قدم در تولید و توسعه نرم افزار داشتن یک نگرش سیستمی به بسته یا محصول نرم افزاری میباشد. اما چرا ما باید نرم افزار را به عنوان یک سیستم در نظربگیریم ؟
جواب این سئوال را باید از تعریف تئوری سیستم و خصوصیاتی که یک سیستم دارا میباشد استخراج کنیم.
تئوری سیستمها
دانشی برای سهولت کار با سیستمها و بررسی دقیق این مفهوم است ؛ در واقع تئوری سیستمها روشی برای شناخت محیط اطراف یا روشی برای شناخت دنیای واقع میباشد .
از تعریف فوق میتوان نتیجه گرفت :
برنامه نویسان برای ساخت برنامه هایی که با نیاز کاربران همسو باشد ، نیاز به شناخت محیطی دارند که کاربران در آن فعالیت میکنند پس برای شناخت محیط باید با دید سیستمی به مسئله نگاه کرد.
خصوصیات مهم سیستم :
1. محیط – Environment: هر سیستم در یک محیط قرار دارد.
2. مرز – Boundary : سیستمهای موجود در یک محیط توسط مرزها از یکدیگر جدا میشوند.
3. ورودی و خروجی – I/O : هر سیستم ورودی هایی را از محیط میگیرد و خروجی هایی را به محیط پس میدهد.
4. واسط – Interface : امکان محاوره سیستمها در یک محیط را فراهم میکند.
5. زیر سیستم – Sub System : هر سیستم میتواند حاوی چندین زیرسیستم باشد . زیر سیستمها تمام خصوصیتهای یک سیستم را دارا میباشند.
6. مکانیزم کنترلی – Controller : مهمترین بخش یک سیستم میباشد. مکانیزم کنترلی در واقع کنترل کننده تمامی فعالیتهای انجام شده توسط یک سیستم است . ورودیها از طریق مکانیزم کنترلی دریافت میشود و بر اساس آن خروجی هایی به محیط پس داده میشود.
نتیجه گیری :
با توجه به خصوصیاتی که در مورد سیستمها مطرح شد به راحتی میتوانیم دلیل علاقه مندی برنامه -نویسان به نوع نگرش سیستمی را در یابیم ، و جود محیط پیرامون یک سیستم و نحوه تبادل اطلاعات این سیستم با سایر سیستمها در این محیط ، شکستن یک سیستم به چند زیر سیستم برای راحتی مسئله و پیاده سازی آسانتر آن و نیز وجود اینترفیسها برای برقراری محاوره ای استاندارد بین زیر سیستمهای یک سیستم و همچنین وجود ورودی هاو تصمیم گیری براساس ورودی هاو تولید یک خروجی همه و همه از نکات مورد توجه برنامه نویسان در تولید یک بسته نرم افزاری هستند که هماهنگی کاملی با مفاهیم تئوری سیستمها دارند.
اگر به ساز و کار شیرپوینت مایکروسافت دقت کنید، همه چیز را داخل دیتابیس ذخیره میکند (از اطلاعات رکوردها گرفته تا فایلها و غیره). حال شاید این سؤال مطرح شود که برای ذخیره سازی فایلهایی با تعداد بیش از یک میلیون عدد، استفاده از دیتابیس مناسب است یا فایل سیستم متداول. برای پاسخ به این سؤال باید به نکات ذیل توجه داشت:
- هر نوع عملیاتی که بر روی فایلها صورت گیرد، بستن، بازکردن و غیره، نیازمند اعمالی در سطح سیستم عامل است (برای مثال بررسی سطح دسترسی لازم برای انجام اینکارها).
- هر گونه عملیاتی بر روی فایلها نیازمند یک حداقل قفل گذاری بر روی آنها است که این نیز مصرف CPU قابل توجهی را سبب خواهد شد.
- تمامی اعمال ذکر شده کل سرور و تمامی سرویسهای در حال اجرا را تحت تاثیر قرار داده و بازدهی آنهارا کاهش میدهند.
- حتی سیستم عاملها نیز از یک file system database جهت مدیریت اعمال خود استفاده میکنند اما این روش برای مدیریت میلیونها و میلیاردها فایل بهینه سازی نشده است.
- ذخیره سازی میلیونها و میلیاردها فایل به تدریج سبب ایجاد fragmentation قابل توجهی شده و این مورد نیز بر روی کارآیی تاثیر منفی خواهد گذاشت (همچنین این مورد بر روی طول عمر تجهیزات ذخیره سازی دادهها تاثیر منفی دارند).
- تهیه پشتیبان و بازگرداندن میلیونها فایل بسیار زمانگیر است (برای مثال جابجایی یک فایل یک مگابایتی بسیار سریعتر است از جابجایی 100 فایل 10 کیلوبایتی).
- مدیریت تغییرات و همچنین بررسی اینکه چه شخصی چه فایلی را قرار داده، حذف کرده یا تغییر داده است در حالت استفاده از file system مشکل است.
- به صورت پیش فرض عموما مباحث replication و امثال آن توسط روش استفاده از file system خصوصا با تعداد بالای فایل، پشتیبانی نمیشود.
- در حالت استفاده از file system ، برنامههای وب باید دسترسی write بر روی یک سری پوشه داشته باشند که این مورد همیشه از دیدگاه امنیتی مساله ساز بوده و مشکل آفرین.
- کرش file system مساوی است با کرش سیستم عامل و بازگشت اینها زمانبر خواهد بود.
با توجه به این نکات استفاده از دیتابیس برای ذخیره سازی تعداد زیادی فایل، مزایای زیر را به همراه خواهد داشت:
- اکثر سیستمهای دیتابیسی امروزی برای کار با حجم عظیمی از دادهها به حد بلوغ خود رسیدهاند.
- هنگام استفاده از دیتابیس برای ذخیره سازی فایلها دیگر سر و کار ما با میلیونها فایل نخواهد بود و حداکثر چند فایل دیتابیس و ملحقات آن مانند لاگ فایل، کل سیستم را تشکیل میدهند.
- فایلهای دیتابیس برای مثال SQL Server ، همیشه توسط SQL Server در حالت باز قرار داشته و مباحث قفلگذاری بر روی فایلهای دیتابیس و بررسی سطح دسترسی و غیره توسط سیستم عامل در اینجا به حداقل خود میرسد.
- در این حالت بار سیستم عامل شما تنها سیستمی است که مشغول سرویس دهی اطلاعات دیتابیسهای شما است.
- جستجوی فایلها، حتی جستجو در محتوای این فایلهای ذخیره شده در یک دیتابیس بسیار سریعتر از روش file system میباشد. امکان استفاده از کوئریهای SQL انعطاف پذیری خاصی را به این سیستمها خواهند داد (برای مثال قابلیت full text search مربوط به SQL server امکان جستجو بر روی رکوردهایی با محتوای pdf را نیز پس از انجام اندکی تنظیمات، دارا میباشد).
- هنگام کار با دیتابیس مباحث تراکنشی نقش بسیار حائز اهمیتی را بازی میکنند اما عموما سیستم عاملها در این زمینه نیازمند کار و برنامه نویسی قابل توجهی هستند (این قابلیت به ویندوز ویستا اضافه شده است).
- کرش یک دیتابیس عموما سبب کرش سیستم عامل یا حتی کرش سایر دیتابیسهای موجود نخواهد شد.
- امکان تهیه پشتیبان از دیتابیسها و بازیابی آنها ساده است. (حداقل از بازیابی میلیونها فایل سادهتر است)
- امکانات replication به صورت پیش فرض در اکثر سیستمهای دیتابیسی امروزی مهیا است.
- امکان ثبت وقایع و مدیریت اطلاعات افزوده شده به دیتابیس، از طریق نرم افزارهایی که برای این کار نوشته خواهند شد (یا حتی امکانات توکار این برنامهها) از هر لحاظ نسبت به روش file system برتری دارد.
- امکانات سوئیچ کردن به دیتابیسی دیگر در شبکه در صورت کرش یک نود، مهیا است و پیش بینی شده است.
- برای استفاده از یک دیتابیس توسط یک برنامه وب، نیازی به داشتن دسترسی write بر روی هیچ فولدری وجود ندارد که این خود یک مزیت امنیتی مهم است و همچنین امکان محدود کردن سطوح دسترسی به فایلهای ذخیره شده در دیتابیس با برنامههای نوشته شده نیز سادهتر است. (البته در اینجا مسلما منظور از دیتابیس، دیتابیس Access نیست و SQL Server یا MySQL مد نظر هستند)
استفاده از Web API در ASP.NET Web Forms
- Web API یک بحث سمت سرور است. به آن به زبان ساده به چشم یک وب سرویس مدرن نگاه کنید. برای نمونه بجای وبمتدهای استاتیک صفحات aspx یا فایلهای ashx یا asmx و حتی سرویسهای WCF از نوع REST و امثال آن، بهتر است از Web API استفاده کنید.
- برای نمونه پایه مباحثی مانند Forms Authentication در اینجا هم کاربرد دارد (البته این یک نمونه است).
- برای کار با Web API الزاما نیازی به ASP.NET ندارید (نه وب فرمها و نه MVC)؛ به هیچکدام از نگارشهای آن. سمت کاربر آن AngularJS و سمت سرور آن Web API باشد. کار میکند. (اهمیت این مساله در اینجا است که الان میشود یک فریم ورک جدید توسعهی برنامههای وب را کاملا مستقل از وب فرمها و MVC طراحی کرد)
Bundling and Minifying Inline Css and Js
بحث Optimization در لینک زیر کاملتر بررسی شده، البته مطلب شما برایم تازگی داشت.
http://go.microsoft.com/fwlink/?LinkId=254725
نکاتی که میشه گفت
در وب فرم هم قابل استفاده است
برای single download کردن فایلهای css و js دو روش وجود دارد:
1. تنظیم debug=false در بخش compilation در فایل Web.config
2. نوشتن کد زیر در کلاسی که باندلهای خود را در bundleTable درج میکنید.
BundleTable.EnableOptimizations = true
install-package Microsoft.AspNet.Web.Optimization
تفاوتی که در scriptها ایجاد میکند میتوان به حذف کردن description ها، تغییر در variableها ، و min کردن jsهای شما اشاره کرد.
موفق باشید.
بررسی تغییرات Blazor 8x - قسمت هشتم - مدیریت انتقال اطلاعات Pre-Rendering سمت سرور، به جزایر تعاملی
بررسی نحوهی عملکرد سرویس PersistentComponentState
سرویس PersistentComponentState، در داتنت 6، به Blazor اضافه شد و امکان جدیدی نیست. قسمتی از این مباحث جدید SSR که بهنظر مختص به Blazor 8x هستند، پیشتر هم وجود داشتند؛ تحت عنوان pre-rendering. برای مثال فقط کافی بودن تا در برنامههای Blazor Server قبلی، فایل Host.cshtml_ را به صورت زیر ویرایش کرد تا pre-rendering فعال شود:
<component type="typeof(App)" render-mode="ServerPrerendered" />
<body> <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> <persist-component-state /> </body>
@page "/" @implements IDisposable @inject PersistentComponentState applicationState @inject IUserRepository userRepository @foreach(var user in users) { <ShowUserInformation user="@item"></ShowUserInformation> } @code { private const string cachingKey = "something_unique"; private List<User> users = new(); private PersistingComponentStateSubscription persistingSubscription; protected override async Task OnInitializedAsync() { persistingSubscription = applicationState.RegisterOnPersisting(PersistData); if (applicationState.TryTakeFromJson<List<User>>(cachingKey, out var restored)) { users = restored; } else { users = await userRepository.GetAllUsers(); } } public void Dispose() { persistingSubscription.Dispose(); } private Task PersistData() { applicationState.PersistAsJson(cachingKey, users); return Task.CompletedTask; } }
- ابتدا تزریق سرویس PersistentComponentState را مشاهده میکنید. این همان کامپوننتی است که کار کش کردن اطلاعات را مدیریت میکند.
- سپس فراخوانی متد RegisterOnPersisting انجام شدهاست. متدی که توسط آن ثبت میشود، در حین عملیات pre-rendering فراخوانی میشود تا اطلاعاتی را کش کند. نحوهی این کش شدن را در ادامهی مطلب بررسی میکنیم.
- سپس فراخوانی متد TryTakeFromJson رخدادهاست. اگر این متد اطلاعاتی را برگرداند، یعنی pre-rendering سمت سرور پیشتر انجام شده و اطلاعاتی کش شده، برای دریافت در سمت کلاینت، وجود داشته و نیازی به مراجعهی مجدد و دوباره، به بانک اطلاعاتی نیست.
- دراینجا یک قسمت Dispose را هم مشاهده میکنید تا اگر کاربر به صفحهی دیگری مراجعه کرد، متد ثبت شدهی در اینجا، از لیست مواردی که باید در حین pre-rendering سریالایز شوند، حذف گردد.
اگر از این روش استفاده نشود، به علت دوبار فراخوانی شدن متد OnInitializedAsync یک کامپوننت به همراه pre-rendering، اطلاعاتی که در حین pre-rendering کامپوننت از بانک اطلاعاتی، برای تولید محتوای استاتیک صفحه در سمت سرور دریافت شده، با فعالسازی آتی تعاملی سمت کلاینت آن کامپوننت، از دست خواهد رفت و در این حالت کلاینت باید مجددا این اطلاعات را از بانک اطلاعاتی درخواست کند. بنابراین هدف از persist-component-state، حفظ دادهها در بین دو رندر است؛ رندر اولی که در سمت سرور انجام میشود و رندر دومی که متعاقبا در سمت کلاینت رخ میدهد.
یک نکته: به یک چنین قابلیتی در فریمورکهای دیگر «hydration» هم گفته میشود که در اصل یک شیء دیکشنری است که مقدار شیء حالت را در سمت سرور دریافت کرده و آنرا در زمان فعال شدن سمت کلاینت کامپوننت، برای استفادهی مجدد مهیا میکند.
سؤال: اطلاعات سرویس PersistentComponentState کجا ذخیره میشوند؟
همانطور که در مثال فوق هم مشاهده کردید، سرویس PersistentComponentState، این state را به صورت JSON ذخیره میکند. اما ... این اطلاعات دقیقا کجا ذخیره میشوند؟
برای مشاهدهی آنها، فقط کافی است به source صفحه مراجعه کنید تا با دو مقدار مخفی رمزنگاری شدهی زیر در انتهای HTML صفحه مواجه شوید!
<!--Blazor-Server-Component-State:CfDJXyz-> <!--Blazor-WebAssembly-Component-State:eyJVc2Xyz->
مایکروسافت از این نوع کارها پیشتر در ASP.NET Web forms توسط ViewStateها انجام دادهاست! البته ViewStateها جهت نگهداری اطلاعات وضعیت کلاینت، به سمت سرور ارسال میشوند؛ اما این Component-Stateهای مخفی، فقط یکبار توسط قسمت کلاینت خوانده میشوند و هدف آنها ارسال اطلاعاتی به سمت سرور نیست.
یک نکته: همانطور که عنوان شد، در نگارشهای قبلی Blazor، از تگهلپری به نام persist-component-state برای درج این اطلاعات در انتهای صفحه استفاده میکردند. استفاده از این تگهلپر در Blazor 8x منسوخ شده و به صورت خودکار دادههای تمام سرویسهای PersistentComponentState فعالی که توسط PersistAsJson اطلاعاتی را ذخیره میکنند، جمع آوری و به صورت یکجا در انتهای صفحه به صورت رمزنگاری شده، درج میکنند.
روش خاموش کردن Pre-rendering
برای خاموش کردن pre-rendering نیاز است پارامتر همنامی را به نحو زیر با false مقدار دهی کرد:
@rendermode renderMode @code { private static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false); }
بازنویسی مثال قسمت قبل با استفاده از سرویس PersistentComponentState
در قسمت قبل هرچند روش مواجه شدن با مشکل دوبار فراخوانی شدن متد آغازین یک کامپوننت را در سمت سرور و کلاینت بررسی کردیم، اما ... در نهایت دوبار مراجعه به بانک اطلاعاتی انجام میشود؛ یکبار در زمان pre-rendering و با مراجعهی مستقیم به یک سرویس سمت سرور و یکبار دیگر هم در زمان فراخوانی httpClient.GetFromJsonAsync در سمت کلاینت برای دریافت اطلاعات مورد نیاز از یک Web API Endpoint. اگر بخواهیم اطلاعات لیست محصولات دریافتی سمت سرور را به سمت کلاینت منتقل کنیم و مجددا آنرا از بانک اطلاعاتی دریافت نکنیم، راهحل سوم آن، استفاده از سرویس PersistentComponentState است.
در کدهای زیر، بازنویسی کامپوننت محصولات مشابه را مشاهده میکنید که کمی پیچیدهتر شدهاست. اینبار لیست محصولات مشابه، در همان بار اول رندر کامپوننت نمایش داده میشوند و نه پس از کلیک بر روی دکمهای. به همین جهت باید کار مدیریت دوبار فراخوانی متد رویدادگردان OnInitializedAsync را به درستی انجام داد. متد OnInitializedAsync یکبار در حین پیشرندر سمت سرور اجرا میشود و بار دیگر زمانیکه وباسمبلی جاری در مرورگر به صورت کامل دریافت شده و فعال میشود؛ یعنی برای بار دوم، کدهای اجرایی آن سمت کلاینت هستند.
در این مثال با استفاده از سرویس PersistentComponentState، اطلاعات دریافت شدهی در حین pre-rendering ابتدایی رخدادهی در سمت سرور، در متد OnPersistingAsync، کش شده و در حین فعال شدن وباسمبلی مرتبط در سمت کلاینت، با استفاده از متد PersistentState.TryTakeFromJson، از کش خوانده میشوند و دیگر دوبار رفت و برگشت به بانک اطلاعاتی را شاهد نخواهیم بود.
@implements IDisposable @inject IProductStore ProductStore @inject PersistentComponentState PersistentState <h3>Related products</h3> @if (_relatedProducts == null) { <p>Loading...</p> } else { <div class="mt-3"> @foreach (var item in _relatedProducts) { <a href="/ProductDetails/@item.Id"> <div class="col-sm"> <h5 class="mt-0">@item.Title (@item.Price)</h5> </div> </a> } </div> } @code{ private const string StateCachingKey = "products"; private IList<Product>? _relatedProducts; private PersistingComponentStateSubscription _persistingSubscription; [Parameter] public int ProductId { get; set; } protected override async Task OnInitializedAsync() { _persistingSubscription = PersistentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); if (PersistentState.TryTakeFromJson<IList<Product>>(StateCachingKey, out var restored)) { _relatedProducts = restored; } else { await Task.Delay(500); // Simulates asynchronous loading _relatedProducts = await ProductStore.GetRelatedProducts(ProductId); } } private Task OnPersistingAsync() { PersistentState.PersistAsJson(StateCachingKey, _relatedProducts); return Task.CompletedTask; } public void Dispose() { _persistingSubscription.Dispose(); } }
نکتهی مهم: فعلا کدهای فوق فقط برای حالت بارگذاری اولیهی کامپوننت درست کار میکنند. یعنی اگر نگارش وباسمبلی کامپوننت پس از وقوع پیشرندر سمت سرور، در مرورگر دریافت و بارگذاری کامل شود؛ اما برای دفعات بعدی مراجعهی به این صفحه با استفاده از enhanced navigation و راهبری بهبود یافته که در قسمت ششم این سری بررسی کردیم ... دیگر کار نمیکنند و در این حالت کش شدنی رخ نمیدهد که در نتیجه، شاهد دوبار رفت و برگشت به بانک اطلاعاتی خواهیم بود و اساسا استفادهی از PersistentComponentState را زیر سؤال میبرد؛ چون در حین راهبری بهبود یافته، از آن استفاده نمیشود (قسمت PersistentState.TryTakeFromJson آن، هیچگاه اطلاعاتی را از کش نمیخواند). اطلاعات بیشتر
قسمت قبل به IIS7 اختصاص داشت که شاید برای خیلیها کاربرد نداشته باشد خصوصا اینکه برنامه نویسها ترجیح میدهند به روشهایی روی بیاورند که کمتر نیاز به دخالت مدیر سرور داشته باشد؛ یا زمانیکه سایت شما بر روی یک هاست اینترنتی قرار گرفته است عملا شاید دسترسی خاصی به تنظیمات IIS نداشته باشید (مگر اینکه یک هاست اختصاصی را تهیه کنید).
برای IIS6 و ماقبل از آن و حتی بعد از آن!، حداقل دو روش برای کش کردن اطلاعات استاتیک وجود دارد:
الف) استفاده از web resources معرفی شده در ASP.Net 2.0 به بعد
در مورد نحوهی تعریف و بکارگیری web resources میتوان به مقاله "تبدیل پلاگینهای jQuery به کنترلهای ASP.Net" رجوع کرد.
همانطور که در شکل فوق نیز ملاحظه میکنید، هدر مربوط به مدت زمان منقضی شدن کش سمت کلاینت یک web resource توسط موتور ASP.Net به صورت خودکار به سال 2010 تنظیم شده است و این مقدار خالی نیست.
ب) افزودن این هدر به صورت دستی
برای این منظور باید در نحوهی ارائه فایلهای استاتیک دخالت کنیم و اینکار را با استفاده از یک generic handler میتوان انجام داد.
کد این generic handler میتواند به صورت زیر باشد:
using System;
using System.IO;
using System.Web;
using System.Web.Services;
using System.Reflection;
namespace test1
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class cache : IHttpHandler
{
private static void cacheIt(TimeSpan duration)
{
HttpCachePolicy cache = HttpContext.Current.Response.Cache;
FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(duration));
cache.SetMaxAge(duration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
public void ProcessRequest(HttpContext context)
{
string file = context.Request.QueryString["file"];
if (string.IsNullOrEmpty(file))
{
return;
}
string contetType = context.Request.QueryString["contetType"];
if (string.IsNullOrEmpty(contetType))
{
return;
}
context.Response.Write(File.ReadAllText(context.Server.MapPath(file)));
//Set the content type
context.Response.ContentType = contetType;
// Cache the resource for 30 Days
cacheIt(TimeSpan.FromDays(30));
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
این generic handler دو کوئری استرینگ را دریافت میکند؛ file جهت دریافت نام فایل و contetType جهت مشخص سازی نوع محتوایی که باید سرو شود؛ مثلا جاوا اسکریپت یا استایل شیت و امثال آن. سپس زمانیکه محتوا را Response.Write میکند، هدر مربوط به کش شدن آنرا نیز به 30 روز تنظیم مینماید.
تابع مربوط به کش کردن اطلاعات از مقاله ASP.NET Ajax Under-the-hood Secrets استخراج شد.
روش استفاده در مورد فایلهای CSS
بجای تعریف یک فایل CSS در صفحه، به صورت استاندارد، اکنون تعریف متداول را به صورت زیر اصلاح کنید:
<link type="text/css" href="cache.ashx?v=1&file=site.css&contetType=text/css" rel="Stylesheet" />
روش استفاده در مورد فایلهای JS
<script type="text/javascript" src="cache.ashx?v=1&file=js/jquery-1.3.2.min.js&contetType=application/x-javascript"></script>
برای نوشتن تستهای واحد(Unit Test) برای پروژه مذکور به چه صورت میشه EntityFramework رو Mock کرد؟ یعنی تزریق وابستگی رو با StructureMap میشه انجام داد یا باید پیاده سازی دیگه ای رو استفاده کرد. میشه یه راهنمایی در مورد نوشتن یه تست واحد توی این سناریو بکنید.
SignalR - قسمت دوم
با توجه به تصویر بالا SignalR در شرایط موجود بهترین روش برای برقراری ارتباط با سرور رو forever frame تشخیص داده و مشاهده میشه که این ارتباط دائمیه و فعلا نتیجهای از سمت سرور دریافت نکرده و ارتباط کاملا زنده است. البته اگر در این ابزار درباره درخواستهای ارسالی به سرور بیشتر جستجو بکنین اطلاعات بیشتری نصیبتون میشه که آوردنش اینجا بحث رو طولانی میکنه.
حالا برنامه رو در یه مرورگر دیگه که از html5 پشتیبانی میکنه اجرا کنین. مثلا نتیجه در گوگل کروم و ابزار توسعه اون به شکل زیره:
همونطور که میبینین در اینجا روش استفاده شده Server Sent Events هست.
در فایرفاکس هم با استفاده از ابزار محبوب firebug نتیجه مشابه کروم بدست میاد:
البته اگر علاقه زیادی به کندوکاو در جزئیات این درخواستها دارین (مثل خود من) چیزی بهتر از fiddler2 پیدا نمیشه. میتونین پس از ارسال یک متن دوباره این درخواستها رو مورد بررسی قرار بدین و ببینین که چیجوری کانالهای ارتباط پس از ارسال و دریافت دیتا قطع و برقرار میشه.
این نکته رو هم باید یادآور بشم که هرچند که این کتابخونه بهترین روش رو میتونه انتخاب کنه اما به برنامه نویس امکان تعیین صریح روش ارتباط رو هم میده. اگر به راهنماهای این کتابخونه سر بزنین میبینین که امکانات زیادی بهش اضافه شده و امکانات زیادی هم در آینده به اون اضافه میشه. امکاناتی از قبیل ارسال دادهها به یک کلاینت خاص و یا به گروهی خاص از کلاینتها، خصوصیسازی آدرس سرور و همچنین پشتیبانی از Cross Domain در آخرین نسخه، امکان استفاده از Reactive Extension (بلاگ)، بحث Self Hosting که امکان خیلی جالبیه و میتونه خیلی جاها یه عنوان یک راهحل سبک و سریع به کار بیاد، قابلیت فوق العاده در بایندینگ دادهها در سمت سرور و مخصوصا کلاینت، امکان تشخیص برقراری یا قطع ارتباط کلاینتها در سمت سرور، استفاده از امکانات این کتابخونه برای برقراری ارتباط با کلاینتها در خارج از فضای کلاسهای مشتق شده از دو کلاس پایه (Hub و PersistentConnection) و چند مورد دیگه تا نسخه جاری اضافه شدند.
درحال حاضر دارم روی یه برنامه چت با امکانات بیشتر کار میکنم که پس از آماده شدن ارائه میدمش. یکی از پروژههای متن بازی که با استفاده از این کتابخونه توسعه داده شده jabbr.net است. یه اتاق گفتگوی کامل با امکانات جالبه که میتونین به اون هم یه سری بزنین.
در آخر هم یه لینک جالب برای مطالعه معرفی میکنم: Highest voted Signalr Questions - stackoverflow
طبق تصویر فایلها را به صفحهای که ساختیم اضافه میکنیم:
پروژه را اجرا کرده و توسط افزونهی firebug درخواستهایی را که از سرور شدهاست، بررسی میکنیم. مشاهده خواهید کرد که به ازای هر فایل، یک درخواست به سرور ارسال شده و هیچکدام از فایلها توسط وب سرور فشرده سازی نشدهاند و اطلاعاتی در مورد کش، به هدر آنها اضافه نشده است.
<?xml version="1.0" encoding="utf-8" ?> <combres xmlns='urn:combres'> <filters> <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" /> </filters> <resourceSets url="~/combres.axd" defaultDuration="30" defaultVersion="auto" defaultDebugEnabled="false" defaultIgnorePipelineWhenDebug="true" localChangeMonitorInterval="30" remoteChangeMonitorInterval="60"> <resourceSet name="siteCss" type="css"> <resource path="~/content/Site.css" /> <resource path="~/content/anotherCss.css" /> <resource path="~/scripts/yetAnotherCss.css" /> </resourceSet> <resourceSet name="siteJs" type="js"> <resource path="~/scripts/jquery-1.4.4.js" /> <resource path="~/scripts/anotherJs.js" /> <resource path="~/scripts/yetAnotherJs.js" /> </resourceSet> </resourceSets> </combres>
<?xml version="1.0" encoding="utf-8" ?> <combres xmlns='urn:combres'> <filters> <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" /> </filters> <resourceSets url="~/combres.axd" defaultDuration="30" defaultVersion="auto" defaultDebugEnabled="false" defaultIgnorePipelineWhenDebug="true" localChangeMonitorInterval="30" remoteChangeMonitorInterval="60"> <resourceSet name="siteCss" type="css"> <resource path="~/Styles/Site.css" /> </resourceSet> <resourceSet name="siteJs" type="js"> <resource path="~/Scripts/jquery-1.10.2.js" /> <resource path="~/Scripts/jquery-1.10.2.min.js" /> </resourceSet> </resourceSets> </combres>
<configuration> <configSections> <section name="combres" type="Combres.ConfigSectionSetting, Combres, Version=2.2, Culture=neutral, PublicKeyToken=1ca6b37997dd7536" /> </configSections> <system.web> <pages> <namespaces> <add namespace="Combres" /> </namespaces> </pages> </system.web> <combres definitionUrl="~/App_Data/combres.xml" /> <appSettings> <add key="CombresSectionName" value="combres" /> </appSettings> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="AjaxMin" publicKeyToken="21ef50ce11b5d80f" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.84.4790.14405" newVersion="4.84.4790.14405" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.AddCombresRoute("Combres Route"); }
<%@ Import Namespace="Combres" %> <head runat="server"> <%= WebExtensions.CombresLink("siteCss") %> <%= WebExtensions.CombresLink("siteJs") %> </head>
<%= Url.CombresLink("siteCss") %> <%= Url.CombresLink("siteJs") %>
در ادامه میتوانید فایل site.css قبلی و فعلی را مقایسه کنید!
در قسمت بعد با سازوکار combres و روش استفاده از فیلترها، بیشتر آشنا میشویم.