مطالب
ثبت لینک‌های غیرتکراری
ثبت لینک‌های مختلف در یک سیستم (مثلا قسمت به اشتراک گذاری لینک‌ها) در ابتدای کار شاید ساده به نظر برسد؛ خوب، هر صفحه‌ای که یک آدرس منحصربفرد بیشتر ندارد. ما هش این لینک را محاسبه می‌کنیم و بعد روی این هش، یک کلید منحصربفرد را تعریف خواهیم کرد تا دیگر رکوردی تکراری ثبت نشود. همچنین چون این هش نیز طول کوتاهی دارد، جستجوی آن بسیار سریع خواهد بود. واقعیت این است که خیر! این روش ناکارآمدترین حالت پردازش لینک‌های مختلف است.
برای مثال لینک‌های http://www.site.com و http://www.site.com/index.htm دو هش متفاوت را تولید می‌کنند اما در عمل یکی هستند. نمونه‌ی دیگر، لینک‌های http://www.site.com/index.htm و http://www.site.com/index.htm#section1 هستند که فقط اصطلاحا در یک fragment با هم تفاوت دارند و از این دست لینک‌هایی که باید در حین ثبت یکی درنظر گرفته شوند، زیاد هستند و اگر علاقمند به مرور آن‌ها هستید، می‌توانید به صفحه‌ی URL Normalization در ویکی‌پدیا مراجعه کنید.
اگر نکات این صفحه را تبدیل به یک کلاس کمکی کنیم، به کلاس ذیل خواهیم رسید:
using System;
using System.Web;

namespace OPMLCleaner
{
    public static class UrlNormalization
    {
        public static bool AreTheSameUrls(this string url1, string url2)
        {
            url1 = url1.NormalizeUrl();
            url2 = url2.NormalizeUrl();
            return url1.Equals(url2);
        }

        public static bool AreTheSameUrls(this Uri uri1, Uri uri2)
        {
            var url1 = uri1.NormalizeUrl();
            var url2 = uri2.NormalizeUrl();
            return url1.Equals(url2);
        }

        public static string[] DefaultDirectoryIndexes = new[]
            {
                "default.asp",
                "default.aspx",
                "index.htm",
                "index.html",
                "index.php"
            };

        public static string NormalizeUrl(this Uri uri)
        {
            var url = urlToLower(uri);
            url = limitProtocols(url);
            url = removeDefaultDirectoryIndexes(url);
            url = removeTheFragment(url);
            url = removeDuplicateSlashes(url);
            url = addWww(url);
            url = removeFeedburnerPart(url);
            return removeTrailingSlashAndEmptyQuery(url);
        }

        public static string NormalizeUrl(this string url)
        {
            return NormalizeUrl(new Uri(url));
        }

        private static string removeFeedburnerPart(string url)
        {
            var idx = url.IndexOf("utm_source=", StringComparison.Ordinal);
            return idx == -1 ? url : url.Substring(0, idx - 1);
        }

        private static string addWww(string url)
        {
            if (new Uri(url).Host.Split('.').Length == 2 && !url.Contains("://www."))
            {
                return url.Replace("://", "://www.");
            }
            return url;
        }

        private static string removeDuplicateSlashes(string url)
        {
            var path = new Uri(url).AbsolutePath;
            return path.Contains("//") ? url.Replace(path, path.Replace("//", "/")) : url;
        }

        private static string limitProtocols(string url)
        {
            return new Uri(url).Scheme == "https" ? url.Replace("https://", "http://") : url;
        }

        private static string removeTheFragment(string url)
        {
            var fragment = new Uri(url).Fragment;
            return string.IsNullOrWhiteSpace(fragment) ? url : url.Replace(fragment, string.Empty);
        }

        private static string urlToLower(Uri uri)
        {
            return HttpUtility.UrlDecode(uri.AbsoluteUri.ToLowerInvariant());
        }

        private static string removeTrailingSlashAndEmptyQuery(string url)
        {
            return url
                    .TrimEnd(new[] { '?' })
                    .TrimEnd(new[] { '/' });
        }

        private static string removeDefaultDirectoryIndexes(string url)
        {
            foreach (var index in DefaultDirectoryIndexes)
            {
                if (url.EndsWith(index))
                {
                    url = url.TrimEnd(index.ToCharArray());
                    break;
                }
            }
            return url;
        }
    }
}
از این روش برای تمیز کردن و حذف فیدهای تکراری در فایل‌های OPML تهیه شده نیز می‌شود استفاده کرد. عموما فیدخوان‌های نه‌چندان با سابقه، نکات یاد شده در این مطلب را رعایت نمی‌کنند و به سادگی می‌شود در این سیستم‌ها، فیدهای تکراری زیادی را ثبت کرد.
برای مثال اگر یک فایل OPML چنین ساختار XML ایی را داشته باشد:
<?xml version="1.0" encoding="utf-8"?>
<opml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0">
  <body>
    <outline text="آی تی ایرانی">
    <outline type="rss" 
                           text="‫فید کلی آخرین نظرات، مطالب، اشتراک‌ها و پروژه‌های .NET Tips" 
                           title="‫فید کلی آخرین نظرات، مطالب، اشتراک‌ها و پروژه‌های .NET Tips" 
                           xmlUrl="https://www.dntips.ir/Feed/LatestChanges"
                           htmlUrl="https://www.dntips.ir/" />
    </outline> 
  </body>
</opml>
هر outline آن‌را به کلاس زیر می‌توان نگاشت کرد:
using System.Xml.Serialization;

namespace OPMLCleaner
{
    [XmlType(TypeName="outline")]
    public class Opml
    {
        [XmlAttribute(AttributeName="text")]
        public string Text { get; set; }

        [XmlAttribute(AttributeName = "title")]
        public string Title { get; set; }

        [XmlAttribute(AttributeName = "type")]
        public string Type { get; set; }

        [XmlAttribute(AttributeName = "xmlUrl")]
        public string XmlUrl { get; set; }

        [XmlAttribute(AttributeName = "htmlUrl")]
        public string HtmlUrl { get; set; }
    }
}
برای اینکار فقط کافی است از LINQ to XML به نحو ذیل استفاده کنیم:
var document = XDocument.Load("it-92-03-01.opml");
var results = (from node in document.Descendants("outline")
                           where node.Attribute("htmlUrl") != null && node.Parent.Attribute("text") != null 
                            && node.Parent.Attribute("text").Value == "آی تی ایرانی"
                           select new Opml
                           {
                               HtmlUrl = (string)node.Attribute("htmlUrl"),
                               Text = (string)node.Attribute("text"),
                               Title = (string)node.Attribute("title"),
                               Type = (string)node.Attribute("type"),
                               XmlUrl = (string)node.Attribute("xmlUrl")
                           }).ToList();
در این حالت لیست کلیه فیدهای یک گروه را چه تکراری و غیرتکراری، دریافت خواهیم کرد. برای حذف موارد تکراری نیاز است از متد Distinct استفاده شود. به همین جهت باید کلاس ذیل را نیز تدارک دید:
using System.Collections.Generic;

namespace OPMLCleaner
{
    public class OpmlCompare : EqualityComparer<Opml>
    {
        public override bool Equals(Opml x, Opml y)
        {
            return UrlNormalization.AreTheSameUrls(x.HtmlUrl, y.HtmlUrl);                
        }

