- استفاده از Luke برای بهبود کیفیت جستجوی لوسین
- استفاده از لوسین برای برجسته سازی عبارت جستجو شده در نتایج حاصل
- یافتن «مطالب مرتبط» توسط لوسین
- نحوه اضافه کردن قابلیت غلط گیر املایی شبیه به جستجوی گوگل توسط لوسین
- نحوه اضافه کردن Auto-Complete به جستجوی لوسین در ASP.NET MVC و Web forms
- استفاده از لوسین برای انجام محاسبات آماری بر روی متون
- نحوه استفاده صحیح از لوسین در ASP.NET
- حذف اعراب از حروف و کلمات
آموزش سیلورلایت 4 - قسمتهای 21 تا 27
بنابراین مدل قابل استفاده در سمت کلاینت هم از این سرویس سمت سرور دریافت میشود و جالب اینجا است که تمام مباحث مطلع سازی تغییرات خواص، اعتبار سنجی، نکات ریز binding و غیره در این مدلهای WCF RIA Services به صورت خودکار گنجانده شده و بار کدنویسی شما بسیار کمتر میشود.
برای MVVM ، کنترل domain data source را با کد نویسی تولید میکنم تا بتونم در View Model استفاده کنم و از حالت متداول کشیدن و رها کردن این کنترل روی فرم که با اصول MVVM سازگار نیست به این صورت رها خواهم شد.
UI
در نهایت نوبت به طراحی و کدنویسی UI میرسد تا بتوانیم محصولات را به کاربر نمایش دهیم. اما قبل از شروع باید موضوعی را یادآوری کنم. اگر به یاد داشته باشید، در کلاس ProductService موجود در لایهی Domain، از طریق یکی از روشهای الگوی Dependency Injection به نام Constructor Injection، فیلدی از نوع IProductRepository را مقداردهی نمودیم. حال زمانی که بخواهیم نمونه ای را از ProductService ایجاد نماییم، باید به عنوان پارامتر ورودی سازنده، شی ایجاد شده از جنس کلاس ProductRepository موجود در لایه Repository را به آن ارسال نماییم. اما از آنجایی که میخواهیم تفکیک پذیری لایهها از بین نرود و UI بسته به نیاز خود، نمونه مورد نیاز را ایجاد نموده و به این کلاس ارسال کند، از ابزارهایی برای این منظور استفاده میکنیم. یکی از این ابزارها StructureMap میباشد که یک Inversion of Control Container یا به اختصار IoC Container نامیده میشود. با Inversion of Control در مباحث بعدی بیشتر آشنا خواهید شد. StructureMap ابزاری است که در زمان اجرا، پارامترهای ورودی سازندهی کلاسهایی را که از الگوی Dependency Injection استفاده نموده اند و قطعا پارامتر ورودی آنها از جنس یک Interface میباشد را، با ایجاد شی مناسب مقداردهی مینماید.
به منظور استفاده از StructureMap در Visual Studio 2012 باید بر روی پروژه UI خود کلیک راست نموده و گزینهی Manage NuGet Packages را انتخاب نمایید. در پنجره ظاهر شده و از سمت چپ گزینهی Online و سپس در کادر جستجوی سمت راست و بالای پنجره واژهی structuremap را جستجو کنید. توجه داشته باشید که باید به اینترنت متصل باشید تا بتوانید Package مورد نظر را نصب نمایید. پس از پایان عمل جستجو، در کادر میانی structuremap ظاهر میشود که میتوانید با انتخاب آن و فشردن کلید Install آن را بر روی پروژه نصب نمایید.
جهت آشنایی بیشتر با NuGet و نصب آن در سایر نسخههای Visual Studio میتوانید به لینکهای زیر رجوع کنید:
کلاسی با نام BootStrapper را با کد زیر به پروژه UI خود اضافه کنید:
using StructureMap; using StructureMap.Configuration.DSL; using SoCPatterns.Layered.Repository; using SoCPatterns.Layered.Model; namespace SoCPatterns.Layered.WebUI { public class BootStrapper { public static void ConfigureStructureMap() { ObjectFactory.Initialize(x => x.AddRegistry<ProductRegistry>()); } } public class ProductRegistry : Registry { public ProductRegistry() { For<IProductRepository>().Use<ProductRepository>(); } } }
ممکن است یک WinUI ایجاد کرده باشید که در این صورت به جای فضای نام SoCPatterns.Layered.WebUI از SoCPatterns.Layered.WinUI استفاده نمایید.
هدف کلاس BootStrapper این است که تمامی وابستگیها را توسط StructureMap در سیستم Register نماید. زمانی که کدهای کلاینت میخواهند به یک کلاس از طریق StructureMap دسترسی داشته باشند، Structuremap وابستگیهای آن کلاس را تشخیص داده و بصورت خودکار پیاده سازی واقعی (Concrete Implementation) آن کلاس را، براساس همان چیزی که ما برایش تعیین کردیم، به کلاس تزریق مینماید. متد ConfigureStructureMap باید در همان لحظه ای که Application آغاز به کار میکند فراخوانی و اجرا شود. با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:
در WebUI:
فایل Global.asax را به پروژه اضافه کنید و کد آن را بصورت زیر تغییر دهید:
namespace SoCPatterns.Layered.WebUI { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { BootStrapper.ConfigureStructureMap(); } } }
در WinUI:
در فایل Program.cs کد زیر را اضافه کنید:
namespace SoCPatterns.Layered.WinUI { static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); BootStrapper.ConfigureStructureMap(); Application.Run(new Form1()); } } }
سپس برای طراحی رابط کاربری، با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:
در WebUI:
صفحه Default.aspx را باز نموده و کد زیر را به آن اضافه کنید:
<asp:DropDownList AutoPostBack="true" ID="ddlCustomerType" runat="server"> <asp:ListItem Value="0">Standard</asp:ListItem> <asp:ListItem Value="1">Trade</asp:ListItem> </asp:DropDownList> <asp:Label ID="lblErrorMessage" runat="server" ></asp:Label> <asp:Repeater ID="rptProducts" runat="server" > <HeaderTemplate> <table> <tr> <td>Name</td> <td>RRP</td> <td>Selling Price</td> <td>Discount</td> <td>Savings</td> </tr> <tr> <td colspan="5"><hr /></td> </tr> </HeaderTemplate> <ItemTemplate> <tr> <td><%# Eval("Name") %></td> <td><%# Eval("RRP")%></td> <td><%# Eval("SellingPrice") %></td> <td><%# Eval("Discount") %></td> <td><%# Eval("Savings") %></td> </tr> </ItemTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater>
در WinUI:
فایل Form1.Designer.cs را باز نموده و کد آن را بصورت زیر تغییر دهید:
#region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.cmbCustomerType = new System.Windows.Forms.ComboBox(); this.dgvProducts = new System.Windows.Forms.DataGridView(); this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colRrp = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colSellingPrice = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colDiscount = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colSavings = new System.Windows.Forms.DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)(this.dgvProducts)).BeginInit(); this.SuspendLayout(); // // cmbCustomerType // this.cmbCustomerType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbCustomerType.FormattingEnabled = true; this.cmbCustomerType.Items.AddRange(new object[] { "Standard", "Trade"}); this.cmbCustomerType.Location = new System.Drawing.Point(12, 90); this.cmbCustomerType.Name = "cmbCustomerType"; this.cmbCustomerType.Size = new System.Drawing.Size(121, 21); this.cmbCustomerType.TabIndex = 3; // // dgvProducts // this.dgvProducts.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dgvProducts.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.colName, this.colRrp, this.colSellingPrice, this.colDiscount, this.colSavings}); this.dgvProducts.Location = new System.Drawing.Point(12, 117); this.dgvProducts.Name = "dgvProducts"; this.dgvProducts.Size = new System.Drawing.Size(561, 206); this.dgvProducts.TabIndex = 2; // // colName // this.colName.DataPropertyName = "Name"; this.colName.HeaderText = "Product Name"; this.colName.Name = "colName"; this.colName.ReadOnly = true; // // colRrp // this.colRrp.DataPropertyName = "Rrp"; this.colRrp.HeaderText = "RRP"; this.colRrp.Name = "colRrp"; this.colRrp.ReadOnly = true; // // colSellingPrice // this.colSellingPrice.DataPropertyName = "SellingPrice"; this.colSellingPrice.HeaderText = "Selling Price"; this.colSellingPrice.Name = "colSellingPrice"; this.colSellingPrice.ReadOnly = true; // // colDiscount // this.colDiscount.DataPropertyName = "Discount"; this.colDiscount.HeaderText = "Discount"; this.colDiscount.Name = "colDiscount"; // // colSavings // this.colSavings.DataPropertyName = "Savings"; this.colSavings.HeaderText = "Savings"; this.colSavings.Name = "colSavings"; this.colSavings.ReadOnly = true; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(589, 338); this.Controls.Add(this.cmbCustomerType); this.Controls.Add(this.dgvProducts); this.Name = "Form1"; this.Text = "Form1"; ((System.ComponentModel.ISupportInitialize)(this.dgvProducts)).EndInit(); this.ResumeLayout(false); } #endregion private System.Windows.Forms.ComboBox cmbCustomerType; private System.Windows.Forms.DataGridView dgvProducts; private System.Windows.Forms.DataGridViewTextBoxColumn colName; private System.Windows.Forms.DataGridViewTextBoxColumn colRrp; private System.Windows.Forms.DataGridViewTextBoxColumn colSellingPrice; private System.Windows.Forms.DataGridViewTextBoxColumn colDiscount; private System.Windows.Forms.DataGridViewTextBoxColumn colSavings;
سپس در Code Behind، با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:
در WebUI:
وارد کد نویسی صفحه Default.aspx شده و کد آن را بصورت زیر تغییر دهید:
using System; using System.Collections.Generic; using SoCPatterns.Layered.Model; using SoCPatterns.Layered.Presentation; using SoCPatterns.Layered.Service; using StructureMap; namespace SoCPatterns.Layered.WebUI { public partial class Default : System.Web.UI.Page, IProductListView { private ProductListPresenter _productListPresenter; protected void Page_Init(object sender, EventArgs e) { _productListPresenter = new ProductListPresenter(this,ObjectFactory.GetInstance<Service.ProductService>()); this.ddlCustomerType.SelectedIndexChanged += delegate { _productListPresenter.Display(); }; } protected void Page_Load(object sender, EventArgs e) { if(!Page.IsPostBack) _productListPresenter.Display(); } public void Display(IList<ProductViewModel> products) { rptProducts.DataSource = products; rptProducts.DataBind(); } public CustomerType CustomerType { get { return (CustomerType) int.Parse(ddlCustomerType.SelectedValue); } } public string ErrorMessage { set { lblErrorMessage.Text = String.Format("<p><strong>Error:</strong><br/>{0}</p>", value); } } } }
در WinUI:
وارد کدنویسی Form1 شوید و کد آن را بصورت زیر تغییر دهید:
using System; using System.Collections.Generic; using System.Windows.Forms; using SoCPatterns.Layered.Model; using SoCPatterns.Layered.Presentation; using SoCPatterns.Layered.Service; using StructureMap; namespace SoCPatterns.Layered.WinUI { public partial class Form1 : Form, IProductListView { private ProductListPresenter _productListPresenter; public Form1() { InitializeComponent(); _productListPresenter = new ProductListPresenter(this, ObjectFactory.GetInstance<Service.ProductService>()); this.cmbCustomerType.SelectedIndexChanged += delegate { _productListPresenter.Display(); }; dgvProducts.AutoGenerateColumns = false; cmbCustomerType.SelectedIndex = 0; } public void Display(IList<ProductViewModel> products) { dgvProducts.DataSource = products; } public CustomerType CustomerType { get { return (CustomerType)cmbCustomerType.SelectedIndex; } } public string ErrorMessage { set { MessageBox.Show( String.Format("Error:{0}{1}", Environment.NewLine, value)); } } } }
با توجه به کد فوق، نمونه ای را از کلاس ProductListPresenter، در لحظهی نمونه سازی اولیهی کلاس UI، ایجاد نمودیم. با استفاده از متد ObjectFactory.GetInstance مربوط به StructureMap، نمونه ای از کلاس ProductService ایجاد شده است و به سازندهی کلاس ProductListPresenter ارسال گردیده است. در مورد Structuremap در مباحث بعدی با جزئیات بیشتری صحبت خواهم کرد. پیاده سازی معماری لایه بندی در اینجا به پایان رسید.
اما اصلا نگران نباشید، شما فقط پرواز کوتاه و مختصری را بر فراز کدهای معماری لایه بندی داشته اید که این فقط یک دید کلی را به شما در مورد این معماری داده است. این معماری هنوز جای زیادی برای کار دارد، اما در حال حاضر شما یک Applicaion با پیوند ضعیف (Loosely Coupled) بین لایهها دارید که دارای قابلیت تست پذیری قوی، نگهداری و پشتیبانی آسان و تفکیک پذیری قدرتمند بین اجزای آن میباشد. شکل زیر تعامل بین لایهها و وظایف هر یک از آنها را نمایش میدهد.
مدیریت تغییرات گریدی از اطلاعات به کمک استفاده از الگوی واحد کار مشترک بین ViewModel و لایه سرویس
در این صفحه با کلیک بر روی دکمه به علاوه، یک ردیف به ردیفهای موجود اضافه شده و در اینجا میتوان اطلاعات کاربر جدیدی به همراه سطح دسترسی او را وارد و ذخیره کرد و یا حتی اطلاعات کاربران موجود را ویرایش نمود. اگر بخواهیم مانند مراحلی که در قسمت قبل در مورد تعریف یک صفحه جدید در برنامه توضیح داده شد، عمل کنیم، به صورت خلاصه به ترتیب ذیل عمل شده است:
1) ایجاد صفحه تغییر مشخصات کاربر
ابتدا صفحه Views\Admin\AddNewUser.xaml به پروژه ریشه که Viewهای برنامه در آن تعریف میشوند، اضافه شده است. به همراه دو دکمه و یک ListView که تطابق بهتری با قالب متروی مورد استفاده دارد.
2) تنظیم اعتبارسنجی صفحه اضافه شده
مرحله بعد تعریف هر صفحهای در سیستم، مشخص سازی وضعیت دسترسی به آن است:
/// <summary> /// افزودن و مدیریت کاربران سیستم /// </summary> [PageAuthorization(AuthorizationType.ApplyRequiredRoles, "IsAdmin, CanAddNewUser")]
3) تغییر منوی برنامه جهت اشاره به صفحه جدید
در ادامه در فایل منوی برنامه Views\MainMenu.xaml تعریف دسترسی به صفحه Views\Admin\AddNewUser.xaml قید شده است:
<Button Style="{DynamicResource MetroCircleButtonStyle}" Height="55" Width="55" Command="{Binding DoNavigate}" CommandParameter="\Views\Admin\AddNewUser.xaml" Margin="2"> <Rectangle Width="28" Height="17.25"> <Rectangle.Fill> <VisualBrush Stretch="Fill" Visual="{StaticResource appbar_user_add}" /> </Rectangle.Fill> </Rectangle> </Button>
4) ایجاد ViewModel متناظر با صفحه
مرحله نهایی تعریف صفحه AddNewUser، افزودن ViewModel متناظر با آن است که سورس کامل آنرا در فایل ViewModels\Admin\AddNewUserViewModel.cs پروژه Infrastructure میتوانید ملاحظه کنید.
نکته مهم این ViewModel، ارائه خاصیت لیست کاربران از نوع ObservableCollection به View و گرید برنامه است:
public ObservableCollection<User> UsersList { set; get; }
/// <summary> /// جهت مقاصد انقیاد دادهها در دبلیو پی اف طراحی شده است /// لیستی از کاربران سیستم را باز میگرداند /// </summary> /// <param name="count">تعداد کاربر مد نظر</param> /// <returns>لیستی از کاربران</returns> public ObservableCollection<User> GetSyncedUsersList(int count = 1000) { _users.OrderBy(x => x.FriendlyName).Take(count) .Load(); // For Databinding with WPF. // Before calling this method you need to fill the context by using `Load()` method. return _users.Local; }
در اینجا از قابلیت خاصیتی به نام Local که یک ObservableCollection تحت نظر EF را بازگشت میدهد، استفاده شده است. برای استفاده از این خاصیت، ابتدا باید کوئری خود را تهیه و سپس متد Load را بر روی آن فراخوانی کرد. سپس خاصیت Local بر اساس اطلاعات کوئری قبلی پر و مقدار دهی خواهد شد.
علت انتخاب نام Synced برای این متد، تحت نظر بودن اطلاعات خاصیت Local است تا زمانیکه Context تعریف شده زنده نگه داشته شود. به همین جهت در برنامه جاری از روش زنده نگه داشتن Context به ازای یک ViewModel استفاده شده است.
به Context، توسط اینترفیس IUnitOfWork تزریق شده در سازنده کلاس ViewModel میتوان دسترسی یافت. چون در اینجا از تزریق وابستگیها استفاده شده است، وهلهای که IUnitOfWork کلاس AddNewUserViewModel را تشکیل میدهد، دقیقا همان وهلهای است که در کلاس UsersService لایه سرویس استفاده شده است. در نتیجه، در گرید برنامه هر تغییری اعمال شود، تحت نظر IUnitOfWork خواهد بود و صرفا با فراخوانی متد uow.ApplyAllChanges آن، کلیه تغییرات تمام ردیفهای تحت نظر EF به صورت خودکار در بانک اطلاعاتی درج و یا به روز خواهند شد.
همچنین در مورد ViewModelContextHasChanges نیز در قسمت قبل بحث شد. در اینجا پیاده سازی کننده آن صرفا خاصیت uow.ContextHasChanges است. به این ترتیب اگر کاربر، تغییری را در صفحه داده باشد و بخواهد به صفحه دیگری رجوع کند، با پیام زیر مواجه خواهد شد:
از همین خاصیت برای فعال و غیرفعال کردن دکمه ذخیره سازی اطلاعات نیز استفاده شده است:
/// <summary> /// فعال و غیرفعال سازی خودکار دکمه ثبت /// این متد به صورت خودکار توسط RelayCommand کنترل میشود /// </summary> private bool canDoSave() { // آیا در حین نمایش صفحهای دیگر باید به کاربر پیغام داد که اطلاعات ذخیره نشدهای وجود دارد؟ return ViewModelContextHasChanges; }
/// <summary> /// رخداد ذخیره سازی اطلاعات را دریافت میکند /// </summary> public RelayCommand DoSave { set; get; }
DoSave = new RelayCommand(doSave, canDoSave);
این بررسی نیز بسیار سبک و سریع است. از این جهت که تغییرات Context در حافظه نگهداری میشوند و مراجعه به آن مساوی مراجعه به بانک اطلاعاتی نیست.
2- استفاده از {}less.
Dotless یک پیاده سازی از کتابخانه جاوا اسکریپتی LESS برای دات نت میباشد. پکیج نیوگت DotLess را نیز میتوانید از اینجا دریافت کنید. بعد از اضافه شدن فایلهای آن، یک ارجاع به dotless.core به پروژه تان اضافه خواهد شد. همچنین در فایل Web.Config در قسمت HttpHandler خط زیر اضافه خواهد شد:
<add type="dotless.Core.LessCssHttpHandler,dotless.Core" validate="false" path="*.LESS" verb="*" />
خط فوق یعنی به محض مواجه شدن با فایل LESS، پردازشگر فایلهای LESS وارد عمل میشود. همچنین خط زیر نیز جهت پیکربندی به قسمت configSections در فایل Web.Config اضافه میشود:
<section name="dotless" type="dotless.Core.configuration.DotlessConfigurationSectionHandler,dotless.Core" />
همچنین اگر مایل بودید میتوانید تنظیمات مربوط به فشرده سازی و caching را نیز فعال کنید:
<dotless minifyCss="false" cache="true" />
3- استفاده از افزونهی Web Essentials
Web Essentials برای کامپایل فایلهای LESS از کامپایلر node استفاده میکند. کار با این افزونه خیلی ساده است. کافی است پسوند فایل CSS موجود در پروژه تان را درون ویژوال استودیو، به less. تغییر دهید. با دوبار کلیک بر روی فایل، ویرایشگر فایلهای LESS برای شما نمایش داده میشود، همزمان نیز فایل یک فایل CSS و یک نسخه از فایل CSS را به صورت فشرده، برایتان تولید میکند. خب، هر بار که فایل LESS را تغییر دهید، Web Essentials به صورت خودکار فایلهای css. و min.css. را برایتان روز رسانی میکند.
خوب با کلیک بر روی فایل less، ویرایشگر فایلهای less نمایش داده میشود که با تغییر فایل css میتوانید پیش نمایش آنرا در سمت راست مشاهده کنید:
تعریف متغیر
با استفاده از syntax زیر میتوانید متغیرهای خود را تعریف کنید:
@variable-name: variableValue;
یکی از قابلیتهای جالب در حین مقداردهی متغیرها به خصوص زمانیکه مقدار یک کد رنگی باشد، نمایش کادر انتخاب رنگ است، این کادر بلافاصله بعد از نوشتن علامت # در ابتدای مقدار متغیر نمایش داده میشود:
به طور مثال با تعریف متغیر فوق هر جایی میتوانیم برای تعیین رنگ از آن استفاده کنیم:
@primary-color: #ff6a00; body { background-color: @primary-color; }
استفاده از توابع
LESS شامل تعداد زیادی توابع از پیش نوشته شده است که میتوانید به راحتی از آنها استفاده کنید، توابعی از جمله کار با رنگ ها، اعمال ریاضی و غیره. استفاده از آنها خیلی ساده است. به طور مثال در کد زیر از تابع percentage جهت تبدیل 0.5 به 50% استفاده کرده ایم:
.myClass { width: percentage(0.5); }
استخراج یک فایل
یکی دیگر از قابلیتهای Web Essentials استخراج(Extract) یک فایل میباشد به طور مثال فایل LESS شما شامل متغیرهای زیر است:
@primary-color: #7BA857; @primary-color-light: #B6DE8F; @primary-color-lighter: #D3EFC3; @primary-color-lightest: #EFFAE6; @secondary-color: #AE855C; @text-color-light: #666666; @text-color-dark: #0444;
به راحتی میتوانید تعاریف فوق را درون یک فایل LESS دیگر با نام colors.less قرار دهید:
تغییر تنظیمات پیش فرض Web Essentials
افزونه Web Essentials دارای یک قسمت جهت تغییر تنظیمات پیش فرض برای کار با LESS میباشد که با مراجعه به منوی Tools در ویژوال استودیو و سپس Options میتوانید آنها را تغییر دهید:
Auto-compile dependent files on save: توسط این گزینه میتوانیم تعیین کنیم که فایلهای که import کرده ایم تنها در صورتی که تغییر کرده و ذخیره شده باشند، در فایل CSS جاری کامپایل شوند.
Compile files on build: توسط این گزینه میتوانیم تعیین کنیم که فایلهای less در زمان Build پروژه کامپایل شوند.
Compile files on save: توسط این گزینه میتوانیم تعیین کنیم که فایلهای less در زمان ذخیره کردن پروژه کامپایل شوند.
Create source map files: اگر این گزینه True باشد فایل map. نیز تولید خواهد شد.
Custom output directory: اگر میخواهید خروجی در پوشهی موردنظر شما نمایش داده شود میتوانید آدرس آن را تعیین کنید.
Don't save raw compilation output: با فعال بودن این گزینه فایل CSS عادی ایجاد نخواهد شد.
Process source maps: توسط این گزینه میتوانید قابلیتهای ویرایشگر فایلهای source map را فعال یا غیرفعال کنید.
Strict Math: با فعال بودن این گزینه LESS تمام اعمال ریاضی درون فایل CSS را پردازش خواهد کرد.
Show preview pane: از این گزینه نیز جهت نمایش یا عدم نمایش preview window استفاده میشود.
در قسمت قبلی به اصل وجودی CLR پرداختیم. در این قسمت تا حدودی به بررسی ماژول مدیریت شده managed module که از زبانهای دیگر، کامپایل شده و به زبان میانی تبدیل گشته است صحبت میکنیم.
یک ماژول مدیریت شده شامل بخشهای زیر است:
نام بخش | توضیح |
هدر PE32 یا PE32+ | CLR باید بداند که برنامهی نوشته شده قرار است روی چه پلتفرمی و
با چه معماری، اجرا گردد. این برنامه یک برنامهی 32 بیتی است یا 64
بیتی. همچنین این هدر اشاره میکند که نوع فایل از چه نوعی است؛ GUI,CUI یا
DLL. به علاوه تاریخ ایجاد یا کامپایل فایل هم در آن ذکر شده است. در صورتیکه
این فایل شامل کدهای بومی native CPU هم باشد، اطلاعاتی در مورد این نوع
کدها نیز در این هدر ذکر میشود و اگر ماژول ارائه شده تنها شامل کد IL باشد،
قسمت بزرگی از اطلاعات این هدر در نظر گرفته نمیشود. |
CLR Header | اطلاعاتی را در مورد CLR ارائه میکند. اینکه برای
اجرا به چه ورژنی از CLR نیاز دارد. منابع مورد استفاده. آدرس و اندازه جداول
و فایلهای متادیتا و جزئیات دیگر. |
metadata | هر کد یا ماژول مدیریت شدهای، شامل جداول متادیتا است که این جداول
بر دو نوع هستند. اول جداولی که نوعها و اعضای تعریف شده در کد را توصیف
میکنند و دومی جداولی که نوعها و اعضایی را که در کد به آن ارجاع شده است،
توصیف میکنند. |
IL Code | اینجا محل قرار گیری کدهای میانی تبدیل شده است که در زمان اجرا، CLR آنها را به کدهای بومی تبدیل میکند. |
کامپایلرهایی که بر اساس CLR کار میکنند، وظیفه دارند جداول متادیتاها را به طور کامل ساخته و داخل فایل نهایی embed کنند. متادیتاها مجموعهی کاملی از فناوریهای قدیمی چون فایلهای COM یا Component Object Model و همچنین IDL یا Interface Definition (Description) Language هستند. گفتیم که متادیتاها همیشه داخل فایل IL که ممکن است DLL باشد یا EXE، ترکیب یا Embed شدهاند و جدایی آنها غیر ممکن است. در واقع کامپایلر در یک زمان، هم کد IL و هم متادیتاها را تولید کرده و آنها را به صورت یک نتیجهی واحد در میآورد.
متادیتاها استفادههای زیادی دارند که در زیر به تعدادی از آنان اشاره میکنیم:
- موقع کامپایل نیاز به هدرهای C و ++C از بین میرود؛ چرا که فایل نهایی شامل تمامی اطلاعات ارجاع شده میباشد. کامپایلرها میتوانند مستقیما اطلاعات را از داخل متادیتاها بخوانند.
- ویژوال استودیو از آنها برای کدنویسی راحتتر بهره میگیرد. با استفاده از قابلیت IntelliSense، متادیتاها به شما خواهند گفت چه متدهایی، چه پراپرتیهایی، چه رویدادهایی و ... در دسترس شماست و هر متد انتظار چه پارامترهایی را از شما دارد.
- CLR Code Verification از متادیتا برای اینکه اطمینان کسب کند که کدها تنها عملیات type Safe را انجام میدهند، استفاده میکند.
- متادیتاها به فیلد یک شیء اجازه میدهند که خود را به داخل بلوکهای حافظ انتقال داده و بعد از ارسال به یک ماشین دیگر، همان شیء را با همان وضعیت، ایجاد نماید.
- متادیتاها به GC اجازه میدهند که طول عمر یک شیء را رصد کند. GC برای هر شیء موجود میتواند نوع هر شیء را تشخیص داده و از طریق متادیتاها میتواند تشخیص دهد که فیلدهای یک شیء به اشیاء دیگری هم متصل هستند.
در آینده بیشتر در مورد متادیتاها صحبت خواهیم کرد.
Please open an issue in the library repository to alert its author and ask them to package the library using the Angular Package Format (https://goo.gl/jB3GVv).
مراحل ایجاد یک پروژهی «کتابخانه» توسط Angular CLI 6.0
مرحلهی اول ایجاد یک پروژهی کتابخانه، مانند قبل، توسط دستور ng new و ایجاد یک پروژهی دلخواه جدید است:
ng new my-lib-test
پس از ایجاد پروژهی my-lib-test توسط دستور فوق و وارد شدن به پوشهی اصلی آن توسط خط فرمان، میتوان با اجرای دستور زیر، پروژههای دیگری را به پروژهی جاری افزود:
ng generate application my-app-name
ng generate library my-lib
همچنین یک پوشهی جدید به نام projects نیز ایجاد شده و پروژهی my-lib داخل آن قرار گرفتهاست.
فایل جدید public_api.ts
پس از ایجاد کتابخانهی جدید «my-lib»، فایل جدیدی به نام projects\my-lib\src\public_api.ts نیز به آن اضافه شدهاست:
با این محتوا:
/* * Public API Surface of my-lib */ export * from './lib/my-lib.service'; export * from './lib/my-lib.component'; export * from './lib/my-lib.module';
برای مثال اگر فایل جدید projects\my-lib\src\lib\my-lib.models.ts را به این کتابخانه اضافه کنیم که شامل تعدادی مدل و اینترفیس قابل دسترسی توسط استفاده کنندگان باشد، باید یک سطر زیر را به انتهای فایل public_api.ts اضافه کنیم:
export * from './lib/my-lib.models';
این پروژهی کتابخانه حتی به همراه فایلهای package.json, tsconfig.json, tslint.json مخصوص به خود نیز میباشد تا بتوان آنها را صرفا جهت این پروژه سفارشی سازی کرد.
ساختار my-lib.service پیشفرض یک پروژهی کتابخانه
اگر به فایل projects\my-lib\src\lib\my-lib.service.ts دقت کنیم:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class MyLibService { constructor() { } }
شاید بپرسید چرا؟ هدف اصلی از آن، بهبود فرآیند tree-shaking یا حذف کدهای مرده و استفاده نشدهاست. ممکن است سرویسی را تعریف کنید، اما در برنامه استفاده نشود. این حالت خصوصا در پروژههای کتابخانههای ثالث ممکن است زیاد رخ دهد. به همین جهت با ارائهی این قابلیت، امکان حذف سادهتر سرویسهایی که در برنامه استفاده نشدهاند از خروجی نهایی کامپایل شده، وجود خواهد داشت.
چگونه به پروژهی کتابخانهی جدید، یک کامپوننت جدید را اضافه کنیم؟
تمام دستورات Angular CLI، در اینجا نیز کار میکنند. تنها تفاوت آنها، ذکر صریح نام پروژهی مورد استفاده است:
ng generate component show-data --project=my-lib
البته در اینجا باید فایل my-lib.module.ts را اندکی ویرایش کرد و ShowDataComponent را به قسمت exports نیز افزود:
@NgModule({ imports: [ CommonModule, HttpClientModule ], declarations: [MyLibComponent, ShowDataComponent], exports: [MyLibComponent, ShowDataComponent] }) export class MyLibModule { }
همچنین قسمت imports آن نیز به صورت پیشفرض خالی است. اگر نیاز است با ngIf کار کنید، باید CommonModule را در اینجا قید کنید و اگر نیاز است تبادلات HTTP وجود داشته باشد، ذکر HttpClientModule نیز ضروری است.
مرحلهی ساخت پروژه
پیش از استفادهی از این پروژهی کتابخانه، باید آنرا build کرد:
ng build my-lib
پس از اجرای این دستور، خروجی ذیل مشاهده میشود:
Building Angular Package Building entry point 'my-lib' Rendering Stylesheets Rendering Templates Compiling TypeScript sources through ngc Downleveling ESM2015 sources through tsc Bundling to FESM2015 Bundling to FESM5 Bundling to UMD Minifying UMD bundle Remap source maps Relocating source maps Copying declaration files Writing package metadata Removing scripts section in package.json as it's considered a potential security vulnerability. Built my-lib Built Angular Package! - from: D:\my-lib-test\projects\my-lib - to: D:\my-lib-test\dist\my-lib
استفادهی از کتابخانهی تولید شده
پس از پایان موفقیت آمیز مرحلهی Build، اکنون نوبت به استفادهی از این کتابخانه است. استفادهی از آن نیز همانند تمام کتابخانهها و وابستگیهای ثالثی است که تا پیش از این از آنها استفاده کردهایم. برای مثال ماژول آنرا در قسمت imports مربوط به NgModule کلاس AppModule معرفی میکنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و MyLibModule را به نحو ذیل اضافه میکنیم:
import { MyLibModule } from "my-lib"; @NgModule({ imports: [ BrowserModule, MyLibModule ] }) export class AppModule { }
اما سؤال اینجا است که آیا این پوشه پس از build، داخل پوشهی node_modules نیز کپی شدهاست؟ پاسخ آن خیر است و برای مدیریت خودکار آن، به صورت زیر عمل شدهاست:
اگر به فایل tsconfig.json اصلی و واقع در ریشهی workspace دقت کنید، پس از اجرای دستور «ng generate library my-lib»، قسمت paths آن نیز به صورت خودکار ویرایش شدهاست:
{ "compilerOptions": { "paths": { "my-lib": [ "dist/my-lib" ] } } }
برای نمونه اگر شارهگر ماوس را بر روی my-lib قرار دهید، به درستی مسیر خوانده شدن آن، تشخیص داده میشود.
به این ترتیب مسیر این import، چه در این پروژهی محلی و چه برای کسانیکه پوشهی dist/my-lib را به صورت یک بستهی npm جدید دریافت کردهاند، یکی خواهد بود.
در ادامه اگر به فایل app.component.html مراجعه کرده و selector کامپوننت show-data را به آن اضافه کنیم:
<lib-show-data></lib-show-data>
توزیع کتابخانهی ایجاد شده برای عموم
برای اینکه این کتابخانهی تولیدی را در اختیار عموم، در سایت npm قرار دهیم، ابتدا باید کتابخانه را در حالت production build تولید و سپس آنرا publish کرد:
ng build my-lib --prod cd dist/my-lib npm publish
البته دستور آخر نیاز به ایجاد یک اکانت در سایت npm و وارد شدن به آنرا دارد. جزئیات بیشتر آن در اینجا.
اگر با وب فرمها کار کرده باشید، حتما با تنظیم زیر در فایل web.config برنامههای وب آشنا هستید:
<pages validaterequest="false"></pages>
Request Validation قابلیتی است که از زمان ASP.NET 1.1 وجود داشتهاست و توسط آن اگر اطلاعات دریافتی از کاربر به همراه تگهای HTML و یا کدهای JavaScript ای باشد، خطرناک تشخیص داده شده و با ارائهی پیام خطایی (مانند تصویر فوق)، پردازش درخواست متوقف میشود. این اعتبارسنجی بر روی هدرها، کوئری استرینگها، بدنهی درخواست و کوکیها صورت میگیرد. هدف آن نیز به حداقل رساندن امکان حملات Cross-Site Scripting و یا XSS است.
محدودیتهای اعتبارسنجی درخواستها
هر چند Request validation یک ویژگی و امکان جالب است، اما ... در عمل راهحل جامعی نیست و تنها اگر کاربر تگهای HTML ای را ارسال کند، متوجه وجود یک خطر احتمالی میشود. برای مثال اگر این اطلاعات خطرناک به نحو دیگری در قسمتهای مختلفی مانند attributeها، CSSها و غیره نیز تزریق شوند، عکس العملی را نشان نخواهد داد. به علاوه اگر این نوع حملات به همراه ترکیب آنها با روشهای Unicode نیز باشد، میتوان این اعتبارسنجی را دور زد.
اعتبارسنجی خودکار درخواستها و حس کاذب امنیت
متاسفانه وجود اعتبارسنجی خودکار درخواستها سبب این توهم میشود که برنامه در مقابل حملات XSS امن است و بالاخره این قابلیت توسط مایکروسافت در برنامه قرار داده شدهاست و ما هم به آن اطمینان داریم. اما با توجه به نحوهی پیاده سازی و محدودیتهای یاد شدهی آن، این قابلیت صرفا یک لایهی بسیار ابتدایی اعتبارسنجی اطلاعات ارسالی به سمت سرور را شامل میشود و بررسی تمام حالات حملات XSS را پوشش نمیدهد (اگر علاقمند هستید که بدانید چه بازهای از این حملات ممکن هستند، آزمونهای واحد کتابخانهی HtmlSanitizer را بررسی کنید).
پایان اعتبارسنجی درخواستها در ASP.NET Core
طراحان ASP.NET Core تصمیم گرفتهاند که یک چنین قابلیتی را به طور کامل از ASP.NET Core حذف کنند؛ چون به این نتیجه رسیدهاند که ... ایدهی خوبی نبوده و در اکثر مواقع برنامه نویسها کاملا آنرا خاموش میکنند (مانند مثالهای وب فرم و MVC فوق). اعتبارسنجی درخواستها مشکل یک برنامه است و مراحل و سطوح آن از هر برنامه، به برنامهی دیگری بر اساس نیازمندیهای آن متفاوت است. به همین جهت تعیین اجباری اعتبارسنجی درخواستها در نگارشهای قبلی ASP.NET سبب شدهاست که عملا برنامه نویسها با آن کار نکنند. بنابراین در اینجا دیگر خبری از ویژگیهای ValidateInput و یا AllowHtml و یا مانند وب فرمها و HTTP Module مخصوص آن، به همراه یک میانافزار تعیین اعتبار درخواستها نیست.
اکنون برای مقابله با حملات XSS در کدهای سمت سرور برنامههای ASP.NET Core چه باید کرد؟
در ASP.NET Core، کار مقابلهی با حملات XSS، از فریمورک، به خود برنامه واگذار شدهاست و در اینجا شما بر اساس نیازمندیهای خود تصمیم خواهید گرفت که تا چه حدی و چه مسایلی را کنترل کنید. برای این منظور در سمت سرور، استفادهی ترکیبی از سه روش زیر توصیه میشود:
الف) تمیز کردن اطلاعات ورودی رسیدهی از کاربران توسط کتابخانههایی مانند HtmlSanitizer
ب) محدود کردن بازهی اطلاعات قابل قبول ارسالی توسط کاربران
[Required] [StringLength(50)] [RegularExpression(@"^[a-zA-Z0-9 -']*$")] public string Name {get;set;}
دارا بودن امکانات بسیار قدرتمند و پشتیبانی از محیط فارسی و همچنین پشتیبانی آنها جهت پاسخگویی به سوالات، چه از طریق ایمیل یا چت، از نقاط قوت این ابزار به شمار میروند. در جدول مقایسات میتوانید تفاوت نسخههای موجود این گزارش ساز را مشاهده کنید. برای استفاده در MVC از نسخه وب آن استفاده میکنیم.
در این مقاله قصد داریم با نحوه راه ندازی این ابزار در وب (MVC) آشنا شویم که شامل مباحث زیر میشود:
- استفاده از EF به عنوان منبع داده و ارسال آنها به سمت گزارش ساز
- نحوه طراحی فایل MRT و بایند کردن دادههای اطلاعاتی و ایجاد جدول
- استفاده از امکانات فایل خروجی ، چاپ و پیش نمایش و...
- بررسی Direction جهت استفاده در محیطهای فارسی زبان
- نحوه ارسال اطلاعات بین دو اکشن متفاوت
طراحی فایل MRT
فایل MRT در واقع یک قالب (Template) خالی از مقادیر متغیر است که در StimulSoft Studio به طراحی آن میپردازیم و در برنامه خود، این مقادیر متغیر را با اطلاعات دلخواه خود جایگزین میکنیم. تصویر زیر یک نمونه از یک گزارش خالی است که ابتدا آن را طراحی کرده و سپس در برنامه آن را مورد استفاده قرار میدهیم:
برای اینکه فایل MRT بتواند دیتاهای لازمی را که به آن پاس میدهیم، بخواند و در جای مشخص شده قرار بدهد، باید یک BussinessObject برای آن ایجاد کنیم. بعد از اینکه یک گزارش جدید ایجاد کردید، در سمت راست به قسمت Dictionary بروید و در قسمت BussinessObject گزینه NewBussinessObject را انتخاب کنید. یک نام و نام مستعار که عموما هم یکی است، برای آن انتخاب کنید. در زیر همان پنجره شما میتوانید ستونهای اطلاعاتی خود را تعریف کنید. در اینجا من میخواهم اطلاعات یک راننده را به همراه خودروی وی، نشان دهم. برای همین، من دو موجودیت راننده و خودروی راننده را دارم. پس اسم Business Object را DriverReport میگذارم و ستونهای اطلاعاتی فقط راننده (بدون در نظر گرفتن خودروی وی) را وارد میکنم.
در همین کادر بالا شما میتوانید تصیم بگیرید که آیا میخواهید اطلاعات خودرو را به همراه دیگرستونهای اطلاعاتی راننده، ایجاد کنید یا اینکه برای خودرو یک نوع مجزا انتخاب کنید. اگر تنها یک خودرو برای راننده باشد، شاید راحتتر باشید همانند اطلاعات راننده با آن رفتار کنید. ولی اگر مثلا بخواهید خودرویهای گذشته راننده را هم جز لیست داشته باشید، بهتر است یک Business Object جدید متعلق و زیر مجموعه Business Object راننده ایجاد کنید. در اینجا چون تنها یک خودرو است، من آن اطلاعات آن را به همراه راننده، ارسال میکنم. شکل زیر ساختار درختی از گزارش بالاست:
شکل زیر هم یک ساختار دیگر از یک گزارش است که شامل Business objectهای مختلف میشود:
سپس همین فیلدها را به سمت صفحه خالی بکشانید. با دو بار کلیک روی فیلدهای قرار گرفته در صفحه، با نحوه بایند کردن مقادیر آشنا میشوید؛ هر فیلدی که قرار است دیتای آن بایند شود، باید به شکل زیر در بخش Expression پنجره باز شده، نوشته شود:
{driverReport.LastName}
در دیکشنری همچنین انواع دیگری از فیلدها نیز به چشم میخورد:
متغیرها: این نوع فیلد یک متغیر است که به طور جداگانه میتواند مقداردهی شود و از آن بیشتر برای ارسال دادههای تکی چون تصاویر، تاریخ شمسی و ... میتوان استفاده کرد.
متغیرهای سیستمی: این نوع متغیرها توسط خود گزارش ساز به طور مستقیم پر میشوند که شامل شماره صفحه، تاریخ و زمان، تعداد صفحات، مقادیر دو ارزشی (آیا صفحه آخر گزارش است؟) و ... میشود.
توابع: گزارش ساز شامل یک سری توابع آماده برای اعمال تغییرات بر روی دادهها میباشد که در دستههای مختلفی چون کار با رشتهها، زمان، ریاضیات و... قرار گرفتهاند.
بعد از تکمیل آن، فایل MRT را ذخیره و در یک دایرکتوری در ساختار پروژه قرار دهید.
راه اندازی گزارش ساز در ASP.Net MVC
اولین کاری که میکنیم، ورود سه dll اصلی به پروژه است:
Stimulate.Base
Stimulate.Report
Stimulate.Report.MVC
در مرحله بعد یک متد ساخته و یک ویوو را برای صفحه گزارش گیری ایجاد میکنیم:
public ActionResult Report(int id) { return View(); }
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions() { Localization = "~/content/reports/fa.xml", Actions = { GetReportSnapshot = "LoadReportSnapshot", ViewerEvent = "ViewerEvent", ExportReport = "ExportReport", PrintReport = "PrintReport", } }
در نسخههای دو سال اخیر، استفاده از این Helper تفاوتهایی در نحوه استفاده از خصوصیتهای آن کرده است. در این روش جدید، پراپرتیها دسته بندی شده و برای دسترسی به هر کدام باید به بخش آن مراجعه کنید؛ مثلا پراپرتیهای Action، در دسته Actions قرار گرفتهاند یا خصوصیتهای ظاهری در دسته Appearance، یا گزینههای مرتبط با خروجی گرفتنها، در دسته Export قرار گرفتهاند و الی آخر که در نسخههای پیشین، کد بالا را به شکل زیر، با پیشوند نام دسته مینوشتیم:
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions() { Localization = "~/content/reports/fa.xml", ActionGetReportSnapshot = "LoadReportSnapshot", ActionViewerEvent = "ViewerEvent", ActionExportReport = "ExportReport", ActionPrintReport = "PrintReport", }
بعد از آن لازم است دیتاها را از طریق EF خوانده و به یک مدل جدید که بر اساس اطلاعات گزارش شماست و قرار است گزارش شما این پراپرتیها را بشناسد، به طور دستی یا با استفاده یک کتابخانه mapping مثل automapper انتقال دهید. یا حتی میتوانید مانند کد زیر از ساختاری ناشناس استفاده کنید. در کد زیر، من به صورت تمرینی اطلاعات یک راننده و خودروی او را انتقال میدهم:
var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 };
var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", car = new { Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 } };
var report = new StiReport(); report.Load(Server.MapPath("~/Content/Reports/driver.mrt")); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date));
var report = new StiReport(); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date)); report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
پس کد کامل ما برای ایجاد یک گزارش به شکل زیر میشود:
public ActionResult LoadReportSnapshot() { var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 }; var report = new StiReport(); report.Load(Server.MapPath("~/Content/Reports/driver.mrt")); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date)); return StiMvcViewer.GetReportSnapshotResult(HttpContext, report); }
اگر دوباره در ویو مربوطه، به سراغ helper برویم میبینیم که سه اکشن متد دیگر وجود دارند که در زیر، به ترتیب با نحوه کار آنها و کد اکشن متد آنها اشاره میکنیم:
Viewer Events : این اکشن متد که تنها یک خط ActionResult استاتیک را فراخوانی میکند، جهت مدیریت رویدادهای گزارش چون: زوم، صفحه بندی گزارش، خروجیها و چاپ میباشد و وجود آن در گزارش از الزامات است.
public virtual ActionResult ViewerEvent() { return StiMvcViewer.ViewerEventResult(); }
PrintReport: برای مدیریت و ارسال گزارشات به دستگاه چاپ میباشد. این اطلاعات از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط PrintReportResult آن را دریافت میکند.
public virtual ActionResult PrintReport() { return StiMvcViewer.PrintReportResult(this.HttpContext); }
ExportReport: گزارش ساز استیمول به شما اجاز میدهد در فرمتهای گوناگونی چون xlsx,docx,pptx,pdf,rtf و ... از گزارش خود خروجی بگیرید. اطلاعات گزارش از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط ExportReportResult دریافت میشود.
public virtual ActionResult ExportReport() { return StiMvcViewer.ExportReportResult(this.HttpContext); }
البته خوشبختانه این مشکل در حالت پیش نمایش و چاپ و خروجیها دیده نمیشود و فقط مختص نمایش روی فرم Html است. برای حل این مشکل ممکن است از گزینه یا پراپرتی RightToLeft، در بخش Appearance موجود در helper استفاده کنید که البته استفاده از آن مانند تصویر بالا، فقط محدود به container گزارش و نوار ابزار آن میشود. برای حل این مشکل کافی است کد css زیر را به صفحه گزارش اضافه کنید تا مشکل حل شود:
.stiMvcViewerReportPanel table{ direction:ltr !important; }
حال حتما پیش خود میگویید که این روش برای اطلاعات ایستا و تمرینی مناسب است و من چگونه باید پارامترهای ارسالی به اکشن متد Report را به اکشن متد LoadReportSnapshot ارسال کنم. برای این منظور استفاده از SessionStateها زیاد توصیه شدهاست:
public virtual ActionResult Report(int id) { TempData["id"]=id; return View(); } public virtual ActionResult LoadReportSnapshot() { var driverId = (int)TempData ["id"]; //..... }
public virtual ActionResult Report(int id) { return View(); } public virtual ActionResult LoadReportSnapshot(int id) { //..... }
نکته بسیار مهم: گزارش ساز استیمول متاسفانه شامل تنظیم پیش فرض نامناسبی است که عملیات کش را بر روی گزارشها اعمال میکند. به عنوان مثال تصور کنید من صفحه گزارش شخصی به نام «وحید نصیری» را باز میکنم و در تب دیگر گزارش شخص دیگری با نام «علی یگانه مقدم» را باز میکنم. حال اگر کاربر به سراغ تب آقای نصیری برود و بخواهد چاپ یا خروجی درخواست کند، اشتباها با گزارش علی یگانه مقدم روبرو خواهد شد که این اتفاق به دلیل کش شدن رخ میدهد. برای غیر فعال کردن این قابلیت پیش فرض، کد زیر را در Helper اضافه کنید:
Server = { GlobalReportCache = false }
FluentValidation #1
public class Customer { public int Id { get; set; } public string Surname { get; set; } public string Forename { get; set; } public decimal Discount { get; set; } public string Address { get; set; } }
using FluentValidation; public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator { RuleFor(customer => customer.Surname).NotNull(); } }
اعتبارسنجی زنجیره ای برای یک خاصیت
RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult results = validator.Validate(customer);
خروجی متد Validate، یک ValidationResult است که شامل دو خاصیت زیر میباشد:
- IsValid: از نوع bool برای تعیین اینکه اعتبارسنجی موفقیت آمیز بوده یا خیر.
- Errors: یک مجموعه از ValidationFailure که جزئیات تمام اعتبارسنجیهای ناموفق را شامل میشود.
Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult results = validator.Validate(customer); if(! results.IsValid) { foreach(var failure in results.Errors) { Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage); } }
پرتاب استثناها (Throwing Exceptions)
Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); validator.ValidateAndThrow(customer);
public class Customer { public string Name { get; set; } public Address Address { get; set; } } public class Address { public string Line1 { get; set; } public string Line2 { get; set; } public string Town { get; set; } public string County { get; set; } public string Postcode { get; set; } } public class AddressValidator : AbstractValidator<Address> { public AddressValidator() { RuleFor(address => address.Postcode).NotNull(); //etc } } public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(customer => customer.Name).NotNull(); RuleFor(customer => customer.Address).SetValidator(new AddressValidator()) } }
استفاده از Validatorها برای مجموعهها (Collections)
public class Customer { public IList<Order> Orders { get; set; } } public class Order { public string ProductName { get; set; } public decimal? Cost { get; set; } } var customer = new Customer(); customer.Orders = new List<Order> { new Order { ProductName = "Foo" }, new Order { Cost = 5 } };
public class OrderValidator : AbstractValidator<Order> { public OrderValidator() { RuleFor(x => x.ProductName).NotNull(); RuleFor(x => x.Cost).GreaterThan(0); } }
این Validator میتواند داخل CustomerValidator مورد استفاده قرار بگیرد (با استفاده از متد SetCollectionValidator):
public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator()); } }
می توان با استفاده از متد Where یا Unless روی اعتبارسنجی شرط گذاشت:
RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator()).Where(x => x.Cost != null);
گروه بندی قوانین اعتبارسنجی
public class PersonValidator : AbstractValidator<Person> { public PersonValidator() { RuleSet("Names", () => { RuleFor(x => x.Surname).NotNull(); RuleFor(x => x.Forename).NotNull(); }); RuleFor(x => x.Id).NotEqual(0); } }
var validator = new PersonValidator(); var person = new Person(); var result = validator.Validate(person, ruleSet: "Names");