۱۳۹۰/۰۴/۰۸

تبديل تعدادي تصوير به يك فايل PDF


صورت مساله:
تعدادي تصوير داريم، مي‌خواهيم اين‌ها را تبديل به فايل PDF كنيم به اين شرط كه هر تصوير در يك صفحه مجزا قرار داده شود.
در ادامه براي اين منظور از كتابخانه‌ي iTextSharp استفاده خواهيم كرد.

iTextSharp چيست؟
iTextSharp كتابخانه‌ي سورس باز و معروفي جهت توليد فايل‌هاي PDF ، توسط برنامه‌هاي مبتني بر دات نت است. آن را از آدرس زير مي‌توان دريافت كرد:


كتابخانه iTextSharp نيز جزو كتابخانه‌هايي است كه از جاوا به دات نت تبديل شده‌اند. نام كتابخانه اصلي iText است و اگر كمي جستجو كنيد مي‌توانيد كتاب 617 صفحه‌اي iText in Action از انتشارات MANNING را در اين مورد نيز بيابيد. هر چند اين كتاب براي برنامه نويس‌هاي جاوا نوشته شده اما نام كلاس‌ها و متدها در iTextSharp تفاوتي با iText اصلي ندارند و مطالب آن براي برنامه نويس‌‌هاي دات نت هم قابل استفاده است.

مجوز استفاده از iTextSharp كدام است؟
مجوز اين كتابخانه GNU Affero General Public License است. به اين معنا كه شما موظفيد، تغييري در قسمت تهيه كننده خواص فايل PDF توليدي كه به صورت خودكار به نام كتابخانه تنظيم مي‌شود، ندهيد. اگر مي‌خواهيد اين قسمت را تغيير دهيد بايد هزينه كنيد. همچنين با توجه به اينكه اين مجوز، GPL است يعني زمانيكه از آن استفاده كرديد بايد كار خود را به صورت سورس باز ارائه دهيد؛ درست خونديد! بله! مثل مجوز استفاده از نگارش عمومي و رايگان MySQL و اگر نمي‌خواهيد اينكار را انجام دهيد، در اينجا تاكيد شده كه بايد كتابخانه را خريداري كنيد.

نحوه استفاده از كتابخانه iTextSharp
در ابتدا كد تبديل تصاوير به فايل PDF را در ذيل مشاهده خواهيد كرد. فرض بر اين است كه ارجاعي را به اسمبلي itextsharp.dll اضافه كرده‌ايد:
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
public class ImageToPdf
{
public iTextSharp.text.Rectangle PdfPageSize { set; get; }
public ImageFormat ImageCompressionFormat { set; get; }
public bool FitImagesToPage { set; get; }

public void ExportToPdf(IList<string> imageFilesPath, string outPdfPath)
{
using (var pdfDoc = new Document(PdfPageSize))
{
PdfWriter.GetInstance(pdfDoc, new FileStream(outPdfPath, FileMode.Create));
pdfDoc.Open();

foreach (var file in imageFilesPath)
{
var pngImg = iTextSharp.text.Image.GetInstance(file);

if (FitImagesToPage)
{
pngImg.ScaleAbsolute(pdfDoc.PageSize.Width, pdfDoc.PageSize.Height);
}
pngImg.SetAbsolutePosition(0, 0);

//add to page
pdfDoc.Add(pngImg);
//start a new page
pdfDoc.NewPage();
}
}
}
}
}
توضيحات:
استفاده از كتابخانه‌ي iTextSharp هميشه شامل 5 مرحله است. ابتدا شيء Document ايجاد مي‌شود. سپس وهله‌اي از PdfWriter ساخته شده و Document جهت نوشتن در آن گشوده خواهد شد. در طي يك سري مرحله محتويات مورد نظر به Document اضافه شده و نهايتا اين شيء بسته خواهد شد. البته در اينجا چون كلاس Document اينترفيس IDisposable را پياده سازي كرده، بهترين روش استفاده از آن بكارگيري واژه كليدي using جهت مديريت منابع آن است. به اين ترتيب كامپايلر به صورت خودكار قطعه try/finally مرتبط را جهت پاكسازي منابع، تشكيل خواهد داد.
اندازه صفحات توسط سازنده‌ي شيء Document مشخص خواهند شد. اين شيء از نوع iTextSharp.text.Rectangle است؛ اما مقدار دهي آن توسط كلاس iTextSharp.text.PageSize صورت مي‌گيرد كه انواع و اقسام اندازه صفحات استاندارد در آن تعريف شده‌اند.
متد iTextSharp.text.Image.GetInstance كه در اين مثال جهت دريافت اطلاعات تصاوير مورد استفاده قرار گرفت، 15 overload دارد كه از آدرس مستقيم يك فايل تا استريم مربوطه تا Uri يك آدرس وب را نيز مي‌پذيرد و از اين لحاظ بسيار غني است.

