مطالب
آشنایی با Refactoring - قسمت 9

این قسمت از آشنایی با Refactoring به کاهش cyclomatic complexity اختصاص دارد و خلاصه آن این است: «استفاده از if های تو در تو بیش از سه سطح، مذموم است» به این علت که پیچیدگی کدهای نوشته شده را بالا برده و نگهداری آن‌ها را مشکل می‌کند. برای مثال به شبه کد زیر دقت کنید:

if
if
if
if
do something
endif
endif
endif
endif


که حاصل آن شبیه به نوک یک پیکان (Arrow head) شده است. یک مثال بهتر:


namespace Refactoring.Day9.RemoveArrowhead.Before
{
public class Role
{
public string RoleName { set; get; }
public string UserName { set; get; }
}
}

using System;
using System.Collections.Generic;
using System.Linq;

namespace Refactoring.Day9.RemoveArrowhead.Before
{
public class RoleRepository
{
private IList<Role> _rolesList = new List<Role>();

public IEnumerable<Role> Roles { get { return _rolesList; } }

public void AddRole(string username, string roleName)
{
if (!string.IsNullOrWhiteSpace(roleName))
{
if (!string.IsNullOrWhiteSpace(username))
{
if (!IsInRole(username, roleName))
{
_rolesList.Add(new Role
{
UserName=username,
RoleName=roleName
});
}
else
{
throw new InvalidOperationException("User is already in this role.");
}
}
else
{
throw new ArgumentNullException("username");
}
}
else
{
throw new ArgumentNullException("roleName");
}
}

public bool IsInRole(string username, string roleName)
{
return _rolesList.Any(x => x.RoleName == roleName && x.UserName == username);
}
}
}

متد AddRole فوق، نمونه‌ی بارز پیچیدگی بیش از حد حاصل از اعمال if های تو در تو است و ... بسیار متداول. برای حذف این نوک پیکان حاصل از if های تو در تو، از بالاترین سطح شروع کرده و شرط‌ها را برعکس می‌کنیم؛ با این هدف که هر چه سریعتر متد را ترک کرده و خاتمه دهیم:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Refactoring.Day9.RemoveArrowhead.After
{
public class RoleRepository
{
private IList<Role> _rolesList = new List<Role>();

public IEnumerable<Role> Roles { get { return _rolesList; } }

public void AddRole(string username, string roleName)
{
if (string.IsNullOrWhiteSpace(roleName))
throw new ArgumentNullException("roleName");

if (string.IsNullOrWhiteSpace(username))
throw new ArgumentNullException("username");

if (IsInRole(username, roleName))
throw new InvalidOperationException("User is already in this role.");

_rolesList.Add(new Role
{
UserName = username,
RoleName = roleName
});
}

public bool IsInRole(string username, string roleName)
{
return _rolesList.Any(x => x.RoleName == roleName && x.UserName == username);
}
}
}

اکنون پس از اعمال این Refactoring ، متد AddRole بسیار خواناتر شده و هدف اصلی آن که اضافه کردن یک شیء به لیست نقش‌ها است، واضح‌تر به نظر می‌رسد. به علاوه اینبار قسمت‌های مختلف متد AddRole، فقط یک کار را انجام می‌دهند و وابستگی‌های آن‌ها به یکدیگر نیز کاهش یافته است.


مطالب
مروری بر کاربردهای Action و Func - قسمت دوم
در قسمت قبل از  Func و Actionها برای ساده سازی طراحی‌های مبتنی بر اینترفیس‌هایی با یک متد استفاده کردیم. این مورد خصوصا در حالت‌هایی که قصد داریم به کاربر اجازه‌ی فرمول نویسی بر روی اطلاعات موجود را بدهیم، بسیار مفید است.

مثال دوم) به استفاده کننده از API کتابخانه خود، اجازه فرمول نویسی بدهید

