مطالب
بررسی تغییرات HttpClient در NET 5.0.
پیشتر بسته‌ی نیوگتی به نام Microsoft.AspNet.WebApi.Client وجود داشت/دارد که کار آن ارائه‌ی یک سری متد الحاقی کار با JSON، جهت HttpClient است. در نگارش 5 دات نت، تمام این متدهای الحاقی جزئی از دات نت استاندارد شده‌اند و برای کار با آن‌ها دیگر نیازی به استفاده‌ی از بسته‌های نیوگت خاصی نیست.


تغییرات API دات نت 5 از دیدگاه افزونه‌های HttpClient

در اینجا لیست کامل متدهای الحاقی اضافه شده‌ی به فضای نام جدید و استاندارد System.Net.Http.Json را مشاهده می‌کنید:
namespace System.Net.Http.Json {
    public static class HttpClientJsonExtensions {
        public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken);
    }

    public static class HttpContentJsonExtensions {
        public static Task<object> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<T> ReadFromJsonAsync<T>(this HttpContent content, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
    }

    public sealed class JsonContent : HttpContent {
        public Type ObjectType { get; }
        public object Value { get; }
        public static JsonContent Create(object inputValue, Type inputType, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null);
        public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null);
        protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context);
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken);
        protected override bool TryComputeLength(out long length);
    }
}


متدهای الحاقی جدید کلاس HttpClientJsonExtensions

این متدها به صورت خلاصه شامل سه متد زیر می‌شوند:
- GetFromJsonAsync : یک درخواست Get را به آدرسی خاص ارسال کرده و خروجی JSON دریافتی را به کمک امکانات توکار System.Text.Json، پردازش و deserialize می‌کند.
- PostAsJsonAsync : یک درخواست POST را به آدرسی خاص، ارسال می‌کند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال می‌گردد.
- PutAsJsonAsync : یک درخواست PUT را به آدرسی خاص، ارسال می‌کند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال می‌گردد.

در ذیل چند مثال را در مورد نحوه‌ی کار با این متدهای الحاقی جدید فضای نام استاندارد System.Net.Http.Json، مشاهده می‌کنید:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:5000");

var profiles = await httpClient.GetFromJsonAsync<Profile[]>("api/users/profiles");

var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 };
using var response1 = await httpClient.PostAsJsonAsync("api/users/profiles", profile);
response1.EnsureSuccessStatusCode();


var updatedProfile = new Profile { FirstName = "User 2", LastName = "Name 2", Age = 40 };
using var response2 = await httpClient.PutAsJsonAsync("api/users/profiles", profile);
response2.EnsureSuccessStatusCode();

اگر می‌خواستیم یک چنین کارهایی را پیش از دات نت 5 انجام دهیم، می‌بایستی قسمت Serialize کردن و همچنین تنظیم content-type را دستی انجام می‌دادیم:
var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 };
var json = JsonSerializer.Serialize(profile);
var stringContent = new StringContent(json, Encoding.UTF8, "application/json");
using var response4 = await httpClient.PostAsync("api/users/profiles", stringContent);
response4.EnsureSuccessStatusCode();


متدهای الحاقی جدید کلاس HttpContentJsonExtensions

این کلاس، متد الحاقی جدید ReadFromJsonAsync را ارائه می‌دهد که کار آن، خواندن یک محتوای HTTP از نوع HttpContent و deserialize آن به صورت JSON است. یک مثال:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:5000");

var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles");
using var response1 = await httpClient.SendAsync(request);
if (response1.IsSuccessStatusCode)
{
  var profiles = await response1.Content.ReadFromJsonAsync<Profile[]>();
}

انجام اینکار در نگارش‌های پیشین دات نت، نیاز به فراخوانی دستی JsonSerializer.DeserializeAsync را دارد:
var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles");
using var response2 = await httpClient.SendAsync(request);
if (response2.IsSuccessStatusCode)
{
   using var streamResult = await response2.Content.ReadAsStreamAsync();
   var profiles = JsonSerializer.DeserializeAsync<Profile[]>(streamResult);
}


متدهای جدید کلاس JsonContent

روش‌های زیادی برای کار با HttpClient وجود دارند. یک روش آن، ساخت دستی HttpRequestMessage و سپس ارسال آن توسط متد SendAsync است؛ بجای استفاده از متد PostAsJsonAsync که بررسی شد. در این حالت با استفاده از متد جدید JsonContent.Create، می‌توان کار تبدیل یک شیء را به JSON و همچنین تنظیم content-type را به صورت خودکار انجام داد:
var httpClient = new HttpClient();
var uri = "https://localhost:5000";
httpClient.BaseAddress = new Uri(uri);

var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://localhost:5000")
{
   Content = JsonContent.Create(new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 })
};
using var reponse1 = await httpClient.SendAsync(requestMessage);
reponse1.EnsureSuccessStatusCode();
بازخوردهای دوره
نگاهی به SignalR Hubs
یک hub را داخل یک پروژه دیگر از نوع class library  قرار دادم و با ارجاع به یک کنسول که selfhost شده، میخواستم ازش استفاده کنم، کار نمیکند. به همین سادگی!
کد hub
[HubName("messageHub")]
    public class MessageHub : Hub
    {
        public void NotifyAllClients()
        {
            Clients.All.Notify();
        } 
    }
کلاس startup
public partial class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var hubConfiguration = new HubConfiguration()
            {
                EnableDetailedErrors = true
            };

            appBuilder.MapSignalR(hubConfiguration);

            appBuilder.UseCors(CorsOptions.AllowAll);


        }
    }
نقطه آغازین برنامه:
static void Main(string[] args)
        {
            const string baseAddress = "http://localhost:9000/"; // "http://*:9000/";

            using (var webapp = WebApp.Start<Startup>(baseAddress))
            {
                Console.WriteLine("Start app...");

                var hubConnection = new HubConnection(baseAddress);
                IHubProxy messageHubProxy = hubConnection.CreateHubProxy("messageHub");

                messageHubProxy.On("notify", () =>
                {
                    Console.WriteLine();
                    Console.WriteLine("Notified!");
                });

                hubConnection.Start().Wait();

                Console.WriteLine("Start signalr...");

                bool dontExit = true;
                while (dontExit)
                {
                    var key = Console.ReadKey();
                    if (key.Key == ConsoleKey.Escape) dontExit = false;

                    messageHubProxy.Invoke("NotifyAllClients");
                }

            }
        }
اگر کلاس hub را به داخل پروژه‌ی slefhost منتقل کنم، کار میکند. اما در یک class library دیگر خیر.
نگارش دات نت و ارجاعات همه یکسان است. dotnet 4.5 
آیا نکته ای جاانداخته شده در این نمونه کد؟
مطالب
OpenCVSharp #10
محاسبه و ترسیم Histogram تصاویر

هیستوگرام یک تصویر، توزیع میزان روشنایی آن تصویر را نمایش می‌دهد و در آن تعداد نقاط قسمت‌های روشن تصویر، ترسیم می‌شوند. محاسبه‌ی هیستوگرام تصاویر در حین دیباگ الگوریتم‌های پردازش تصویر، کاربرد زیادی دارند.
OpenCV به همراه متد توکاری است به نام cv::calcHist که قادر است هیستوگرام تعدادی آرایه را محاسبه کند و در C++ API آن قرار دارد. البته هدف اصلی این متد، انجام محاسبات مرتبط است و در اینجا قصد داریم این محاسبات را نمایش دهیم.


تغییر میزان روشنایی و وضوح تصاویر در OpenCV

