گاهی از اوقات شاید نیاز شود تا از یک کنترل Active-X در WPF استفاده شود؛ مثلا هیچ نمایش دهندهی PDF ایی را در ویندوز نمیتوان یافت که امکانات و کیفیت آن در حد Acrobat reader و Active-X آن باشد. یک روش استفاده از آنرا به کمک کنترل WebBrowser در WPF پیشتر در این سایت مطالعه کردهاید. روش معرفی شده برای WinForm هم در WPF قابل استفاده است که در ادامه شرح آن خواهد آمد.
الف) بجای اضافه کردن یک User control مخصوص WPF یک user control از نوع WinForms را به یک پروژه WPF اضافه کنید.
سپس مراحل مشابهی را مانند حالت WinForms، باید طی کرد:
ب) در VS.NET از طریق منوی Tools گزینهی Choose toolbox items ، برگهی Com components را انتخاب کنید.
ج) سپس گزینهی Adobe PDF reader را انتخاب نمائید و بر روی دکمهی OK کلیک کنید.
د) اکنون این کنترل جدید را بر روی فرم user control قسمت الف برنامه قرار دهید. به صورت خودکار COMReference های متناظر هم به پروژه اضافه میشوند.
پس از اینکه کنترل بر روی فرم قرار گرفت بهتر است به خواص آن مراجعه کرده و خاصیت Dock آنرا با Fill مقدار دهی کرد تا کنترل به صورت خودکار در هر اندازهای کل ناحیهی متناظر را پوشش دهد.
کدهای مرتبط با نمایش فایل PDF این کنترل هم به شرح زیر است:
using System.Windows.Forms;
namespace WpfPdfViewer.Controls
{
public partial class AcroReader : UserControl
{
public AcroReader(string fileName)
{
InitializeComponent();
ShowPdf(fileName);
}
public void ShowPdf(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName)) return;
axAcroPDF1.LoadFile(fileName);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}
خوب، ما تا اینجا یک کنترل Active-X را از طریق یک User controls مخصوص WinForms به پروژهی WPF جاری اضافه کردهایم. برای اینکه بتوانیم این کنترل را درون مثلا یک User control از جنس WPF و XAML نمایش دهیم باید از کنترل WindowsFormsHost استفاده کرد. برای این منظور نیاز است تا ارجاعی را به اسمبلی WindowsFormsIntegration اضافه کنیم. پس از آن کنترل یاد شده قابل استفاده خواهد بود.
برای نمونه کدهای XAML پنجره اصلی برنامه میتواند به صورت زیر باشد:
<Window x:Class="WpfPdfViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<WindowsFormsHost x:Name="WindowsFormsHost1" />
</Grid>
</Window>
سپس جهت استفاده از کنترل WindowsFormsHost خواهیم داشت:
using WpfPdfViewer.Controls;
namespace WpfPdfViewer
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
WindowsFormsHost1.Child = new AcroReader(@"PageSummary.pdf");
}
}
}
فقط کافی است شیء Child این کنترل را با وهلهای از یوزرکنترل AcroReader اضافه شده به برنامه مقدار دهی کنیم.
سؤال: این روش زیاد MVVM friendly نیست. به عبارتی Child را نمیتوان از طریق Binding مقدار دهی کرد. آیا راهی برای آن وجود دارد؟
پاسخ: بله. روش متداول برای حل این نوع مشکلات، نوشتن یک DependencyObject و Attached property مناسب میباشد که به آنها Behaviors هم میگویند. برای مثال یک نمونه از این پیاده سازی را در ذیل مشاهده میکنید:
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace WpfPdfViewer.Behaviors
{
public class WindowsFormsHostBehavior : DependencyObject
{
public static readonly DependencyProperty BindableChildProperty =
DependencyProperty.RegisterAttached("BindableChild",
typeof(Control),
typeof(WindowsFormsHostBehavior),
new UIPropertyMetadata(null, BindableChildPropertyChanged));
public static Control GetBindableChild(DependencyObject obj)
{
return (Control)obj.GetValue(BindableChildProperty);
}
public static void SetBindableChild(DependencyObject obj, Control value)
{
obj.SetValue(BindableChildProperty, value);
}
public static void BindableChildPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var windowsFormsHost = o as WindowsFormsHost;
if (windowsFormsHost == null)
throw new InvalidOperationException("This behavior can only be attached to a WindowsFormsHost.");
var control = (Control)e.NewValue;
windowsFormsHost.Child = control;
}
}
}
که نهایتا برای استفاده از آن خواهیم داشت:
<WindowsFormsHost
Behaviors:WindowsFormsHostBehavior.BindableChild="{Binding ...}" />
و در ViewModel برنامه هم مانند مثال فوق، فقط کافی است یک وهله از new AcroReader به این خاصیت قابل انقیاد از نوع Control، انتساب داده شود.
یا حتی میتوان بجای نوشتن یک BindableChild، برای مثال مسیر فایل pdf را به DependencyObject تعریف شده ارسال کرد و سپس در همانجا این وهله سازی و انتسابات صورت گیرد (بجای ViewModel برنامه که اینبار فقط مسیر را تنظیم میکند).
بررسی یک مثال:
ابتدا کلاسی به صورت زیر ایجاد نمایید. در این کلاس میتوانید کدهای لازم برای سفارشی کردن شی ServiceHost را قرار دهید:
public class CustomServiceHost : ServiceHost { public CustomServiceHost( Type t, params Uri baseAddresses ) : base( t, baseAddresses ) {} public override void OnOpening() { this.Description.Add( new MyServiceBehavior() ); } }
public class CustomServiceHostFactory : ServiceHostFactory { public override ServiceHost CreateServiceHost( Type t, Uri[] baseAddresses ) { return new CustomServiceHost( t, baseAddresses ) } }
<%@ ServiceHost Language="C#" Debug="true" Service="WcfService1.Service1" CodeBehind="Service1.svc.cs" %>
<%@ ServiceHost Language="C#" Debug="true" Factory="CustomServiceHostFactory" Service="WcfService1.Service1" CodeBehind="Service1.svc.cs" %>
از این به بعد عملیات وهله سازی از سرویس بر اساس تنظیمات پیش فرض صورت گرفته در این کلاسها انجام میگیرد.
- Testing with a mocking framework - EF6 onwards
+ شخصا اعتقادی به Unit tests درون حافظهای، در مورد لایه دسترسی به دادهها ندارم. به قسمت «Limitations of EF in-memory test doubles» مراجعه کنید؛ توضیحات خوبی را ارائه دادهاست.
تست درون حافظهی LINQ to Objects با تست واقعی LINQ to Entities که روی یک بانک اطلاعاتی واقعی اجرا میشود، الزاما نتایج یکسانی نخواهد داشت (به دلیل انواع قیود بانک اطلاعاتی، پشتیبانی از SQL خاص تولید شده تا بارگذاری اشیاء مرتبط و غیره) و نتایج مثبت آن به معنای درست کار کردن برنامه در دنیای واقعی نخواهد بود. در اینجا Integration tests بهتر جواب میدهند و نه Unit tests.
در نگارشهای قبلی، پس از اجرای برنامه، صرفا یک سطر زیر نمایش داده میشد:
Now listening on: http://localhost:5000
Now listening on: https://localhost:5001 Now listening on: http://localhost:5000
علت هدایت خودکار به آدرس HTTPS، به تغییرات فایل آغازین برنامه بر میگردد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection();
اگر خواستید این شمارهی پورت را تغییر دهید، میتوانید به صورت زیر عمل کنید:
services.AddHttpsRedirection(options => options.HttpsPort = 5002);
services.AddHsts(options => { options.MaxAge = TimeSpan.FromDays(100); options.IncludeSubDomains = true; options.Preload = true; });
اما چرا برنامه در حالت HTTPS قابل مشاهده نیست؟
پس از نصب SDK نگارش جدید NET Core.، یک مجوز SSL توسعه نیز به سیستم عامل اضافه میشود:
ASP.NET Core ------------ Successfully installed the ASP.NET Core HTTPS Development Certificate.
برای مشاهدهی این مجوز، دستور certmgr.msc را در قسمت run ویندوز وارد کرده و enter کنید:
این مجوز پیش فرض در قسمت «Personal/Certificates» با نام «ASP.NET Core HTTPS development certificate» قابل مشاهدهاست که در حقیقت یک Self Signed Certificate است و به صورت پیش فرض توسط سیستم معتبر و قابل اطمینان شناخته نمیشود.
برای اعلام قابل اطمینان بودن این مجوز به سیستم، در همین کنسول مدیریت مجوزها، بر روی این مجوز کلیک راست کرده و آنرا کپی کنید. سپس آنرا در مسیر «Trusted Root Certification Authorities/Certificates» قرار دهید (paste کنید).
در این حالت صفحه دیالوگ فوق ظاهر میشود. آنرا تائید کنید تا این مجوز توسعه، به قسمت مجوزهای امن و معتبر سیستم اضافه شود.
روش دوم انجام اینکار، استفاده از دستور زیر است:
dotnet install tool dotnet-dev-certs -g --version 2.1.0-preview1-final dotnet dev-certs https --trust
پس از اینکار اگر مرورگر را ریفرش کنید، باز هم همان خطای قبلی نمایش داده میشود. برای رفع این مشکل باید یکبار مرورگر را کاملا بسته و مجددا اجرا کنید تا مجوز جدید را دریافت کند:
تنظیمات مخصوص IIS Express برای اجرای برنامههای ASP.NET Core 2.1
دستور «dotnet run» از IIS برای اجرا استفاده نمیکند و مبتنی بر وب سرور Kestrel است. تنظیمات IIS و IIS Express از فایل Properties\launchSettings.json خوانده میشوند که اینبار به صورت زیر تغییر کردهاست:
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:4929", "sslPort": 44313 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_HTTPS_PORT": "44313" } }, "TestWebApp": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000" } } } }
بررسی تغییرات Blazor 8x - قسمت هشتم - مدیریت انتقال اطلاعات Pre-Rendering سمت سرور، به جزایر تعاملی
بررسی نحوهی عملکرد سرویس PersistentComponentState
سرویس PersistentComponentState، در داتنت 6، به Blazor اضافه شد و امکان جدیدی نیست. قسمتی از این مباحث جدید SSR که بهنظر مختص به Blazor 8x هستند، پیشتر هم وجود داشتند؛ تحت عنوان pre-rendering. برای مثال فقط کافی بودن تا در برنامههای Blazor Server قبلی، فایل Host.cshtml_ را به صورت زیر ویرایش کرد تا pre-rendering فعال شود:
<component type="typeof(App)" render-mode="ServerPrerendered" />
<body> <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> <persist-component-state /> </body>
@page "/" @implements IDisposable @inject PersistentComponentState applicationState @inject IUserRepository userRepository @foreach(var user in users) { <ShowUserInformation user="@item"></ShowUserInformation> } @code { private const string cachingKey = "something_unique"; private List<User> users = new(); private PersistingComponentStateSubscription persistingSubscription; protected override async Task OnInitializedAsync() { persistingSubscription = applicationState.RegisterOnPersisting(PersistData); if (applicationState.TryTakeFromJson<List<User>>(cachingKey, out var restored)) { users = restored; } else { users = await userRepository.GetAllUsers(); } } public void Dispose() { persistingSubscription.Dispose(); } private Task PersistData() { applicationState.PersistAsJson(cachingKey, users); return Task.CompletedTask; } }
- ابتدا تزریق سرویس PersistentComponentState را مشاهده میکنید. این همان کامپوننتی است که کار کش کردن اطلاعات را مدیریت میکند.
- سپس فراخوانی متد RegisterOnPersisting انجام شدهاست. متدی که توسط آن ثبت میشود، در حین عملیات pre-rendering فراخوانی میشود تا اطلاعاتی را کش کند. نحوهی این کش شدن را در ادامهی مطلب بررسی میکنیم.
- سپس فراخوانی متد TryTakeFromJson رخدادهاست. اگر این متد اطلاعاتی را برگرداند، یعنی pre-rendering سمت سرور پیشتر انجام شده و اطلاعاتی کش شده، برای دریافت در سمت کلاینت، وجود داشته و نیازی به مراجعهی مجدد و دوباره، به بانک اطلاعاتی نیست.
- دراینجا یک قسمت Dispose را هم مشاهده میکنید تا اگر کاربر به صفحهی دیگری مراجعه کرد، متد ثبت شدهی در اینجا، از لیست مواردی که باید در حین pre-rendering سریالایز شوند، حذف گردد.
اگر از این روش استفاده نشود، به علت دوبار فراخوانی شدن متد OnInitializedAsync یک کامپوننت به همراه pre-rendering، اطلاعاتی که در حین pre-rendering کامپوننت از بانک اطلاعاتی، برای تولید محتوای استاتیک صفحه در سمت سرور دریافت شده، با فعالسازی آتی تعاملی سمت کلاینت آن کامپوننت، از دست خواهد رفت و در این حالت کلاینت باید مجددا این اطلاعات را از بانک اطلاعاتی درخواست کند. بنابراین هدف از persist-component-state، حفظ دادهها در بین دو رندر است؛ رندر اولی که در سمت سرور انجام میشود و رندر دومی که متعاقبا در سمت کلاینت رخ میدهد.
یک نکته: به یک چنین قابلیتی در فریمورکهای دیگر «hydration» هم گفته میشود که در اصل یک شیء دیکشنری است که مقدار شیء حالت را در سمت سرور دریافت کرده و آنرا در زمان فعال شدن سمت کلاینت کامپوننت، برای استفادهی مجدد مهیا میکند.
سؤال: اطلاعات سرویس PersistentComponentState کجا ذخیره میشوند؟
همانطور که در مثال فوق هم مشاهده کردید، سرویس PersistentComponentState، این state را به صورت JSON ذخیره میکند. اما ... این اطلاعات دقیقا کجا ذخیره میشوند؟
برای مشاهدهی آنها، فقط کافی است به source صفحه مراجعه کنید تا با دو مقدار مخفی رمزنگاری شدهی زیر در انتهای HTML صفحه مواجه شوید!
<!--Blazor-Server-Component-State:CfDJXyz-> <!--Blazor-WebAssembly-Component-State:eyJVc2Xyz->
مایکروسافت از این نوع کارها پیشتر در ASP.NET Web forms توسط ViewStateها انجام دادهاست! البته ViewStateها جهت نگهداری اطلاعات وضعیت کلاینت، به سمت سرور ارسال میشوند؛ اما این Component-Stateهای مخفی، فقط یکبار توسط قسمت کلاینت خوانده میشوند و هدف آنها ارسال اطلاعاتی به سمت سرور نیست.
یک نکته: همانطور که عنوان شد، در نگارشهای قبلی Blazor، از تگهلپری به نام persist-component-state برای درج این اطلاعات در انتهای صفحه استفاده میکردند. استفاده از این تگهلپر در Blazor 8x منسوخ شده و به صورت خودکار دادههای تمام سرویسهای PersistentComponentState فعالی که توسط PersistAsJson اطلاعاتی را ذخیره میکنند، جمع آوری و به صورت یکجا در انتهای صفحه به صورت رمزنگاری شده، درج میکنند.
روش خاموش کردن Pre-rendering
برای خاموش کردن pre-rendering نیاز است پارامتر همنامی را به نحو زیر با false مقدار دهی کرد:
@rendermode renderMode @code { private static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false); }
بازنویسی مثال قسمت قبل با استفاده از سرویس PersistentComponentState
در قسمت قبل هرچند روش مواجه شدن با مشکل دوبار فراخوانی شدن متد آغازین یک کامپوننت را در سمت سرور و کلاینت بررسی کردیم، اما ... در نهایت دوبار مراجعه به بانک اطلاعاتی انجام میشود؛ یکبار در زمان pre-rendering و با مراجعهی مستقیم به یک سرویس سمت سرور و یکبار دیگر هم در زمان فراخوانی httpClient.GetFromJsonAsync در سمت کلاینت برای دریافت اطلاعات مورد نیاز از یک Web API Endpoint. اگر بخواهیم اطلاعات لیست محصولات دریافتی سمت سرور را به سمت کلاینت منتقل کنیم و مجددا آنرا از بانک اطلاعاتی دریافت نکنیم، راهحل سوم آن، استفاده از سرویس PersistentComponentState است.
در کدهای زیر، بازنویسی کامپوننت محصولات مشابه را مشاهده میکنید که کمی پیچیدهتر شدهاست. اینبار لیست محصولات مشابه، در همان بار اول رندر کامپوننت نمایش داده میشوند و نه پس از کلیک بر روی دکمهای. به همین جهت باید کار مدیریت دوبار فراخوانی متد رویدادگردان OnInitializedAsync را به درستی انجام داد. متد OnInitializedAsync یکبار در حین پیشرندر سمت سرور اجرا میشود و بار دیگر زمانیکه وباسمبلی جاری در مرورگر به صورت کامل دریافت شده و فعال میشود؛ یعنی برای بار دوم، کدهای اجرایی آن سمت کلاینت هستند.
در این مثال با استفاده از سرویس PersistentComponentState، اطلاعات دریافت شدهی در حین pre-rendering ابتدایی رخدادهی در سمت سرور، در متد OnPersistingAsync، کش شده و در حین فعال شدن وباسمبلی مرتبط در سمت کلاینت، با استفاده از متد PersistentState.TryTakeFromJson، از کش خوانده میشوند و دیگر دوبار رفت و برگشت به بانک اطلاعاتی را شاهد نخواهیم بود.
@implements IDisposable @inject IProductStore ProductStore @inject PersistentComponentState PersistentState <h3>Related products</h3> @if (_relatedProducts == null) { <p>Loading...</p> } else { <div class="mt-3"> @foreach (var item in _relatedProducts) { <a href="/ProductDetails/@item.Id"> <div class="col-sm"> <h5 class="mt-0">@item.Title (@item.Price)</h5> </div> </a> } </div> } @code{ private const string StateCachingKey = "products"; private IList<Product>? _relatedProducts; private PersistingComponentStateSubscription _persistingSubscription; [Parameter] public int ProductId { get; set; } protected override async Task OnInitializedAsync() { _persistingSubscription = PersistentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); if (PersistentState.TryTakeFromJson<IList<Product>>(StateCachingKey, out var restored)) { _relatedProducts = restored; } else { await Task.Delay(500); // Simulates asynchronous loading _relatedProducts = await ProductStore.GetRelatedProducts(ProductId); } } private Task OnPersistingAsync() { PersistentState.PersistAsJson(StateCachingKey, _relatedProducts); return Task.CompletedTask; } public void Dispose() { _persistingSubscription.Dispose(); } }
نکتهی مهم: فعلا کدهای فوق فقط برای حالت بارگذاری اولیهی کامپوننت درست کار میکنند. یعنی اگر نگارش وباسمبلی کامپوننت پس از وقوع پیشرندر سمت سرور، در مرورگر دریافت و بارگذاری کامل شود؛ اما برای دفعات بعدی مراجعهی به این صفحه با استفاده از enhanced navigation و راهبری بهبود یافته که در قسمت ششم این سری بررسی کردیم ... دیگر کار نمیکنند و در این حالت کش شدنی رخ نمیدهد که در نتیجه، شاهد دوبار رفت و برگشت به بانک اطلاعاتی خواهیم بود و اساسا استفادهی از PersistentComponentState را زیر سؤال میبرد؛ چون در حین راهبری بهبود یافته، از آن استفاده نمیشود (قسمت PersistentState.TryTakeFromJson آن، هیچگاه اطلاعاتی را از کش نمیخواند). اطلاعات بیشتر
MVC Scaffolding #1
کل سری ASP.NET MVC
به همراه کل سری EF Code First
MVC Scaffolding چیست؟
MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامههای ASP.NET MVC مبتنی بر EF Code First.
بررسی مقدماتی MVC Scaffolding
امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژههای ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندیها و قابلیتهای سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class Task { public int Id { set; get; } [Required] public string Name { set; get; } [DisplayName("Due Date")] public DateTime? DueDate { set; get; } [DisplayName("Is Complete")] public bool IsComplete { set; get; } [StringLength(450)] public string Description { set; get; } } }
همانطور که ملاحظه میکنید در قسمت قالبها، تولید کنترلرهایی با اکشن متدهای ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آنرا پذیرفتهایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:
الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدلهای برنامه ایجاد میگردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه میکند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلیهای دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)
معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شدهای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی میتواند شامل فیلدهای بیشتری باشد و نباید آنرا مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).
مزیتها:
قسمت عمدهای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شدهاند. کارهای اولیهای که با هر روش و الگوی شناخته شدهای قصد پیاده سازی آنها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمدهای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرمهای آنرا بخواهید با jQuery Ajax پیاده سازی کنید یا کنترلهای پیش فرض را با افزونههای jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.
نصب بسته اصلی MVC Scaffolding توسط NuGet
بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console میتوان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
Attempting to resolve dependency 'T4Scaffolding'. Attempting to resolve dependency 'T4Scaffolding.Core'. Attempting to resolve dependency 'EntityFramework'. Successfully installed 'T4Scaffolding.Core 1.0.0'. Successfully installed 'T4Scaffolding 1.0.8'. Successfully installed 'MvcScaffolding 1.0.9'. Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1. Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1. Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز میتوان پیاده سازی کرد:
Scaffold Controller Task
نکته و مزیت مهم دیگری که در اینجا در دسترس میباشد، سوئیچهای خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچهای قابل استفاده است.
برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding
در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
public class TasksController : Controller { private readonly ITaskRepository taskRepository; // If you are using Dependency Injection, you can delete the following constructor public TasksController() : this(new TaskRepository()) { } public TasksController(ITaskRepository taskRepository) { this.taskRepository = taskRepository; }
public interface ITaskRepository : IDisposable { IQueryable<Task> All { get; } IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties); Task Find(int id); void InsertOrUpdate(Task task); void Delete(int id); void Save(); }
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزنهای تعریف شده را تک موجودیتی میکند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیتها به کلاسهای مخزن واگذار شدهاند و کنترلرها به این نحو خلوتتر گردیدهاند، یک مرحله پیشرفت محسوب میشود.
شما نیز با دانستن قواعد ساده کد نویسی برای Text templateها میتوانید از این امکان برای تولید اتوماتیک کلاسها، HTML ، XML و بطور کلی هر نوع فایل متنی استفاده نمایید. کاربردهای عملی Text template بیشتر برای تولید کد از روی دیتابیس و مثلا بر اساس فیلدهای هر جدول و امثال آن میباشد. اما با کمی خلاقیت استفادههای بسیار زیاد و جالبی را میتوانید از آن مشاهده نمایید.
کاربردهایی مانند : تولید خود کار فایل Config، تولید خودکار Unit Test، تولید خودکار کلاسهای CRUD برای هر موجودیت در لایه Data، تولید خودکار SQL برای StoredProcedure ها، تولید خودکار Html و Js، تولید خودکار فرمهای ASPX و ... برای فرمهای ثابت و تکراری با فرآیند مشخص، تولید خودکار مستندات پروژه در قالب Word Document , … ، تولید خودکار فایلهای Excel از اطلاعات خاص و تولید خودکار فرمهای xaml و .....
در این آموزش با قواعد اصلی نوشتن کد برای Text templateها آشنا میشویم و چند نمونه از کاربردهای آن را به عنوان مثال کاربردی مورد بررسی قرار خواهیم داد.
برای شروع قبل از توضیح در مورد قواعد کد نویسی Text template، یک فایل Text template ایجاد نمایید. برای ایجاد، از پنجره Add New Item یک فایل Text template به پروژه اضافه نمایید:
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".txt" #>
Extension نیز پسوند فایل خروجی تولید شده را مشخص مینماید .
اگر در ادامه متن Hello dotnettips را بنویسید و فایل را Save کنید، کنار فایل tt مذکور، یک فایل با همان نام و پسوند txt ایجاد خواهد شد که داخل آن نوشته است:
Hello dotnettips
برای ایجاد فایل خروجی همچنین میتوانید روی فایل tt کلیک راست نموده و از منوی باز شده Run Custom Tool را انتخاب نمایید. توجه داشته باشید که در تنظیمات Custom Tool باید مقدار TextTemplatingFileGenerator وجود داشته باشد:
خوب؛ برای ادامه، باید با قواعد کد نویسی در Text template آشنا شوید. جلسه بعدی قواعد کد نویسی T4 را بررسی خواهیم کرد.
معرفی #F
#F یک زبان برنامه نویسی تابع گرا است و گزینه ای بسیار مناسب برای حل مسایل کامپیوتری. اما استفاده از زبان برنامه نویسی تابعی محض برای نوشتن و تولید پروژههای نرم افزاری مناسب نمیباشد. به همین دلیل نیاز به استفاده از این زبانها در کنار سایر زبانهای شی گرا احساس میشود. #F یک زبان همه منظوره دات نت است که برای حالت اجرا به صورت همه منظوره استفاده میشود. برخی زبانهای تابع گرا دیگر نظیر Lisp و Haskel و OCaml (که #F بسیار نزدیک به این زبان میباشد) با دستورات زبان اجرای سفارشی کار میکنند و این مسئله باعث نبود زبان برنامه نویسی چند فعالیته میشود. شما میتوانید از برنامه نویسی توصیفی هم استفاده کنید و توابع را به راحتی با هم ترکیب کنید و یا روشهای شی گرایی و دستوری را در همان برنامه استفاده کنید.
تاریخچه
#F توسط دکتر دون سیم ابداع شد. در حال حاضر #F وابسته به تیمی کوچک ولی پیشرفته واقع در مرکز تحقیقات شرکت مایکروسافت میباشد. #Fمدل خود را از روی زبان برنامه نویسی OCAML انتخاب کرد و سپس با گسترش قابلیتهای فنی، خود را در دات نت گنجاند. #F در بسیاری از برنامههای بزرگ دنیای واقعی استفاده شده است که این خود نمایانگر آکادمیک نبودن محض این زبان است. با توجه به اینکه زبان تابع گرای دیگر به ندرت در دات نت توسعه پیدا کرده است #F به عنوان استاندارد در این مقوله در آمده است. زبان #F از نظر کیفیت و سازگار بودن با دات نت و VisualStudio بسیار وضعیت بهتری نسبت به رقبای خود دارد و این خود دلیلی دیگری است برای انتخاب این زبان.
استفاده در دات نت
#F کاملا از دات نت پشتیبانی میکند و این قابلیت را به برنامه نویسان میدهد که هر چیزی را که در سایر زبانهای دات نت استفاده میکنند در این زبان نیز قابل استفاده باشد. همچنین میتواند برای کد نویسی IL نیز استفاده شود.
#F به راحتی قابل اجرا در محیط لینوکس و مکینتاش نیز است.
استفاده کنندگان #F
#F در شرکت مایکرو سافت به شدت استفاده میشود. رالف هربریش که یکی از مدیران دوگانه گروه بازیهای مایکروسافت و از متخصصین آموزش ماشین است در این باره میگوید:
*اولین برنامه کاربردی برای انتقال 110 گیگا بایت از طریق 11000 فایل متنی در بیش از 300 دایرکتوری و وارد کردن آنها در دیتابیس بود. کل برنامه 90 خط بود و در کمتر از 18 ساعت توانست اطلاعات مربوطه را در SQL ذخیره کند. یعنی ده هزار خط برنامه متنی در هر ثانیه مورد پردازش قرار گرفت.همچنین توجه کنید که من برنامه را بهینه نکردم بلکه به صورت کاملا عادی نوشتم. این جواب بسیار قابل توجه بود زیرا من انتظار داشتم حداقل یک هفته زمان ببرد.
دومین برنامه، برنامه پردازش میلیونها Feekback مشتریان بود. ما روابط مدلی زیادی را توسعه دادیم و من این روابط را در #F قرار دادم و دادههای مربوط به SQL را در آن فراخوانی کردم و نتایج را در فایل داده ای MATLAB قرار دادم و کل پروژه در حد صد خط بود به همراه توضیحات. زمان اجرای پروژه برای دریافت خروجی ده دقیقه بود در حالی که همین کار را توسط برنامه #C قبلا توسعه داده بودیم که بیش از هزار خط بود و نزدیک به دو روز زمان میبرد.*
Derivative One که یک شرکت بزرگ در تولید نرم افزارهای شبیه ساز مالی است مدلهای مالی نرم افزارهای خود را در #F پیاده سازی کرده است.
چرا #F ؟
همیشه باید دلیلی برای انتخاب یک زبان باشد. در حال حاضر #F یکی از قدرتمندترین زبانهای برنامه نویسی است. در ذیل به چند تا از این دلایل اشاره خواهم کرد:
- #F یک زبان استنباطی است. برای مثال در هنگام تعریف متغیر و شناسه نیاز به ذکر نوع آن نیست. کامپایلر با توجه به مقدار اولیه تصمیم میگیرد که متغیر از چه نوعی است.
- بسیار راحت میتوان به کتابخانه قدرتمند دات نت دسترسی داشت و از آنها در پروژههای خود استفاده کنید.
- #F از انواع روشهای برنامه نویسی نظیر تابعی، موازی، شی گرا و دستوری پیشتیبانی میکند.
- برخلاف تصور بعضی افراد، در #F امکان تهیه و توسعه پروژههای وب و ویندوز و حتی WPF و Silverlight هم وجود دارد.
- نوع کدنویسی و syntax زبان #F به برنامه نویسان این اجازه را میدهد که الگوریتمهای پیچیده مورد نظر خود را بسیار راحتتر پیاده سازی کنند. به همین دلیل بعضی برنامه نویسان این زبان را با Paython مقایسه میکنند.
- #F به راحتی با زبان #C و VB تعامل دارد. یعنی میتونیم در طی روند تولید پروژه از قدرتهای هر سه زبان بهره بگیریم.
- طبق آمار گرفته شده از برنامه نویسان، #F به دلیل پشتیبانی از نوع داده ای قوی و مبحث Unit Measure، خطاها و Bugهای نرم افزار را کاهش میدهد.
- به دلیل پشتیبانی VS.Net از زبان #F و وجود ابزار قدرتمند برای توسعه نرم افزار به کمک این زبان (unitTesting و ابزارهای debuging و ..)این زبان تبدیل به قدرتهای دنیای برنامه نویسی شده است.
- #F یک زبان بسیار مناسب برای پیاده سازی الگوریتمهای data-mining است.
- #F از immutability در تعریف شناسهها پشتیبانی میکند.(در فصلهای مربوطه بحث خواهد شد)
- و.....
چرا #F نه ؟
#F هم مانند سایر زبان ها، علاوه بر قدرت بی همتای خود دارای معایبی نیز میباشد. (مواردی که در پایین ذکر میشود صرفا بر اساس تجربه است نه مستندات).
- نوع کدنویسی و syntax زبان #F برای برنامه نویسان دات بیگانه ( و البته کمی آزار دهنده) است. اما به مرور این مشکل، تبدیل به قدرت برای مانورهای مختلف در کد میشود.
- درست است که در #F امکان تعریف اینترفیس وجود دارد و یک کلاس میتواند اینترفیس مورد نظر را پیاده سازی کند ولی هنگام فراخوانی متدهای کلاس (اون هایی که مربوط به اینترفیس است) حتما باید instance کلاس مربوطه به اینترفیس cast شود و این کمی آزار دهنده است.(در فصل شی گرایی در این مورد شرح داده شده است).
- زبان #F در حال حاضر توسط VS.Net به صورت Visual پشتیبانی نمیشود.(امکاناتی نظیر drag drop کنترلها برای ساخت فرم و ....). البته برای حل این مشکل نیز افزونه هایی وجود دارد که در جای مناسب بحث خواهیم کرد.
آیا برای یادگیری #F نیاز به داشتن دانش در برنامه نویسی #C یا VBداریم؟
به طور قطع نه. نوع کد نویسی (نه مفاهیم)در #F کاملا متفاوت در #C است و این دو زبان از نظر کد نویسی شباهتشان در حد صفر است. برای یادگیری #F بیشتر نیاز به داشتن آگاهی اولیه در برنامه نویسی (آشنایی با تابع، حلقه تکرار، متغیر ها) و شی گرایی(مفاهیم کلاس، اینترفیس، خواص، متدها و...) دارید تا آشنایی با #C یا VB.
چگونه شروع کنیم؟
اولین گام برای یادگیری آشنایی با نحوه کد نویسی #F است. بدین منظور در طی فصول آموزش سعی بر این شده است از مثالهای بسیار زیاد برای درک بهتر مفاهیم استفاده کنم. تا جای ممکن برای اینکه تکرار مکررات نشود و شما خواننده عزیز به خاطر مطالب واضح و روشن خسته نشوید از تشریح مباحث واضح خودداری کردم و بیشتر به پیاده سازی مثال اکتفا نمودم.