نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
به روز رسانی
در VS 2017 و ساختار MSBuild ایی آن، فایل‌های project.lock.json, project.json, global.json از پروژه‌های ASP.NET Core حذف شده‌اند.
- معادل فایل global.json اکنون قسمت خواص پروژه و انتخاب فریم ورک مدنظر است:


- معادل فایل project.json، همان فایل csproj برنامه است که اینبار بجای ساختار JSON ایی قبلی، XML ایی شده‌است و اگر بر روی آن کلیک راست کنید، گزینه‌ی Edit آن‌را مشاهده خواهید کرد:


 سایر موارد مانند نصب بسته‌های نیوگت، از طریق رابط کاربری آن تفاوتی نکرده‌اند و یکی است. فقط اینبار بسته‌های نیوگت
به صورت یک ItemGroup، به فایل csproj اضافه می‌شوند:
<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.1" />
اشتراک‌ها
ساخت گزارش دفتر معین در نرم افزار Crystal Reports
یکی از نرم افزارهای پرکاربرد در بازار نرم افزار ایران، سیستم حسابداری می‌باشد. خوب همه ما می‌دانیم که تعداد نرم افزارهای حسابداری خیلی زیاد می‌باشد. کاربران معمولاً نرم افزارهایی را انتخاب می‌کنند که بتوانند گزارش‌های استاندارد مالی را از آن استخراج نمایند. یکی از این گزارش‌ها که در واقع یکی از اصلی‌ترین گزارش‌های سیستم حسابداری است، دفتر معین می‌باشد
ساخت گزارش دفتر معین در نرم افزار Crystal Reports
نظرات مطالب
انتقال SVN به یک سیستم جدید
سلام
من سالها قبل تجربه استفاده از visual source safe را داشتم. ولی همانطور که میدانید این نرم افزار مشکلات بسیاری دارد.
هم اکنون برای پروژه های شخصی خود به دنبال یک نرم افزار Source Controller میگردم. Team Foundation server میتواند گزینه مناسبی باشد ولی آنهم نیاز به نصب یک Windows server و Active Directory دارد.
آیا SVN یا Subversion میتواند پاسخگوی نیاز های شخصی باشد؟
شما از کدامیک از این دو استفاده می کنید؟
زمزمه هائی درباره وجود یک نرم افزار Source Control بر روی Visual Studio 2010 وجود دارد.
آیا باید منتظر تولد نرم افزاری جدید در این زمینه بود؟
مطالب
معادل‌های چندسکویی اجزای فایل web.config در ASP.NET Core
هنوز هم اجزای مختلف فایل web.config در ASP.NET Core قابل تعریف و استفاده هستند؛ اما اگر صرفا بخواهیم از این نوع برنامه‌ها در ویندوز و به کمک وب سرور IIS استفاده کنیم. با انتقال برنامه‌های چندسکویی مبتنی بر NET Core. به سایر سیستم عامل‌ها، دیگر اجزایی مانند استفاده‌ی از ماژول فشرده سازی صفحات IIS و یا ماژول URL rewrite آن و یا تنظیمات static cache تعریف شده‌ی در فایل web.config، شناسایی نشده و تاثیری نخواهند داشت. به همین جهت تیم ASP.NET Core، معادل‌های توکار و چندسکویی را برای عناصری از فایل web.config که به IIS وابسته هستند، تهیه کرده‌است که در ادامه آن‌‌ها را مرور خواهیم کرد.


میان‌افزار چندسکویی فشرده سازی صفحات در ASP.NET Core

پیشتر مطلب «استفاده از GZip توکار IISهای جدید و تنظیمات مرتبط با آن‌ها» را در سایت جاری مطالعه کرده‌اید. این قابلیت صرفا وابسته‌است به IIS و همچنین در صورت نصب بودن ماژول httpCompression آن کار می‌کند. بنابراین قابلیت انتقال به سایر سیستم عامل‌ها را نخواهد داشت و هرچند تنظیمات فایل web.config آن هنوز هم در برنامه‌های ASP.NET Core معتبر هستند، اما چندسکویی نیستند. برای رفع این مشکل، تیم ASP.NET Core، میان‌افزار توکاری را برای فشرده سازی صفحات ارائه داده‌است که جزئی از تازه‌های ASP.NET Core 1.1 نیز به‌شمار می‌رود.
برای نصب آن دستور ذیل را در کنسول پاورشل نیوگت، اجرا کنید:
 PM> Install-Package Microsoft.AspNetCore.ResponseCompression
که معادل است با افزودن وابستگی ذیل به فایل project.json پروژه:
{
    "dependencies": {
        "Microsoft.AspNetCore.ResponseCompression": "1.0.0"
    }
}

مرحله‌ی بعد، افزودن سرویس‌های و میان افزار مرتبط، به کلاس آغازین برنامه هستند. همیشه متدهای Add کار ثبت سرویس‌های میان‌افزار را انجام می‌دهند و متدهای Use کار افزودن خود میان‌افزار را به مجموعه‌ی موجود تکمیل می‌کنند.
public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression(options =>
    {
        options.MimeTypes = Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults.MimeTypes;
    });
}
متد AddResponseCompression کار افزودن سرویس‌های مورد نیاز میان‌افزار ResponseCompression را انجام می‌دهد. در اینجا می‌توان تنظیماتی مانند MimeTypes فایل‌ها و صفحاتی را که باید فشرده سازی شوند، تنظیم کرد. ResponseCompressionDefaults.MimeTypes به این صورت تعریف شده‌است:
namespace Microsoft.AspNetCore.ResponseCompression
{
    /// <summary>
    /// Defaults for the ResponseCompressionMiddleware
    /// </summary>
    public class ResponseCompressionDefaults
    {
        /// <summary>
        /// Default MIME types to compress responses for.
        /// </summary>
        // This list is not intended to be exhaustive, it's a baseline for the 90% case.
        public static readonly IEnumerable<string> MimeTypes = new[]
        {
            // General
            "text/plain",
            // Static files
            "text/css",
            "application/javascript",
            // MVC
            "text/html",
            "application/xml",
            "text/xml",
            "application/json",
            "text/json",
        };
    }
}
اگر علاقمند بودیم تا عناصر دیگری را به این لیست اضافه کنیم، می‌توان به نحو ذیل عمل کرد:
services.AddResponseCompression(options =>
{
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
                                {
                                    "image/svg+xml",
                                    "application/font-woff2"
                                });
            });