همانطور که عنوان شد، کار هیستوگرام تصاویر، نمایش توزیع میزان روشنایی نقاط و اجزای آن‌ها است. بنابراین می‌توان جهت مشاهده‌ی تغییر هیستوگرام محاسبه شده با تغییر میزان روشنایی و وضوح تصویر، از متد ذیل کمک گرفت:
private static void updateBrightnessContrast(Mat src, Mat modifiedSrc, int brightness, int contrast)
{
    brightness = brightness - 100;
    contrast = contrast - 100;
 
    double alpha, beta;
    if (contrast > 0)
    {
        double delta = 127f * contrast / 100f;
        alpha = 255f / (255f - delta * 2);
        beta = alpha * (brightness - delta);
    }
    else
    {
        double delta = -128f * contrast / 100;
        alpha = (256f - delta * 2) / 255f;
        beta = alpha * brightness + delta;
    }
    src.ConvertTo(modifiedSrc, MatType.CV_8UC3, alpha, beta);
}
در اینجا src تصویر اصلی است. brightness و contrast، مقادیر میزان روشنایی و وضوح دریافتی از کاربر هستند. این مقادیر را می‌توان به متد ConvertTo ارسال کرد تا src را تبدیل به modifiedSrc نماید و وضوح و روشنایی آن‌را تغییر دهد.

پس از اینکه متد تغییر وضوح تصویر اصلی را تهیه کردیم، می‌توان به پنجره‌ی نمایش تصویر اصلی، دو tracker جهت دریافت brightness و contrast اضافه کرد و به این ترتیب امکان نمایش پویای تغییرات را مهیا نمود:
using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
{
    using (var sourceWindow = new Window("Source", image: src,
           flags: WindowMode.AutoSize | WindowMode.FreeRatio))
    {
        using (var histogramWindow = new Window("Histogram",
               flags: WindowMode.AutoSize | WindowMode.FreeRatio))
        {
            var brightness = 100;
            var contrast = 100;
 
            var brightnessTrackbar = sourceWindow.CreateTrackbar(
                    name: "Brightness", value: brightness, max: 200,
                    callback: pos =>
                    {
                        brightness = pos;
                        updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                    });
 
            var contrastTrackbar = sourceWindow.CreateTrackbar(
                name: "Contrast", value: contrast, max: 200,
                callback: pos =>
                {
                    contrast = pos;
                    updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                });
 
 
            brightnessTrackbar.Callback.DynamicInvoke(brightness);
            contrastTrackbar.Callback.DynamicInvoke(contrast);
 
            Cv2.WaitKey();
        }
    }
}
در اینجا src تصویر اصلی است. پنجره‌ی Source کار نمایش تصویر اصلی را به عهده دارد. همچنین به این پنجره، دو tracker اضافه شده‌اند تا کار دریافت مقادیر روشنایی و وضوح را از کاربر، مدیریت کنند.
پنجره‌ی دومی نیز به نام هیستوگرام در اینجا تعریف شده‌است. در این پنجره قصد داریم هیستوگرام تغییرات پویای تصویر اصلی را نمایش دهیم.



روش محاسبه‌ی هیستوگرام تصاویر و نمایش آن‌ها در OpenCVSharp

کدهای کامل محاسبه‌ی هیستوگرام تصویر اصلی تغییر یافته (modifiedSrc) و سپس نمایش آن‌را در پنجره‌ی histogramWindow، در ادامه ملاحظه می‌کنید:
private static void calculateHistogram1(Window histogramWindow, Mat src, Mat modifiedSrc)
{
    const int histogramSize = 64;
    int[] dimensions = { histogramSize }; // Histogram size for each dimension
    Rangef[] ranges = { new Rangef(0, histogramSize) }; // min/max
 
    using (var histogram = new Mat())
    {
        Cv2.CalcHist(
            images: new[] { modifiedSrc },
            channels: new[] { 0 },
            mask: null,
            hist: histogram,
            dims: 1,
            histSize: dimensions,
            ranges: ranges);
 
        using (var histogramImage = (Mat)(Mat.Ones(rows: src.Rows, cols: src.Cols, type: MatType.CV_8U) * 255))
        {
            // Scales and draws histogram
 
            Cv2.Normalize(histogram, histogram, 0, histogramImage.Rows, NormType.MinMax);
            var binW = Cv.Round((double)histogramImage.Cols / histogramSize);
 
            var color = Scalar.All(100);
 
            for (var i = 0; i < histogramSize; i++)
            {
                Cv2.Rectangle(histogramImage,
                    new Point(i * binW, histogramImage.Rows),
                    new Point((i + 1) * binW, histogramImage.Rows - Cv.Round(histogram.Get<float>(i))),
                    color,
                    -1);
            }
 
            histogramWindow.Image = histogramImage;
        }
    }
}
معادل متد cv::calcHist، متد Cv2.CalcHist در OpenCVSharp است. این متد آرایه‌ای از تصاویر را قبول می‌کند که در اینجا تنها قصد داریم با یک تصویر کار کنیم. به همین جهت آرایه‌های images، اندازه‌های آن‌ها و بازه‌های min/max این تصاویر تنها یک عضو دارند. خروجی این متد پارامتر hist آن است که توسط یک new Mat تامین شده‌است. مقدار dims به یک تنظیم شده‌است؛ زیرا در اینجا تنها قصد داریم شدت نقاط را اندازه گیری کنیم. پارامتر ranges مشخص می‌کند که مقادیر اندازه گیری شده باید در چه بازه‌ایی جمع آوری شوند.
پس از محاسبه‌ی هیستوگرام، یک تصویر خالی پر شده‌ی با عدد یک را توسط متد Mat.Ones ایجاد می‌کنیم. این تصویر به عنوان منبع تصویر هیستوگرام نمایش داده شده، مورد استفاده قرار می‌گیرد. سپس نیاز است اطلاعات محاسبه شده، در مقیاسی قرار گیرند که قابل نمایش باشد. به همین جهت با استفاده از متد Normalize، آن‌ها را در مقیاس و بازه‌ی ارتفاع تصویر، تغییر اندازه خواهیم داد. سپس به کمک متد مستطیل، خروجی آرایه هیستوگرام را در صفحه، با رنگ خاکستری مشخص شده توسط متد Scalar.All ترسیم خواهیم کرد.


همانطور که در این تصویر ملاحظه می‌کنید، با کدرتر شدن تصویر اصلی، هیستوگرام آن، توزیع روشنایی کمتری را نمایش می‌دهد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
استفاده از افزونه Typeahead مجموعه Twitter Bootstrap در ASP.NET MVC
با تعدادی از کامپوننت‌های Bootstrap در مطلب «نگاهی به اجزای تعاملی Twitter Bootstrap» آشنا شدید. یکی دیگر از این افزونه‌ها، Typeahead نام دارد که در حقیقت نوعی Autocomplete text box است. در ادامه قصد داریم نحوه استفاده از آن‌را در ASP.NET MVC بررسی کنیم.

استفاده‌ی استاتیک از افزونه Typeahead

منظور از استفاده‌ی استاتیک، مشخص بودن آرایه عناصر و هچنین درج آن به صورت html encoded در صفحه است. برای این منظور، کنترلر برنامه چنین شکلی را خواهد داشت:
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };
            ViewBag.JsonString = new JavaScriptSerializer().Serialize(array);
            return View();
        }
    }
}
در اینجا یک آرایه با تعداد عناصر مشخص، تبدیل به رشته JSON معادل آن شده و توسط ViewBag.JsonString به View ارسال می‌شود.
View متناظر با آن به نحو ذیل با مشخص سازی نوع data-provide (تا به کتابخانه‌ی جاوا اسکریپتی همراه bootstrap اعلام کند از چه افزونه‌ای در اینجا قرار است استفاده شود)، منبع داده data-source و حداکثر تعداد آیتم ظاهر شونده data-items، می‌تواند طراحی شود:
@{
    ViewBag.Title = "Index";    
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8,
                                  data_source = @ViewBag.JsonString
                              })

