مطالب
آشنایی با Contained Databases در SQL Server 2012
مقدمه:
بعضی وقت‌ها به هر دلیلی لازم است پایگاه داده برنامه هایمان را به سرور دیگری انتقال دهیم . برخلاف Oracle که انتقال پایگاه داده به سرور دیگر کار مشکل و تخصصی می‌باشد در SQL Server براحتی با یک Detach و Attach مجدد این انتقال انجام خواهد شد. اما اطلاعات Logins در پایگاه داده Master  سرور ذخیره شده است و با فایل پایگاه داده انتقال نمی‌یابد و به هیمن خاطر برخی مواقع پس از Attach پایگاه داده با پیغام The database is not accessible یا Cannot open user default database مواجع خواهیم شد که مشکل بخاطر همین نبود کاربران سرور قبلی و Owner متفاوت پایگاه داده می‌باشد که با استفاده از رویه ذخیره شده sp_change_users_login این مشکل قابل حل می‌باشد اما در SQL Server 2012 راحل بهتری فراهم شده است.

Contained Databases در Sql Server 2012
یکی از امکانات اضافه شده در SQL Server 2012  امکان Contained Databases  هست که برای حل همین مشکل و رفع کامل وابستی پایگاه داده با Instance نصب شده و خاصیت قابل حمل کامل database‌ها می‌باشد. در ادامه نحوه استفاده از این امکان را بررسی خواهیم کرد:
  1. فعال کردن خاصیت Contained Databases 

    قبل از استفاده از Contained Databases می بایست این امکان را فعال کرد. برای این کار می‌توانید از SQL Server Management Studio یا T-SQL commands استفاده نمایید. بر روی نام Instance راست کلیت کنید و گزینه Properties را انتخاب نمایید. از گزینه Advanced که در شکل زیر مشاهده می‌نمایید خاصیت Enable Contained Databases را بر روی True قرار دهید.

    یا می‌توایند از sp_configure این کار را انجام دهید.دستورات زیر این موضوع را نشان می‌دهد.
    sp_configure 'show advanced options',1
    GO
    RECONFIGURE WITH OVERRIDE
    GO
    sp_configure 'contained database authentication',1
    GO
    RECONFIGURE WITH OVERRIDE
    GO

  2. ایجاد یا تغییر یک پایگاه داده از نوع Contained Databases

    برای ایجاد یک پایگاه داده با این خاصیت یا تغییر پایگاه داده موجود کافیست مقدار گزینه Containment type را بر روی Partial قرار دهید. برای پایگاه داده موجود از پنجره Properties پایگاه داده صفحه Options را انتخاب کنید.


  3. ایجاد یک کاربر برای پایگاه داده Contained Databases

    برای تعریف یک کاربر در سطح پایگاه داده پوشه Security پایگاه داده خود را باز کنید بر روی پوشه Users راست کیلک و گزینه New User را انتخاب نمایید از گزینه User type که در شکل زیر نشان داده شده است SQL user with password را انتخاب نمایید و نام کاربر و رمز عبور و تکرار آن را وارد نمایید. کاربر ایجاد شده در سطح پایگاه داده می‌باشد و با انتقال به سرور دیگر نیر قابل دسترسی می‌باشد.

  4.  اتصال به پایگاه داده Contained Databases

    برای اتصال به پایگاه داده کافیست در حالت SQL Server Authentication نام کاربری و رمز عبور جدید را وارد و گزینه Options  را انتخاب و از برگه Additional Connection Parameters نام پایگاه داده مورد نظر را مانند شکل زیر وارد نمایید پس از ورود تنها پایگاه داده خود را مشاهده می‌نمایید. یکی از کاربرهای این قابلیت برای مدیران سرور پایگاه داده می‌باشد که بدون استفاده از مجوز sysadmin  به کاربران اجازه دسترسی را می‌دهد.



نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت پنجم - پیاده سازی ورود و خروج از سیستم
با سلام و تشکر؛ من قسمت کلاینت مثال جاری را با Server side Blazor عوض کردم. متاسفانه در هنگامیکه کاربر به صفحه لاگین ارجاع داده میشود و لاگین میکند، برنامه به صفحه ای که کاربر در آن بوده باز نمی‌گردد و به جای آن صفحه Welcome to IdentityServer4 نمایش داده می‌شود. ظاهرا Blazor به صورت داخلی دارای چنین مکانیسمی نیست. اگر راهنمایی بفرمایید که چطور می‌توانم کاربر را به صفحه ای که در آن بوده Redirect کنم ممنون می‌شوم.
مطالب
بهینه سازی پروژه‌های Angular
Angular یکی از محبوب‌ترین فریم ورک‌ها، برای ساختن برنامه‌های تک صفحه‌ای می‌باشد. اگرچه گفتیم تک صفحه‌ای، اما ضرورتا منظور این نیست که پروژه‌ی شما تنها شامل یک صفحه باشد. شما می‌توانید با Angular یک وب سایت را با هزاران صفحه نیز ایجاد کنید. با این حال وقتی بحث از کارآیی باشد، بهتر است همیشه در رابطه با تعدادی فکر کنیم که باعث می‌شوند پروژه، نرم و سریع اجرا شود. 


#1 Optimize main bundle with Lazy Loading
وقتی که پروژه را برای حالت ارائه‌ی نهایی (Production ) بدون در نظر گرفتن Lazy Load، بیلد می‌کنیم احتمالا فایل‌های تولید شده زیر را در پوشه‌ی dist خواهیم دید: 

  1. polyfills.js :  برای ساختن برنامه‌های سازگار با انواع مرورگر‌ها می‌باشد. به دلیل اینکه وقتی کد‌ها را با جدیدترین ویژگی‌ها می‌نویسیم، ممکن است که همه‌ی مرورگر‌ها توانایی پشتیبانی از آن ویژگی‌ها را نداشته باشند.
  2. scripts.js : شامل اسکریپت‌هایی می‌باشد که در بخش scripts، در فایل angular.json تعریف کرده‌ایم.
  3.  webpack loader : runtime.js می‌باشد. این فایل شامل webpack utilities‌هایی می‌باشد که برای بارگذاری دیگر فایل‌ها مورد نیاز است.
  4. styles.css : شامل style ‌هایی است که در بخش styles، در فایل angular.json تعریف کرده‌ایم.
  5. main.js : شامل تمامی کد‌ها از قبیل کامپوننت‌ها ( کد‌های مربوط به css ، html ، ts) ، دایرکتیو‌ها، pipes و سرویس‌ها و ماژول‌های ایمپورت شده از جمله third party‌های می‌باشد.