در اینجا تصاویر از نوع svg و همچنین فایل‌های فونت woff2 نیز اضافه شده‌اند.

به علاوه options ذکر شده‌ی در اینجا دارای خاصیت options.Providers نیز می‌باشد که نوع و الگوریتم فشرده سازی را مشخص می‌کند. در صورتیکه مقدار دهی نشود، مقدار پیش فرض آن Gzip خواهد بود:
services.AddResponseCompression(options =>
{
  //If no compression providers are specified then GZip is used by default.
  //options.Providers.Add<GzipCompressionProvider>();

همچنین اگر علاقمند بودید تا میزان فشرده سازی تامین کننده‌ی Gzip را تغییر دهید، نحوه‌ی تنظیمات آن به صورت ذیل است:
services.Configure<GzipCompressionProviderOptions>(options =>
{
  options.Level = System.IO.Compression.CompressionLevel.Optimal;
});

به صورت پیش‌فرض، فشرده سازی صفحات Https انجام نمی‌شود. برای فعال سازی آن تنظیم ذیل را نیز باید قید کرد:
 options.EnableForHttps = true;

مرحله‌ی آخر این تنظیمات، افزودن میان افزار فشرده سازی خروجی به لیست میان افزارهای موجود است:
public void Configure(IApplicationBuilder app)
{
   app.UseResponseCompression()  // Adds the response compression to the request pipeline
   .UseStaticFiles(); // Adds the static middleware to the request pipeline  
}
در اینجا باید دقت داشت که ترتیب تعریف میان‌افزارها مهم است و اگر UseResponseCompression پس از UseStaticFiles ذکر شود، فشرده سازی صورت نخواهد گرفت؛ چون UseStaticFiles کار ارائه‌ی فایل‌ها را تمام می‌کند و نوبت اجرا، به فشرده سازی اطلاعات نخواهد رسید.


تنظیمات کش کردن چندسکویی فایل‌های ایستا در ASP.NET Core

تنظیمات کش کردن فایل‌های ایستا در web.config مخصوص IIS به صورت ذیل است :
<staticContent>
   <clientCache httpExpires="Sun, 29 Mar 2020 00:00:00 GMT" cacheControlMode="UseExpires" />
</staticContent>
معادل چندسکویی این تنظیمات در ASP.NET Core با تنظیم Response.Headers میان افزار StaticFiles انجام می‌شود:
public void Configure(IApplicationBuilder app,
                      IHostingEnvironment env,
                      ILoggerFactory loggerFactory)
{
    app.UseResponseCompression()
       .UseStaticFiles(
           new StaticFileOptions
           {
               OnPrepareResponse =
                   _ => _.Context.Response.Headers[HeaderNames.CacheControl] = 
                        "public,max-age=604800" // A week in seconds
           })
       .UseMvc(routes => routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"));
}
در اینجا پیش از آماده شدن یک فایل استاتیک برای ارائه‌ی نهایی، می‌توان تنظیمات Response آن‌را تغییر داد و برای مثال هدر Cache-Control آن‌را به یک هفته تنظیم نمود.


معادل چندسکویی ماژول URL Rewrite در ASP.NET Core

مثال‌هایی از ماژول URL Rewrite را در مباحث بهینه سازی سایت برای بهبود SEO پیشتر بررسی کرده‌ایم (^ و ^ و ^). این ماژول نیز همچنان در ASP.NET Core هاست شده‌ی در ویندوز و IIS قابل استفاده است (البته به شرطی که ماژول مخصوص آن در IIS نصب و فعال شده باشد). معادل چندسکویی این ماژول به صورت یک میان‌افزار توکار به ASP.NET Core 1.1 اضافه شده‌است.
برای استفاده‌ی از آن، ابتدا نیاز است بسته‌ی نیوگت آن‌را به نحو ذیل نصب کرد:
 PM> Install-Package Microsoft.AspNetCore.Rewrite
و یا افزودن آن به لیست وابستگی‌های فایل project.json:
{
    "dependencies": {
        "Microsoft.AspNetCore.Rewrite": "1.0.0"
    }
}

پس از نصب آن، نمونه‌ای از نحوه‌ی تعریف و استفاده‌ی آن در کلاس آغازین برنامه به صورت ذیل خواهد بود:
public void Configure(IApplicationBuilder app)
{
    app.UseRewriter(new RewriteOptions()
                            .AddRedirectToHttps()
                            .AddRewrite(@"app/(\d+)", "app?id=$1", skipRemainingRules: false) // Rewrite based on a Regular expression
                            //.AddRedirectToHttps(302, 5001) // Redirect to a different port and use HTTPS
                            .AddRedirect("(.*)/$", "$1")  // remove trailing slash, Redirect using a regular expression
                            .AddRedirect(@"^section1/(.*)", "new/$1", (int)HttpStatusCode.Redirect)
                            .AddRedirect(@"^section2/(\\d+)/(.*)", "new/$1/$2", (int)HttpStatusCode.MovedPermanently)
                            .AddRewrite("^feed$", "/?format=rss", skipRemainingRules: false));
این میان‌افزار نیز باید پیش از میان افزار فایل‌های ایستا و همچنین MVC معرفی شود.

در اینجا مثال‌هایی را از اجبار به استفاده‌ی از HTTPS، تا حذف / از انتهای مسیرهای وب سایت و یا هدایت آدرس قدیمی فید سایت، به آدرسی جدید واقع در مسیر format=rss، توسط عبارات باقاعده مشاهده می‌کنید.
در این تنظیمات اگر پارامتر skipRemainingRules به true تنظیم شود، به محض برآورده شدن شرط انطباق مسیر (پارامتر اول ذکر شده)، بازنویسی مسیر بر اساس پارامتر دوم، صورت گرفته و دیگر شرط‌های ذکر شده، پردازش نخواهند شد.

این میان‌افزار قابلیت دریافت تعاریف خود را از فایل‌های web.config و یا htaccess (لینوکسی) نیز دارد:
 app.UseRewriter(new RewriteOptions()
.AddIISUrlRewrite(env.ContentRootFileProvider, "web.config")
.AddApacheModRewrite(env.ContentRootFileProvider, ".htaccess"));
بنابراین اگر می‌خواهید تعاریف قدیمی <system.webServer><rewrite><rules> وب کانفیگ خود را در اینجا import کنید، متد AddIISUrlRewrite چنین کاری را به صورت خودکار برای شما انجام خواهد داد و یا حتی می‌توان این تنظیمات را در یک فایل UrlRewrite.xml نیز قرار داد تا توسط IIS پردازش نشود و مستقیما توسط ASP.NET Core مورد استفاده قرار گیرد.

و یا اگر خواستید منطق پیچیده‌تری را نسبت به عبارات باقاعده اعمال کنید، می‌توان یک IRule سفارشی را نیز به نحو ذیل تدارک دید:
public class RedirectWwwRule : Microsoft.AspNetCore.Rewrite.IRule
{
    public int StatusCode { get; } = (int)HttpStatusCode.MovedPermanently;
    public bool ExcludeLocalhost { get; set; } = true;
    public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        var host = request.Host;
        if (host.Host.StartsWith("www", StringComparison.OrdinalIgnoreCase))
        {
            context.Result = RuleResult.ContinueRules;
            return;
        }
        if (ExcludeLocalhost && string.Equals(host.Host, "localhost", StringComparison.OrdinalIgnoreCase))
        {
            context.Result = RuleResult.ContinueRules;
            return;
        }
        string newPath = request.Scheme + "://www." + host.Value + request.PathBase + request.Path + request.QueryString;
 
        var response = context.HttpContext.Response;
        response.StatusCode = StatusCode;
        response.Headers[HeaderNames.Location] = newPath;
        context.Result = RuleResult.EndResponse; // Do not continue processing the request
    }
}
در اینجا تنظیم context.Result به RuleResult.ContinueRules سبب ادامه‌ی پردازش درخواست جاری، بدون تغییری در نحوه‌ی پردازش آن خواهد شد. در آخر کار، با تغییر HeaderNames.Locatio به مسیر جدید و تنظیم Result = RuleResult.EndResponse، سبب اجبار به بازنویسی مسیر درخواستی، به مسیر جدید تنظیم شده، خواهیم شد.

و سپس می‌توان آن‌را به عنوان یک گزینه‌ی جدید Rewriter معرفی نمود:
 app.UseRewriter(new RewriteOptions().Add(new RedirectWwwRule()));
کار این IRule جدید، اجبار به درج www در آدرس‌های هدایت شده‌ی به سایت است؛ تا تعداد صفحات تکراری گزارش شده‌ی توسط گوگل به حداقل برسد (یک نگارش با www و دیگری بدون www).

یک نکته: در اینجا در صورت نیاز می‌توان از تزریق وابستگی‌های در سازنده‌ی کلاس Rule جدید تعریف شده نیز استفاده کرد. برای اینکار باید RedirectWwwRule را به لیست سرویس‌های متد ConfigureServices معرفی کرد و سپس نحوه‌ی دریافت وهله‌ای از آن جهت معرفی به میان‌افزار بازنویسی مسیرهای وب به صورت ذیل درخواهد آمد:
 var options = new RewriteOptions().Add(app.ApplicationServices.GetService<RedirectWwwRule>());
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers
به روز رسانی
با حذف فایل project.json در VS 2017، اکنون با کلیک راست بر روی گروه نام پروژه (فایل csproj)، گزینه‌ی Edit آن ظاهر شده و مداخل ذکر شده‌ی در مطلب فوق، چنین تعاریفی را پیدا می‌کنند: 
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Razor.Runtime" Version="1.1.1" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" />
  </ItemGroup>
</Project>

همچنین Razor Tools به صورت یک افزونه‌ی مجزا درآمده‌است که از اینجا قابل دریافت می‌باشد (و به صورت پیش فرض در VS 2017 نصب است). اطلاعات بیشتر
مطالب
کار با اسکنر در برنامه های تحت وب (قسمت دوم و آخر)

در قسمت قبل « کار با اسکنر در برنامه‌های تحت وب (قسمت اول) » دیدی از کاری که قرار است انجام دهیم، رسیدیم. حالا سراغ یک پروژه‌ی عملی و پیاده سازی مطالب مطرح شده می‌رویم.

ابتدا پروژه‌ی   WCF را شروع می‌کنیم. ویژوال استودیو را باز کرده و از قسمت New Project > Visual C# > WCF یک پروژه‌ی WCF Service Application جدید را مثلا با نام "WcfServiceScanner" ایجاد نمایید. پس از ایجاد، دو فایل IService1.cs و Service1.scv موجود را به IScannerService و ScannerService تغییر نام دهید. سپس ابتدا محتویات کلاس اینترفیس IScannerService را به صورت زیر تعریف نمایید :

    [ServiceContract]
    public interface IScannerService
    {
        [OperationContract]
        [WebInvoke(Method = "GET",
           BodyStyle = WebMessageBodyStyle.Wrapped,
           RequestFormat = WebMessageFormat.Json,
           ResponseFormat = WebMessageFormat.Json,
           UriTemplate = "GetScan")]
        string GetScan();
    }
در اینجا ما فقط اعلان متدهای مورد نیاز خود را ایجاد کرده‌ایم. علت استفاده از Attribute ایی با نام WebInvoke ، مشخص نمودن نوع خروجی به صورت Json است و همچنین عنوان آدرس مناسبی برای صدا زدن متد. پس از آن کلاس ScannerService را مطابق کدهای زیر تغییر دهید:
    public class ScannerService : IScannerService
    {
        public string GetScan()
        {
            // TODO Add code here
        }
    }
تا اینجا فقط یک WCF Service معمولی ساخته‌ایم .در ادامه به سراغ کلاس WIA برای ارتباط با اسکنر می‌رویم.
بر روی پروژه‌ی خود راست کلیک کرده و Add Reference را انتخاب نموده و سپس در قسمت COM، گزینه‌ی Microsoft Windows Image Acquisition Library v2.0 را به پروژه‌ی خود اضافه نمایید.
با اضافه شدن این ارجاع به پروژه، دسترسی به فضای نام WIA برای ما امکان پذیر می‌شود که ارجاعی از آن را در کلاس ScannerService قرار می‌دهیم.
using WIA;
اکنون متد GetScan را مطابق زیر اصلاح می‌نماییم:
        public string GetScan()
        {
            var imgResult = String.Empty;
            var dialog = new CommonDialogClass();
            try
            {
                // نمایش فرم پیشفرض اسکنر
                var image = dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType);
                
                // ذخیره تصویر در یک فایل موقت
                var filename = Path.GetTempFileName();
                image.SaveFile(filename);
                var img = Image.FromFile(filename);

                // img جهت ارسال سمت کاربر و نمایش در تگ Base64 تبدیل تصویر به 
                imgResult = ImageHelper.ImageToBase64(img, ImageFormat.Jpeg);
            }
            catch
            {
                // از آنجاییه که امکان نمایش خطا وجود ندارد در صورت بروز خطا رشته خالی 
                // بازگردانده می‌شود که به معنای نبود تصویر می‌باشد
            }

            return imgResult;
        }
دقت داشته باشید که کدها را در زمان توسعه بین Try..Catch قرار ندهید چون ممکن‌است در این زمان به خطاهایی برخورد کنید که نیاز باشد در مرورگر آنها را دیده و رفع خطا نمایید.
CommonDialogClass  کلاس اصلی در اینجا جهت نمایش فرم کار با اسکنر می‌باشد و متد‌های مختلفی را جهت ارتباط با اسکنر در اختیار ما قرار می‌دهد که بسته به نیاز خود می‌توانید از آنها استفاده کنید. برای نمونه در مثال ما نیز متد اصلی که مورد استفاده قرار گرفته، ShowAcquireImage می‌باشد که این متد، فرم پیش فرض دریافت اسکنر را به کاربر نمایش می‌دهد و کاربر از طریق آن می‌تواند قبل از شروع اسکن، یکسری تنظیمات را انجام دهد.
این متد ابتدا به صورت خودکار فرم تعیین دستگاه اسکنر ورودی را نمایش داده :


و سپس فرم پیش فرض اسکنر‌های TWAIN را جهت تعیین تنظیمات اسکن نمایش می‌دهد که این امکان نیز در این فرم فراهم است تا دستگاه‌های Feeder یا Flated انتخاب گردند.

خروجی این متد همان عکس اسکن شده است که از نوع WIA.ImageFile می‌باشد و ما پس از دریافتش، ابتدا آن را در یک فایل موقت ذخیره نموده و سپس با استفاده از یک متد کمکی آن را به فرمت Base64 برای درخواست کننده اسکن ارسال می‌نماییم.

کدهای کلاس کمکی ImageHelper:

        public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format)
        {
            if (image != null)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    // Convert Image to byte[]
                    image.Save(ms, format);
                    byte[] imageBytes = ms.ToArray();

                    // Convert byte[] to Base64 String
                    string base64String = Convert.ToBase64String(imageBytes);
                    return base64String;
                }
            }
            return String.Empty;
        }