به این ترتیب، یک چنین خروجی در صفحه درج می‌شود:
<input autocomplete="off" data-items="8" data-provide="typeahead" 
data-source="[&quot;Afghanistan&quot;,&quot;Albania&quot;,&quot;Algeria&quot;,&quot;American Samoa&quot;,&quot;Andorra&quot;,&quot;Angola&quot;,&quot;Anguilla&quot;,&quot;Antarctica&quot;,&quot;Antigua and/or Barbuda&quot;]" 
id="search" name="search" type="text" value="" />
همانطور که ملاحظه می‌کنید دقیقا data-source تهیه شده مطابق نیاز خاص این افزونه، html encoded است. به علاوه هر جایی در htmlAttributes صفحه از under line استفاده شده، در این سمت به صورت خودکار به - ترجمه گردیده است.
اگر هم بخواهیم برای آن یک Html Helper درست کنیم، می‌توان به نحو ذیل عمل کرد:
        public static MvcHtmlString TypeaheadFor<TModel, TValue>(
                this HtmlHelper<TModel> htmlHelper,
                Expression<Func<TModel, TValue>> expression,
                IEnumerable<string> source,
                int items = 8)
        {
            var jsonString = new JavaScriptSerializer().Serialize(source);
            return htmlHelper.TextBoxFor(
                expression,
                new
                {
                    autocomplete = "off",
                    data_provide = "typeahead",
                    data_items = items,
                    data_source = jsonString
                }
            );
        }


استفاده پویا و Ajax ایی از افزونه Typeahead

اگر بخواهیم data-source را به صورت پویا، هربار از بانک اطلاعاتی دریافت و ارائه دهیم، نیاز به کمی اسکریپت نویسی خواهد بود:
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public JsonResult GetNames(string term)
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };

            var results = array.Where(n =>
                n.StartsWith(term, StringComparison.OrdinalIgnoreCase));

            return Json(results.ToArray(), JsonRequestBehavior.AllowGet);
        }
    }
}
در این حالت، کدهای اکشن متدی که یک عبارت، یا قسمتی از آن را از طریق پارامتر term دریافت و خروجی JSON مناسبی را ارائه می‌کند، همانند متد GetNames فوق خواهد بود.
سپس در تعاریف View، قسمت data-source مرتبط با TextBox حذف و از طریق فراخوانی مستقیم کدهای افزونه typeahead مقدار دهی می‌گردد:
@{
    ViewBag.Title = "Index";
    var url = Url.Action("GetNames", "Home");
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8
                              })
@section JavaScript
{
    <script type="text/javascript">
            $(function () {
                $('#search').typeahead({
                    source: function (term, process) {
                        return $.getJSON('@url', { term: term }, function (data) { return process(data); });
                    }
                });
            });
    </script>
}
در اینجا توسط متد getJSON کتابخانه jQuery، مقدار عبارت وارد شده در TextBox جستجو، به آدرس اکشن متد GetNames ارسال و سپس حاصل به source افزونه typeahead انتساب داده می‌شود.
مطالب
یافتن آدرس نهایی یک Url پس از Redirect

برای مثال آدرس http://feedproxy.google.com/~r/nettuts/~3/tWCksueANyY/ را در نظر بگیرید. گوگل پس از خرید feedburner ، از feedproxy.google.com جهت ردیابی آدرس‌های فیدها استفاده می‌کند؛‌ مثلا فید شما چند نفر خواننده دارد، کدام موارد بیشتر خوانده شده، با چه مرورگرهایی، از چه مکان‌هایی و مواردی از این دست. البته می‌توان در تنظیمات فیدبرنر این نوع آدرس دهی را خاموش کرد ولی به صورت پیش فرض فعال است. مشکل feedproxy.google.com هم این است که در ایران فیلتر است. بنابراین یافتن آدرس اصلی این نوع لینک‌ها پس از Redirect نهایی می‌تواند جهت ارائه عمومی آن‌ها مفید باشد. با استفاده از قطعه کد زیر می‌توان این آدرس را یافت:

using System;
using System.Net;

namespace Linq2Rss
{
public class RedirectFinder
{
public CookieContainer Cookies { set; get; }
public string Url { get; set; }

public string GetRedirectUrl()
{
var hops = 1;
const int MaxRedirects = 20;

do
{
var request = (HttpWebRequest)WebRequest.Create(Url);
request.UserAgent = "MyUA";
request.KeepAlive = true;
request.Referer = Url;
if (Cookies == null) Cookies = new CookieContainer();
request.CookieContainer = Cookies;
request.AllowAutoRedirect = false;

using (var webResp = request.GetResponse() as HttpWebResponse)
{
if ((webResp.StatusCode == HttpStatusCode.Found) ||
(webResp.StatusCode == HttpStatusCode.Redirect) ||
(webResp.StatusCode == HttpStatusCode.Moved) ||
(webResp.StatusCode == HttpStatusCode.MovedPermanently))
{
var newLocation = webResp.Headers["Location"];
if (newLocation.StartsWith("/"))
{
var uri = new Uri(Url);
Url = string.Format("{0}://{1}:{2}{3}", uri.Scheme, uri.Host, uri.Port, newLocation);
}
else
Url = newLocation;
}
else
{
if (webResp.StatusCode == HttpStatusCode.OK)
return Url;
}
}

hops++;
} while (hops <= MaxRedirects);
return Url;
}
}
}


برای یافتن آدرس واقعی یک Url پس از Redirect راهی بجز درخواست آن از وب سرور اولیه وجود ندارد. سپس وضعیت پاسخ داده شده بررسی می‌شود؛ اگر حاوی Found ، Moved یا Redirect بود، به این معنا است که باید آدرس جدید را از هدر پاسخ دریافتی استخراج کنیم. این آدرس، در کلید Location ذخیره می‌شود. اکنون یکبار دیگر نیاز است تا این آدرس جدید بررسی شود، زیرا ممکن است این مورد هم به آدرس دیگری اشاره کند. در کل، کد فوق 20 بار این بررسی را انجام خواهد داد (هر چند عموما در دو یا سه سعی به جواب خواهیم رسید).
مطالب
پردازش داده‌های جغرافیایی به کمک SQL Server و Entity framework
پشتیبانی SQL Server از Spatial data

از SQL Server 2008 به بعد، نوع داده جدیدی به نام geography به نوع‌های قابل تعریف ستون‌ها اضافه شده‌است. در این نوع ستون‌ها می‌توان طول و عرض جغرافیایی یک نقطه را ذخیره کرد و سپس به کمک توابع توکاری از آن‌ها کوئری گرفت.


در اینجا نمونه‌ای از نحوه‌ی تعریف و همچنین مقدار دهی این نوع ستون‌ها را مشاهده می‌کنید:
 CREATE TABLE [Geo](
[id] [int] IDENTITY(1,1) NOT NULL,
[Location] [geography] NULL
)

 insert into Geo( Location , long, lat ) values
( geography::STGeomFromText ('POINT(-121.527200 45.712113)', 4326))
متد geography::STGeoFromText یک SQL CLR function است. این متد در مثال فوق، مختصات یک نقطه را دریافت کرده‌است. همچنین نیاز دارد بداند که این نقطه توسط چه نوع سیستم مختصاتی ارائه می‌شود. عدد 4326 در اینجا یک SRID یا Spatial Reference System Identifier استاندارد است. برای نمونه اطلاعات ارائه شده توسط Google و یا Bing توسط این استاندارد ارائه می‌شوند.
در اینجا متدهای توکار دیگری مانند geography::STDistance برای یافتن فاصله مستقیم بین نقاط نیز ارائه شد‌ه‌اند. خروجی آن بر حسب متر است.


پشتیبانی از Spatial Data در Entity framework