همانطور که متوجه هستید، فایل main.js در طول زمان بزرگتر و بزرگتر خواهد شد که این یک مشکل است. در این حالت برای مشاهده‌ی وب سایت، مرورگر نیاز دارد که فایل main.js را دانلود کرده و سپس در صفحه،  آن را اجرا و رندر کند که این یک چالش برای کاربران موبایل با اینترنت ضعیف و هم چنین کاربران دسکتاپ می‌باشد. 
آسان‌ترین راه برای برطرف کردن این مشکل این است که پروژه را به چندین ماژول lazy load، تقسیم کنیم. وقتی که از lazy module‌ها استفاده می‌کنیم، انگیولار chunk مربوط به آن را تولید می‌کند که در ابتدا بارگذاری نخواهد شد؛ مگر اینکه مورد نیاز باشند (معمولا با فعال سازی یک مسیر اتفاق می‌افتد). 
وقتی که از lazy loading استفاده می‌کنیم، بعد از فرایند build، فایل‌های جدیدی تولید خواهند شد، مثل  4.386205799sfghe4.js که یک  چانک از  یک lazy module می‌باشد و در زمان راه اندازی صفحه اول اجرا نخواهد شد که نتیجه‌ی آن داشتن فایل main.js ای با حجم کم می‌باشد. بنابراین بارگذاری صفحه‌ی اول، خیلی سریع انجام خواهد شد. 

با این حال، بارگذاری هر قسمت می‌تواند بر روی کارآیی تاثیر داشته باشد (بارگذاری ممکن است کند باشد). خوشبختانه انگیولار یک راه را برای برطرف کردن این مشکل فراهم کرده است ( PreloadingStrategy ) . بعد از اینکه فایل main.js  به صورت کامل بارگذاری و اجرا شد، کار پیش واکشی ماژول‌ها را انجام می‌دهد و زمانیکه کاربری مسیری را درخواست می‌دهد، آْن مسیر را بدون درنگ مشاهده خواهد کرد.

#2 Debug bundles with Webpack Bundle Analyzer 
حتی ممکن است بعد از تقسیم کردن منطق برنامه به چند ماژول lazy load، شما یک فایل main.js بزرگ داشته باشید. در این حالت می‌توانید بهینه سازی بیشتری را با استفاده از Webpack Bundle Analyzer انجام دهید. با استفاده از این پکیج می‌توانید آمار‌هایی را در رابطه با هر باندل داشته باشید. در ابتدا با استفاده از دستور زیر پکیج آن‌را نصب کنید:
npm install --save-dev webpack-bundle-analyzer
سپس فایل package.json را باز کرده و در بخش scripts، مدخل زیر را اضافه کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"
توجه کنید که اگر خروجی برنامه شما مستقیما در dist می‌باشد، به صورت بالا عمل کنید؛ ولی اگر خروجی برنامه شما در پوشه‌ی dist/YourApplicationName باشد، آن را به حالت زیر ویرایش کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/YourApplicationName/stats.json"
در نهایت دستور زیر را اجرا کنید:
npm run bundle-report
دستور بالا یک بیلد را برای پروژه برای حالت ارائه‌ی نهایی  (Production) همراه با  آمار‌هایی در رابطه با هر باندل ایجاد می‌کند. در اینجا می‌توانیم ببینیم که چه ماژولها/فایل‌هایی در هر باندل استفاده شده‌است. این مورد فوق العاده کمک می‌کند. هم چنین می‌توانیم به صورت بصری ببینیم که چه چیز‌هایی در هر ماژول شامل شده‌اند که بهتر بود آنجا نباشند:


#3 Use Lazy Loading for images that are not visible in page 
وقتی که صفحه اصلی را در اولین بار، بارگذاری می‌کنیم، می‌توانیم تصاویری را داشته باشیم که در صفحه‌ی نمایش، نمایان نباشند (منظور viewport می‌باشد) و کاربر برای دیدن آن تصاویر باید صفحه را به پایین اسکرول کند. با این وجود وقتی که صفحه بارگذاری می‌شود، تصاویر هم بلافاصله دانلود می‌شوند. اگر تعداد تصاویر زیاد باشند این مورد می‌تواند بر روی performance تاثیر منفی داشته باشد. برای برطرف کردن این مشکل می‌توان از lazy loading تصاویر استفاده کرد. در این حالت تصاویر زمانی بارگذاری می‌شوند که کاربر به آن‌ها می‌رسد. یک JavaScript API به نام Intersection Observer API وجود دارد که باعث می‌شود پیاده سازی lazy load خیلی آسان شود. علاوه بر این می‌توان یک دایرکتیو را با قابلیت استفاده مجدد طراحی کرد ( lazy loading برای تصاویر با استفاده از Intersection Observer).

#4 Use virtual scrolling for large lists  
با استفاده از virtual scrolling  می‌توان المنت‌ها را در Dom بر اساس بخش‌های قابل مشاهده‌ای از یک لیست، Load  یا unload کرد که برنامه را فوق العاده سریع می‌کند.
 