مثالي در مورد نحوه استفاده از كلاس فوق:
using System.Collections.Generic;
using System.Drawing.Imaging;

namespace iTextSharpTests
{
class Program
{
static void Main(string[] args)
{
new ImageToPdf
{
FitImagesToPage = true,
ImageCompressionFormat = ImageFormat.Jpeg,
PdfPageSize = iTextSharp.text.PageSize.A4
}.ExportToPdf(
imageFilesPath: new List<string>
{
@"D:\3.jpg",
@"D:\4.jpg"
},
outPdfPath: @"D:\tst.pdf"
);
}
}
}

۱۳۹۰/۰۴/۰۷

رديابي واژه‌هاي كليدي در سايت‌هاي اجتماعي


فرض كنيد مي‌خواهيد واژه‌ KeyWord را در يك سري سايت اجتماعي از طريق Google reader تحت نظر داشته باشد كه كلا اوضاع از چه قرار است و علاقمند‌ي‌هاي عموم در اين زمينه چيست؟ آخرين مطالب به اشتراك گذاشته شده كدام‌ها هستند و در اين زمينه خاص، دنيا دست كيست؟

فيد تحت نظر قرار دادن يك واژه كليدي در
  • كليه وبلاگ‌هاي سايت wordpress.com :
http://en.wordpress.com/tag/KeyWord/feed/
  • سايت twitter.com: (پيدا كردن فيد اين يكي آنچنان واضح نيست چون به نظر اخيرا توئيتر فيدها رو حذف يا مخفي كرده)
http://search.twitter.com/search.atom?q=KeyWord

  • سايت delicious.com :
http://feeds.delicious.com/v2/rss/tag/KeyWord?count=15

  • سايت friendfeed.com :
http://friendfeed.com/search?q=KeyWord&format=atom

  • سايت stackoverflow.com :
http://stackoverflow.com/feeds/tag/KeyWord


شما هم اگر مورد خاصي را مد نظر داريد كه در بالا ذكر نشده، لطفا مطرح كنيد.

۱۳۹۰/۰۴/۰۴

نگاشت IDictionary در Fluent NHibernate


نگاشت خودكار مجموعه‌ها در Fluent NHibernate ساده است و نياز به تنظيم خاصي ندارد. براي مثال IList به صورت خودكار به Bag ترجمه مي‌شود و الي آخر.
البته شايد سؤال بپرسيد كه اين Bag از كجا آمده؟ كلا 6 نوع مجموعه در NHibernate پشتيباني مي‌شوند كه شامل Array، Primitive-Array ، Bag ، Set ، List و Map هستند؛ اين‌ اسامي هم جهت حفظ سازگاري با جاوا تغيير نكرده‌اند و گرنه معادل‌هاي آن‌ها در دات نت به اين شرح هستند:
Bag=IList
Set=Iesi.Collections.ISet
List=IList
Map=IDictionary

البته در دات نت 4 ، ISet هم به صورت توكار اضافه شده، اما NHibernate از مدت‌ها قبل آن‌را از كتابخانه‌ي Iesi.Collections به عاريت گرفته است. مهم‌ترين تفاوت‌هاي اين مجموعه‌ها هم در پذيرفتن يا عدم پذيرش اعضاي تكراري است. Set و Map اعضاي تكراري نمي‌پذيرند.
در ادامه مي‌خواهيم طرز كار با Map يا همان IDictionary دات نت را بررسي كنيم:

الف) حالتي كه نوع كليد و مقدار (در يك عضو Dictionary تعريف شده)، Entity نيستند
using System.Collections.Generic;

namespace Test1.Model12
{
public class User
{
public virtual int Id { set; get; }
public virtual string Name { get; set; }
public virtual IDictionary<string, string> Preferences { get; set; }
}
}

نحوه تعريف نگاشت كه مبتني است بر مشخص سازي تعاريف كليد و مقدار آن جهت تشكيل يك Map يا همان Dictionary :
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model12
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany(x => x.Preferences)
.AsMap<string>("FieldKey")
.Element("FieldValue", x => x.Type<string>().Length(500));
}
}
}

خروجي SQL متناظر:
create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

create table Preferences (
User_id INT not null,
FieldValue NVARCHAR(500) null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)

alter table Preferences
add constraint FKD6CB18523B1FD789
foreign key (User_id)
references "User"

ب) حالتي كه مقدار، Entity است
using System.Collections.Generic;

namespace Test1.Model13
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<string, Property> Properties { get; set; }
}

public class Property
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
public virtual User User { get; set; }
}
}

نحوه تعريف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model13
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<Property>(x => x.Properties)
.AsMap<string>("FieldKey")
.Component(x => x.Map(c => c.Id));
}
}
}
خروجي SQL متناظر:
create table "Property" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
Value NVARCHAR(255) null,
User_id INT null,
primary key (Id)
)

create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

create table Properties (
User_id INT not null,
Id INT null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)

alter table "Property"
add constraint FKF9F4D85A3B1FD7A2
foreign key (User_id)
references "User"