پشتیبانی از نوع مخصوص geography، در EF 5 توسط نوع داده‌ای DbGeography ارائه شد. این نوع داده‌ای immutable است. به این معنا که پس از نمونه سازی، دیگر مقدار آن قابل تغییر نیست.
در اینجا برای نمونه مدلی را مشاهده می‌کنید که از نوع داده‌ای DbGeography استفاده می‌کند:
using System.Data.Entity.Spatial;

namespace EFGeoTests.Models
{
    public class GeoLocation
    {
        public int Id { get; set; }
        public DbGeography Location { get; set; }
        public string Name { get; set; }
        public string Type { get; set; }

        public override string ToString()
        {
            return string.Format("Name:{0}, Location:{1}", Name, Location);
        }
    }
}
به همراه یک Context، تا کلاس GeoLocation در معرض دید EF قرار گیرد:
using System;
using System.Data.Entity;
using EFGeoTests.Models;

namespace EFGeoTests.Config
{
    public class MyContext : DbContext
    {
        public DbSet<GeoLocation> GeoLocations { get; set; }

        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
}
برای مقدار دهی خاصیت Location از نوع DbGeography می‌توان از متد ذیل استفاده کرد که بسیار شبیه به متد geography::STGeoFromText عمل می‌کند:
   private static DbGeography createPoint(double longitude, double latitude,  int coordinateSystemId = 4326)
  {
       var text = string.Format(CultureInfo.InvariantCulture.NumberFormat,"POINT({0} {1})", longitude, latitude);
       return DbGeography.PointFromText(text, coordinateSystemId);
  }


تهیه منبع داده‌ی جغرافیایی

برای تدارک یک مثال واقعی جغرافیایی، نیاز به اطلاعاتی دقیق داریم. این نوع اطلاعات عموما توسط یک سری فایل مخصوص به نام Shapefiles که حاوی اطلاعات برداری جغرافیایی هستند ارائه می‌شوند. برای نمونه اطلاعات جغرافیایی به روز ایران را از آدرس ذیل می‌توانید دریافت کنید:
http://download.geofabrik.de/asia/iran.html
http://download.geofabrik.de/asia/iran-latest.shp.zip

پس از دریافت این فایل، به تعدادی فایل با پسوندهای shp، shx و dbf خواهیم رسید.
فایل‌های shp بیانگر فرمت اشکال ذخیره شده هستند. فایل‌های shx یک سری ایندکس بوده و فایل‌های dbf از نوع بانک اطلاعاتی dBase IV می‌باشند.
همچنین اگر فایل‌های prj را باز کنید، یک چنین اطلاعاتی در آن موجودند:
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
نکته‌ی مهمی که در اینجا باید مدنظر داشت، استاندارد GCS_WGS_1984 آن است. این استاندارد معادل است با استاندارد EPSG 4326. عدد 4326 آن جهت ثبت این اطلاعات در یک بانک اطلاعاتی SQL Server حائز اهمیت است (پارامتر coordinateSystemId در متد createPoint) و ممکن است از هر فایلی به فایل دیگر متفاوت باشد.



خواند‌ن فایل‌های shp در دات نت

پس از دریافت فایل‌های shp و بانک‌های اطلاعاتی مرتبط با اطلاعات جغرافیایی ایران، اکنون نوبت به پردازش این فایل‌های مخصوص با فرمت بانک اطلاعاتی فاکس پرو مانند، رسیده‌است. برای این منظور می‌توان از پروژه‌ی سورس باز ذیل استفاده کرد:

این پروژه در خواندن فایل‌های shp بدون نقص عمل می‌کند اما توانایی خواندن نام‌های فارسی وارد شده در این نوع بانک‌های اطلاعاتی را ندارد. برای رفع این مشکل، سورس آن را از Codeplex دریافت کنید. سپس فایل Shapefile.cs را گشوده و ابتدای خاصیت Current آن‌را به نحو ذیل تغییر دهید:
        /// <summary>
        /// Gets the current shape in the collection
        /// </summary>
        public Shape Current
        {
            get 
            {
                if (_disposed) throw new ObjectDisposedException("Shapefile");
                if (!_opened) throw new InvalidOperationException("Shapefile not open.");
               
                // get the metadata
                StringDictionary metadata = null;
                if (!RawMetadataOnly)
                {
                    metadata = new StringDictionary();
                    for (int i = 0; i < _dbReader.FieldCount; i++)
                    {
                        string value = _dbReader.GetValue(i).ToString();
                        if (_dbReader.GetDataTypeName(i) == "DBTYPE_WVARCHAR")
                        {
                            // برای نمایش متون فارسی نیاز است
                            value = Encoding.UTF8.GetString(Encoding.GetEncoding(720).GetBytes(value));
                        }
                        metadata.Add(_dbReader.GetName(i),
                            value);
                    }
                }
در اینجا فقط سطر استفاده از Encoding خاصی با شماره 720 و تبدیل آن به UTF8 اضافه شده‌است. پس از آن بدون مشکل می‌توان برچسب‌های فارسی را از فایل‌های dBase IV این نوع بانک‌های اطلاعاتی استخراج کرد (اصلاح شده‌ی آن در فایل پیوست مطلب موجود است).
using System.Collections.Generic;
using System.Linq;
using Catfood.Shapefile;

namespace EFGeoTests
{
    public class MapPoint
    {
        public Dictionary<string, string> Metadata { set; get; }
        public double X { set; get; }
        public double Y { set; get; }
    }

    public static class ShapeReader
    {
        public static IList<MapPoint> ReadShapeFile(string path)
        {
            var results = new List<MapPoint>();

            using (var shapefile = new Shapefile(path))
            {
                foreach (var shape in shapefile)
                {
                    if (shape.Type != ShapeType.Point)
                        continue;

                    var shapePoint = shape as ShapePoint;
                    if (shapePoint == null)
                        continue;


                     var metadataNames = shape.GetMetadataNames();
                    if(!metadataNames.Any())
                        continue;

                    var metadata = new Dictionary<string, string>();
                    foreach (var metadataName in metadataNames)
                    {
                        metadata.Add(metadataName,shape.GetMetadata(metadataName));
                    }

                    results.Add(new MapPoint
                    {
                        Metadata = metadata,
                        X = shapePoint.Point.X,
                        Y = shapePoint.Point.Y
                    });
                }
            }

            return results;
        }
    }
}
در کدهای فوق به کمک کتابخانه‌ی C# Esri Shapefile Reader، اطلاعات نقاط بانک اطلاعاتی shape files را خوانده و به صورت لیست‌هایی از MapPoint بازگشت می‌دهیم. نکته‌ی مهم آن، Metadata است که از هر فایلی به فایل دیگر می‌توان متفاوت باشد. به همین جهت این اطلاعات را به شکل ویژگی‌های key/value در این نوع بانک‌های اطلاعاتی ذخیره می‌کنند.


افزودن اطلاعات جغرافیایی به بانک اطلاعاتی SQL Server به کمک Entity framework

فایل places.shp را در مجموعه فایل‌هایی که در ابتدای بحث عنوان شدند، می‌توانید مشاهده کنید. قصد داریم اطلاعات نقاط آن‌را به مدل GeoLocation انتساب داده و سپس ذخیره کنیم:
            var points = ShapeReader.ReadShapeFile("IranShapeFiles\\places.shp");
            using (var context = new MyContext())
            {
                context.Configuration.AutoDetectChangesEnabled = false;
                context.Configuration.ProxyCreationEnabled = false;
                context.Configuration.ValidateOnSaveEnabled = false;

                if (context.GeoLocations.Any())
                    return;

                foreach (var point in points)
                {
                    context.GeoLocations.Add(new GeoLocation
                    {
                        Name = point.Metadata["name"],
                        Type = point.Metadata["type"],
                        Location = createPoint(point.X, point.Y)
                    });
                }

                context.SaveChanges();
            }
تعریف متد createPoint را که بر اساس X و Y نقاط، معادل قابل پذیرش آن‌را جهت SQL Server تهیه می‌کند، در ابتدای بحث مشاهده کردید.
در فایل‌های مرتبط با places.shp، متادیتا name، معادل نام شهرهای ایران است و type آن بیانگر شهر، روستا و امثال آن می‌باشد.
پس از اینکه اطلاعات مکان‌های ایران، در SQL Server ذخیره شدند، نمایش بصری آن‌ها را در management studio نیز می‌توان مشاهده کرد:



کوئری گرفتن از اطلاعات جغرافیایی

فرض کنید می‌خواهیم مکان‌هایی را با فاصله کمتر از 5 کیلومتر از تهران پیدا کنیم:
            var tehran = createPoint(51.4179604, 35.6884243);

            using (var context = new MyContext())
            {
                // find any locations within 5 kilometers ordered by distance
                var locations = context.GeoLocations
                    .Where(loc => loc.Location.Distance(tehran) < 5000)
                    .OrderBy(loc => loc.Location.Distance(tehran))
                    .ToList();

                foreach (var location in locations)
                {
                    Console.WriteLine(location.Name);
                }
            }
همانطور که پیشتر نیز عنوان شد، متد Distance بر اساس متر کار می‌کند. به همین جهت برای تعریف 5 کیلومتر به نحو فوق عمل شده‌است. همچنین نحوه‌ی مرتب سازی اطلاعات نیز بر اساس فاصله از یک مکان مشخص صورت گرفته‌است.
و یا اگر بخواهیم دقیقا بر اساس مختصات یک نقطه، مکانی را بیابیم، می‌توان از متد SpatialEquals استفاده کرد:
            var tehran = createPoint(51.4179604, 35.6884243);
            using (var context = new MyContext())
            {
                // find any locations within 5 kilometers ordered by distance
                var tehranLocation = context.GeoLocations.FirstOrDefault(loc => loc.Location.SpatialEquals(tehran));
                if (tehranLocation != null)
                {
                    Console.WriteLine(tehranLocation.Type);
                }
            }

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
EFGeoTests.zip
 
مطالب
روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
اگر در کدهای خود قطعه کد ذیل را دارید:
using(var client = new HttpClient())
{
   // do something with http client
}
استفاده‌ی از using در اینجا، نه‌تنها غیرضروری و اشتباه است، بلکه سبب از کار افتادن زود هنگام برنامه‌ی شما با صدور استثنای ذیل خواهد شد:
 Unable to connect to the remote server
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.


HttpClient خود را Dispose نکنید

کلاس HttpClient اینترفیس IDisposable را پیاده سازی می‌کند. بنابراین روش استفاده‌ی اصولی آن باید به صورت ذیل و با پیاده سازی خودکار رهاسازی منابع مرتبط با آن باشد:
using (var client = new HttpClient())
{
       var result = await client.GetAsync("http://example.com/");
}
اما در این حال فرض کنید به همین روش تعدادی درخواست را ارسال کرده‌اید:
for (int i = 0; i < 10; i++)
{
      using (var client = new HttpClient())
      {
            var result = await client.GetAsync("http://example.com/");
            Console.WriteLine(result.StatusCode);
      }
}
مشکل این روش، در ایجاد سوکت‌های متعددی است که حتی پس از بسته شدن برنامه نیز باز، باقی خواهند ماند:
  TCP    192.168.1.6:13996      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13997      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13998      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13999      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14000      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14001      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14002      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14003      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14004      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14005      93.184.216.34:http     TIME_WAIT
این یک نمونه‌ی خروجی برنامه‌ی فوق، توسط دستور netstat «پس از بسته شدن کامل برنامه» است.

بنابراین اگر برنامه‌ی شما تعداد زیادی کاربر دارد و یا تعداد زیادی درخواست را به روش فوق ارسال می‌کند، سیستم عامل به حد اشباع ایجاد سوکت‌های جدید خواهد رسید.
این مشکل نیز ارتباطی به طراحی این کلاس و یا زبان #C و حتی استفاده‌ی از using نیز ندارد. این رفتار، رفتار معمول سیستم عامل، با سوکت‌های ایجاد شده‌است. TIME_WAIT ایی را که در اینجا ملاحظه می‌کنید، به معنای بسته شدن اتصال از طرف برنامه‌ی ما است؛ اما سیستم عامل هنوز منتظر نتیجه‌ی نهایی، از طرف دیگر اتصال است که آیا قرار است بسته‌ی TCP ایی را دریافت کند یا خیر و یا شاید در بین راه تاخیری وجود داشته‌است. برای نمونه ویندوز به مدت 240 ثانیه یک اتصال را در این حالت حفظ خواهد کرد، که مقدار آن نیز در اینجا تنظیم می‌شود:
 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay]

بنابراین روش توصیه شده‌ی کار با HttpClient، داشتن یک وهله‌ی سراسری از آن در برنامه و عدم Dispose آن است. HttpClient نیز thread-safe طراحی شده‌است و دسترسی به یک شیء سراسری آن در برنامه‌های چند ریسمانی مشکلی را ایجاد نمی‌کند. همچنین Dispose آن نیز غیرضروری است و پس از پایان برنامه به صورت خودکار توسط سیستم عامل انجام خواهد شد.


تمام اجزای HttpClient به صورت Thread-safe طراحی نشده‌اند

تا اینجا به این نتیجه رسیدیم که روش صحیح کار کردن با HttpClient، نیاز به داشتن یک وهله‌ی Singleton از آن‌را در سراسر برنامه دارد و Dispose صریح آن، بجز اشباع سوکت‌های سیستم عامل و ناپایدار کردن تمام برنامه‌هایی که از آن سرویس می‌گیرند، حاصلی را به همراه نخواهد داشت. در این بین مطابق مستندات HttpClient، استفاده‌ی از متدهای ذیل این کلاس thread-safe هستند:
CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync
اما تغییر این خواص در کلاس HttpClient به هیچ عنوان thread-safe نبوده و در برنامه‌های چند ریسمانی و چند کاربری، مشکل ساز می‌شوند:
BaseAddress
DefaultRequestHeaders
MaxResponseContentBufferSize
Timeout
بنابراین در طراحی کلاس مدیریت کننده‌ی HttpClient برنامه‌ی خود نیاز است به ازای هر BaseAddress‌، یک HttpClient خاص آن‌را ایجاد کرد و HttpClientهای سراسری نمی‌توانند BaseAddress‌های خود را نیز به اشتراک گذاشته و تغییری را در آن ایجاد کنند.


استفاده‌ی سراسری و مجدد از HttpClient، تغییرات DNS را متوجه نمی‌شود

با طراحی یک کلاس مدیریت کننده‌ی سراسری HttpClient با طول عمر Singelton، به یک مشکل دیگر نیز برخواهیم خورد: چون در اینجا از اتصالات، استفاده‌ی مجدد می‌شوند، دیگر تغییرات DNS را لحاظ نخواهند کرد.
برای حل این مشکل، در زمان ایجاد یک HttpClient سراسری، به ازای یک BaseAddress مشخص، باید از ServicePointManager کوئری گرفته و زمان اجاره‌ی اتصال آن‌را دقیقا مشخص کنیم:
var sp = ServicePointManager.FindServicePoint(new Uri("http://thisisasample.com"));
sp.ConnectionLeaseTimeout = 60*1000; //In milliseconds
با این‌کار هرچند هنوز هم از اتصالات استفاده‌ی مجدد می‌شود، اما این استفاده‌ی مجدد، نامحدود نبوده و مدت معینی را پیدا می‌کند.


طراحی یک کلاس، برای مدیریت سراسری وهله‌های HttpClient‌

تا اینجا به صورت خلاصه به نکات ذیل رسیدیم:
- HttpClient باید به صورت یک وهله‌ی سراسری Singleton مورد استفاده قرار گیرد. هر وهله سازی مجدد آن 35ms زمان می‌برد.
- Dispose یک HttpClient غیرضروری است.
- HttpClient تقریبا thread safe طراحی شده‌است؛ اما تعدادی از خواص آن مانند BaseAddress‌  اینگونه نیستند.
- برای رفع مشکل اتصالات چسبنده (اتصالاتی که هیچگاه پایان نمی‌یابند)، نیاز است timeout آن‌را تنظیم کرد.

بنابراین بهتر است این نکات را در یک کلاس به صورت ذیل کپسوله کنیم:
using System;
using System.Collections.Generic;
using System.Net.Http;

namespace HttpClientTips
{
    public interface IHttpClientFactory : IDisposable
    {
        HttpClient GetOrCreate(
            Uri baseAddress,
            IDictionary<string, string> defaultRequestHeaders = null,
            TimeSpan? timeout = null,
            long? maxResponseContentBufferSize = null,
            HttpMessageHandler handler = null);
    }
}

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;

namespace HttpClientTips
{
    /// <summary>
    /// Lifetime of this class should be set to `Singleton`.
    /// </summary>
    public class HttpClientFactory : IHttpClientFactory
    {
        // 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the HttpClient more than
        // once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple
        // threads but only one of the objects succeeds in creating the HttpClient.
        private readonly ConcurrentDictionary<Uri, Lazy<HttpClient>> _httpClients =
                         new ConcurrentDictionary<Uri, Lazy<HttpClient>>();
        private const int ConnectionLeaseTimeout = 60 * 1000; // 1 minute

        public HttpClientFactory()
        {
            // Default is 2 minutes: https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.dnsrefreshtimeout(v=vs.110).aspx
            ServicePointManager.DnsRefreshTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
            // Increases the concurrent outbound connections
            ServicePointManager.DefaultConnectionLimit = 1024;
        }

        public HttpClient GetOrCreate(
           Uri baseAddress,
           IDictionary<string, string> defaultRequestHeaders = null,
           TimeSpan? timeout = null,
           long? maxResponseContentBufferSize = null,
           HttpMessageHandler handler = null)
        {
            return _httpClients.GetOrAdd(baseAddress,
                             uri => new Lazy<HttpClient>(() =>
                             {
                                 // Reusing a single HttpClient instance across a multi-threaded application means
                                 // you can't change the values of the stateful properties (which are not thread safe),
                                 // like BaseAddress, DefaultRequestHeaders, MaxResponseContentBufferSize and Timeout.
                                 // So you can only use them if they are constant across your application and need their own instance if being varied.
                                 var client = handler == null ? new HttpClient { BaseAddress = baseAddress } :
                                               new HttpClient(handler, disposeHandler: false) { BaseAddress = baseAddress };
                                 setRequestTimeout(timeout, client);
                                 setMaxResponseBufferSize(maxResponseContentBufferSize, client);
                                 setDefaultHeaders(defaultRequestHeaders, client);
                                 setConnectionLeaseTimeout(baseAddress, client);
                                 return client;
                             },
                             LazyThreadSafetyMode.ExecutionAndPublication)).Value;
        }

        public void Dispose()
        {
            foreach (var httpClient in _httpClients.Values)
            {
                httpClient.Value.Dispose();
            }
        }

        private static void setConnectionLeaseTimeout(Uri baseAddress, HttpClient client)
        {
            // This ensures connections are used efficiently but not indefinitely.
            client.DefaultRequestHeaders.ConnectionClose = false; // keeps the connection open -> more efficient use of the client
            ServicePointManager.FindServicePoint(baseAddress).ConnectionLeaseTimeout = ConnectionLeaseTimeout; // ensures connections are not used indefinitely.
        }

        private static void setDefaultHeaders(IDictionary<string, string> defaultRequestHeaders, HttpClient client)
        {
            if (defaultRequestHeaders == null)
            {
                return;
            }
            foreach (var item in defaultRequestHeaders)
            {
                client.DefaultRequestHeaders.Add(item.Key, item.Value);
            }
        }

        private static void setMaxResponseBufferSize(long? maxResponseContentBufferSize, HttpClient client)
        {
            if (maxResponseContentBufferSize.HasValue)
            {
                client.MaxResponseContentBufferSize = maxResponseContentBufferSize.Value;
            }
        }

        private static void setRequestTimeout(TimeSpan? timeout, HttpClient client)
        {
            if (timeout.HasValue)
            {
                client.Timeout = timeout.Value;
            }
        }
    }
}
در اینجا به ازای هر baseAddress جدید، یک HttpClient خاص آن ایجاد می‌شود تا در کل برنامه مورد استفاده‌ی مجدد قرار گیرد. برای مدیریت thread-safe ایجاد HttpClientها نیز از نکته‌ی مطلب «الگویی برای مدیریت دسترسی همزمان به ConcurrentDictionary» استفاده شده‌است. همچنین نکات تنظیم ConnectionLeaseTimeout و سایر خواص غیر thread-safe کلاس HttpClient نیز در اینجا لحاظ شده‌اند.

پس از تدارک این کلاس، نحوه‌ی معرفی آن به سیستم باید به صورت Singleton باشد. برای مثال اگر از ASP.NET Core استفاده می‌کنید، آن‌را به صورت ذیل ثبت کنید:
namespace HttpClientTips.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IHttpClientFactory, HttpClientFactory>();
            services.AddMvc();
        }

اکنون، یک نمونه، نحوه‌ی استفاده‌ی از اینترفیس IHttpClientFactory تزریقی به صورت ذیل می‌باشد:
namespace HttpClientTips.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IHttpClientFactory _httpClientFactory;
        public HomeController(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task<IActionResult> Index()
        {
            var host = new Uri("http://localhost:5000");
            var httpClient = _httpClientFactory.GetOrCreate(host);
            var responseMessage = await httpClient.GetAsync("home/about").ConfigureAwait(false);
            var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
            return Content(responseContent);
        }
سرویس IHttpClientFactory یک HttpClient را به ازای host درخواستی ایجاد کرده و در طول عمر برنامه از آن استفاده‌ی مجدد می‌کند. به همین جهت دیگر مشکل اشباع سوکت‌ها در این سیستم رخ نخواهند داد.


برای مطالعه‌ی بیشتر

You're using HttpClient wrong and it is destabilizing your software
Disposable, Finalizers, and HttpClient
Using HttpClient as it was intended (because you’re not)
Singleton HttpClient? Beware of this serious behaviour and how to fix it
Beware of the .NET HttpClient
Effectively Using HttpClient
بازخوردهای پروژه‌ها
چند متد الحاقی پیشنهادی
با سلام
جناب آقای اسم رام من از این چند متد الحاقی خیلی استفاده می‌کنم شاید اگر به این پروژه اضافه شود

using System;
using System.Text;
using System.Xml.Linq;

namespace DotNetTips
{
    public static class ExtentionMethods
    {
        public static string Set_PersianCharacter(this string value)
        {
            return value.Replace('ی', 'ی').Replace('ک', 'ک').Replace('ة', 'ت').Replace('إ', 'ا').Replace('أ', 'ا').Replace('ؤ', 'و');
        }

        public static string Format(this string value, params object[] param)
        {
            //For Example :  "test {0} , {1}, {2} , {3}".Format(10,11,12,13);    return => test 10 , 11, 12 , 13

            return string.Format(value, param);
        }

        public static string Apend(this string text, string value)
        {
            return new StringBuilder(text).Append(value).ToString();
        }

        public static string ApendFormat(this string text, string value, params object[] param)
        {
            return new StringBuilder(text).AppendFormat(value, param).ToString();
        }

        public static int ToInt(this string value)
        {
            return Convert.ToInt32(value);
        }

        public static bool IsNullOrEmpty(this string value)
        {
            return string.IsNullOrEmpty(value.Trim());
        }

        public static bool IsNull(this object obj)
        {
            return obj == null;
        }

        public static XElement AddElement(this XElement element, XElement subElement)
        {
            /* For Example :
            var element = new XElement("Root").AddElement(new XElement("element1")).AddElement(new XElement("element2")); 
            
            return => <Root>
                         <element1 />
                         <element2 />
                      </Root>
            */
            element.Add(subElement);
            return element;
        }
        public static XElement SetAttribute(this XElement element, XName name, object value)
        {
            //For Example : var elementt = new XElement("Root").SetAttribute("name", "").SetAttribute("Id", 10);    return => <Root name="" url="10" />

            element.SetAttributeValue(name, value);
            return element;
        }
    }
}


مطالب
اجرای یک Script حاوی دستورات Go در سی شارپ
سلام ؛

سال نو مبارک ! امیدوارم سال بسیار خوبی در پیش داشته باشید :)

از زمانی که استفاده از ORM‌های Code First رایج شده ، اجرای اسکریپت‌های طولانی جهت ایجاد دیتابیس خیلی استفاده ندارد، اما حالت خاص همیشه پیش می‌آید.
مثلا قصد داریم پیش از آغاز برنامه پس از ایجاد دیتابیس توسط Entity Framework به یک سری جداول فیلدی با نوع داده‌ی Geometry اضافه کنیم. یا باید به دیتابیس یک سری Stored Procedure و View اضافه کرد.
Script‌ها ی Generate شده توسط SQL Server حاوی دستور Go هستند. ADO.NET اجرای Script که حاوی Go باشد را پشتیبانی نمی‌کند.
اما روش‌ها و ترفند‌های زیادی برای اجرای یک فایل Script طولانی حاوی دستور Go روی دیتابیس وجود دارد :
        private static string GetScript()
        {
            string path = AppDomain.CurrentDomain.BaseDirectory +
                          @"Scripts\script.sql";
            var file = new FileInfo(path);

            string script = file.OpenText().ReadToEnd();
            return script;
        }

