مطالب
لینک‌های هفته آخر آذر

وبلاگ‌ها و سایت‌های ایرانی


امنیت


Visual Studio


ASP. Net


طراحی وب


PHP

  • Aptana PHP 1.0 منتشر شد (اگر قبلا این IDE بسیار قابل توجه را دریافت کرده بودید فقط کافی است به منوی aptana و گزینه my aptana مراجعه کرده و از قسمت plugins ، این پلاگین 18 مگابایتی را دریافت کنید.)

اس‌کیوال سرور


سی شارپ


عمومی دات نت


ویندوز


متفرقه


نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
زمانی که وظیفه ای میخواهد اجرا شود همزمان با آن کنترلر Home هم فراخوانی می‌شود
این درخواست هایی هست که لاگ VS قسمت Output ثبت می‌شود

Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Dependency duration","kind":"Aggregation","value":4143.777,"count":1,"min":4143.777,"max":4143.777,"stdDev":0}],"properties":{"_MS.MetricId":"dependencies/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","Dependency.Type":"Http","DeveloperMode":"true","Dependency.Success":"True"}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Server response time","kind":"Aggregation","value":4136.7653,"count":1,"min":4136.7653,"max":4136.7653,"stdDev":0}],"properties":{"_MS.MetricId":"requests/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","DeveloperMode":"true","Request.Success":"True"}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Metric","time":"2019-01-08T11:23:01.0000000Z","tags":{"ai.cloud.roleInstance":"BAHARI-PC","ai.internal.sdkVersion":"m-agg2:2.8.1-22898"},"data":{"baseType":"MetricData","baseData":{"ver":2,"metrics":[{"name":"Dependency duration","kind":"Aggregation","value":3972.6929,"count":3,"min":12.8297,"max":2687.0447,"stdDev":1092.34881420812}],"properties":{"_MS.MetricId":"dependencies/duration","_MS.IsAutocollected":"True","_MS.AggregationIntervalMs":"60000","Dependency.Type":"SQL","DeveloperMode":"true","Dependency.Success":"True"}}}}
این هم نمونه ای از وظیفه:
#region Using

using Autofac;
using CHK.ServiceLayer.Interfaces;
using DNTScheduler;
using System;

#endregion

namespace CHK.Web.Scheduler
{
    public class InventoryTask : ScheduledTaskTemplate
    {
        #region Properties

        public override int Order => 1;
        public override string Name => "Inventory-DiscountCoupon-GiftCard";
        public IContainer Container { get; set; }

        #endregion

        #region Methods

        public override bool RunAt(DateTime utcNow)
        {
            if (IsShuttingDown || Pause) return false;
            var currentDateTime = utcNow.AddHours(3.5);
            return (currentDateTime.Minute % 15 == 0 && currentDateTime.Second > 5 && currentDateTime.Second < 15);
        }

        public override void Run()
        {
            if (IsShuttingDown || Pause) return;
            Pause = true;
            using (var scope = Container.BeginLifetimeScope())
            {
                var schedulerService = scope.Resolve<ISchedulerService>();
                schedulerService.UpdateInventory();
            }
            Pause = false;
        }

        #endregion
    }
}

نظرات مطالب
نحوه استفاده از ViewModel در ASP.NET MVC
سلام، من توی مدل پراپرتی از نوع DateTime? دارم که توی view اون رو با کنترل datepicker نمایش میدم. مشکل اینجاست که مقداری که به مدل پاس میشه، تاریخ شمسی هستش و مدل انتظار datetime رو داره. نهایتا توی uow.SaveChanges با این خطا مواجه میشم:
the conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value

مطالب
یافتن مقادیر نال در Entity framework
کلاس شخص زیر را درنظر بگیرید
public class Person
{
        public int Id { get; set; }                
        public string Name { get; set; }
        public int? Age { get; set; }
}
در اینجا با توجه به اینکه Name از نوع string است، خودبخود به فیلدی نال‌پذیر نگاشت خواهد شد و همچنین Age عددی نیز در سمت کدهای ما Nullable است، بنابراین خاصیت سن هم به فیلدی نال‌پذیر نگاشت می‌شود.
اگر تمام مراحل متداول ایجاد Context را طی کنیم، به نظر شما خروجی SQL عبارت زیر چه خواهد بود؟
string name = null;
var list1 = ctx.Users.Where(x => x.Name == name).ToList();
در این عبارت، name به صورت یک متغیر ارسال شده است و نه یک مقدار ثابت (فرض کنید یک متد را تعریف کرده‌اید که name را به صورت پارامتر دریافت می‌کند).
خروجی SQL آن به نحو زیر است:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age]
FROM [dbo].[People] AS [Extent1]
WHERE [Extent1].[Name] = @p__linq__0
-- p__linq__0 (dbtype=String, size=-1, direction=Input) = null
به عبارتی خروجی مورد انتظار name is null را تولید نکرده است و کوئری ما حداقل با SQL Server نتیجه‌ای را به همراه نخواهد داشت. در مورد Age نیز به همین صورت است.