#5 Use FOUT instead of FOIT for fonts  
در بیشتر وب سایت‌ها می‌توان فونت‌های سفارشی زیبایی را به جای فونت‌های معمول دید. با این حال برای استفاده از فونت‌های فراهم شده توسط سرویس‌های دیگر لازم است که مرورگر آن‌ها را دانلود و parse  کند. اگر از فونت‌های سفارشی استفاده کنیم، مثل  Google Fonts، چه اتفاقی می‌افتد؟ در اینجا دو سناریو وجود دارد: 
  • در این حالت مرورگر منتظر می‌ماند تا فونت دانلود شود و آن را parse  کند و تنها بعد از آن متن را بر روی صفحه نمایش می‌دهد. متن روی صفحه تا زمانیکه فونت دانلود و parse  نشده باشد، قابل مشاهده نیست؛ این FOIT است (Flash of invisible text) .
  • مرورگر در ابتدا متن را با استفاده از فونت معمول نمایش می‌دهد و بعد از آن سعی می‌کند که ساختار‌های فونت خارجی را دریافت کند. وقتی که دانلود انجام شد و سپس آن را parse  کرد، فونت سفارشی دانلود شده، با فونت معمول جایگزین خواهد شد؛ این FOUT است ( Flash of unstyled text ).

بیشتر مرورگر‌ها از FOIT  استفاده می‌کنند و تنها Internet Explorer از FOUT استفاده می‌کند. برای برطرف کردن این مشکل می‌توان از توصیف‌گر font-display استفاده کرد و به مرورگر بگوییم که می‌خواهیم در ابتدا متن را با فونت معمول نمایش دهیم و جایگزینی فونت، بعد از دانلود باشد (بیشتر).
نظرات مطالب
خلاصه اشتراک‌های روز دو شنبه 16 آبان 1390
سلام
من از reportViewer مایکروسافت برای نشون دادن report های خودم استفاده می کنم
وقتی صفحه رو با ie نگاه می کنم مشکلی وجود نداره اما وقتی صفحه رو با ff نگاه می کنم به دلیل اینکه دکمه print یک ActieX هستش و فقط در ie کار می کنه ؛ در ff دکمه print نمایش داده نمیشه
خیلی جستجو کردم تا راه حلی پیدا کنم اما موفق نشدم
لطفا منو در این زمینه راهنمایی کنید
مطالب
کپی کردن ساختار و داده‌های یک جدول از یک دیتابیس به دیتابیسی دیگر
در مواقعی ممکن است نیاز داشته باشیم که جدول یا جدول‌هایی از یک پایگاه داده را به یک پایگاه داده دیگر انتقال دهیم. در این مقاله قصد داریم روند انجام این کار را هم به صورت کوئری و هم به صورت ویزارد(گرافیکی) انجام دهیم.

برای شروع کار ابتدا دو دیتابیس به اسم‌های databasefrm و databaseto می‌سازیم. دیتابیس databasefrm شامل یک جدول به اسم emp با سه فیلد ID,Name,Address می‌باشد. قصد داریم جدول tmp از دیتابیس databasefrm را به دیتابیس databaseto انتقال دهیم. برای انجام این کار، یکی از روش‌های زیر را استفاده خواهیم کرد:

روش 1 : استفاده از کوئری

ساختار کلی انجام این عمل به صورت زیر خواهد بود:
Select * into DestinationDB.dbo.tableName from SourceDB.dbo.SourceTable
مثال :
select * into databaseto.dbo.emp from databasefrm.dbo.Emp
با اجرای دستور فوق یک کپی از جدول emp به همراه تمامی داده‌های آن به دیتابیس databaseto منتقل و ایجاد می‌شوند. اگر بخواهیم تمامی ایندکس‌ها، تریگر‌ها و قید‌ها (Constraint) نیز منتقل شوند، برای اینکار نیاز به تولید یک اسکریپت خواهد بود (در ادامه).

حال اگر بخواهیم یک کپی از  جدول را در دیتابیس جاری ایجاد کنیم، ساختار آن به صورت زیر خواهد بود  :
select * into newtable from SourceTable
که نمونه ای از آن برای دیتابیس ما به صورت زیرخواهد بود :
 select * into  emp1 from emp

می‌توانیم فقط فیلدهایی مشخص را به جدول دیگر کپی کنیم. برای انجا این کار کافیست به جای *  اسم فیلد‌های مورد نیاز را نوشت که ساختار دستوری آن به صورت زیر است :
select col1, col2 into <destination_table> from <source_table>
که برای مثال ما به صورت زیر خواهد بود :
select Id,Name into databaseto.dbo.emp1 from databasefrm.dbo.Emp

بعد از اجرای کوئری فوق نتیجه به صورت زیر خواهد بود :

کد فوق باعث کپی کردن فیلد‌های Id,Name شده است.

اگر بخواهیم فقط ساختار جدول را کپی کنیم روند کار به صورت زیر خواهد بود :

select *into <destination_database.dbo.destination table> from _
<source_database.dbo.source table> where 1 = 2
که نمونه ای از آن برای مثال ما به صورت زیر خواهد بود :
select * into databaseto.dbo.emp from 
databasefrm.dbo.emp where 1 = 2
کاربرد where در دستور فوق برای این است که عنوان فیلد‌ها را بگیریم و در جدول دیگری ذخیره کنیم.

نکته: هر وقت نیاز بود که فقط فیلد‌های یک جدول را دریافت کنید، می‌تواند از کدی همانند فوق استفاده کنید؛ با یک شرط که همیشه false برگرداند. ولی راه بهتری که توصیه میکنم استفاده از Top در دستور  Select می‌باشد. نمونه‌ای از دستور فوق:
select top(0) * into databaseto.dbo.emp from 
databasefrm.dbo.emp
همانطور که مشاهده می‌کنید دیگر در دستور فوق خبری از where نیست.

روش 2: ویزارد

جهت تهیه کارهای فوق به صورت ویزارد، به صورت خلاصه فقط به روند انجام کار بسنده می‌کنیم:
1- SSMS را باز کنید.
2- بر روی دیتابیس مورد نظر کلیک راست کرده و از منوی ظاهر شده Task را انتخاب نموده و در کادر بازشو Export data را انتخاب کنید.
3- در پنجره‌ی ظاهر شده بر روی دکمه next کلیک کرده و در پنجره بعدی، نوع اعتبار سنجی را انتخاب کرده و دیتابیس مورد نظر را انتخاب نمایید (databasefrm).
4- همانند مرحله 3 است با این تفاوت که اینبار دیتابیس مقصد را انتخاب می‌کنیم (databaseto).
5- در پنجره‌ی بعدی گزینه اول را انتخاب کرده (copy data from ...) و بعد از کلیک بر روی next در پنجره ظاهر شده، جدول یا جداول مورد نظر را انتخاب کنید.