توجه داشته باشید که خروجی این متد قرار است توسط callBack یک متد جاوا اسکریپتی مورد استفاده قرار گرفته و احیانا عکس مورد نظر در صفحه نمایش داده شود. پس بهتر است که از قالب تصویر به شکل Base64 استفاده گردد. ضمن اینکه پلاگین‌های Jquery مرتبط با ویرایش تصویر هم از این قالب پشتیبانی می‌کنند. (اینجا )

این مثال به ساده‌ترین شکل نوشته شد. کلاس دیگری هم در اینجا وجود دارد و در صورتیکه از اسکنر نوع Feeder استفاده می‌کنید، می‌توانید از کدهای آن استفاده کنید.

کار ما تا اینجا در پروژه‌ی WCF Service تقریبا تمام است. اگر پروژه را یکبار Build نمایید برای اولین بار احتمالا پیغام خطاهای زیر ظاهر خواهند شد:


جهت رفع این خطا، در قسمت Reference‌های پروژه خود، WIA را انتخاب نموده و از Properties‌های آن خصوصیت Embed Interop Types را به False تغییر دهید؛ مشکل حل می‌شود.

به سراغ پروژه‌ی ویندوز فرم جهت هاست کردن این WCF سرویس می‌رویم. می‌توانید این سرویس را بر روی یک Console App یا Windows Service هم هاست کنید که در اینجا برای سادگی مثال، از WinForm استفاده می‌کنیم.
یک پروژه‌ی WinForm جدید را ایجاد کنید و سپس از قسمت Add Reference > Solution به مسیر پروژه‌ی قبلی رفته و dll‌‌های آن را به پروژه خود اضافه نمایید.
Form1.cs  را باز کرده و ابتدا دو متغیر زیر را در آن به صورت عمومی تعریف نمایید:
        private readonly Uri _baseAddress = new Uri("http://localhost:6019");
        private ServiceHost _host;