راه حل:

برای حالت Age، روش زیر خروجی age is null را تولید می‌کند:
 var list2 = ctx.Users.Where(x => !x.Age.HasValue).ToList();
و یا استفاده از object.Equals نیز مشکل را برطرف خواهد کرد:
int? age = null;
var list2 = ctx.Users.Where(x => object.Equals(x.Age, age)).ToList();
برای حالت Name رشته‌ای می‌توان از روش زیر استفاده کرد:
 var list1 = ctx.Users.Where(x => string.IsNullOrEmpty(x.Name)).ToList();
و یا روش کلی‌تر زیر نیز جواب می‌دهد:
string name = null;
var list1 = ctx.Users.Where(x => name == null ? x.Name == null : x.Name == name).ToList();
کاری که در اینجا انجام شده استفاده از x.Name == null در حالت نال بودن name است. از این جهت که EF با کوئری ذیل به علت عدم استفاده از پارامتر برای معرفی مقداری نال، مشکلی ندارد:
 var list1 = ctx.Users.Where(x => x.Name == null).ToList();

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


Mocking چیست؟

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


دلایل و مزایای استفاده‌ی از Mocking

- یکی از مهم‌ترین دلایل استفاده‌ی از Mocking، کاهش پیچیدگی تنظیمات اولیه‌ی نوشتن آزمون‌های واحد است. برای مثال اگر در برنامه‌ی خود از تزریق وابستگی‌ها استفاده می‌کنید و کلاسی دارای چندین وابستگی تزریق شده‌ی به آن است، برای آزمایش این کلاس نیاز به تدارک تمام این وابستگی‌ها را خواهید داشت تا بتوان این کلاس را وهله سازی کرد و همچنین برنامه را نیز کامپایل نمود. اما در این بین ممکن است آزمایش متدی در همان کلاس، الزاما از تمام وابستگی‌های تزریق شده‌ی در یک کلاس استفاده نکند. در این حالت، Mocking می‌تواند تنظیمات پیچیده‌ی وهله سازی این کلاس را به حداقل برساند.
- Mocking می‌تواند سبب افزایش سرعت اجرای آزمون‌های واحد نیز شود. برای مثال با تقلید سرویس‌های خارجی مورد استفاده‌ی در برنامه (هر عملی که از مرزهای سیستم رد شود مانند کار با شبکه، بانک اطلاعاتی، فایل سیستم و غیره)، می‌توان میزان I/O و همچنین زمان صرف شده‌ی به آن‌را به حداقل رساند.
- از mock objects می‌توان برای رهایی از مشکلات کار با مقادیر غیرمشخص استفاده کرد. برای مثال اگر در کدهای خود از DateTime.Now استفاده می‌کنید یا اعداد اتفاقی و امثال آن، هربار که آزمون‌های واحد را اجرا می‌کنیم، خروجی متفاوتی را دریافت کرده و بسیاری از آزمون‌های نوشته شده با مشکل مواجه می‌شوند. به کمک mocking می‌توان بجای این مقادیر غیرمشخص، یک مقدار ثابت و مشخص را بازگشت دهد.
- چون به سادگی می‌توان mock objects را تهیه کرد، می‌توان کار توسعه و آزمایش برنامه را پیش از به پایان رسیدن پیاده سازی اصلی سرویس‌های مدنظر، همینقدر که اینترفیس آن سرویس مشخص باشد، شروع کرد که می‌تواند برای کارهای تیمی بسیار مفید باشد.
- اگر وابستگی مورد استفاده ناپایدار و یا غیرقابل پیش بینی است، می‌توان توسط mocking به یک نمونه‌ی قابل پیش بینی و پایدار مخصوص آزمون‌های برنامه رسید.
- اگر وابستگی خارجی مورد استفاده به ازای هر بار استفاده، هزینه‌ای را شارژ می‌کند، می‌توان توسط mocking، هزینه‌ی آزمون‌های برنامه را کاهش داد.