روش 3 : تولید اسکریپت

 
با استفاده از دو روش فوق فقط می‌توانستیم ساختار جداول و داده‌های آن را انتقال بدهیم. برای انتقال کامل جداول مثل تریگرها، قیدها و ... می‌بایست از جدول یا جداول اسکریپت تولید و در نهایست اسکریپت را اجرا نماییم.
Right click on datbase >>Task>>Generate script>>next

انتخاب دیتابیس مورد نظر و بعد انتخاب مواردی که قصد داریم از آنها اسکریپت ایجاد کنیم و در پایان اسکریپت مورد نظر را بر روی دیتابیس مقصد (databaseto) اجرا می‌کنیم.

و در پایان نهایت تشکر را از تمام عزیزان و دوستان نویسنده‌ی سایت دارم. امیدوارم در سال 94 شاهد موفقیت‌های خوبی در حوزه‌ی نرم افزار باشیم.
اشتراک‌ها
تهیه Qr-Code
سلام 
یکی از مواردی که به تازگی مرسوم شده است استفاده از QR-Code‌ها است ، گاهی لازم میشود در سایتهایی که به عنوان فروشگاه طراحی میگردند برای کالاها بارکد مشخصی طراحی کرد  و یا گاهی لازم است کاربر را بدون مجبور کردن به تایپ مورد خاص به موقعیت جدید از طریق گوشی موبال هدایت کرد و یا اینکه گاهی لازم میشود نرم افزار و یا فایلی را در گوش و یا browser  شخصی آپلود کنید ، بهترین روش استفاده از این نوع کد خواهد بود .api  اصلی این برنامه در وب سایت گوگل است البته با توجه به عدم دسترسی ساده به این api تعدادی از سایتهای ایرانی و خارجی این سرویس را ارائه میدهند 

http://www.esponce.com/api-help-v1 
با استفاده از ارسال پارامترها میتوانید این عکس و یا همان کد را  تغییر دهید .
http://qr-code.ir/api/ 
این دو سایت امکانت ساده و کاملی در این خصوص ارائه میدهند.
تهیه Qr-Code
مطالب
آشنایی با ساختار IIS قسمت سوم
همانطور که در مطلب قبلی گفتم، در این مطلب قرار است به WAS بپردازیم؛ در دنباله متن قبلی گفتیم که دومین وظیفه WWW Service این است: موقعی‌که یک درخواست جدید در صف درخواست‌ها وارد شد، به اطلاع WAS برساند.

WAS یا Windows Process Activation Service
در نسخه 7 به بعد، WAS مدیریت پیکربندی application pool و پروسه‌های کارگر را به جای WWW Service به عهده گرفته است. این مورد شما را قادر می‌سازد تا همان پیکربندی که برای Http در نظر گرفته‌اید، بر روی درخواست هایی که Http نیستند هم اعمال کنید. همچنین موقعی که سایت شما نیازی به درخواست‌های Http ندارد می‌توانید WAS را بدون WWW Service راه اندازی کنید. به عنوان یک مثال فرض کنید شما یک وب سرویس WCF را از طریق WCF Listener Adapter مدیریت می‌کنید و احتیاجی به درخواست‌های نوع Http listener ندارید و http.sys کاری برای انجام ندارد پس نیازی هم به راه اندازی www service نیست.

پیکربندی مدیریتی در WAS
در زمان شروع کار IIS، سرویس WAS اطلاعاتی را از فایل ApplicationHost.config می‌خواند و آن‌ها را به دست listener adapter‌های مربوطه می‌رساند و lsitener adapter‌ها ارتباط بین WAS و listener‌های مختلف را در IIS، برقرار می‌کنند. آداپتورها اطلاعات لازم را از WAS می‌گیرند و به listener‌های مربوطه انتقال می‌دهند تا listener‌ها بر اساس آن تنظیمات یا پیکربرندی‌ها، به درخواست‌ها گوش فرا دهند.
در مورد WCF ، ابتدا WAS تنظیمات را برای آداپتور WCF که NetTcpActivator نام دارد ارسال کرده و این آداپتور بر اساس آن  listener مربوطه را پیکربندی کرده تا به درخواست هایی که از طریق پروتوکل net.tcp می‌رسد گوش فرا دهد.
لیست زیر تعدادی از اطلاعاتی را که از فایل پیکربندی می‌خواند و ارسال می‌کند را بیان کرده است:
  • Global configuration information 
  • Protocol configuration information for both HTTP and non-HTTP protocols
  • Application pool configuration, such as the process account information 
  • Site configuration, such as bindings and applications 
  • Application configuration, such as the enabled protocols and the application pools to which the applications belong 
نکته پایانی اینکه اگر فایل ApplicationHost.config  تغییری کند، WAS یک اعلان دریافت کرده و اطلاعات آداپتورها را به روز می‌کند.

مدیریت پروسه‌ها Process Managment
گفتیم که مدیریت پول و پروسه‌های کارگر جزء وظایف این سرویس به شمار می‌رود. موقعی که یک protocol listener درخواستی را دریافت می‌کند، WAS چک می‌کند که آیا یک پروسه کارگر در حال اجراست یا خیر. اگر application pool پروسه‌ای داشته باشد که در حال سرویس دهی به درخواست هاست، آداپتور درخواست را به پروسه کارگر ارسال می‌کند. در صورتی که پروسه‌ای در application pool در حال اجرا نباشد، WAS یک پروسه جدید را آغاز می‌کند و آداپتور درخواست را به آن پاس می‌کند.
نکته: از آنجایی که WAS هم پروسه‌های http و هم non-http را مدیریت می‌کند، پس میتوانید از یک applicatio pool برای چندین protocol استفاده کنید. به عنوان مثال شما یکی سرویس XML دارید که می‌توانید از آن برای سرویس دهی به پروتوکل‌های Http و net.tcp بهره بگیرید.