        private static void ExecuteScript()
        {
            string script = GetScript();

            //split the script on "GO" commands
            var splitter = new[] {"\r\nGO\r\n"};
            string[] commandTexts = script.Split(splitter,
                                                 StringSplitOptions.RemoveEmptyEntries);
            foreach (string commandText in commandTexts)
            {
                using (var ctx = new DbContext())
                {
                    if (!string.IsNullOrEmpty(commandText))
                    {
                        ctx.Database.ExecuteSqlCommand(commandText);
                    }
                }
            }
        }

در اینجا به جای آنکه تلاش کنیم یک فایل را روی دیتابیس یک جا اجرا کنیم دستورات را جدا کرده و تک به تک اجرا می‌کنیم. 
بروزرسانی:
مطالب
شروع به کار با EF Core 1.0 - قسمت 15 - نوشتن آزمون‌های واحد
یکی از مشخصات آزمون‌های واحد، عدم خروج از مرزهای IO سیستم، در حین اجرای آن‌ها است و چون درهنگام کار با بانک‌های اطلاعاتی حتما از مرزهای IO سیستم رد خواهیم شد (کار با شبکه، کار با فایل سیستم، برای به روز رسانی و درج اطلاعات)، نوشتن آزمون‌های واحد واقعی، برای برنامه‌هایی که از ORMها استفاده می‌کنند مشکل است. به همین جهت مباحث mocking، تقلید قسمت‌های مختلف ORMها و جایگزین کردن آن‌ها با نمونه‌های درون حافظه‌ای بسیار مرسوم است. برای رفع این مشکلات، تیم EF Core، یک تامین کننده‌ی بانک اطلاعاتی ویژه‌ی «درون حافظه‌ای» را به نام «Entity Framework Core InMemory provider» ارائه داده‌است. به این ترتیب، این محل ذخیره سازی اطلاعات درون حافظه‌ای، مشکل رد شدن از مرزهای IO سیستم را برطرف کرده و عملا نیاز به کار کردن با فریم ورک‌های mocking را منتفی می‌کند (حداقل برای تقلید قسمت‌های مختلف EF Core).
در این قسمت ابتدا نحوه‌ی فعال سازی فریم ورک آزمون‌های واحد مایکروسافت و سپس نحوه‌ی فعال سازی این تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای را بررسی خواهیم کرد. به علاوه برای سرویس بلاگ‌های قسمت قبل نیز آزمون واحد خواهیم نوشت.


نحوه‌ی فعالسازی فریم ورک MSTest در یک پروژه‌ی Class library از نوع NET Core.


تنها نکته‌ی مهم فعالسازی MSTest در یک پروژه‌ی Class library جدید که برای نوشتن آزمون‌های واحد مورد استفاده قرار خواهیم داد، تنظیمات فایل project.json آن است که در ذیل آمده است:
{
    "version": "1.0.0-*",
 
    "testRunner": "mstest",
    "dependencies": {
        "Microsoft.NETCore.App": {
            "type": "platform",
            "version": "1.0.0"
        },
        "dotnet-test-mstest": "1.1.1-preview",
        "MSTest.TestFramework": "1.0.1-preview",
        "NETStandard.Library": "1.6.0",
        "Microsoft.EntityFrameworkCore": "1.0.0",
        "Microsoft.EntityFrameworkCore.InMemory": "1.0.0",
        "Core1RtmEmptyTest.DataLayer": "1.0.0-*",
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Core1RtmEmptyTest.Services": "1.0.0-*",
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*"
    },
 
    "frameworks": {
        "netcoreapp1.0": {
            "imports": [
                "dnxcore50",
                "portable-net45+win8"
            ]
        }
    }
}
- در اینجا قید testRunner الزامی است؛ در غیراینصورت آزمون‌های واحد شما شناسایی نخواهند شد. همچنین بسته‌های dotnet-test-mstest و MSTest.TestFramework نیز باید اضافه شوند.
- به علاوه در اینجا ارجاعاتی را به اسمبلی‌های موجودیت‌ها، Services و DataLayer که در قسمت «شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها» بررسی شدند نیز ملاحظه می‌کنید.
- همچنین وابستگی جدید Microsoft.EntityFrameworkCore.InMemory نیز در اینجا قابل ملاحظه است. این وابستگی را تنها به پروژه‌ی آزمون‌های واحد خود اضافه می‌کنیم. از این جهت که تنظیمات آن صرفا در این قسمت جدید قید می‌شوند و نه در سایر قسمت‌های برنامه.