alter table Properties
add constraint FK63646D853B1FD7A2
foreign key (User_id)
references "User"

ج) حالتي كه كليد، Entity است
using System;
using System.Collections.Generic;

namespace Test1.Model14
{
public class FormData
{
public virtual int Id { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual IDictionary<FormField, string> FormPropertyValues { get; set; }
}

public class FormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
}

نحوه تعريف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model14
{
public class FormDataMapping : IAutoMappingOverride<FormData>
{
public void Override(AutoMapping<FormData> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<FormField>(x => x.FormPropertyValues)
.AsEntityMap("FieldId")
.Element("FieldValue", x => x.Type<string>().Length(500))
.Cascade.All();
}
}
}
خروجي SQL متناظر:
create table "FormData" (
Id INT IDENTITY NOT NULL,
DateTime DATETIME null,
primary key (Id)
)

create table FormPropertyValues (
FormData_id INT not null,
FieldValue NVARCHAR(500) null,
FieldId INT not null,
primary key (FormData_id, FieldId)
)

create table "FormField" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

alter table FormPropertyValues
add constraint FKB807B9C090849E
foreign key (FormData_id)
references "FormData"

alter table FormPropertyValues
add constraint FKB807B97165898A
foreign key (FieldId)
references "FormField"

يك مثال عملي:
امكانات فوق جهت طراحي قسمت ثبت اطلاعات يك برنامه «فرم ساز» مبتني بر Key-Value بسيار مناسب هستند؛ براي مثال:
برنامه‌اي را در نظر بگيريد كه مي‌تواند تعدادي خدمات داشته باشد كه توسط مدير برنامه قابل اضافه شدن است؛ براي نمونه خدمات درخواست نصب نرم افزار، خدمات درخواست تعويض كارت پرسنلي، خدمات درخواست مساعده، خدمات ... :
public class Service
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ServiceFormField> Fields { get; set; }
public virtual IList<ServiceFormData> Forms { get; set; }
}

براي هر خدمات بايد بتوان يك فرم طراحي كرد. هر فرم هم از يك سري فيلد خاص آن خدمات تشكيل شده است. براي مثال:
public class ServiceFormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual bool IsRequired { get; set; }
public virtual Service Service { get; set; }
}

