مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت دوم
در ادامه قسمت قبلی به برسی ویژگی‌های پیشرفته‌ی AutoMapper می‌پردازیم...


Custom type converters
همانطور که از اسمش مشخصه، زمانی کاربرد داره که نوع عضو یا اعضای یک شی در مبداء، با معادلشون در مقصد یکی نیستند. مثلا فرض کنید نوع Bool در مبداء رو می‌خواهیم به نوع String در مقصد نگاشت کنیم؛ همون Yes و No  معروف بجای True یا False .
کلاس‌های زیر رو در نظر بگیرید:
public class Source
{
    public string Value1 { get; set; }
    public string Value2 { get; set; }
    public string Value3 { get; set; }
}

public class Destination
{
    public int Value1 { get; set; }
    public DateTime Value2 { get; set; }
    public Type Value3 { get; set; }
}
طبق مستندات AutoMapper  اگه بخواهیم این دو رو نگاشت کنیم Exception  میده چون AutoMapper  نمیدونه چطوری باید مثلا Int  رو به String تبدیل کنه؛ برای همین ما باید به AutoMapper  بگیم چطور این تبدیل نوع رو انجام بده.

نکته: در تستی که من انجام دادم، AutoMapper  تبدیل نوع‌های ابتدایی رو خودش انجام میده؛ مثلا همین تبدیل Int به String  رو!

یکی از روش‌های مهیا کردن تبدیل کننده‌ی نوع، پیاده سازی اینترفیس ITypeConverter<TSource, TDestination> هست. تقریبا مثل کاری که در WPF  و SL با پیاده سازی اینترفیس IValueConverter انجام می‌دادیم.   
من برای تست از همون  تبدیل نوع Bool  به String استفاده میکنم و البته بخاطر ساده بودن دیگه  Model ‌ها رو نمی‌نویسم.
ابتدا تعریف کلاس تبدیل کننده‌ی نوع:
public class BooltoStringTypeConvertor : ITypeConverter<bool, string>
    {
        public string Convert(ResolutionContext context)
        {
            return (bool)context.SourceValue ? "Yes" : "No";
        }
    }
و نحوه استفاده:
Mapper.CreateMap<bool,string>().ConvertUsing<BooltoStringTypeConvertor>();
            Mapper.CreateMap<Product, ProductDto>();
            Mapper.AssertConfigurationIsValid();

var product = new Product { Id = 1,Name ="PC" ,InStock = true };
var productDto = Mapper.Map<Product, ProductDto>(product);
خروجی به شکل زیر میشه.

نکته: TypeConvertor‌ها میدان دیدشون سراسریه و نیازی نیست به ازای هر نگاشتی اونو به AutoMapper  معرفی کنیم Global Scope.

Custom value resolvers

کلاس‌های زیر رو در نظر بگیرید

public class Person
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }
}

public class PersonDTO
{
    public int Id { get; set; }

    public string RawData { get; set; }
}
فرض کنید داخل RawData  تمامی اعضای شی مبداء رو به صورت Comma Delimited ذخیره کنیم. برای این کار می‌تونیم از Value Resolver استفاده کنیم.
یک روش برای این کار ارث بری از کلاس Abstract  ی بنام ValueResolver<TSource, TDestination> هست.
public class CommaDelimetedResolver:ValueResolver<Person,string>
    {
        protected override string ResolveCore(Person source)
        {
            return string.Join(",", source.Id, source.FirstName, source.LastName);
        }
    }
و نحوه استفاده
Mapper.CreateMap<Person, PersonDTO>().ForMember(
                des => des.RawData, op => op.ResolveUsing<CommaDelimetedResolver>());


var person = new Person
{
Id = 1,
FirstName = "Mohammad",
LastName = "Saheb",
};

var personDTO = Mapper.Map<Person, PersonDTO>(person);
و خروجی به شکل زیر میشه

نکته: توجه کنید این فقط یک مثال بود و این کار رو با روش‌های دیگه هم میشه انجام داد مثلا MapFrom  و...
نکته: میدان دید Value Resolver‌ها سراسری نیست و باید به ازای هر نگاشتی اونو معرفی کنیم.

Custom Value Formatters
فرض کنید تاریخ رو در بانک، به صورت میلادی ذخیره کرده‌اید و می‌خواهید سمت View به صورت شمسی نمایش بدید. بنابراین در مبدا ویژگی بنام MiladiDate از نوع DateTime دارید و در مقصد ویژگی بنام ShamsiDate از نوع String. هنگام نگاشت، AutoMapper  به صورت پیش فرض ToString رو فراخونی میکنه که بدرد ما نمیخوره و...
برای این کار میشه  از Value Formatter استفاده کرد با پیاده سازی اینترفیس IValueFormatter.
public class ShamsiFormatter:IValueFormatter
    {
        public string FormatValue(ResolutionContext context)
        {
            return ToShamsi(context.SourceValue.ToString());
        }
    }