 پس از آن، کار با این فریم ورک، همانند سایر نگارش‌های دات نت خواهد بود:
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace EFCore.MsTests
{
    [TestClass]
    public class CoreTests
    {
        [TestMethod]
        public void Test1()
        {
            Assert.IsTrue(true);
        }
    }
}
ابتدا کلاس مدنظر، با ویژگی TestClass مزین می‌شود. سپس متد آزمون واحد نوشته شده نیز باید به صورت public void و مزین شده‌ی با ویژگی TestMethod، ارائه شود.
پس از نوشتن اولین آزمون واحد، یکبار پروژه را build کرده و سپس از منوی Test، گزینه‌ی Windows را انتخاب کرده و در اینجا گزینه‌ی Test Explorer را انتخاب کنید. اندکی صبر کنید تا آزمون‌های واحد شما شناسایی شوند و سپس گزینه‌ی Run All را انتخاب کنید:



تغییرات Context برنامه جهت استفاده‌ی از تامین کننده‌ی داخل حافظه‌ای

در مورد نحوه‌ی تعریف و افزودن وابستگی‌های EF Core در مطلب «شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه» پیشتر بحث شد و همچنین در مطلب «شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر»، اطلاعات Context برنامه را به اسمبلی دیگری منتقل کردیم.
اگر از روش بازنویسی متد OnConfiguring برای تنظیم تامین کننده‌ی بانک اطلاعاتی مورد نظر استفاده می‌کنید، متد OnConfiguring کلاس Context برنامه چنین شکلی را پیدا می‌کند:
public class ApplicationDbContext : DbContext, IUnitOfWork
{
    private readonly IConfigurationRoot _configuration;
 