        public override int GetHashCode(Opml obj)
        {
            return obj.HtmlUrl.GetHashCode();
        }
    }
}
اکنون با کمک کلاس OpmlCompare فوق که از کلاس UrlNormalization برای تشخیص لینک‌های تکراری استفاده می‌کند، می‌توان به لیست بهتر و متعادل‌تری رسید:
 var distinctResults = results.Distinct(new OpmlCompare()).ToList();
 
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت ششم - دخالت در مراحل وهله سازی اشیاء توسط IoC Container
روش متداول کار با تزریق وابستگی‌های برنامه‌های مبتنی بر NET Core.، عموما با ثبت و معرفی یک سرویس به صورت زیر، توسط متدهای AddTransient، AddSingleton و AddScoped است:
public class Startup 
{ 
    public void ConfigureServices(IServiceCollection services) 
    { 
        // ... 
         
        services.AddTransient<ICustomerService, DefaultCustomerService>(); 
         
        // ... 
    } 
}
و سپس استفاده‌ی از این سرویس، با تزریق آن در سازنده‌ی یک کنترلر که نمونه‌های بیشتری از آن‌را در قسمت چهارم بررسی کردیم:
public class SupportController 
{ 
    // DefaultCustomerService will be injected here: 
    public SupportController(ICustomerService customerService) 
    { 
        // ... 
    } 
}
در اینجا کار وهله سازی DefaultCustomerService به صورت خودکار و راسا توسط IoC Container توکار برنامه صورت می‌گیرد و ما هیچگونه دخالتی را در آن نداریم. اما اگر در این بین نیاز باشد پس از وهله سازی DefaultCustomerService، یک خاصیت آن نیز بر اساس شرایط جاری مقدار دهی شود و حاصل نهایی در اختیار SupportController فوق قرار گیرد چه باید کرد؟
برای سفارشی سازی مراحل وهله سازی اشیاء توسط IoC Container توکار برنامه و امکان دخالت در آن، قابلیتی تحت عنوان «factory registration» نیز پیش بینی شده‌است که در ادامه آن‌را بررسی می‌کنیم.


Factory Registration چیست؟

اگر در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions و فضای نام Microsoft.Extensions.DependencyInjection آن به کلاس ServiceCollectionServiceExtensions که متدهای الحاقی مانند AddScoped را ارائه می‌کند، بیشتر دقت کنیم، تک تک این متدها امضاهای دیگری را نیز دارند:
namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceCollectionServiceExtensions
    {
        public static IServiceCollection AddScoped<TService>(
     this IServiceCollection services) where TService : class;
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType, Type implementationType);
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType, 
 Func<IServiceProvider, object> implementationFactory);
        public static IServiceCollection AddScoped<TService, TImplementation>(this IServiceCollection services)
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType);
        public static IServiceCollection AddScoped<TService>(
     this IServiceCollection services, 
 Func<IServiceProvider, TService> implementationFactory) where TService : class;
        public static IServiceCollection AddScoped<TService, TImplementation>(
     this IServiceCollection services, 
 Func<IServiceProvider, TImplementation> implementationFactory)
// ...
    }
}
همانطور که ملاحظه می‌کنید، امضای تعدادی از این overloadها، دارای پارامترهایی از نوع Func نیز هست و هدف آن‌ها فراهم آوردن روشی برای سفارشی سازی مراحل وهله سازی سرویسی‌های بازگشتی از طریق سیستم تزریق وابستگی‌های برنامه است. توسط این پارامتر، پیش از وهله سازی سرویس درخواستی، IServiceProvider جاری یا همان root container را در اختیار شما قرار می‌دهد (اطلاعات بیشتر در مورد IServiceProvider را در قسمت دوم بررسی کردیم) و توسط آن می‌توان ابتدا وهله‌ای از سرویس یا سرویس‌های خاصی را دریافت کرد و پس از ترکیب و سفارشی سازی آن‌ها، در آخر یک object را بازگشت داد که در نهایت به عنوان وهله‌ی اصلی این سرویس درخواستی، در سراسر برنامه مورد استفاده قرار می‌گیرد. در ادامه با مثال‌هایی، کاربردهای این پارامتر از نوع Func، یا Implementation Factory را بررسی می‌کنیم.


مثال 1 : تزریق وابستگی‌ها در حالتیکه کلاس سرویس مدنظر دارای تعدادی پارامتر ثابت است

IoC Container توکار برنامه‌های NET Core.، به صورت خودکار وابستگی‌های تزریق شده‌ی در سازنده‌های سرویس‌های مختلف را تا هر چند سطح ممکن، به صورت خودکار وهله سازی می‌کند؛ به شرطی‌که این وابستگی‌های تزریق شده نیز خودشان سرویس بوده باشند و در تنظیمات ابتدایی آن ثبت و معرفی شده باشند. به عبارتی زمانیکه با سیستم تزریق وابستگی‌ها کار می‌کنیم، مهم نیست که نگران مقدار دهی پارامترهای سازنده‌ی تزریق شده‌ی در سازنده‌های سرویسی خاص باشیم. اما ... برای نمونه سرویس زیر را که یک رشته را در سازنده‌ی خود دریافت می‌کند درنظر بگیرید:
namespace CoreIocServices
{
    public interface IParameterizedService
    {
        string GetConstructorParameter();
    }

    public class ParameterizedService : IParameterizedService
    {
        private readonly string _connectionString;

        public ParameterizedService(string connectionString)
        {
            _connectionString = connectionString;
        }

        public string GetConstructorParameter()
        {
            return _connectionString;
        }
    }
}
اینبار دیگر نمی‌توان این سرویس را از طریق متداول زیر ثبت و معرفی کرد:
services.AddTransient<IParameterizedService, ParameterizedService>();
چون IoC Container نمی‌داند که چگونه و از کجا باید پارامتر رشته‌ای درخواستی در سازنده‌ی کلاس ParameterizedService را تامین کند. همچنین ثبت سرویس‌ها نیز در کلاس ServiceCollectionServiceExtensions معرفی شده‌ی در ابتدای بحث، به قید «where TService : class» محدود شده‌است. اینجا است که روش factory registration به کمک ما خواهد آمد تا بتوانیم مراحل وهله سازی این سرویس را سفارشی سازی کنیم:
services.AddTransient<IParameterizedService>(serviceProvider =>
{
   return new ParameterizedService("some value ....");
});
البته چون بدنه‌ی این Func، صرفا از یک return تشکیل شده‌است، معادل ساده شده‌ی زیر را هم می‌تواند داشته باشد:
services.AddTransient<IParameterizedService>(serviceProvider => new ParameterizedService("some value ...."));
اینبار در سراسر برنامه اگر سرویس IParameterizedService درخواست شود، وهله‌ای از کلاس ParameterizedService را با پارامتر سازنده‌ی "some value ...."، دریافت خواهد کرد.

در اینجا چون serviceProvider نیز در اختیار ما است، حتی می‌توان این مقدار را از سرویسی دیگر دریافت کرد و سپس مورد استفاده قرار داد:
services.AddTransient<IParameterizedService>(serviceProvider =>
{
   var config = serviceProvider.GetRequiredService<ITestService>().GetConfigValue();
   return new ParameterizedService(config);
});

نمونه‌ی دیگری از این دست، کار با IUrlHelper توکار ASP.NET Core است. این سرویس برای اینکه پاسخ درستی را ارائه دهد، نیاز به ActionContext جاری را دارد تا بتواند از طریق آن به تمام جزئیات اکشن متد یک کنترلر و درخواست رسیده دسترسی داشته باشد. در این حالت برای ساده سازی کار با آن، بهتر است تامین وابستگی‌های لحظه‌ای این سرویس را با سفارشی سازی نحوه‌ی وهله سازی آن، انجام دهیم، تا اینکه این قطعه کد تکراری را در هر جائیکه به IUrlHelper نیاز است، تکرار کنیم:
services.AddScoped<IUrlHelper>(serviceProvider =>
{
   var actionContext = serviceProvider.GetRequiredService<IActionContextAccessor>().ActionContext;
   var urlHelperFactory = serviceProvider.GetRequiredService<IUrlHelperFactory>();
   return urlHelperFactory.GetUrlHelper(actionContext);
});
اکنون اگر IUrlHelper را به سازنده‌ی یک کنترلر تزریق کنیم، دیگر نیازی به سه سطر نوشته‌ی تامین factory و action context آن نخواهد بود.