در اينجا نيازي نيست به ازاي هر فيلد جديد واقعا يك فيلد متناظر به ديتابيس اضافه شود و ساختار آن تغيير كند (برخلاف حالت dynamic components كه پيشتر در مورد آن بحث شد).
اكنون با داشتن يك خدمات و فيلدهاي پوياي آن كه توسط مديربرنامه تعريف شده‌اند، مي‌توان اطلاعات وارد كرد. مهم‌ترين نكته‌ي آن هم IDictionary تعريف شده است كه حاوي ليستي از فيلدها به همراه مقادير وارد شده توسط كاربر خواهد بود:
public class ServiceFormData
{
public virtual int Id { get; set; }
public virtual IDictionary<ServiceFormField, string> FormPropertyValues { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual Service Service { get; set; }
}

در مورد نحوه نگاشت آن هم در حالت «ج» فوق توضيح داده شد.

۱۳۹۰/۰۴/۰۲

بررسي مقدار دهي اوليه متغيرها در T-SQL


يكي از موارد مشكل ساز حين استفاده از T-SQL ، مقدار دهي اوليه متغيرها به نال است و اگر اسكريپت تهيه شده كمي طولاني باشد، خطايابي مشكلات مرتبط با آن بسيار مشكل مي‌شود. براي مثال:
Declare
@x int,
@y int

Set @x = 1
If (@x + @y = 1)
BEGIN
print 'yes!'
End

Set @y = (select sum(id) from Account)
If @x + @y = 1
BEGIN
print 'yes!'
End

كد فوق بدون هيچگونه خطايي اجرا مي‌شود و هيچ وقت هم yes را چاپ نمي‌كند. مشكل هم همينجا است. خطايابي قسمت دوم اين اسكريپت كمي مشكل‌تر از حالت قبل است. چون در اينجا به نظر متغير y صريحا مقدار دهي شده است؛ اما در عمل ممكن است براي مثال به دليل عدم وجود ركوردي در جدول Account، باز هم null به آن نسبت داده شود.

بنابراين سؤال اين است كه چگونه اين نوع مشكلات را در يك پروژه با تعداد زيادي رويه ذخيره شده، تابع و غيره مي‌توان تشخيص داد؟
پاسخ:
در اين مورد قبلا مطلبي در اين سايت منتشر شده [+] (البته اگر از نگارش كامل VS 2010 استفاده مي‌كنيد نيازي به نصب چيزي نخواهيد داشت) و نكته‌ي آن بررسي SR0007 است.



۱۳۹۰/۰۳/۳۰

استفاده از فيلدهاي XML در NHibernate


در مورد طراحي يك برنامه "فرم ساز" در مطلب قبلي بحث شد ... حدودا سه سال قبل اينكار را براي شركتي انجام دادم. يك برنامه درخواست خدمات نوشته شده با ASP.NET كه مديران برنامه مي‌توانستند براي آن فرم طراحي كنند؛ فرم درخواست پرينت، درخواست نصب نرم افزار، درخواست وام، درخواست پيك، درخواست آژانس و ... فرم‌هايي كه تمامي نداشتند! آن زمان براي حل اين مساله از فيلدهاي XML استفاده كردم.
فيلدهاي XML قابليت نه چندان جديدي هستند كه از SQL Server 2005 به بعد اضافه شده‌اند. مهم‌ترين مزيت آن‌ها‌ هم امكان ذخيره سازي اطلاعات هر نوع شيء‌ايي به عنوان يك فيلد XML است. يعني همان زيرساختي كه براي ايجاد يك برنامه فرم ساز نياز است. ذخيره سازي آن هم آداب خاصي را طلب نمي‌كند. به ازاي هر فيلد مورد نظر كاربر، يك نود جديد به صورت رشته معمولي بايد اضافه شود و نهايتا رشته توليدي بايد ذخيره گردد. از ديد ما يك رشته‌ است، از ديد SQL Server يك نوع XML واقعي؛ به همراه اين مزيت مهم كه به سادگي مي‌توان با T-SQL/XQuery/XPath از جزئيات اطلاعات اين نوع فيلدها كوئري گرفت و سرعت كار هم واقعا بالا است؛ به علاوه بر خلاف مطلب قبلي در مورد dynamic components ، اينبار نيازي نيست تا به ازاي هر يك فيلد درخواستي كاربر، واقعا يك فيلد جديد را به جدول خاصي اضافه كرد. داخل اين فيلد XML هر نوع ساختار دلخواهي را مي‌توان ذخيره كرد. به عبارتي به كمك فيلدهايي از نوع XML مي‌توان داخل يك سيستم بانك اطلاعاتي رابطه‌اي، schema-less كار كرد (un-typed XML) و همچنين از اين اطلاعات ويژه، كوئري‌هاي پيچيده هم گرفت.
تا جايي كه اطلاع دارم، چند شركت ديگر هم در ايران دقيقا از همين ايده فيلدهاي XML براي ساخت برنامه فرم ساز استفاده كرده‌اند ...؛ البته مطلب جديدي هم نيست؛ برنامه‌هاي فرم ساز اوراكل و IBM هم سال‌ها است كه از XML براي همين منظور استفاده مي‌كنند. مايكروسافت هم به همين دليل (شايد بتوان گفت مهم‌ترين دليل وجودي فيلدهاي XML در SQL Server)، پشتيباني توكاري از XML به عمل آورده‌ است.
يا روش ديگري را كه براي طراحي سيستم‌هاي فرم ساز پيشنهاد مي‌كنند استفاده از بانك‌هاي اطلاعاتي مبتني بر key-value‌ مانند Redis يا RavenDb است؛ يا استفاده از بانك‌هاي اطلاعاتي schema-less واقعي مانند CouchDb.


خوب ... اكنون سؤال اين است كه NHibernate براي كار با فيلدهاي XML چه تمهيداتي را درنظر گرفته است؟
براي اين منظور خاصيتي را كه قرار است به يك فيلد از نوع XML نگاشت شود، با نوع XDocument مشخص خواهيم ساخت:
using System.Xml.Linq;

namespace TestModel
{
public class DynamicTable
{
public virtual int Id { get; set; }
public virtual XDocument Document { get; set; }
}
}

سپس بايد جهت معرفي اين نوع ويژه، به صورت صريح از XDocType استفاده كرد؛ يعني نكته‌ي اصلي، استفاده از CustomType مرتبط است:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
using NHibernate.Type;

namespace TestModel
{
public class DynamicTableMapping : IAutoMappingOverride<DynamicTable>
{
public void Override(AutoMapping<DynamicTable> mapping)
{
mapping.Id(x => x.Id);
mapping.Map(x => x.Document).CustomType<XDocType>();
}
}
}

البته لازم به ذكر است كه دو نوع NHibernate.Type.XDocType و NHibernate.Type.XmlDocType براي كار با فيلد‌هاي XML در NHibernate وجود دارند. XDocType براي كار با نوع System.Xml.Linq.XDocument طراحي شده است و XmlDocType مخصوص نگاشت نوع System.Xml.XmlDocument است.

اكنون اگر به كمك كلاس SchemaExport ، اسكريپت توليد جدول متناظر با اطلاعات فوق را ايجاد كنيم به حاصل زير خواهيم رسيد:
   if exists (select * from dbo.sysobjects
where id = object_id(N'[DynamicTable]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [DynamicTable]

create table [DynamicTable] (
Id INT IDENTITY NOT NULL,
Document XML null,
primary key (Id)
)

يك سري اعمال متداول ذخيره سازي اطلاعات و تهيه كوئري نيز در ادامه ذكر شده‌اند:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicTable
{
Document = System.Xml.Linq.XDocument.Parse(
@"<Doc><Node1>Text1</Node1><Node2>Text2</Node2></Doc>"
)
};
savedId = session.Save(obj);
tx.Commit();
}
}

//simple query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicTable>(savedId);
if (entity != null)
{
Console.WriteLine(entity.Document.Root.ToString());
}

tx.Commit();
}
}