    public ApplicationDbContext(IConfigurationRoot configuration)
    {
        _configuration = configuration;
    }
 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    } 
 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer(
                _configuration["ConnectionStrings:ApplicationDbContextConnection"]
                , serverDbContextOptionsBuilder =>
                {
                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                    serverDbContextOptionsBuilder.CommandTimeout(minutes);
                });
        }
    }
در اینجا دو تغییر جدید قابل ملاحظه هستند:
الف) اضافه شدن سازنده‌ی دومی که <DbContextOptions<ApplicationDbContext را دریافت می‌کند. از آن در سمت کدهای آزمون واحد برنامه جهت ثبت ()options.UseInMemoryDatabase استفاده می‌شود.
ب) به متد OnConfiguring، بررسی optionsBuilder.IsConfigured هم اضافه شده‌است. چون در سمت کدهای آزمون واحد، تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای اضافه می‌شود، مقدار optionsBuilder.IsConfigured به true تنظیم خواهد شد و دیگر از تامین کننده‌ی SQL Server استفاده نمی‌شود.

اگر از متد OnConfiguring به این شکل استفاده نمی‌کنید، تنها ذکر سازنده‌ی دوم ضروری است. از این جهت که در آزمون‌های واحد، از تنظیمات متد ConfigureServices کلاس آغازین برنامه استفاده نخواهد شد.