مثال 2: وهله سازی در صورت نیاز به وابستگی‌های یک سرویس، به کمک Lazy loading

فرض کنید دو سرویس را در سازنده‌ی سرویس دیگری تزریق کرده‌اید:
namespace Services
{
    public class OrderHandler : IOrderHandler
    {
        private readonly IAccounting _accounting;
        private readonly ISales _sales;
        public OrderHandler(IAccounting accounting, ISales sales)
        {
بعد در این کلاس، در یک متد، از سرویس accounting استفاده می‌شود و در متدی دیگر از سرویس sales. یعنی هرچند در زمان وهله سازی شیء OrderHandler هر دو وابستگی تزریق شده‌ی در سازنده‌ی آن نیز وهله سازی خواهند شد، اما در بسیاری از شرایط، بسته به متد مورد استفاده، فقط از یکی از آن‌ها استفاده می‌کنیم. اکنون این سؤال مطرح می‌شود که آیا می‌توان سربار وهله سازی تمام سازنده‌های این کلاس را به زمان استفاده‌ی از آن‌ها منتقل کرد؟ یعنی سرویس accounting تزریق شده فقط زمانی وهله سازی شود که واقعا قرار است از آن استفاده کنیم.
روش انجام یک چنین کارهایی با استفاده از کلاس Lazy اضافه شده‌ی به NET 4x. قابل انجام است:
   public class OrderHandlerLazy : IOrderHandler
    {
        public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales)
        {
 و برای معرفی آن در اینجا می‌توان از روش factory registration استفاده کرد:
services.AddTransient<IOrderHandler, OrderHandlerLazy>();
services.AddTransient<IAccounting, Accounting>()
            .AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>()));
services.AddTransient<ISales, Sales>()
           .AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- در اینجا در ابتدا تمام سرویس‌ها (حتی آن‌هایی که قرار است به صورت Lazy استفاده شوند) یکبار به صورت متداولی معرفی می‌شوند.
- سپس سرویس‌هایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.

پس از این تنظیمات، اگر سرویس IOrderHandler را از طریق سیستم تزریق وابستگی‌ها درخواست کنید، وابستگی‌های تزریق شده‌ی در سازنده‌ی آن فقط زمانی و در محلی وهله سازی می‌شوند که از طریق خاصیت Value شیء Lazy آن‌ها مورد استفاده قرار گرفته شده باشند.
مثال کامل IOrderHandler را از فایل پیوستی انتهای مطلب می‌توانید دریافت. اگر آن‌را اجرا کنید (برنامه‌ی کنسول آن‌را)، در خروجی آن، فقط اجرا شدن سازنده‌ی سرویسی را مشاهده می‌کنید که مورد استفاده قرار گرفته و نه وابستگی دومی که تزریق شده، اما استفاده نشده‌است.


مثال 3: چگونه بجای اینترفیس‌ها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگی‌ها درخواست کنیم؟

فرض کنید سرویسی را به صورت زیر به سیستم تزریق وابستگی‌ها معرفی کرده‌اید:
services.AddTransient<IMyDisposableService, MyDisposableService>();
در ادامه اگر سرویس IMyDisposableService را از این سیستم درخواست کنیم، برنامه بدون مشکل اجرا می‌شود؛ اما اگر خود MyDisposableService را تزریق کنیم چطور؟
public class AnotherController 
{ 
    public AnotherController(MyDisposableService customerService) 
    { 
        // ... 
    } 
}
در این حالت برنامه با استثنای زیر متوقف می‌شود و عنوان می‌کند که نمی‌داند چگونه باید این وابستگی تزریق شده را تامین کند:
An unhandled exception occurred while processing the request. 
InvalidOperationException: Unable to resolve service for type ‘MyDisposableService’ while attempting to activate ‘AnotherController’. 
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
این مورد را نیز می‌توان توسط factory registration به نحو زیر مدیریت کرد:
services.AddTransient<IMyDisposableService, MyDisposableService>();
services.AddTransient<MyDisposableService>(serviceProvider =>
serviceProvider.GetRequiredService<IMyDisposableService>() as MyDisposableService);
هر زمانیکه وهله‌ای از کلاس MyDisposableService درخواست شود، وهله‌ای از سرویس IMyDisposableService را بازگشت می‌دهیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-06.zip
مطالب
فراخوانی یک متداز یک کنترل WPF از XAML
در بعضی مواقع نیاز است که یک متد از یک کنترل درون XAML فراخوانی شود. برای مثال لازم است یکی از متد‌های یک کنترل در یک استایل فراخوانی شود. یکی از روش‌های انجام این کار استفاده از خصوصیت‌های پیوست شده( AttachedPropery) است. شیوه‌ی کار به این صورت است که یک خصوصیت از نوع Bool ایجاد می‌کنیم. هنگامیکه مقدار این خصوصیت تغییر کند یک رویه فراخوانی می‌شود که کار فراخوانی متد مورد نظر را انجام میدهد:
public class SelectAllBehavior
    {
        public static bool GetSelectAll(TextBoxBase target)
        {
            return (bool)target.GetValue(SelectAllProperty);
        }

        public static void SetSelectAll(TextBoxBase target, bool value)
        {
            target.SetValue(SelectAllProperty, value);
        }

        public static readonly DependencyProperty SelectAllProperty = DependencyProperty.RegisterAttached("SelectAll", typeof(bool), typeof(SelectAllBehavior), new UIPropertyMetadata(false, OnSelectAllPropertyChanged));

        static void OnSelectAllPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((TextBoxBase)o).SelectAll();
        }
    }
 برای استفاده از کلاس فوق درون یک استایل باید به شکل زیر عمل کرد:
<Style TargetType="{x:Type TextBoxBase}" >
    <Style.Triggers>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="x: SelectAllBehavior.SelectAll" Value="True"/>
        </Trigger>
    </Style.Triggers>
</Style>

در تکه کد بالا ،هنگامی که خصوصیت IsFocused مربوط به کنترل TextBox برابر True می‌شود، یعنی Focus روی TextBox قرار می‌گیرد،مقدار خصوصیت پیوست شده نیز برابر True می‌شود که همانطور که گفته شد باعث فراخوانی OnSelectAllPropertyChanged می‌شود.

تا اینجا فراخوانی یک متد از کنترل از طریق استایل توضیح داده شد، همانطور که در عنوان مطلب آورده شده است. اما اگر بخواهید مثال فوق را به درستی اجرا کنید یعنی هنگام کلیک روی TextBox متن درون آن انتخاب شود، نیاز به اضافه کردن کدهای دیگری وجود دارد. چرا که به صورت پیش فرض زمانی که MouseLeftButtonUp اجرا می‌شود در صورتی که حالت متن به صورت انتخاب شده باشد، از حالت انتخاب خارج میشود. این بار برای اینکار از   خصوصیت‌های پیوست شده( AttachedPropery)  برای کنترل رویداد PreviewMouseLeftButtonDown استفاده میکنیم و هنگام فشرده شدن کلید سمت چپ موس رویدادهای Click و بقیه LeftMouseButtonDown‌ها را غیرفعال می‌کنیم.

 public class PreviewMouseLeftButtonDownBehavior
{
    public static readonly DependencyProperty PreviewMouseLeftButtonDownProperty = DependencyProperty.RegisterAttached("PreviewMouseLeftButtonDown"
                  , typeof(bool?)
                  , typeof(PreviewMouseLeftButtonDownBehavior)
                  , new UIPropertyMetadata(null, OnPreviewMouseLeftButtonDown)
                  );