//advanced query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var list = session.CreateSQLQuery("select [Document].value('(//Doc/Node1)[1]','nvarchar(255)') from [DynamicTable] where id=:p0")
.SetParameter("p0", savedId)
.List();

if (list != null)
{
Console.WriteLine(list[0]);
}

tx.Commit();
}
}

و در پايان بديهي است كه جهت كار با امكانات پيشرفته‌تر موجود در SQL Server در مورد فيلد‌هاي XML ( براي نمونه: + و +) بايد مثلا رويه ذخيره شده تهيه كرد (يا مستقيما از متد CreateSQLQuery همانند مثال فوق كمك گرفت) و آن‌را در NHibernate مورد استفاده قرار داد. البته به اين صورت كار شما محدود به SQL Server خواهد شد و بايد در نظر داشت كه در كل تعداد كمي بانك اطلاعاتي وجود دارند كه نوع‌هاي XML را به صورت توكار پشتيباني مي‌كنند.

۱۳۹۰/۰۳/۲۷

فيلدهاي پويا در NHibernate


يكي از قابليت‌هاي جالب NHibernate امكان تعريف فيلدها به صورت پويا هستند. به اين معنا كه زيرساخت طراحي يك برنامه "فرم ساز" هم اكنون در اختيار شما است! سيستمي كه امكان افزودن فيلدهاي سفارشي را دارا است كه توسط برنامه نويس در زمان طراحي اوليه آن ايجاد نشده‌اند. در ادامه نحوه‌ي تعريف و استفاده از اين قابليت را توسط Fluent NHibernate بررسي خواهيم كرد.

در اينجا كلاسي كه قرار است توانايي افزودن فيلدهاي سفارشي را داشته باشد به صورت زير تعريف مي‌شود:
using System.Collections;

namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}

Attributes در عمل همان فيلدهاي سفارشي مورد نظر خواهند بود. جهت معرفي صحيح اين قابليت نياز است تا نگاشت آن‌را از نوع dynamic component تعريف كنيم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
و مهم‌ترين نكته‌ي اين بحث هم همين نگاشت فوق است.
ابتدا از IgnoreProperty جهت نديد گرفتن Attributes استفاده كرديم. زيرا درغيراينصورت در حالت Auto mapping ، يك رابطه چند به يك به علت وجود IDictionary به صورت خودكار ايجاد خواهد شد كه نيازي به آن نيست (يافتن اين نكته نصف روز كار برد ....! چون مرتبا خطاي An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر مي‌شد و مشخص نبود كه مشكل از كجاست).
سپس Attributes به عنوان يك DynamicComponent معرفي شده است. در اينجا چهار فيلد سفارشي را اضافه كرده‌ايم. به اين معنا كه اگر نياز باشد تا فيلد سفارشي ديگري به سيستم اضافه شود بايد يكبار Session factory ساخته شود و SchemaUpdate فراخواني گردد و خوشبختانه با وجود Fluent NHibernate ، تمام اين تغييرات در كدهاي برنامه قابل انجام است (بدون نياز به سر و كار داشتن با فايل‌هاي XML نگاشت‌ها و ويرايش دستي آن‌ها). از تغيير نام جدول كه براي مثال در اينجا tblDynamicEntity در نظر گرفته شده تا افزودن فيلدهاي ديگر در قسمت DynamicComponent فوق.
همچنين باتوجه به اينكه اين نوع تغييرات (ساخت دوبار سشن فكتوري) مواردي نيستند كه قرار باشد هر ساعت انجام شوند، بنابراين سربار آنچناني را به سيستم تحميل نمي‌كنند.
   drop table tblDynamicEntity

create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)

اگر با SchemaExport، اسكريپت خروجي معادل با نگاشت فوق را تهيه كنيم به جدول فوق خواهيم رسيد. نوع و طول اين فيلدهاي سفارشي بر اساس نوعي كه براي اشياء ديكشنري مشخص مي‌كنيد، تعيين خواهند شد.

چند مثال جهت كار با اين فيلدهاي سفارشي يا پويا :

نحوه‌ي افزودن ركوردهاي جديد بر اساس خاصيت‌هاي سفارشي:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;

savedId = session.Save(obj);
tx.Commit();
}
}

با خروجي
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]

نحوه‌ي كوئري گرفتن از اين اطلاعات (فعلا پايدارترين روشي را كه براي آن يافته‌ام استفاده از HQL مي‌باشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();

if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
با خروجي:
    select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]