برای استفاده از کلاس ServiceHost لازم است تا ارجاعی به فضای نام System.ServiceModel داده شود. متغیر baseAddress_ نگه دارنده‌ی آدرس ثابت سرویس اسکنر در سمت کلاینت می‌باشد و به این ترتیب ما دقیقا می‌دانیم باید سرویس را با کدام آدرس در کدهای جاوا اسکریپتی خود فراخوانی نماییم.
حال در رویداد Form_Load برنامه، کدهای زیر را جهت هاست کردن سرویس اضافه می‌نماییم:
        private void Form1_Load(object sender, EventArgs e)
        {
            _host = new ServiceHost(typeof(WcfServiceScanner.ScannerService), _baseAddress);
            _host.Open();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _host.Close();
        }
همین چند خط برای هاست کردن سرویس روی آدرس localhost و پورت 8010 کامپیوتر کلاینت کافی است. اما یکسری تنظیمات مربوط به خود سرویس هم وجود دارد که باید در زمان پیاده سازی سرویس، در خود پروژه‌ی سرویس، ایجاد می‌گردید. اما از آنجا که ما قرار است سرویس را در یک پروژه‌ی دیگر هاست کنیم، بنابراین این تنظیمات را باید در همین پروژه‌ی WinForm قرار دهیم.
فایل App.Config پروژه‌ی WinForm را باز کرده و کدهای آنرا مطابق زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="BehaviourMetaData">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WcfServiceScanner.ScannerService"
               behaviorConfiguration="BehaviourMetaData">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="WcfServiceScanner.IScannerService" />
      </service>
    </services>
  </system.serviceModel>