    public static void SetPreviewMouseLeftButtonDown(DependencyObject target, bool? value)
    {
        target.SetValue(PreviewMouseLeftButtonDownProperty, value);
    }
    public static object GetPreviewMouseLeftButtonDown(DependencyObject target)
    {
        return target.GetValue(PreviewMouseLeftButtonDownProperty);
    }

    public static void OnPreviewMouseLeftButtonDown(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var control = obj as Control;
        if (control == null)
            return;
        if (e.NewValue != null && e.OldValue == null)
            control.PreviewMouseLeftButtonDown += control_PreviewMouseLeftButtonUp;
        else if ((e.NewValue == null) && (e.OldValue != null))
        {
            control.PreviewMouseLeftButtonDown -= control_PreviewMouseLeftButtonUp;
        }
    }
    static void control_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        var tb = (sender as TextBox);
        if (tb != null)
        {
            if (!tb.IsKeyboardFocusWithin)
            {
                e.Handled = true;
                tb.Focus();
            }
        }
    }
}

و استایل را به صورت زیر تغییر میدهیم:

<Style TargetType="{x:Type TextBoxBase}" >
    <Setter Property="x:PreviewMouseLeftButtonDownBehavior.IsPreviewMouseLeftButtonDown" Value="True"/>
    <Style.Triggers>
        <Trigger Property="IsKeyboardFocusWithin" Value="True">
            <Setter Property="InputLanguageManager.InputLanguage" Value="fa-ir" />
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="xamlServices:SelectAllTextBoxBehavior.SelectAll" Value="True"/>
        </Trigger>
    </Style.Triggers>
</Style>

در این مثال با Focus رو هر TextBox که استایل فوق را به کار گرفته باشد، متن در حالت انتخاب شده قرار میگیرد. (البته این مشروط به این است که نیاز نباشد رویداد PreviewMouseLeftButtonDown  کنترل مورد نظر درون برنامه مقیدسازی(Bind) شود.)

مطالب
دریافت زمانبندی شده به روز رسانی‌های آنتی ویروس Symantec به کمک کتابخانه‌های Quartz.NET و Html Agility Pack
در این رابطه آقای راد در دو قسمت به صورت مختصر و مفید این کتابخانه قدرتمند رو همراه با ارائه چندین مثال کاربردی معرفی کردند:
قسمت اول
قسمت دوم
در تکمیل قسمت‌های فوق بنده می‌خوام مثالی رو در این رابطه براتون بذارم، هدف از ارائه این مثال اتوماتیک سازی یک فرآیند روتین می‌باشد، به این صورت که در جایی که بنده مشغول به کار هستم یک سری لایسنس آنتی ویروس برای کلاینت‌ها در یک شبکه با مقیاس متوسط تهیه گردیده است، حال یک نسخه رایگان نیز برای کاربرانی که قصد دارند آنتی ویروس را برای سیستم شخصی خود نصب کنند نیز موجود می‌باشد که نیاز به آپدیت دارد معمولا آپدیت‌ها هر چند روز یکبار یا هر هفته در دو نسخه 64 و 32 بیتی ارائه می‌شوند، روال معمول برای دریافت آپدیت مراجعه به سایت و دانلود نسخه‌های مربوطه میباشد.

حال توسط کتابخانه قدرتمند Quartz.NET این فرآیند روتین را به صورت اتوماتیک می‌خواهیم انجام دهیم، استفاده از کتابخانه ذکر شده سخت نیست همانطور که در دو مطلب قبلی مرتبط ذکر گردیده، تنها پیاده سازی چندین اینترفیس است و بس.

namespace SymantecUpdateDownloader
{
    using System;
    using System.IO;
    using Quartz;
    using Quartz.Impl;
    using System.Globalization;
    public class TestJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            new Download().Scraping();
        }
    }
    public interface ISchedule
    {
        void Run();
    }
    public class TestSchedule : ISchedule
    {
        public void Run()
        {
            
            DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second);

            IJobDetail job = JobBuilder.Create<HelloJob>()
                                       .WithIdentity("job1")
                                       .Build();

            ITrigger trigger = TriggerBuilder.Create()
                                             .WithIdentity("trigger1")
                                             .StartAt(startTime)
                                             .WithDailyTimeIntervalSchedule(x => x.OnEveryDay().StartingDailyAt(new TimeOfDay(7, 0)).WithRepeatCount(0))
                                             .Build();

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

            sc.Start();
        }
    }
}
در این کد که همانند کدهای پیشنیازهای مطلب است، در خط 33 از متد WithDailyTimeIntervalSchedule استفاده شده است و همانطور که مشخص است وظیفه تعیین شده و هر روز ساعت 7 اجرا میشود.
مورد بعدی عملیات دانلود فایل می‌باشد که در ادامه مشاهده خواهید کرد، صفحه ایی که لینک فایل‌های دانلود را ارائه داده است دو نسخه مد نظر ما را در ابتدا لیست کرده است و با استفاده از web scrapingمی توانیم موارد تعیین شده را استخراج کنیم برای این منظور از کتابخانه htmlagilitypack استفاده میکنیم، تطبیق دو مورد(لینک) اول جهت دریافت نسخه‌های 32 و 64 بیتی به کمک Regular Expression میسر است و همانطور که در شکل زیر مشاهده میکنید از سمت چپ تاریخ به صورت 8 رقم، سه رقم قسمت دوم و ارقام و حروف قسمت سوم است به اضافه پسوند فایل مشخص است :


public class Download
    {
        static WebClient wc = new WebClient();
        static ManualResetEvent handle = new ManualResetEvent(true);
        
        private DateTime myDate = new DateTime();
        public void Scraping()
        {
            using (WebClient client = new WebClient())
            {
                client.Encoding = System.Text.Encoding.UTF8;
                var doc = new HtmlAgilityPack.HtmlDocument();
                ArrayList result = new ArrayList();
                doc.LoadHtml(client.DownloadString("https://www.symantec.com/security_response/definitions/download/detail.jsp?gid=savce"));
                var tasks = new List<Task>();
                foreach (var href in doc.DocumentNode.Descendants("a").Select(x => x.Attributes["href"]))
                {
                    if (href == null) continue;
                    string s = href.Value;
                    Match m = Regex.Match(s, @"http://definitions.symantec.com/defs/(\d{8}-\d{3}-v5i(32|64)\.exe)");
                    if (m.Success)
                    {
                        Match date = Regex.Match(m.Value, @"(\d{4})(\d{2})(\d{2})");
                        Match filename = Regex.Match(m.Value, @"\d{8}-\d{3}-v5i(32|64)\.exe");
                        int year = Int32.Parse(date.Groups[0].Value);
                        int month = Int32.Parse(date.Groups[1].Value);
                        int day = Int32.Parse(date.Groups[3].Value);

                        myDate = new DateTime(
                                Int32.Parse(date.Groups[1].Value),
                                Int32.Parse(date.Groups[2].Value),
                                Int32.Parse(date.Groups[3].Value));
                        if (myDate == DateTime.Today)
                        {
                            tasks.Add(DownloadUpdate(m.Value, filename.Value));
                        }
                        else
                        {
                            MessageBox.Show("امروز آپدیت موجود نیست");
                        }
                    }
                }
                DownloadTask = Task.WhenAll(tasks);
            }
        }
        private static Task DownloadTask;
        private Task DownloadUpdate(string url, string fileName)
        {
            var wc = new WebClient();
            return wc.DownloadFileTaskAsync(new Uri(url), @"\\10.1.0.15\SymantecUpdate\\" + fileName);
        }

    }
