- واکشی اطلاعات در Excel
- استفاده از Performance Point Service در Sharepoint (ترجیحا 2013)
- استفاده از کامپوننتهای OLAP در محیط Visual Studio مانند RadarSoft,Dundas,...
- استفاده از نرم افزار هایی که کارشان ساختن گزارشات از MDBها میباشد (CubeWare)
نحوه استفاده
نحوه استفاده از آن بسیار راحت است و در دموی html همراه آن به طور ساده در سه مثال توضیح داده شده است. ابتدا از این آدرس کتابخانه آن را دریافت کنید. این کتابخانه شامل یک فایل js که شامل کدهای پلاگین است، یک فایل css جهت تغییر استایل کدهایی است که پلاگین تولید میکند که اسامی آن دقیقا مشخص میکند که هر کلاس متعلق به چه بخشی است.
گام اول:
فایلهای مورد نظر را بعد از صدا زدن کتابخانهی جی کوئری صدا بزنید.
<link type="text/css" href="css/RowAdder.css" rel="stylesheet" /> <script src="js/RowAdder.js" type="text/javascript"></script>
گام دوم :
در تکه کدهای html، کدی را که قرار است در هر سطر تکرار شود، داخل یک div قرار داده و نامی مثل row-sample را برای آن قرار دهید (فعلا حتما این نام باشد)، بعدها پلاگین، کدهای داخل این تگ div را به عنوان هر سطر خواهد شناخت:
<div id="row-sample"> <form style="margin: 0; padding: 0;"> Name:<input type="text"/> <input type="radio" name="Gender" value="male" checked="checked">Male <input type="radio" name="Gender" value="female">Female </form> </div>
گام سوم:
سپس یک div دیگر ایجاد کنید و نامی مثل mypanel را به آن بدهید تا سطرهایی که ایجاد میشوند داخل این div قرار بگیرند.
<div id="mypanel"></div>
گام چهارم:
در بخش head یک تگ اسکریپت باز کرده و کدهای زیر را به آن اضافه میکنیم. این کد باعث میشود که پلاگین فعال شود.
<script> $(document).ready(function() { $("#mypanel").RowAdder(); }); </script>
یک دکمه جهت افزودن سطر به صفحه اضافه میکنیم
<button id="addanotherform">Add New Form</button>
و در قسمت تگ اسکریپت هم کد زیر را اضافه میکنیم:
$("#addanotherform").on('click', function() { $("#mypanel").RowAdder('add'); });
حال از صفحه تست میگیریم: با هر بار کلیک بر روی دکمهی Add New Form یک سطر جدید ایجاد میگردد.
در تصویر بالا دکمههای دیگر هم دیده میشوند که به دیگر متدهای آن اشاره دارد:
جهت مخفی سازی:
$("#mypanel").RowAdder('hide');
چهت نمایش:
$("#mypanel").RowAdder('show');
جهت افزودن سطر با کد:
$("#mypanel").RowAdder('add');
جهت دریافت تعداد سطرهای ایجاد شده:
$("#mypanel").RowAdder('count')
جهت دریافت کدهای یک سطر در اندیس x
$("#mypanel").RowAdder('content', 3)
جهت حذف یک سطر با اندیس x
$("#mypanel").RowAdder('remove', 3);
همانطور که با صدا زدن اولین متد پلاگین متوجه شدید و نتیجهی آن را در دمو دیدید، این پلاگین از پیش فرضهایی جهت راه اندازی اولیه استفاده میکند که این پیش فرضها عبارتند از تگ row-sample که بدون معرفی رسمی، آن را شناسایی کرد. همچنین ممکن است بخواهید عبارت Remove را با کلمهی فارسی «حذف» جایگزین نمایید. برای اینکار میتوانید پلاگین را به شکل زیر به کار ببرید:
$("#mypanel").RowAdder({ sample: '#my-custom-sample', type: 'text', value:'حذف' });
تغییر اولین پیش فرض، تغییر نام تگ row-sample به my-custom-sample بود و در مرحلهی بعد هم نام فارسی حذف را جایگزین remove کردیم. عبارت type به طور پیش فرض بر روی text قرار دارد که اجباری به ذکر آن در کد بالا نبود. ولی اگر دوست دارید که به جای نمایش عبارت حذف، از یک آیکن یا تصویر استفاده کنید، کد را به شکل زیر تغییر دهید:
$("#mypanel").RowAdder({ type: 'image', value: 'images/remove.png' });
فایل RowAdder.css
در بردارنده هر سطر .each-section { margin: 20px; padding: 5px; } جهت استایل بندی لینک چه تصویر و چه متن .remove-link { color:#999; text-decoration: none; } a:hover.remove-link { color:#802727; } جهت تغییر استایل بر روی خود تصویر .remove-image { }
آشنایی با کد پلاگین
(function ($) { var settings = null; $.fn.RowAdder = function (method) { // call methods if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.RowAdder'); } }; })(jQuery);
متدها
//methods var methods = { init: function (options) { //default-settings settings = $.extend({ 'sample': '#row-sample', 'type': 'text', 'value': 'Remove' }, options); this.attr('data-sample', settings.sample); this.attr('data-type', settings.type); this.attr('data-value', settings.value); Do(this); }, show: function () { this.css("display", "inline"); }, hide: function () { this.css("display", "none"); }, add: function () { Do(this); }, remove: function (index) { console.log(index); this.find(".each-section")[index].remove(); }, content: function (index) { return this.find(".each-section")[index]; }, count: function (index) { return this.find(".each-section").size(); } };
تابع Do
function Do(panelDiv) { settings.sample = panelDiv.data('sample'); settings.type = panelDiv.data('type'); settings.value = panelDiv.data('value'); //find sample code var rowsample = $(settings.sample); rowsample.css("display", "none"); var sample = rowsample.html(); var i = panelDiv.find(".each-section").size(); //add html details to create a correct template var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i }); var image = $("<img />", { "src": settings.value,"class":"remove-image" }); var link = $("<a />", { "text": settings.value,"class":"remove-link" }); //remove event for remove selected form //create new form sectionDiv.html(sample); link.on('click', function (e) { e.preventDefault(); var $this = $(this); $this.closest(".each-section").remove(); }); if (i > 0) { if (settings.type == 'image') { link.text(''); link.append(image); } sectionDiv.append(link); } //add new created form on document panelDiv.append(sectionDiv); }
settings.sample = panelDiv.data('sample'); settings.type = panelDiv.data('type'); settings.value = panelDiv.data('value'); //find sample code var rowsample = $(settings.sample); rowsample.css("display", "none"); var sample = rowsample.html(); var i = panelDiv.find(".each-section").size();
//add html details to create a correct template var sectionDiv = $('<div />', { "class": 'each-section', 'id': 'section'+i }); var image = $("<img />", { "src": settings.value,"class":"remove-image" }); var link = $("<a />", { "text": settings.value,"class":"remove-link" });
در خط بعدی محتویات نمونه را داخل تگ sectiondiv قرار میدهیم:
//create new form sectionDiv.html(sample);
بعد از آن برای رویداد کلیک لینک حذف، کد زیر را وارد میکنیم:
link.on('click', function (e) { e.preventDefault(); var $this = $(this); $this.closest(".each-section").remove(); });
اولین شرط زیر بررسی میکند که آیا این سطری که ایجاد شده است سطر دوم به بعد است یا خیر؟ اگر آری پس باید دکمهی حذف را به همراه داشته باشد. در صورتیکه سطر دوم به بعد باشد، وارد آن میشود. حالا بررسی میکند که کاربر برای دکمهی حذف، درخواست لینک تصویری یا لینک متنی داده است و لینک مناسب را ساخته و آن را به انتهای sectionDiv اضافه میکند.
if (i > 0) { if (settings.type == 'image') { link.text(''); link.append(image); } sectionDiv.append(link); }
در انتها کل تگ sectionDiv را به تگ داده شده اضافه میکنیم تا به کاربر نمایش داده شود.
//add new created form on document panelDiv.append(sectionDiv);
مدیریت حافظه در JavaScript
در گزارشات Crosstab، ردیفهای یک گزارش، تبدیل به ستونهای آن میشوند؛ به همین جهت به آنها Pivot tables هم میگویند.
برای مثال فرض کنید که قصد دارید گزارش تعداد ساعت کارکرد را به ازای هر پروژه در طول چند ماه تعیین کنید. گزارش متداول از این نوع اطلاعات، یک لیست بلند بالای بیمفهوم است. این گزارش تشکیل شده از صدها رکورد به ازای کارکنان مختلف در پروژههای مختلف و ... هیچ ارزش آماری خاصی ندارد. یک گزارش بدوی است. زمانیکه این گزارش را تبدیل به حالت crosstab میکنیم، اولین ستون فقط یک شماره پروژه خواهد بود و ستونهای بعدی، مثلا نام ماهها و مقادیر آنها هم جمع کارکرد افراد بر روی یک پروژه مشخص.
مثال اول) تهیه گزارش Crosstab جمع هزینههای واحدهای مختلف به تفکیک ماه
کلاس هزینههای زیر را در نظر بگیرید که به کمک آن میتوان به ازای هر واحد یا دپارتمان در تاریخهای متفاوت، هزینهای را مشخص ساخت:
using System;
namespace Pivot.Sample1
{
public class Expense
{
public DateTime Date { set; get; }
public string Department { set; get; }
public decimal Expenses { set; get; }
}
}
با توجه به این کلاس، یک منبع داده آزمایشی جهت تهیه گزارشات، میتواند به صورت زیر باشد:
using System;
using System.Collections.Generic;
namespace Pivot.Sample1
{
public class ExpenseDataSource
{
public static IList<Expense> ExpensesDataSource()
{
return new List<Expense>
{
new Expense { Date = new DateTime(2011,11,1), Department = "Computer", Expenses = 100 },
new Expense { Date = new DateTime(2011,11,1), Department = "Math", Expenses = 200 },
new Expense { Date = new DateTime(2011,11,1), Department = "Physics", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Computer", Expenses = 75 },
new Expense { Date = new DateTime(2011,10,1), Department = "Math", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Physics", Expenses = 130 },
new Expense { Date = new DateTime(2011,9,1), Department = "Computer", Expenses = 90 },
new Expense { Date = new DateTime(2011,9,1), Department = "Math", Expenses = 95 },
new Expense { Date = new DateTime(2011,9,1), Department = "Physics", Expenses = 100 }
};
}
}
}
و اگر این لیست را به همین شکلی که هست نمایش دهیم، خروجی زیر را خواهیم داشت:
که ... خروجی مطلوبی نیست. در اینجا ما فقط 9 رکورد داریم؛ اما در عمل به ازای هر روز، یک رکورد میتواند وجود داشته باشد و این لیست طولانی، هیچ ارزش آماری خاصی ندارد. میخواهیم سرستونهای گزارش ما مطابق جدول زیر باشند:
یعنی اگر سه ماه را در نظر بگیریم با هر تعداد رکورد، فقط سه ردیف به ازای هر ماه باید حاصل شود و ستونهای دیگر هم نام بخشها یا واحدهای موجود باشند.
برای رسیدن به این خروجی Crosstab، میتوان کوئری LINQ زیر را به کمک امکانات گروه بندی اطلاعات آن تهیه کرد:
using System.Collections;
using System.Linq;
namespace Pivot.Sample1
{
public class PivotTable
{
public static IList ExpensesCrossTab()
{
return ExpenseDataSource
.ExpensesDataSource()
.GroupBy(t =>
new
{
Year = t.Date.Year,
Month = t.Date.Month
})
.Select(myGroup =>
new
{
//Year = myGroup.Key.Year,
Month = myGroup.Key.Month,
ComputerDepartment = myGroup.Where(x => x.Department == "Computer").Sum(x => x.Expenses),
MathDepartment = myGroup.Where(x => x.Department == "Math").Sum(x => x.Expenses),
PhysicsDepartment = myGroup.Where(x => x.Department == "Physics").Sum(x => x.Expenses)
})
.ToList();
}
}
}
که اینبار خروجی زیر را تولید میکند.
اگر علاقمند باشید که مثال فوق را در برنامهی LINQPad آزمایش کنید، این فایل را دریافت نموده و در آن برنامه باز نمائید.
مثال دوم) تهیه لیست Crosstab حضور و غیاب افراد در طول یک هفته
کلاس StudentStat را جهت ثبت اطلاعات حضور یک دانشجو، میتوان به شکل زیر تعریف کرد:
using System;
namespace Pivot.Sample2
{
public class StudentStat
{
public int Id { set; get; }
public string Name { set; get; }
public DateTime Date { set; get; }
public bool IsPresent { set; get; }
}
}
و بر همین اساس یک منبع داده فرضی جهت انجام گزارشات میتواند به نحو زیر تهیه شود:
using System;
using System.Collections.Generic;
namespace Pivot.Sample2
{
public class StudentsStatDataSource
{
public static IList<StudentStat> CreateMonthlyReportDataSource()
{
var result = new List<StudentStat>();
var rnd = new Random();
for (int day = 1; day < 6; day++)
{
for (int student = 1; student < 6; student++)
{
result.Add(new StudentStat
{
Id = student,
Date = new DateTime(2011, 11, day),
IsPresent = rnd.Next(-1, 1) == 0 ? true : false,
Name = "student " + student
});
}
}
return result;
}
}
}
خروجی این گزارش هم در این حالت ساده با 5 دانشجو و فقط 5 روز، 25 رکورد خواهد بود:
که ... این هم آنچنان از لحاظ آماری مطلوب و مفهوم نیست. میخواهیم سطرهای این گزارش همانند لیست واقعی حضورغیاب، فقط از نام افراد تشکیل شود و همچنین ستونها مثلا شماره یا نام روزهای یک هفته یا ماه باشند. مثلا به شکل زیر:
برای رسیدن به این خروجی Crosstab، مثلا میتوان از کوئری LINQ زیر کمک گرفت که بر اساس شماره دانشجویی اطلاعات را گروه بندی کرده است:
using System.Collections;
using System.Linq;
namespace Pivot.Sample2
{
public class PivotTable
{
public static IList StudentsStatCrossTab()
{
return StudentsStatDataSource
.CreateWeeklyReportDataSource()
.GroupBy(x =>
new
{
x.Id
})
.Select(myGroup =>
new
{
myGroup.Key.Id,
Name = myGroup.First().Name,
Day1IsPresent = myGroup.Where(x => x.Date.Day == 1).First().IsPresent,
Day2IsPresent = myGroup.Where(x => x.Date.Day == 2).First().IsPresent,
Day3IsPresent = myGroup.Where(x => x.Date.Day == 3).First().IsPresent,
Day4IsPresent = myGroup.Where(x => x.Date.Day == 4).First().IsPresent,
Day5IsPresent = myGroup.Where(x => x.Date.Day == 5).First().IsPresent,
PresentsCount = myGroup.Where(x => x.IsPresent).Count(),
AbsentsCount = myGroup.Where(x => !x.IsPresent).Count()
})
.ToList();
}
}
}
و این کوئری خروجی زیر را تولید میکند که از هر لحاظ نسبت به لیست قبلی مفهومتر است:
فایل LINQPad این مثال را میتوانید از اینجا دریافت کنید.
PM> Install-Package DNTFrameworkCore.Web
PM> Install-Package DNTFrameworkCore.Web.EntityFramework
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build() .MigrateDbContext<ProjectDbContext>() .Run(); } //... }
[Route("api/[controller]")] public class BlogsController : CrudController<IBlogService, int, BlogModel> { public BlogsController(IBlogService service) : base(service) { } protected override string CreatePermissionName => PermissionNames.Blogs_Create; protected override string EditPermissionName => PermissionNames.Blogs_Edit; protected override string ViewPermissionName => PermissionNames.Blogs_View; protected override string DeletePermissionName => PermissionNames.Blogs_Delete; }
var data = JSON.parse(responseBody) pm.environment.set("token", data.token);
حال برای ارسال درخواستهای HTTP به BlogsController به شکل زیر عمل کنید:
query={"page":1,"pageSize":100,"filter":{"logic":"and","filters":[{"field":"title","value":"Blog1","operator":"startswith"}]}}
{{endpoint}}/blogs/10
[Route("api/[controller]")] public class UsersController : CrudController<IUserService, long, UserReadModel, UserModel> { private readonly ILookupService _lookupService; public UsersController(IUserService service, ILookupService lookupService) : base(service) { _lookupService = lookupService ?? throw new ArgumentNullException(nameof(lookupService)); } protected override string CreatePermissionName => PermissionNames.Users_Create; protected override string EditPermissionName => PermissionNames.Users_Edit; protected override string ViewPermissionName => PermissionNames.Users_View; protected override string DeletePermissionName => PermissionNames.Users_Delete; [HttpGet("[action]")] [PermissionAuthorize(PermissionNames.Users_Create, PermissionNames.Users_Edit)] public async Task<IActionResult> RoleList() { var result = await _lookupService.ReadRolesAsync(); return Ok(result); } }
باتوجه به اینکه UserRole به عنوان یکی از وابستگیهای موجودیت User محسوب میشود، در پاسخ درخواست GET مرتبط با کاربری با شناسه 2، roles آن، لیستی از UserRoleModel هستند که به عنوان یک DetailModel طراحی شده است. به عنوان مثال برای حذف اتصال یک گروه کاربری باید درخواست PUT را به شکل زیر ارسال کنید:
اینبار اگر برای درخواست GET کاربر با شناسه 2 اقدام کنیم، به خروجی زیر خواهم رسید:
{ "userName": "rabbal", "displayName": "غلامرضا ربال", "password": null, "isActive": false, "roles": [], "permissions": [], "ignoredPermissions": [], "rowVersion": "AAAAAAACGxI=", "id": 2 }
برای بررسی بیشتر، پیشنهاد میکنم پروژه DNTFrameworkCore.TestAPI موجود در مخزن این زیرساخت را بازبینی کنید.
با توجه به در حال اجرا بودن 24 ساعتهی سرویس SQL server agent، استفادههای ارزندهای از آن میتوان کرد. برای مثال هر از گاهی بررسی کند که آیا هارد سرور پر شده یا نه؟ و اگر بله (کمبود میزان فضای خالی به حد خطرناکی رسیده)، یک ایمیل خودکار به مسؤول مربوطه ارسال کند.
عمدهی مطالبی که در این مقاله بررسی خواهند شد همانند مطلب مونیتور کردن میزان مصرف CPU توسط اس کیوال سرور است و از تکرار آنها در اینجا صرفنظر خواهد شد (راه اندازی دیتابیس میل و همچنین تعریف یک job جدید که در مورد آنها صحبت شد، همانند قبل است). تنها مطلب جدیدی که به آن اشاره خواهد شد، اسکریپت بررسی میزان فضای خالی و سپس ارسال ایمیل است که در یک job جدید همانند مقالهی قبل باید به سرور اضافه شود. این اسکریپت به شرح زیر است:
DECLARE @DriveBenchmark INT
DECLARE @MachineName NVARCHAR(1000)
DECLARE @DiskFreeSpace INT
DECLARE @DriveLetter CHAR(1)
DECLARE @AlertMessage NVARCHAR(MAX)
DECLARE @MailSubject NVARCHAR(MAX)
DECLARE @NewLine CHAR(2)
SET @NewLine = CHAR(13) + CHAR(10)
SET @DriveBenchmark = 2048 -- 2GB
SET @MailSubject = 'Free space is low on ' + @@SERVERNAME
SET @AlertMessage = ''
IF EXISTS (
SELECT *
FROM tempdb..sysobjects
WHERE id = OBJECT_ID(N'[tempdb]..[#disk_free_space]')
)
DROP TABLE #disk_free_space
CREATE TABLE #disk_free_space
(
DriveLetter CHAR(1) NOT NULL,
FreeMB INTEGER NOT NULL
)
/* Populate #disk_free_space with data */
INSERT INTO #disk_free_space
EXEC MASTER..xp_fixeddrives
DECLARE DriveSpace CURSOR FAST_FORWARD
FOR
SELECT DriveLetter,
FreeMB
FROM #disk_free_space
OPEN DriveSpace
FETCH NEXT FROM DriveSpace INTO @DriveLetter, @DiskFreeSpace
WHILE (@@FETCH_STATUS = 0)
BEGIN
IF @DiskFreeSpace < @DriveBenchmark
BEGIN
SET @AlertMessage = @AlertMessage + 'Drive ' + @DriveLetter + ' on ' + @@SERVERNAME
+ ' has only ' + CAST(@DiskFreeSpace AS VARCHAR) + ' MB left.' + @NewLine
END
FETCH NEXT FROM DriveSpace INTO @DriveLetter, @DiskFreeSpace
END
CLOSE DriveSpace
DEALLOCATE DriveSpace
DROP TABLE #disk_free_space
IF @AlertMessage <> ''
BEGIN
EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@copy_recipients = 'Administrator@site.net', -- Change This
@Subject = @MailSubject,
@Body = @AlertMessage
,@importance = 'High'
END
همه چیز از رویهی سیستمی xp_fixeddrives شروع میشود. حاصل اجرای این رویه، دریافت میزان فضای خالی هر درایو موجود در سرور خواهد بود. همانطور که در اسکریپت نیز مشخص است، برای ذخیره سازی خروجی این رویه، یک جدول موقتی (disk_free_space) ایجاد شده و خروجی آن به درون این جدول اضافه خواهد شد. سپس یک cursor ایجاد شده و تک تک رکوردهای حاصل با مقدار متغیر DriveBenchmark که در اینجا 2 گیگابایت در نظر گرفته شده است، مقایسه میگردند. سپس هر کدام از رکوردها که کمتر از 2 گیگابایت بود، متغیر AlertMessage ما را مقدار دهی خواهد کرد. در پایان اگر این متغیر مقدار دهی شده بود، یعنی مشکل حاصل شده و نتیجهی بررسی به صورت یک ایمیل ارسال میگردد. بدیهی است که در صورت نیاز مقدار متغیر DriveBenchmark و آرگومانهای recipients و copy_recipients کد فوق باید اصلاح شوند.
برای استفاده از آن یک job جدید تعریف کنید که مثلا هر سه ساعت یکبار اجرا شده و این اسکریپت را فراخوانی نماید.
تامین مقادیر پارامترها در حین نگاشتهای AutoMapper
- جلوگیری از Lazy loading اشتباه
- کاهش تعداد فیلدهای بازگشت داده شدهی از دیتابیس و محدود ساختن آنها به خواصی که قرار است نگاشت شوند. در حالت معمولی استفادهی از متد Mapper.Map، تمام فیلدهای مدل بارگذاری شده و سپس در سمت کلاینت توسط AutoMapper نگاشت خواهند شد. اما در حالت استفادهی از متد ویژهی Project To، کوئری SQL ارسالی به بانک اطلاعاتی نیز مطابق نگاشت تعریف شده، تغییر کرده و خلاصه خواهد شد.
در این حالت یک چنین سناریویی را درنظر بگیرید. مدل متناظر با جدول بانک اطلاعاتی ما چنین ساختاری را دارد:
public class UserModel { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
public class UserViewModel { public string FirstName { get; set; } public string LastName { get; set; } public string UserIdentityName { get; set; } }
تعریف نگاشتهای پارامتری
برای حل این مساله، از روش زیر استفاده میشود:
string userIdentityName = null; this.CreateMap<UserModel, UserViewModel>() .ForMember(d => d.UserIdentityName, opt => opt.MapFrom(src => userIdentityName));
اکنون جهت استفادهی از این متغیر با قابلیت جایگزینی، میتوان به نحو ذیل عمل کرد:
var uiUsers = users.AsQueryable() .Project() .To<UserViewModel>(new { userIdentityName = "User.Identity.Name Value Here" }) .ToList();
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
EF Code First #3
نحوه استفاده از ViewModel در ASP.NET MVC
برای اتصال ViewModel به مدل مینویسیم: یک خاصیت viewmodel ،خودت رو به یک خاصیت model انتساب بده پدرجان! همین! (البته حالت پیشرفتهتر استفاده از AutoMapper هست که نخوای اینها رو اگه سختت هست دستی بنویسی)
خواص مدل رابطه یک به یک داره با جدول شما در بانک اطلاعاتی. اون هم اطلاعاتش نیاز به اعتبار سنجی داره. ViewModel هم نیاز داره چون یک سری خاصیت اضافی عموما داره که در بانک اطلاعاتی نیست و فقط در View تعریف شده.
رابطه ای از جدول Type به جدول Category.
به کمک Scaffolding یک کنترلر برای کلاس Tap (شیر آب) میسازیم ، به طور عادی در فایل Create.chtml مقدار گروه را به صورت DropDown نمایش میدهد، حال ما نیاز داریم که خودمان DropDown را برای Type ایجاد کنیم و بعد ارتباط اینها را بر قرار کنیم.
تابع اولی Create را این طوری ویرایش میکنیم :
public ActionResult Create() { ViewBag.Type = new SelectList(db.Types, "Id", "Title"); ViewBag.Category = new SelectList(db.Categories, "Id", "Title"); return View(); }
همان طور که مشخص است ، علاوه بر مقادیر Category که خودش ارسال میکند ، ما نیز مقادیر نوعها را به View مورد نظر ارسال میکنیم.
برای نمایش دادن هر دو DropDownList ویو مورد نظر را به این صورت ویرایش میکنیم :
<div> نوع </div> <div> @Html.DropDownList("Type", (SelectList)ViewBag.Type, "-- انتخاب ---", new { id = "rdbTyoe" }) @Html.ValidationMessageFor(model => model.Category) </div> <div> دسته بندی </div> <div> @Html.DropDownList("Category", (SelectList)ViewBag.Category, "-- انتخاب ---", new { id = "rdbCategory"}) @Html.ValidationMessageFor(model => model.Category) </div>
همان طور که مشاهده میکنید ، در اینجا DropDownList مربوط به Type که خودمان سمت سرور ،مقادیر آن را پر کرده بودیم نمایش میدهیم.
خب شاید تا اینجای کار ، ساده بود ولی میرسیم به اصل مطلب و ارتباط بین این دو DropDownList. (قبل از این قسمت حتما نگاهی به ساختار DropDownList یا همان تگ select بیندازید ، اطلاعات جی کوئری شما در این قسمت خیلی کمک حال شما است)
برای این کار ما از jQuery استفادی میکنیم ، کار به این صورت است که هنگامی که مقدار DropDownList اول تغییر کرد :
- ما Id آن را به سرور ارسال میکنیم.
- در آنجا Category هایی که دارای Type با Id مورد نظر هستند را جدا میکنیم
- فیلدهای مورد نیاز یعنی Id و Title را میگیریم
- و بعد به کمک Json مقادیر را بر میگردانیم
- و مقادیر ارسالی از سرور را در optionهای DropDownList دوم (گروهها ) قرار میدهم
public ActionResult SelectCategory(int id) { var categoris = db.Categories.Where(m => m.Type1.Id == id).Select(c => new { c.Id, c.Title }); return Json(categoris, JsonRequestBehavior.AllowGet); }
$('#rdbTyoe').change(function () { jQuery.getJSON('@Url.Action("SelectCategory")', { id: $(this).attr('value') }, function (data) { $('#rdbCategory').empty(); jQuery.each(data, function (i) { var option = $('<option></option>').attr("value", data[i].Id).text(data[i].Title); $("#rdbCategory").append(option); }); }); });
- ابتدا یک تگ option میسازیم
- مقادیر مربوطه شامل Id که باید در attribute مورد نظر value قرار گیرد و متن آن که باید به عنوان text باشد را مقدار دهی میکنیم
- option آماده شده را به DropDownList دومی (Category ) اضافه میکنیم.
$('#rdbCategory').empty();
بعد از انتخاب :