برای نمونه مثال ساده زیر را درنظر بگیرید که در آن قرار است یک سری عدد که از منبع داده‌ای دریافت شده‌اند، بر روی صفحه نمایش داده شوند:
public static void PrintNumbers()
{
    var numbers = new[] { 1,2,3,5,7,90 }; // from a data source
    foreach(var item in numbers)
    {
        Console.WriteLine(item);
    }    
}
قصد داریم به برنامه نویس استفاده کننده از کتابخانه گزارش‌سازی خود، این اجازه را بدهیم که پیش از نمایش نهایی اطلاعات، بتواند توسط فرمولی که مشخص می‌کند، فرمت اعداد نمایش داده شده را تعیین کند.
روال کار اکثر ابزارهای گزارش‌سازی موجود، ارائه یک زبان اسکریپتی جدید برای حل این نوع مسایل است. اما با استفاده از Func و ... روش‌های Code first (بجای روش‌های Wizard first)، خیلی از این رنج و دردها را می‌توان ساده‌تر و بدون نیاز به اختراع و یا آموزش زبان جدیدی حل کرد:
public static void PrintNumbers(Func<int,string> formula)
{
    var numbers = new[] { 1,2,3,5,7,90 };  // from a data source
    foreach(var item in numbers)
    {
        var data = formula(item);
        Console.WriteLine(data);
    }    
}
اینبار با استفاده از Func، امکان فرمول نویسی را به کاربر استفاده کننده از API ساده گزارش ساز فرضی خود داده‌ایم. Func تعریف شده در اینجا یک عدد int را در اختیار استفاده کننده قرار می‌دهد. در این بین، برنامه نویس می‌تواند هر نوع تغییر یا هر نوع فرمولی را که مایل است بر روی این عدد به کمک دستور زبان جاری مورد استفاده، اعمال کند و در آخر تنها باید نتیجه این عملیات را به صورت یک string بازگشت دهد. برای مثال:
 PrintNumbers(number => string.Format("{0:n0}",number));
البته سطر فوق ساده شده فراخوانی زیر است:
 PrintNumbers((number) =>{ return string.Format("{0:n0}",number); });
به این ترتیب اعداد نهایی با جدا کننده سه رقمی نمایش داده خواهند شد.
از این نوع طراحی، در ابزارها و کتابخانه‌های جدید گزارش سازی مخصوص ASP.NET MVC زیاد مشاهده می‌شوند.


مثال سوم) حذف کدهای تکراری برنامه

فرض کنید قصد دارید در برنامه وب خود مباحث caching را پیاده سازی کنید:
using System;
using System.Web;
using System.Web.Caching;
using System.Collections.Generic;

namespace WebToolkit
{
    public static class CacheManager
    {
        public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes)
        {
            if (data == null) return;
            httpContext.Cache.Add(
                key,
                data,
                null,
                DateTime.Now.AddMinutes(durationMinutes),
                TimeSpan.Zero,
                CacheItemPriority.AboveNormal,
                null);
        }
    }
}
در هر قسمتی از برنامه که قصد داشته باشیم اطلاعاتی را در کش ذخیره کنیم، الگوی تکراری زیر باید طی شود:
var item = httpContext.Cache[key];
if (item == null)
{
    item = ReadDataFromDataSource();
    if (item == null)
          return null;

    CacheInsert(httpContext, key, item, durationMinutes);
}
ابتدا باید وضعیت کش جاری بررسی شود؛ اگر اطلاعاتی در آن موجود نبود، ابتدا از منبع داده‌ای مورد نظر خوانده شده و سپس در کش درج شود.
می‌توان در این الگوی تکراری، خواندن اطلاعات را از منبع داده، به یک Func واگذار کرد و به این صورت کدهای ما به نحو زیر بازسازی خواهند شد:
using System;
using System.Web;
using System.Web.Caching;
using System.Collections.Generic;

namespace WebToolkit
{
    public static class CacheManager
    {
        public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes)
        {
            if (data == null) return;
            httpContext.Cache.Add(
                key,
                data,
                null,
                DateTime.Now.AddMinutes(durationMinutes),
                TimeSpan.Zero,
                CacheItemPriority.AboveNormal,
                null);
        }

        public static T CacheRead<T>(this HttpContextBase httpContext, string key, int durationMinutes, Func<T> ifNullRetrievalMethod)
        {
            var item = httpContext.Cache[key];
            if (item == null)
            {
                item = ifNullRetrievalMethod();
                if (item == null)
                    return default(T);

                CacheInsert(httpContext, key, item, durationMinutes);
            }
            return (T)item;
        }
    }
}
و استفاده از آن نیز به نحو زیر خواهد بود:
var user = HttpContext.CacheRead(
                            "Key1",
                            15,
                            () => _usersService.FindUser(userId));
پارامتر سوم متد CacheRead به صورت خودکار تنها زمانیکه اطلاعات کش متناظری با کلید Key1 وجود نداشته باشند، اجرا شده و نتیجه در کش ثبت می‌گردد. در اینجا دیگر از if و else و کدهای تکراری بررسی وضعیت کش خبری نیست.
 