ماژول‌ها در IIS
قبلا مقاله ای در مورد module‌ها با نام "کمی در مورد httpmoduleها" قرار داده بودیم که بهتر است برای آشنایی بیشتر، به آن رجوع کنید. به غیر از وب کانفیگ که برای معرفی ماژول‌ها استفاده می‌کردیم ، میتوانید به صورت گرافیکی و دستی هم این کار را انجام بدهید. ابتدا یک پروژه class library ایجاد کرده و ماژول خود را بنویسید و سپس آن را به یک dll تبدیل کنید و dll را در شاخه bin که این شاخه در ریشه وب سایتتان قرار دارد کپی کنید. سپس در IIS قسمت module گزینه Add را انتخاب کنید و در قسمت اول نامی برای آن و در قسمت بعدی دقیقا همان قوانین type که در وب کانفیگ مشخص می‌کردید را مشخص کنید: Namespace.ClassName
گزینه invoke only for requests to asp.net and manage handlers را هم تیک بزنید. کار تمام است.

ماژول‌های کد ماشین یا  native
این ماژول‌ها به صورت پیش فرض به سیستم اضافه شده‌اند و در صورتی که میخواهید جایگزینی به منظور خصوصی سازی انجام دهید آن‌ها را پاک کنید و ماژول جدید را اضافه کنید.

جدول ماژول‌های HTTP
نام ماژول
توضیحات
نام فایل منبع
CustomErrorModule  موقعی که هنگام response، کد خطایی تولید می‌گردد، پیام خطا را پیکربندی و سپس ارسال می‌کند.  Inetsrv\Custerr.dll 
 HttpRedirectionModule   تنظمیات redirection برای درخواست‌های http را در دسترس قرار می‌دهد.  Inetsrv\Redirect.dll 
 ProtocolSupportModule   انجام عملیات مربوط به پروتوکل‌ها بر عهده این ماژول است؛ مثل تنظیم کردن قسمت هدر برای response.  Inetsrv\Protsup.dll 
 RequestFilteringModule   این ماژول از IIS 7.5 به بعد اضافه شد. درخواست‌ها را فیلتر می‌کند تا پروتوکل و رفتار محتوا را کنترل کند.  Inetsrv\modrqflt.dll 
 WebDAVModule   این ماژول از IIS 7.5 به بعد اضافه شد. امنیت بیشتر در هنگام انتشار محتوا روی HTTP SSL  Inetsrv\WebDAV.dll 

ماژول‌های امنیتی
نام ماژول  توضیحات  نام فایل منبع 
 AnonymousAuthenticationModule  موقعی که هیچ کدام از عملیات authentication  با موفقیت روبرو نشود، عملیات  Anonymous authentication انجام می‌شود.  Inetsrv\Authanon.dll 
 BasicAuthenticationModule   عمل ساده و اساسی authentication  را انجام می‌دهد.  Inetsrv\Authbas.dll 
 CertificateMappingAuthenticationModule   انجام عمل Certificate Mapping authentication  در Active Directory  Inetsrv\Authcert.dll
 
 DigestAuthenticationModule   Digest authentication   Inetsrv\Authmd5.dll 
 IISCertificateMappingAuthenticationModule  همان Certificate Mapping authentication  ولی اینبار با IIS Certificate .  Inetsrv\Authmap.dll 
 RequestFilteringModule   عملیات اسکن URL از قبیل نام صفحات و دایرکتوری‌ها ، توع verb و یا کاراکترهای مشکوک و خطرآفرین  Inetsrv\Modrqflt.dll 
 UrlAuthorizationModule   عمل URL authorization   Inetsrv\Urlauthz.dll 
 WindowsAuthenticationModule   عمل NTLM integrated authentication   Inetsrv\Authsspi.dll 
 IpRestrictionModule   محدود کردن IP‌های نسخه 4 لیست شده در IP Security در قسمت پیکربندی  Inetsrv\iprestr.dll 
 