توضیح کدهای فوق :
ابتدا توسط متد LoadHtml خط 14 صفحه مورد نظر که حاوی لینک‌ها می‌باشد رو Load میکنیم، سپس توسط یک حلقه foreach خط 16 مقدار خصوصیت href تمام لینک‌های موجود  در صفحه را استخراج میکنیم مثلا مقدار خصوصیت href در لینک‌ها به صورت  زیر می‌باشد :
 
http://definitions.symantec.com/defs/20130622-007-v5i32.exe

http://definitions.symantec.com/defs/20130622-007-v5i64.exe

همانطور که مشخص است در دو مورد فوق تنها نام فایل متفاوت می‌باشد، همانطور که بحث شد برای نام فایل‌ها هم می‌توانیم یک Pattern را به صورت زیر داشته باشیم :
(\d{8}-\d{3}-v5i(32|64)\.exe)
در خط 20 نیز عملیات تطبیق تمام href‌های موجود در صفحه را توسط Regular Expression فوق تطبیق می‌دهیم، اگر تطبیق با موفقیت انجام پذیرفت باید نام فایل و همچنین تاریخ موجود در نام فایل را نیز توسط دو Regular Expression استخراج کنیم(خط 23 و 24) در ادامه برای جدا کردن مقادیر سال ، ماه ، روز از امکان Groups در RegEx استفاده کرده ایم:
int year = Int32.Parse(date.Groups[0].Value);
int month = Int32.Parse(date.Groups[1].Value);
int day = Int32.Parse(date.Groups[3].Value);
در ادامه تاریخ استخراج شده را با تاریخ روز جاری مقایسه می‌کنیم اگر مساوی بود عملیات دانلود فایل‌ها توسط یک Task تعریف شده به صورت همزمان بر روی سرور مربوطه دانلود می‌شوند.
البته لازم به ذکر است که کدهای فوق مسلما نیاز یه Refactoring دارند منتها هدف از ارائه این مثال آشنایی بیشتر با کتابخانه‌های فوق می‌باشد.
نکته آخر اینکه برنامه فوق به حالت‌های مختلفی می‌تواند اجرا گردد مثل یک برنامه وب  یا یک سرویس ویندوزی و ... ، بهترین حالت یک سرویس ویندوز می‌باشد، ولی در حالت خام در حال حاضر یک ویندوز اپلیکیشن ساده می‌باشد که بر روی سرور RUN شده است که در آینده به صورت یک سرویس ویندوز ارائه خواهد شد.
مطالب
محاسبه مجدد میزان مصرف حافظه‌ی برنامه‌های دات نت

اگر به میزان مصرف حافظه‌ اولیه‌ی برنامه‌های دات نت دقت کنیم، نسبت به مثلا یک برنامه‌ی MFC چند برابر به نظر می‌رسند و ... این علت دارد:
زمانیکه یک برنامه‌ی مبتنی بر دات نت اجرا می‌شود، ابتدا JIT compiler شروع به کار کرده و شروع به کامپایل برنامه می‌کند. این بارگزاری هم در همان پروسه‌ی اصلی برنامه انجام می‌شود. به همین جهت میزان مصرف حافظه‌ی برنامه‌های دات نت عموما بالا به نظر می‌رسد.
اکنون سؤال اینجا است که آیا می توان این حافظه‌ای را که دیگر مورد استفاده نیست (و توسط JIT compiler اخذ شده) به سیستم بازگرداند و محاسبه‌ی مجددی را در این مورد انجام داد. پاسخ به این سؤال را در متد ReEvaluateWorkingSet زیر می‌توان مشاهده کرد:


using System;
using System.Diagnostics;

namespace Toolkit
{
public static class Memory
{
public static void ReEvaluateWorkingSet()
{
try
{
Process loProcess = Process.GetCurrentProcess();
//it doesn't matter what you set maxWorkingSet to
//setting it to any value apparently causes the working set to be re-evaluated and excess discarded
loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet + 1);
}
catch
{
//The above code requires Admin privileges.
//So it's important to trap exceptions in case you're running without admin rights.
}
}
}
}

در این متد ابتدا پروسه جاری دریافت شده و سپس MaxWorkingSet به یک عدد دلخواه تنظیم می‌شود. مهم نیست که این عدد چه چیزی باشد، زیرا این تنظیم سبب می‌شود که در پشت صحنه به شکل حساب شده‌ای حافظه‌ای که مورد استفاده نیست به سیستم بازگردانده شود و سپس عددی که در task manager نمایش داده می‌شود، مجددا محاسبه گردد. همچنین باید دقت داشت که این کد تنها با دسترسی مدیریتی قابل اجرا است و به همین دلیل وجود این try/catch ضروری است.

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌های WinForms :
فایل Program.cs را یافته و سپس در روال رویداد گردان Idle برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید (مثلا هر زمان که برنامه minimized شد اجرا می‌شود):

//Program.cs
namespace MemUsage
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//...

Application.Idle += applicationIdle;
}

static void applicationIdle(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}
}
}

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌های WPF :
فایل App.xaml.cs را یافته و سپس در روال رویدادگردان Deactivated برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید:

//App.xaml.cs

public App()
{
this.Deactivated += appDeactivated;
}

void appDeactivated(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}

تاثیر آن هم قابل ملاحظه است (حداقل از لحاظ روانی!). تست کنید!

مطالب
یک دست سازی ی و ک در برنامه‌های Entity framework 6
تا قبل از EF 6 برای طراحی یک سیستم عمومی تغییر مقادیر ثبت شده در بانک اطلاعاتی، می‌شد با استفاده از امکانات توکار Tracking آن، مقادیر تغییر کرده را یافت و برای مثال ی و ک آن‌ها را پیش از درج در بانک اطلاعاتی، یک دست کرد. در EF 6 با معرفی یک سری interceptor می‌توان به مراحل پیش و پس از اجرای کوئری‌ها دسترسی پیدا کرد. عمده‌ترین کاربرد آن، لاگ کردن SQLهای تولیدی و نوشتن برنامه‌هایی شبیه به EF Profiler است. اما ... استفاده‌ی دیگری را نیز می‌توان از IDbCommandInterceptor جدید آن تدارک دید: دستکاری SQL تولیدی توسط آن پیش از اعمال به بانک اطلاعاتی.

طراحی یک Interceptor برای یک دست سازی ی و ک

در اینجا کدهای کلاس YeKeInterceptor را ملاحظه می‌کنید. در متدهایی که به کلمه‌ی Executing ختم می‌شوند، می‌توان به دستورات SQL تولید شده توسط EF، پیش از اعمال بر روی بانک اطلاعاتی دسترسی داشت:
    public class YeKeInterceptor : IDbCommandInterceptor
    {
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }

        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }

        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }

        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }

        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            command.ApplyCorrectYeKe();
        }
    }
DbCommand، حاوی تمام اطلاعاتی است که به آن نیاز داریم؛ شامل CommandText یا همان SQL تولید شده و همچنین command.Parameters برای دسترسی به مقادیر پارامترهای کوئری. نکته‌ی مهم تمام این موارد، قابل ویرایش بودن آن‌ها است.
    public static class YeKe
    {
        public const char ArabicYeChar = (char)1610;
        public const char PersianYeChar = (char)1740;

        public const char ArabicKeChar = (char)1603;
        public const char PersianKeChar = (char)1705;

        public static string ApplyCorrectYeKe(this object data)
        {
            return data == null ? null : ApplyCorrectYeKe(data.ToString());
        }

        public static string ApplyCorrectYeKe(this string data)
        {
            return string.IsNullOrWhiteSpace(data) ?
                        string.Empty :
                        data.Replace(ArabicYeChar, PersianYeChar).Replace(ArabicKeChar, PersianKeChar).Trim();
        }

        public static void ApplyCorrectYeKe(this DbCommand command)
        {
            command.CommandText = command.CommandText.ApplyCorrectYeKe();

            foreach (DbParameter parameter in command.Parameters)
            {
                switch (parameter.DbType)
                {
                    case DbType.AnsiString:
                    case DbType.AnsiStringFixedLength:
                    case DbType.String:
                    case DbType.StringFixedLength:
                    case DbType.Xml:
                        parameter.Value =   parameter.Value is DBNull ? parameter.Value : parameter.Value.ApplyCorrectYeKe();
                        break;
                }
            }
        }
    }