نوشتن آزمون‌های واحد مخصوص EF Core

پس از برپایی پیشنیازهای نوشتن آزمون‌ها واحد، شامل تنظیمات فریم ورک MSTest و همچنین افزودن وابستگی‌های مرتبط با فایل project.json ایی که در ابتدای بحث عنوان شد و اصلاح سازنده و متد OnConfiguring کلاس Context برنامه جهت آماده سازی آن‌ها برای پذیرش تامین کننده‌های دیگر، اکنون یک نمونه از آزمون‌های واحد درون حافظه‌ای EF Core، چنین شکلی را خواهد داشت:
using System;
using System.Linq;
using Core1RtmEmptyTest.DataLayer;
using Core1RtmEmptyTest.Entities;
using Core1RtmEmptyTest.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace Core1RtmEmptyTest.MsTests
{
    [TestClass]
    public class CoreTests
    {
        private readonly IServiceProvider _serviceProvider;
 
        public CoreTests()
        {
            var services = new ServiceCollection();
            services.AddEntityFrameworkInMemoryDatabase()
                        .AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase());
 
            services.AddScoped<IUnitOfWork, ApplicationDbContext>();
            services.AddScoped<IBlogService, BlogService>();
 
            _serviceProvider = services.BuildServiceProvider();
        }
 
        [TestMethod]
        public void Find_searches_url()
        {
            // Insert seed data into the database using one instance of the context
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
                    context.Set<Blog>().Add(new Blog { Url = "http://sample.com/cats" });
                    context.Set<Blog>().Add(new Blog { Url = "http://sample.com/catfish" });
                    context.Set<Blog>().Add(new Blog { Url = "http://sample.com/dogs" });
                    context.SaveAllChanges();
                }
            }
 
            // Use a separate instance of the context to verify correct data was saved to database
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
                    Assert.AreEqual(3, context.Set<Blog>().Count());
                    Assert.AreEqual("http://sample.com/cats", context.Set<Blog>().First().Url);
                }
            }
 
            // Use a clean instance of the context to run the test
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                var blogService = serviceScope.ServiceProvider.GetRequiredService<IBlogService>();
                var results = blogService.GetPagedBlogsAsNoTracking(pageNumber: 0, recordsPerPage: 10);
                Assert.AreEqual(3, results.Count);
            }
        }
    }
}
توضیحات:
همانطور که در قسمت «تغییرات Context برنامه جهت استفاده‌ی از تامین کننده‌ی داخل حافظه‌ای» فوق عنوان شد، در حین انجام آزمون‌های واحد، دیگر به کلاس آغازین برنامه و تنظیمات آن مراجعه نمی‌شود. بنابراین باید شبیه به عملکرد متد ConfigureServices آن‌را در اینجا پیاده سازی کرد. نمونه‌ای از انجام اینکار را در سازنده‌ی کلاس انجام آزمون‌های واحد مشاهده می‌کنید:
        private readonly IServiceProvider _serviceProvider;
 
        public CoreTests()
        {
            var services = new ServiceCollection();
            services.AddEntityFrameworkInMemoryDatabase()
                        .AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase());
 
            services.AddScoped<IUnitOfWork, ApplicationDbContext>();
            services.AddScoped<IBlogService, BlogService>();
 
            _serviceProvider = services.BuildServiceProvider();
        }
در اینجا است که توسط متد AddEntityFrameworkInMemoryDatabase، کار افزودن تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای انجام شده و سپس Context برنامه نیز از آن مطلع می‌شود (علت افزودن سازنده‌ی دومی که <DbContextOptions<ApplicationDbContext را دریافت می‌کند).
سپس همانند قبل، باید تمام سرویس‌های مدنظر تنظیم شوند تا بتوان از آن‌ها استفاده کرد.

نکته‌ی مهم دیگری را که باید به آن دقت داشت، ایجاد scope و سپس دسترسی به سرویس‌ها از طریق این Scope است. از این جهت که چون خارج از طول عمر یک درخواست وب قرار داریم، دیگر Scopeها برای ما به صورت خودکار ایجاد و تخریب نمی‌شوند و باید همان‌کاری را که ASP.NET Core در پشت صحنه انجام می‌دهد، به صورت دستی پیاده سازی کنیم:
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
اگر اینکار صورت نگیرد، چون Scope ایی ایجاد و تخریب نمی‌شود، کار کردن با متد serviceProvider.GetRequiredService_ اشتباه بوده و همیشه یک وهله از Context را باز می‌گرداند که مدنظر ما نیست. شبیه به این نکته را در قسمت «مقدار دهی اولیه‌ی جداول بانک‌های اطلاعاتی در EF Core» پیشتر ملاحظه کرده‌اید.


یک نکته‌ی تکمیلی

EF Core به همراه تامین کننده‌ی بانک اطلاعاتی SQLite نیز هست. یکی از نکات ویژه‌ی بانک اطلاعاتی SQLite، امکان تنظیم پارامتری است در رشته‌ی اتصالی آن، که آن‌را نیز تبدیل به یک «بانک اطلاعاتی درون حافظه‌ای» می‌کند. این روش سال‌ها است که جهت انجام آزمون‌های واحد ORMها مورد استفاده قرار می‌گیرد. بنابراین می‌توان آن‌را به عنوان جایگزینی برای مطلب جاری نیز درنظر گرفت.
 var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
services.AddEntityFrameworkSqlite().AddDbContext<CmsDbContext>(options => options.UseSqlite(connection));
اهمیت آن در اینجا است که تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای EF، قیود را اعمال نمی‌کند ؛ اما بانک اطلاعاتی درون حافظه‌ای SQLite واقعا همانند یک بانک اطلاعاتی رابطه‌ای کامل عمل می‌کند.