نظرات مطالب
EF Code First #1
نظرات مطالب
نکاتی در مورد ELMAH
توضیحات بیشتر در اینجا
نظرات مطالب
ASP.NET MVC #13
آیا استفاده از ویژگیها (Attributes یا Data Annotations) خلاف SRP یا Single Responsibility Principle کلاسها است؟
پاسخ: خیر.
توضیحات:
این سؤال از اینجا ناشی میشود که چون برای مثال با استفاده از ویژگی StringLength میتوان طول یک خاصیت رشتهای را مشخص کرد و اگر طول وارد شده توسط کاربر بیش از مقدار تعیین شده باشد، استثنایی صادر میشود یا مکانیزمهای اعتبار سنجی سمت کاربر فعال خواهند شد، بنابراین کلاس تعریف شده در این حالت بیش از یک مسئولیت را عهده دار شده است.
این طرز برداشت صحیح نیست، زیرا ویژگیها (Attributes) هیچ نوع کدی را به کلاس جاری تزریق نمیکنند؛ بلکه این فریم ورک خارجی مورد استفاده است که قابلیت پردازش و تصمیمگیری نهایی را بر اساس متادیتای تعیین شده، دارد. برای مثال این ویژگیها را در یک برنامه ساده کنسول تعریف کنید. هیچ اتفاقی رخ نخواهد داد. زیرا در این حالت متادیتای تعریف شده توسط کتابخانه خارجی خاصی مورد استفاده قرار نمیگیرد.
با استفاده از ویژگیها عملکردی به کلاس مورد نظر اضافه نمیشود؛ بلکه فقط نشانه گذاری صورت میگیرد. این نشانه گذاری هم تنها در صورتیکه یک کتابخانه خارجی که قابلیت درک آنرا دارد وجود خارجی داشته باشد، مورد استفاده قرار خواهد گرفت.
بنابراین استفاده از ویژگیها ناقض SRP نیست و در اینجا مسئولیت پردازش نشانه گذاریهای انجام شده به یک کلاس یا فریم ورک خارجی واگذار میشود.
پاسخ: خیر.
توضیحات:
این سؤال از اینجا ناشی میشود که چون برای مثال با استفاده از ویژگی StringLength میتوان طول یک خاصیت رشتهای را مشخص کرد و اگر طول وارد شده توسط کاربر بیش از مقدار تعیین شده باشد، استثنایی صادر میشود یا مکانیزمهای اعتبار سنجی سمت کاربر فعال خواهند شد، بنابراین کلاس تعریف شده در این حالت بیش از یک مسئولیت را عهده دار شده است.
این طرز برداشت صحیح نیست، زیرا ویژگیها (Attributes) هیچ نوع کدی را به کلاس جاری تزریق نمیکنند؛ بلکه این فریم ورک خارجی مورد استفاده است که قابلیت پردازش و تصمیمگیری نهایی را بر اساس متادیتای تعیین شده، دارد. برای مثال این ویژگیها را در یک برنامه ساده کنسول تعریف کنید. هیچ اتفاقی رخ نخواهد داد. زیرا در این حالت متادیتای تعریف شده توسط کتابخانه خارجی خاصی مورد استفاده قرار نمیگیرد.
با استفاده از ویژگیها عملکردی به کلاس مورد نظر اضافه نمیشود؛ بلکه فقط نشانه گذاری صورت میگیرد. این نشانه گذاری هم تنها در صورتیکه یک کتابخانه خارجی که قابلیت درک آنرا دارد وجود خارجی داشته باشد، مورد استفاده قرار خواهد گرفت.
بنابراین استفاده از ویژگیها ناقض SRP نیست و در اینجا مسئولیت پردازش نشانه گذاریهای انجام شده به یک کلاس یا فریم ورک خارجی واگذار میشود.
مزیت استفاده از PartialViewها، ماژولار کردن برنامه است. برای مثال اگر
صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا، سخن روز و آمار
کاربران تشکیل شود، میتوان هر کدام را توسط یک PartialView پیاده سازی کرد
و سپس صفحه اصلی را از کنار هم قرار دادن این PartialViewها تهیه نمود.(منبع).
در این پست قصد دارم به نحوه بارگزاری یک PartialView با استفاده از ASP.NET MVC بپردازم.
ابتدا یک پروژه جدید ایجاد کنید. حال میخواهیم زمانیکه صفحه اصلی سایت بارگزاری میشود، لیست تمام محصولات را نمایش دهد. برای نمایش محصولات یک PartialView جدید را ایجاد میکنیم؛ همانند شکل ذیل:
در ادامه قصد داریم که درIndex، این View را بارگزاری کنیم. برای این کار از متد ajax مربوط به کتابخانه jQuery به صورت زیر استفاده میکنیم:
در این پست قصد دارم به نحوه بارگزاری یک PartialView با استفاده از ASP.NET MVC بپردازم.
ابتدا یک پروژه جدید ایجاد کنید. حال میخواهیم زمانیکه صفحه اصلی سایت بارگزاری میشود، لیست تمام محصولات را نمایش دهد. برای نمایش محصولات یک PartialView جدید را ایجاد میکنیم؛ همانند شکل ذیل:
حال برای ساده کردن مثال، متن ثابتی را درون این PartialView مینویسیم:
product List Partial
$(function () { $.ajax({ //مشخص کردن اکشنی که باید فراخوانی شود url: '/Home/Details', contentType: 'application/html; charset=utf-8', type: 'GET', //نوع نتیجه بازگشتی dataType: 'html' }) .success(function (result) { //زمانی که کدهای سمت سرور بدون خطا اجرا شده اند //این قسمت فراخوانی میشود و نتیجه اکشن درون متغیر //result //قرار میگیرد $('#sectionContents').html(result); }) .error(function (xhr, status) { alert(xhr.responseText); }); });
و پس از آن محلی را که قرار است PartialView در آن بارگزاری شود، ایجاد میکنیم:
حال فقط باید اکشن مورد نظر را در HomeController پیاده سازی کنیم:
با استفاده از این کد مشخص کردیم که این اکشن، یک PartialView را با نام ProductList_ برگشت میدهد. البته در یک مثال واقعی باید لیست محصولات را هم به این PartialView پاس دهیم.
نتیجه اجرا به صورت زیر است:
<div id="sectionContents"></div>
public ActionResult Details() { return PartialView("partial/_ProductList"); }
نتیجه اجرا به صورت زیر است:
توسط ماژولها میتوانیم یک مجموعه از دستورات را گروهبندی کنیم و تحت عنوان یک پکیج ارائه دهیم که برای دیگران نیز قابل استفاده باشند. برای ایجاد یک ماژول کافی است اسکریپتهای خود را درون یک فایل با پسوند psm1 قرار دهیم؛ به این فایل اصطلاحاً root module گفته میشود. در واقع میتوان گفت ماژولها یک روش مناسب برای به اشتراکگذاری اسکریپتها میباشند. تا اینجا با کمک پروفایلها توانستیم امکان استفاده مجدد از توابع و اسکریپتها را داشته باشیم؛ ماژولها نیز یک روش دیگر برای بارگذاری اسکریپتها درون شل هستند. زمانیکه شل را باز میکنیم PowerShell به صورت خودکار یکسری مسیر را برای بارگذاری ماژولها اسکن میکند. توسط متغیر env:PSModulePath$ میتوانیم لیست این مسیرها را ببینیم:
همانطور که عنوان شد برای ایجاد یک ماژول کافی است اسکریپتهای خود را داخل یک فایل با پسوند psm1 ذخیره کنیم. به عنوان مثال میتوانیم تابع Get-PingReply را درون یک فایل با نام PingModule.psm1 ذخیره و سپس توسط دستور Import-Module ماژول را ایمپورت کنیم:
سپس توسط دستور Get-Module PingModule میتوانیم جزئیات ماژول ایمپورت شده را مشاهده نمائیم:
به صورت پیشفرض تمام توابع درون اسکریپت export خواهند شد. اگر ExportedCommands خالی باشد به این معنا است که ماژول به درستی ایمپورت نشدهاست. به عنوان مثال اگر سعی کنید فایل قبل را با پسوند ps1 به عنوان ماژول ایمپورت کنید. خطایی هنگام ایمپورت کردن مشاهده نخواهید کرد و قسمت ExportedCommands خالی خواهد بود. در اینحالت نیز امکان استفاده از تابع درون اسکریپت را خواهیم داشت؛ اما هیچ تضمینی نیست که به صورت یک ماژول به درستی عمل کند. بنابراین بهتر است ماژولهایی که ایجاد میکنیم حتماً پسوند psm1 داشته باشند.
ممکن است بخواهیم یکسری توابع را به صورت private تعریف کنیم و فقط تعداد محدودی از توابع به صورت public باشند. در این حالت میتوانیم درون فایل psm1 با کمک دستور Export-ModuleMember اینکار را انجام دهیم:
تنها پراپرتی موردنیاز برای ایجاد module manifest پراپرتی Path میباشد. این پراپرتی به فایلی که متادیتا قرار است درون آن ایجاد شود اشاره دارد. همانطور که مشاهده میکنید یکسری پراپرتی دیگر نیز اضافه کردهایم و توسط splatted hash table (با کمک @) به دستور New-ModuleManifest ارسال کردهایم. به این معنا که کلیدها و مقادیر درون hash table به جای اینکه یکجا به عنوان یک آبجکت به دستور موردنظر ارسال شوند، به صورت جدا پاس داده شدهاند. اما اگر از $ استفاده میکردیم:
با خطای زیر مواجه میشدیم:
در نهایت فایل manifest در مسیر تعیین شده ایجاد خواهد شد:
هر ماژول باید یک آیدی منحصر به فرد داشته باشد که به صورت Guid توسط یک پراپرتی با همین نام تعیین میشود. برای هر پراپرتی درون این فایل توضیحی به صورت کامنت نوشته شده است؛ اما برای دیدن جزئیات کامل میتوانید به اینجا مراجعه نمائید. در اینجا RootModule به PingModule.psm1 تنظیم شده است. تنظیم این پراپرتی نحوه نمایش module type را در خروجی Get-Module مشخص میکند. این مقدار اگر به فایل ماژول (psm1) اشاره کند، نوع ماژول script در نظر گرفته میشود، اگر به یک DLL اشاره کند به binary و در نهایت اگر به یک فایلی با پسوند دیگری اشاره کند manifest در نظر گرفته میشود. همچنین درون فایل فوق یکسری پراپرتی مانند CmdletsToExport, VariablesToExport, AliasesToExport به صورت خودکار تنظیم شدهاند. در نهایت برای تست صحت فایل میتوانیم از دستور Test-ModuleManifest استفاده کنیم:
در کد فوق میتوانید مقدار متغیر RepoPath را به محل مدنظر روی سیستمتان تنظیم کنید. ساختار ماژولی که میخواهیم پابلیش کنیم نیز اینچنین خواهد بود:
در ادامه برای پابلیش ماژول فوق درون ProjectRoot دستور Publish-Module را اینگونه اجرا خواهیم کرد:
خروجی دستور فوق یک فایل با پسوند nupkg در مسیر مخزنی است که معرفی کردیم. همچنین با کمک دستور Find-Module میتوانیم ماژول موردنظر را لیست کنیم:
برای نصب ماژول روی یک سیستم دیگر نیز ابتدا باید مطمئن شوید که سیستم مقصد، مخزن لوکال را تعریف کرده باشد و در نهایت توسط دستور Install-Module میتوانیم ماژول موردنظر را نصب کنیم:
همچنین امکان پابلیش کردن ورژنهای متفاوت را نیز خواهیم داشت:
برای پابلیش کردن هر کدام از ماژولهای فوق باید به ازای هر کدام یکبار دستور Publish-Module را اجرا کنیم:
اکنون حین نصب ماژول میتوانیم ورژن را نیز تعیین کنیم؛ در غیراینصورت آخرین ورژن یعنی 1.0.1 نصب خواهد شد:
در اینحالت باید اسکریپتها و فایلهای موردنیاز را توسط dot sourcing درون فایل ماژول بارگذاری کنیم:
بنابراین ساختار پروژه را اینگونه ایجاد خواهیم کرد:
برای استفاده از پکیج ذکر شده نیاز خواهد بود که Dllهای موردنیاز را نیز به عنوان وابستگی به پروژه اضافه کنیم (محتویات پوشه dependencies) سپس درون فایل ماژول (SignPdf.psm1) همانطور که عنوان شد میبایست اسکریپتها درون پوشه Public را بارگذاری کنیم و همچنین درون تابعی که قرار است Export شود را نیز تعیین کردهایم (Set-PDFSingature)
در ادامه درون فایل Set-PDFSignature پیادهسازی را انجام خواهیم داد:
روال قرار دادن یک امضاء بر روی یک فایل PDF قبلاً در سایت توضیح داده شده است. کدهای فوق در واقع معادل PowerShell همان کدهای موجود در سایت هستند و نکته خاصی ندارند. در نهایت میتوانیم ماژول تهیه شده را روی مخزن موردنظر پابلیش کنیم:
برای نصب ماژول میتوانیم از دستور Install-Module استفاده کنیم:
در نهایت برای استفاده از ماژول ایجاد شده میتوانیم اینگونه عمل کنیم:
خروجی نیز فایل امضاء شده خواهد بود:
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' )
اضافه کردن متادیتا به ماژولها
برای هر ماژول میتوانیم با کمک module manifest یکسری متادیتا به ماژول اضافه کنیم. این متادیتا درون یک فایل با پسوند psd1 که محتویات آن در واقع یک Hashtable است ذخیره میشود. برای ایجاد فایل manifest میتوانیم از دستور New-ModuleManifest استفاده کنیم. به عنوان مثال برای ایجاد فایل manifest برای ماژول PingModule.psm1 اینگونه عمل خواهیم کرد:
$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
پابلیش ماژول
در نهایت توسط Publish-Module میتوانیم ماژول موردنظرمان را درون یک مخزن PowerShell پابلیش کنیم. رایجترین مخزن برای انتشار پکیجهای PowerShell یک فید نیوگت (مانند PowerShell Gallery) است. همچنین میتوانیم یک مخزن لوکال نیز برای پابلیش پکیجها نیز ایجاد کنیم و خروجی نهایی را به صورت یک فایل با پسوند nupkg با دیگران به اشتراک بگذاریم. برای اینکار ابتدا نیاز است یک مخزن لوکال را به PowerShell معرفی کنیم:
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.
ساختار مناسب برای ایجاد ماژولها
برای توسعه ماژولهای PowerShell توصیه میشود که از ساختار Multi-file layout استفاده شود به این معنا که بخشهای مختلف پروژه به قسمتهای کوچکتر و قابلنگهداری تقسیم شوند:
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 }
یک مثال عملی
در ادامه میخواهیم یک ماژول تهیه کنیم که قابلیت امضاء روی PDF را با کمک کتابخانه iTextSharp.LGPLv2.Core انجام دهیم و به شکل زیر برای کاربر قابل استفاده باشد:
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"
کدهای ماژول را میتوانید از اینجا دانلود کنید.
در هنگام گفتگو با افراد مختلفی که در پروژههای توسعه نرم افزار، نقشهای مختلفی را دارا میباشند، یکی از جالبترین و اساسیترین بحثها تفاوت بین Desktop App و Web App میباشد، و این که پروژه بر اساس کدام مدل باید نوشته شود.
در اینترنت و در منابع معتبر، تفسیرهای متفاوتی از این دو وجود دارد، که گاه دقیقا با نظر من یکی بوده و گاه تا 180 درجه بر عکس هستند، آنچه که در ادامه میخوانید میتواند لزوما نظر شما نباشد.
گروهی از افراد بر این باور هستند که اجرای برنامه در محیط مرورگر (ظاهر مرورگر و نه Sandbox آن)، یکی از ملاکهای ما بین Desktop App و Web App است، گروهی دیگر نیز اجرا شدن برنامه بر روی بستر اینترنت و یا شبکهی محلی را جزو ملاکها میدانند، و گروهی دیگر نیز زبان برنامه نویسی برنامه را ملاک میدانند، برای مثال اگر با HTML/JS باشد Web App است، اگر نه Desktop App است.
اما آنچه که در عمل میتواند تفاوت بین یک Desktop App را با یک Web App مشخص کند، رفتار و عملکرد خود آن برنامه است، نه بستر اجرای آن و این که آن رفتار منتج شده از چه کدی و چه زبان برنامه نویسی ای است.
اگر کمی دقیق به مطلب نگاه کنیم، میبینیم این که یک برنامه در چارچوب ظاهری یک مرورگر (نه Sandbox آن) اجرا شود، اصلا مقوله ای اهمیت دار نیست، کما این که برای مثال Silverlight اجازه میدهد، برنامه هم در داخل مرورگر و هم در بیرون از آن اجرا شود، و این کار با یک کلیک امکانپذیر است، آیا با همین یک کلیک برنامه از Web App به Desktop App تبدیل میشود یا بالعکس ؟
آیا یک برنامه مبتنی بر دلفی که تا همین یک ساعت پیش بر روی شبکه محلی در حال اجرا بوده، با انتقال پیدا کردن آن بر روی شبکهی اینترنت، تبدیل به یک Web App میشود؟
آیا اگر ما با HTML/JS یک برنامه Native برای ویندوز فون بنویسیم که تک کاربره آفلاین باشد و اصلا سروری هم نداشته باشد، آیا Web App نوشته ایم ؟
اصلیترین تفاوت مابین Web App و Desktop App که به تفاوت در عملکرد آنها و مزایا و معایب آنها منجر میشود، این است که انجام کارهایی که اپراتور با آنها در سمت کلاینت و سیستم مشتری سر و کار دارد، در کجا صورت میپذیرد؟
برای مثال در نظر بگیرید که یک دیتاگرید داریم که دارای Paging است، و ما از Page اول به Page بعدی میرویم، در یک Desktop App تنها اطلاعات از سرور گرفته میشود، و ترسیم خطوط و ستونها و ردیفها و ظاهر نمایشی دیتاگرید بر عهده کلاینت است، برای مثال اگر ستون قیمت داشته باشیم، و بخواهیم برای ردیف هایی که قیمت آنها زیر 10000 ریال است، قیمت به شکل سبز رنگ نمایش داده شود و برای بقیه ردیفها به رنگ قرمز باشد، پردازش این مسئله و این if به عهده کلاینت است، اما در یک Web App، علاوه بر اطلاعات، تعداد زیادی tagهای مختلف، مانند table - tr - td و ... نیز به همراه اطلاعات آورده میشوند، که وظیفه نمایش ظاهری اطلاعات را بر عهده دارند، و آن if مثال ما یعنی رنگ سبز و قرمز در سمت سرور مدیریت شده است، و کلاینت در اینجا نمایش دهندهی آن چیزی است که به صورت آماده از سرور آورده شده است.
در برنامههای Desktop آنچه که در سمت سرور وجود دارد، برای مثال یک WCF Service یا ASP.NET Web API است که فقط به رد و بدل کردن اطلاعات میپردازد، اما در Web Appها در سمت سرور ASP.NET Web Forms، ASP.NET MVC و PHP وجود دارند که علاوه بر اطلاعات برای کلاینت شما ظاهر صفحات را نیز آماده میکنند، و ظاهر اصلی صفحات از سمت سرور به سیستم مشتری ارسال میشوند، اگر چه که ممکن است در سمت کلاینت تغییراتی را داشته باشند.
به هر میزان رفتار برنامه ما شبیه به حالت اول باشد، برنامه ما Desktop App بوده و به هر میزان برنامه ما به حالت دوم نزدیکتر باشد، برنامه ما Web App است.
مزیت اصلی Web Appها در عدم انداختن بار زیاد بر روی دوش کلاینتهای بعضا نحیف بوده، و عملا کلاینت به علت این که کار خاصی را انجام نمیدهد، پیش نیاز نرم افزاری و یا سخت افزاری خاصی احتیاج ندارد، و این مورد Web Appها را به یک گزینه ایده آل برای وب سایت هایی تبدیل کرده است که با عموم مردم در ارتباطند، زیرا که امکان ارائه آسان برنامه وجود دارد و تقریبا همه میتوانند از آن استفاده کنند.
با توجه به شناخت عموم از برنامههای Web App به توضیح بیشتر برنامههای Desktop App میپردازم.
مزیت اصلی Desktop Appها در سرعت عمل بالاتر(به علت این که فقط دیتا را رد و بدل میکند)، توانایی بیشتر در استفاده از منابع سیستمی مانند سرویس نوشتن، و امکانات محلی مانند ارائه Notification و ... است، و در کنار آن برای مثال یک Desktop App میتواند به نحوی طراحی شود که به صورت Offline نیز کار کند.
این مزیتها باعث میشود که Desktop Appها گزینه ای مناسب برای برنامههای سازمانی باشند.
ضعفی که از گذشته در Desktop Appها وجود داشته است، که البته به معماری Desktop App بر نمیگردد، بلکه متاثر از امکانات است، عدم Cross Platform بودن آنها بوده است، تا آنجا که Desktop App در نظر خیلی از افراد همان نوشتن برنامه برای سیستم عامل ویندوز است.
با توجه به رویکرد جدی ای که در طول دو سال اخیر برای نوشتن برنامه Desktop App به شکل Cross Platform رخ داده است، خوشبختانه این مشکل حل شده است و اکنون لااقل دو راهکار جدی برای نوشتن یک برنامه Cross Platform با ویژگیهای Desktop وجود دارد، که یکی از آنها راه حلهای مبتنی بر HTML/JS است و دیگری راه حلهای مبتنی بر C#/XAML
در راه حلهای مبتنی بر HTML/JS در صورتی که شما برنامه را به شکل Web App طراحی نکرده باشید، و برای مثال در آن از ASP.NET Web Forms و ASP.NET MVC، PHP و ... استفاده نکرده باشید، میتوانید یک خروجی کاملا Native با تمامی ویژگیهای Desktop App برای انواع پلتفرمها بگیرید.
استفاده از فریم ورک هایی که با طراحی Desktop App سازگار هستند، مانند Angular JS، Kendo UI و Ext JS، Jay-data و ... و استفاده از مدل طراحی Single Page Application میتواند سیستم کدنویسی ای ساده را فراهم آورد، که در آن شما با یک بار نوشتن برنامه میتوانید خروجی اکثر پلتفرمهای مطرح را داشته باشید، اعم از ویندوز فون، اندروید، iOS و ویندوز
امروزه حتی مرورگرها با فراهم آوردن امکاناتی مانند Client side databases و Manifest based deployment اجازه نوشتن برنامه Desktop با HTML/JS را که حتی میتواند Offline کار کند را به شما ارائه میکنند.
در کنار این راهکار، استفاده از C#/XAML برای نوشتن برنامه برای اکثر پلتفرمهای مطرح بازار اعم از اندروید، iOS و Windows Phone و ویندوز، نیز به عنوان راهکاری دیگر قابلیت استفاده را دارا است.
حرکت پر شتاب و پر انرژی جهانی برای توسعه Cross Platform Desktop Development، خوشبختانه توانسته است تا حد زیادی امتیاز نوشتن برنامههای Desktop را در سیستمهای Enterprise بالا ببرد.
مطالب
خواندنیهای 4 شهریور
آفیس
اس کیوال سرور
الگوهای طراحی برنامه نویسی شیءگرا
توسعه وب
دات نت فریم ورک
متفرقه
محیطهای مجتمع توسعه
مرورگرها
ویندوز