Node.js Tools 1.0 for Visual Studio (NTVS) is now available for download! NTVS is a free, open source extension for Visual Studio 2012 and Visual Studio 2013 that turns Visual Studio into a Node.js IDE. NTVS 1.0 supports the free Visual Studio Community and Visual Studio Express for Web editions, as well as Visual Studio Professional and higher
بررسی ویژگیهای جدید C# 8
روال کار به این صورت بوده که قبل از رندر شدن صفحه در سمت سرور خروجی نهایی بررسی شده و با استفاده از عبارات با قاعده الگوهای مورد نظر لیست شده و سپس حذف میشوند و در نهایت خروجی مورد نظر حاصل خواهد شد. برای راحتی کار و عدم نوشتن این روال در تمامی صفحات میتواند در مستر پیج این عمل را انجام داد. مثلا:
private static readonly Regex RegexBetweenTags = new Regex(@">\s+<", RegexOptions.Compiled); private static readonly Regex RegexLineBreaks = new Regex(@"\r\s+", RegexOptions.Compiled); protected override void Render(HtmlTextWriter writer) { using (var htmlwriter = new HtmlTextWriter(new System.IO.StringWriter())) { base.Render(htmlwriter); var html = htmlwriter.InnerWriter.ToString(); html = RegexBetweenTags.Replace(html, "> <"); html = RegexLineBreaks.Replace(html, string.Empty); html = html.Replace("//<![CDATA[", "").Replace("//]]>", ""); html = html.Replace("// <![CDATA[", "").Replace("// ]]>", ""); writer.Write(html.Trim()); } }
این عمل در ASP.NET Webform به آسانی انجام شده و باعث حذف فضاهای خالی در خروجی صفحه میشود.
برای انجام این عمل در ASP.NET MVC روال کار به این صورت نیست و نمیتوان مانند ASP.NET Webform عمل کرد.
چون در MVC از ViewPage استفاده میشود و ما مستقیما به خروجی آن دسترسی نداریم یک روش این است که میتوانیم یک کلاس برای ViewPage تعریف کرده و رویداد Write آن را تحریف کرده و مانند مثال بالا فضای خالی را در خروجی حذف کرد. البته برای استفاده باید کلاس ایجاد شده را به عنوان فایل پایه جهت ایجاد صفحات در MVC فایل web.config معرفی کنیم. این روش در اینجا به وضوح شرح داده شده است.
اما هدف ما پیاده سازی با استفاده از اکشن فیلتر هاست. برای پیاده سازی ایتدا یک اکشن فیلتر به نام CompressAttribute تعریف میکنیم مانند زیر:
using System; using System.IO; using System.IO.Compression; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; namespace PWS.Common.ActionFilters { public class CompressAttribute : ActionFilterAttribute { #region Methods (2) // Public Methods (1) /// <summary> /// Called by the ASP.NET MVC framework before the action method executes. /// </summary> /// <param name="filterContext">The filter context.</param> public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = filterContext.HttpContext.Response; if (IsGZipSupported(filterContext.HttpContext.Request)) { String acceptEncoding = filterContext.HttpContext.Request.Headers["Accept-Encoding"]; if (acceptEncoding.Contains("gzip")) { response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); response.AppendHeader("Content-Encoding", "gzip"); } else { response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); response.AppendHeader("Content-Encoding", "deflate"); } } // Allow proxy servers to cache encoded and unencoded versions separately response.AppendHeader("Vary", "Content-Encoding"); //حذف فضاهای خالی
response.Filter = new WhitespaceFilter(response.Filter); } // Private Methods (1) /// <summary> /// Determines whether [is G zip supported] [the specified request]. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> private Boolean IsGZipSupported(HttpRequestBase request) { String acceptEncoding = request.Headers["Accept-Encoding"]; if (acceptEncoding == null) return false; return !String.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"); } #endregion Methods } /// <summary> /// Whitespace Filter /// </summary> public class WhitespaceFilter : Stream { #region Fields (3) private readonly Stream _filter; /// <summary> /// /// </summary> private static readonly Regex RegexAll = new Regex(@"\s+|\t\s+|\n\s+|\r\s+", RegexOptions.Compiled); /// <summary> /// /// </summary> private static readonly Regex RegexTags = new Regex(@">\s+<", RegexOptions.Compiled); #endregion Fields #region Constructors (1) /// <summary> /// Initializes a new instance of the <see cref="WhitespaceFilter" /> class. /// </summary> /// <param name="filter">The filter.</param> public WhitespaceFilter(Stream filter) { _filter = filter; } #endregion Constructors #region Properties (5) //methods that need to be overridden from stream /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// </summary> /// <returns>true if the stream supports reading; otherwise, false.</returns> public override bool CanRead { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking. /// </summary> /// <returns>true if the stream supports seeking; otherwise, false.</returns> public override bool CanSeek { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports writing. /// </summary> /// <returns>true if the stream supports writing; otherwise, false.</returns> public override bool CanWrite { get { return true; } } /// <summary> /// When overridden in a derived class, gets the length in bytes of the stream. /// </summary> /// <returns>A long value representing the length of the stream in bytes.</returns> public override long Length { get { return 0; } } /// <summary> /// When overridden in a derived class, gets or sets the position within the current stream. /// </summary> /// <returns>The current position within the stream.</returns> public override long Position { get; set; } #endregion Properties #region Methods (6) // Public Methods (6) /// <summary> /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed. /// </summary> public override void Close() { _filter.Close(); } /// <summary> /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// </summary> public override void Flush() { _filter.Flush(); } /// <summary> /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns> /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// </returns> public override int Read(byte[] buffer, int offset, int count) { return _filter.Read(buffer, offset, count); } /// <summary> /// When overridden in a derived class, sets the position within the current stream. /// </summary> /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param> /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param> /// <returns> /// The new position within the current stream. /// </returns> public override long Seek(long offset, SeekOrigin origin) { return _filter.Seek(offset, origin); } /// <summary> /// When overridden in a derived class, sets the length of the current stream. /// </summary> /// <param name="value">The desired length of the current stream in bytes.</param> public override void SetLength(long value) { _filter.SetLength(value); } /// <summary> /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// </summary> /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param> /// <param name="count">The number of bytes to be written to the current stream.</param> public override void Write(byte[] buffer, int offset, int count) { string html = Encoding.Default.GetString(buffer); //remove whitespace html = RegexTags.Replace(html, "> <"); html = RegexAll.Replace(html, " "); byte[] outdata = Encoding.Default.GetBytes(html); //write bytes to stream _filter.Write(outdata, 0, outdata.GetLength(0)); } #endregion Methods } }
در کلاس WhitespaceFilter با تحریف متد Write الگوهای فضای خالی موجود در درخواست یافت شده و آنها را حذف میکنیم. در نهایت خروجی این کلاس که از نوع استریم است به ویژگی فیلتر صفحه اعمال میشود.
برای معرفی فیلتر تعریف شده میتوان در فایل Global.asax در رویداد Application_Start به صورت زیر فیلتر مورد نظر را به فیلترهای MVC اعمال کرد.
GlobalFilters.Filters.Add(new CompressAttribute());
پ.ن: جهت سهولت، در این کلاس ها، صفحات فشرده سازی و همزمان فضاهای خالی آنها حذف شده است.
ثبت وقایع توکار در NET Core.
For an application, logging is very important to keep track of that application and keep it error-free. In .NET Core, we don't need any third party logging; instead, we can use built-in logging whenever we want. This is very efficient in terms of code and performance.
Let’s start. Create a new .NET Core application and name it.
این پروژه در 12 بخش گوناگون تقسیم بندی شدهاست که هر کدام در قالب یک فایل HTML میباشد و تمامی اسکریپتهای مورد نیاز به آن افزوده شدهاست. هر بخش به صورت مجزا به شرح یک ویژگی کاربردی در angular-translate میپردازد.
ex1_basic_usage
<script src="Scripts/angular.js"></script> <script src="Scripts/angular-translate.js"></script>
angular.module('app', ['pascalprecht.translate']) .config([ '$translateProvider', function ($translateProvider) { // Adding a translation table for the English language $translateProvider.translations('en_US', { "TITLE": "How to use", "HEADER": "You can translate texts by using a filter.", "SUBHEADER": "And if you don't like filters, you can use a directive.", "HTML_KEYS": "If you don't like an empty elements, you can write a key for the translation as an inner HTML of the directive.", "DATA_TO_FILTER": "Your translations might also contain any static ({{staticValue}}) or random ({{randomValue}}) values, which are taken directly from the model.", "DATA_TO_DIRECTIVE": "And it's no matter if you use filter or directive: static is still {{staticValue}} and random is still {{randomValue}}.", "RAW_TO_FILTER": "In case you want to pass a {{type}} data to the filter, you have only to pass it as a filter parameter.", "RAW_TO_DIRECTIVE": "This trick also works for {{type}} with a small mods.", "SERVICE": "Of course, you can translate your strings directly in the js code by using a $translate service.", "SERVICE_PARAMS": "And you are still able to pass params to the texts. Static = {{staticValue}}, random = {{randomValue}}." }); // Adding a translation table for the Russian language $translateProvider.translations('ru_RU', { "TITLE": "Как пользоваться", "HEADER": "Вы можете переводить тексты при помощи фильтра.", "SUBHEADER": "А если Вам не нравятся фильтры, Вы можете воспользоваться директивой.", "HTML_KEYS": "Если вам не нравятся пустые элементы, Вы можете записать ключ для перевода в как внутренний HTML директивы.", "DATA_TO_FILTER": "Ваши переводы также могут содержать любые статичные ({{staticValue}}) или случайные ({{randomValue}}) значения, которые берутся прямо из модели.", "DATA_TO_DIRECTIVE": "И совершенно не важно используете ли Вы фильтр или директиву: статическое значение по прежнему {{staticValue}} и случайное - {{randomValue}}.", "RAW_TO_FILTER": "Если вы хотите передать \"сырые\" ({{type}}) данные фильтру, Вам всего лишь нужно передать их фильтру в качестве параметров.", "RAW_TO_DIRECTIVE": "Это также работает и для директив ({{type}}) с небольшими модификациями.", "SERVICE": "Конечно, Вы можете переводить ваши строки прямо в js коде при помощи сервиса $translate.", "SERVICE_PARAMS": "И вы все еще можете передавать параметры в тексты. Статическое значение = {{staticValue}}, случайное = {{randomValue}}." }); // Tell the module what language to use by default $translateProvider.preferredLanguage('en_US'); }])
.controller('ctrl', ['$scope', '$translate', function ($scope, $translate) { $scope.tlData = { staticValue: 42, randomValue: Math.floor(Math.random() * 1000) }; $scope.jsTrSimple = $translate.instant('SERVICE'); $scope.jsTrParams = $translate.instant('SERVICE_PARAMS', $scope.tlData); $scope.setLang = function (langKey) { // You can change the language during runtime $translate.use(langKey); // A data generated by the script have to be regenerated $scope.jsTrSimple = $translate.instant('SERVICE'); $scope.jsTrParams = $translate.instant('SERVICE_PARAMS', $scope.tlData); }; }]);
<p> <a href="#" ng-click="setLang('en_US')">English</a> | <a href="#" ng-click="setLang('ru_RU')">Русский</a> </p> <!-- Translation by a filter --> <h1>{{'HEADER' | translate}}</h1> <!-- Translation by a directive --> <h2 translate="SUBHEADER">Subheader</h2> <!-- Using inner HTML as a key for translation --> <p translate>HTML_KEYS</p> <hr> <!-- Passing a data object to the translation by the filter --> <p>{{'DATA_TO_FILTER' | translate: tlData}}</p> <!-- Passing a data object to the translation by the directive --> <p translate="DATA_TO_DIRECTIVE" translate-values="{{tlData}}"></p> <hr> <!-- Passing a raw data to the filter --> <p>{{'RAW_TO_FILTER' | translate:'{ type: "raw" }' }}</p> <!-- Passing a raw data to the filter --> <p translate="RAW_TO_DIRECTIVE" translate-values="{ type: 'directives' }"></p> <hr> <!-- Using a $translate service --> <p>{{jsTrSimple}}</p> <!-- Passing a data to the $translate service --> <p>{{jsTrParams}}</p>
ex2_remember_language_cookies
<script src="Scripts/angular-cookies.js"></script> <script src="Scripts/angular-translate-storage-cookie.js"></script>
// Tell the module to store the language in the cookie $translateProvider.useCookieStorage();
ex3_remember_language_local_storage
این مثال همانند مثال قبل رفتار میکند، با این تفاوت که به جای اینکه کلید زبان کنونی را درون کوکی ذخیره کند، آن را درون Local Storage با نام NG_TRANSLATE_LANG_KEY قرار میدهد. برای اجرا کافیست اسکریپتها و تکه کد زیر را با موارد مثال قبل جایگزین کنید.
<script src="Scripts/angular-translate-storage-local.js"></script> // Tell the module to store the language in the local storage $translateProvider.useLocalStorage();
مثال های ex4_set_a_storage_key و ex5_set_a_storage_prefix نام کلیدی که برای ذخیره سازی زبان کنونی در کوکی یا Local Storage قرار میگیرد را تغییر میدهد که به دلیل سادگی از شرح آن میگذریم.
ex6_namespace_support
translate table در angular-translate قابلیت مفید namespacing را نیز داراست. این قابلیت به ما کمک میکند که جهت کپسوله کردن بخشهای مختلف، ترجمه آنها را با namespaceهای خاص خود نمایش دهیم. به مثال زیر توجه کنید:
$translateProvider.translations('en_US', { "TITLE": "How to use namespaces", "ns1": { "HEADER": "A translations table supports namespaces.", "SUBHEADER": "So you can to structurize your translation table well." }, "ns2": { "HEADER": "Do you want to have a structured translations table?", "SUBHEADER": "You can to use namespaces now." } });
همانطور که توجه میکنید بخش ns1 خود شامل زیر مجموعههایی است و ns2 نیز به همین صورت. هر کدام دارای کلید HEADER و SUBHEADER میباشند. فرض کنید هر کدام از این بخشها میخواهند اطلاعات درون یک section را نمایش دهند. حال به نحوهی فراخوانی این translate tableها دقت کنید:
<!-- section 1: Translate Table Called by ns1 namespace --> <h1 translate>ns1.HEADER</h1> <h2 translate>ns1.SUBHEADER</h2> <!-- section 2: Translate Table Called by ns2 namespace --> <h1 translate>ns2.HEADER</h1> <h2 translate>ns2.SUBHEADER</h2>
به همین سادگی میتوان تمامی بخشها را با namespaceهای مختلف در translate table قرار داد.
در بخش بعدی (پایانی) شش قابلیت دیگر angular translate که شامل فراخوانی translate table از یک فایل JSON، فراخوانی فایلهای translate table به صورت lazy load و تغییر زبان بخشی از صفحه به صورت پویا هستند، بررسی خواهند شد.
فایل پروژه: AngularJs-Translate-BestPractices.zip
کتاب رایگان React.js
using(var client = new HttpClient()) { // do something with http client }
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
بنابراین اگر برنامهی شما تعداد زیادی کاربر دارد و یا تعداد زیادی درخواست را به روش فوق ارسال میکند، سیستم عامل به حد اشباع ایجاد سوکتهای جدید خواهد رسید.
این مشکل نیز ارتباطی به طراحی این کلاس و یا زبان #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
BaseAddress DefaultRequestHeaders MaxResponseContentBufferSize Timeout
استفادهی سراسری و مجدد از 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; } } } }
پس از تدارک این کلاس، نحوهی معرفی آن به سیستم باید به صورت 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); }
برای مطالعهی بیشتر
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
System.Data.SqlClient.SqlException (0x80131904): There is already an object named 'NumberedEntity' in the database.