نحوه استفاده
Mapper.CreateMap<Person, PersonDTO>().ForMember(
            des => des.ShamsiDate, op => op.AddFormatter<ShamsiFormatter>());
مطالب
اضافه کردن پیوست به فایل‌های PDF با استفاده از iTextSharp

فایل PDF موجود عجیب و غریبی است. می‌شود به آن فایل پیوست اضافه کرد. مثلا اگر یک راهنمای آموزشی را با فرمت PDF تهیه می‌کنید، لازم نیست تا فایل‌های مرتبط با آن‌را جداگانه ارائه دهید. می‌شود تمام این‌ها را داخل همان فایل PDF مدفون کرد. روش انجام اینکار به کمک iTextSharp ساده است اما چند نکته را نیز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

pdfDoc.Add(new Phrase("Test"));

var fs = PdfFileSpecification.FileEmbedded(pdfWriter, @"C:\path\logo.png", "logo.png", null);
pdfWriter.AddFileAttachment("توضیحات",fs);
}

Process.Start("Test.pdf");
}
}
}

در ساده‌ترین حالت ممکن، با استفاده از متد AddFileAttachmen شیء PdfWriter می‌توان پیوستی را به یک فایل PDF در حال تولید اضافه کرد. اگر به فایل نهایی مراجعه کنیم و همچنین قسمت attachments را هم دستی در Adobe reader انتخاب نمائیم، شکل زیر حاصل خواهد شد:


روش متداول بکارگرفته شده دو مشکل را به همراه دارد:
  • قسمت modified مقدار دهی نشده است.
  • پنل مربوط به پیوست‌ها باید دستی باز شود.

نحوه مقدار دهی ستون modified پس از تعریف یک PdfDictionary و قرار دادن PdfName.MODDATE در آن، به صورت زیر است:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

pdfDoc.Add(new Phrase("Test"));

var filePath = @"C:\path\logo.png";
var fileInfo = new FileInfo(filePath);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
pdfWriter.AddFileAttachment("توضیحات", fs);
}

Process.Start("Test.pdf");
}
}
}

که اینبار خروجی زیر را به همراه دارد:


و برای نمایش خودکار پنل پیوست‌ها در Adobe reader به طوری که کاربر نهایی متوجه وجود این فایل‌های پیوست شده گردد، می‌توان ViewerPreferences شیء pdfWriter را مقدار دهی نمود:

pdfWriter.ViewerPreferences = PdfWriter.PageModeUseAttachments;

در مورد فایل‌های موجود چطور؟ آیا می‌توان به یک فایل PDF از پیش تهیه شده هم فایل پیوست کرد؟
پاسخ: بله. باید از امکانات شیء PdfReader استفاده کرد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
var reader = new PdfReader("Test.pdf");
using (var stamper = new PdfStamper(reader, new FileStream("newTest.pdf", FileMode.Create)))
{
var filePath = @"C:\path\logo.png";
addAttachment(stamper, filePath, "توضیحات");
stamper.Close();
}

Process.Start("newTest.pdf");
}

private static void addAttachment(PdfStamper stamper, string filePath, string description)
{
var fileInfo = new FileInfo(filePath);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
var pdfWriter = stamper.Writer;
var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
stamper.AddFileAttachment(description, fs);
}
}
}

در اینجا به کمک کلاس PdfReader، یک فایل موجود خوانده شده و سپس با استفاده از امکانات کلاس PdfStamper که خاصیت Writer آن همان pdfWriter است می‌توان فایل مورد نظر را به فایل موجود افزود.


نظرات مطالب
ASP.NET MVC #10
در هنگام ارسال اطلاعات فرم همه پارامترها در url  نمایش داده میشه
چطور میشه از نمایش این پارامترها جلوگیری کرد ؟
و در url  تنها نام همان اکشن نمایش داده شود
و اینکه من از FormCollection برای  دریافت مقادیر فرم استفاده می‌کنم
نظرات مطالب
نمایش پیام هشدار در Blazor با استفاده از کامپوننت Alert بوت استرپ ۵
معرفی کامپوننت Error Boundaries در Blazor 6x

شبیه به کامپوننت Alert ای که در اینجا ملاحظه می‌کنید و امکان دسترسی به آن توسط یک CascadingValue در سایر کامپوننت‌ها میسر شده، در Blazor 6x، کامپوننت جدید ErrorBoundary اضافه شده‌است که می‌توان همانند مثال فوق، آن‌را در بالاترین سطح ممکن در فایل MainLayout.razor، جهت محصور سازی Body به صورت زیر معرفی کرد:
<ErrorBoundary> 
    <ChildContent> 
        @Body 
    </ChildContent> 
    <ErrorContent> 
        <p>Whoa, sorry about that! While we fix this problem, buy some shirts!</p> 
    </ErrorContent> 