استفاده از HQL هم يك مزيت مهم دارد: چون به صورت رشته قابل تعريف است، به سادگي مي‌توان آن‌را داخل ديتابيس ذخيره كرد. براي مثال يك سيستم گزارش ساز پويا هم در اين كنار طراحي كرد ....

نحوه‌ي به روز رساني و حذف اطلاعات بر اساس فيلدهاي پويا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}

//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}

۱۳۹۰/۰۳/۲۶

مشكلات نصب به روز رساني‌هاي اخير


مشكل نصب به روز رساني‌هاي دات نت فريم ورك
اگر احتمالا آپديت‌هاي اخير دات نت فريم ورك را نتوانسته‌ايد نصب كنيد و هربار پس از ري استارت كامپيوتر مجددا سر و كله برنامه نصاب پيدا مي‌شود و نهايتا پيغام failed را دريافت مي‌كنيد، متاسفانه فقط يك راه حل دارد. حذف موارد موجود و نصب مجدد نگارش جديد آن‌ها. براي حذف كليه نگارش‌هاي دات نت فريم ورك از برنامه زير استفاده كنيد:


سپس بهتر است از نصاب نگارش وب دات نت فريم ورك استفاده كنيد. از اين لحاظ كه تمام به روز رساني‌ها را به همراه دارد و متفاوت است با نسخه‌ي آفلاين آن.


مشكل نصب به روز رساني‌هاي سيلورلايت
اگر هنگام نصب به روز رساني جديد سيلورلايت هر بار پيغام silverlight.msi File Missing را دريافت مي‌كنيد، مسير زير را از رجيستري ويندوز حذف كنيد. سپس به روز رساني سيلورلايت بدون مشكل نصب خواهد شد:
HKEY_CLASSES_ROOT / installer / products / D7314F9862C648A4DB8BE2A5B47BE100


پ.ن.
من هر دو مشكل را با ويندوز سرور 2003 داشتم.

۱۳۹۰/۰۳/۲۵

نكات امنيتي OWASP مخصوص برنامه نويس‌هاي دات نت

OWSAP ارگاني است غيرانتفاعي كه هدف آن ترويج طراحي برنامه‌هاي امن وب است. در اين راه هم مطالب آموزشي بسيار ارزشمندي را منتشر كرده است [+]. در لينك‌هاي زير اين مطالب از ديدگاه برنامه نويس‌هاي دات نت مورد بررسي قرار گرفته‌اند. هر چند مطابق آخرين گزارش WhiteHat كه اخيرا منتشر شده [+]، تعداد Exploits مربوط به ASP.NET در مقايسه با PHP و جاوا بسيار كمتر بوده اما نياز است تا با مشكلات عمومي موجود و راه‌حل‌هاي مرتبط بيشتر آشنا شد:



۱۳۹۰/۰۳/۲۲

سرعت ايجاد اشياء CLR


به نظر شما چه تعداد شيء CLR را مي‌توان در يك ثانيه ايجاد كرد؟
برنامه كنسول زير دو نسخه معمولي و نسخه پردازش موازي يك آزمايش ساده را براي اندازه گيري اين مطلب ارائه مي‌دهد:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ObjectInitSpeedTest
{
class Program
{
//Note: don't forget to build it in Release mode.
static void Main()
{
normalSpeedTest();
parallelSpeedTest();

Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Press a key ...");
Console.ReadKey();
}

private static void parallelSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("parallelSpeedTest");

long totalObjectsCreated = 0;
long totalElapsedTime = 0;

var tasks = new List<Task>();
var processorCount = Environment.ProcessorCount;

Console.WriteLine("Running on {0} cores", processorCount);

for (var t = 0; t < processorCount; t++)
{
tasks.Add(Task.Factory.StartNew(
() =>
{
const int reps = 1000000000;
var sp = Stopwatch.StartNew();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Interlocked.Add(ref totalObjectsCreated, reps);
Interlocked.Add(ref totalElapsedTime, sp.ElapsedMilliseconds);
}
));
}

// let's complete all the tasks
Task.WaitAll(tasks.ToArray());

Console.WriteLine("Created {0:N} objects in 1 sec\n", (totalObjectsCreated / (totalElapsedTime / processorCount)) * 1000);
}

private static void normalSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("normalSpeedTest");

const int reps = 1000000000;
var sp = Stopwatch.StartNew();
sp.Start();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Console.WriteLine("Created {0:N} objects in 1 sec\n", (reps / sp.ElapsedMilliseconds) * 1000);
}
}
}

هنگام اجراي برنامه فراموش نكنيد كه حالت Build را بر روي Release قرار دهيد.



۱۳۹۰/۰۳/۲۰

سشن صفر