مطالب
مدیریت تغییرات در سیستم های مبتنی بر WCF
تشریح مسئله : در صورتی که بعد از انتشار برنامه؛ در نسخه بعدی مدل سمت سرور تغییر کرده باشد و امکان بروز رسانی مدل‌های سمت کلاینت وجود نداشته باشد برای حل این مسئله بهترین روش کدام است.
نکته : برای فهم بهتر مطالب آشنایی اولیه با مفاهیم WCF الزامی است.
ابتدا مدل زیر را در نظر بگیرید:
   [DataContract]
    public class Book 
    {
        [DataMember]
        public int Code { get; set; }

        [DataMember]
        public string Name { get; set; }             
    }
حالا یک سرویس برای دریافت و ارسال اطلاعات این مدل به کلاینت می‌نویسیم.
  [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        IEnumerable<Book> GetAll();

        [OperationContract]
        void Save( Book book );
    }
و سرویسی که Contract بالا رو پیاده سازی کند.
public class SampleService : ISampleService
    {
        public List<Book> ListOfBook 
        {
            get; 
            private set; 
        }

        public SampleService()
        {
            ListOfBook = new List<Book>();
        }
        public IEnumerable<Book> GetAll()
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code=1 , Name="Book1"},
                new Book(){Code=2 , Name="Book2"},
            } );
            return ListOfBook;
        }

        public void Save( Book book )
        {
            ListOfBook.Add( book );
        }
    }
 متد GetAll برای ارسال اطلاعات به کلاینت و متد Save نیز برای دریافت اطلاعات از کلاینت.
حالا یک پروژه Console Application بسازید و از روش AddServiceReference سرویس مورد نظر را به Client اضافه کنید. برنامه را تست کنید. بدون هیچ مشکلی کار می‌کند.
حالا اگر در نسخه بعدی سیستم مجبور شویم به مدل Book یک خاصیت دیگر به نام Author را نیز اضافه کنیم و امکان Update کردن سرویس در سمت کلاینت وجود نداشته باشد چه اتفاقی خواهد افتاد.
به صورت زیر:
 [DataContract]
    public class Book 
    {
        [DataMember]
        public int Code { get; set; }
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string Author { get; set; }
    }
به طور پیش فرض اگر در DataContract‌های سمت سرور و کلاینت اختلاف وجود داشته باشد این موارد نادیده گرفته می‌شوند. یعنی همیشه مقدار خاصیت Author برابر null خواهد بود.
نکته : برای Value Type‌ها مقادیر پیش فرض و برای Reference Type‌ها مقدار Null.
اگر برای DataMemberAttribute خاصیت IsRequired را برابر true کنیم از این پس برای هر درخواستی که مقدار Author  آن مقدار نداشته باشد یک Protocol Exception  پرتاب می‌شود. به صورت زیر:
[DataMember( IsRequired = true )]
public string Author { get; set; }
اما این همیشه راه حل مناسبی نیست.
روش دیگر این است که Desrialize کردن مدل را تغییر دهیم. بدین معنی که هر گاه مقدار Author برابر Null بود یک مقدار پیش فرض برای آن در نظر بگیریم. این کار با نوشتن یک متد و قراردادن OnDeserializingAttribute به راحتی امکان پذیر است. کلاس Book به صورت زیر تغییر می‌کند.
  [DataContract]
    public class Book
    {
        [DataMember]
        public int Code { get; set; }
        [DataMember]
        public string Name { get; set; }

        [DataMember( IsRequired = true )]
        public string Author { get; set; }

        [OnDeserializing]
        private void OnDeserializing( StreamingContext context )
        {
            if ( string.IsNullOrEmpty( Author ) )
            {
                Author = "Masoud Pakdel";
            }
        }
    }
حال اگر از سمت کلاینت کلاس Book دریافت شود که مقدار خاصیت Author آن برابر Null باشد توسط متد OnDeserializing مقدار پیش فرض به آن اعمال می‌شود.مثل تصویر زیر:

روش بعدی استفاده از اینترفیس IExtensibleDataObject  است. بعد از اینکه کلاس Book این اینترفیس را پیاده سازی کرد مشکل Versioning Round Trip حل می‌شود. به این صورت که سرویس یا کلاینتی که نسخه قدیمی را می‌شناسد اگر نسخه جدید را دریافت کند خصوصیاتی را که نمی‌شناسد مثل Author در خاصیت ExtensionData ذخیره می‌شود و هنگامی که کلاس Book برای سرویس یا کلاینتی که نسخه جدید را می‌شناسد DataContractSerializer اطلاعات مورد نظر را از خصوصیت ExtensionData بیرون می‌کشد و کلاس Book جدید را باز سازی می‌کند. بررسی کلاس ExtensionData توسط خود DataContractSreializer انجام می‌شود و نیاز به هیچ گونه ای کد نویسی ندارد.

[DataContract]
    public class Book : IExtensibleDataObject
    {
        [DataMember]
        public int Code { get; set; }
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string Author { get; set; }
    
        public virtual ExtensionDataObject ExtensionData
        {
            get { return _extensionData; }
            set
            {
                _extensionData = value;
            }
        }
        private ExtensionDataObject _extensionData;
    }
اگر کد متد GetAll سمت سرور را به صورت زیر تغییر دهیم که خاصیت Author هم مقدار داشته باشد با استفاده از خاصیت ExtensionData کلاینت هم از این مقدار مطلع خواهد شد.
public IEnumerable<Book> GetAll()
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code=1 , Name="Book1", Author="Masoud Pakdel"},
                new Book(){Code=2 , Name="Book2" },
            } );
            return ListOfBook;
        }
کلاینت هم به صورت زیر :

همان طور که می‌بینید این نسخه از کلاینت هیچ گونه اطلاعی از وجود یک خاصیت به نام Author ندارد ولی از طریق ExtensionData متوجه می‌شود یک خاصیت به نام Author به مدل سمت سرور اضافه شده است.

اما در صورتی که قصد داشته باشیم که یک سرویس خاص از همان نسخه قدیمی کلاس Book استفاده کند و نیاز به نسخه جدید آن نداشته باشد می‌توانیم این کار را از طریق مقدار دهی True به خاصیت IgnoreExtensionDataObject  در ServiceBehaviorAttribute انجام داد. بدین شکل

 [ServiceBehavior( IgnoreExtensionDataObject = true )]
  public class SampleService : ISampleService
از این پس سرویس بالا از همان مدل Book بدون خاصیت Author استفاده می‌کند.

منابع :
نظرات مطالب
تغییرات بوجود آمده در Bundling and Minification -MVC4
حق با آقای راد هستش برای اضافه کردن اسکریپت میشه به شکل زیر عمل کرد:
 public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include("~/Scripts/jquery-1.*"));

bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
          "~/Content/themes/base/jquery.ui.core.css",
          "~/Content/themes/base/jquery.ui.resizable.css",
          "~/Content/themes/base/jquery.ui.selectable.css",
          "~/Content/themes/base/jquery.ui.accordion.css",
          "~/Content/themes/base/jquery.ui.autocomplete.css",
          "~/Content/themes/base/jquery.ui.button.css",
          "~/Content/themes/base/jquery.ui.dialog.css",
          "~/Content/themes/base/jquery.ui.slider.css",
          "~/Content/themes/base/jquery.ui.tabs.css",
          "~/Content/themes/base/jquery.ui.datepicker.css",
          "~/Content/themes/base/jquery.ui.progressbar.css",
          "~/Content/themes/base/jquery.ui.theme.css"));
}
که در نسخه 4 3 تا ویژگی داره:
  1. اگر فایل   FileX.min.js  و همچنینن فایل  FileX.js موجود باشد  .min را انتخاب می‌کند
  2. برای دیباگ کردن فایل اصلی را انتخاب میکند.( FileX.js )
  3. از فایلهای  -vsdoc صرف نظر میکند.چون فقط جهت استفاده از  IntelliSense هستند.
به صورت زیر نیز میتوان عمل کرد.
    @Styles.Render("~/Content/themes/base/css", "~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
فقط باید توجه داشت که زمانی که به عنوان مثال به روش زیر اضافه می‌کنیم
Include("~/Scripts/Common/*.js")
چون از طریق حروف الفبا برای ما فایلها را لود میکند( ترتیب مهم است نه حروف الفبا)ممکن است به مشکل بر بخوریم.
نظرات مطالب
معرفی List Patterns Matching در C# 11
تبدیل کدهایی که از اندیس‌های آرایه‌ها استفاده می‌کنند به List Patterns matching

فرض کنید قصد داریم با اجزای آرایه‌ی زیر کار کنیم:
var data = "item1|item2|item3";
var collection = data.Split('|');
برای مثال اگر آرایه‌ی collection دارای 2 عضو بود، از طریق collection[0], collection[1] با این دو عضو کار کنیم و یا اگر 3 عضوی بود، به همان صورت بر اساس ایندکس‌ها دسترسی صورت گیرد و اگر این آرایه 2 و یا 3 عضوی نبود، استثنایی را صادر کند. روش متداول انجام اینکار به صورت زیر است:
var formattedDataBefore = collection.Length switch
{
    2 => FormatData(collection[0], collection[1]),
    3 => FormatData(collection[0], collection[1], collection[2]),
    var length => throw new InvalidOperationException($"Expected 3 parts, but got {length} parts for formatted string: {data}."),
};
می‌توان این قطعه کد را با استفاده از List Patterns Matching به صورت زیر بازنویسی کرد:
var formattedDataAfter = collection switch
{
    [var part1, var part2] => FormatData(part1, part2),
    [var part1, var part2, var part3] => FormatData(part1, part2, part3),
    var parts => throw new InvalidOperationException($"Expected 3 parts, but got {parts.Length} parts for formatted string: {data}."),
};

نمونه‌ی دیگر این دسترسی‌های بر اساس ایندکس‌ها، مثال زیر است. در اینجا ساختار شیء Song به صورت زیر تعریف شده‌است:
public class Song
{
    public string Name { get; set; }
    public List<string> Lyrics { get; set; }
}
و فرض کنید songs لیستی از آن است. در این حالت یک روش جستجوی ابتدایی در این لیست، به صورت زیر است که برای مثال اولین Lyrics آن song خاص، مساوی Hello، تعداد Lyrics آن 6 و آخرین عضو Lyrics آن مساوی ? باشد:
for (var i = 0; i < songs.Count; i++)
{
    if (songs[i].Lyrics[0] == "Hello" && songs[i].Lyrics.Count == 6 &&
        songs[i].Lyrics[songs[i].Lyrics.Count - 1] == "?")
    {
        Console.WriteLine($"{i}");
    }
}
می‌توان این قطعه کد را در C# 11 به صورت زیر بازنویسی کرد که بسیار خواناتر است:
for (var i = 0; i < songs.Count; i++)
{
    if (songs[i].Lyrics is ["Hello", _, _, _, _, "?"])
    {
       Console.WriteLine($"{i}");
    }
}
و یا اگر از تعداد Lyrics مساوی 6، صرفنظر کنیم و تعداد اعضای آن مهم نباشد، می‌توان به صورت زیر عمل کرد:
foreach (Song song in songs)
{
    if (song.Lyrics is ["Hello", .., "?"])
    {
        Console.WriteLine(song.Name);
    }
}

به علاوه اگر در جستجویی دیگر، نیاز به کار با اعضای آرایه‌ی 5 عضوی یافت شده وجود داشت، می‌توان بدون نیاز به مراجعه‌ی متداول به ایندکس‌های آرایه، به صورت زیر عمل کرد:
foreach (Song song in songs)
{
  if (song.Lyrics is ["Hello", "from" or "is", var third, var forth, var fifth])
    {
      Console.WriteLine(song.Name);
      Console.WriteLine($"The third word is : {third}");
      Console.WriteLine($"The forth word is : {forth}");
      Console.WriteLine($"The fifth word is : {fifth}");
    }
}
مطالب
مقایسه سرعت نگاشت AutoMapper

قبل ازاین مقاله، درباره راه اندازی و استفاده از کتابخانه Automapper  بحث شده ولی موردی که شاید کمتر به آن توجه شده سرعت این نگاشت میباشد. در این مقاله با استفاده از نوشتن تست، این موضوع بررسی میشود.

کلاس ساده زیر را در نظر بگیرید که برای مثال از سمت لایه دسترسی به داده گرفته شده است:

public enum PersonType
{
    Real =0,
    Legal=1
}

public class Person
{
    public long PersonId { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public PersonType PersonType { get; set; }

    public Person(long personId, string name, string family, PersonType personType)
    {
        PersonId = personId;
        Name = name;
        Family = family;
        PersonType = personType;
    }
}

از سازنده آن برای دریافت مقادیر مربوط به خصوصیات شیء استفاده شد.

در طرف دیگر نیز کلاسی برای نگاشت از آبجکت رسیده از سمت لایه داده ساخته میشود که برای نمایش در ویوها ایجاد شده است: 

public class PersonDto
{
    public long PersonId { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public PersonType PersonType { get; set; }

    public PersonDto(long personId, string name, string family, PersonType personType)
    {
        PersonId = personId;
        Name = name;
        Family = family;
        PersonType = personType;
    }
}

همانطور که مشاهده میکنید در سازنده این کلاس نیز مقادیر خصوصیات، دریافت شده‌است.

برای ایجاد لیستی که در تست مورد استفاده قرار میگیرد نیز کلاس زیر را فراهم میکنیم: 

public class PersonList
{
    readonly List<Person> _list = new List<Person>();
    public ReadOnlyCollection<Person> GetPersons()
    {
        if (!_list.Any())
        {
            for (int i = 0; i < 100*1000; i++)
            {
                _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2)));
            }

        }
        return _list.AsReadOnly();
    }
}

در اینجا برای محسوس بودن نتیجه تست میتوان تعداد آبجکتهای لازم برای تست را تعیین کرد، فعلا 100 هزار آبجکت در نظر گرفته شده است: 

for (int i = 0; i < 100*1000; i++)
{
    _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2)));
}

برای ارجاع به AutoMapper، با استفاده از نیوگت، پکیج را به پروژه تست ارجاع میدهیم: (در حال حاضر نسخه 5.1.1 استفاده شده است) 

<package id="AutoMapper" version="5.1.1" targetFramework="net452" />

در سمت تست نگاشت نیز از دو متد برای مقایسه استفاده میکنیم؛ یکی با استفاده از AutoMapper و دیگری بدون استفاده از آن: 

[TestMethod]
public void FillPersonDtoList_AutoMapperShouldMapPersonListToPersonDtoList_WhenLargeAmountOfPerson()
{
    // arrange
    var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons();

    // act
    personDtoList = Mapper.Map<List<PersonDto>>(persons);

    //assert
    Assert.AreEqual(persons.Count, personDtoList.Count);
}
[TestMethod]
public void FillPersonDtoList_UsingHandlyAssignment_WhenLargeAmountOfPerson()
{
    // arrange
    var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons();

    // act
    foreach (var person in persons)
    {
        personDtoList.Add(new PersonDto(person.PersonId, person.Name, person.Family, person.PersonType));
    }

    //assert
    Assert.AreEqual(persons.Count, personDtoList.Count);
}

سرعت نگاشت AutoMapper در نسخه حال حاضر تقریبا سه بار کندتر از استفاده معمول برای تهیه نگاشت جدید از یک آبجکت است: 

نکته: این تست با نسخه قدیمی تر(4.0.4.0) نیز انجام شده که این اختلاف سرعت نزدیک به 13 بار کندتر هم رسیده است. 

پ.ن: سورس پروژه تست

مطالب
تولید فایل Word بدون نصب MS Word بر روی سرور

یکی از مواردی که ممکن است در محیط کاری با آن برخورد داشت، تقاضای تولید فایل word یک گزارش با فرمتی مشخص از یک برنامه ASP.Net است. برای مثال یک قالب درست کرده‌اند که header‌ و footer و کلا یک فرمت رسمی دارد. الان برنامه شما باید این فایل word رسمی را با گزارشی که تولید می‌کند پر کند. حالا اینجاست که گرفتاری برنامه نویس شروع می‌شود! روی سرور باید word نصب باشد تا توسط اشیاء COM آن بتوان یک چنین کارهایی را آن‌هم با ASP.Net که به صورت پیش فرض کمترین سطح دسترسی را روی سیستم دارد انجام داد. یا اینکه باید به سراغ کامپوننت‌های تجاری رفت و حالا اینجا با این وضع تحریم و غیره چگونه بتوان آنها را خریداری کرد یا شاید احتمالا در سایت‌های وارز بتوان نسخه تکه پاره شده آنها را یافت. مشکلی هم که این نوع کامپوننت‌ها دارند این است که ممکن است سال دیگر اصلا ساپورت نشوند. محصولات مایکروسافت هم که مرتبا در حال به روز رسانی هستند. در این حالت برنامه متکی به این نوع کامپوننت‌های تجاری سورس بسته در همان نگارش قبلی خود مجبور است باقی بماند.
خوشبختانه با ارائه آفیس 2007 و فرمت OpenXML فایلهای آن، این مشکل تقریبا مرتفع شده است. مایکروسافت نیز برای سهولت تولید این نوع اسناد، OpenXML SDK را ارائه داده است که از آدرس زیر قابل دریافت است:
Open XML Format SDK 1.0

البته پیش نمایش نگارش دو SDK آن نیز موجود است که در مطلب جاری به آن پرداخته نخواهد شد.

فایل‌های office 2007 از یک فایلzip تشکیل شده از چند فایل xml داخل آن، ایجاد شده‌اند. برای مثال یک فایل docx را با winrar یا امثال آن باز کنید (تصویر زیر):



برای کار با اینگونه اسناد باید با اصطلاحات زیر آشنا شد:
Package : فایل zip شما (همان فایل برای مثال docx) اینجا یک بسته نام دارد.
Parts : اجزای این بسته که همان فایل‌های آن هستند، parts نامیده شده اند.
Relations : اگر به فایل‌های موجود در یک بسته دقت کنید، فایلهایی با پسوند rels را خواهید دید که بیانگر نحوه ارتباط Parts با یکدیگر هستند.
Relations Ids: هر ارتباط با یک ID منحصربفرد تعریف می‌گردد.

اگر علاقمند باشید که پوستری را در این رابطه مشاهده نمائید می‌توان به آدرس زیر مراجعه نمود.
Open XML Developer Map

نحوه استفاده از OpenXML SDK در دات نت:
ابتدا باید ارجاعی را به فایل DocumentFormat.OpenXml.dll که پس از نصب در مسیر OpenXMLSDK\1.0.1825\lib قرار گرفته است به پروژه افزود. سپس نیاز است تا ارجاعی به کتابخانه WindowsBase نیز به برنامه افزوده شود (تصویر زیر). افزودن ارجاعی به این کتابخانه جهت کامپایل برنامه ضروری است (شکل زیر).


تا اینجا ارجاعات برنامه به صورت زیر خواهند بود:



یک مثال ساده:
قصد داریم یک فایل docx ساده را با استفاده از OpenXML SDK ایجاد کنیم. در مثال زیر فرمت متغیر docXml را می‌توان با ایجاد یک فایل docx ساده در word و سپس باز کردن بسته فشرده شده آن و مشاهده محتوای فایل word\document.xml بدست آورد.
using System.IO;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;

namespace OpenXMLTestApp
{
class CWord
{

public static void CreateDocument(string documentFileName, string text)
{
using (WordprocessingDocument wordDoc =
WordprocessingDocument.Create(documentFileName, WordprocessingDocumentType.Document))
{
MainDocumentPart mainPart = wordDoc.AddMainDocumentPart();

string docXml =
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
<w:body><w:p><w:r><w:t>#REPLACE#</w:t></w:r></w:p></w:body>
</w:document>";

docXml = docXml.Replace("#REPLACE#", text);

using (Stream stream = mainPart.GetStream())
{
byte[] buf = (new UTF8Encoding()).GetBytes(docXml);
stream.Write(buf, 0, buf.Length);
}
}
}
}
}

و نحوه استفاده از آن می‌تواند به صورت زیر باشد:

CWord.CreateDocument("test.docx", "سلام دنیا");

این کتابخانه کار ایجاد فایل‌های xml ، تولید روابط بین‌ آنها و همچنین بسته بندی و zip کردن نهایی را به صورت خودکار انجام می‌دهد.

برای مطالعه بیشتر می‌توان به منابع زیر مراجعه نمود:

یک ویدیوی آموزشی رایگان از مایکروسافت
دریافت

سؤالات متداول در MSDN
http://msdn.microsoft.com/en-us/library/bb491088.aspx
البته اگر پس از نصب SDK به پوشه doc آن مراجعه نمائید، این سؤال و جواب‌ها را در فایل راهنمای chm آن نیز می‌توان پیدا کرد.

مثال دیگری در مورد ایجاد یک گزارش از بانک اطلاعاتی و گرفتن خروجی docx از آن
http://openxmldeveloper.org/articles/GenerateWordTable.aspx
البته این مثال خیلی قدیمی است و قسمت‌های کار با پکیج را با SDK‌ ارائه شده می‌توان به صورت خودکار انجام داد. اما حداقل نحوه تولید جداول استاندارد OpenXML را می‌توان از آن ایده گرفت.

مثالی در مورد نحوه قرار دادن عکس در فایل docx تولیدی

همچنین مثال‌های بیشتری را در وبلاگ‌های مربوطه می‌توان یافت:
http://blogs.msdn.com/brian_jones/
http://blogs.msdn.com/ericwhite/default.aspx


نظرات مطالب
نوشتن TagHelperهای سفارشی برای ASP.NET Core
پیاده سازی  TagHelper سفارشی ImageGravatar  جهت نمایش آواتار کاربر از سایت gravatar 


پیاده سازی کلاس GarvatarTagHelper
[HtmlTargetElement("img-gravatar")]
    public class GravatarTagHelper : TagHelper
    {
        [HtmlAttributeName("email")]
        public string Email { get; set; }

        [HtmlAttributeName("alt")]
        public string Alt { get; set; }

        [HtmlAttributeName("class")]
        public string Class { get; set; }

        [HtmlAttributeName("size")]
        public int Size { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (!string.IsNullOrWhiteSpace(Email))
            {
                var hash = Md5HashHelper.GetHash(Email);

                output.TagName = "img";
                if (!string.IsNullOrWhiteSpace(Class))
                {
                    output.Attributes.Add("class", Class); 
                }

                if (!string.IsNullOrWhiteSpace(Alt))
                {
                    output.Attributes.Add("alt", Alt);
                }
                
                output.Attributes.Add("src", GetAvatarUrl(hash, Size));
                output.TagMode = TagMode.SelfClosing;
            } 
        }

        private static string GetAvatarUrl(string hash, int size)
        {
            var sizeArg = size > 0 ? $"?s={size}" : "";

            return $"https://www.gravatar.com/avatar/{hash}{sizeArg}";
        }
 
استفاده از TagHepler  با مقدار  Email فرد در View مورد نظر
 <img-gravatar email="@Model.Email" class="img-thumbnail" size="150" />

مطالب
چگونگی دسترسی به فیلد و خاصیت غیر عمومی
یک از ابتدایی‌ترین مواردی که در یادگیری دات نت آموزش داده می‌شود مباحث مربوط به کپسوله سازی است. برای مثال فیلد‌ها و خواص Private که به صورت خصوصی هستند یا Protected هستند از خارج کلاس قابل دسترسی نیستند. برای دسترسی به این کلاس‌ها باید از خواص یا متدهای عمومی استفاده کرد.
public class Book
    {
        private int code = 10;        

        public int GetCode()
        {
            return code;
        }
    }
یا فیلدها و خواصی که به صورت فقط خواندنی هستند،(RealOnly) امکان تغییر مقدار برای اون‌ها وجود ندارد. برای مثال کد پایین کامپایل نخواهد شد.
public class Book
    {
        private readonly int code = 10;        

        public int GetCode()
        {
            return code = 20;
        }        
    }
اما در دات نت با استفاده از Reflection‌ها می‌تونیم تمام قوانین بالا رو نادیده بگیریم. یعنی می‌تونیم هم به خواص و فیلد‌های غیر عمومی کلاس دسترسی پیدا کنیم و هم می‌تونیم مقدار فیلدهای فقط خواندنی رو تغییر بدیم. به مثال‌های زیر دقت کنید.
#مثال اول
using System.Reflection;

 public class Book
 {
        private int code = 10;
 }

 public class Program
 {
        static void Main( string[] args )
        {
            Book book = new Book();
            var codeField = book.GetType().GetField( "code", BindingFlags.NonPublic | BindingFlags.Instance );
            codeField.SetValue( book, 20 );
            var value = codeField.GetValue( book );
        }
    }
ابتدا یک کلاس که دارای یک متغیر به نام کد است ساخته ایم که مقدار 10 را دارد. فیلد به صورت private  است. بعد از اجرا به راحتی مقدار Code را به دست می‌آوریم.


حتی امکان تغییر مقدار فیلد private هم امکان پذیر است.

#مثال دوم.
در این مثال قصد داریم مقدار یک فیلد، از نوع فقط خواندنی رو تغییر دهیم.
using System.Reflection;

 public class Book
 {
        private readonly int code = 10;
 }

 public class Program
 {
        static void Main( string[] args )
        {
            Book book = new Book();
            var codeField = book.GetType().GetField( "code", BindingFlags.NonPublic | BindingFlags.Instance );
            codeField.SetValue( book, 50);
            var value = codeField.GetValue( book );
        }
    }
بعد از اجرا مقدار متغیر code به 50 تغییر می‌یابد.

مطالب تکمیلی