Unit test چیست؟

بدیهی است در کنار آزمایش ایزوله‌ی قسمت‌های مختلف برنامه توسط mocking، باید کل برنامه را جهت بررسی دستیابی به نتایج واقعی نیز آزمایش کرد که به این نوع آزمون‌ها، آزمون یکپارچگی (Integration Tests)، API Tests ،UI Tests و غیره می‌گویند که در کنار Unit tests ما حضور خواهند داشت. بنابراین اکنون این سؤال مطرح می‌شود که یک Unit چیست؟
در برنامه‌ای که از چندین کلاس تشکیل می‌شود، به یک کلاس، یک Unit گفته می‌شود. همچنین اگر در این سیستم، دو یا چند کلاس با هم کار می‌کنند (کلاسی که از چندین وابستگی استفاده می‌کند)، این‌ها با هم نیز یک Unit را تشکیل دهند. بنابراین تعریف Unit بستگی به نحوه‌ی درک عملکرد یک سیستم و تعامل اجزای آن با هم دارد.


واژه‌های متناظر با Mock objects

در حین مطالعه‌ی منابع مرتبط با آزمون‌های واحد ممکن است با این واژه‌های تقریبا مشابه مواجه شوید: fakes ،stubs ،dummies و mocks. اما تفاوت آن‌ها در چیست؟
- Fakes در حقیقت یک نمونه پیاده سازی واقعی، اما غیرمناسب محیط واقعی و اصلی پروژه‌است. برای نمونه EF Core به همراه یک نمونه in-memory database هم هست که دقیقا با مفهوم Fakes تطابق دارد.
- از Dummies صرفا جهت تهیه‌ی پارامترهای مورد نیاز برای اجرای یک آزمایش استفاده می‌شوند. این پارامترها، هیچگاه در آزمایش‌های انجام شده مورد استفاده قرار نمی‌گیرند.
- از Stubs برای ارائه‌ی پاسخ‌هایی مشخص به فراخوان‌ها استفاده می‌شود. برای مثال یک متد یا خاصیت، دقیقا چه چیزی را باید بازگشت دهند.
- از Mocks برای بررسی تعامل اجزای مختلف در حال آزمایش استفاده می‌شود. آیا متدی یا خاصیتی مورد استفاده قرار گرفته‌است یا خیر؟

باید درنظر داشت که زمانیکه یک شیء Mock را توسط کتابخانه‌ی Moq تهیه می‌کنیم، هر سه مفهوم stubs ،dummies و mocks را با هم به همراه دارد. به همین جهت در این سری زمانیکه به یک mock object اشاره می‌شود، هر سه مفهوم مدنظر هستند.

واژه‌ی دیگری که ممکن است در این گروه زیاد مشاهده شود، «Test double» نام دارد که ترکیب هر 4 مورد fakes ،stubs ،dummies و mocks می‌باشد. در کل هر زمانیکه یک شیء مورد استفاده‌ی در زمان اجرای برنامه را جهت آزمایش ساده‌تر آن جایگزین می‌کنید، یک Test double را ایجاد کرده‌اید.


بررسی ساختار برنامه‌ای که می‌خواهیم آن‌را آزمایش کنیم

در این سری قصد داریم یک برنامه‌ی وام دهی را آزمایش کنیم که قسمت‌های مختلف آن دارای وابستگی‌های خاصی می‌باشند. ساختار این برنامه را در ادامه مشاهده می‌کنید:


موجودیت‌های برنامه‌ی وام دهی
namespace Loans.Entities
{
    public class Applicant
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public int Age { set; get; }

        public string Address { set; get; }

        public decimal Salary { set; get; }
    }
}

namespace Loans.Entities
{
    public class LoanProduct
    {
        public int Id { set; get; }

        public string ProductName { set; get; }

        public decimal InterestRate { set; get; }
    }
}

namespace Loans.Entities
{
    public class LoanApplication
    {
        public int Id { set; get; }

        public LoanProduct Product { set; get; }

        public LoanAmount Amount { set; get; }

        public Applicant Applicant { set; get; }

        public bool IsAccepted { set; get; }
    }

    public class LoanAmount
    {
        public string CurrencyCode { get; set; }

        public decimal Principal { get; set; }
    }
}

مدل‌های برنامه‌ی وام دهی