</ErrorBoundary>
کار آن نمایش خطایی در زمان بروز یک استثنای مدیریت نشده، در کامپوننت‌های ذیل این سلسله مراتب است و قالب ErrorContent آن، امکان سفارشی سازی پیامی را جهت نمایش به کاربر میسر می‌کند. اگر از این قالب استفاده نشود، فقط پیام «An error has occurred» نمایش داده خواهد شد. بنابراین دیگر بروز استثناءها، سبب خاتمه‌ی کل برنامه و نیاز به ری‌استارت آن نمی‌شوند.

روش دسترسی به اصل استثناء: قالب محتوای آن به صورت جنریک، یعنی <RenderFragment<Exception تعریف شده‌است. بنابراین می‌توان به اصل استثنای رخ داده به صورت زیر دسترسی یافت:
<ErrorContent Context="exception"> 
  @exception
</ErrorContent>

روش پاک کردن استثنای نمایش داده شده: اطلاعات نمایش داده شده چون در بالاترین سطح (در layout برنامه) رندر می‌شوند، تا زمانیکه مرورگر باز است به همان نحو باقی می‌مانند و تغییر نمی‌کنند. البته خود این کامپوننت به همراه خاصیت MaximumErrorCount است که به صورت پیش‌فرض به 100 تنظیم شده‌است و پس از وقوع 100 استثناء، ریست می‌شود. اگر می‌خواهید این ریست، پس از هدایت به صفحات دیگر صورت گیرد و در صفحه‌ی جدید، خطای صفحه‌ی قبلی را مشاهده نکنید، خودتان باید به صورت دستی آن‌را ریست کنید:
<ErrorBoundary @ref="errorBoundary">
    @Body
</ErrorBoundary>

@code {
    ErrorBoundary errorBoundary;
    protected override void OnParametersSet()
    {
        // On each page navigation, reset any error state
        errorBoundary?.Recover();
    }
}
در اینجا با استفاده از یک ref@، به وهله‌ای از کامپوننت، دسترسی یافته و سپس می‌توان متد Recover آن‌را جهت پاک کردن سابقه‌ی استثناءهای رخ داده فراخوانی کرد.


پ.ن.
این کامپوننت جدید از امکان مشابهی در React ایده گرفته شده‌است.
مطالب
قابلیت های جدید VisualStudio.NET 2012 - قسمت سوم
اگر شما در زمینه طراحی وب سایت و برنامه‌های کاربردی تحت وب فعالیت دارید حتماً با ابزارهایی مانند Firebug آشنا هستید. معمولاً فرآیند بررسی مشکلات رابط کاربری و موضوعات مشابه آن بصورت زیر بوده است:
 
 

