Tuple در دات نت 4
نوع جدیدی در دات نت 4 به نام Tuple اضافه شده است که در این مطلب به بررسی آن خواهیم پرداخت.
در ریاضیات، Tuple به معنای لیست مرتبی از اعضاء با تعداد مشخص است. Tuple در زبانهای برنامه نویسی Dynamic مانند اف شارپ، Perl ، LISP و بسیاری موارد دیگر مطلب جدیدی نیست. در زبانهای dynamic برنامه نویسها میتوانند متغیرها را بدون معرفی نوع آنها تعریف کنند. اما در زبانهای Static مانند سی شارپ، برنامه نویسها موظفند نوع متغیرها را پیش از کامپایل آنها معرفی کنند که هر چند کار کد نویسی را اندکی بیشتر میکند اما به این صورت شاهد خطاهای کمتری نیز خواهیم بود (البته سی شارپ 4 این مورد را با معرفی واژهی کلیدی dynamic تغییر داده است).
برای مثال در اف شارپ داریم:
let data = (“John Doe”, 42)
که سبب ایجاد یک tuple که المان اول آن یک رشته و المان دوم آن یک عدد صحیح است میشود. اگر data را بخواهیم نمایش دهیم خروجی آن به صورت زیر خواهد بود:
printf “%A” data
// Output: (“John Doe”,42)
در دات نت 4 فضای نام جدیدی به نام System.Tuple معرفی شده است که در حقیقت ارائه دهندهی نوعی جنریک میباشد که توانایی در برگیری انواع مختلفی را دارا است :
public class Tuple<T1>
up to:
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>
همانند آرایهها، اندازهی Tuples نیز پس از تعریف قابل تغییر نیستند (immutable). اما تفاوت مهم آن با یک آرایه در این است که اعضای آن میتوانند نوعهای کاملا متفاوتی داشته باشند. همچنین تفاوت مهم آن با یک ArrayList یا آرایهای از نوع Object، مشخص بودن نوع هر یک از اعضاء آن است که type safety بیشتری را به همراه خواهد داشت و کامپایلر میتواند در حین کامپایل دقیقا مشخص نماید که اطلاعات دریافتی از نوع صحیحی هستند یا خیر.
یک مثال کامل از Tuples را در کلاس زیر ملاحظه خواهید نمود:
using System;
using System.Linq;
using System.Collections.Generic;
namespace TupleTest
{
class TupleCS4
{
#region Methods (4)
// Public Methods (4)
public static Tuple<string, string> GetFNameLName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new NullReferenceException("name is empty.");
var nameParts = name.Split(',');
if (nameParts.Length != 2)
throw new FormatException("name must contain ','");
return Tuple.Create(nameParts[0], nameParts[1]);
}
public static void PrintSelectedTuple()
{
var list = new List<Tuple<string, int>>
{
new Tuple<string, int>("A", 1),
new Tuple<string, int>("B", 2),
new Tuple<string, int>("C", 3)
};
var item = list.Where(x => x.Item2 == 2).SingleOrDefault();
if (item != null)
Console.WriteLine("Selected Item1: {0}, Item2: {1}",
item.Item1, item.Item2);
}
public static void PrintTuples()
{
var tuple1 = new Tuple<int>(12);
Console.WriteLine("tuple1 contains: item1:{0}", tuple1.Item1);
var tuple2 = Tuple.Create("Item1", 12);
Console.WriteLine("tuple2 contains: item1:{0}, item2:{1}",
tuple2.Item1, tuple2.Item2);
var tuple3 = Tuple.Create(new DateTime(2010, 5, 6), "Item2", 20);
Console.WriteLine("tuple3 contains: item1:{0}, item2:{1}, item3:{2}",
tuple3.Item1, tuple3.Item2, tuple3.Item3);
}
public static void Tuple8()
{
var tup =
new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>
(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int>(8, 9));
Console.WriteLine("tup.Rest Item1: {0}, Item2: {1}",
tup.Rest.Item1,tup.Rest.Item2);
}
#endregion Methods
}
}
using System;
namespace TupleTest
{
class Program
{
static void Main()
{
var data = TupleCS4.GetFNameLName("Vahid, Nasiri");
Console.WriteLine("Data Item1:{0} & Item2:{1}",
data.Item1, data.Item2);
TupleCS4.PrintTuples();
TupleCS4.PrintSelectedTuple();
TupleCS4.Tuple8();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
توضیحات :
- روشهای متفاوت ایجاد Tuples را در متد PrintTuples میتوانید ملاحظه نمائید. همچنین نحوهی دسترسی به مقادیر هر کدام از اعضاء نیز مشخص شده است.
- کاربرد مهم Tuples در متد GetFNameLName نمایش داده شده است؛ زمانیکه نیاز است تا چندین خروجی از یک تابع داشته باشیم. به این صورت دیگر نیازی به تعریف آرگومانهایی به همراه واژه کلیدی out نخواهد بود یا دیگر نیازی نیست تا یک شیء جدید را ایجاد کرده و خروجی را به آن نسبت دهیم. به همان سادگی زبانهای dynamic در اینجا نیز میتوان یک tuple را ایجاد و استفاده کرد.
- بدیهی است از Tuples در یک لیست جنریک و یا حالات دیگر نیز میتوان استفاده کرد. مثالی از این دست را در متد PrintSelectedTuple ملاحظه خواهید نمود. ابتدا یک لیست جنریک از Tuple ایی با دو عضو تشکیل شده است. سپس با استفاده از امکانات LINQ ، عضوی که آیتم دوم آن مساوی 2 است یافت شده و سپس المانهای آن نمایش داده میشود.
- نکتهی دیگری را که حین کار با Tuples میتوان در نظر داشت این است که اعضای آن حداکثر شامل 8 عضو میتوانند باشند که عضو آخر باید یک Tuple تعریف گردد و بدیهی است این Tuple نیز میتواند شامل 8 عضو دیگر باشد و الی آخر که نمونهای از آن را در متد Tuple8 میتوان مشاهده کرد.
اهمیت محل فراخوانی OrderBy و Distinct
من برنامه ای شبیه زیر نوشته ام:
class MainClass { class Student { public string Name { get; set; } public int Age { get; set; } } static void Main() { List<Student> s = new List<Student>() { new Student(){Name="A",Age=12}, new Student(){Name="B",Age=10}, new Student(){Name="A",Age=5}, new Student(){Name="A",Age=6}, }.ToList(); var model = s.Where(x => x.Name.Contains("A")).Distinct().OrderBy(x => x.Age).ToList(); Console.Read(); } }
سوالی که داشتم اینه چرا با وجود متد Distinct دستورات فوق، سه رکورد را بر میگرداند؟
در ادامه بررسی تصویر امنیتی سایت مواردی که باید پیاده سازی شود برای مورد اول میتوان کلاس زیر را در نظر گرفت که متدهایی برای تولید اعداد بصورت تصادفی در بین بازه معرفی شده را بازگشت میدهد:
// RandomGenerator.cs using System; using System.Security.Cryptography; namespace PersianCaptchaHandler { public class RandomGenerator { private static readonly byte[] Randb = new byte[4]; private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider(); public static int Next() { Rand.GetBytes(Randb); var value = BitConverter.ToInt32(Randb, 0); if (value < 0) value = -value; return value; } public static int Next(int max) { Rand.GetBytes(Randb); var value = BitConverter.ToInt32(Randb, 0); value = value%(max + 1); if (value < 0) value = -value; return value; } public static int Next(int min, int max) { var value = Next(max - min) + min; return value; } } }
// NumberToString.cs namespace PersianCaptchaHandler { public class NumberToString { #region Fields private static readonly string[] Yakan = new[] { "صفر", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه" }; private static readonly string[] Dahgan = new[] { "", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" }; private static readonly string[] Dahyek = new [] { "ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", "شانزده", "هفده", "هجده", "نوزده" }; private static readonly string[] Sadgan = new [] { "", "یکصد", "دوصد", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد" }; private static readonly string[] Basex = new [] { "", "هزار", "میلیون", "میلیارد", "تریلیون" }; #endregion private static string Getnum3(int num3) { var s = ""; var d12 = num3 % 100; var d3 = num3 / 100; if (d3 != 0) s = Sadgan[d3] + " و "; if ((d12 >= 10) && (d12 <= 19)) { s = s + Dahyek[d12 - 10]; } else { var d2 = d12 / 10; if (d2 != 0) s = s + Dahgan[d2] + " و "; var d1 = d12 % 10; if (d1 != 0) s = s + Yakan[d1] + " و "; s = s.Substring(0, s.Length - 3); } return s; } public static string ConvertIntNumberToFarsiAlphabatic(string snum) { var stotal = ""; if (snum == "0") return Yakan[0]; snum = snum.PadLeft(((snum.Length - 1) / 3 + 1) * 3, '0'); var l = snum.Length / 3 - 1; for (var i = 0; i <= l; i++) { var b = int.Parse(snum.Substring(i * 3, 3)); if (b != 0) stotal = stotal + Getnum3(b) + " " + Basex[l - i] + " و "; } stotal = stotal.Substring(0, stotal.Length - 3); return stotal; } } }
using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace PersianCaptchaHandler { public class Encryptor { #region constraints private static string Password { get { return "Mehdi"; } } private static string Salt { get { return "Payervand"; } } #endregion public static string Encrypt(string clearText) { // Turn text to bytes var clearBytes = Encoding.Unicode.GetBytes(clearText); var pdb = new PasswordDeriveBytes(Password, Encoding.Unicode.GetBytes(Salt)); var ms = new MemoryStream(); var alg = Rijndael.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write); cs.Write(clearBytes, 0, clearBytes.Length); cs.Close(); var encryptedData = ms.ToArray(); return Convert.ToBase64String(encryptedData); } public static string Decrypt(string cipherText) { // Convert text to byte var cipherBytes = Convert.FromBase64String(cipherText); var pdb = new PasswordDeriveBytes(Password, Encoding.Unicode.GetBytes(Salt)); var ms = new MemoryStream(); var alg = Rijndael.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(cipherBytes, 0, cipherBytes.Length); cs.Close(); var decryptedData = ms.ToArray(); return Encoding.Unicode.GetString(decryptedData); } } }
و نیز برای اعتبار سنجی عدد دریافتی از کاربر میتوان از عبارت با قاعده زیر استفاده کرد:
// Utils.cs using System.Text.RegularExpressions; namespace PersianCaptchaHandler { public class Utils { private static readonly Regex NumberMatch = new Regex(@"^([0-9]*|\d*\.\d{1}?\d*)$", RegexOptions.Compiled); public static bool IsNumber(string number2Match) { return NumberMatch.IsMatch(number2Match); } } }
<add verb="GET" path="/captcha/" type="PersianCaptchaHandler.CaptchaHandler, PersianCaptchaHandler, Version=1.0.0.0, Culture=neutral" />
<!-- ASPX --> <dl> <dt>تصویر امنیتی</dt> <dd> <asp:Image ID="imgCaptchaText" runat="server" AlternateText="CaptchaImage" /> <asp:HiddenField ID="hfCaptchaText" runat="server" /> <asp:ImageButton ID="btnRefreshCaptcha" runat="server" ImageUrl="/img/refresh.png" OnClick="btnRefreshCaptcha_Click" /> <br /> <asp:TextBox ID="txtCaptcha" runat="server" AutoCompleteType="Disabled"></asp:TextBox> </dd> <dd> <asp:Button ID="btnSubmit" runat="server" Text="ثبت" OnClick="btnSubmit_Click" /> </dd> </dl> <asp:Label ID="lblMessage" runat="server"></asp:Label>
protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) SetCaptcha(); } private void SetCaptcha() { lblMessage.Text = txtCaptcha.Text = string.Empty; var newNumber = RandomGenerator.Next(100, 999) ; var farsiAlphabatic = NumberToString.ConvertIntNumberToFarsiAlphabatic(newNumber.ToString()); hfCaptchaText.Value = HttpUtility .UrlEncode( Encryptor.Encrypt( farsiAlphabatic ) ); txtCaptcha.Text = string.Empty; imgCaptchaText.ImageUrl = "/captcha/?text=" + hfCaptchaText.Value; }
private string GetCaptcha() { var farsiAlphabatic = NumberToString.ConvertIntNumberToFarsiAlphabatic(txtCaptcha.Text); var encryptedString = HttpUtility .UrlEncode( Encryptor.Encrypt( farsiAlphabatic ) ); return encryptedString; } private bool ValidateUserInputForLogin() { if (!Utils.IsNumber(txtCaptcha.Text)) { lblMessage.Text = "تصویر امنیتی را بطور صحیح وارد نکرده اید"; return false; } var strGetCaptcha = GetCaptcha(); var strDecodedVAlue = hfCaptchaText.Value; if (strDecodedVAlue != strGetCaptcha) { lblMessage.Text = "کلمه امنیتی اشتباه است"; SetCaptcha(); return false; } return true; } protected void btnSubmit_Click(object sender, EventArgs e) { if (!ValidateUserInputForLogin()) return; lblMessage.Text = "کلمه امنیتی درست است"; } protected void btnRefreshCaptcha_Click(object sender, ImageClickEventArgs e) { SetCaptcha(); }
آموزش Prism #2
تشریح پروژه:
میخواهیم برنامه ای بنویسیم که دارای سه ماژول زیر است.:
- ماژول Navigator : برای انتخاب و Switch کردن بین ماژولها استفاده میشود؛
- ماژول طبقه بندی کتابها : لیست طبقه بندی کتابها را به ما نمایش میدهد؛
- ماژول لیست کتابها : عناوین کتابها به همراه نویسنده و کد کتاب را به ما نمایش میدهد.
ابتدا یک پروژه WPF در Vs.Net ایجاد کنید(در اینجا من نام آن را FirstPrismSample گذاشتم). قصد داریم یک صفحه طراحی کنیم که دو ماژول مختلف در آن لود شود. ابتدا باید Shell پروژه رو طراحی کنیم. یک Window جدید به نام Shell بسازید و کد زیر را در آن کپی کنید.
<Window x:Class="FirstPrismSample.Shell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:com="http://www.codeplex.com/CompositeWPF" Title="Prism Sample By Masoud Pakdel" Height="400" Width="600" WindowStartupLocation="CenterScreen"> <DockPanel> <ContentControl com:RegionManager.RegionName="WorkspaceRegion" Width="400"/> <ContentControl com:RegionManager.RegionName="NavigatorRegion" DockPanel.Dock="Left" Width="200" /> </DockPanel> </Window>
#پروژه Common
قبل از هر چیز یک پروژه Common میسازیم و مشترکات بین ماژولها رو در آن قرار میدهیم(این پروژه باید به تمام ماژولها رفرنس داده شود). این مشترکات شامل :
- کلاس پایه ViewModel
- کلاس ViewRequestEvent
- کلاس ModuleService
کد کلاس ViewModelBase که فقط اینترفیس INotifyPropertyChanged رو پیاده سازی کرده است:
using System.ComponentModel; namespace FirstPrismSample.Common { public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChangedEvent( string propertyName ) { if ( PropertyChanged != null ) { PropertyChangedEventArgs e = new PropertyChangedEventArgs( propertyName ); PropertyChanged( this, e ); } } } }
using Microsoft.Practices.Composite.Presentation.Events; namespace FirstPrismSample.Common.Events { public class ViewRequestedEvent : CompositePresentationEvent<string> { } }
توضیح درباره EventAggregator
EventAggregator یا به اختصار EA مکانیزمی است در پروژهای ماژولار برای اینکه در Composite UIها بتوانیم بین کامپوننتها ارتباط برقرار کنیم. استفاده از EA وابستگی بین ماژولها را از بین خواهد برد. برنامه نویسانی که با MVVM Light آشنایی دارند از قابلیت Messaging موجود در این فریم ورک برای ارتباط بین View و ViewModel استفاده میکنند. در Prism این عملیات توسط EA انجام میشود. یعنی برای ارتباط با Viewها باید از EA تعبیه شده در Prism استفاده کنیم. در ادامه مطلب، چگونگی استفاده از EA را خواهید آموخت.
namespace FirstPrismSample .Common { public interface IModuleServices { void ActivateView(string viewName); } }
using Microsoft.Practices.Composite.Regions; using Microsoft.Practices.Unity; namespace FirstPrismSample.Common { public class ModuleServices : IModuleServices { private readonly IUnityContainer m_Container; public ModuleServices(IUnityContainer container) { m_Container = container; } public void ActivateView(string viewName) { var regionManager = m_Container.Resolve<IRegionManager>(); // غیر فعال کردن ویو IRegion workspaceRegion = regionManager.Regions["WorkspaceRegion"]; var views = workspaceRegion.Views; foreach (var view in views) { workspaceRegion.Deactivate(view); } //فعال کردن ویو انتخاب شده var viewToActivate = regionManager.Regions["WorkspaceRegion"].GetView(viewName); regionManager.Regions["WorkspaceRegion"].Activate(viewToActivate); } } }
*نکته: در هر ماژول ارجاع به اسمبلیهای Prism مورد نیاز است.
#ماژول طبقه بندی کتاب ها:
برای شروع یک Class Library جدید به نام ModuleCategory به پروژه اضافه کنید. یک UserControl به نام CategoryView بسازید و کدهای زیر را در آن کپی کنید.
<UserControl x:Class="FirstPrismSample.ModuleCategory.CategoryView " xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="LightGray" FlowDirection="RightToLeft" FontFamily="Tahoma"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text=" طبقه بندی ها"/> <ListView Grid.Row="1" Margin="10" Name="lvCategory"> <ListView.View> <GridView> <GridViewColumn Header="کد" Width="50" /> <GridViewColumn Header="عنوان" Width="200" /> </GridView> </ListView.View> </ListView> </Grid> </UserControl>
using Microsoft.Practices.Composite.Events; using Microsoft.Practices.Composite.Modularity; using Microsoft.Practices.Composite.Regions; using Microsoft.Practices.Unity; using FirstPrismSample.Common; using FirstPrismSample.Common.Events; using Microsoft.Practices.Composite.Presentation.Events; namespace FirstPrismSample.ModuleCategory { [Module(ModuleName = "ModuleCategory")] public class CategoryModule : IModule { private readonly IUnityContainer m_Container; private readonly string moduleName = "ModuleCategory"; public CategoryModule(IUnityContainer container) { m_Container = container; } ~CategoryModule() { var eventAggregator = m_Container.Resolve<IEventAggregator>(); var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>(); viewRequestedEvent.Unsubscribe(ViewRequestedEventHandler); } public void Initialize() { var regionManager = m_Container.Resolve<IRegionManager>(); regionManager.Regions["WorkspaceRegion"].Add(new CategoryView(), moduleName); var eventAggregator = m_Container.Resolve<IEventAggregator>(); var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>(); viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, true); } public void ViewRequestedEventHandler(string moduleName) { if (this.moduleName != moduleName) return; var moduleServices = m_Container.Resolve<IModuleServices>(); moduleServices.ActivateView(moduleName); } } }
*ModuleAttribute استفاده شده در بالای کلاس برای تعیین نام ماژول استفاده میشود. این Attribute دارای دو خاصیت دیگر هم است :
- OnDemand : برای تعیین اینکه ماژول باید به صورت OnDemand (بنا به درخواست) لود شود.
- StartupLoaded : برای تعیین اینکه ماژول به عنوان ماژول اول پروزه لود شود.(البته این گزینه Obsolute شده است)
*برای تعریف ماژول کلاس مورد نظر حتما باید اینترفیس IModule را پیاده سازی کند. این اینترفیس فقط شامل یک متد است به نام Initialize.
*در این پروژه چون Viewهای برنامه صرفا جهت نمایش هستند در نتیجه نیاز به ایجاد ViewModel برای آنها نیست. در پروژههای اجرایی حتما برای هر View باید ViewModel متناظر با آن تهیه شود.
توضیح درباره متد Initialize
در این متد ابتدا با استفاده از Container موجود RegionManager را به دست میآوریم. با استفاده از RegionManager میتونیم یک CompositeUI طراحی کنیم. در فایل Shell مشاهده کردید که یک صفحه به دو ناحیه تقسیم شد و به هر ناحیه هم یک نام اختصاص دادیم. دستور زیر به یک ناحیه اشاره خواهد داشت:
regionManager.Regions["WorkspaceRegion"]
#ماژول لیست کتاب ها:
ابتدا یک Class Library به نام ModuleBook بسازید و همانند ماژول قبلی نیاز به یک Window و یک کلاس داریم:
BookWindow که کاملا مشابه به CategoryView است.
<UserControl x:Class="FirstPrismSample.ModuleBook.BookView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="LightGray" FontFamily="Tahoma" FlowDirection="RightToLeft"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text="لیست کتاب ها"/> <ListView Grid.Row="1" Margin="10" Name="lvBook"> <ListView.View> <GridView> <GridViewColumn Header="کد" Width="50" /> <GridViewColumn Header="عنوان" Width="200" /> <GridViewColumn Header="نویسنده" Width="150" /> </GridView> </ListView.View> </ListView> </Grid> </UserControl>
کلاس BookModule که پیاده سازی و توضیحات آن کاملا مشابه به CategoryModule میباشد.
using Microsoft.Practices.Composite.Events; using Microsoft.Practices.Composite.Modularity; using Microsoft.Practices.Composite.Presentation.Events; using Microsoft.Practices.Composite.Regions; using Microsoft.Practices.Unity; using FirstPrismSample.Common; using FirstPrismSample.Common.Events; namespace FirstPrismSample.ModuleBook { [Module(ModuleName = "moduleBook")] public class BookModule : IModule { private readonly IUnityContainer m_Container; private readonly string moduleName = "ModuleBook"; public BookModule(IUnityContainer container) { m_Container = container; } ~BookModule() { var eventAggregator = m_Container.Resolve<IEventAggregator>(); var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>(); viewRequestedEvent.Unsubscribe(ViewRequestedEventHandler); } public void Initialize() { var regionManager = m_Container.Resolve<IRegionManager>(); var view = new BookView(); regionManager.Regions["WorkspaceRegion"].Add(view, moduleName); regionManager.Regions["WorkspaceRegion"].Deactivate(view); var eventAggregator = m_Container.Resolve<IEventAggregator>(); var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>(); viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, true); } public void ViewRequestedEventHandler(string moduleName) { if (this.moduleName != moduleName) return; var moduleServices = m_Container.Resolve<IModuleServices>(); moduleServices.ActivateView(m_WorkspaceBName); } } }
برای این ماژول هم ابتدا View مورد نظر را ایجاد میکنیم:
<UserControl x:Class="FirstPrismSample.ModuleNavigator.NavigatorView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Grid> <StackPanel VerticalAlignment="Center"> <TextBlock Text="انتخاب ماژول" Foreground="Green" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Tahoma" FontSize="24" FontWeight="Bold" /> <Button Command="{Binding ShowModuleCategory}" Margin="5" Width="125">طبقه بندی کتاب ها</Button> <Button Command="{Binding ShowModuleBook}" Margin="5" Width="125">لیست کتاب ها</Button> </StackPanel> </Grid> </UserControl>
public interface INavigatorViewModel { ICommand ShowModuleCategory { get; set; } ICommand ShowModuleBook { get; set; } string ActiveWorkspace { get; set; } IUnityContainer Container { get; set; } event PropertyChangedEventHandler PropertyChanged; }
*خاصیت ActiveWorkspace برای تعیین workspace فعال تعریف شده است.
حال به پیاده سازی مثال بالا میپردازیم:
public class NavigatorViewModel : ViewModelBase, INavigatorViewModel { public NavigatorViewModel(IUnityContainer container) { this.Initialize(container); } public ICommand ShowModuleCategory { get; set; } public ICommand ShowModuleBook { get; set; } public string ActiveWorkspace { get; set; } public IUnityContainer Container { get; set; } private void Initialize(IUnityContainer container) { this.Container = container; this.ShowModuleCategory = new ShowModuleCategoryCommand(this); this.ShowModuleBook = new ShowModuleBookCommand(this); this.ActiveWorkspace = "ModuleCategory"; } }
public class ShowModuleCategoryCommand : ICommand { private readonly NavigatorViewModel viewModel; private const string workspaceName = "ModuleCategory"; public ShowModuleCategoryCommand(NavigatorViewModel viewModel) { this.viewModel = viewModel; } public bool CanExecute(object parameter) { return viewModel.ActiveWorkspace != workspaceName; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { CommandServices.ShowWorkspace(workspaceName, viewModel); } }
public class ShowModuleBookCommand : ICommand { private readonly NavigatorViewModel viewModel; private readonly string workspaceName = "ModuleBook"; public ShowModuleBookCommand( NavigatorViewModel viewModel ) { this.viewModel = viewModel; } public bool CanExecute( object parameter ) { return viewModel.ActiveWorkspace != workspaceName; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute( object parameter ) { CommandServices.ShowWorkspace( workspaceName , viewModel ); } }
public static void ShowWorkspace(string workspaceName, INavigatorViewModel viewModel) { var eventAggregator = viewModel.Container.Resolve<IEventAggregator>(); var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>(); viewRequestedEvent.Publish(workspaceName); viewModel.ActiveWorkspace = workspaceName; }
عدم وابستگی ماژول ها
xcopy "$(TargetDir)FirstPrismSample.ModuleBook.dll" "$(SolutionDir)FirstPrismSample\bin\$(ConfigurationName)\Modules\" /Y
مانند:
مراحل بالا برای هر ماژول باید تکرار شود(ModuleNavigation , ModuleBook , ModuleCategory). بعد از Rebuild پروژه در فولدر bin پروژه اصلی یک فولدر به نام Module ایجاد میشود که اسمبلی هر ماژول در آن کپی خواهد شد.
ایجاد Bootstrapper
حال نوبت به Bootstrapper میرسد(در پست قبلی در باره مفهوم Bootstrapper شرح داده شد). در پروژه اصلی یعنی جایی که فایل App.xaml قرار دارد کلاس زیر را ایجاد کنید.
public class Bootstrapper : UnityBootstrapper { protected override void ConfigureContainer() { base.ConfigureContainer(); Container.RegisterType<IModuleServices, ModuleServices>(); } protected override DependencyObject CreateShell() { var shell = new Shell(); shell.Show(); return shell; } protected override IModuleCatalog GetModuleCatalog() { var catalog = new DirectoryModuleCatalog(); catalog.ModulePath = @".\Modules"; return catalog; } }
متد GetModuleCatalog برای تعیین مسیر ماژولها در پروژه کاربرد دارد. در این متد با استفاده از خاصیت ModulePath کلاس DirectoryModuleCatalog تعیین کرده ایم که ماژولهای پروژه در فولدر Modules موجود در bin اصلی پروژه قرار دارد. اگر به دستورات کپی در Post Build Event قسمت قبل توجه کنید میبینید که دستور ساخت فولدر وجود دارد.
"$(SolutionDir)FirstPrismSample\bin\$(ConfigurationName)\Modules\" /Y
در پایان باید فایل App.xaml را تغییر دهید به گونه ای که متد Run در کلاس Bootstapper ابتدا اجرا شود.
public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var bootstrapper = new Bootstrapper(); bootstrapper.Run(); } }
اجرای پروژه:
بعد از اجرا، با انتخاب ماژول مورد نظر اطلاعات ماژول در Workspace Content Control لود خواهد شد.
ادامه دارد...
var pdfReader = new PdfReader(pdfFile); for (var pageNumber = 1; pageNumber <= pdfReader.NumberOfPages; pageNumber++) { var pdf = new PdfReader(pdfFile); var pg = pdf.GetPageN(pageNumber); // process page }
var pdf = new PdfReader(pdfFile); var pg = pdf.GetPageN(pageNumber); var res = (PdfDictionary)PdfReader.GetPdfObject(pg.Get(PdfName.RESOURCES)); var xobj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT)); if (xobj == null) continue; foreach (var name in xobj.Keys) { var obj = xobj.Get(name); if (obj.IsIndirect()) { // process obj ... } }
var tg = (PdfDictionary)PdfReader.GetPdfObject(obj); var width = tg.Get(PdfName.WIDTH).ToString(); var height = tg.Get(PdfName.HEIGHT).ToString(); ImageRenderInfo imgRi = ImageRenderInfo.CreateForXObject(new Matrix(float.Parse(width), float.Parse(height)), (PRIndirectReference)obj, tg);
private void RenderImage(ImageRenderInfo renderInfo, string imgPath) { var image = renderInfo.GetImage(); using (var dotnetImg = image.GetDrawingImage()) { if (dotnetImg == null) return; using (var ms = new MemoryStream()) { dotnetImg.Save(ms, ImageFormat.Jpeg); var d = new Bitmap(dotnetImg); d.Save(imgPath); } } }
private void ExtractImage(string pdfFile, string imgPath) { var fileCounter = 0; var pdfReader = new PdfReader(pdfFile); for (var pageNumber = 1; pageNumber <= pdfReader.NumberOfPages; pageNumber++) { var pdf = new PdfReader(pdfFile); var pg = pdf.GetPageN(pageNumber); var res = (PdfDictionary)PdfReader.GetPdfObject(pg.Get(PdfName.RESOURCES)); var xobj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT)); if (xobj == null) continue; foreach (var name in xobj.Keys) { var obj = xobj.Get(name); if (obj.IsIndirect()) { var tg = (PdfDictionary)PdfReader.GetPdfObject(obj); var width = tg.Get(PdfName.WIDTH).ToString(); var height = tg.Get(PdfName.HEIGHT).ToString(); var imgRi = ImageRenderInfo.CreateForXObject(new Matrix(float.Parse(width), float.Parse(height)), (PRIndirectReference)obj, tg); fileCounter++; RenderImage(imgRi, imgPath + fileCounter + ".jpeg"); } } } }
var path = @"C:\"; var fileName = "1.pdf"; var outPath = path + fileName + @"_extractedImgs\"; Directory.CreateDirectory(outPath); ExtractImage(path + fileName, outPath);
این افزونه با استفاده از ابزار Visual Studio Tools for Office که به VSTO مشهور شده است، تهیه شد. در بسته به روز رسانی سیستم که در ذیل (معرفی افزونه) نیز معرفی شد نگارش sp1 vsto3.0 آن به صورت خودکار نصب خواهد شد.
برای ایجاد این پروژه در VS.Net 2008 ، تنها کافی است یک پروژه جدید Word add-in را آغاز نمائیم. (شکل زیر)
قبل از ادامه بحث، بهتر است در مورد بانک اطلاعاتی مورد استفاده نیز توضیح داده شود. در اینجا از SQLite استفاده شد. (بسیار سبک، کم حجم و سریع است و اساسا یک کاربر نهایی برای تنظیمات آن نیازی نیست اطلاعاتی داشته باشد). بسته به روز رسانی سیستم (در مطلب قبلی)، این مورد را نیز به صورت خودکار نصب خواهد کرد (در GAC باید نصب شود وگرنه افزونه قادر به یافتن آن نخواهد شد).
برای ایجاد این بانک اطلاعاتی، از افزونه SQLite manager برای فایرفاکس استفاده شد. (این افزونه رایگان شما را از هر ابزار جانبی برای مدیریت یک بانک اطلاعاتی SQLite بینیاز میکند)
برای مثال فایل ErrorsBank.sqlite برنامه افزونه فارسی به پارسی را توسط افزونه SQLite manager فایرفاکس باز کنید (این فایل را در محل نصب افزونه میتوانید پیدا کنید). در اینجا میتوان جداول جدید را ایجاد کرد، کوئریهای دلخواه را اجرا نمود و یا اطلاعات را مرور کرده، حذف یا ویرایش کرد (شکل زیر).
و خوشبختانه این بانک اطلاعاتی و محصور کنندههای آن با اطلاعات یونیکد فارسی هیچ مشکلی ندارند و برای کارهایی با وسعت کم و تعداد رکورد پائین یکی از بهترین انتخابها بهشمار میروند.
نحوه استفاده از SQLite نیز در دات نت بسیار ساده است. اگر با ADO.Net کار کرده باشید، پس از افزودن ارجاعی از اسمبلی System.Data.SQLite.DLL به پروژه و معرفی فضای نام آن به پروژه، تنها کافی است در کدهای قبلی خود برای مثال SqlConnection را به SQLiteConnectionتغییر دهید و امثال آن. یعنی دانش ADO.Net شما در اینجا نیز کاملا قابل استفاده خواهد بود و نیازی نیست مدتی را صرف آشنا شدن با کلاسها و مفاهیم جدید نمائید (البته این تنها زمانی معنا خواهد داشت که به ویزاردها عادت نکرده باشید و کارهای خود را با کد نویسی انجام داده باشید).
تنها یک نکته را باید بهخاطر داشت و آن هم مربوط است به ساز و کار درونی SQLite . هنگام انجام عملیات update یا insert حتما از transaction استفاده کنید تا سرعت کوئریهای شما در SQLite به نحو شگفت انگیزی افزایش یابد. مثالی در این مورد را در فایل chm راهنمای SQLite.NET میتوانید پیدا کنید.
مطلب دیگری که پیش از پرداختن به کد نویسی افزونه باید با آن آشنا شویم، مفهوم smart tags در مجموعه آفیس است که در این پروژه از آن استفاده گردید.
smart tags در مجموعه آفیس برچسبهایی هستند که به صورت خودکار توسط یکی از محصولات آفیس مثلا ورد یا اکسل و امثال آن، پس از تشخیص یک کلمه خاص ایجاد میشوند و میتوان اعمالی را به این برچسب ایجاد شده انتساب داد. برای مثال در اینجا امکان جایگزین کردن کلمه فارسی با معادل پارسی در نظر گرفته شد.
ویدیویی در مورد نحوه ایجاد اسمارت تگها در VS.Net و یا مثالی پیشرفتهتر در مورد تشخیص دمای فارنهایت در یک متن و ایجاد smart tag مخصوص به آن برای تبدیل به سلسیوس. (از regular expressions جهت یافتن یک الگو در متن استفاده شده است)
در این پروژه، حدود 3800 واژه فارسی به یک smart tag انتساب داده میشود (در روال استاندارد ThisAddIn_Startup). سپس در هنگام نمایش آن، معادل پارسی کلمه نیز به منوی باز شده افزوده گشته و در روال رخداد کلیک آن، تعویض کلمه تشخیص داده شده با واژه پیدا شده صورت خواهد گرفت.
در ادامه فرض بر این است که یک پروژه جدید word add-in را در VS.Net ایجاد کردهاید و همچنین ارجاعی را به فایل System.Data.SQLite.DLL افزودهاید.
using System;
using System.Diagnostics;
using Microsoft.Office.Tools.Word;
using Action = Microsoft.Office.Tools.Word.Action;
private SmartTag _st;
private void init()
{
try
{
//Enable Smart Tags in Word
if (!Application.Options.LabelSmartTags)
{
//ممکن است اسمارت تگها در ورد غیرفعال باشند. به این صورت میشود آنها را فعال کرد
Application.Options.LabelSmartTags = true;
}
_st = new SmartTag(@"www.microsoft.com/Demo#FarsiSmartTag", @"فارسی به پارسی");
//دریافت واژههای فارسی از دیتابیس و افزودن خودکار آنها به اسمارت تگها
if (!DBhelper.AddSmartTagItems(_st, "select distinct farsi from tblFarsiToParsi")) return;
Action stActions = new Action("تبدیل");//تعریف یک اکشن جدید
stActions.Click += stActions_Click;//انتساب روالهای رخداد گردان
stActions.BeforeCaptionShow += stActions_BeforeCaptionShow;
_st.Actions = new[] { stActions };
VstoSmartTags.Add(_st);//افزودن اسمارت تگ به مجموعه
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}
private void ThisAddIn_Startup(object sender, EventArgs e)
{
init();
}
static void stActions_BeforeCaptionShow(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
string parsi = DBhelper.FindParsi(e.Text);//معادل پارسی از دیتابیس دریافت میشود
clickedAction.Caption = (parsi == string.Empty ? e.Text : parsi);
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}
static void stActions_Click(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
e.Range.Text = clickedAction.Caption;//جایگزینی متن موجود با عنوانی که پیشتر پارسی شده است
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}
همچنین باید دقت داشت که اگر متغیری در سطح کلاس تعریف نشود به احتمال زیاد تا دقایقی بعد توسط garbage collector به دیار باقی خواهد شتافت (تعریف st_ در اینجا). اینجاست که شاید ساعتها وقت صرف کنید که چرا روالهای رخداد گردان دیگر اجرا نمیشوند. چرا افزونه دیگر کار نمیکند.
همین! کل سورس این add-in منهای بحث دریافت اطلاعات از دیتابیس همین بود! وظیفهی تشخیص کلمات معرفی شده به ms-word بهعهدهی خود آن است و اینکار را نیز بهخوبی انجام میدهد. در گذشتههای نچندان دور ایجاد یک افزونه برای word واقعا مشکل بود که با این روش بسیاری از موانع برطرف شده است.
کلاس DBHelper که کار دریافت اطلاعات واژهها را از دیتابیس SQLite انجام میدهد به شرح زیر است:
using System;
using System.Data.SQLite;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Office.Tools.Word;
namespace Farsi2Parsi
{
class DBhelper
{
#region Methods (2)
// Public Methods (2)
public static bool AddSmartTagItems(SmartTag st, string strSQL)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
bool ret = false;
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();
if (myReader != null)
while (myReader.Read())
{
if (myReader.GetValue(0) != DBNull.Value)
st.Terms.Add(myReader.GetValue(0).ToString());
}
ret = true;
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 7);
}
finally
{
if (myReader != null)
myReader.Close();
if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}
public static string FindParsi(string farsi)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
string ret = string.Empty;
string strSQL = "select parsi from tblFarsiToParsi where farsi='" + farsi.Replace("'", "''") + "'";
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();
if (myReader != null)
{
myReader.Read(); //اولین مورد کافی است
if (myReader.GetValue(0) != DBNull.Value)
ret = myReader.GetValue(0).ToString();
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 8);
}
finally
{
if (myReader != null)
myReader.Close();
if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}
#endregion Methods
}
}
هنگام نصب برنامه، مسیر پوشه نصب در رجیستری ویندوز توسط نصاب نوشته خواهد شد. از همین مورد برای ایجاد رشته اتصالی به دیتابیس استفاده گردید.
class ConStr
{
public static string ConnectionString
{
get
{
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\FarsiToParsi").GetValue("folder") + "\\ErrorsBank.sqlite";
}
}
}
نصاب برنامه با استفاده از NSIS ایجاد شده که در روزی دیگر دربارهی آن توضیح خواهم داد.
اگر قصد داشته باشید از روشهای متداول استفاده کنید، مشاهده ویدیوی زیر توصیه میشود:
http://msdn.microsoft.com/en-us/office/bb851702.aspx
برای توزیع این نوع افزونهها علاوه بر دات نت فریم ورک، به چهار به روز رسانی دیگر نیز نیاز خواهد بود:
به روز رسانی نصاب ویندوز (که احتمالا نصب هست)
WindowsInstaller-KB893803-v2-x86.exe
Microsoft Office System Update: Redistributable Primary Interop Assemblies :
o2007pia.msi
نصب vsto و همچنین sp1 آن
vstor30.exe
vstor30sp1-KB949258-x86.exe
این موارد را من در بسته به روز رسانی سیستم قرار دادهام که به صورت خودکار و یکی پس از دیگری اجرا و نصب خواهند شد.
پس از آن با کلیک بر روی فایلی با پسوند vsto که در پوشه build برنامه موجود است، میتوان افزونه را نصب کرد (click once installation).
سایر اطلاعات در مورد پروژههای VSTO را میتوان از طریق وبلاگ رسمی آنها دنبال کرد:
http://blogs.msdn.com/vsto/
ایدههای دیگری را هم در همین رابطه میتوان پیاده سازی کرد. برای مثال درست کردن یک افزونه برای بررسی آئین نگارش فارسی در متون word. دقیقا با همین روش قابل پیاده سازی است و یا ایجاد غلط یاب بهتری نسبت به آنچه که هم اکنون برای آفیس 2003 توسط مایکروسافت ارائه شده است (این غلط یاب با صفحه کلید استاندارد تایپ ایران همخوانی ندارد، به همین جهت با استقبال نیز مواجه نشد).
http://www.site.com/post/12/slug
سؤال: چگونه میتوان اعراب را از متون فارسی یا عربی حذف کرد؟
متد انجام اینکار را در ذیل مشاهده میکنید:
using System.Globalization; using System.Text; static string RemoveDiacritics(string text) { var normalizedString = text.Normalize(NormalizationForm.FormD); var stringBuilder = new StringBuilder(); foreach (var c in normalizedString) { var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); if (unicodeCategory != UnicodeCategory.NonSpacingMark) { stringBuilder.Append(c); } } return stringBuilder.ToString().Normalize(NormalizationForm.FormC); }
توضیحات
متد Normalize با پارامتر NormalizationForm.FormD، سبب میشود تا کاراکترها به گلیفهای اصلی تشکیل دهندهی آنها تجزیه شوند. به عبارتی، حروف از اعراب جدا خواهند شد. در ادامه این کاراکترها اسکن شده و صرفا مواردی که حروف پایه را تشکیل میدهند، جمع آوری و بازگشت داده میشوند. حالت NormalizationForm.FormC که در انتها بکار گرفته شده، برعکس است.
در یونیکد یک حرف میتواند از یک یا چند code point تشکیل شود. در حالت FormC، هر حرف با اعراب آن یک code point را تشکیل میدهند. در حالت FormD، حرف با اعراب آن دو code point را تشکیل خواهند داد. به همین جهت در ابتدای کار، رشته تبدیل به حالت D شده تا بتوان اعراب آنرا مجزای از حروف پایه حذف کرد.
البته اعراب در اینجا به اعراب عربی ختم نمیشود. یک سری حروف اروپایی مانند "ä" ،"ö" و "ü" را نیز شامل میشود.
ایجاد پروژهی Globe1
پروژهی کتابخانهی Globe1 را بر اساس netcoreapp2.1 به این صورت توسط فایلهای زیر ایجاد میکنیم:
الف) فایل Globe1.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> </Project>
ب) فایل Globe.cs
using System; namespace Space { public class Globe { public string GetColor() => "Blue"; } }
ایجاد پروژهی Globe2
پروژهی کتابخانهی Globe2 را بر اساس netstandard2.0 به این صورت توسط فایلهای زیر ایجاد میکنیم:
الف) فایل Globe2.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> </Project>
ب) فایل Globe.cs
using System; namespace Space { public class Globe { public string GetColor() => "Green"; } }
ایجاد یک پروژهی کنسول استفاده کنندهی از این اسمبلیها
همانطور که ملاحظه میکنید اگر این دو پروژهی class library را کامپایل کنیم، به دو فایل dll یا اسمبلی خواهیم رسید که هر دو دارای فضای نام Space و همچنین کلاس Globe هستند. اما نگارشهای آنها و یا TargetFrameworkهای آنها متفاوت است. اکنون میخواهیم از هر دوی اینها در یک پروژهی Console استفاده کنیم. بنابراین ابتدا این پروژه را با ایجاد فایل csproj آن شروع میکنیم:
الف) فایل Owl.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> <ItemGroup> <Reference Include="Globe1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <HintPath>..\Globe1\bin\$(Configuration)\netcoreapp2.1\Globe1.dll</HintPath> <Aliases>Lib1</Aliases> </Reference> <Reference Include="Globe2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <HintPath>..\Globe2\bin\$(Configuration)\netstandard2.0\Globe2.dll</HintPath> <Aliases>Lib2</Aliases> </Reference> </ItemGroup> </Project>
ب) فایل Program.cs
اکنون قسمت مهم این فایل، همان extern aliasهایی هستند که پیشتر تعریف کردیم:
extern alias Lib1; extern alias Lib2; using System; using SpaceOne = Lib1::Space; using SpaceTwo = Lib2::Space; namespace Owl { class Program { static void Main(string[] args) { var owl = new SuperOwl(); owl.IntegrateGlobe(new SpaceOne.Globe()); owl.IntegrateGlobe(new SpaceTwo.Globe()); Console.WriteLine(owl.GetGLobeColors()); } } public class SuperOwl { private SpaceOne.Globe _firstGlobe; private SpaceTwo.Globe _secondGlobe; public void IntegrateGlobe(SpaceOne.Globe globe) => _firstGlobe = globe; public void IntegrateGlobe(SpaceTwo.Globe globe) => _secondGlobe = globe; public string GetGLobeColors() => $"First: {_firstGlobe.GetColor()}, Second: {_secondGlobe.GetColor()}"; } }
extern alias Lib1; extern alias Lib2;
using SpaceOne = Lib1::Space; using SpaceTwo = Lib2::Space;
اگر این برنامه را اجرا کنیم، چنین خروجی حاصل میشود:
First: Blue, Second: Green
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: alias.zip
39 فونت فارسی استانداردسازی شده
EOT ، WOFF و TTF
EOT فقط در IE پشتیبانی میشود.
WOFF در کروم و فایرفاکس و IE 9 به بعد.
TTF در کروم و فایرفاکس و تا حدودی از IE 9 به بعد.
بنابراین در این بین الحاق EOT و WOFF هر قلم ضروری است.
برای تبدیل فونتهای TTF به سایر فرمتها از سایتهای زیر کمک بگیرید:
Webfont Generator
TTF به EOT
TTF به سایر فرمتها
ضمنا IIS هم باید برای ارائه این فونتها تنظیم شود:
<system.webServer> <staticContent> <mimeMap fileExtension=".svg" mimeType="images/svg+xml" /> <mimeMap fileExtension=".svgz" mimeType="images/svg+xml" /> <remove fileExtension=".eot" /> <mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" /> <mimeMap fileExtension=".otf" mimeType="font/otf" /> <mimeMap fileExtension=".woff" mimeType="font/x-woff" /> </staticContent>