namespace Loans.Models
{
    public class IdentityVerificationStatus
    {
        public bool Passed { get; set; }
    }
}

سرویس‌های برنامه‌ی وام دهی

using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {
        void Initialize();

        bool Validate(string applicantName, int applicantAge, string applicantAddress);

        void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);

        void Validate(string applicantName, int applicantAge, string applicantAddress,
            ref IdentityVerificationStatus status);
    }
}

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
    }
}

using System;
using Loans.Entities;
using Loans.Services.Contracts;

namespace Loans.Services
{
    public class LoanApplicationProcessor
    {
        private const decimal MinimumSalary = 1_500_000_0;
        private const int MinimumAge = 18;
        private const int MinimumCreditScore = 100_000;

        private readonly IIdentityVerifier _identityVerifier;
        private readonly ICreditScorer _creditScorer;

        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }

        public bool Process(LoanApplication application)
        {
            application.IsAccepted = false;

            if (application.Applicant.Salary < MinimumSalary)
            {
                return application.IsAccepted;
            }

            if (application.Applicant.Age < MinimumAge)
            {
                return application.IsAccepted;
            }

            _identityVerifier.Initialize();

            var isValidIdentity = _identityVerifier.Validate(
                application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);

            if (!isValidIdentity)
            {
                return application.IsAccepted;
            }

            _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
            if (_creditScorer.Score < MinimumCreditScore)
            {
                return application.IsAccepted;
            }

            application.IsAccepted = true;
            return application.IsAccepted;
        }
    }
}

using System;
using Loans.Models;
using Loans.Services.Contracts;

namespace Loans.Services
{
    public class IdentityVerifierServiceGateway : IIdentityVerifier
    {
        public DateTime LastCheckTime { get; private set; }

        public void Initialize()
        {
            // Initialize connection to external service
        }

        public bool Validate(string applicantName, int applicantAge, string applicantAddress)
        {
            Connect();
            var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
            LastCheckTime = DateTime.Now;
            Disconnect();

            return isValidIdentity;
        }

        private void Connect()
        {
            // Open connection to external service
        }

        private bool CallService(string applicantName, int applicantAge, string applicantAddress)
        {
            // Make call to external service, interpret the response, and return result

            return false; // Simulate result for demo purposes
        }

        private void Disconnect()
        {
            // Close connection to external service
        }

        public void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid)
        {
            throw new NotImplementedException();
        }

        public void Validate(string applicantName, int applicantAge, string applicantAddress,
            ref IdentityVerificationStatus status)
        {
            throw new NotImplementedException();
        }
    }
}
توضیحات:
هدف از این برنامه، درخواست یک وام جدید است. Application در اینجا به معنای درخواست یا فرم جدید است و Applicant نیز شخصی است که این درخواست را داده‌است.
در اینجا بیشتر تمرکز ما بر روی کلاس LoanApplicationProcessor است که دارای دو وابستگی تزریق شده‌ی به آن نیز می‌باشد:
        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }
از این وابستگی‌ها برای تصدیق هویت درخواست کننده و همچنین بررسی میزان اعتبار او استفاده می‌شود.
تمام این منطق نیز در متد Process آن قابل مشاهده‌است که هدف اصلی آن، بررسی قابل پذیرش بودن درخواست یک وام جدید است.


نوشتن اولین تست، برای برنامه‌ی وام دهی

در اولین تصویر این قسمت، پروژه‌ی class library دومی را نیز به نام Loans.Tests مشاهده می‌کنید. فایل csproj آن به صورت زیر برای کار با MSTest تنظیم شده‌است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Loans\Loans.csproj" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
    <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />    
  </ItemGroup>
</Project>
که در آن ارجاعی به پروژه‌ی Loans.csproj و همچنین وابستگی‌های MSTest، تنظیم شده‌اند.

اکنون اولین آزمون واحد ما در کلاس جدید LoanApplicationProcessorShould چنین شکلی را پیدا می‌کند:
using Loans.Entities;
using Loans.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void DeclineLowSalary()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};
            var processor = new LoanApplicationProcessor(null, null);
            processor.Process(application);

            Assert.IsFalse(application.IsAccepted);
        }
    }
}
در حین کار با MSTest، کلاس آزمون واحد باید به ویژگی TestClass و متدهای public void آن به ویژگی TestMethod مزین شوند تا توسط این فریم‌ورک آزمون واحد شناسایی شده و مورد آزمایش قرار گیرند.
در این آزمایش، شخص درخواست کننده، حقوق کمی دارد و می‌خواهیم بررسی کنیم که آیا LoanApplicationProcessor می‌تواند آن‌را بر اساس مقدار MinimumSalary، رد کند یا خیر؟
public class LoanApplicationProcessor
{
    private const decimal MinimumSalary = 1_500_000_0;

در حین وهله سازی LoanApplicationProcessor، دو وابستگی آن به null تنظیم شده‌اند؛ چون می‌دانیم که بررسی MinimumSalary پیش از سایر بررسی‌ها صورت می‌گیرد و اساسا در این آزمایش، نیازی به این وابستگی‌ها نداریم.
اما اگر سعی در اجرای این آزمایش کنیم (برای مثال با اجرای دستور dotnet test در خط فرمان)، آزمایش اجرا نشده و با استثنای زیر مواجه می‌شویم:
Test method Loans.Tests.LoanApplicationProcessorShould.DeclineLowSalary threw exception:
System.ArgumentNullException: Value cannot be null.
Parameter name: identityVerifier
چون در سازنده‌ی کلاس LoanApplicationProcessor، در صورت نال بودن وابستگی‌های دریافتی، یک استثناء صادر می‌شود. بنابراین ذکر آن‌ها الزامی است:
        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }


نصب کتابخانه‌ی Moq جهت برآورده کردن وابستگی‌های کلاس LoanApplicationProcessor

در این آزمایش چون وجود وابستگی‌های در سازنده‌ی کلاس، برای ما اهمیتی ندارند و همچنین ذکر آن‌ها نیز الزامی است، می‌خواهیم توسط کتابخانه‌ی Moq، دو نمونه‌ی تقلیدی از آن‌ها را تهیه کرده (همان dummies که پیشتر معرفی شدند) و جهت برآورده کردن بررسی صورت گرفته‌ی در سازنده‌ی کلاس LoanApplicationProcessor، آن‌ها را ارائه کنیم.
کتابخانه‌ی بسیار معروف Moq، با پروژه‌های مبتنی بر NETFramework 4.5. و همچنین NETStandard 2.0. به بعد سازگار است و برای نصب آن، می‌توان یکی از دو دستور زیر را صادر کرد:
> dotnet add package Moq
> Install-Package Moq

اما چرا کتابخانه‌ی Moq؟
کتابخانه‌ی Moq این اهداف را دنبال می‌کند: ساده‌است، به شدت کاربردی‌است و همچنین strongly typed است. این کتابخانه سورس باز بوده و تعداد بار دانلود بسته‌ی نیوگت آن میلیونی است.


پس از نصب آن، اولین آزمایشی را که نوشتیم، به صورت زیر اصلاح می‌کنیم:
using Loans.Entities;
using Loans.Services;
using Loans.Services.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void DeclineLowSalary()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsFalse(application.IsAccepted);
        }
    }
}
در اینجا بجای ارسال null به سازنده‌ی کلاس LoanApplicationProcessor، جهت برآورده کردن مقدار پیش‌فرض پارامترهای آن و کامپایل شدن برنامه، نمونه‌های تقلیدی دو وابستگی مورد نیاز آن‌را تهیه و به آن ارسال کرده‌ایم.
کار با ذکر new Mock شروع شده و آرگومان جنریک آن‌را از نوع وابستگی‌هایی که نیاز داریم، مقدار دهی می‌کنیم. سپس خاصیت Object آن، امکان دسترسی به این شیء تقلید شده را میسر می‌کند.
اکنون اگر مجددا این آزمون واحد را اجرا کنیم، مشاهده خواهیم کرد که بجای صدور استثناء، با موفقیت به پایان رسیده‌است:



گاهی از اوقات جایگزین کردن یک وابستگی null با نمونه‌ی Mock آن کافی نیست

در مثالی که بررسی کردیم، اشیاء mock، کار برآورده کردن نیازهای ابتدایی آزمایش را انجام داده و سبب اجرای موفقیت آمیز آن شدند؛ اما همیشه اینطور نیست:
using Loans.Entities;
using Loans.Services;
using Loans.Services.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void Accept()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
تفاوت این آزمایش جدید با قبلی، در دو مورد است: مقدار Salary به MinimumSalary تنظیم شده‌است و در آخر Assert.IsTrue را داریم.
اگر این آزمایش را اجرا کنیم، با شکست مواجه خواهد شد. علت اینجا است که هرچند در حال استفاده‌ی از دو mock object به عنوان وابستگی‌های مورد نیاز هستیم، اما تنظیمات خاصی را بر روی آن‌ها انجام نداده‌ایم و به همین جهت خروجی مناسبی را در اختیار LoanApplicationProcessor قرار نمی‌دهند. برای مثال مرحله‌ی بعدی بررسی اعتبار شخص در کلاس LoanApplicationProcessor، فراخوانی سرویس identityVerifier و متد Validate آن است که خروجی آن بر اساس کدهای فعلی، همیشه false است:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
در قسمت بعدی، کار تنظیم اشیاء mock را انجام خواهیم داد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-01.zip
مطالب
MVC Scaffolding #3
شاید کیفیت کدهای تولیدی یا کدهای View حاصل از MVC Scaffolding مورد تائید شما نباشد. در این قسمت به نحوه تغییر و سفارشی سازی این موارد خواهیم پرداخت.

آشنایی با ساختار اصلی MVC Scaffolding

پس از نصب MVC Scaffolding از طریق NuGet به پوشه Packages مراجعه نمائید. در اینجا پوشه‌های MvcScaffolding، T4Scaffolding و T4Scaffolding.Core ساختار اصلی این بسته را تشکیل می‌دهند. برای نمونه اگر پوشه T4Scaffolding\tools را باز کنیم، شاهد تعدادی فایل ps1 خواهیم بود که همان فایل‌های پاورشل هستند. مطابق طراحی NuGet، همواره فایلی با نام init.ps1 در ابتدا اجرا خواهد شد. همچنین در  اینجا پوشه‌های T4Scaffolding\tools\EFRepository و T4Scaffolding\tools\EFDbContext نیز قرار دارند که حاوی قالب‌های اولیه کدهای مرتبط با الگوی مخزن و DbContext تولیدی می‌باشند.
در پوشه MvcScaffolding\tools، ساختار قالب‌های پیش فرض تولید Viewها و کنترلرهای تولیدی قرار دارند. در اینجا به ازای هر مورد، دو نگارش vb و cs قابل مشاهده است.


سفارشی سازی قالب‌های پیش فرض Viewهای MVC Scaffolding

برای سفارشی سازی قالب‌های پیش فرض از دستور کلی زیر استفاده می‌شود:
Scaffold CustomTemplate Name Template
مانند دستور زیر:
Scaffold CustomTemplate View Index
در اینجا View نام یک Scaffolder است و Index نام قالبی در آن.
اگر دستور فوق را اجرا کنیم، فایل جدیدی به نام CodeTemplates\Scaffolders\MvcScaffolding.RazorView\Index.cs.t4 به پروژه جاری اضافه می‌شود. از این پس کلیه فرامین اجرایی، از نسخه محلی فوق بجای نمونه‌های پیش فرض استفاده خواهند کرد.
در ادامه قصد داریم اندکی این قالب پیش فرض را جهت اعمال ویژگی DisplayName به هدر جدول تولیدی نمایش اطلاعات Tasks تغییر دهیم. در کلاس Task، خاصیت زمان موعود با ویژگی DisplayName مزین شده است. این نام نمایشی حین تولید فرم‌های ثبت و ویرایش اطلاعات بکار گرفته می‌شود، اما در زمان تولید جدول اطلاعات ثبت شده، به هدر جدول اعمال نمی‌گردد.
[DisplayName("Due Date")]
public DateTime? DueDate { set; get; }
برای تغییر و بهبود این مساله، فایل Index.cs.t4 را که پیشتر به پروژه اضافه کردیم باز کنید. کلاس ModelProperty را یافته و خاصیت جدید DisplayName را به آن اضافه کنید:
// Describes the information about a property on the model
class ModelProperty {
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public string ValueExpression { get; set; }
    public EnvDTE.CodeTypeRef Type { get; set; }
    public bool IsPrimaryKey { get; set; }
    public bool IsForeignKey { get; set; }
    public bool IsReadOnly { get; set; }
}
در حالت پیش فرض فقط از خاصیت Name برای تولید هدر جدول در ابتدای فایل t4 در حال ویرایش استفاده می‌شود. در پایان فایل t4 جاری، متد زیر را اضافه کنید:
static string GetDisplayName(EnvDTE.CodeProperty prop)
{
  var displayAttr = prop.Attributes.OfType<EnvDTE80.CodeAttribute2>().Where(x => x.FullName == typeof(System.ComponentModel.DisplayNameAttribute).FullName).FirstOrDefault();
  if(displayAttr == null)
  {
    return prop.Name;
  }
  return displayAttr.Value.Replace("\"","");
}
در اینجا بررسی می‌شود که آیا ویژگی DisplayNameAttribute بر روی خاصیت در حال بررسی وجود دارد یا خیر. اگر خیر از نام خاصیت استفاده خواهد شد و اگر بلی، مقدار ویژگی نام نمایشی استخراج شده و بازگشت داده می‌شود.
اکنون برای اعمال متد GetDisplayName، متد GetEligibleProperties را یافته و به نحو زیر تغییر دهید:
results.Add(new ModelProperty {
Name = prop.Name,
DisplayName = GetDisplayName(prop), 
ValueExpression = "Model." + prop.Name,
Type = prop.Type,
IsPrimaryKey = Model.PrimaryKeyName == prop.Name,
IsForeignKey = ParentRelations.Any(x => x.RelationProperty == prop),
IsReadOnly = !prop.IsWriteable()
});
در اینجا خاصیت DisplayName به لیست خروجی اضافه شده است.
اکنون قسمت هدر جدول تولیدی را در ابتدای فایل t4 یافته و به نحو زیر تغییر می‌دهیم تا از DisplayName استفاده کند:
<#
List<ModelProperty> properties = GetModelProperties(Model.ViewDataType, true);
foreach (ModelProperty property in properties) {
    if (!property.IsPrimaryKey && !property.IsForeignKey) {
#>
        <th>
            <#= property.DisplayName #>
        </th>
<#
    }
}
#>
در ادامه برای آزمایش تغییرات فوق، دستور ذیل را صادر می‌کنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
پس از اجرای دستور، به فایل Views\Tasks\Index.cshtml مراجعه نمائید. اینبار هدر خودکار تولیدی از Due Date بجای DueDate استفاده کرده است.