توجه داشته باشید که یک صفحه وب که در مرورگر به نمایش در می‌آید، برآیند فایل‌های جاوا اسکریپت، شیوه نامه ها، User control ها، صفحات ASPX و Master Page‌ها و ... است. افزون بر این آنچه در مرورگر نمایش داده می‌شود با چیزی که ما در محیط طراحی (Visual Studio.NET (Design View  می‌بینیم متفاوت است.
تمام مشکلات و سختی‌های بالا دست به دست هم دادند تا در نسخه جدید نرم افزار Visual Studio.NET شاهد ابزار جادویی با عنوان Page Inspector باشیم.
این ابزار بصورت Real-time امکان نگاشت (mapping) عناصر موجود در نتیجه نهایی برنامه وب را با سورس کد مهیا می‌کند. بدین معنا توسط Page Inspector با حرکت ماوس روی عناصر صفحه در مرورگر، Visual Studio.NET بخشی را که آن عنصر را تولید کرده است (User Control, Master Page, View, و ...) نمایش می‌دهد و شما می‌توانید بلافاصله پس از اعمال تغییرات جدید بر روی سورس کد، نتیجه را روی مرورگر ببنید. البته عکس این موضوع نیز صادق است و شما می‌توانید با حرکت در سورس کد، نتیجه بصری و عناصر HTML ی که در نتیجه تولید می‌شوند را مشاهده کنید. (عناصر متناظر به حالت Select در می‌آیند.)
 

 
از دیگر قابلیت‌های این ابزار نمایش CSS‌های متناظر هر عنصر است. شما می‌توانید هر یک از قوانینی که در Style هر عنصر تعریف کرده اید را فعال و یا غیر فعال کنید. همچنین امکان ویرایش آن‌ها وحود دارد.
همچنین از طریق گزینه File می‌توان لیست تمام فایل‌های سورس صفحه را مشاهده کرد.
 

 
با وجود چنین ابزاری یقیناً داشتن دو مانیتور برای برنامه نویسان و توسعه دهندگان وب کاملاً حیاتی است. چراکه Visual Studio.NET به شما این امکان را می‌دهد تا Page Inspector را در یک مانیتور و نمای سورس را در مانیتور دیگر به نمایش در آورید.
 
نکته:
جهت استفاده از تمام امکانات این ابزار باید دستور زیر را در تگ appsettings فایل web.config اضافه کنید:
 <add key="VisualStudioDesignTime:Enabled" value="true" />
پیشنهاد می‌کنم برای درک بهتر این ابزار و آشنایی با آن ویدئو مربوطه در کانال 9  را از دست ندهید.
 
مطالب
غیرفعال سازی کش مرورگر در مورد تصاویر ایستا

امروز هنگام نمایش تصاویر در یک گرید با مشکل زیر مواجه شده بودم. (گرفتار شده بودم!!)
فرض کنید یک فرم درست کرده‌اید که یک تصویر به همراه توضیحاتی توسط آن ثبت می‌شود. جهت اطمینان خاطر ثبت کننده، تصاویر ثبت شده در یک گرید پائین فرم نمایش داده خواهند شد. نام این تصاویر همواره ثابت است (برای مثال نام تصویر تشکیل شده است از یک شماره پروژه بعلاوه پسوند فایل).
مشکلی در ثبت تصاویر یا توضیحات آنها وجود نداشت. مشکل در این بود که پس از ویرایش رکورد و انتخاب تصویری جدید و ثبت آن، همان تصویر قبلی در مرورگر نمایش داده می‌شد. این مورد را می‌شد با فشردن دکمه‌های ctrl+f5 حل کرد (به روز کردن کش مرورگر)، اما این راه حل اصولی حل این مشکل نیست. (تصورش را بکنید که به کاربر گفته شود پس از هر بار فشردن دکمه ثبت، یکبار هم دکمه‌های ctrl+f5 را فشار دهید!)
راه ‌حل‌های استاندارد غیرفعال کردن کش در ASP.Net هم هیچکدام افاقه نکردند. ترکیبی از موارد زیر در page_load صفحه تست شدند:
            //do not cache...
Response.CacheControl = "no-cache";
Response.AddHeader("Pragma", "no-cache");
Response.Expires = -1;
Response.Cache.SetNoStore();
Response.Cache.SetCacheability(HttpCacheability.NoCache);

چون نام تصاویر تغییری نمی‌کرد، برای مثال 1234 همیشه همان 1234 باقی می‌ماند (صرفنظر از محتوای جدید آن)، مرورگر این تصویر غیرپویا را کش می‌کرد و فقط با ctrl+f5 این کش به روز می‌شد. (روش فوق در مورد غیرفعال کردن کش کردن یک صفحه پویای ASP.Net مؤثر است (برای مثال توصیه می‌شود که کش کردن صفحات لاگین را حتما به این صورت غیرفعال کنید)، اما در مورد اشیاء غیرپویای صفحه مطابق آزمایش من اثری نداشت)

این مشکل به صورت زیر حل شد: (یک ستون GridView است)
<asp:TemplateField HeaderText="لوگو">
<ItemTemplate>
<asp:Image ID="Image2" runat="server"
ImageUrl='<%# "pics/"+Eval("filename")+"?uid="+Guid.NewGuid().ToString("N") %>' />
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>

یک عبارت منحصر بفرد به صورت کوئری استرینگ به انتهای نام تصویر اضافه شد. مشکلی هم در نمایش تصویر ایجاد نمی‌کند، مرورگر آن‌را یک تصویر جدید به حساب آورده و دوباره همان تصویر قبلی موجود در کش را نمایش نخواهد داد. (با IE و فایرفاکس تست شد و اینجا دیگر مهم نیست که وضعیت تنظیمات به روز رسانی کش مرورگرهای تک تک کاربران به چه صورتی است و آیا باید نگران همه‌ی آنها بود یا خیر یا این‌که اصلا ارزش آن‌را دارد که برای یک صفحه کلا کش مرورگر را غیرفعال کرد؟)

زمانیکه GridView رندر شود، تصویر ما به صورت زیر خواهد بود:
<img id="GridView1__ctl2_Image2" src="pics/123456.PNG?uid=2a2c4247ae264ef49be46a2436ae03c9" border="0" />

بازخوردهای دوره
ارتباطات بلادرنگ و SignalR
من فکر می‌کنم اگر عنوان دوره‌ها - مانند عنوان آخرین مطالب - در صفحه اول سایت نمایش داده شوند بیشتر مورد استقبال قرار خواهد گرفت.
نظرات اشتراک‌ها
معرفی کتابخانه‌ی DNTCaptcha.Core
زمانیکه از این کامپوننت در لینوکس استفاده میکنم کپچا رو به این صورت نمایش میده: