مطالب
MongoDb در سی شارپ (بخش هفتم)
در اینجا قصد داریم که دیتاهای استاتیک و دیتاهای پویا را بررسی کنیم. همانطور که میدانید مونگو تنها خواصی را که در کلاس وجود دارند ذخیره میکند و همان‌ها را هم در برگشت به کلاس انتساب میدهد. ولی ممکن است برای بعضی از اسناد هر بار فیلدهایی را تعریف کنیم که در کلاس اصلی پراپرتی برای آن وجود ندارد. فیلدهایی که ممکن است در زمان اجرا آن‌ها را بشناسیم. برای این کار دو روش متفاوت توسط تیم فنی مونگو پیشنهاد شده است.
اولین روش این است که یک پراپرتی را مثلا به عنوان متادیتا به کلاس اضافه و در قالب کلید و مقدار آن‌ها را مقدار دهی کنیم:
public class Book:Entity
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
        public int Price { get; set; }
        public List<Author> Authors { get; set; }
        public BsonDocument ExtraFields { get; set; }
        public Language Language { get; set; }
        public ObjectId Image { get; set; }
        public int Year { get; set; }
        public DateTime LastStock { get; set; }

    }
 در مدل Book، یک فیلد را به نام ExtraField اضافه میکنیم و نوع آن را BsonDocument میگذاریم . آن را به شکل زیر مقدار دهی می‌کنیم:
     var book = new Book()
            {
      
                Title = "Gone With Wind",
                ISBN = "43442424",
                Price = 50000,
                Year = 1936,
                LastStock = DateTime.Now.AddDays(-13),
                Language = new Language()
                {
                    Name = "Persian"
                },
                Authors = new List<Author>()
               {
                   new Author()
                   {
                       Name = "Margaret Mitchell"
                   }
               },
                ExtraFields=new BsonDocument("Translator", "Ali Mahboobi")
            };
در اینجا ما یک فیلد را اضافه کرد‌ه‌ایم که نام آن Translator بوده و مقدارش را علی محبوبی گذاشتیم. اگر به سند ذخیره شده‌ی آن نگاهی بیندازیم می‌بینیم که این دیتا به شکل زیر ذخیره شده است:

همینطور که می‌بینید این فیلد جدید به عنوان یک شیء جدا یا یک سند توکار ذخیره شده‌است. ولی اگر میخواهید به عنوان یک فیلد، همانند دیگر فیلدها ذخیره شود، باید فیلد ExtraField را به ویژگی BsonExtraElement مزین کنید. پس مدل را به شکل زیر بازنویسی میکنیم:

public class Book:Entity
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
        public int Price { get; set; }
        public List<Author> Authors { get; set; }
        [BsonExtraElements]
        public BsonDocument ExtraFields { get; set; }
        public Language Language { get; set; }
        public ObjectId Image { get; set; }
        public int Year { get; set; }
        public DateTime LastStock { get; set; }

    }
حال اگر مقادیر ذخیره شده را بررسی کنیم، باید شکل زیر را ببینید:


الان translator همانند دیگر فیلدها به یک شکل نمایش داده میشود.
در این روش فقط تیم مونگو اخطار داده است که مراقب باشید قبلا فیلدی به این نام نبوده باشد تا بعدا دچار مشکل و تصادم شود.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 17 - بررسی فریم ورک Logging
نکته تکمیلی 
اگر در تنظیمات Serilog برنامه جهت لاگ زدن پیغام استثنائات این خط ()Enrich.WithExceptionDetails را اضافه کرده اید.  
using Serilog;
using Serilog.Exceptions;

ILogger logger = new LoggerConfiguration()
    .Enrich.WithExceptionDetails()   // This Line
    .WriteTo.RollingFile(
        new JsonFormatter(renderMessage: true), 
        @"C:\logs\log-{Date}.txt")    
    .CreateLogger();
در EntityFrameworkCore باید موارد دیگری را  نیز به تنظیمات اضافه کنید. در غیر این صورت در مواقع خاص باعث میشود کل جداول موجود در DbConext برنامه را Select زده، که باعث افت شدید برنامه و گاها از کار افتادن برنامه می‌شود.
 
مطالب دوره‌ها
معرفی پروژه NotifyPropertyWeaver
پس از معرفی مباحث IL Code Weaving و همچنین ارائه راه حلی در مورد «استفاده از AOP Interceptors برای حذف کدهای تکراری INotifyPropertyChanged در WPF» راه حل مشابهی به نام NotifyPropertyWeaver ارائه شده است که همان کار AOP Interceptors را انجام می‌دهد؛ اما بدون نیاز به تشکیل پروکسی و سربار اضافی. کار نهایی را توسط ویرایش اسمبلی و افزودن کدهای IL لازم انجام می‌دهد؛ البته بدون استفاده از PostSharp. این پروژه از کتابخانه سورس باز پایه‌ای به نام Fody استفاده می‌کند که جهت IL Code weaving طراحی شده است.
اگر به Wiki آن مراجعه نمائید، لیست افزونه‌های قابل توجهی را در مورد آن خواهید یافت که PropertyChanged تنها یکی از آن‌ها است.


پیشنیازها
الف) صفحه پروژه در GitHub
ب) دریافت از طریق نوگت


روش استفاده

پس از نصب بسته نوگت پروژه PropertyChanged.Fody
 PM> Install-Package PropertyChanged.Fody
کلاسی را که باید پس از کامپایل، پیاده سازی‌های خودکار OnPropertyChanged را شامل شود، با ویژگی ImplementPropertyChanged مزین کنید.
using PropertyChanged;

namespace AOP02
{
    [ImplementPropertyChanged]
    public class Person
    {
        public string Id { set; get; }
        public string Name { set; get; }
    }
}
و سپس پروژه را کامپایل نمائید. خروجی کنسول Build در VS.NET :
------ Build started: Project: AOP02, Configuration: Debug x86 ------
  Fody (version 1.13.6.1) Executing
  Finished Fody 287ms.
  AOP02 -> D:\Prog\AOP02\bin\Debug\AOP02.exe
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
اکنون اگر فایل اسمبلی نهایی پروژه را در برنامه ILSpy باز کنیم، چنین پیاده سازی را می‌توان شاهد بود:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace AOP02
{
    public class Person : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Id
        {
            [System.Runtime.CompilerServices.CompilerGenerated]
            get
            {
                return this.<Id>k__BackingField;
            }
            [System.Runtime.CompilerServices.CompilerGenerated]
            set
            {
              if (string.Equals(this.<Id>k__BackingField, value, System.StringComparison.Ordinal))
              {
                  return;
              }
              this.<Id>k__BackingField = value;
              this.OnPropertyChanged("Id");
            }
        }
        public string Name
        {
            [System.Runtime.CompilerServices.CompilerGenerated]
            get
            {
               return this.<Name>k__BackingField;
            }
            [System.Runtime.CompilerServices.CompilerGenerated]
            set
           {
             if (string.Equals(this.<Name>k__BackingField, value, System.StringComparison.Ordinal))
             {
                return;
             }
             this.<Name>k__BackingField = value;
             this.OnPropertyChanged("Name");
            }
        }
        public virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت اول
دقیقا این کاری بود که من قبلا انجام داده بودم ولی مشکلی که پیش می‌آمد این بود که مثلا اگر در یک ارسال بایستی 200 پیامک ارسال گردد قبل از اینکه ارسال این 200 پیامک به اتمام برسد زمان اجرا به پایان رسیده و تابع execute  مجددا فراخوانی می‌شود و از انجایی که هنوز وضعیت این رکورد در دیتابیس به ارسال شده تغییر پیدا نکرده مجددا این 200 پیامک ارسال می‌گردد. این مشکل حتی زمانی که از حلقه for هم استفاده نمی‌شود وجود دارد و در تعداد ارسال بالا به مشکل می‌خورد .
در زیر کدی که برای ارسال استفاده نموده ام را قرار دادم.
با تشکر از شما
namespace SchedulerDemo.Jobs
{
    using System;
    using System.Linq;
    using System.IO;
    using Quartz;
    using System.Collections.Generic;
    using System.Configuration;

    [PersistJobDataAfterExecution]
    [DisallowConcurrentExecution]
    public class SendJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
          
            using (var db = new DALModel.DALEntities())
            {
               
                byte status = (byte)AllEnums.Sms.Status.InProgress;
                var item = db.SentBoxes.Where(p => p.Status == status && p.IsDeleted==false && p.UserInfo.IsDeleted==false &&  p.HasTime == true && p.SendInTime == false && p.SendDateX <= DateTime.Now).OrderBy(p=>p.Id).FirstOrDefault();
                Cls_SMS.ClsSend sms_Batch = new Cls_SMS.ClsSend();

                if (item != null)
                {
                    decimal smsCount = 0;
                    if (item.UserInfo.CalculateType == Convert.ToByte(AllEnums.FinancialTransaction.CalculationUnit.Message))
                    {
                        smsCount = Convert.ToDecimal(Function.GetSmsCount(item.Price, item.UserId));
                    }
                    else
                    {
                        smsCount = Convert.ToDecimal(item.CorrectCount);
                    }
                    decimal adminCredit = Function.GetAdminCreditLink1000();
                    if (adminCredit != -1 && adminCredit >= smsCount)
                    {
                       
                        if ((item.UserInfo.Credit - (item.UserInfo.LowCredit)) >= item.Price)
                        {
                            item.SendInTime = true;
                            db.SaveChanges();
                            string numberList = item.NumberList;
                            int position = item.NumberList.LastIndexOf(',');
                            numberList = item.NumberList.Substring(0, position);
                            List<string> receivers_List = new List<string>();
                            receivers_List = (numberList).Split(',').ToList();
                            string[] ret2 = new string[2];
                            string[] DestAdd = new string[receivers_List.Count];
                            DestAdd = receivers_List.ToArray();
                            ret2 = sms_Batch.SendSMS_Batch(item.Message, DestAdd, item.UserInfoSenderNumber.AllNumber.Number, ConfigurationManager.AppSettings["SmsUserNameLink1000"], ConfigurationManager.AppSettings["SmsPasswordLink1000"], ConfigurationManager.AppSettings["SmsIPAddressLink1000"], ConfigurationManager.AppSettings["SmsCompanyLink1000"], false, item.Id);
                            var sentBoxUpdate = db.SentBoxes.FirstOrDefault(p => p.Id == item.Id);
                            sentBoxUpdate.Status = Convert.ToByte(AllEnums.Sms.Status.Send);
                            sentBoxUpdate.FinancialTransactionId = db.FinancialTransactions.Where(p => p.UserId == item.UserId).Max(p => p.Id);

                            if (ret2 != null)
                            {
                                sentBoxUpdate.RetValue0 = ret2[0];
                                sentBoxUpdate.RetValue1 = ret2[1];
                            }
                            db.SaveChanges();
                        }
                        else
                        {
                            byte statusFailedForAccount = (byte)AllEnums.Sms.Status.FailedForAccount;
                            item.SendInTime = true;
                            item.Status = statusFailedForAccount;
                            item.FailedCount = item.CorrectCount;
                            item.FailedList = item.NumberList;
                            db.SaveChanges();

                        }
                    }
                    else
                    {
                        byte statusFaildForError = (byte)AllEnums.Sms.Status.FaildForError;
                        item.SendInTime = true;
                        item.Status = statusFaildForError;
                        item.FailedCount = item.CorrectCount;
                        item.FailedList = item.NumberList;
                        db.SaveChanges();

                    }
                }


            }
        }
    }
}

namespace SchedulerDemo.Interfaces
{
    public interface IScheduleSend
    {
        void RunSendSms();
    }
}

namespace SchedulerDemo.Jobs
{
    using System;
    using Quartz;
    using Quartz.Impl;
    using SchedulerDemo.Interfaces;
    using SchedulerDemo.Jobs;

    public class SendSchedule : IScheduleSend
    {
        public void RunSendSms()
        {
            DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second);

            IJobDetail job = JobBuilder.Create<SendJob>()
                                       .WithIdentity("jobSendSmsInTime")
                                       .Build();

            ITrigger trigger = TriggerBuilder.Create()
                                             .WithIdentity("triggerSendSmsInTime")
                                             .StartAt(startTime)
                                             .WithSimpleSchedule(x => x.WithIntervalInMinutes(5).RepeatForever())
                                             .Build();

            ISchedulerFactory sf = new StdSchedulerFactory();
            IScheduler sc = sf.GetScheduler();
            sc.ScheduleJob(job, trigger);

            sc.Start();
        }
    }
}
مطالب
استفاده از Lambda Expression در پروژه های مبتنی بر WCF
نکته : آشنایی با مفاهیم پایه WCF برای فهم بهتر مفاهیم توصیه می‌شود.

امروزه استفاده از WCF در پروژه‌های SOA بسیار فراگیر شده است. کمتر کسی است که در مورد قدرت تکنولوژی WCF نشنیده باشد یا از این تکنولوژی در پروژه‌های خود استفاده نکرده باشد. WCF مدل برنامه نویسی یکپارچه مایکروسافت برای ساخت نرم‌افزارهای سرویس گرا است و برای توسعه دهندگان امکانی را فراهم می‌کند که راهکارهایی امن، و مبتنی بر تراکنش را تولید نمایند که قابلیت استفاده در بین پلتفرم‌های مختلف را دارند. قبل از WCF توسعه دهندگان پروژه‌های نرم افزاری برای تولید پروژه‌های توزیع شده باید شرایط موجود برای تولید و توسعه را در نظر می‌گرفتند. برای مثال اگر استفاده کننده از سرویس در داخل سازمان و بر پایه دات نت تهیه شده بود از .net remoting استفاده می‌کردند و اگر استفاده کننده سرویس از خارج سازمان یا مثلا بر پایه تکنولوژی J2EE بود از Web Service‌ها استفاده می‌شد. با ظهور WCF این تکنولوژی با هم تجمیع شدند(بهتر بگم تبدیل به یک تکنولوژی واحد شدند) و دیگر خبری از net remoting یا web service‌ها نیست.
  WCF با تمام قدرت و امکاناتی که داراست دارای نقاط ضعفی هم می‌باشد که البته این معایب (یا محدودیت) بیشتر جهت سازگار سازی سرویس‌های نوشته شده با سیستم‌ها و پروتکل‌های مختلف است.
برای انتقال داده‌ها از طریق WCF بین سیستم‌های مختلف باید داده‌های مورد نظر حتما سریالایز شوند که مثال هایی از این دست رو در همین سایت می‌تونید مطالعه کنید:
(^ )  و (^ ) و (^ )

با توجه به این که داده‌ها سریالایز می‌شوند، در نتیجه امکان انقال داده هایی که از نوع object  هستند در WCF وجود ندارد. بلکه نوع داده باید صراحتا ذکر شود و این نوع باید قابیلت سریالایز شدن را دارا باشد.برای مثال شما نمی‌تونید متدی داشته باشید که پارامتر ورودی آن از نوع delegate باشد یا کلاسی باشد که صفت [Serializable] در بالای اون قرار نداشته باشد یا کلاسی باشد که صفت DataContract برای خود کلاس و صفت DataMember برای خاصیت‌های اون تعریف نشده باشد. حالا سوال مهم این است اگر متدی داشته باشیم که پارامتر ورودی آن حتما باید از نوع delegate باشد چه باید کرد؟

برای تشریح بهتر مسئله یک مثال می‌زنم؟

سرویسی داریم برای اطلاعات کتاب ها. قصد داریم متدی بنوسیم که پارامتر ورودی آن از نوع Lambda Expression است تا Query مورد نظر کاربر از سمت کلاینت به سمت سرور دریافت کند و خروجی مورد نظر را با توجه به Query ورودی به کلاینت برگشت دهد.( متدی متداول در اکثر پروژه ها). به صورت زیر عمل می‌کنیم.

*ابتدا یک Blank Solution ایجاد کنید.

*یک ClassLibrary به نام Model ایجاد کنید و کلاسی به نام Book در آن بسازید .(همانطور که میبینید کلاس مورد نظر سریالایز شده است):

   [DataContract]
    public class Book
    {
        [DataMember]
        public int Code { get; set; }

        [DataMember]
        public string Title { get; set; }
    }
* یک WCF Service Application ایجاد کنید
یک Contract برای ارتباط بین سرور و کلاینت می‌سازیم:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.ServiceModel;

namespace WcfLambdaExpression
{
    [ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression );
    }
}
متد GetByExpression دارای پارامتر ورودی expression است که نوع آن نیز Lambda Expression  می‌باشد. حال یک سرویس ایجاد می‌کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace WcfLambdaExpression
{    
    public class BookService : IBookService
    {        
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook 
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

            return ListOfBook.AsQueryable().Where( expression );
        }       
    }
}
بعد از Build پروژه همه چیز سمت سرور آماده است. یک پروژه دیگر از نوع Console ایجاد کنید و از روش AddServiceReference سعی کنید که سرویس مورد نظر را به پروژه اضافه کنید. در هنگام Add Service Reference برای اینکه سرویس سمت سرور و کلاینت هر دو با یک مدل کار کنند باید از یک Reference assembly استفاده کنند و کافی است از قسمت Advanced گزینه Reuse types in referenced assemblies را تیک بزنید و assembly‌های مورد نظر را انتخاب کنید.( در این پروژه باید Model و System.Xml.Linq را انتخاب کنید)


به طور حتم با خطا روبرو خواهید شد. دلیل آن هم این است که امکان سریالایز کردن برای پارامتر ورودی expression میسر نیست.
خطای مربوطه به شکل زیر خواهد بود:
Type 'System.Linq.Expressions.Expression`1[System.Func`2[WcfLambdaExpression.Book,System.Boolean]]' cannot be serialized. 
Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  
If the type is a collection, consider marking it with the CollectionDataContractAttribute.  
See the Microsoft .NET Framework documentation for other supported types
حال چه باید کرد؟
روش‌های زیادی برای بر طرف کردن این محدودیت وجود دارد. اما در این پست روشی رو که خودم از اون استفاده می‌کنم رو براتون شرح می‌دهم.
در این روش باید از XElement استفاده شود که در فضای نام System.Linq.Xml قرار دارد. یعنی آرگومان ورودی سمت کلاینت باید به فرمت Xml سریالایز شود و سمت سرور دوباره دی سریالایز شده و تبدیل به یک Lambda Expression شود. اما سریالایز کردن Lambda Expression واقعا کاری سخت و طاقت فرساست . با توجه به این که در اکثر پروژه‌ها این متد‌ها به صورت Generic نوشته می‌شوند. برای حل این مسئله بعد از مدتی جستجو، کلاسی رو پیدا کردم که این کار رو برام انجام می‌داد. بعد از مطالعه دقیق و مشاهده روش کار کلاس، تغییرات مورد نظرم رو اعمال کردم و الان در اکثر پروژه هام دارم از این کلاس استفاده می‌کنم.
یک مثال از روش استفاده :
برای اینکه از این کلاس در هر دو پروژه (سرور و کلاینت) استفاده می‌کنیم باید یک Class Library جدید به نام Common بسازید و یک ارجاع از اون رو به هر دو پروژه سمت سرور و کلاینت بدید.
سرویس و Contract بالا رو به صورت زیر باز نویسی کنید.
[ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( XElement expression );
    }
و سرویس :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;

namespace WcfLambdaExpression
{
    public class BookService : IBookService
    {
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( XElement expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

             Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            return ListOfBook.AsQueryable().Where( serializer.Deserialize( expression ) as Expression<Func<Book, bool>> );
        }
    }
بعد از Build پروژه از روش Add Service Reference استفاده کنید و می‌بینید که بدون هیچ گونه مشکلی سرویس مورد نظر به پروژه Console اضافه شد. برای استفاده سمت کلاینت به صورت زیر عمل کنید.

using System;
using System.Linq.Expressions;
using TestExpression.MyBookService;

namespace TestExpression
{
    class Program
    {
        static void Main( string[] args )
        {
            BookServiceClient bookService = new BookServiceClient();

            Expression<Func<Book, bool>> expression = x => x.Code > 2 && x.Code < 5;

            Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            bookService.GetByExpression( serializer.Serialize( expression ) );
        }
    }
}
بعد از اجرای پروژه، در سمت سرور خروجی‌های زیر رو مشاهده می‌کنیم.

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

دریافت سورس کامل Expression-Serialization
مطالب
AOP و پردازش فراخوانی‌های تو در تو
هنگامی‌که از روش AOP استفاده می‌کنیم گاهی نیاز است متد تزیین‌شده را از متدی درون خود کلاس فراخوانی کنیم و می‌خواهیم aspectهای آن متد نیز فراخوانی شوند.

پیش‌نیازدوره‌ی AOP
(برای سادگی کار از تعریف attribute خودداری کردم. شما می‌توانید با توجه به آموزش، attributeهای دلخواه را به متدها بیافزایید).
Interface و کلاس پیاده‌سازی‌شده‌ی آن در لایه سرویس:
public interface IMyService 
{ 
  void foo();
  void bar();
}

public class MyService : IMyService
{
  public void foo()
  {
    Console.Write("foo");
    bar();
  }

  public void bar()
  {
    Console.Write("bar");
  }
}
نام متد در خروجی نوشته می‌شود. همچنین می‌خواهیم پیش از فراخوانی این متد‌ها، متنی در خروجی نوشته شود.

آماده‌سازی Interceptor
یک interceptor ساده که نام متد را در خروجی می‌نویسد.
//using Castle.DynamicProxy;

public class Interceptor : IInterceptor 
{
  public void Intercept(IInvocation invocation)
  {
    Console.WriteLine("Intercepted: " + invocation.Method.Name);
    invocation.Proceed();
  }
}

معرفی Interceptor به سیستم
همانند قبل:
//using System;
//using Castle.DynamicProxy;
//using StructureMap;

class Program
    {
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                var dynamicProxy = new ProxyGenerator();
                x.For<IMyService>()
                 .EnrichAllWith(myTypeInterface => 
                                     dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new Intercept()))
                 .Use<MyService>();
            });
 
            var myService = ObjectFactory.GetInstance<IMyService>();
            myService.foo();
        }
    }

انتظار ما این است که خروجی زیر تولید شود:
Intercepted foo
foo
Intercepted bar
bar
اما نتیجه این می‌شود که دلخواه ما نیست:
Intercepted foo
foo
bar

راه‌حل
برای حل این مشکل دو کار باید انجام داد:
1- متد تزیین‌شده باید virtual باشد.
public class MyService : IMyService
{
  public virtual void foo()
  {
    Console.Write("foo");
    bar();
  }

  public virtual void bar()
  {
    Console.Write("foo");
    bar();
  }
}
2- شیوه معرفی متد به سیستم باید به روش زیر باشد:
// جایگزین روش پیشین در متد Main
x.For<IMyService>()
                    .EnrichAllWith(myTypeInterface  => dynamicProxy.CreateClassProxy<MyService>(new Intercept()))                    
                    .Use<MyService>();

دلیل این مسئله به دو روش proxy برمی‌گردد که برای اطلاع بیشتر به مستندات پروژه Castle مراجعه کنید.
در این‌جا روش Inheritance-based به کار رفته است. در این روش، تنها متدهای virtual را می‌توان intercept کرد. در روش پیشین(Composition-based) برای همه‌ی متدها عملیات intercept انجام می‌شد (کلاس proxy پیاده‌سازی‌شده‌ی interface ما بود) که در این‌جا این‌گونه نیست و می‌تواند به سرعت برنامه کمک کند.
مطالب
امکانات جدید Entity Framework Designer در VS 2012
ویرایش 2012 ابزار Visual Studio جهت کار با EF امکانات جدیدی دارد که سعی دارم به طور خلاصه چند مورد آنرا توضیح دهم.

پشتیبانی از Enum
در نسخه‌های قبل از EF 5 پیشتیبانی توکاری از Enum‌ها وجود نداشت و برنامه نویس برای استفاده از آن‌ها مجبور بود از روش‌های دیگری استفاده کند؛ مانند ^ استفاده کند. در نسخه 5 این امکان براحتی قابل اعمال است. بدین منظور کافی است:
1- در Designer بر روی خصوصیتی که قصد تبدیل آنرا به enum دارید راست کلیک کرده و گزینه Convert to enum را انتخاب کنید.

2- در پنجره Add enum ابتدا در قسمت Underlying type نوع Int32 را انتخاب کنید سپس می‌توان نام enum و اعضای آنرا و تعیین کرد.

3- دکمه ok را کلیک کنید و سپس پروژه را Build کنید.
در ادامه به راحتی می‌توان از آن در برنامه به صورت زیر استفاده کرد:
var department = (from d in context.Departments
                        where d.Name == DepartmentNames.English
                        select d).FirstOrDefault();

تقسیم یک مدل در Entity Framework به چند دیاگرام 
هنگامی که یک دیاگرام جدید ایجاد می‌کنید این دیاگرام به طور پیش فرض با نام Diagram1 به پوشه Diagrams اضافه می‌شود. اطلاعات مربوط به ظاهر موجودیت مانند رنگ و شکل و روابط آنها نیز در فایلی با پسوند edmx.diagram قرار می‌گیرند. شما بصورت دستی نمی‌توانید این فایل را تغییر دهید چون اطلاعات آن دوباره توسط جنریتور رونویسی می‌شود. لذا تغییر در دیاگرام به روش دستی مورد اطمینان نیست!

 
حتما برای کسانیکه از EF ِDesigner استفاده می‌کنند پیش آمده که بخواهند مدل موجویت هایشان را بجای یک فایل در چند فایل قرار دهند. اینکار مخصوصا زمانی که تعداد موجودیت‌ها زیاد است لازم به نظر می‌رسد بعلاوه اینکه مدیریت و مرور موجودیت‌ها را در پروژهای بزرگ آسانتر می‌کند. خوشبختانه این امکان در Visual Studio 2012 ایجاد شده است. 
بدین منظور در دیاگرام برنامه موجودیت هایی را که می‌خواهید به دیاگرام دیگری انتقال دهید با کلیک و شیفت انتخاب کنید. سپس راست کلیک کرده و گزینه Move to new Diagram را انتخاب کنید. دیاگرام جدیدی ایجاد شده و موجودیت انتخاب شده به آنجا انتقال داده می‌شود. بهتر است موجودیت هایی که برای انتقال انتخاب کرده اید به صورت یک گروه مستقل باشند یعنی با موجودیت‌های دیگر رابطه نداشته باشند.
کار انتقال به یک دیاگرام جدید را می‌توان به کمک کلیدهای Ctrl+C و Ctrl+X نیز انجام داد، باید توجه داشت در حالتی که موجودیت را کپی می‌کنید، نام موجودیت جدید با اضافه شدن یک عدد از موجودیت موجود جدا می‌شود.

تغییر رنگ موجودیت
روش دیگری که جهت متمایز و جدا کردن موجودیت‌ها می‌توان از آن استفاده کرد، تغییر رنگ آنهاست. بدین منظور پس از انتخاب موجودیت‌ها می‌توانید با تغییر مقدار Fill Color در پنجره Properties رنگ موجودیت‌های انتخاب شده را تغییر داد.

 
مطالب دوره‌ها
تزریق خودکار وابستگی‌ها در ASP.NET Web API به همراه رها سازی خودکار منابع IDisposable
در انتهای مطلب « تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET MVC » اشاره‌ای کوتاه به روش DependencyResolver توکار Web API شد که این روش پس از بررسی‌های بیشتر (^ و ^) به دلیل ماهیت service locator بودن آن و همچنین از دست دادن Context جاری Web API، مردود اعلام شده و استفاده از IHttpControllerActivator توصیه می‌گردد. در ادامه این روش را توسط Structure map 3 پیاده سازی خواهیم کرد.

پیش نیازها
- شروع یک پروژه‌ی جدید وب با پشتیبانی از Web API
- نصب دو بسته‌ی نیوگت مرتبط با Structure map 3
 PM>install-package structuremap
PM>install-package structuremap.web

پیاده سازی IHttpControllerActivator توسط Structure map

using System;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using StructureMap;

namespace WebApiDISample.Core
{
    public class StructureMapHttpControllerActivator : IHttpControllerActivator
    {
        private readonly IContainer _container;
        public StructureMapHttpControllerActivator(IContainer container)
        {
            _container = container;
        }

        public IHttpController Create(
                HttpRequestMessage request,
                HttpControllerDescriptor controllerDescriptor,
                Type controllerType)
        {
            var nestedContainer = _container.GetNestedContainer();
            request.RegisterForDispose(nestedContainer);
            return (IHttpController)nestedContainer.GetInstance(controllerType);
        }
    }
}
در اینجا نحوه‌ی پیاده سازی IHttpControllerActivator را توسط StructureMap ملاحظه می‌کنید.
نکته‌ی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب می‌شود تا کلیه کلاس‌های IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.


معرفی StructureMapHttpControllerActivator به برنامه

فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http;
using System.Web.Http.Dispatcher;
using StructureMap;
using WebApiDISample.Core;
using WebApiDISample.Services;

namespace WebApiDISample
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // IoC Config
            ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>());

            var container = ObjectFactory.Container;
            GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container));


            // Web API routes
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}
 در ابتدا تنظیمات متداول کلاس‌ها و اینترفیس‌ها صورت می‌گیرد. سپس نحوه‌ی معرفی  StructureMapHttpControllerActivator را به GlobalConfiguration.Configuration.Services مخصوص Web API ملاحظه می‌کنید. این مورد سبب می‌شود تا به صورت خودکار کلیه وابستگی‌های مورد نیاز یک Web API Controller به آن تزریق شوند.


تهیه سرویسی برای آزمایش برنامه

namespace WebApiDISample.Services
{
    public interface IEmailsService
    {
        void SendEmail();
    }
}

using System;

namespace WebApiDISample.Services
{
    /// <summary>
    /// سرویسی که دارای قسمت دیسپوز نیز هست
    /// </summary>
    public class EmailsService : IEmailsService, IDisposable
    {
        private bool _disposed;

        ~EmailsService()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void SendEmail()
        {
            //todo: send email!
        }

        protected virtual void Dispose(bool disposeManagedResources)
        {
            if (_disposed) return;
            if (!disposeManagedResources) return;

            //todo: clean up resources here ...

            _disposed = true;
        }
    }
}
در اینجا یک سرویس ساده ارسال ایمیل را بدون پیاده سازی خاصی مشاهده می‌کنید.
نکته‌ی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شده‌است). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.


استفاده از سرویس تعریف شده در یک Web API Controller

using System.Web.Http;
using WebApiDISample.Services;

namespace WebApiDISample.Controllers
{
    public class ValuesController : ApiController
    {
        private readonly IEmailsService _emailsService;
        public ValuesController(IEmailsService emailsService)
        {
            _emailsService = emailsService;
        }

        // GET api/values/5
        public string Get(int id)
        {
            _emailsService.SendEmail();
            return "_emailsService.SendEmail(); called!";
        }
    }
}
در اینجا مثال ساده‌ای را از نحوه‌ی تزریق سرویس ارسال ایمیل را در ValuesController مشاهده می‌کنید.
تزریق وهله‌ی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت می‌گیرد.

فراخوانی متد Get آن‌را نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2>

@section scripts
{
    <script type="text/javascript">
        $(function () {
            $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) {
                alert(data);
            });
        });
    </script>
}
درون متد Get کنترلر، یک break point قرار دهید. همچنین داخل متد Dispose لایه سرویس نیز جهت بررسی بیشتر یک break point قرار دهید.
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهله‌ی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی می‌شود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
WebApiDISample.zip 
مطالب دوره‌ها
تبدیلگر تاریخ شمسی برای AutoMapper
فرض کنید مدل معادل با جدول بانک اطلاعاتی ما چنین ساختاری را دارد:
public class User
{
    public int Id { set; get; }
    public string Name { set; get; }
    public DateTime RegistrationDate { set; get; }
}
و ViewModel ایی که قرار است به کاربر نمایش داده شود این ساختار را دارد:
public class UserViewModel
{
    public int Id { set; get; }
    public string Name { set; get; }
    public string RegistrationDate { set; get; }
}
در اینجا می‌خواهیم حین تبدیل User به UserViewModel، تاریخ میلادی به صورت خودکار، تبدیل به یک رشته‌ی شمسی شود. برای مدیریت یک چنین سناریوهایی توسط AutoMapper، امکان نوشتن تبدیلگرهای سفارشی نیز پیش بینی شده‌است.


تبدیلگر سفارشی تاریخ میلادی به شمسی مخصوص AutoMapper

در ذیل یک تبدیلگر سفارشی مخصوص AutoMapper را با پیاده سازی اینترفیس ITypeConverter آن ملاحظه می‌کنید:
public class DateTimeToPersianDateTimeConverter : ITypeConverter<DateTime, string>
{
    private readonly string _separator;
    private readonly bool _includeHourMinute;
 
    public DateTimeToPersianDateTimeConverter(string separator = "/", bool includeHourMinute = true)
    {
        _separator = separator;
        _includeHourMinute = includeHourMinute;
    }
 
    public string Convert(ResolutionContext context)
    {
        var objDateTime = context.SourceValue;
        return objDateTime == null ? string.Empty : toShamsiDateTime((DateTime)context.SourceValue);
    }
 
    private string toShamsiDateTime(DateTime info)
    {
        var year = info.Year;
        var month = info.Month;
        var day = info.Day;
        var persianCalendar = new PersianCalendar();
        var pYear = persianCalendar.GetYear(new DateTime(year, month, day, new GregorianCalendar()));
        var pMonth = persianCalendar.GetMonth(new DateTime(year, month, day, new GregorianCalendar()));
        var pDay = persianCalendar.GetDayOfMonth(new DateTime(year, month, day, new GregorianCalendar()));
        return _includeHourMinute ?
            string.Format("{0}{1}{2}{1}{3} {4}:{5}", pYear, _separator, pMonth.ToString("00", CultureInfo.InvariantCulture), pDay.ToString("00", CultureInfo.InvariantCulture), info.Hour.ToString("00"), info.Minute.ToString("00"))
            : string.Format("{0}{1}{2}{1}{3}", pYear, _separator, pMonth.ToString("00", CultureInfo.InvariantCulture), pDay.ToString("00", CultureInfo.InvariantCulture));
    } 
}
ITypeConverter دو پارامتر جنریک را قبول می‌کند. پارامتر اول نوع ورودی و پارامتر دوم، نوع خروجی مورد انتظار است. در اینجا باید خروجی متد Convert را بر اساس آرگومان دوم ITypeConverter مشخص کرد. توسط ResolutionContext می‌توان به برای مثال context.SourceValue که معادل DateTime دریافتی است، دسترسی یافت. سپس این DateTime را بر اساس متد toShamsiDateTime تبدیل کرده و بازگشت می‌دهیم.


ثبت و معرفی تبدیلگرهای سفارشی AutoMapper

پس از تعریف یک تبدیلگر سفارشی AutoMapper، اکنون نیاز است آن‌را به AutoMapper معرفی کنیم:
public class TestProfile1 : Profile
{
    protected override void Configure()
    {
        // این تنظیم سراسری هست و به تمام خواص زمانی اعمال می‌شود
        this.CreateMap<DateTime, string>().ConvertUsing(new DateTimeToPersianDateTimeConverter()); 
        this.CreateMap<User, UserViewModel>();
     }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
جهت مدیریت بهتر نگاشت‌های AutoMapper ابتدا یک کلاس Profile را آغاز خواهیم کرد و سپس توسط متدهای CreateMap، کار معرفی نگاشت‌ها را آغاز می‌کنیم.
همانطور که مشاهده می‌کنید در اینجا دو نگاشت تعریف شده‌اند. یکی برای تبدیل User به UserViewModel و دیگری، معرفی نحوه‌ی نگاشت DateTime به string، توسط تبدیلگر سفارشی DateTimeToPersianDateTimeConverter است که به کمک متد الحاقی ConvertUsing صورت گرفته‌است.
باید دقت داشت که تنظیمات تبدیلگرهای سفارشی سراسری هستند و در کل برنامه و به تمام پروفایل‌ها اعمال می‌شوند.


بررسی خروجی تبدیلگر سفارشی تاریخ

اکنون کار استفاده از تنظیمات AutoMapper با ثبت پروفایل تعریف شده آغاز می‌شود:
Mapper.Initialize(cfg => // In Application_Start()
{
     cfg.AddProfile<TestProfile1>();
});
سپس نحوه‌ی استفاده از متد Mapper.Map همانند قبل خواهد بود:
var dbUser1 = new User
{
    Id = 1,
    Name = "Test",
    RegistrationDate = DateTime.Now.AddDays(-10)
};
 
var uiUser = new UserViewModel();

Mapper.Map(source: dbUser1, destination: uiUser);
در اینجا در حین کار تبدیل و نگاشت dbUser به uiUser، زمانیکه AutoMapper به هر خاصیت DateTime ایی می‌رسد، مقدار آن‌را با توجه به تبدیلگر سفارشی تاریخی که به آن معرفی کردیم، تبدیل به معادل رشته‌ای شمسی می‌کند.


نوشتن تبدیلگرهای غیر سراسری

همانطور که عنوان شد، معرفی تبدیلگرها به AutoMapper سراسری است و در کل برنامه اعمال می‌شود. اگر نیاز است فقط برای یک مدل خاص و یک خاصیت خاص آن تبدیلگر نوشته شود، باید نگاشت مورد نظر را به صورت ذیل تعریف کرد:
this.CreateMap<User, UserViewModel>()
             .ForMember(userViewModel => userViewModel.RegistrationDate,
                        opt => opt.ResolveUsing(src =>
                        {
                             var dt = src.RegistrationDate;
                             return dt.ToShortDateString();
                        }));
اینبار در همان کلاس پروفایل ابتدای بحث، نگاشت User به ViewModel آن با کمک متد ForMember، سفارشی سازی شده‌است. در اینجا عنوان شده‌است که اگر به خاصیت ویژه‌ی RegistrationDate رسیدی، مقدار آن‌را با توجه به فرمولی که مشخص شده، محاسبه کرده و بازگشت بده. این تنظیم خصوصی است و به کل برنامه اعمال نمی‌شود.


خصوصی سازی تبدیلگرها با تدارک موتورهای نگاشت اختصاصی

اگر می‌خواهید تنظیمات TestProfile1 به کل برنامه اعمال نشود، نیاز است یک MappingEngine جدید و مجزای از MappingEngine سراسری AutoMapper را ایجاد کرد:
var configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
configurationStore.AddProfile<TestProfile1>();
var mapper = new MappingEngine(configurationStore);
mapper.Map(source: dbUser1, destination: uiUser);
به صورت پیش فرض و در پشت صحنه، متد Mapper.Map از یک MappingEngine سراسری استفاده می‌کند. اما می‌توان در یک برنامه چندین MappingEngine مجزا داشت که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample02.zip
نظرات مطالب
راه اندازی StimulSoft Report در ASP.NET MVC
مانند مثال بالا می‌توانید به سطوح جزییات گزارش خود بیافزایید. به طور مثال مدل‌های زیر را در نظر بگیرید:
  public class CustomerServiceReport
  {
        public string ReportTitle { get; set; }
        public string ReportDate { get; set; }
        //...

        public List<CustomerRow> Customers { get; set; } = new List<CustomerRow>(); 
  }

 public class CustomerRow
 {
        public int CustomerId { get; set; }
        public string CustomerName { get; set; }
        public string Phone { get; set; }
        //...

        public List<ServiceRow> Services { get; set; } = new List<ServiceRow>();  
} public class ServiceRow { public int ServiceId { get; set; } public string ServiceName { get; set; } public int Count { get; set; } public long Price { get; set; } }
که در فایل mrt مربوط به گزارش به صورت زیر پیاده‌سازی می‌شود

در برنامه هم به صورت زیر دیتای مورد نیاز گزارش - به صورت نمونه - ایجاد می‌شود و در انتها با استفاده از RegBusinessObject و SynchronizeBusinessObjects به گزارش ثبت و ارسال می‌گردد.
var data = new CustomerServiceReport
{
    ReportDate = "1399/09/04",
    ReportTitle = "گزارش سرویس‌های مشتریان"
};

for (int i = 0; i < 5; i++)
{
    var customer = new CustomerRow
    {
        CustomerId = i + 1,
        CustomerName = $"مشتری شماره {i + 1}",
        Phone = "001122"
    };

    for (int j = 0; j < 3; j++)
    {
        var service = new ServiceRow
        {
            ServiceId = j + 1,
            ServiceName = $"سرویس شماره {j + 1}",
            Count = (j + 1) * 10,
            Price = (j + 1) * 10000
        };

        customer.Services.Add(service); //اضافه کردن سرویس به هر مشتری
    }

    data.Customers.Add(customer); // اضافه کردن مشتری به گزارش
}

var report = new StiReport();
report.RegBusinessObject("CustomerServiceReport", data);
report.Dictionary.SynchronizeBusinessObjects();