</configuration>
اکنون پروژه‌ی هاست آماده اجرا می‌باشد. اگر آنرا اجرا کنید و در مرورگر خود آدرس ذکر شده را وارد کنید، صفحه‌ی زیر را مشاهده خواهد کرد که به معنی صحت اجرای سرویس اسکنر می‌باشد.

اگر موفق به اجرا نشدید و احیانا با خطای زیر مواجه شدید، اطمینان حاصل کنید که ویژوال استودیو Run as Administrator باشد. مشکل حل خواهد شد.


به سراغ پروژه‌ی بعدی، یعنی وب سایت خود می‌رویم. یک پروژه‌ی MVC جدید ایجاد نمایید و در View مورد نظر خود، کدهای زیر را جهت صدا زدن متد GetScan اضافه می‌کنیم.
( از آنجا که کدها به صورت جاوا اسکریپت می‌باشد، پس مهم نیست که حتما پروژه MVC باشد؛ یک صفحه‌ی HTML ساده هم کافی است).
<a href="#" id="get-scan">Get Scan</a>
<img src="" id="img-scanned" />
<script>
    $("#get-scan").click(function () {
        var url = 'http://localhost:6019/';
        $.get(url, function (data) {
            $("#img-scanned").attr("src","data:image/Jpeg;base64,  "+ data.GetScanResult);
        });
    });
</script>
دقت کنید در هنگام دریافت اطلاعات از سرویس، نتیجه به شکل GetScanResult خواهد بود. الان اگر پروژه را اجرا نمایید و روی لینک کلیک کنید، اسکنر شروع به دریافت اسکن خواهد کرد اما نتیجه‌ای بازگشت داده نخواهد شد و علت هم مشکل امنیتی CORS می‌باشد که به دلیل دریافت اطلاعات از یک دامین دیگر رخ می‌دهد و اگر با Firebug درخواست را بررسی کنید متوجه خطا به شکل زیر خواهید شد.