ماژول‌های محتوا
 نام ماژول  توضیحات نام فایل منبع
 CgiModule   ایجاد پردازش‌های (Common Gateway Interface (CGI به منظور ایجاد خروجی response  Inetsrv\Cgi.dll 
 DefaultDocumentModule   تلاش برای ساخت یک سند پیش فرض برای درخواست هایی که دایرکتوری والد ارسال می‌شود  Inetsrv\Defdoc.dll 
 DirectoryListingModule   لیست کردن محتوای یک دایرکتوری  Inetsrv\dirlist.dll 
 IsapiModule   میزبانی فایل های ISAPI Inetsrv\Isapi.dll
 IsapiFilterModule   پشتیبانی از فیلتر های ISAPI  Inetsrv\Filter.dll 
 ServerSideIncludeModule   پردازش کدهای include شده سمت سرور  Inetsrv\Iis_ssi.dll 
 StaticFileModule   ارائه فایل‌های ایستا  Inetsrv\Static.dll 
 FastCgiModule   پشتبانی از CGI  Inetsrv\iisfcgi.dll 

ماژول‌های فشرده سازی
 DynamicCompressionModule  فشرده سازی پاسخ response با gzip  Inetsrv\Compdyn.dll   
 StaticCompressionModule   فشرده سازی محتوای ایستا  Inetsrv\Compstat.dll 

ماژول‌های کش کردن
 FileCacheModule  تهیه کش در مد کاربری برای فایل‌ها.    Inetsrv\Cachfile.dll 
 HTTPCacheModule   تهیه کش مد کاربری و مد کرنل برای http.sys  Inetsrv\Cachhttp.dll 
 TokenCacheModule   تهیه کش مد کاربری بر اساس جفت نام کاربری و یک token که توسط  Windows user principals تولید شده است.   Inetsrv\Cachtokn.dll 
 UriCacheModule   تهیه یک کش مد کاربری از اطلاعات URL  Inetsrv\Cachuri.dll 

ماژول‌های عیب یابی و لاگ کردن
 CustomLoggingModule  بارگزاری ماژول‌های خصوصی سازی شده جهت لاگ کردن  Inetsrv\Logcust.dll
 FailedRequestsTracingModule   برای ردیابی درخواست‌های ناموفق  Inetsrv\Iisfreb.dll 
 HttpLoggingModule   دریافت اطلاعات  و پردازش وضعیت http.sys برای لاگ کردن  Inetsrv\Loghttp.dll 
 RequestMonitorModule   ردیابی درخواست هایی که در حال حاضر در پروسه‌های کارگر در حال اجرا هستند و گزارش اطلاعاتی در مورد وضعیت اجرا و کنترل رابط برنامه نویسی کاربردی.  Inetsrv\Iisreqs.dll 
 TracingModule   گزارش رخدادهای Microsoft Event Tracing for Windows یا به اختصار ETW  Inetsrv\Iisetw.dll 

ماژول‌های مدیریتی و نظارتی بر کل ماژول‌ها
 ManagedEngine   مدیرتی بر ماژول‌های غیر native که در پایین قرار دارند. Microsoft.NET\Framework\v2.0.50727\webengine.dll
 ConfigurationValidationModule  اعتبارسنجی خطاها، مثل موقعی که برنامه در حالت integrated اجرا شده و ماژول‌ها یا هندلرها در system.web تعریف شده‌اند. Inetsrv\validcfg.dll 
از IIS6 به بعد در حالت integrated و ماقبل، در حالت کلاسیک می‌باشند. اگر مقاله ماژول ها را خوانده باشید می‌دانید که تعریف آن‌ها در وب کانفیگ در بین این دو نسخه متفاوت هست و رویداد سطر آخر در جدول بالا این موقعیت را چک می‌کند و اگر به خاطر داشته باشید با اضافه کردن یک خط اعتبارسنجی آن را قطع می‌کردیم. در مورد هندلرها هم به همین صورت می‌باشد.
به علاوه ماژول‌های native بالا، IIS این امکان را فراهم می‌آورند تا از ماژول‌های کد مدیریت شده (یعنی CLR) برای توسعه توابع و کارکرد IIS بهره مند شوید:
 ماژول توضیحات   منبع
 AnonymousIdentification   مدیریت منابع تعیین هویت برای کاربران ناشناس مانند asp.net profile System.Web.Security.AnonymousIdentificationModule  
 DefaultAuthentication   اطمینان از وجود شی Authentication در context مربوطه  System.Web.Security.DefaultAuthenticationModule 
 FileAuthorization   تایید هویت کاربر برای دسترسی به فایل درخواست  System.Web.Security.FileAuthorizationModule 
 FormsAuthentication   با این قسمت که باید کاملا آشنا باشید؛ برای تایید هویت کاربر  System.Web.Security.FormsAuthenticationModule 
OutputCache  مدیریت کش  System.Web.Caching.OutputCacheModule 
 Profile   مدیریت پروفایل کاربران که تنظیماتش را در یک منبع داده‌ای چون دیتابیس ذخیره و بازیابی می‌کند.  System.Web.Profile.ProfileModule 
 RoleManager   مدیریت نقش و سمت کاربران  System.Web.Security.RoleManagerModule 
 Session   مدیریت session ها  System.Web.SessionState.SessionStateModule 
 UrlAuthorization   آیا کاربر جاری حق دسترسی به URL درخواست را دارد؟  System.Web.Security.UrlAuthorizationModule 
 UrlMappingsModule   تبدیل یک Url واقعی به یک Url کاربرپسند  System.Web.UrlMappingsModule 
 WindowsAuthentication  شناسایی و تایید و هویت یک کاربر بر اساس لاگین او به ویندوز   System.Web.Security.WindowsAuthenticationModule 
مطالب
اعتبارسنجی در Angular 2 توسط JWT
در مقالاتی که در سایت منتشر شده‌است، آشنایی و همچنین نحوه پیاده سازی Json Web Token را فرا گرفتیم. در اینجا میخواهیم با استفاده از توکن تولید شده، برنامه‌های Angular2 یا هر نوع فریمورک spa را با آن ارتباط دهیم. در سایت جاری قبلا در مورد نحوه پیاده سازی آن صحبت شده‌است و میخواهیم از آن در یک پروژه Angular 2 صحبت کنیم.
پروژه دات نت را از طریق این آدرس دریافت کرده  و آن را در حالت اجرا بگذارید.

سپس یک سرویس جدید را در پروژه Angular خود اجرا کنید و متد زیر را به آن اضافه کنید:
login():any{
    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('username', 'Vahid');
    urlSearchParams.append('password', '1234');
    urlSearchParams.append('grant_type', 'password');
    let body = urlSearchParams.toString();

    let headers = new Headers();
    headers.append('Content-Type', 'application/x-www-form-urlencoded');
    return this._http.post('http://localhost:9577/login', body, { headers: headers })
        .map(res => res.json());
}
در متد بالا ابتدا از کلاس  URLSearchParams  یک شیء میسازیم. این کلاس در ماژول Http قرار دارد و وظیفه آن تبدیل پارامترهای داده شده به شکل زیر میباشد:
username=vahid&password=1234
دلیل اینکه ما در اینجا همانند jquery از JSON.stringify استفاده نکردیم این است که در حالتیکه خصوصیت Content-Type هدر را بر روی application/x-wwww-form-urlencoded قرار میدهیم، شیء ایجاد شده از کلاس Http در اینجا، کل عبارت را تبدیل به کلید بدون مقدار میکند و باعث میشود که پارامترها به درستی به سمت Owin هدایت نشوند. بعد از آن هدری که ذکر شد را در درخواست قرار داده و آن را ارسال میکنیم.
از آنجاکه پروژه انگیولار ساخته شده در آدرسی دیگر و جدا از پروژه‌ی دات نت قرار دارد و همینطور که می‌بینید آدرس کامل آن را به این دلیل قرار دادم، ممکن است خطای CORS را دریافت کنید که میتوانید آن را با نصب یک بسته نیوگتی حل کنید.
 
همچنین برای تست و انجام یک عمل مرتبط با توکن، متد زیر را هم به آن اضافه می‌کنیم:
ApiAdmin(token:any):any{
    let headers = new Headers();
    headers.append('Authorization', 'bearer ' + token);
    return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers })
        .map(res => res.json());
}
در اینجا با استفاده از روش Http Bearer که در اعتبارسنجی در سطح OAuth کاربرد زیادی دارد، توکن تولید شده را که در پارامتر ورودی متد دریافت کرده‌ایم، به هدر اضافه کرده و آن را ارسال میکنیم.