يكي از تفاوت‌هاي مهم امنيتي ويندوزهاي جديد (از سرويس پك 2 ويندوز 2003 به بعد)، اجراي سرويس‌هاي ويندوز در جلسه‌اي كاري (سشني) جدا از جسله كاري برنامه‌هاي عادي است كه توسط كاربران وارد شده به سيستم اجرا مي‌شوند. در اين ويندوزها، تنها سرويس‌ها هستند كه در سشن صفر اجرا مي‌شوند و مابقي برنامه‌هاي ساير كاربران در سشن‌هاي ديگر.



اين برخلاف چيزي است كه براي مثال در ويندوز XP وجود دارد؛ يعني اجراي دوستانه‌ي تمام برنامه‌ها در يك سشن (چون كاربر وارد شده به سيستم نيز در سشن صفر قرار مي‌گيرد).



در اين حالت برنامه‌هاي عادي مي‌توانند سرويس‌هاي ويندوز را به راحتي مورد حمله قرار دهند. اطلاعات بيشتر:


مهم‌ترين معناي اجراي ايزوله سرويس‌ها در سشن صفر، به خاطره سپرده شدن سرويس‌هايي است كه امكان تعامل با كاربر را داشتند؛ براي مثال سرويس‌هايي كه يك رابط كاربري را نمايش مي‌دادند (به كمك همان تيك معروف Allow Service to Interact with Desktop در صفحه تنظيمات يك سرويس). زيرا اكنون اولين كاربر وارد شده به سيستم، در سشن يك قرار مي‌گيرد (و نه همانند قبل در سشن صفر). به اين صورت ديگر به رابط كاربري نمايش داده شده در سشن صفر دسترسي نداشته و چيزي را مشاهده نخواهد كرد.




البته شايد سؤال بپرسيد كه چه سرويس‌هايي نياز به نمايش رابط كاربري دارند؟ چون عموما سرويسي كه صحيح طراحي شده باشد نياز به تعامل مستقيم با كاربر را از طريق رابط كاربري ندارد.
و پاسخ اين است كه خيلي از سرويس‌ها! در بسياري از برنامه‌هاي متداول، امكان اجراي به صورت سرويس ويندوز NT پيش بيني نشده است. اگر نياز باشد تا اين نوع برنامه‌ها را به صورت سرويس، براي مثال در يك ويندوز سرور اجرا كرد (تا هميشه در حال اجرا باشند و همچنين با ري استارت شدن سيستم نيز مجددا بدون دخالت كاربر شروع به كار كنند)، در اين حالت نياز خواهد بود تا رابط كاربري آن‌ها نيز نمايش داده شوند.

سؤال: در ويندوزهاي جديد براي تعامل با سشن صفر چه بايد كرد؟
در ويندوز 2003 (به همراه تمام به روز رساني‌ها) براي اتصال به سشن صفر مي‌توان دستور زير را در خط فرمان صادر كرد:
Mstsc /console

به اين ترتيب امكان اتصال به سشن كنسول يا همان سشن صفر ميسر مي‌شود.
اين پارامتر در ويندوز سرور 2008 نديد گرفته خواهد شد زيرا در اين ويندوز امكان نصب يك سرويس در سشن‌هايي بجز سشن صفر نيز پيش بيني شده است؛ هر چند پيش فرض نصب همان سشن صفر است. اما به جهت سازگاري با نگارش‌هاي قبل اين‌بار بجاي پارامتر console از پارامتر admin استفاده كنيد.

با استفاده از برنامه نويسي چطور؟
روش كار در اين مقاله توضيح داده شده است اما سطر اول آن به اين نكته تاكيد دارد كه ... اين‌كار را نكنيد! سرويسي كه درست طراحي شده باشد نيازي به رابط كاربري تعاملي عموما جهت اعمال تنظيمات آن، ندارد.

۱۳۹۰/۰۳/۱۸

200 بلاگ برتر Agile دنيا


ليستي در سايت زير در مورد 200 بلاگ برتر Agile‌ دنيا منتشر شده (طبقه بندي شده بر اساس تعداد خواننده و رتبه‌ بندي‌هاي متداول):


كه طبق معمول يك چيزي كم دارد و آن هم فايل OPML‌ مربوطه است كه آن‌را از اينجا مي‌توانيد دريافت كنيد.

۱۳۹۰/۰۳/۱۷

به روز رساني جديد دات نت 4


به روز رساني جديدي جهت رفع 35 مشكل در دات نت 4 ، روز قبل ارائه شده است كه از آدرس ذيل قابل دريافت مي‌باشد. اين به روز رساني شامل كليه موارد برطرف شده در به روز رساني‌هاي قبلي نيز مي‌باشد:

۱۳۹۰/۰۳/۱۵

مكان دريافت درايور‌هاي جديد SQLite مخصوص دات نت


كتابخانه‌ي System.Data.SQLite مدت مديدي توسط Robert Simpson در آدرس http://sourceforge.net/projects/sqlite-dotnet2 به روز مي‌شد و قابل دسترسي بود. اما اين پروژه از سال 2010 متوقف شده و آخرين نسخه‌ي موجود آن 1.0.66.0 است. به همين جهت fork جديدي از اين پروژه در آدرس ذيل (كه جزو دومين SQLite نيز مي‌باشد) جهت به روز نگه داشتن آن تشكيل شده است:

در زمان نگارش اين مطلب، نگارش 1.0.73.0 اين پروژه كه در بردارنده‌ي SQLite 3.7.6.3 است، از آدرس فوق قابل دريافت مي‌باشد.

۱۳۹۰/۰۳/۱۳

فلسفه وجودي Path.Combine


عموما اكثر كدهاي موجود از روش زير براي ساخت يك مسير استفاده مي‌كنند:
string path = somePath + "\\" + filename;

اما اگر همين برنامه تحت Mono در لينوكس اجرا شود به مشكل بر مي‌خورد زيرا در لينوكس مسيرها اين‌بار به صورت زير هستند:
/somepath/filename

به همين جهت توصيه شده است براي ساخت مسيرها در برنامه‌ي خود، از متد Path.Combine موجود در فضاي نام System.IO استفاده كنيد زيرا اين متد از مقادير Path.DirectorySeperatorChar و Path.VolumeSeparatorChar جهت تهيه مسير نهايي استفاده مي‌كند. اين مقادير در ويندوز (\) و لينوكس (/) متفاوت بوده و به صورت خودكار در زمان اجرا توسط فريم ورك مورد استفاده مديريت خواهند شد.
همچنين مزيت ديگر استفاده از Path.Combine ، تعيين اعتبار ورودي است؛ به اين معنا كه اگر از كاراكترهاي غيرمجاز استفاده شود، يك استثناء صادر خواهد شد.

يك مورد ديگر هم شايد بد نباشد همينجا اضافه شود و آن هم فلسفه وجودي Environment.NewLine است. مطابق معمول رسم بر اين است كه سطر جديد با n\ در انتهاي يك رشته مشخص شود اما اين هميشه صحيح نيست و در پلتفرم‌هاي مختلف متفاوت است. Environment.NewLine در ويندوز مساوي r\n\ است و در سيستم‌هاي مبتني بر Unix مساوي n\ خواهد بود. به همين جهت بهتر است از اين پس بجاي n\ از Environment.NewLine جهت مشخص سازي سطر جديد استفاده كنيد.

۱۳۹۰/۰۳/۱۲

مجوز WTFPL


در بين مجوز‌هاي سورس باز، يكي از اون‌ها كه اتفاقا مورد پذيرش FSF هم هست، عنوان جالبي داره كه ترجمه‌اش به فارسي مي‌شود: "برو هر غلطي كه دلت مي‌خواد باهاش بكن!" يا WTFPL = Do What The F.u.c.k You Want To Public License
نگارش يك اين مجوز توسط Banlu Kemiyatorn نويسنده برنامه Window maker در سال 2000 ارائه شده و در سال 2007 توسط مدير پروژه تيم Debian نگارش دوم آن ارائه گرديده است!
اين مجوز به شما اجازه هر نوع تغيير يا هر روش توزيعي را در مورد برنامه‌ي مورد نظر مي‌دهد.
ترجمه اين مجوز هم به زبان فارسي به صورت زير است:

"
مجوز برو هر غلطي كه دلت مي‌خواد بكن!
نگارش 2، دسامبر 2004

هر كسي مجاز است اين مستند را كپي يا توزيع كند با اين شرط كه اگر تغييري در اصل آن داده شد، نامش را تغيير دهد.
شروط اصلي اين مجوز به شرح ذيل اعلام مي‌گردد:
0- فقط برو هر غلطي كه دلت مي‌خواد باهاش بكن

"

البته شايد اين سؤال پيش بياد كه اين موارد به چه دليلي اضافه شده؟ احتمالا شايد شنيده باشيد كه عده‌اي GPL‌ رو يك نوع سرطان مي‌دونند؛ از اين لحاظ كه اگر طرف اون رفتيد بايد كل برنامه خودتون رو سورس باز ارائه بديد. به همين جهت كساني كه كار تجاري انجام مي‌دهند از طرف سورس‌هاي پروژه‌هاي مبتني بر GPL رد هم نمي‌شوند. در مقابل آن مجوزهايي مانند BSD يا MIT ملاحظات GPL را ندارند (+). در كل GPL تا به امروز لينوكس را زنده نگه داشته است.

درك نمودار


سؤال: از نمودار زير چه چيزي را برداشت مي‌كنيد؟!



منحني كه بالا رفته يعني چي؟ يعني بده، خوبه؟!
منحني‌هاي پايين‌تر يعني چي؟ اين‌ها بهترند يا بالايي‌هاي آن‌ها؟
با بالا رفتن حجم فايل‌ها، كدام يك كارآيي بهتري دارد؟ بالايي‌ها يا پاييني‌ها؟


ماخذ اين نمودار:(+). البته قبل از مراجعه به ماخذ و مطالعه آن‌، سعي كنيد به سؤالات فوق پاسخ دهيد.