استفاده از StructureMap به عنوان یک IoC Container
در نگارش بعدی، ObjectFactory استاتیک حذف میشود. بجای آن باید بنویسید:
var container = new Container(x => { // تنظیمات در اینجا });
var controller = container.GetInstance(controllerType) as SomeType;
public class MyController { public MyController(IContainer container) { } }
var searchLocation = DbGeography.FromText(String.Format("POINT({0} {1})", longitude, latitude)); var nearbyLocations = (from location in _context.GeoLocations where // (Additional filtering criteria here...) select new { LocationID = location.ID, // ... Distance = searchLocation.Distance( DbGeography.FromText("POINT(" + location.Longitude + " " + location.Latitude + ")")) }) .OrderBy(location => location.Distance) .ToList();
استفاده از JSON.NET در ASP.NET MVC
JsonNetValueProviderFactory.cs
+ نحوهی ثبت بهتر این کلاس دقیقا در همان ایندکس اصلی آن:
public static void RegisterFactory() { var defaultJsonFactory = ValueProviderFactories.Factories .OfType<JsonValueProviderFactory>().FirstOrDefault(); var index = ValueProviderFactories.Factories.IndexOf(defaultJsonFactory); ValueProviderFactories.Factories.Remove(defaultJsonFactory); ValueProviderFactories.Factories.Insert(index, new JsonNetValueProviderFactory()); }
public ActionResult SuppliersSelect() { var list = BlNews.Select().Distinct().ToList(); var suppliers = list.Select(x => new SelectListItem { Text = x.Admin.UserName, Value = x.Admin.Code.ToString(CultureInfo.InvariantCulture) }).ToList(); return PartialView("_SelectPartial", suppliers); }
هدر فقط در صفحه نخست گزارش
.MainTablePreferences(table => { table.ShowHeaderRow(false);
events.MainTableCreated(args => { var infoTable = new PdfGrid(numColumns: 1) { WidthPercentage = 100 }; infoTable.AddSimpleRow((cellData, properties) => {}); // ... the rest ... var table = infoTable.AddBorderToTable(borderColor: BaseColor.LIGHT_GRAY, spacingBefore: 10f); table.SpacingAfter = 10f; args.PdfDoc.Add(table); });
PS /> $env:PSModulePath -Split ":" /Users/sirwanafifi/.local/share/powershell/Modules /usr/local/share/powershell/Modules /usr/local/microsoft/powershell/7/Modules
PS /> Import-Module ./PingModule.psm1
PS /> Get-Module PingModule ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Script 0.0 PingModule Get-PingReply
PS /> Import-Module ./PingModule.ps1 PS /> Get-Module PingModule ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Script 0.0 PingModule
Function Get-PingReply { // as before } Function Get-PrivateFunction { Write-Debug 'This is a private function' } Export-ModuleMember -Function @( 'Get-PingReply' )
$moduleSettings = @{ Path = './PingModule.psd1' Description = 'A module to ping a remote system' RootModule = 'PingModule.psm1' ModuleVersion = '1.0.0' FunctionsToExport = @( 'Get-PingReply' ) PowerShellVersion = '5.1' CompatiblePSEditions = @( 'Core' 'Desktop' ) } New-ModuleManifest @moduleSettings
$moduleSettings = @{ Author = 'John Doe' Description = 'This is a sample module' } New-ModuleManifest $moduleSettings
New-ModuleManifest : A parameter cannot be found that matches parameter name 'Author'.
# # Module manifest for module 'PingModule' # # Generated by: sirwanafifi # # Generated on: 01/01/2023 # @{ # Script module or binary module file associated with this manifest. RootModule = './PingModule.psm1' # Version number of this module. ModuleVersion = '1.0.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' # ID used to uniquely identify this module GUID = '3f8561fc-c004-4c8e-b2fc-4a4191504131' # Author of this module Author = 'sirwanafifi' # Company or vendor of this module CompanyName = 'Unknown' # Copyright statement for this module Copyright = '(c) sirwanafifi. All rights reserved.' # Description of the functionality provided by this module Description = 'A module to ping a remote system' # Minimum version of the PowerShell engine required by this module PowerShellVersion = '5.1' # Name of the PowerShell host required by this module # PowerShellHostName = '' # Minimum version of the PowerShell host required by this module # PowerShellHostVersion = '' # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # DotNetFrameworkVersion = '' # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # ClrVersion = '' # Processor architecture (None, X86, Amd64) required by this module # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module # RequiredModules = @() # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module # FormatsToProcess = @() # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = 'Get-PingReply' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. AliasesToExport = '*' # DSC resources to export from this module # DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. # Tags = @() # A URL to the license for this module. # LicenseUri = '' # A URL to the main website for this project. # ProjectUri = '' # A URL to an icon representing this module. # IconUri = '' # ReleaseNotes of this module # ReleaseNotes = '' # Prerelease string of this module # Prerelease = '' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false # External dependent modules of this module # ExternalModuleDependencies = @() } # End of PSData hashtable } # End of PrivateData hashtable # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' }
PS /> Test-ModuleManifest ./PingModule.psd1 ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Script 1.0.0 PingModule Get-PingReply
PS /> Register-PSRepository -Name 'PSLocal' ` >> -SourceLocation "$(Resolve-Path $RepoPath)" ` >> -PublishLocation "$(Resolve-Path $RepoPath)" ` >> -InstallationPolicy 'Trusted'
ProjectRoot | -- PingModule | -- PingModule.psd1 | -- PingModule.psm1
PS /> Publish-Module -Path ./PingModule/ -Repository PSLocal
PS /> Find-Module -Name PingModule -Repository PSLocal Version Name Repository Description ------- ---- ---------- ----------- 0.0.1 PingModule PSLocal Get-PingReply is a.
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
ProjectRoot | -- PingModule | -- 1.0.0 | -- PingModule.psd1 | -- PingModule.psm1 | -- 1.0.1 | -- PingModule.psd1 | -- PingModule.psm1
PS /> Publish-Module -Path ./PingModule/1.0.0 -Repository PSLocal PS /> Publish-Module -Path ./PingModule/1.0.1 -Repository PSLocal
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser PS /> Get-InstalledModule -Name PingModule Version Name Repository Description ------- ---- ---------- ----------- 1.0.1 PingModule PSLocal Get-PingReply is a.
ProjectRoot | -- PingModule | -- 1.0.0 | -- Public/ | -- Private/ | -- PingModule.psd1 | -- PingModule.psm1
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1 foreach ($Script in $ScriptList) { . $Script.FullName } $ScriptList = Get-ChildItem -Path $PSScriptRoot/Private/*.ps1 -Filter *.ps1 foreach ($Script in $ScriptList) { . $Script.FullName }
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
ProjectRoot | -- SignPdf | -- 1.0.0 | -- Public/ | -- dependencies/ | -- BouncyCastle.Crypto.dll | -- System.Drawing.Common.dll | -- Microsoft.Win32.SystemEvents.dll | -- iTextSharp.LGPLv2.Core.dll | -- Set-PDFSingature.ps1 | -- SignPdf.psd1 | -- SignPdf.psm1
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1 foreach ($Script in $ScriptList) { . $Script.FullName } Export-ModuleMember -Function Set-PDFSingature
using namespace iTextSharp.text using namespace iTextSharp.text.pdf using namespace System.IO Function Set-PDFSingature { [CmdletBinding()] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateScript({ if (Test-Path ([Path]::Join($(Get-Location), $_))) { return $true } else { throw "Signature image not found" } if ($_.EndsWith('.pdf')) { return $true } else { throw "File extension must be .pdf" } })] [string]$PdfToSign, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateScript({ if (Test-Path ([Path]::Join($(Get-Location), $_))) { return $true } else { throw "Signature image not found" } if ($_.EndsWith('.png') -or $_.EndsWith('.jpg')) { return $true } else { throw "File extension must be .png or .jpg" } })] [string]$SignatureImage, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [int]$XPos = 130, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [int]$YPos = 50 ) Try { Add-Type -Path "$PSScriptRoot/dependencies/*.dll" $pdf = [PdfReader]::new("$(Get-Location)/$PdfToSign") $fs = [FileStream]::new("$(Get-Location)/$PdfToSign-signed.pdf", [FileMode]::Create) $stamper = [PdfStamper]::new($pdf, $fs) $stamper.AcroFields.AddSubstitutionFont([BaseFont]::CreateFont()) $content = $stamper.GetOverContent(1) $width = $pdf.GetPageSize(1).Width $image = [Image]::GetInstance("$(Get-Location)/$SignatureImage") $image.SetAbsolutePosition($width - $XPos, $YPos) $image.ScaleAbsolute(100, 30) $content.AddImage($image) $stamper.Close() $pdf.Close() $fs.Dispose() } Catch { Write-Host "Error: $($_.Exception.Message)" } }
PS /> Publish-Module -Path ./SignPdf/1.0.0 -Repository PSLocal
PS /> Install-Module -Name SignPdf -Repository PSLocal -Scope CurrentUser
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
کدهای ماژول را میتوانید از اینجا دانلود کنید.
C:\Windows\System32\inetsrv\config\applicationHost.config
جدول خصوصیت ها
خصوصیت | توضیح |
customLogPluginClsid | یک پارامتر رشتهای اختیاری که در آن، آی دی کلاس یا کلاسهایی نوشته میشود که برای custom logging نوشته شدهاند و این گزینه ترتیب اجرای آنها را تعیین میکند. |
directory | اختیاری است. محل ذخیرهی لاگ فایلها را مشخص میکند و در صورتیکه ذکر نشود، همان مسیر پیش فرض است. |
enabled | اختیاری است. فعال بودن سیستم لاگ برای آن سایت را مشخص میکند. مقدار پیش فرض آن true است. |
flushByEntryCountW3CLog | این مقدار مشخص میکند چند رخداد باید اتفاق بیفتد تا عمل ذخیره سازی لاگ صورت گیرد. اگر بعد از هر رخداد عمل ثبت لاگ انجام شود، سرعت ثبت لاگها بالا میرود؛ ولی باعث استفادهی مداوم از منابع و همچنین درخواست ثبت اطلاعات را روی دیسک خواهد داد و تاوان آن با زیاد شدن عملیات روی دیسک، پرداخته خواهد شد. ولی در حالتیکه چند رخداد را نگهداری سپس دستهای ثبت کند، باعث افزایش کارآیی و راندمان سرور خواهد شد. در صورتیکه سرور به مشکلات لحظهای برخورد میکند مقدار آن را کاهش دهید. مقدار پیش فرض 0 است. یعنی اینکه ثبت، بعد از 64000 لاگ خواهد بود. |
localTimeRollover | نحوهی نامگذاری فایلهای لاگ را مشخص میکند که مقدار بولین گرفته و اختیاری است. به طور پیش فرض مقدار false دارد. |
logExtFileFlags | این گزینه در حالتی به کارتان میآید که فرمت W3C را برای ثبت لاگها انتخاب کرده باشید و در اینجا مشخص میکنید که چه فیلدهایی باید در لاگ باشند و اگر بیش از یکی بود میتوان با ، (کاما) از هم جدایشان کرد. |
logFormat | نوع فرمت ذخیره سازی لاگها |
logSiteId | اختیاری است و مقدار پیش فرض آن true است. بدین معنا که کد یا شمارهی سایت هم در لاگ خواهد بود و این در حالتی است که گزارش در سطح سرور باشد. در غیر این صورت اگر هر سایت، جداگانه لاگی برای خود داشته باشد، ذکر نمیگردد. |
logTargetW3C | اختیاری است و و مقدار file و *ETW را میگیرد که به طور پیش فرض روی File تنظیم است. در این حالت فایل لاگها در یک فایل متنی توسط http.sys ذخیره میشود. ولی موقعیکه از ETW استفاده میشود، http.sys با استفاده از iislogprovider دادهها را به سمت ETW ارسال میکند که منجر به اجرای سرویس Logsvc شده که از دادهها کوئری گرفته و آنها را مستقیما از پروسههای کارگر جمع آوری و به سمت فایل لاگ ارسال میکند. همچنین انتخاب این دو گزینه نیز ممکن است. |
maxLogLineLength | حداکثر تعداد خطی که یک لاگ میتواند داشته باشد تا اینکه بتوانید در مصرف دیسک سخت صرفه جویی کنید و بیشتر کاربرد آن برای لاگهای کاستوم است. این عدد باید از نوع Uint باشد و اختیاری است و از 2 تا 65536 مقدار میپذیرد که مقدار پیش فرض آن 65536 میباشد. |
period | همان مبحث زمان بندی در مورد ایجاد فایلهای لاگ است که در مقالهی پیشین برسی کردیم و مقادیر Dialy,Hourly,monthlyو weekly را میپذیرد. همچنین maxsize هم هست؛ موقعی که لاگ به نهایت حجمی که برای آن تعیین کردیم میرسد. |
truncateSize | اختیاری است و مقدار آن از نوع int64 است. حداکثر حجم یک فایل لاگ را مشخص میکند تا اگر period روی maxsize تنظیم شده بود، حداکثر حجم را میتوان از اینجا تعیین نمود. در مقاله پیشین در این باره صحبت کردیم؛ حداقل عدد برای آن 1,048,576 است و اگر کمتر از آن بنویسید، سیستم همین عدد 1,048,576 را در نظر خواهد گرفت. مقدار پیش فرض آن 20971520 می باشد. |
<system.applicationHost> <sites> <siteDefaults> <logFile logFormat="W3C" directory="%SystemDrive%\inetpub\logs\LogFiles" enabled="true"> <customFields> <clear/> <add logFieldName="ContosoField" sourceName="ContosoSource" sourceType="ServerVariable" /> </customFields> </logFile> </siteDefaults> </sites> </system.applicationHost>
تغییر تنظمیات لاگ با Appcmd
appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.enabled:"True" /commit:apphost appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.logFormat:"W3C" /commit:apphost appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.directory:"%SystemDrive%\inetpub\logs\LogFiles" /commit:apphost
تنظمیات تگ لاگ با برنامه نویسی و اسکریپت نویسی
using System; using System.Text; using Microsoft.Web.Administration; internal static class Sample { private static void Main() { using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites"); ConfigurationElement siteDefaultsElement = sitesSection.GetChildElement("siteDefaults"); ConfigurationElement logFileElement = siteDefaultsElement.GetChildElement("logFile"); logFileElement["logFormat"] = @"W3C"; logFileElement["directory"] = @"%SystemDrive%\inetpub\logs\LogFiles"; logFileElement["enabled"] = true; serverManager.CommitChanges(); } } }
با استفاده از اسکریپت نویسی توسط جاوااسکریپت و وی بی اسکریپت هم نیز این امکان مهیاست:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager'); adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"; var sitesSection = adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST"); var siteDefaultsElement = sitesSection.ChildElements.Item("siteDefaults"); var logFileElement = siteDefaultsElement.ChildElements.Item("logFile"); logFileElement.Properties.Item("logFormat").Value = "W3C"; logFileElement.Properties.Item("directory").Value = "%SystemDrive%\\inetpub\\logs\\LogFiles"; logFileElement.Properties.Item("enabled").Value = true; adminManager.CommitChanges();
FTP Logging
IIS را باز کنید و در لیست درختی، سرور را انتخاب کنید. در قسمت FTP میتوانید گزینهی Ftp logging را ببینید. تنظیمات این قسمت هم دقیقا همانند قسمت logging میباشد و همان موارد برای آن هم صدق میکند.
بررسی تگ آن در applicationhost
تگ این نوع لاگ در فایل applicationhost در زیر مجموعهی تگ <site> به شکل زیر نوشته میشود:
<system.ftpServer> <log centralLogFileMode="Central"> <centralLogFile enabled="true" /> </log> </system.ftpServer>
گزینه centralLogFileMode دو مقدار central و site را میپذیرد. اگر گزینهی central انتخاب شود، یعنی همهی لاگها را داخل یک فایل در سطح سرور ثبت کن ولی اگر گزینهی site انتخاب شده باشد، لاگ هر سایت در یک فایل ثبت خواهد شد.
گزینهی logInUTF8 یک خصوصیت اختیاری است که مقدار پیش فرض آن true میباشد. در این حالت باید تمامی رشتهها به انکدینگ UTF-8 تبدیل شوند.
همانطور که میبینید تگ log در بالا یک تگ فرزند هم به اسم centralLogFile دارد که همان خصوصیات جدول بالا در آن مهیاست.
دسترسی به تنظیمات این قسمت توسط دستور Appcmd:
appcmd.exe set config -section:system.ftpServer/log /centralLogFileMode:"Central" /commit:apphost appcmd.exe set config -section:system.ftpServer/log /centralLogFile.enabled:"True" /commit:apphost
دسترسی به تنظیمات این قسمت توسط دات نت:
using System; using System.Text; using Microsoft.Web.Administration; internal static class Sample { private static void Main() { using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection logSection = config.GetSection("system.ftpServer/log"); logSection["centralLogFileMode"] = @"Central"; ConfigurationElement centralLogFileElement = logSection.GetChildElement("centralLogFile"); centralLogFileElement["enabled"] = true; serverManager.CommitChanges(); } } }
دسترسی به تنظیمات این قسمت توسط Javascript:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager'); adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"; var logSection = adminManager.GetAdminSection("system.ftpServer/log", "MACHINE/WEBROOT/APPHOST"); logSection.Properties.Item("centralLogFileMode").Value = "Central"; var centralLogFileElement = logSection.ChildElements.Item("centralLogFile"); centralLogFileElement.Properties.Item("enabled").Value = true; adminManager.CommitChanges();
using System; namespace CS6NewFeatures { class Person { public string FirstName { set; get; } public string LastName { set; get; } public int Age { set; get; } } class Program { static void Main(string[] args) { var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 }; var message = string.Format("Hello! My name is {0} {1} and I am {2} years old.", person.FirstName, person.LastName, person.Age); Console.Write(message); } } }
در C# 6 جهت رفع این مشکلات، راه حلی به نام String interpolation ارائه شدهاست و اگر افزونهی ReSharper یا یکی از افزونههای Roslyn را نصب کرده باشید، به سادگی امکان تبدیل کدهای قدیمی را به فرمت جدید آن خواهید یافت:
در این حالت کد قدیمی فوق، به کد ذیل تبدیل خواهد شد:
static void Main(string[] args) { var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 }; var message = $"Hello! My name is {person.FirstName} {person.LastName} and I am {person.Age} years old."; Console.Write(message); }
عملیاتی که در اینجا توسط کامپایلر صورت خواهد گرفت، تبدیل این کدهای جدید مبتنی بر String interpolation به همان string.Format قدیمی در پشت صحنهاست. بنابراین این قابلیت جدید C# 6 را به کدهای قدیمی خود نیز میتوانید اعمال کنید. فقط کافی است VS 2015 را نصب کرده باشید و دیگر شمارهی دات نت فریم ورک مورد استفاده مهم نیست.
امکان انجام محاسبات با String interpolation
زمانیکه $ در ابتدای رشته قرار گرفت، عبارات داخل {}ها توسط کامپایلر محاسبه و جایگزین میشوند. بنابراین میتوان چنین محاسباتی را نیز انجام داد:
var message2 = $"{Environment.NewLine}Test {DateTime.Now}, {3*2}"; Console.Write(message2);
تغییر فرمت عبارات نمایش داده شده توسط String interpolation
همانطور که با string.Format میتوان نمایش سه رقم جدا کنندهی هزارها را فعال کرد و یا تاریخی را به نحوی خاص نمایش داد، در اینجا نیز همان قابلیتها برقرار هستند و باید پس از ذکر یک : عنوان شوند:
var message3 = $"{Environment.NewLine}{1000000:n0} {DateTime.Now:dd-MM-yyyy}"; Console.Write(message3);
سفارشی سازی String interpolation
اگر متغیر رشتهای معرفی شدهی توسط $ را با یک var مشخص کنیم، نوع آن به صورت پیش فرض، از نوع string خواهد بود. برای نمونه در مثالهای فوق، message و message2 از نوع string تعریف میشوند. اما این رشتههای ویژه را میتوان از نوع IFormattable و یا FormattableString نیز تعریف کرد.
در حقیقت رشتههای آغاز شدهی با $ از نوع IFormattable هستند و اگر نوع متغیر آنها ذکر نشود، به صورت خودکار به نوع FormattableString که اینترفیس IFormattable را پیاده سازی میکند، تبدیل میشوند. بنابراین پیاده سازی این اینترفیس، امکان سفارشی سازی خروجی string interpolation را میسر میکند. برای نمونه میخواهیم در مثال message2، نحوهی نمایش تاریخ را سفارشی سازی کنیم.
class MyDateFormatProvider : IFormatProvider { readonly MyDateFormatter _formatter = new MyDateFormatter(); public object GetFormat(Type formatType) { return formatType == typeof(ICustomFormatter) ? _formatter : null; } class MyDateFormatter : ICustomFormatter { public string Format(string format, object arg, IFormatProvider formatProvider) { if (arg is DateTime) return ((DateTime)arg).ToString("MM/dd/yyyy"); return arg.ToString(); } } }
پس از پیاده سازی این سفارشی کنندهی تاریخ، نحوهی استفادهی از آن به صورت ذیل است:
static string formatMyDate(FormattableString formattable) { return formattable.ToString(new MyDateFormatProvider()); }
در ادامه برای اعمال این سفارشی سازی، فقط کافی است متد formatMyDate را به رشتهی مدنظر اعمال کنیم:
var message2 = formatMyDate($"{Environment.NewLine}Test {DateTime.Now}, {3*2}"); Console.Write(message2);
و اگر تنها میخواهید فرهنگ جاری را عوض کنید، از روش سادهی زیر استفاده نمائید:
public static string faIr(IFormattable formattable) { return formattable.ToString(null, new CultureInfo("fa-Ir")); }
نمونهی کاربردیتر آن اعمال InvariantCulture به String interpolation است:
static string invariant(FormattableString formattable) { return formattable.ToString(CultureInfo.InvariantCulture); }
یک نکته: همانطور که عنوان شد این قابلیت جدید با نگارشهای قبلی دات نت نیز سازگار است؛ اما این کلاسهای جدید را در این نگارشها نخواهید یافت. برای رفع این مشکل تنها کافی است این کلاسهای یاد شده را به صورت دستی در فضای نام اصلی آنها تعریف و اضافه کنید. یک مثال
غیرفعال سازی String interpolation
اگر میخواهید در رشتهای که با $ شروع شده، بجای محاسبهی عبارتی، دقیقا خود آنرا نمایش دهید (و { را escape کنید)، از {{}} استفاده کنید:
var message0 = $"Hello! My name is {person.FirstName} {{person.FirstName}}";
پردازش عبارات شرطی توسط String interpolation
همانطور که عنوان شد، امکان ذکر یک عبارت کامل هم در بین {} وجود دارد (محاسبات، ذکر یک عبارت LINQ، ذکر یک متد و امثال آن). اما در این میان اگر یک عبارت شرطی مدنظر بود، باید بین () قرار گیرد:
Console.Write($"{(person.Age>50 ? "old": "young")}");
ASP.NET MVC #20
تهیه گزارشات تحت وب به کمک WebGrid
WebGrid از ASP.NET MVC 3.0 به صورت توکار به شکل یک Html Helper در دسترس میباشد و هدف از آن سادهتر سازی تهیه گزارشات تحت وب است. البته این گرید، تنها گرید مهیای مخصوص ASP.NET MVC نیست و پروژه MVC Contrib یا شرکت Telerik نیز نمونههای دیگری را ارائه دادهاند؛ اما از این جهت که این Html Helper، بدون نیاز به کتابخانههای جانبی در دسترس است، بررسی آن ضروری میباشد.
صورت مساله
لیستی از کارمندان به همراه حقوق ماهیانه آنها در دست است. اکنون نیاز به گزارشی تحت وب، با مشخصات زیر میباشد:
1- گزارش باید دارای صفحه بندی بوده و هر صفحه تنها 10 ردیف را نمایش دهد.
2- سطرها باید یک در میان دارای رنگی متفاوت باشند.
3- ستون حقوق کارمندان در پایین هر صفحه، باید دارای جمع باشد.
4- بتوان با کلیک بر روی عنوان هر ستون، اطلاعات را بر اساس ستون انتخابی، مرتب ساخت.
5- لینکهای حذف یا ویرایش یک ردیف نیز در این گزارش مهیا باشد.
6- لیست تهیه شده، دارای ستونی به نام «ردیف» نیست. این ستون را نیز به صورت خودکار اضافه کنید.
7- لیست نهایی اطلاعات، دارای ستونی به نام مالیات نیست. فقط حقوق کارمندان ذکر شده است. ستون محاسبه شده مالیات نیز باید به صورت خودکار در این گزارش نمایش داده شود. این ستون نیز باید دارای جمع پایین هر صفحه باشد.
8- تمام اعداد این گزارش در حین نمایش باید دارای جدا کننده سه رقمی باشند.
9- تاریخهای موجود در لیست، میلادی هستند. نیاز است این تاریخها در حین نمایش شمسی شوند.
10- انتهای هر صفحه گزارش باید بتوان برچسب «صفحه y/n» را مشاهده کرد. n در اینجا منظور تعداد کل صفحات است و y شماره صفحه جاری میباشد.
11- انتهای هر صفحه گزارش باید بتوان برچسب «رکوردهای y تا x از n» را مشاهده کرد. n در اینجا منظور تعداد کل رکوردها است.
12- نام کوچک هر کارمند، ضخیم نمایش داده شود.
13- به ازای هر شماره کارمندی، یک تصویر در پوشه images سایت وجود دارد. برای مثال images/id.jpg. ستونی برای نمایش تصویر متناظر با هر کارمند نیز باید اضافه شود.
14- به ازای هر کارمند، تعدادی پروژه هم وجود دارد. پروژههای متناظر را توسط یک گرید تو در تو نمایش دهید.
راه حل به کمک استفاده از WebGrid
ابتدا یک پروژه خالی ASP.NET MVC را آغاز کنید. سپس مدلهای زیر را به آن اضافه نمائید (یک کارمند که میتواند تعداد پروژه منتسب داشته باشد):
using System;
using System.Collections.Generic;
namespace MvcApplication17.Models
{
public class Employee
{
public int Id { set; get; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime AddDate { get; set; }
public double Salary { get; set; }
public IList<Project> Projects { get; set; }
}
}
namespace MvcApplication17.Models
{
public class Project
{
public int Id { set; get; }
public string Name { set; get; }
}
}
سپس منبع داده نمونه زیر را به پروژه اضافه کنید. به عمد از ORM خاصی استفاده نشده تا بتوانید پروژه جاری را به سادگی در یک پروژه آزمایشی جدید، تکرار کنید.
using System;
using System.Collections.Generic;
namespace MvcApplication17.Models
{
public static class EmployeeDataSource
{
public static IList<Employee> CreateEmployees()
{
var list = new List<Employee>();
var rnd = new Random();
for (int i = 1; i <= 1000; i++)
{
list.Add(new Employee
{
Id = i + 1000,
FirstName = "fName " + i,
LastName = "lName " + i,
AddDate = DateTime.Now.AddYears(-rnd.Next(1, 10)),
Salary = rnd.Next(400, 3000),
Projects = CreateRandomProjects()
});
}
return list;
}
private static IList<Project> CreateRandomProjects()
{
var list = new List<Project>();
var rnd = new Random();
for (int i = 0; i < rnd.Next(1, 7); i++)
{
list.Add(new Project
{
Id = i,
Name = "Project " + i
});
}
return list;
}
}
}
در ادامه یک کنترلر جدید را با محتوای زیر اضافه نمائید:
using System.Web.Mvc;
using MvcApplication17.Models;
namespace MvcApplication17.Controllers
{
public class HomeController : Controller
{
[HttpPost]
public ActionResult Delete(int? id)
{
return RedirectToAction("Index");
}
[HttpGet]
public ActionResult Edit(int? id)
{
return View();
}
[HttpGet]
public ActionResult Index(string sort, string sortdir, int? page = 1)
{
var list = EmployeeDataSource.CreateEmployees();
return View(list);
}
}
}
علت تعریف متد index با پارامترهای sort و غیره به URLهای خودکاری از نوع زیر بر میگردد:
http://localhost:3034/?sort=LastName&sortdir=ASC&page=3
همانطور که ملاحظه میکنید، گرید رندر شده، از یک سری کوئری استرینگ برای مشخص سازی صفحه جاری، یا جهت مرتب سازی (صعودی و نزولی بودن آن) یا فیلد پیش فرض مرتب سازی، کمک میگیرد.
سپس یک View خالی را نیز برای متد Index ایجاد کنید. تا اینجا تنظیمات اولیه پروژه انجام شد.
کدهای کامل View را در ادامه ملاحظه میکنید:
@using System.Globalization
@model IList<MvcApplication17.Models.Employee>
@{
ViewBag.Title = "Index";
}
@helper WebGridPageFirstItem(WebGrid grid)
{
@(((grid.PageIndex + 1) * grid.RowsPerPage) - (grid.RowsPerPage - 1));
}
@helper WebGridPageLastItem(WebGrid grid)
{
if (grid.TotalRowCount < (grid.PageIndex + 1 * grid.RowsPerPage))
{
@grid.TotalRowCount;
}
else
{
@((grid.PageIndex + 1) * grid.RowsPerPage);
}
}
<h2>Employees List</h2>
@{
var grid = new WebGrid(
source: Model,
canPage: true,
rowsPerPage: 10,
canSort: true,
defaultSort: "FirstName"
);
var salaryPageSum = 0;
var taxPageSum = 0;
var rowIndex = ((grid.PageIndex + 1) * grid.RowsPerPage) - (grid.RowsPerPage - 1);
}
<div id="container">
@grid.GetHtml(
tableStyle: "webgrid",
headerStyle: "webgrid-header",
footerStyle: "webgrid-footer",
alternatingRowStyle: "webgrid-alternating-row",
selectedRowStyle: "webgrid-selected-row",
rowStyle: "webgrid-row-style",
htmlAttributes: new { id = "MyGrid" },
mode: WebGridPagerModes.All,
columns: grid.Columns(
grid.Column(header: "#",
style: "text-align-center-col",
format: @<text>@(rowIndex++)</text>),
grid.Column(columnName: "FirstName", header: "First Name",
format: @<span style='font-weight: bold'>@item.FirstName</span>,
style: "text-align-center-col"),
grid.Column(columnName: "LastName", header: "Last Name"),
grid.Column(header: "Image",
style: "text-align-center-col",
format: @<text><img alt="@item.Id" src="@Url.Content("~/images/" + @item.Id + ".jpg")" /></text>),
grid.Column(columnName: "AddDate", header: "Start",
style: "text-align-center-col",
format: item =>
{
int ym = item.AddDate.Year;
int mm = item.AddDate.Month;
int dm = item.AddDate.Day;
var persianCalendar = new PersianCalendar();
int ys = persianCalendar.GetYear(new DateTime(ym, mm, dm, new GregorianCalendar()));
int ms = persianCalendar.GetMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));
int ds = persianCalendar.GetDayOfMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));
return ys + "/" + ms.ToString("00") + "/" + ds.ToString("00");
}),
grid.Column(columnName: "Salary", header: "Salary",
format: item =>
{
salaryPageSum += item.Salary;
return string.Format("${0:n0}", item.Salary);
},
style: "text-align-center-col"),
grid.Column(header: "Tax", canSort: true,
format: item =>
{
var tax = item.Salary * 0.2;
taxPageSum += tax;
return string.Format("${0:n0}", tax);
}),
grid.Column(header: "Projects", columnName: "Projects",
style: "text-align-center-col",
format: item =>
{
var subGrid = new WebGrid(
source: item.Projects,
canPage: false,
canSort: false
);
return subGrid.GetHtml(
htmlAttributes: new { id = "MySubGrid" },
tableStyle: "webgrid",
headerStyle: "webgrid-header",
footerStyle: "webgrid-footer",
alternatingRowStyle: "webgrid-alternating-row",
selectedRowStyle: "webgrid-selected-row",
rowStyle: "webgrid-row-style"
);
}),
grid.Column(header: "",
style: "text-align-center-col",
format: item => @Html.ActionLink(linkText: "Edit", actionName: "Edit",
controllerName: "Home", routeValues: new { id = item.Id },
htmlAttributes: null)),
grid.Column(header: "",
format: @<form action="/Home/Delete/@item.Id" method="post"><input type="submit"
onclick="return confirm('Do you want to delete this record?');"
value="Delete"/></form>),
grid.Column(header: "", format: item => item.GetSelectLink("Select"))
)
)
<strong>Page:</strong> @(grid.PageIndex + 1) / @grid.PageCount,
<strong>Records:</strong> @WebGridPageFirstItem(@grid) - @WebGridPageLastItem(@grid) of @grid.TotalRowCount
@*
@if (@grid.HasSelection)
{
@RenderPage("~/views/path/_partial_view.cshtml", new { Employee = grid.SelectedRow })
}
*@
</div>
@section script{
<script type="text/javascript">
$(function () {
$('#MyGrid tbody:first').append(
'<tr class="total-row"><td></td>\
<td></td><td></td><td></td>\
<td><strong>Total:</strong></td>\
<td>@string.Format("${0:n0}", @salaryPageSum)</td>\
<td>@string.Format("${0:n0}", @taxPageSum)</td>\
<td></td><td></td><td></td></tr>');
});
</script>
}
توضیحات ریز جزئیات View فوق
تعریف ابتدایی شیء WebGrid و مقدار دهی آن
در ابتدا نیاز است یک وهله از شیء WebGrid را ایجاد کنیم. در اینجا میتوان تنظیم کرد که آیا نیاز است اطلاعات نمایش داده شده دارای صفحه بندی (canPage) خودکار باشند؟ منبع داده (source) کدام است. در صورت فعال سازی صفحه بندی خودکار، چه تعداد ردیف (rowsPerPage) در هر صفحه نمایش داده شود. آیا نیاز است بتوان با کلیک بر روی سر ستونها، اطلاعات را بر اساس فیلد متناظر با آن مرتب (canSort) ساخت؟ همچنین در صورت نیاز به مرتب سازی، اولین باری که گرید نمایش داده میشود، بر اساس چه فیلدی (defaultSort) باید مرتب شده نمایش داده شود:
@{
var grid = new WebGrid(
source: Model,
canPage: true,
rowsPerPage: 10,
canSort: true,
defaultSort: "FirstName"
);
var salaryPageSum = 0;
var taxPageSum = 0;
var rowIndex = ((grid.PageIndex + 1) * grid.RowsPerPage) - (grid.RowsPerPage - 1);
}
در اینجا همچنین سه متغیر کمکی هم تعریف شده که از اینها برای تهیه جمع ستونهای حقوق و مالیات و همچنین نمایش شماره ردیف جاری استفاده میشود. فرمول نحوه محاسبه اولین ردیف هر صفحه را هم ملاحظه میکنید. شماره ردیفهای بعدی، rowIndex++ خواهند بود.
تعریف رنگ و لعاب گرید نمایش داده شده
در ادامه به کمک متد grid.GetHtml، رشتهای معادل اطلاعات HTML صفحه جاری، بازگشت داده میشود. در اینجا میتوان یک سری خواص تکمیلی را تنظیم نمود. برای مثال:
tableStyle: "webgrid",
headerStyle: "webgrid-header",
footerStyle: "webgrid-footer",
alternatingRowStyle: "webgrid-alternating-row",
selectedRowStyle: "webgrid-selected-row",
rowStyle: "webgrid-row-style",
htmlAttributes: new { id = "MyGrid" },
هر کدام از این رشتهها در حین رندر نهایی گرید، تبدیل به یک class خواهند شد. برای نمونه:
<div id="container">
<table class="webgrid" id="MyGrid">
<thead>
<tr class="webgrid-header">
به این ترتیب با اندکی ویرایش css سایت، میتوان انواع و اقسام رنگها را به سطرها و ستونهای گرید نهایی اعمال کرد. برای مثال اطلاعات زیر را به فایل css سایت اضافه نمائید:
/* Styles for WebGrid
-----------------------------------------------------------*/
.webgrid
{
width: 100%;
margin: 0px;
padding: 0px;
border: 0px;
border-collapse: collapse;
font-family: Tahoma;
font-size: 9pt;
}
.webgrid a
{
color: #000;
}
.webgrid-header
{
padding: 0px 5px;
text-align: center;
border-bottom: 2px solid #739ace;
height: 20px;
border-top: 2px solid #D6E8FF;
border-left: 2px solid #D6E8FF;
border-right: 2px solid #D6E8FF;
}
.webgrid-header th
{
background-color: #eaf0ff;
border-right: 1px solid #ddd;
}
.webgrid-footer
{
padding: 6px 5px;
text-align: center;
background-color: #e8eef4;
border-top: 2px solid #3966A2;
height: 25px;
border-bottom: 2px solid #D6E8FF;
border-left: 2px solid #D6E8FF;
border-right: 2px solid #D6E8FF;
}
.webgrid-alternating-row
{
height: 22px;
background-color: #f2f2f2;
border-bottom: 1px solid #d2d2d2;
border-left: 2px solid #D6E8FF;
border-right: 2px solid #D6E8FF;
}
.webgrid-row-style
{
height: 22px;
border-bottom: 1px solid #d2d2d2;
border-left: 2px solid #D6E8FF;
border-right: 2px solid #D6E8FF;
}
.webgrid-selected-row
{
font-weight: bold;
}
.text-align-center-col
{
text-align: center;
}
.total-row
{
background-color:#f9eef4;
}
همانطور که ملاحظه میکنید، رنگهای ردیفها، هدر و فوتر گرید و غیره در اینجا تنظیم میشوند.
به علاوه اگر دقت کرده باشید در تعاریف گرید، htmlAttributes هم مقدار دهی شده است. در اینجا به کمک یک anonymously typed object، مقدار id گرید مشخص شده است. از این id در حین کار با jQuery استفاده خواهیم کرد.
تعیین نوع Pager
پارامتر دیگری که در متد grid.GetHtml تنظیم شده است، mode: WebGridPagerModes.All میباشد. WebGridPagerModes یک enum با محتوای زیر است و توسط آن میتوان نوع Pager گرید را تعیین کرد:
[Flags]
public enum WebGridPagerModes
{
Numeric = 1,
//
NextPrevious = 2,
//
FirstLast = 4,
//
All = 7,
}
نحوه تعریف ستونهای گرید
اکنون به مهمترین قسمت تهیه گزارش رسیدهایم. در اینجا با مقدار دهی پارامتر columns، نحوه نمایش اطلاعات ستونهای مختلف مشخص میگردد. مقداری که باید در اینجا تنظیم شود، آرایهای از نوع WebGridColumn میباشد و مرسوم است به کمک متد کمکی grid.Columns، اینکار را انجام داد.
متد کمکی grid.Column، یک وهله از شیء WebGridColumn را بر میگرداند و از آن برای تعریف هر ستون استفاده خواهیم کرد. توسط پارامتر columnName آن، نام فیلدی که باید اطلاعات ستون جاری از آن اخذ شود مشخص میشود. به کمک پارامتر header، عبارت سرستون متناظر تنظیم میگردد. پارامتر format، مهمترین و توانمندترین پارامتر متد grid.Column است:
grid.Column(columnName: "FirstName", header: "First Name",
format: @<span style='font-weight: bold'>@item.FirstName</span>,
style: "text-align-center-col"),
grid.Column(columnName: "LastName", header: "Last Name"),
پارامتر format، به نحو زیر تعریف شده است:
Func<dynamic, object> format
به این معنا که هر بار پیش از رندر سطر جاری، زمانیکه قرار است سلولی رندر شود، یک شیء dynamic در اختیار شما قرار میگیرد. این شیء dynamic یک رکورد از اطلاعات Model جاری است. به این ترتیب به اطلاعات تمام سلولهای ردیف جاری دسترسی خواهیم داشت. بر این اساس هر نوع پردازشی را که لازم بود، انجام دهید (شبیه به فرمول نویسی در ابزارهای گزارش سازی، اما اینبار با کدهای سی شارپ) و مقدار فرمت شده نهایی را به صورت یک رشته بر گردانید. این رشته نهایتا در سلول جاری درج خواهد شد.
اگر از پارامتر فرمت استفاده نشود، همان مقدار فیلد جاری بدون تغییری رندر میگردد.
حداقل به دو نحو میتوان پارامتر فرمت را مقدار دهی کرد:
format: @<span style='font-weight: bold'>@item.FirstName</span>
or
format: item =>
{
salaryPageSum += item.Salary;
return string.Format("${0:n0}", item.Salary);
}
مستقیما از توانمندیهای Razor استفاده کنید. مثلا یک تگ کامل را بدون نیاز به محصور سازی آن بین "" شروع کنید. سپس @item به وهلهای از رکورد در دسترس اشاره میکند که در اینجا وهلهای از شیء کارمند است.
و یا همانند روشی که برای محاسبه جمع حقوق هر صفحه مشاهده میکنید، مستقیما از lambda expressions برای تعریف یک anonymous delegate استفاده کنید.
نحوه اضافه کردن ستون ردیف
ستون ردیف، یک ستون محاسبه شده (calculated field) است:
grid.Column(header: "#",
style: "text-align-center-col",
format: @<text>@(rowIndex++)</text>),
نیازی نیست حتما یک grid.Column، به فیلدی در کلاس کارمند اشاره کند. مقدار سفارشی آن را به کمک پارامتر format تعیین خواهیم کرد. هر بار که قرار است یک ردیف رندر شود، یکبار این پارامتر فراخوانی خواهد شد. فرمول محاسبه rowIndex ابتدای صفحه را نیز پیشتر ملاحظه نمودید.
نحوه اضافه کردن ستون سفارشی تصاویر کارمندها
ستون تصویر کارمندها نیز مستقیما در کلاس کارمند تعریف نشده است. بنابراین میتوان آنرا با مقدار دهی صحیح پارامتر format ایجاد کرد:
grid.Column(header: "Image",
style: "text-align-center-col",
format: @<text><img alt="@item.Id" src="@Url.Content("~/images/" + @item.Id + ".jpg")" /></text>),
در این مثال، تصاویر کارمندها در پوشه images واقع در ریشه سایت، قرار دارند. به همین جهت از متد Url.Content برای مقدار دهی صحیح آن استفاده کردیم. به علاوه در اینجا @item.Id به Id رکورد در حال رندر اشاره میکند.
نحوه تبدیل تاریخها به تاریخ شمسی
در ادامه بازهم به کمک پارامتر format، یک وهله از شیء dynamic اشاره کننده به رکورد در حال رندر را دریافت میکنیم. سپس فرصت خواهیم داشت تا بر این اساس، فرمول نویسی کنیم. دست آخر هم رشته مورد نظر نهایی را بازگشت میدهیم:
grid.Column(columnName: "AddDate", header: "Start",
style: "text-align-center-col",
format: item =>
{
int ym = item.AddDate.Year;
int mm = item.AddDate.Month;
int dm = item.AddDate.Day;
var persianCalendar = new PersianCalendar();
int ys = persianCalendar.GetYear(new DateTime(ym, mm, dm, new GregorianCalendar()));
int ms = persianCalendar.GetMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));
int ds = persianCalendar.GetDayOfMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));
return ys + "/" + ms.ToString("00") + "/" + ds.ToString("00");
}),
اضافه کردن ستون سفارشی مالیات
در کلاس کارمند، خاصیت حقوق وجود دارد اما مالیات خیر. با توجه به آن میتوانیم به کمک پارامتر format، به اطلاعات شیء dynamic در حال رندر دسترسی داشته باشیم. بنابراین به اطلاعات حقوق دسترسی داریم و سپس با کمی فرمول نویسی، مقدار نهایی مورد نظر را بازگشت خواهیم داد. همچنین در اینجا میتوان نحوه بازگشت مقدار حقوق را به صورت رشتهای حاوی جدا کنندههای سه رقمی نیز مشاهده کرد:
grid.Column(columnName: "Salary", header: "Salary",
format: item =>
{
salaryPageSum += item.Salary;
return string.Format("${0:n0}", item.Salary);
},
style: "text-align-center-col"),
grid.Column(header: "Tax", canSort: true,
format: item =>
{
var tax = item.Salary * 0.2;
taxPageSum += tax;
return string.Format("${0:n0}", tax);
}),
اضافه کردن گردیدهای تو در تو
متد Grid.GetHtml، یک رشته را بر میگرداند. بنابراین در هر چند سطح که نیاز باشد میتوان یک گرید را بر اساس اطلاعات دردسترس رندر کرد و سپس بازگشت داد:
grid.Column(header: "Projects", columnName: "Projects",
style: "text-align-center-col",
format: item =>
{
var subGrid = new WebGrid(
source: item.Projects,
canPage: false,
canSort: false
);
return subGrid.GetHtml(
htmlAttributes: new { id = "MySubGrid" },
tableStyle: "webgrid",
headerStyle: "webgrid-header",
footerStyle: "webgrid-footer",
alternatingRowStyle: "webgrid-alternating-row",
selectedRowStyle: "webgrid-selected-row",
rowStyle: "webgrid-row-style"
);
}),
در اینجا کار اصلی از طریق پارامتر format شروع میشود. سپس به کمک item.Projects به لیست پروژههای هر کارمند دسترسی خواهیم داشت. بر این اساس یک گرید جدید را تولید کرد و سپس رشته معادل با آن را به کمک متد subGrid.GetHtml دریافت و بازگشت میدهیم. این رشته در سلول جاری درج خواهد شد. به نوعی یک گزارش master detail یا sub report را تولید کردهایم.
اضافه کردن دکمههای ویرایش، حذف و انتخاب
هر سه دکمه ویرایش، حذف و انتخاب در ستونهایی سفارشی قرار خواهند گرفت. بنابراین مقدار دهی header و format متد grid.Column کفایت میکند:
grid.Column(header: "",
style: "text-align-center-col",
format: item => @Html.ActionLink(linkText: "Edit", actionName: "Edit",
controllerName: "Home", routeValues: new { id = item.Id },
htmlAttributes: null)),
grid.Column(header: "",
format: @<form action="/Home/Delete/@item.Id" method="post"><input type="submit"
onclick="return confirm('Do you want to delete this record?');"
value="Delete"/></form>),
grid.Column(header: "", format: item => item.GetSelectLink("Select"))
نکته جدیدی که در اینجا وجود دارد متد item.GetSelectLink میباشد. این متد جزو متدهای توکار گرید است و کار آن بازگشت دادن شیء grid.SelectedRow میباشد. این شیء پویا، حاوی اطلاعات رکورد انتخاب شده است. برای مثال اگر نیاز باشد این اطلاعات به صفحهای ارسال شود، میتوان از روش زیر استفاده کرد:
@if (@grid.HasSelection)
{
@RenderPage("~/views/path/_partial_view.cshtml", new { Employee = grid.SelectedRow })
}
نمایش برچسبهای صفحه x از n و رکوردهای x تا y از z
در یک گزارش خوب باید مشخص باشد که صفحه جاری، کدامین صفحه از چه تعداد صفحه کلی است. یا رکوردهای صفحه جاری چه بازهای از تعداد رکوردهای کلی را تشکیل میدهند. برای این منظور چند متد کمکی به نامهای WebGridPageFirstItem و WebGridPageLastItem تهیه شدهاند که آنها را در ابتدای View ارائه شده، مشاهده نمودید:
<strong>Page:</strong> @(grid.PageIndex + 1) / @grid.PageCount,
<strong>Records:</strong> @WebGridPageFirstItem(@grid) - @WebGridPageLastItem(@grid) of @grid.TotalRowCount
نمایش جمع ستونهای حقوق و مالیات در هر صفحه
گرید توکار همراه با ASP.NET MVC در این مورد راه حلی را ارائه نمیدهد. بنابراین باید اندکی دست به ابتکار زد. مثلا:
@section script{
<script type="text/javascript">
$(function () {
$('#MyGrid tbody:first').append(
'<tr class="total-row"><td></td>\
<td></td><td></td><td></td>\
<td><strong>Total:</strong></td>\
<td>@string.Format("${0:n0}", @salaryPageSum)</td>\
<td>@string.Format("${0:n0}", @taxPageSum)</td>\
<td></td><td></td><td></td></tr>');
});
</script>
}
در این مثال به کمک jQuery با توجه به اینکه id گرید ما MyGrid است، یک ردیف سفارشی که همان جمع محاسبه شده است، به tbody جدول نهایی تولیدی اضافه میشود. از tbody:first هم در اینجا استفاده شده است تا ردیف اضافه شده به گریدهای تو در تو اعمال نشود.
سپس فایل Views\Shared\_Layout.cshtml را گشوده و از section تعریف شده، برای مقدار دهی master page سایت، استفاده نمائید:
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
@RenderSection("script", required: false)
</head>