کد کل سرویس،  الان به شکل زیر شده است:
import { Http, Headers, URLSearchParams } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import "rxjs/Rx";

@Injectable()
export class MemberShipService {
    constructor(private _http: Http) { }

    ApiAdmin(token: any): any {
        let headers = new Headers();
        headers.append('Authorization', 'bearer ' + token);
        return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers })
            .map(res => res.json());
    }

    login(): any {
        let urlSearchParams = new URLSearchParams();
        urlSearchParams.append('username', 'Vahid');
        urlSearchParams.append('password', '1234');
        urlSearchParams.append('grant_type', 'password');
        let body = urlSearchParams.toString();

        let headers = new Headers();
        headers.append('Content-Type', 'application/x-www-form-urlencoded');
        return this._http.post('http://localhost:9577/login', body, { headers: headers })
            .map(res => res.json());
    }
}
سپس سرویس ساخته شده را در ngModule معرفی میکنیم. در کامپوننت هدف دو دکمه را قرار میدهیم؛ یکی برای لاگین و دیگری برای دریافت اطلاعاتی که نیاز به اعتبار سنجی دارد. رویداد کلیک دکمه‌ها را به متدهای زیر متصل میکنیم:
Login(){
    this._service.login()
        .subscribe(res => {
            localStorage.setItem('access_token', res.access_token);
            localStorage.setItem('refresh_token', res.refresh_token);
        }
        , error => console.log(error));
}
در اینجا ما اطلاعات لاگین را به سمت سرور فرستاده و در صورتیکه پاسخ صحیحی را دریافت کردیم، متد Subscribe اجرا خواهد شد و مقادیر دریافتی را باید به نحوی در سیستم و بین کامپوننت‌های مختلف، ذخیره و نگهداری کنیم. در اینجا من از روش Local Storage که در سایت جاری قبلا به آن پرداخته شده‌است، استفاده میکنم. access_token و refresh_token جاری و اطلاعات دیگری را چون رول‌ها و ... هر یک را با یک کلید ذخیره میکنیم تا بعدا به آن دسترسی داشته باشیم.
در متد بعدی که قرار است توسط آن صحت اعتبارسنجی مورد آزامایش قرار بگیرد، کدهای زیر را مینویسیم:
CallApi()
{
    let item = localStorage.getItem("access_token");
    if (item == null) {
        alert('please Login First.');
        return;
    }
    this._service.ApiAdmin(item)
        .subscribe(res => {
            alert(res);
        }
        , error => console.log(error));
}
در اینجا ابتدا توکن ذخیره شده را از Local Storage درخواست میکنیم. اگر نتیجه این درخواست نال باشد، به این معنی است که کاربر قبلا لاگین نکرده است؛ در غیر این صورت درخواست را با توکن دریافتی میفرستیم و پاسخ موفق آن را در یک alert می‌بینیم. در صورتیکه توکن ما اعتبار نداشته باشد، خطای بازگشتی در کنسول نمایش خواهد یافت.


اعتبارسنجی در مسیریابی


یکی از روش‌هایی که انگیولار باید بررسی کند این است که کاربر جاری با توجه به نقش‌هایی که ممکن است داشته باشد، یا اعتبار سنجی شده است یا خیر و میزان دسترسی او به کامپوننت‌ها، باید مشخص گردد. برای این مورد خصوصیتی به مسیریابی اضافه شده است به نام CanActivate که از شما یک کلاس را که در آن اینترفیس CanActivate پیاده سازی شده است، درخواست میکند. در اینجا ما یک Guard را با نام LoginGuard ایجاد میکنیم، تا تنها کاربرانی که لاگین کرده‌اند، به این صفحه دسترسی داشته باشند:
import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class LoginGuard implements CanActivate {
    constructor() { }

    canActivate() {
        let item = localStorage.getItem('access_token');
        if (item == null)
            return false;
        return true;
    }
}
در متد Activate باید خروجی boolean بازگردد. در صورتیکه true بازگشت داده شود، عملیات هدایت به کامپوننت مقصد با موفقیت انجام خواهد شد. در صورتیکه خلاف این موضوع اتفاق بیفتد، کامپوننت هدف نمایش داده نمیشود. در اینجا بررسی کرده‌ایم که اگر توکن مورد نظر در localStorage  قرار داشت، به معنی این است که لاگین شده‌است. ولی این موضوع روشن است که در یک مثال واقعی باید صحت این توکن بررسی شود. این Guard در واقع یک سرویس است که باید در فایل ngModule معرفی شده و آن را در فایل مسیریابی به شکل زیر استفاده کنیم:
 { path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard]}
در معرفی یک مسیر به فایل مسیریابی، خصوصیاتی چون مسیری که نوشته میشود و کامپوننت مربوط به آن مسیر ذکر می‌شود. خصوصیت دیگر، CanActivate است که به آن کلاس LoginGuard را معرفی مکنیم. در اینجا شما میتواند به هر تعداد گارد را که دوست دارید، معرفی کنید ولی به یاد داشته باشید که اگر یکی از آن‌ها در اعتبارسنجی ناموفق باشد، دیگر کامپوننت جاری در دسترس نخواهد بود. به معنای دیگر تمامی گاردها باید نتیجه‌ی true را بازگردانند تا دسترسی به کامپوننت هدف ممکن شود.
 { path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard]}