سفارشی سازی قالب‌های پیش فرض کنترلرهای MVC Scaffolding

در ادامه قصد داریم کدهای الگوی مخزن تهیه شده را اندکی تغییر دهیم. برای مثال با توجه به اینکه از تزریق وابستگی‌ها استفاده خواهیم کرد، نیازی به سازنده اولیه پیش فرض کنترلر که در بالای آن ذکر شده «در صورت استفاده از یک DI این مورد را حذف کنید»، نداریم. برای این منظور دستور زیر را اجرا کنید:
 PM> Scaffold CustomTemplate Controller ControllerWithRepository
در اینجا قصد ویرایش قالب پیش فرض کنترلرهای تشکیل شده با استفاده از الگوی مخزن را داریم. نام ControllerWithRepository از فایل ControllerWithRepository.cs.t4 موجود در پوشه packages\MvcScaffolding\tools\Controller گرفته شده است.
به این ترتیب فایل جدید CodeTemplates\Scaffolders\MvcScaffolding.Controller\ControllerWithRepository.cs.t4 به پروژه جاری اضافه خواهد شد. در این فایل چند سطر ذیل را یافته و سپس حذف کنید:
// If you are using Dependency Injection, you can delete the following constructor
        public <#= Model.ControllerName #>() : this(<#= String.Join(", ", Repositories.Values.Select(x => "new " + x.RepositoryTypeName + "()")) #>)
        {
        }
برای آزمایش آن دستور زیر را صادر نمائید:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force -ForceMode ControllerOnly
چون تنها قصد تغییر کنترلر را داریم از پارامتر ForceMode با مقدار ControllerOnly استفاده شده است.
یا اگر نیاز به تغییر کدهای الگوی مخزن مورد استفاده است می‌توان از دستور ذیل استفاده کرد:
 Scaffold CustomScaffolder EFRepository
به این ترتیب فایل جدید CodeTemplates\Scaffolders\EFRepository\EFRepositoryTemplate.cs.t4 جهت ویرایش به پروژه جاری اضافه خواهد شد.
لیست Scaffolder‌های مهیا با دستور Get-Scaffolder قابل مشاهده است.
مطالب
جایگزینی اسکریپت‌های WebResource.axd با فایل‌های استاتیک در ASP.NET Web forms
مدتی قبل سؤال مهمی در سایت مطرح شد، به شرح زیر:
«من از کنترل‌های تلریک استفاده می‌کنم که یک سری اسکریپت را بصورت
 http://localhost:1244/WebResource.axd?d=aklE6L8AEfPEgIS3T-oXc6mevPfbpi6VRp_ZTP2nBVrnt5ULOFYD3GNWRrDHwANC3VDQlL8dLAa5g35dzgHyuzAgAguIpYrf-_NXIJwNNu0YRSnH3-MgKMfnwKBKF_Lk2E5oeIcLL78uDlQ0se_GxQ2&t=635231470568640000 