در اینجا پیاده سازی متد الحاقی ApplyCorrectYeKe را که در کلاس YeKeInterceptor مورد استفاده قرار گرفت، ملاحظه می‌کنید.
در آن، CommandText و همچنین parameter.Valueها در صورت رشته‌ای بودن، اصلاح می‌شوند.
سربار این روش نسبت به روش‌های پیشین استفاده از Reflection کمتر است. همچنین اشیاء پیچیده و تو در تو را نیز بهتر پشتیبانی می‌کند؛ چون در مرحله Executing، کار پردازش این اشیاء پایان یافته و SQL خام نهایی آن در اختیار ما است.


نحوه‌ی استفاده از YeKeInterceptor

در آغاز برنامه (برای مثال متد Application_Start فایل Global.asax.cs برنامه‌های MVC )، سطر زیر را فراخوانی کنید:
 DbInterception.Add(new YeKeInterceptor());

یک مثال کامل برای دریافت
Sample32.cs
مطالب
FluentValidation #1
FluentValidation یک پروژه سورس باز برای اعتبارسنجی Business Object‌ها با استفاده از Fluent Interface و Lambada Expressions می‌باشد.
جهت نصب این کتابخانه دستور زیر را در Package Manager Console وارد نمایید:
PM> Install-Package FluentValidation

ایجاد یک Validator
برای تعریف مجموعه قوانین اعتبارسنجی برای یک موجودیت ابتدا بایستی یک کلاس ایجاد کرد که از AbstractValidator<T> مشتق می‌شود که T در اینجا برابر موجودیتی است که می‌خواهیم اعتبارسنجی کنیم. به عنوان مثال کلاس مشتری به صورت زیر را در نظر بگیرید:
public class Customer
{
      public int Id { get; set; }
      public string Surname { get; set; }
      public string Forename { get; set; }
      public decimal Discount { get; set; }
      public string Address { get; set; }
}
مجموعه قوانین اعتبارسنجی با استفاده از متد RuleFor و داخل متد سازنده کلاس Validator تعریف می‌شوند. به عنوان مثال برای اطمینان از اینکه مقدار خاصیت Surname برابر Null نباشد باید به صورت زیر عمل کرد:
using FluentValidation;

public class CustomerValidator : AbstractValidator<Customer>
{
      public CustomerValidator
      {
           RuleFor(customer => customer.Surname).NotNull();
      }
}


اعتبارسنجی زنجیره ای برای یک خاصیت

برای اعتبارسنجی یک خاصیت، می‌توان از چندین Validator باهم نیز استفاده کرد:
RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
در اینجا خاصیت Surname نباید Null باشد و همچنین مقدار آن نباید برابر "Foo" باشد.
برای اجراکردن اعتبارسنجی، ابتدا یک نمونه از کلاس Validator مان را ساخته و شیء ای را که می‌خواهیم اعتبارسنجی کنیم به متد Validate آن میفرستیم:
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);

خروجی متد Validate، یک ValidationResult است که شامل دو خاصیت زیر می‌باشد:

  • IsValid: از نوع bool برای تعیین اینکه اعتبارسنجی موفقیت آمیز بوده یا خیر.
  • Errors: یک مجموعه از ValidationFailure که جزئیات تمام اعتبارسنجی‌های ناموفق را شامل می‌شود.
به عنوان مثال قطعه کد زیر، جزئیات اعتبارسنجی‌های ناموفق را نمایش می‌دهد:
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();

ValidationResult results = validator.Validate(customer);

if(! results.IsValid) 
{
     foreach(var failure in results.Errors)
     {
          Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " +      failure.ErrorMessage);
     }
}


پرتاب استثناها (Throwing Exceptions)

به جای برگرداندن ValidationResult شما میتوانید با کمک متد ValidateAndThrow به FluentValidation بگویید که هنگام اعتبارسنجی ناموفق یک استثنا پرتاب کند:
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
validator.ValidateAndThrow(customer);
در این صورت Validator یک ValidationException را پرتاب خواهد کرد که دربردارنده‌ی پیام‌های خطا در خاصیت Errors خود می‌باشد.

استفاده از Validator‌ها برای Complex Properties

جهت درک این ویژگی تصور کنید که کلاس‌های مشتری و آدرس و همچنین کلاس‌های مربوط به اعتبارسنجی آن‌ها را به صورت زیر داریم:
public class Customer
{
     public string Name { get; set; }
     public Address Address { get; set; }
}

public class Address 
{
     public string Line1 { get; set; }
     public string Line2 { get; set; }
     public string Town { get; set; }
     public string County { get; set; }
     public string Postcode { get; set; }
}

public class AddressValidator : AbstractValidator<Address>
{
     public AddressValidator() 
     {
          RuleFor(address => address.Postcode).NotNull();
          //etc
     }
}

public class CustomerValidator : AbstractValidator<Customer> 
{
     public CustomerValidator()
     {
          RuleFor(customer => customer.Name).NotNull();
          RuleFor(customer => customer.Address).SetValidator(new AddressValidator())
      }
}
در این صورت وقتی متد Validate کلاس CustomerValidator را فراخوانی نمایید AddressValidator نیز فراخوانی خواهد شد و نتیجه این اعتبارسنجی به صورت یکجا در یک ValidationResult برگشت داده خواهد شد.


استفاده از Validator‌ها برای مجموعه‌ها (Collections)

Validator‌ها همچنین می‌توانند بر روی خاصیت هایی که شامل مجموعه ای از یک شیء دیگر هستند نیز استفاده شوند. به عنوان مثال یک مشتری که دارای لیستی از سفارشات است را در نظر بگیرید:
public class Customer
{
     public IList<Order> Orders { get; set; }
}

public class Order 
{
     public string ProductName { get; set; }
     public decimal? Cost { get; set; }
}

var customer = new Customer();
customer.Orders = new List<Order> 
{
     new Order { ProductName = "Foo" },
     new Order { Cost = 5 } 
};
کلاس OrderValidator نیز به صورت زیر خواهد بود:
public class OrderValidator : AbstractValidator<Order>
{
     public OrderValidator() 
     {
          RuleFor(x => x.ProductName).NotNull();
          RuleFor(x => x.Cost).GreaterThan(0);
     }
}

این Validator می‌تواند داخل CustomerValidator مورد استفاده قرار بگیرد (با استفاده از متد SetCollectionValidator):

public class CustomerValidator : AbstractValidator<Customer>
{
     public CustomerValidator()
     {
          RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator());
     }
}

می توان با استفاده از متد Where یا Unless روی اعتبارسنجی شرط گذاشت:

RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator()).Where(x => x.Cost != null);


گروه بندی قوانین اعتبارسنجی

