ارسال ایمیل در ASP.NET Core
مطلبی را روز قبل نوشتم در مورد تعیین اعتبار یک کوئری. این مورد از آنجایی حائز اهمیت میشود که برای مثال تغییری در ساختار یکی از جداول حاصل شود. اکنون میخواهیم بررسی کنیم آیا سیستم از کار افتاده یا نه!؟
شما میتوانید نام یک فیلد را تغییر دهید (حتی اگر این فیلد در یک رویه ذخیره شده استفاده شده باشد) و هیچ خطایی هم نخواهید گرفت و این منشاء دردسرهای زیادی خواهد بود.
در حالت استفاده از SET NOEXEC ON ، کوئری مورد نظر فقط کامپایل میشود و همچنین از لحاظ نحوی بررسی خواهد شد، اما این کافی نیست.
مثال زیر را در نظر بگیرید:
Create PROCEDURE Test1
AS
SELECT * FROM tblPIDs1
این کوئری قابل اجرا است. دکمهی F5 را فشار دهید، بلافاصله رویه ذخیره شدهی Test1 برای شما ایجاد خواهد شد.
سپس کوئری زیر را اجرا کنید:
USE testdb
SET NOEXEC ON;
exec test1 ;
SET NOEXEC OFF;
ایرادی هم وارد نیست چون فقط عملیات parsing و compile صورت گرفته و نه اجرای واقعی رویه ذخیره شده. اینجا از لحاظ دستوری مشکلی وجود ندارد.
در این نوع موارد میتوان از SET FMTONLY ON استفاده کرد. این مورد اجرای غیر واقعی یک کوئری را سبب میشود (تاثیری روی دیتابیس موجود نخواهد داشت، برای مثال اگر در رویه ذخیره شما عبارت insert وجود داشت، دیتایی insert نخواهد شد) و تنها متادیتای حاصل را بازگشت میدهد. مثلا نام ستونهای یک کوئری را و همچنین در این حین اگر خطایی رخ داده باشد، آنرا نیز ارائه خواهد داد.
USE testdb
SET FMTONLY ON;
exec test1 ;
SET FMTONLY OFF;
Msg 208, Level 16, State 1, Procedure test1, Line 3
Invalid object name 'tblPIDs1'.
USE testdb;
SET NOCOUNT ON;
DECLARE @name NVARCHAR(MAX),
@sql NVARCHAR(MAX),
@type CHAR(2), -- object type
@type_desc NVARCHAR(60), -- object type description
@params NVARCHAR(MAX) -- parameters
DECLARE @tblInvalid TABLE (
-- invalid objects
[type_desc] NVARCHAR(60),
[name] NVARCHAR(MAX),
[error_number] INT,
[error_message] NVARCHAR(MAX),
[type] CHAR(2)
);
DECLARE testSPs CURSOR FAST_FORWARD
FOR
SELECT [name] = OBJECT_NAME(SM.[object_id]),
[type] = SO.[type],
SO.[type_desc],
[params] = (
SELECT (
SELECT CONVERT(
XML,
(
SELECT STUFF(
(
SELECT ', ' + [name] +
'=NULL' AS
[text()]
FROM sys.parameters
WHERE [object_id] = SM.[object_id]
FOR XML PATH('')
),
1,
1,
''
)
)
)
FOR XML RAW,
TYPE
).value('/row[1]', 'varchar(max)')
)
FROM sys.sql_modules SM
JOIN sys.objects SO
ON SO.[object_id] = SM.[object_id]
WHERE SO.[is_ms_shipped] = 0
AND SO.[type] = 'P'
OPEN testSPs
FETCH NEXT FROM testSPs INTO @name, @type, @type_desc, @params
WHILE (@@FETCH_STATUS = 0)
BEGIN
BEGIN TRY
SET @sql = 'SET FMTONLY ON; exec ' + @name + ' ' + @params +
'; SET FMTONLY OFF;'
--PRINT @sql;
EXEC (@sql) ;
END TRY
BEGIN CATCH
PRINT @type_desc + ', ' + @name + ', Error: ' + CAST(ERROR_NUMBER() AS VARCHAR)
+ ', ' + ERROR_MESSAGE();
INSERT INTO @tblInvalid
SELECT @type_desc,
@name,
ERROR_NUMBER(),
ERROR_MESSAGE(),
@type
;
END CATCH
FETCH NEXT FROM testSPs INTO @name, @type, @type_desc, @params
END
CLOSE testSPs
DEALLOCATE testSPs
SELECT [type_desc],
[name],
[error_number],
[error_message]
FROM @tblInvalid
ORDER BY
CHARINDEX([type], ' U V PK UQ F TR FN TF P SQ '),
[name];
توضیحات:
این کوئری، در دیتابیس جاری که در قسمت use dbname مشخص میشود، تمامی رویههای ذخیره شده را به صورت خودکار پیدا میکند. سپس لیست آرگومانهای آنها را نیز یافته و عبارت exec مربوطه را تشکیل میدهد. سپس با استفاده از SET FMTONLY ON سعی در شبیه سازی اجرای تک تک رویههای ذخیره شده میکند. اگر خطایی در این بین رخ داد، آنها را در یک جدول موقتی ذخیره کرده و در آخر نتیجه را نمایش میدهد.
ارزش این کوئری زمانی مشخص میشود که تعداد زیادی رویه ذخیره شده داشته باشید اما نمیدانید کدامیک از آنها بر اساس آخرین تغییرات صورت گرفته، هنوز معتبر هستند یا نه. آیا به قول معروف، سیستم اومد پایین یا خیر!؟
نکته:
قسمتی که از XML استفاده شده جهت concatenating نتیجه حاصل از کوئری، مورد استفاده قرار گرفته و این روزها بحث رایجی است که در بسیاری از سایتها در مورد آن میتوان مطالب مفیدی را یافت. راه دیگر انجام آن استفاده از COALESCE میباشد.
مآخذ:
Check Validity of SQL Server Stored Procedures
Which of your Stored Procedures are no longer Valid
SET FMTONLY ON
@"<script>alert('xss')</script><div onload=""alert('xss')""" + @"style=""background-color: rgba(0, 0, 0, 1)"">Test<img src=""test.png""" + @"style=""background-image: url(javascript:alert('xss')); margin: 10px""></div>";
رسم نمودار توسط Kendo Chart
<div id="chart"></div> <script> $("#chart").kendoChart({ pdf: { fileName: "Products.pdf" }, legend: { position: "bottom" }, series: [ { name: "Series 1", data: [1, 2, 3] }, { name: "Series 2", data: [3, 4, 5] } ] }); var chart = $("#chart").getKendoChart(); chart.saveAsPDF(); </script>
ASP.NET MVC #21
@model MvcApplication1.Models.Com <script> var onSuccess = function (result) { $.validator.unobtrusive.parse("#result"); }; </script> <div id="result" > @using (Ajax.BeginForm( "SendCommentAction", "Home", null, new AjaxOptions { OnSuccess = "onSuccess", UpdateTargetId = "result" }, null) ) { @Html.TextBoxFor(x=>x.Contect) <br/> @Html.ValidationMessageFor(x=>x.Contect) <br/> @Html.ValidationSummary() <input type="submit" value="Save" /> } </div>
بررسی OLAP
واژه OLAP در اوایل سالهای 1990 شکل گرفت. E.F.Codd بنیانگذار مدل دادهی رابطهای، این واژه را در فرهنگ نامه کاربران بانکهای اطلاعاتی توصیف نمود.مشابه یک بانک اطلاعاتی رابطهای که شامل تعدادی جدول میباشد، یک بانک اطلاعاتی OLAP شامل تعدادی Cube است. هر Cube مجموعه ای از Dimensionها و Measure هاست. Dimension یک شیء تحلیلی است که محورهای مختصات را برای پرسشهای تحلیلی تعریف میکند و از Member هایی تشکیل شده است که Member هر Dimension در قالب سلسله مراتب میتواند تعریف شود؛ در حالیکه Measure یک مقدار عددی است که در مختصات Cube تعریف میشود که این مقادیر از جداول تراکنشی بدست میآید (جدول Fact) که جزئیات هر رکورد تراکنشی در آنها ذخیره میشود. Measureها حاوی اطلاعاتی هستند که از پیش، محاسبات تجمیعی بر روی آنها براساس سلسله مراتب تعریف شده در Dimension انجام شده است.
ساختار OLAP شبیه به یک مکعب روبیک از دادهها است که میتوان آنرا در جهات مختلف چرخانید تا بتوان سناریوهای «قبلا چه شده» و «چه میشد اگر ...» را بررسی نمود. مدل چند بعدی OLAP طریقه نمایش دادن دادهها را در مقایسه با بانکهای اطلاعاتی رابطهای تسهیل میکند. غالبا OLAP دادهها را از یک انباره داده استخراج میکند.
ابزارهای OLAP را به چند دسته تقسیم میکنند:
OLAP رو میزی:
ابزارهای ساده و مستقل که روی کامپیوترهای شخصی نصب شده و مکعبهای کوچکی میسازند و آنها را نیز بر روی سیستم به شکل فایل ذخیره میکنند. بیشتر این ابزارها با صفحات گسترده ای نظیر Excel کار میکنند. به این ترتیب کسانی که در سفر هستند قادر به استفاده از این دسته از محصولات هستند. (در حال حاضر Web OLAP در حال جایگزین کردن این محصولات است)MOLAP:
بجای ذخیره کردن اطلاعات در رکوردهای کلید دار، این دسته از ابزارها، بانکهای اطلاعاتی خاصی را برای خود طراحی کردهاند؛ بطوری که دادهها را به شکل آرایههای مرتب شده بر اساس ابعاد داده ذخیره میکنند. در حال حاضر نیز دو استاندارد برای این نوع ابزار وجود دارد. سرعت این ابزار بالا و سایز بانک اطلاعاتی آن نسبتا کوچک است.ROLAP:
این ابزارها با ایجاد یک بستر روی بانکهای رابطهای اطلاعات را ذخیره و بازیابی میکنند. بطوری که اساس بهینه سازی برخی بانکهای مانند Red Brick ،MicreoStrategy و ... بر همین اساس استوار است. اندازه بانک اطلاعاتی این ابزار قابل توجه میباشد.HOLAP:
در اینجا منظور از hybrid ترکیبی از MOLAP و ROLAP است. ابزار دارای بانک اطلاعاتی بزرگ و راندمان بالاتر نسبت به ROLAP میباشد.مقایسه گزینههای ذخیره سازی در OLAP:
MOLAP:
این نوع ذخیرهسازی بیشترین کاربرد در ذخیره اطلاعات را دارد. همچنین به صورت پیش فرض جهت ذخیرهسازی اطلاعات انتخاب شده است. در این نوع تنها زمانی دادههای منتقل شده به Cube به روز میشوند که Cube پردازش شود و این امر باعث تاخیر بالا در پردازش و انتقال دادهها میشود.ROLAP:
در ذخیرهسازی ROLAP زمان انتقال بالا نیست که از مزایای این نوع ذخیرهسازی نسبت به MOLAP است. در ROLAP اطلاعات و پیشمحاسبهها در یک حالت رابطهای ذخیره میشوند و این به معنای زمان انتقال نزدیک به صفر میان منبع داده (بانک اطلاعاتی رابطهای) و Cube میباشد. از معایب این روش میتوان به کارایی پایین آن اشاره کرد زیرا زمان پاسخ برای پرسوجوهای اجرا شده توسط کاربران طولانی است. دلیل این کارایی پایین بکار نبردن تکنیکهای ذخیرهسازی چند بعدی است.HOLAP:
این نوع ذخیرهسازی چیزی مابین دو حالت قبلی است. ذخیره اطلاعات با روش ROLAP انجام میشود، بنابراین زمان انتقال تقزیبا صفر است. از طرفی برای بالابردن کارایی، پیشمحاسبهها به صورت MOLAP انجام میگیرد در این حالت SSAS آماده است تا تغییری در اطلاعات مبداء رخ دهد و زمانی که تغییرات را ثبت کرد نوبت به پردازش مجدد پیشمحاسبهها میشود. با این نوع ذخیرهسازی زمان انتقال دادهها به Cube را نزدیک به صفر و زمان پاسخ برای اجرای کوئریهای کاربر را زمانی بین نوع ROLAP و MOLAP میرسانیم.این سه روش ذخیرهسازی انعطافپذیری مورد نیاز را برای اجرای پروژه فراهم میکند. انتخاب هر یک از این روشها به نوع پروژه، حجم دادهها و ... بستگی دارد. در پایان میتوان نتیجه گرفت که بهتر است زمان پردازش طولانیتری داشته باشیم تا اینکه کاربر نهایی در هنگام ایجاد گزارشات زمان زیادی را منتظر بماند.
بررسی داده کاوی
حجم زیاد اطلاعات، مدیران مجموعهها را در تحلیل و یافتن اطلاعات مفید دچار چالش کرده است. داده کاوی، ابزار مناسب برای تجزیه و تحلیل اطلاعات و کشف و استخراج روابط پنهان در مجموعههای دادهای سنگین را فراهم میکند. گروه مشاورهای گارتنر داده کاوی را استخراج نیمه اتوماتیک الگوها، تغییرات، وابستگیها، نابهنجاریها و دیگر ساختارهای معنی دار آماری از پایگاههای بزرگ داده تعریف میکند. داده کاوی، تلاشی برای یافتن قوانین، الگوها و یا میل احتمالی داده به مُدلی، در بین انبوهی از دادهها است.
داده کاوی فرآیندی پیچیده جهت شناسایی الگوها و مدلهای صحیح، جدید و به صورت بالقوه مفید، در حجم وسیعی از داده میباشد؛ به طریقی که این الگوها و مدلها برای انسانها قابل درک باشند. داده کاوی به صورت یک محصول قابل خریداری نمیباشد، بلکه یک رشته علمی و فرآیندی است که بایستی به صورت یک پروژه پیاده سازی شود.
به بیانی دیگر داده کاوی، فرآیند کشف الگوهای پنهان، جالب توجه، غیر منتظره و با ارزش از داخل مجموعه وسیعی از دادههاست و فعالیتی در ارتباط با تحلیل دقیق دادههای سنگین بی ساختار است که علم آمار ناتوان از تحلیل آنهاست. بعضی مواقع دانش کشف شده توسط داده کاوی عجیب به نظر میرسد؛ مثلا ارتباط افراد دارای کارت اعتباری و جنسیت با داشتن دفترچه تامین اجتماعی یا سن، جنسیت و درآمد اشخاص با پیش بینی خوش حسابی او در بازپرداخت اقساط وام. داده کاوی در حوزههای تصمیم گیری، پیش بینی، و تخمین مورد استفاده قرار میگیرد.
پایه و اساس این تکنیک، ریشه در علوم زیر دارد:
- علم آمار و احتمال
- کامپیوتر (تکنولوژی اطلاعات)
- هوش مصنوعی (تکنیکهای یادگیری ماشین)
ارتباط داده کاوی و OLAP
OLAP و داده کاوی فن آوریهای تحلیلی در خانواده BI به شمار میآیند. OLAP در زمینه تجمیع مقادیر عظیم دادههای تراکنشی بر پایه تعاریف ابعادی مناسب است.
سوالات موضوعی که در ادامه به آن اشاره میشود توسط OLAP پاسخ داده میشوند:
-
مقدار فروش کل تولیدات در سه ماهه گذشته در یک منطقه بخصوص چقدر بوده است؟
-
کدامیک از محصولات جزء ده محصول پر فروش تمامی فروشگاهها در ماه گذشته بودند؟
-
کدامیک از محصولات برای مشتریان زن و مشتریان مرد فروش قابل توجهی داشته است؟
-
تفاوت میزان فروش روزانه در هنگام تبلیغات در مقایسه با دوره زمانی عادی چیست؟
فن آوری OLAP بر پایه محاسبات تجمیعی است. سرویس دهنده OLAP نوع خاصی از سرویس دهندهی بانک اطلاعاتی محسوب میگردد که با دادههای چند بعدی سروکار دارد. بسیاری از مشکلات و مخاطرات نظیر ایندکس گذاری، ذخیره سازی دادهها و ... که در RDBMSها وجود دارد در سرویس دهندهی OLAP نیز وجود دارد.
داده کاوی در یافتن الگوهای پنهان از یک مجموعه داده توسط تحلیل همبستگی میان مقادیر مشخصهها مناسب است.
تکنیکهای داده کاوی دو گونه هستند: نظارت شده و نظارت نشده. در داده کاوی نظارت شده کاربر میبایست مشخصهی هدف و مجموعه دادهی ورودی را تعیین نماید. الگوریتمهای داده کاوی نظارت شده شامل درخت تصمیم، نیو بیز و شبکههای عصبی هستند. تکنیکهای داده کاوی نظارت نشده نیازی به تعیین مشخصهی قابل پیش بینی ندارد. خوشه بندی مثال خوبی از داده کاوی نظارت نشده میباشد و به گروه بندی نقاط داده ای ناهمگن به زیر گروه هایی میپردازد که در آنها نقاط داده ای کم و بیش مشابه و همگن هستند.
در زیر نمونه ای از سوالات پاسخ داده شده توسط داده کاوی ارائه شده است:
-
مشخصات مشتریانی که تمایل به خرید جدیدترین مدل را دارند، چیست؟
-
چه کالاهایی باید به این دسته از مشتریان خاص توصیه و پیشنهاد گردد؟
-
برآورد میزان فروش مدلی خاص در سه ماهه آینده چیست؟
-
چگونه باید مشتریان را تقسیم بندی کرد؟
یکی از فرآیندهای اصلی داده کاوی، تحلیل همبستگی میان مشخصهها و مقادیر آنها است. محققین آمار در این موارد قرنها مطالعه داشتهاند. OLAP و داده کاوی دو فن آوری مختلف هستند اما فعالیتهای یکدیگر را تکمیل میکنند. OLAP فعالیت هایی نظیر خلاصه سازی، تحلیل تغییرات در طول زمان و تحلیلهای What If را پشتیبانی مینماید. همچنین میتوان آنرا برای تحلیل نتایج داده کاوی در سطوح مختلف و مجزا استفاده کرد. داده کاوی نیز میتواند در ساخت Cubeهای مفیدتر سودمند باشد.
تفاوت میان OLAP و داده کاوی ارتباطی به تفاوت میان دادههای تلخیص شده و دادههای تشریحی ندارد. در واقع تمایز قابل توجهی میان مدل سازی توصیفی و تشریحی وجود دارد. توابع و الگوریتم هایی که معمولاً در ابزارهای OLAP یافت میشود، توابع مدل سازی توصیفی به شمار میآیند. در حالیکه توابعی که در آنچه که اصطلاحاً بسته داده کاوی نامیده میشود، یافت میشود توابع یا الگوهای مدل سازی تشریحی هستند.
الگوریتمهای داده کاوی موجود در SSAS و زمینه کاری متناظر
این الگوریتمها را به 5 دسته تقسیم میتوان نمود:
پیش بینی توالی وقایع
برای مثال جهت تجزیه و تحلیل مجموعه ای از شرایط آب و هوایی که منجر به وقوع پدیده خاصی میشود. از الگوریتم زیر استفاده میشود:
Microsoft Sequence Clustering Algorithm
یافتن گروهی از موارد مشترک در تراکنش ها
معروفترین مثال در خصوص تجزیه و تحلیل سبد بازار است. از الگوریتمهای زیر استفاده میشود:Microsoft Association Algorithm
Microsoft Decision Trees Algorithm
یافتن گروهی از موارد مشابه
معمولترین کاربرد زمینه بخش بندی دادههای مشتریان به منظور یافتن گروههای مجزا از مشتریان است. از الگوریتمهای زیر استفاده میشود:Microsoft Clustering Algorithm
Microsoft Sequence Clustering Algorithm
پیش بینی صفات گسسته
به عنوان مثال، پیش بینی اینکه یک مشتری خاص، تمایلی به خرید محصول جدید دارد یا خیر. از الگوریتمهای زیر استفاده میشود:Microsoft Decision Trees Algorithm
Microsoft Naive Bayes Algorithm
Microsoft Clustering Algorithm
Microsoft Neural Network Algorithm
پیش بینی صفات پیوسته
پیش بینی درآمد در ماه آینده مثالی از آن میباشد. از الگوریتمهای زیر استفاده میشود:Microsoft Decision Trees Algorithm
Microsoft Time Series Algorithm
دارا بودن امکانات بسیار قدرتمند و پشتیبانی از محیط فارسی و همچنین پشتیبانی آنها جهت پاسخگویی به سوالات، چه از طریق ایمیل یا چت، از نقاط قوت این ابزار به شمار میروند. در جدول مقایسات میتوانید تفاوت نسخههای موجود این گزارش ساز را مشاهده کنید. برای استفاده در MVC از نسخه وب آن استفاده میکنیم.
در این مقاله قصد داریم با نحوه راه ندازی این ابزار در وب (MVC) آشنا شویم که شامل مباحث زیر میشود:
- استفاده از EF به عنوان منبع داده و ارسال آنها به سمت گزارش ساز
- نحوه طراحی فایل MRT و بایند کردن دادههای اطلاعاتی و ایجاد جدول
- استفاده از امکانات فایل خروجی ، چاپ و پیش نمایش و...
- بررسی Direction جهت استفاده در محیطهای فارسی زبان
- نحوه ارسال اطلاعات بین دو اکشن متفاوت
طراحی فایل MRT
فایل MRT در واقع یک قالب (Template) خالی از مقادیر متغیر است که در StimulSoft Studio به طراحی آن میپردازیم و در برنامه خود، این مقادیر متغیر را با اطلاعات دلخواه خود جایگزین میکنیم. تصویر زیر یک نمونه از یک گزارش خالی است که ابتدا آن را طراحی کرده و سپس در برنامه آن را مورد استفاده قرار میدهیم:
برای اینکه فایل MRT بتواند دیتاهای لازمی را که به آن پاس میدهیم، بخواند و در جای مشخص شده قرار بدهد، باید یک BussinessObject برای آن ایجاد کنیم. بعد از اینکه یک گزارش جدید ایجاد کردید، در سمت راست به قسمت Dictionary بروید و در قسمت BussinessObject گزینه NewBussinessObject را انتخاب کنید. یک نام و نام مستعار که عموما هم یکی است، برای آن انتخاب کنید. در زیر همان پنجره شما میتوانید ستونهای اطلاعاتی خود را تعریف کنید. در اینجا من میخواهم اطلاعات یک راننده را به همراه خودروی وی، نشان دهم. برای همین، من دو موجودیت راننده و خودروی راننده را دارم. پس اسم Business Object را DriverReport میگذارم و ستونهای اطلاعاتی فقط راننده (بدون در نظر گرفتن خودروی وی) را وارد میکنم.
در همین کادر بالا شما میتوانید تصیم بگیرید که آیا میخواهید اطلاعات خودرو را به همراه دیگرستونهای اطلاعاتی راننده، ایجاد کنید یا اینکه برای خودرو یک نوع مجزا انتخاب کنید. اگر تنها یک خودرو برای راننده باشد، شاید راحتتر باشید همانند اطلاعات راننده با آن رفتار کنید. ولی اگر مثلا بخواهید خودرویهای گذشته راننده را هم جز لیست داشته باشید، بهتر است یک Business Object جدید متعلق و زیر مجموعه Business Object راننده ایجاد کنید. در اینجا چون تنها یک خودرو است، من آن اطلاعات آن را به همراه راننده، ارسال میکنم. شکل زیر ساختار درختی از گزارش بالاست:
شکل زیر هم یک ساختار دیگر از یک گزارش است که شامل Business objectهای مختلف میشود:
سپس همین فیلدها را به سمت صفحه خالی بکشانید. با دو بار کلیک روی فیلدهای قرار گرفته در صفحه، با نحوه بایند کردن مقادیر آشنا میشوید؛ هر فیلدی که قرار است دیتای آن بایند شود، باید به شکل زیر در بخش Expression پنجره باز شده، نوشته شود:
{driverReport.LastName}
در دیکشنری همچنین انواع دیگری از فیلدها نیز به چشم میخورد:
متغیرها: این نوع فیلد یک متغیر است که به طور جداگانه میتواند مقداردهی شود و از آن بیشتر برای ارسال دادههای تکی چون تصاویر، تاریخ شمسی و ... میتوان استفاده کرد.
متغیرهای سیستمی: این نوع متغیرها توسط خود گزارش ساز به طور مستقیم پر میشوند که شامل شماره صفحه، تاریخ و زمان، تعداد صفحات، مقادیر دو ارزشی (آیا صفحه آخر گزارش است؟) و ... میشود.
توابع: گزارش ساز شامل یک سری توابع آماده برای اعمال تغییرات بر روی دادهها میباشد که در دستههای مختلفی چون کار با رشتهها، زمان، ریاضیات و... قرار گرفتهاند.
بعد از تکمیل آن، فایل MRT را ذخیره و در یک دایرکتوری در ساختار پروژه قرار دهید.
راه اندازی گزارش ساز در ASP.Net MVC
اولین کاری که میکنیم، ورود سه dll اصلی به پروژه است:
Stimulate.Base
Stimulate.Report
Stimulate.Report.MVC
در مرحله بعد یک متد ساخته و یک ویوو را برای صفحه گزارش گیری ایجاد میکنیم:
public ActionResult Report(int id) { return View(); }
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions() { Localization = "~/content/reports/fa.xml", Actions = { GetReportSnapshot = "LoadReportSnapshot", ViewerEvent = "ViewerEvent", ExportReport = "ExportReport", PrintReport = "PrintReport", } }
در نسخههای دو سال اخیر، استفاده از این Helper تفاوتهایی در نحوه استفاده از خصوصیتهای آن کرده است. در این روش جدید، پراپرتیها دسته بندی شده و برای دسترسی به هر کدام باید به بخش آن مراجعه کنید؛ مثلا پراپرتیهای Action، در دسته Actions قرار گرفتهاند یا خصوصیتهای ظاهری در دسته Appearance، یا گزینههای مرتبط با خروجی گرفتنها، در دسته Export قرار گرفتهاند و الی آخر که در نسخههای پیشین، کد بالا را به شکل زیر، با پیشوند نام دسته مینوشتیم:
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions() { Localization = "~/content/reports/fa.xml", ActionGetReportSnapshot = "LoadReportSnapshot", ActionViewerEvent = "ViewerEvent", ActionExportReport = "ExportReport", ActionPrintReport = "PrintReport", }
بعد از آن لازم است دیتاها را از طریق EF خوانده و به یک مدل جدید که بر اساس اطلاعات گزارش شماست و قرار است گزارش شما این پراپرتیها را بشناسد، به طور دستی یا با استفاده یک کتابخانه mapping مثل automapper انتقال دهید. یا حتی میتوانید مانند کد زیر از ساختاری ناشناس استفاده کنید. در کد زیر، من به صورت تمرینی اطلاعات یک راننده و خودروی او را انتقال میدهم:
var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 };
var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", car = new { Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 } };
var report = new StiReport(); report.Load(Server.MapPath("~/Content/Reports/driver.mrt")); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date));
var report = new StiReport(); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date)); report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
پس کد کامل ما برای ایجاد یک گزارش به شکل زیر میشود:
public ActionResult LoadReportSnapshot() { var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 }; var report = new StiReport(); report.Load(Server.MapPath("~/Content/Reports/driver.mrt")); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date)); return StiMvcViewer.GetReportSnapshotResult(HttpContext, report); }
اگر دوباره در ویو مربوطه، به سراغ helper برویم میبینیم که سه اکشن متد دیگر وجود دارند که در زیر، به ترتیب با نحوه کار آنها و کد اکشن متد آنها اشاره میکنیم:
Viewer Events : این اکشن متد که تنها یک خط ActionResult استاتیک را فراخوانی میکند، جهت مدیریت رویدادهای گزارش چون: زوم، صفحه بندی گزارش، خروجیها و چاپ میباشد و وجود آن در گزارش از الزامات است.
public virtual ActionResult ViewerEvent() { return StiMvcViewer.ViewerEventResult(); }
PrintReport: برای مدیریت و ارسال گزارشات به دستگاه چاپ میباشد. این اطلاعات از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط PrintReportResult آن را دریافت میکند.
public virtual ActionResult PrintReport() { return StiMvcViewer.PrintReportResult(this.HttpContext); }
ExportReport: گزارش ساز استیمول به شما اجاز میدهد در فرمتهای گوناگونی چون xlsx,docx,pptx,pdf,rtf و ... از گزارش خود خروجی بگیرید. اطلاعات گزارش از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط ExportReportResult دریافت میشود.
public virtual ActionResult ExportReport() { return StiMvcViewer.ExportReportResult(this.HttpContext); }
البته خوشبختانه این مشکل در حالت پیش نمایش و چاپ و خروجیها دیده نمیشود و فقط مختص نمایش روی فرم Html است. برای حل این مشکل ممکن است از گزینه یا پراپرتی RightToLeft، در بخش Appearance موجود در helper استفاده کنید که البته استفاده از آن مانند تصویر بالا، فقط محدود به container گزارش و نوار ابزار آن میشود. برای حل این مشکل کافی است کد css زیر را به صفحه گزارش اضافه کنید تا مشکل حل شود:
.stiMvcViewerReportPanel table{ direction:ltr !important; }
حال حتما پیش خود میگویید که این روش برای اطلاعات ایستا و تمرینی مناسب است و من چگونه باید پارامترهای ارسالی به اکشن متد Report را به اکشن متد LoadReportSnapshot ارسال کنم. برای این منظور استفاده از SessionStateها زیاد توصیه شدهاست:
public virtual ActionResult Report(int id) { TempData["id"]=id; return View(); } public virtual ActionResult LoadReportSnapshot() { var driverId = (int)TempData ["id"]; //..... }
public virtual ActionResult Report(int id) { return View(); } public virtual ActionResult LoadReportSnapshot(int id) { //..... }
نکته بسیار مهم: گزارش ساز استیمول متاسفانه شامل تنظیم پیش فرض نامناسبی است که عملیات کش را بر روی گزارشها اعمال میکند. به عنوان مثال تصور کنید من صفحه گزارش شخصی به نام «وحید نصیری» را باز میکنم و در تب دیگر گزارش شخص دیگری با نام «علی یگانه مقدم» را باز میکنم. حال اگر کاربر به سراغ تب آقای نصیری برود و بخواهد چاپ یا خروجی درخواست کند، اشتباها با گزارش علی یگانه مقدم روبرو خواهد شد که این اتفاق به دلیل کش شدن رخ میدهد. برای غیر فعال کردن این قابلیت پیش فرض، کد زیر را در Helper اضافه کنید:
Server = { GlobalReportCache = false }
اصول کلی طراحی یک اعتبارسنج ساده
در قسمت قبل، تمام اطلاعات فرم لاگین را درون شیء account خاصیت state قرار دادیم. در اینجا نیز شبیه به چنین شیءای را برای ذخیره سازی خطاهای اعتبارسنجی فیلدهای فرم، تعریف میکنیم:
class LoginForm extends Component { state = { account: { username: "", password: "" }, errors: { username: "Username is required" } };
البته در ابتدای کار، خاصیت errors را با یک شیء خالی ({}) مقدار دهی میکنیم و سپس در متد مدیریت ارسال فرم به سرور:
validate = () => { return { username: "Username is required." }; }; handleSubmit = e => { e.preventDefault(); const errors = this.validate(); console.log("Validation errors", errors); this.setState({ errors }); if (errors) { return; } // call the server console.log("Submitted!"); };
- اگر خطایی وجود داشت، به مرحلهی بعد که ارسال فرم به سمت سرور میباشد، نخواهیم رسید و کار را با یک return، خاتمه میدهیم.
- علت فراخوانی متد setState در اینجا، درخواست رندر مجدد فرم، با توجه به خطاهای اعتبارسنجی ممکنی است که به خاصیت errors، اضافه یا به روز رسانی کردهایم.
- نمونهای از خروجی متد validate را نیز در اینجا مشاهده میکنید که تشکیل شدهاست از یک شیء، که هر خاصیت آن، به نام یک فیلد موجود در فرم، اشاره میکند.
پیاده سازی یک اعتبارسنج ساده
در اینجا یک نمونه پیاده سازی ساده و ابتدایی منطق اعتبارسنجی فیلدهای فرم را ملاحظه میکنید:
validate = () => { const { account } = this.state; const errors = {}; if (account.username.trim() === "") { errors.username = "Username is required."; } if (account.password.trim() === "") { errors.password = "Password is required."; } return Object.keys(errors).length === 0 ? null : errors; };
- سپس یک شیء خالی error را تعریف کردهایم.
- در ادامه با توجه به اینکه مقادیر المانهای فرم در state وجود دارند، خالی بودن آنها را بررسی میکنیم. اگر خالی بودند، یک خاصیت جدید را با همان نام المان مورد بررسی، به شیء errors اضافه کرده و پیام خطایی را درج میکنیم.
- در نهایت این شیء errors و یا نال را (در صورت عدم وجود خطایی) بازگشت میدهیم.
برای آزمایش آن، پس از اجرای برنامه، یکبار بدون وارد کردن اطلاعاتی، بر روی دکمهی Login کلیک کنید؛ یکبار هم با وارد کردن اطلاعاتی در فیلدهای مختلف. در این بین کنسول توسعه دهندگان مرورگر را نیز جهت مشاهدهی شیءهای error لاگ شده، بررسی نمائید.
نمایش خطاهای اعتبارسنجی فیلدهای فرم
در قسمت قبل، کامپوننت جدید src\components\common\input.jsx را جهت کاهش کدهای تکراری تعاریف المانهای ورودی، ایجاد کردیم. در اینجا نیز میتوان کار نمایش خطاهای اعتبارسنجی را قرار داد:
import React from "react"; const Input = ({ name, label, value, error, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type="text" className="form-control" /> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Input;
- سپس با توجه به نکتهی «رندر شرطی عناصر در کامپوننتهای React» در قسمت 5، اگر error مقداری داشته باشد و به true تفسیر شود، آنگاه به صورت خودکار، div ای که دارای کلاسهای بوت استرپی اخطار است به همراه متن خطا، رندر خواهد شد؛ در غیراینصورت هیچ div ای به صفحه اضافه نمیشود.
- اکنون متد رندر کامپوننت فرم لاگین را به صورت زیر تکمیل میکنیم:
render() { const { account, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <Input name="username" label="Username" value={account.username} onChange={this.handleChange} error={errors.username} /> <Input name="password" label="Password" value={account.password} onChange={this.handleChange} error={errors.password} /> <button className="btn btn-primary">Login</button> </form> ); }
تا اینجا اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، با کلیک بر روی دکمهی Login، خطاهای اعتبارسنجی به صورت زیر ظاهر میشوند:
در این حالت اگر هر دو فیلد را تکمیل کرده و بر روی دکمهی لاگین کلیک کنیم، به خطای زیر در کنسول توسعه دهندگان مرورگر میرسیم:
loginForm.jsx:55 Uncaught TypeError: Cannot read property 'username' of null at LoginForm.render (loginForm.jsx:55)
this.setState({ errors: errors || {} });
اعتبارسنجی فیلدهای یک فرم در حین ورود اطلاعات در آنها
تا اینجا نحوهی اعتبارسنجی فیلدهای ورودی را در حین submit بررسی کردیم. شبیه به همین روش را به حالت onChange و متد handleChange فرم لاگین که در قسمت قبل تکمیل کردیم نیز میتوان اعمال کرد:
handleChange = ({ currentTarget: input }) => { const errors = { ...this.state.errors }; //cloning an object const errorMessage = this.validateProperty(input); if (errorMessage) { errors[input.name] = errorMessage; } else { delete errors[input.name]; } const account = { ...this.state.account }; //cloning an object account[input.name] = input.value; this.setState({ account, errors }); };
- سپس اینبار فقط نیاز داریم اعتبار اطلاعات ورودی یک فیلد را بررسی کنیم و متد validate فعلی، فیلدهای کل فرم را با هم تعیین اعتبار میکند. به همین جهت متد جدید validateProperty را به صورت زیر تعریف میکنیم. اگر این متد خروجی داشت، خاصیت متناظر با آنرا در شیء errors به روز رسانی میکنیم؛ در غیراینصورت این خاصیت را از شیء errors حذف میکنیم تا پیام اشتباهی را نمایش ندهد. در نهایت توسط متد setState، مقدار خاصیت errors را با شیء errors جاری به روز رسانی میکنیم:
validateProperty = ({ name, value }) => { if (name === "username") { if (value.trim() === "") { return "Username is required."; } // ... } if (name === "password") { if (value.trim() === "") { return "Password is required."; } // ... } };
پس از ذخیره سازی این تغییرات، برای آزمایش آن، یکبار حرف a را بجای username وارد کنید و سپس آنرا حذف کنید. بلافاصله پیام خطای مرتبطی نمایش داده خواهد شد و اگر مجددا عبارتی را وارد کنیم، این پیام محو میشود که معادل قسمت delete در کدهای فوق است.
معرفی Joi
تا اینجا، هدف نمایش ساختار یک اعتبارسنج ساده بود. این روش مقیاس پذیر نیست و در ادامه آنرا با یک کتابخانهی اعتبارسنجی بسیار پیشرفته به نام Joi، جایگزین خواهیم کرد که نمونه مثالهای آنرا در اینجا میتوانید مشاهده کنید:
const Joi = require('@hapi/joi'); const schema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/), email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) }); schema.validate({ username: 'abc', birth_year: 1994 });
برای نصب آن، پس از باز کردن پوشهی اصلی برنامه توسط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
> npm install @hapi/joi --save > npm i --save-dev @types/hapi__joi
سپس به کامپوننت فرم لاگین مراجعه کرده و در ابتدای آن، Joi را import میکنیم:
import Joi from "@hapi/joi";
schema = { username: Joi.string() .required() .label("Username"), password: Joi.string() .required() .label("Password") };
سپس ابتدای متد validate قبلی را به صورت زیر بازنویسی میکنیم:
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult);
اکنون اگر برنامه را اجرا کرده و بدون ورود اطلاعاتی، بر روی دکمهی لاگین کلیک کنیم، خروجی زیر در کنسول توسعه دهندگان مرورگر ظاهر میشود:
همانطور که مشاهده میکنید، خروجی Joi، یک شیء است که اگر دارای خاصیت error بود، یعنی خطای اعتبارسنجی رخدادهاست. سپس باید خاصیت آرایهای details این شیء error را جهت یافتن خواص مشکل دار بررسی کرد. هر خاصیت در اینجا با path مشخص میشود. بنابراین قدم بعدی، تبدیل این ساختار، به ساختار شیء errors موجود در state کامپوننت جاری است تا مابقی برنامه بتواند بدون تغییری از آن استفاده کند.
نگاشت شیء دریافتی از Joi، به شیء errors موجود در state کامپوننت لاگین
خاصیت error شیء دریافتی از متد validate کتابخانهی Joi، تنها زمانی ظاهر میشود که خطایی وجود داشته باشد. همچنین خاصیت details آن نیز آرایهای از اشیاء با خواص message و path است. این path نیز یک آرایه است که اولین المان آن، نام خاصیت در حال بررسی است. اکنون میخواهیم این آرایه را تبدیل به یک شیء قابل درک برای برنامه کنیم:
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult); if (!validationResult.error) { return null; } const errors = {}; for (let item of validationResult.error.details) { errors[item.path[0]] = item.message; } return errors; };
اکنون اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، همانند قبل میتوان خطاهای اعتبارسنجی در حین submit را مشاهده کرد:
بازنویسی متد validateProperty توسط Joi
تا اینجا متد validate ساده و ابتدایی خود را با استفاده از امکانات کتابخانهی Joi، بازنویسی کردیم. اکنون نوبت بازنویسی متد اعتبارسنجی در حین تایپ اطلاعات است:
validateProperty = ({ name, value }) => { const userInputObject = { [name]: value }; const propertySchema = Joi.object({ [name]: this.schema[name] }); const { error } = propertySchema.validate(userInputObject, { abortEarly: true }); return error ? error.details[0].message : null; };
تا اینجا بجای this.state.account که به کل فرم اشاره میکند، شیء اختصاصیتر userInputObject را ایجاد کردهایم که معادل اطلاعات فیلد ورودی کاربر است. یک چنین نکتهای را در مورد schema نیز باید رعایت کرد. در اینجا نمیتوان اعتبارسنجی را با this.schema شروع کرد؛ چون این شیء نیز به اطلاعات کل فرم اشاره میکند و نه تک فیلدی که هم اکنون در حال کار با آن هستیم. بنابراین نیاز است یک Joi.object جدید را بر اساس name رسیده، از this.schema کلی، استخراج و تولید کرد؛ مانند propertySchema که مشاهده میکنید.
اکنون میتوان متد validate این شیء اسکیمای جدید را فراخوانی کرد و خاصیت error شیء حاصل از آنرا توسط Object Destructuring، استخراج نمود. در اینجا abortEarly به true تنظیم شدهاست (البته حالت پیشفرض آن است و نیازی به ذکر صریح آن نیست). علت اینجا است که نمایش خطاهای بیش از اندازه به کاربر، برای او گیج کننده خواهند بود؛ به همین جهت هربار، اولین خطای حاصل را به او نمایش میدهیم.
غیرفعالسازی دکمهی submit در صورت شکست اعتبارسنجیهای فرم
در ادامه میخواهیم دکمهی submit فرم لاگین، تا زمانیکه اعتبارسنجی آن با موفقیت به پایان برسد، غیرفعال باشد:
<button disabled={this.validate()} className="btn btn-primary"> Login </button>
فراخوانی setStateهای تعریف شدهی در متدهای رویدادگردان این فرم هستند که سبب رندر مجدد کامپوننت شده و در این بین در متد رندر، کار بررسی مجدد مقدار نهایی متد validate صورت میگیرد تا بر اساس آن، فعال یا غیرفعال بودن دکمهی Login، مشخص شود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-19.zip