به فرم تزریق می‌کند و بعضی وقت‌ها داخلش xp و یا یک سری دستورات اس‌کیوال تولید می‌شوند. در این حالت این مسیرها توسط ISA Server در شبکه داخلی حمله تشخیص داده شده و بلاک خواهند شد و عملا برنامه از کار می‌افتد. آیا راهی برای خلاصی از دست آن‌ها هست؟»

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

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

در ابتدا اسمبلی حاوی کنترل‌های وب فرم مدنظر خود را باید توسط برنامه‌های Reflector یا ILSpy و امثال آن‌ها گشوده و نام دقیق منبع و همچنین محتوای آن فایل اسکریپت را استخراج کنید. برای مثال:


در این تصویر، اسمبلی استاندارد System.Web.Extensions مورد بررسی قرار گرفته است. برای نمونه اگر بخواهید اسکریپت‌های متناظر با ScriptManager و UpdatePanel را با معادل‌های استاتیک آن‌ها جایگزین کنید، باید دو فایل MicrosoftAjaxWebForms.js و MicrosoftAjax.js را از این اسمبلی استخراج نمائید. (برنامه‌های یاد شده امکان ذخیره سازی منابع را نیز می‌دهند)


ب) وادار کردن ASP.NET به استفاده از نسخه‌ی استاتیک منابع

    <asp:ScriptManager ID="Scriptmanager1" runat="server">
            <Scripts>
                <asp:ScriptReference Name="MicrosoftAjaxWebForms.js" Assembly="System.Web.Extensions"
                    Path="~/staticJS1.js" />
                <asp:ScriptReference Name="MicrosoftAjax.js" Assembly="System.Web.Extensions" Path="~/staticJS2.js" />
            </Scripts>
    </asp:ScriptManager>
فرض کنید دو اسکریپت یاد شده را در فایل‌های staticJS1.js و staticJS2.js در ریشه‌ی سایت خود ذخیره کرده‌اید. اکنون یک ScriptManager را به صفحه اضافه کرده و مطابق کدهای فوق، اسکریپت‌های مدفون شده در اسمبلی System.Web.Extensions را به این فایل‌های استاتیک هدایت کنید. همانطور که عنوان شد نام این مداخل باید دقیقا با نام موجود در اسمبلی یکی باشد؛ در غیر اینصورت با خطای ذیل مواجه خواهید شد:
 The assembly 'System.Web.Extensions' does not contain a Web resource that has the name 'xyz.js'.
Make sure that the resource name is spelled correctly.
Make sure that the application references the correct version of an ASP.NET AJAX Framework assembly.
اکنون اگر برنامه را اجرا کنید (با فرض قرار داشتن یک ScriptManager و UpdatePanel در صفحه)، اینبار دیگر در سورس صفحه، شاهد آدرس‌های طولانی WebResource.axd و ScriptResource.axd نخواهید بود. به صورت خودکار از دو فایل استاتیک تنظیم شده، استفاده می‌شود:
 <script src="staticJS1.js" type="text/javascript"></script>
<script src="staticJS2.js" type="text/javascript"></script>
بدیهی است در صورت نیاز، باید تعاریف سایر اسکریپت‌های مدفون در اسمبلی یاد شده یا اسمبلی System.Web را نیز به صفحه از طریق ScriptManager اضافه کرد. در مورد کنترل‌های ثالث نیز وضع به همین صورت است و استاندارد آن تفاوتی نمی‌کند.


یک نکته‌ی تکمیلی
در مطلب «ASP.NET 4.5 ScriptManager Improvements in WebForms » مشاهده خواهید کرد که از ASP.NET 4.5 به بعد، طی دو بسته‌ی نیوگت که هر از چندگاهی به روز می‌شوند، کلیه اسکریپت‌های System.Web و System.Web.Extensions خارج از این اسمبلی‌ها نیز قابل دریافت بوده و با استفاده از سیستم bunding & minification می‌توان آن‌ها را فشرده و یکی کرد.