RuleSet‌‌ها به شما این امکان را می‌دهند تا بعضی از قوانین اعتبارسنجی را داخل یک گروه قرار دهید تا با یکدیگر اجرا شوند. در حالی که دیگر قوانین نادیده گرفته می‌شوند.
برای مثال تصور کنید شما سه خاصیت در کلاس Person دارید که شامل (Id, Surname, Forename) می‌باشند و همچنین یک قانون برای هرکدام از آن ها. میتوان قوانین مربوط به Surname و Forename را در یک RuleSet مجزا به نام Names قرار داد:
public class PersonValidator : AbstractValidator<Person>
{
     public PersonValidator() 
     {
          RuleSet("Names", () =>
          {
               RuleFor(x => x.Surname).NotNull();
               RuleFor(x => x.Forename).NotNull();
          });
 
          RuleFor(x => x.Id).NotEqual(0);
      }
}
در اینجا دو خاصیت Surname و Forename با یکدیگر داخل یک RuleSet به نام Names گروه شده اند. برای اعتبارسنجی جداگانه این گروه نیز به صورت زیر می‌توان عمل کرد:
var validator = new PersonValidator();
var person = new Person();
var result = validator.Validate(person, ruleSet: "Names");
این ویژگی به شما این امکان را می‌دهد تا یک Validator پیچیده را به چندین قسمت کوچکتر تقسیم کرده و توانایی اعتبارسنجی این قسمت‌ها را به صورت جداگانه داشته باشید.
مطالب
کار با وب سرویس جاوایی تشخیص ایمیل‌های موقتی در دات نت
یکی از وب سرویس‌های سایت name api، امکان تشخیص موقتی بودن ایمیل مورد استفاده‌ی جهت ثبت نام در یک سایت را فراهم می‌کند. آدرس WSDL آن نیز در اینجا قرار دارد. اگر مطابق معمول استفاده از سرویس‌های وب در دات نت، بر روی ارجاعات پروژه کلیک راست کرده و گزینه‌ی Add service refrence را انتخاب کنیم و سپس آدرس WSDL یاد شده را به آن معرفی کنیم، بدون مشکل ساختار این وب سرویس دریافت و برای استفاده‌ی از آن به یک چنین کدی خواهیم رسید:
var client = new SoapDisposableEmailAddressDetectorClient();
 
var context = new soapContext
{
    //todo: get your API key here: http://www.nameapi.org/en/register/
    apiKey = "test"
};
var result = client.isDisposable(context, "DaDiDoo@mailinator.com"); 
 
if (result.disposable.ToString() == "YES")
{
    Console.WriteLine("YES! It's Disposable!");
}
متد isDisposable ارائه شده‌ی توسط این وب سرویس، دو پارامتر context که در آن باید API Key خود را مشخص کرد و همچنین آدرس ایمیل مورد بررسی را دریافت می‌کند. اگر به همین ترتیب این پروژه را اجرا کنید، با خطای Bad request از طرف سرور متوقف خواهید شد:
 Additional information: The remote server returned an unexpected response: (400) Bad Request.
اگر به خروجی این وب سرویس در فیدلر مراجعه کنیم، چنین شکلی را خواهد داشت:
 <html><head><title>Bad Request</title></head><body><h1>Bad Request</h1><p>No api-key provided!</p></body></html>
عنوان کرده‌است که api-key را، در درخواست وب خود ذکر نکرده‌ایم.
اگر همین وب سرویس را توسط امکانات سایت http://wsdlbrowser.com بررسی کنید، بدون مشکل کار می‌کند. اما تفاوت در کجاست؟
خروجی ارسالی به سرور، توسط سایت http://wsdlbrowser.com به این شکل است:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://disposableemailaddressdetector.email.services.v4_0.soap.server.nameapi.org/">
  <SOAP-ENV:Body>
    <ns1:isDisposable>
      <context>
        <apiKey>test</apiKey>       
      </context>
      <emailAddress>sdsdg@site.com</emailAddress>
    </ns1:isDisposable>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
و نمونه‌ی تولید شده‌ی توسط WCF (امکان Add service reference در حقیقت یک WCF Client را ایجاد می‌کند) به صورت زیر می‌باشد:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <isDisposable xmlns="http://disposableemailaddressdetector.email.services.v4_0.soap.server.nameapi.org/">
          <context xmlns=""><apiKey>test</apiKey></context>
          <emailAddress xmlns="">DaDiDoo@mailinator.com</emailAddress>
      </isDisposable>
  </s:Body>
</s:Envelope>
از لحاظ اصول XML، خروجی تولیدی توسط WCF هیچ ایرادی ندارد. از این جهت که نام فضای نام مرتبط با http://schemas.xmlsoap.org/soap/envelope/ را به s تنظیم کرده‌است و سپس با استفاده از این نام، Envelope را تشکیل داده‌است. اما ... این وب سرور جاوایی دقیقا با نام SOAP-ENV کار می‌کند و فضای نام ns1 بعدی آن. کاری هم به اصول XML ندارد که باید بر اساس نام xmlns ذکر شده، کار Parse ورودی دریافتی صورت گیرد و نه بر اساس یک رشته‌ی ثابت از پیش تعیین شده. بنابراین باید راهی را پیدا کنیم تا بتوان این s را تبدیل به SOAP-ENV کرد.

برای این منظور به سه کلاس ذیل خواهیم رسید:
public class EndpointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    { }
 
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    { }
 
    public void Validate(ServiceEndpoint endpoint)
    { }
 
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new ClientMessageInspector());
    }
}
 
public class ClientMessageInspector : IClientMessageInspector
{
    public void AfterReceiveReply(ref Message reply, object correlationState)
    { }
 
    public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel)
    {
        request = new MyCustomMessage(request);
        return request;
    }
}
 
/// <summary>
/// To customize WCF envelope and namespace prefix
/// </summary>
public class MyCustomMessage : Message
{
    private readonly Message _message;
 
    public MyCustomMessage(Message message)
    {
        _message = message;
    }
 
    public override MessageHeaders Headers
    {
        get { return _message.Headers; }
    }
 
    public override MessageProperties Properties
    {
        get { return _message.Properties; }
    }
 
    public override MessageVersion Version
    {
        get { return _message.Version; }
    }
 
    protected override void OnWriteStartBody(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
    }
 
    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        _message.WriteBodyContents(writer);
    }
 
    protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("SOAP-ENV", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
        writer.WriteAttributeString("xmlns", "ns1", null, value: "http://disposableemailaddressdetector.email.services.v4_0.soap.server.nameapi.org/");
    }
}
که پس از تعریف client به نحو ذیل معرفی می‌شوند:
 var client = new SoapDisposableEmailAddressDetectorClient();
client.Endpoint.Behaviors.Add(new EndpointBehavior());
توسط EndpointBehavior سفارشی، می‌توان به متد OnWriteStartEnvelope دسترسی یافت و سپس s آن‌را با SOAP-ENV درخواستی این وب سرویس جایگزین کرد. اکنون اگر برنامه را اجرا کنید، بدون مشکل کار خواهد کرد و دیگر پیام یافت نشدن API-Key را صادر نمی‌کند.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
آشنایی با Refactoring - قسمت 8

یکی از اشتباهاتی که همه‌ی ما کم و بیش به آن دچار هستیم ایجاد کلاس‌هایی هستند که «زیاد می‌دانند». اصطلاحا به آن‌ها God Classes هم می‌گویند و برای نمونه، پسوند یا پیشوند Util دارند. این نوع کلاس‌ها اصل SRP را زیر سؤال می‌برند (Single responsibility principle). برای مثال یک فایل ایجاد می‌شود و داخل آن از انواع و اقسام متدهای «کمکی» کار با دیتابیس تا رسم نمودار تا تبدیل تاریخ میلادی به شمسی و ... در طی بیش از 10 هزار سطر قرار می‌گیرند. یا برای مثال گروه بندی‌های خاصی را در این یک فایل از طریق کامنت‌های نوشته شده برای قسمت‌های مختلف می‌توان یافت. Refactoring مرتبط با این نوع کلاس‌هایی که «زیاد می‌دانند»، تجزیه آن‌ها به کلاس‌های کوچکتر، با تعداد وظیفه‌ی کمتر است.
به عنوان نمونه کلاس CustomerService زیر، دو گروه کار متفاوت را انجام می‌دهد. ثبت و بازیابی اطلاعات ثبت نام یک مشتری و همچنین محاسبات مرتبط با سفارشات مشتری‌ها:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.Before
{
public class CustomerService
{
public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public bool CustomerIsValid(string customer, int order)
{
// do work
throw new NotImplementedException();
}

public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public void Register(string customer)
{
// do work
}

public void ForgotPassword(string customer)
{
// do work
}
}
}