راه حل‌های زیادی برای این مشکل ارائه شده است، و متاسفانه بسیاری از آنها در شرایط پروژه‌ی ما جوابگو نمی‌باشد (به دلیل هاست روی یک پروژه ویندوزی). تنها راه حل مطمئن (تست شده) استفاده از یک کلاس سفارشی در پروژه‌ی WCF Service  می‌باشد که مثال آن در اینجا آورده شده است.
برای رفع مشکل به پروژه WcfServiceScanner بازگشته و کلاس جدیدی را به نام CORSSupport ایجاد کرده و کدهای زیر را به آن اضافه کنید:
    public class CORSSupport : IDispatchMessageInspector
    {
        Dictionary<string, string> requiredHeaders;
        public CORSSupport(Dictionary<string, string> headers)
        {
            requiredHeaders = headers ?? new Dictionary<string, string>();
        }

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;
            if (httpRequest.Method.ToLower() == "options")
                instanceContext.Abort();
            return httpRequest;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            var httpRequest = correlationState as HttpRequestMessageProperty;

            foreach (var item in requiredHeaders)
            {
                httpResponse.Headers.Add(item.Key, item.Value);
            }
            var origin = httpRequest.Headers["origin"];
            if (origin != null)
                httpResponse.Headers.Add("Access-Control-Allow-Origin", origin);

            var method = httpRequest.Method;
            if (method.ToLower() == "options")
                httpResponse.StatusCode = System.Net.HttpStatusCode.NoContent;

        }
    }

    // Simply apply this attribute to a DataService-derived class to get
    // CORS support in that service
    [AttributeUsage(AttributeTargets.Class)]
    public class CORSSupportBehaviorAttribute : Attribute, IServiceBehavior
    {
        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            var requiredHeaders = new Dictionary<string, string>();

            //Chrome doesn't accept wildcards when authorization flag is true
            //requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "Accept, Origin, Authorization, X-Requested-With,Content-Type");
            requiredHeaders.Add("Access-Control-Allow-Credentials", "true");
            foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.MessageInspectors.Add(new CORSSupport(requiredHeaders));
                }
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }
فضاهای نام لازم برای این کلاس به شرح زیر می‌باشد:
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
کلاس ScannerService را باز کرده و آنرا به ویژگی
    [CORSSupportBehavior]
    public class ScannerService : IScannerService
    {
مزین نمایید.

کار تمام است، یکبار دیگر ابتدا پروژه‌ی WcfServiecScanner و سپس پروژه هاست را Build کرده و برنامه‌ی هاست را اجرا کنید. اکنون مشاهده می‌کنید که با زدن دکمه‌ی اسکن، اسکنر فرم تنظیمات اسکن را نمایش می‌دهد که پس از زدن دکمه‌ی Scan، پروسه آغاز شده و پس از اتمام، تصویر اسکن شده در صفحه‌ی وب سایت نمایش داده می‌شود.
مطالب
ویژگی های آگهی استخدام مناسب همراه با نقدی بر یک آگهی استخدام
امروز فرصتی پیش آمد تا سری هم به بخش آگهی‌های این سایت بزنم. با یک آگهی استخدام (مربوط به شرکتی تحت عنوان فناوران سیستم دلفین) روبرو شدم که نظرم را جلب کرد. تقریبا مدت‌ها بود که با یک آگهی استخدام به این صورت روبرو نشده بودم. در رشته کامپیوتر و اغلب در بخش تولید و پشتیبانی نرم افزار کمتر با آگهی‌های استخدام این چنینی که کامل باشند(البته دارای ضعف هایی نیز بود) روبرو می‌شویم. تصمیم گرفتم در این پست به تشریح ویژگیهای یک آگهی استخدام خوب (همراه با نقدی بر این آگهی استخدام ) بپردازم. یک جمله معروف وجود دارد با این مضمون که، استخدام دو مرحله دارد:
  • جذب
  • انتخاب یا گزینش

مرحله جذب:

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

مرحله انتخاب:

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

مواردی که در آگهی استخدام شرکت دلفین رعایت شده بود:

»ذکر نام شرکت

اولین مورد که در بسیاری از آگهی‌های استخدام رعایت نمی‌شود عدم ذکر نام شرکت است. دیده می‌شود در آگهی‌ها بیشتر از عناوینی نظیر "یک شرکت معتبر نرم افزاری" یا "یک شرکت فعال در زمینه تولید نرم افزار" و... استفاده می‌کنند. ذکر نام شرکت از آن جهت مهم است که فرد متقاضی می‌تواند با یک جستجوی ساده در اینترنت (در صورتی که شرکت تقاضا دهنده یک وب سایت مناسب در این زمینه داشته باشد) بسیاری از اطلاعات مهم نظیر زمینه فعالیت شرکت ، مشتریان عمده شرکت ، محصولات تولید شده قبلی یا حتی آدرس شرکت (برای بررسی وضعیت رفت و آمد) را به دست بیاورد. در آگهی استخدام ذکر شده این مورد به خوبی رعایت شده بود.

»ذکر آدرس و شماره تماس:

دلیل اصلی برای ذکر آدرس شرکت صرفا برای بررسی وضعیت رفت و آمد از نظر شرایط ترافیک شهری و بعد مسافت یا حتی شرایط رفت و آمد با خودروی شخصی است که برای بیشتر متقاضیان مهم است. در آگهی بالا نیز این مورد رعایت شده بود.

» ذکر شرایط استخدام

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

»ذکر شرایط تخصصی متقاضیان کار:

در بخش به صورت کامل شرایط تخصصی و مهارتی افراد مورد بررسی قرار گرفته بود. در یک نگاه خوب به نظر می‌رسید ولی اگر با دقت به این بخش نگاه کنید متوجه خواهید شد به دلیل عدم نگارش صحیح موارد مورد نیاز، کمتر کسی دارای این همه توانایی است آن هم به صورت تسلط کامل. استفاده از واژه تسلط کامل باید با احتیاط انجام شود. برای مثال تسلط کامل بر VB.NET , C#.NET Windows Application . بهتر بود از یا استفاده می‌شد. به این صورت " تسلط بر VB.NET یا C#.NET  در Windows Application"

» ذکر اولویت ها

این مورد هم به خوبی در آگهی بالا رعایت شده بود. ذکر اولویت‌ها باعث می‌شود که فرد متقاضی اگر دارای حتی یکی از این موارد است آن را در رزومه خود ذکر کند و از طرفی می‌توان با توجه به اولویت‌های استخدام تا حدودی شرایط کاری در شرکت متقاضی را در همین ابتدا بررسی کرد. 

»ذکر آدرس وب سایت شرکت:

این مورد هم به خوبی در این آگهی رعایت شد و علاوه بر آن یک لینک به آدرس وب سایت سرپرست تیم برنامه نویسان نیز وجود داشت که امکان ارتباط مستقیم را فراهم می‌ساخت. 

مواردی که در آگهی استخدام شرکت دلفین رعایت نشده بود:

»عدم توازن بین عنوان آگهی با محتوای آن :

در عنوان آگهی ذکر شده بود "برنامه نویس تحت وب" در حالی که در شرایط تخصصی، موردی به عنوان تسلط کامل بر برنامه نویس تحت ویندوز ذکر شده  آن هم با دو زبان Vb.Net و C#.Net

آیا منظور از تسلط کامل بر مدیریت بانک اطلاعاتی SqlServer همان DBA است؟ اگر پاسخ مثبت است در این صورت چند نفر واجد شرایط را می‌توان پیدا کرد که هم در سطح DBA باشند و هم در سطح یک برنامه نویس سطح بالا؟ اگر پاسخ منفی است بهتر بود از واژه داشتن تجربه در زمینه مدیریت بانک اطلاعاتی یا حتی آشنایی با مدیریت بانک‌های اطلاعاتی استفاده می‌شد.

مورد دیگری که تا حدودی باعث سردرگمی می‌شد این است که نیاز به تسلط کامل به Asp.Net Web Form و هم چنین به Asp.Net MVC است. چرا هردو، آن هم در سطح تسلط کامل؟

در بخش اولویت‌ها موردی ذکر شد با عنوان آشنایی با کامپوننت‌های برنامه نویسی نظیر DevExpress و Telerik  و Kendo. آیا منظور آشنایی با تمام این موارد بوده است یا فقط یکی از آن ها؟

یک ضعف بزرگ که در اغلب آگهی‌ها (از جمله در این آگهی نیز) وجود دارد، عدم ذکر شرایط شرکت برای متقاضیان است. بهتر بود بعد از ذکر این همه شرایط برای متقاضیان  و برنامه نوسان، کمی هم درباره وضعیت حقوق و مزایا (برای مثال ذکر حداقل حقوق) و بیمه یا حتی شرایط رفاهی و خاص در نظر گرفته شده (که البته با توجه به وضعیت فعلی که دریافت حقوق ماهانه بدون تاخیر یک موهبت است این مورد بیشتر شبیه به رویا برای برنامه نویسان خواهد بود) صحبت می‌شد. 

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

مطالب
آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 1

آزمایش واحد چیست؟

آزمایش واحد (unit testing) هنر و تمرین بررسی صحت عملکرد قطعه‌ای از کد (که در اینجا واحد نامیده شده است)، به وسیله کدهای دیگری است که توسط برنامه نویس نوشته خواهند شد. عموما این آزمایش‌ها جهت بررسی یک متد تهیه می‌شوند. در این مرحله باید درنظر داشت که هدف، بررسی کارآیی نرم افزار نیست. هدف این است که بررسی کنیم آیا قطعه کد جدیدی که به برنامه اضافه شده است درست کار می‌کند و آیا هدف اصلی از توسعه آن‌را برآورده می‌سازد؟
برای مثال متدی را توسعه داده‌اید که آدرس یک دومین را از آدرس اینترنتی دریافت شده، جدا می‌سازد. با استفاده از آزمایشات واحد متعدد می‌توان از صحت عملکرد آن اطمینان حاصل کرد.


اهمیت و مزایای آزمایش واحد کدامند؟

  • کامپایل شدن کد به معنای صحت عملکرد آن نیست. حتما نیاز به روش‌هایی برای آزمایش سیستم وجود دارد. صرفا به شما حقوق داده نمی‌شود که کد بنویسید. به شما حقوق داده می‌شود که کد قابل اجرایی را تهیه کنید.
  • نوشتن آزمایش‌های واحد به تولید کدهایی با کیفیت بالا در دراز مدت منجر خواهد شد. برای نمونه فرض کنید سیستمی را توسعه داده‌اید. امروز کارفرما از شما خواسته است که قابلیت جدیدی را به برنامه اضافه کنید. برای اعمال این تغییرات برای مثال نیاز است تا قسمتی از کدهای موجود تغییر کند، همچنین کلاس‌ها و متدهای جدیدی نیز به برنامه افزوده گردند. پس از انجام درخواست رسیده، چگونه می‌توانید اطمینان حاصل کنید که قسمت‌های پیشین سیستم که تا همین چند لحظه پیش کار می‌کردند، اکنون نیز همانند قبل کار می‌کنند؟ حجم کدهای نوشته شده بالا است. آزمایش دستی تک تک موارد شاید دیگر از لحاظ زمانی مقدور نباشد. آزمایش واحد روشی است برای اطمینان حاصل کردن از اینکه هنگام تحویل کار به کارفرما مرتبا سرخ و سفید نشویم! به این صورت عملیات refactoring کدهای موجود بدون ترس و لرز انجام خواهد شد، چون بلافاصله می‌توانیم آزمایشات قبلی را اجرا کرده و از صحت عملکرد سیستم اطمینان حاصل نمائیم. بدون اینکه در زمان تحویل برنامه در هنگام بروز خطا بگوئیم : "این غیرممکنه!"
  • روال‌های آزمایشات صورت گرفته در آینده تبدیل به مرجع مهمی جهت درک چگونگی عملکرد قسمت‌های مختلف سیستم خواهند شد. چگونه فراخوانی شده‌اند، چگونه باید به آن‌ها مقداری را ارجاع داد و امثال آن.
  • با استفاده از آزمایش‌های واحد، بدترین حالات ممکن را قبل از وقوع می‌توان در نظر گرفت و بررسی کرد.
  • نوشتن آزمایش‌های واحد در حین کار، برنامه نویس را وادار می‌کند که کار خود را به واحدهای کوچکتری که قابلیت بررسی مستقلی دارند، بشکند. برای مثال فرض کنید متدی را توسعه داده‌اید که پس از انجام سه عملیات مختلف بر روی یک رشته، خروجی خاصی را ارائه می‌دهد. هنگام آزمایش این متد چگونه می‌توان اطمینان حاصل کرد که کدام قسمت سبب شکست آزمایش شده است؟ به همین جهت برنامه نویس جهت ساده‌تر کردن آزمایشات، مجبور خواهد شد که کد خود را به قسمت‌های مستقل کوچکتری تقسیم کند.
  • با توجه به امکان اجرای خودکار این آزمایشات، به عنوان جزئی ایده‌آل از پروسه تولید نرم افزار محسوب می‌شوند.


حد و مرز یک آزمایش واحد کجاست؟

آزمایش شما، آزمایش واحد نامیده نخواهد شد اگر:
  • با دیتابیس سر و کار داشته باشد.
  • با شبکه در ارتباط باشد.
  • با فایل‌ها کار کند.
  • نیاز به تمهیدات ویژه‌ای برای اجرای آن وجود داشته باشد. مثلا وجود یک فایل config برای اجرای آن ضروری باشد.
  • همراه و همزمان با سایر کدهای آزمایش‌های واحد شما قابل اجرا نباشد.
برای مثال اگر یکی از متدهای شما بزرگترین عدد یک لیست را از دیتابیس دریافت می‌کند، در متدی که برای آزمایش واحد آن تهیه خواهید کرد نباید هیچگونه کدی جهت برقراری ارتباط با دیتابیس نوشته شود.
این امر سبب سریع‌تر اجرا شدن آزمایشات واحد خواهند شد و در آینده شما را از انجام آن به‌دلیل کند بودن روند انجام آزمایشات، منصرف نخواهد کرد. همچنین تغییرات انجام شده در لایه دسترسی به داده‌ها سبب غیرمعتبر شدن این نوع آزمایشات نخواهند شد. به بیان دیگر وظیفه متد آزمایش واحد، اتصال به دیتابیس یا شبکه و یا خواندن اطلاعات از یک فایل نیست.

ادامه دارد...

مطالب
WF:Windows Workflow #1
چرا از WorkFlow در پروژه‌های نرم افزاری استفاده می‌شود ؟

زمانیکه در حال انجام یک پروژه نرم افزاری هستید که این پروژه دارای پیچیدگی خاصی از لحاظ فرآیند و قوانین کاری می‌باشد بهترین راه حل Workflow Engine یا BPMS Engine می‌باشد.
البته شایان ذکر می‌باشد که میان این دو Engine تفاوت‌های بسیاری وجود دارد. شاید خیلی از برنامه نویس‌ها از خود این سوال را بپرسند که تمام قوانین کاری و فرآیند‌های یک سازمان را می‌توان با کد نویسی انجام داد، چه نیازی به این Engine‌ها برای مکانیزه کردن فرایند‌های یک سازمان است؟
جواب این سوال را با یک مثال ساده آغاز می‌کنم :
فرض کنید یک فرآیند خیلی ساده داریم که کار آن دریافت اطلاعات از بانک اطلاعاتی و ارسال آن به مدیر بخش و دریافت تایید از طرف مدیر می‌باشد. این کار توسط دو کاربر انجام می‌شود که در سازمان نقش و سطح دسترسی مختلفی را دارا می‌باشند و به این نکته توجه کنید و آن اینکه فرض کنید زمانیکه نرم افزار شما در سازمانی در حال انجام کار می‌باشد به شما خبر داده می‌شود که کاربر x به مرخصی رفته و نقش آن به کسی دیگر سپرده شده است و این کار باید از طریق سیستم و با تایید مدیر انجام شود و یا سطح دسترسی افراد در سازمان عوض شود. این ساده‌ترین فرآیند‌ی است که در زمان انجام پروژه با آن رو به رو می‌شویم .
اگر این فرآیند‌های ساده را بخواهیم با  100% کد نویسی  انجام دهیم، تعداد خط کد‌ها بسیار زیاد، زمان بر و انرژی زیادی از گروه گرفته می‌شود و مشکل به تعداد خط کد زیاد نیست، مشکل اصلی آن جایی است که برای پروژه بعدی قصد استفاده از این سیستم را داشته باشیم و نیاز به تغییر در بعضی از قسمت‌های سیستم باشد در این قسمت است که بیشترین زمان و انرژی از گروه گرفته می‌شود ولی در صورت استفاده از Workflow می‌توان در کمترین زمان و هزینه، پیچیده‌ترین Business Logic‌ها را پیاده سازی کرد.
نکته دیگری که در مورد اینگونه Engine‌ها باید گفته شود این است که در معماری SOA نقش فراوانی را دارا می‌باشند .
نظرات مطالب
معرفی DNTProfiler
البته در ورژن قبلی بااضافه کردن خط زیر فایل کانفیگ و اجرای برنامه به صورت Run As Administrator مشکل حل شد.
<runtime>
<loadFromRemoteSources enabled="true"/>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.2.29.0" newVersion="2.2.29.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>