در یک گارد ممکن است کاربر لاگین نکرده باشد و شما نیاز دارید او را به صفحه لاگین هدایت کنید. در این صورت گارد لاگین را به شکل زیر بازنویسی میکنیم:
import { Router } from '@angular/router';
import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class LoginGuard implements CanActivate {

    constructor(public router: Router) { }

    canActivate() {
        let item = localStorage.getItem('access_token');
        if (item == null) {
            this.router.navigate(['/app']);
            return false;
        }
        return true;
    }
}
در اینجا ما از سازنده کلاس، شیءایی از نوع Router را ساختیم که امکانات و متدهای خوبی را در باب مسیریابی به همراه دارد و از آن برای انتقال به مسیری دیگر که میتواند صفحه لاگین باشد، استفاده کردیم.

همچنین گارد میتواند اطلاعات مسیر درخواست شده را خوانده و بر اساس آن به شما پاسخ بدهد. به عنوان مثال پارامترهایی را که به سمت مسیر مورد نظر هدایت میشود، بخواند و بر اساس آن، تصمیم گیری کند. به عنوان نمونه کاربر به مسیر ذکرشده دسترسی دارد، ولی با Id که در مسیر ارسال کرده است، دسترسی ندارد:
import { Router } from '@angular/router';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class SecondGuard implements CanActivate {

    constructor(public router: Router) { }

    canActivate(route: ActivatedRouteSnapshot) {
        let id = route.params['id'];
        if (id == 1) {
            return false;
        }
        return true;
    }
}

متد CanActivate میتواند پارامترهای زیادی را به عنوان ورودی دریافت کند که یکی از آن‌ها ActivatedRouteSnapshot است که اطلاعات خوب و مفیدی را در مورد مسیر ارسال شده از طرف کاربر دارد و با استفاده از آن در اینجا میتوانیم پارامترهای ارسالی را دریافت کنیم. در اینجا ذکر کرده‌ایم که اگر پارامتر Id که بر مبنای مسیر زیر است، برابر 1 بود، مقدار برگشتی برابر false خواهد بود و دسترسی به کامپوننت در اینجا ممکن نخواهد بود.
 { path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard] }
اشتراک‌ها
ایجاد فونتهای تحت وب با استفاده از icon

برای اینکه بتوانید از فونت‌های وبی بجای icon‌های طراحی شده استفاده کنید میتوانید آنها را با استفاده از این وب سایت تبدیل نمائید . استفاده از فونت بجای تصویر در وب مزیتهای بسیاری را بدنبال دارد.

ایجاد فونتهای تحت وب با استفاده از icon
نظرات مطالب
گروه بندی اطلاعات و گزارشات Master-Details در PdfReport

سلام. 

من یک View در SQL دارم که چند جدول مختلف را با هم Join میزند. 

رکوردها باید با فیلد ReceptionPatientId گروه بندی شوند.

کد برنامه:

 return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata
                {
                    Author = username,
                    Application = "",
                    Keywords = "Executive Unit Work List Report",
                    Subject = "Executive Unit Work List Report",
                    Title = "Executive Unit Work List Report"
                });
            })
           .DefaultFonts(fonts => fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                             Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"))
           .PagesFooter(footer => footer.DefaultFooter(DateTime.Now.ToLongPersianDate()))
           .PagesHeader(header =>
           {
               header.CustomHeader(new UnitWorkListHeader { PdfRptFont = header.PdfFont });
           })
           .MainTableTemplate(template => template.BasicTemplate(BasicTemplate.RainyDayTemplate))
           .MainTablePreferences(table =>
           {
               table.ColumnsWidthsType(TableColumnWidthType.Relative);
               table.GroupsPreferences(new GroupsPreferences
               {
                   GroupType = GroupType.HideGroupingColumns,
                   RepeatHeaderRowPerGroup = true,
                   ShowOneGroupPerPage = false,
                   SpacingBeforeAllGroupsSummary = 5f,
                   NewGroupAvailableSpacingThreshold = 170
               });
           })
           .MainTableDataSource(dataSource => 
               dataSource.DataTable(new ReceptionPatientBl(context).
               GetReceptionSummaryView(_rcpIds).
               ToDataSet(false).Tables[0]))
           .MainTableColumns(columns =>
           {
               columns.AddColumn(column =>
               {
                   column.PropertyName("rowNo");
                   column.IsRowNumber(true);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                   column.IsVisible(true);
                   column.Order(0);
                   column.Width(1);
                   column.HeaderCell("#");
               });
               columns.AddColumn(column =>
               {
                   column.PropertyName("ReceptionPatientId");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("ReceptionPatientId");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("ReceptionDate");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("ReceptionDate");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("IsDelete");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("IsDelete");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Fname");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Fname");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Lname");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Lname");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Sex");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Sex");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("BirthDate");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("BirthDate");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("Mobile");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("Mobile");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1 == val2;
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("LabId");
                   column.IsRowNumber(false);
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(3);
                   column.Width(2);
                   column.HeaderCell("LabId");
                   column.Group(true,
                   (val1, val2) =>
                   {
                       return val1.ToString() == val2.ToString();
                   });
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("TestName");
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(4);
                   column.Width(2);
                   column.HeaderCell("نام تست");
                   column.IsVisible(true);
               });

               columns.AddColumn(column =>
               {
                   column.PropertyName("TestShortName");
                   column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                   column.Order(5);
                   column.Width(2);
                   column.HeaderCell("نام اختصاری تست");
                   column.IsVisible(true);
               });

           })
           .MainTableEvents(events => events.DataSourceIsEmpty(message: "There is no data available to display."))
           //.Export(export =>
           //{
           //    export.ToExcel();
           //})
           .Generate(data =>
           {
               var fileName = "ExecutiveWorkListReport.pdf";
               fileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
               data.FlushInBrowser(fileName);
           }); 
        }

اما خروجی به صورت زیر است:

همانطور که مشاهده می‌کنید بدون توجه به فیلد ID سه مرتبه این گروه تکرار شده است.

ممکن است راهنمایی فرمایید مشکل کار کجاست؟