بهتر است این دو گروه، به دو کلاس مجزا بر اساس وظایفی که دارند، تجزیه شوند. به این ترتیب نگهداری این نوع کلاس‌های کوچکتر در طول زمان ساده‌تر خواهند شد:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.After
{
public class CustomerOrderService
{
public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public bool CustomerIsValid(string customer, int order)
{
// do work
throw new NotImplementedException();
}

public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}
}
}

namespace Refactoring.Day8.RemoveGodClasses.After
{
public class CustomerRegistrationService
{
public void Register(string customer)
{
// do work
}

public void ForgotPassword(string customer)
{
// do work
}
}
}

مطالب
استفاده از FluentValidation در ASP.NET MVC
برای هماهنگی این کتابخانه با ASP.NET MVC نیاز به نصب FluentValidation.Mvc3 یا FluentValidation.Mvc4 از طریق Nuget یا دانلود کتابخانه از سایت CodePlex می‌باشد. بعد از نصب کتابخانه، نیاز به تنظیم FluentValidationModelValidatorProvider داخل متد Application_Start (فایل Global.asax) داریم: 

protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    FluentValidationModelValidatorProvider.Configure();
}
تصور کنید دو کلاس Person و PersonValidator را به صورت زیر داریم:
[Validator(typeof(PersonValidator))]
    public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}
 
public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
        RuleFor(x => x.Id).NotNull();
        RuleFor(x => x.Name).Length(0, 10);
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.Age).InclusiveBetween(18, 60);
    }
}
همان طور که ملاحظه می‌کنید، در بالای تعریف کلاس Person با استفاده از ValidatorAttribute مشخص کرده ایم که از PersonValidator جهت اعتبارسنجی استفاده کند.
در آخر می‌توانیم Controller و View ی برنامه مان را درست کنیم:
public class PeopleController : Controller {
    public ActionResult Create() {
        return View();
    }
 
    [HttpPost]
    public ActionResult Create(Person person) {
 
        if(! ModelState.IsValid) { // re-render the view when validation failed.
            return View("Create", person);
        }
 
        TempData["notice"] = "Person successfully created";
        return RedirectToAction("Index");
    }
}
@Html.ValidationSummary()
 
@using (Html.BeginForm()) {
Id: @Html.TextBoxFor(x => x.Id) @Html.ValidationMessageFor(x => x.Id)
<br />
Name: @Html.TextBoxFor(x => x.Name) @Html.ValidationMessageFor(x => x.Name) 
<br />
Email: @Html.TextBoxFor(x => x.Email) @Html.ValidationMessageFor(x => x.Email)
<br />
Age: @Html.TextBoxFor(x => x.Age) @Html.ValidationMessageFor(x => x.Age)
 
<input type="submit" value="submit" />
}
اکنون DefaultModelBinder موجود در MVC برای اعتبارسنجی شیء Person از FluentValidationModelValidatorProvider استفاده خواهد کرد.
توجه داشته باشید که FluentValidation با اعتبارسنجی سمت کاربر ASP.NET MVC به خوبی کار خواهد کرد منتها نه برای تمامی اعتبارسنجی ها. به عنوان مثال تمام قوانینی که از شرط‌های When/Unless استفاده کرده اند، Validator‌های سفارشی، و قوانینی که در آن‌ها از Must استفاده شده باشد، اعتبارسنجی سمت کاربر نخواهند داشت. در زیر لیست Validator هایی که با اعتبارسنجی سمت کاربر به خوبی کار خواهند کرد آمده است:
  • NotNull/NotEmpty
  • Matches 
  • InclusiveBetween 
  • CreditCard 
  • Email 
  • EqualTo 
  • Length 
صفت CustomizeValidator
با استفاده از CustomizeValidatorAttribute می‌توان نحوه اجرای Validator را تنظیم کرد. به عنوان مثال اگر میخواهید که Validator تنها برای یک RuleSet مخصوص انجام شود می‌توانید مانند زیر عمل کنید: 
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
  // ...
}


مواردی که تا اینجا گفته شد برای استفاده در یک برنامه‌ی ساده MVC کافی به نظر می‌رسد، اما از اینجا به بعد مربوط به مواقعی است که نخواهیم از Attribute‌ها استفاده کنیم و کار را به IoC بسپاریم. 
استفاده از Validator Factory با استفاده از یک IoC Container
Validator Factory چیست؟ Validator Factory یک کلاس می‌باشد که وظیفه ساخت نمونه از Validator‌‌ها را بر عهده دارد. اینترفیس IValidatorFactory به صورت زیر می‌باشد:  
public interface IValidatorFactory {
   IValidator<T> GetValidator<T>();
   IValidator GetValidator(Type type);
}
ساخت Validator Factory سفارشی:
برای ساخت یک Validator Factory شما می‌توانید به طور مستقیم IValidatorFactory را پیاده سازی نمایید یا از کلاس ValidatorFactoryBase به عنوان کلاس پایه استفاده کنید (که مقداری از کارها را برای شما انجام داده است). در این مثال نحوه ایجاد یک Validator Factory که از StructureMap استفاده می‌کند را بررسی خواهیم کرد. ابتدا نیاز به ثبت Validator‌ها در StructureMap داریم: 
ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
 
public class MyRegistry : Registry {
    public MyRegistry() {
        For<IValidator<Person>>()
    .Singleton()
    .Use<PersonValidator>();
    }
}
در اینجا StructureMap را طوری تنظیم کرده ایم که از یک Registry سفارشی استفاده کند. در داخل این Registry به StructureMap میگوییم که زمانی که خواسته شد تا یک نمونه از IValidator<Person> ایجاد کند، PersonValidator را بر گرداند. متد CreateInstance نوع مناسب را نمونه سازی می‌کند (CustomerValidator) و آن را بازمی گرداند ( یا Null بر می‌گرداند اگر نوع مناسبی وجود نداشته باشد) 
استفاده از AssemblyScanner 
FluentValidation دارای یک AssemblyScanner می‌باشد که کار ثبت Validator‌ها داخل یک اسمبلی را راحت‌تر می‌سازد. با استفاده از AssemblyScanner کلاس MyRegistery ما شبیه قطعه کد زیر خواهد شد: 
public class MyRegistry : Registry {
   public MyRegistry() {
 
     AssemblyScanner.FindValidatorsInAssemblyContaining<MyValidator>()
       .ForEach(result => {
            For(result.InterfaceType)
               .Singleton()
               .Use(result.ValidatorType);
       });
   }
}
حالا زمان استفاده از factory ساخته شده در متد Application_Start برنامه می‌باشد:
protected void Application_Start() {
    RegisterRoutes(RouteTable.Routes);
 
    //Configure structuremap
    ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
    ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
 
    //Configure FV to use StructureMap
    var factory = new StructureMapValidatorFactory();
 
    //Tell MVC to use FV for validation
    ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(factory));        
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
}
اکنون FluentValidation از StructureMap برای نمونه سازی Validatorها استفاده خواهد کرد و کار اعتبارسنجی مدل‌ها به FluentValidaion سپرده شده است.