مطالب
بررسی شیوه‌نامه‌های المان‌های پر کاربرد بوت استرپ 4
در این قسمت، شیوه‌نامه‌هایی را بررسی می‌کنیم که به المان‌های پر کاربردی مانند دکمه‌ها، لیست‌ها و نشان‌ها اعمال می‌شوند.


شیوه‌نامه‌های کار با دکمه‌ها در بوت استرپ 4

کلاس پایه ایجاد دکمه‌های بوت استرپی، کلاس btn است. البته آن‌را می‌توان با کلاس‌های دیگری نیز ترکیب کرد:
- کلاس btn را می‌توان بر روی المان‌هایی مانند anchor، button و input نیز اعمال کرد:
<a class="btn btn-primary" href="#" role="button">Link</a>
<button class="btn btn-primary" type="submit">Button</button>
<input class="btn btn-primary" type="submit" value="Input">
در این حالت تمام این المان‌ها یکسان به نظر می‌رسند:


- برای تعیین رنگ اجباری دکمه‌های بوت استرپ، از فرمول زیر استفاده می‌شود؛ مانند btn-primary:

<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-warning">Warning</button>
<button class="btn btn-info">Info</button>
<button class="btn btn-light">Light</button>
<button class="btn btn-dark">Dark</button>
با این خروجی:


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

همچنین نگارش outline آن‌ها نیز قابل تعریف است؛ مانند btn-outline-primary:

<button class="btn btn-outline-primary">Primary</button>
<button class="btn btn-outline-secondary">Secondary</button>
<button class="btn btn-outline-success">Success</button>
<button class="btn btn-outline-danger">Danger</button>
<button class="btn btn-outline-warning">Warning</button>
<button class="btn btn-outline-info">Info</button>
<button class="btn btn-outline-light">Light</button>
<button class="btn btn-outline-dark">Dark</button>
با این خروجی:


- کلاس btn-size که در اینجا size می‌تواند sm یا lg باشد و سبب ایجاد دکمه‌هایی کوچک یا بزرگ می‌شوند.
- کلاس btn-block سبب می‌شود تا دکمه‌ای کل عرض container را پر کند.
<button class="btn btn-primary">Default</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary btn-block">Block</button>
با این خروجی:


- امکان اعمال کلاس‌های active و disabled نیز در اینجا میسر است:
<h2>States</h2>
<h3>Active</h3>
<button class="btn btn-primary active">Active Button</button>

<h3>Disabled</h3>
<button class="btn btn-primary disabled">Disabled Button</button>
<a class="btn btn-primary disabled" href="#">Disabled Link Button</a>
با این خروجی:



تشکیل گروهی از دکمه‌ها در بوت استرپ 4

برای تبدیل تعدادی از دکمه‌ها به یک گروه، از کلاس btn-group استفاده می‌شود. همچنین امکان تشکیل گزینه‌ی عمودی آن، با کلاس btn-group-vertical نیز وجود دارد. در این  حالت دکمه‌ها بجای نمایش افقی، به صورت یک ستون نمایش داده می‌شوند. کلاس btn-toolbar نیز برای تشکیل نوار ابزاری از دکمه‌ها در نظر گرفته شده‌است. توسط کلاس‌های btn-group-sm و یا btn-group-lg می‌توان اندازه‌ی این گروه‌ها را تغییر داد.
<div class="btn-toolbar" aria-label="All pets">
    <div class="btn-group mb-2 mr-2" aria-label="Common pets">
        <button type="button" class="btn btn-primary active">Cat</button>
        <button type="button" class="btn btn-primary">Dog</button>
        <button type="button" class="btn btn-primary">Fish</button>
        <button type="button" class="btn btn-primary">Bird</button>
    </div>

    <div class="btn-group mb-2" aria-label="Exotic pets">
        <button type="button" class="btn btn-primary">Amphibian</button>
        <button type="button" class="btn btn-primary active">Reptile</button>
        <button type="button" class="btn btn-primary">Other</button>
    </div>
</div>
با این خروجی:


در اینجا دو گروه از دکمه‌ها تشکیل شده‌اند که این‌ها را داخل یک btn-toolbar قرار داده‌ایم. همچنین تعریف aria-labelها به screen readers و معلولین کمک می‌کند.
به علاوه با استفاده از کلاس‌های mb-2 و mr-2، سبب ایجاد margin بین این نوار ابزار با متن زیر آن و همچنین بین خود آن‌ها شده‌ایم.

و حالت عمودی آن:
<div class="btn-group-vertical mb-2" aria-label="Exotic pets">
    <button type="button" class="btn btn-primary">Amphibian</button>
    <button type="button" class="btn btn-primary active">Reptile</button>
    <button type="button" class="btn btn-primary">Other</button>
</div>
چنین شکلی را پیدا می‌کند:



کلاس‌های جدید تشکیل Badges در بوت استرپ 4

برای تشکیل نشان/آرم از کلاس badge استفاده می‌شود و برای تغییر شکل آن می‌توان از کلاس badge-pill کمک گرفت. برای تغییر رنگ آن نیز فرمول زیر وجود دارد:


یک مثال:
    <div class="container">
        <div class="row">
            <section class="col-12">
                <h1>Our Commitment <span class="badge badge-info">to you</span></h1>
                <h3>Grooming <span class="badge badge-danger badge-pill">new</span></h3>
            </section>
        </div><!-- row -->
    </div><!-- content container -->
با این خروجی:


همانطور که مشاهده می‌کنید، یک badge همواره به اندازه‌ی container آن در آمده و نمایش داده می‌شود.


ایجاد لیستی از آیتم‌ها در بوت استرپ 4

بوت استرپ، کلاس‌هایی را برای ایجاد لیست‌هایی با ظاهر لیست‌های اندرویدی به همراه دارد که در ادامه با مثال‌هایی آن‌ها را بررسی خواهیم کرد.
<ul class="list-group mb-3">
    <li class="list-group-item active">Grooming</li>
    <li class="list-group-item list-group-item-action">
        General Health
    </li>
    <li class="list-group-item list-group-item-action">Nutrition</li>
    <li class="list-group-item list-group-item-action">
        Pest Control
    </li>
    <li class="list-group-item list-group-item-action">Vaccinations</li>
</ul>
با این خروجی:

در اینجا برای تشکیل لیستی با ظاهری شکیل‌تر، می‌توان ابتدا کلاس list-group را به ul انتساب داد و سپس به هر کدام از liهای آن کلاس list-group-item انتساب داده می‌شود. با افزودن کلاس active به اولین مورد، ظاهر آن با رنگی متمایز نمایان می‌شود. همچنین اگر علاقمند بودیم تا هر کدام از آیتم‌ها با عبور ماوس بر روی آن‌ها، با رنگ ملایمی مشخص شوند، می‌توان از کلاس list-group-item-action استفاده کرد.

این list-group را بر روی لینک‌ها و دکمه‌ها نیز می‌توان همانند قبل اعمال کرد:
<div class="list-group mb-3">
    <a class="list-group-item active" href="#">Grooming</a>
    <a class="list-group-item list-group-item-action list-group-item-success"
        href="#">Nutrition</a>
    <a class="list-group-item list-group-item-action  list-group-item-info"
        href="#">
        Pest Control
    </a>
    <a class="list-group-item list-group-item-action  list-group-item-warning"
        href="#">Vaccinations</a>
</div>
با این خروجی:


در اینجا از کلاس‌هایی مانند list-group-item-warning برای تغییر رنگ پس زمینه‌ی هر آیتم می‌توان کمک گرفت.

برای تعریف badge برای هر آیتم، می‌توان به صورت زیر عمل کرد:
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
    Nutrition
    <span class="badge badge-info badge-pill">12</span>
</li>
با این خروجی:


با اعمال کلاس‌های badge، این نشان نمایش داده می‌شود؛ اما دقیقا در کنار متن Nutrition در اینجا. برای اینکه آن‌را به سمت دیگر این ردیف منتقل و همچنین تراز عمودی آن‌را نیز به میانه‌ی صفحه تنظیم کنیم، می‌توان از Flexbox کمک گرفت. با اعمال d-flex، این ردیف تبدیل به یک Flexbox container می‌شود و سپس می‌توان کلاس‌های مخصوص Flexbox مانند justify-content-between و align-items-center را به این ردیف اعمال کرد تا ترازهای عمودی و افقی آیتم‌های قرار گرفته‌ی درون آن تغییر کنند.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_08.zip
نظرات مطالب
الگوی طراحی Factory Method به همراه مثال
اینکه نمیتوان مستقیما از کلاس abstract استفاده کرد که واضح است. سوال بنده این است که چه لزومی برای abstract کردن این کلاس وجود داشت؟ در حالی که کلاس مشتق شده از آن تنها همان یک متد کلاس والد را دارد. با این وجود چرا کلاس فرزند را حذف نکردیم و کلاس والد را از abstract خارج کنیم و مستقیما از همان کلاس استفاده کنیم؟
پاسخ به بازخورد‌های پروژه‌ها
حذف نظرات
تا آن جایی که یادم هست تکه کد زیر نظرات فرزند را نیز حذف میکرد. در نسخه‌های جدید EF هنوز تست نکردم.
 public void Remove(int id)
        {
            var selectedComment = _comments.Find(id);
            _comments.Where(x => x.ParentId == id).Load();
            _comments.Remove(selectedComment);
        }

نظرات مطالب
ASP.NET MVC #19
اگر قرار هست layout به ازای هر کاربر مختلف، جداگانه کش شود و در آن layout، اطلاعات خاص هر کاربر درج شده که از هر کاربر به کاربر دیگری متفاوت است (تنها دلیل منطقی کش نکردن layout) باید varyByCustom را مقدار دهی و پیاده سازی کرد. برای مثال یک پروفایل مخصوص را در web.config تعریف می‌کنید:
<caching>
  <outputCacheSettings>
    <outputCacheProfiles>
      <add name="Dashboard" duration="86400" varyByParam="*" varyByCustom="User" location="Server" />
    </outputCacheProfiles>
  </outputCacheSettings>
</caching>
جایی که قرار است view نمایش داده شود، این پروفایل را تنظیم خواهید کرد (در MVC کار نمایش View از View شروع نمی‌شود):
[OutputCache(CacheProfile="Dashboard")]
public class DashboardController : Controller { ...}
سپس باید در فایل global.asax.cs پیاده سازی و مقدار دهی varyByCustom، به ازای کاربران مختلف لاگین شده، انجام شود:
    //string arg filled with the value of "varyByCustom" in your web.config
    public override string GetVaryByCustomString(HttpContext context, string arg)
    {
        if (arg == "User")
             {
             // depends on your authentication mechanism
             return "User=" + context.User.Identity.Name;
             //?return "User=" + context.Session.SessionID;
             }

        return base.GetVaryByCustomString(context, arg);
    }
به این صورت view رندر شده، به ازای هر کاربر لاگین شده به صورت جداگانه کش می‌شود و این کش شدن به صورت عمومی، برای تمام کاربران و به یک شکل نیست.
مطالب
تزریق وابستگی‌ها در پروفایل‌های AutoMapper در برنامه‌های ASP.NET Core
Profileهای AutoMapper، قابلیت تزریق وابستگی‌ها را در سازنده‌ی خود ندارند؛ به همین جهت در این مطلب، دو راه حل را جهت رفع این محدودیت بررسی می‌کنیم.


مثال: نیاز به نگاشت کلمه‌ی عبور، به کلمه‌ی عبور هش شده

فرض کنید موجودیت کاربری که قرار است در بانک اطلاعاتی ذخیره شود، چنین ساختاری را دارد:
namespace AutoMapperInjection.Entities
{
    public class User
    {
        public int Id { set; get; }

        public string HashedPassword { set; get; }
    }
}
در اینجا کلمه‌ی عبور، به صورت معمولی ذخیره نمی‌شود و معادل هش شده‌ی آن ذخیره خواهد شد. اما اطلاعاتی را که از کاربر دریافت می‌کنیم:
namespace AutoMapperInjection.Models
{
    public class UserDto
    {
        public string Password { set; get; }
    }
}
حاوی کلمه‌ی عبور معمولی است که باید در حین نگاشت UserDto به User، هش شود. این اطلاعات نیز به صورت زیر توسط اکشن متد RegisterUser دریافت می‌شوند که توسط متد mapper.Map، قرار است به یک نمونه از شیء User تبدیل شود:
namespace AutoMapperInjection.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class HomeController : ControllerBase
    {
        private readonly IMapper _mapper;

        public HomeController(IMapper mapper)
        {
            _mapper = mapper ?? throw new NullReferenceException(nameof(mapper));
        }

        [HttpPost("[action]")]
        public IActionResult RegisterUser(UserDto model)
        {
            var user = _mapper.Map<User>(model);

            // TODO: Save user

            return Ok();
        }
    }
}
کار این هش شدن نیز برای مثال توسط سرویس زیر انجام می‌شود:
using System;
using System.Security.Cryptography;
using System.Text;

namespace AutoMapperInjection.Services
{
    public interface IPasswordHasherService
    {
        string GetSha256Hash(string input);
    }

    public class PasswordHasherService : IPasswordHasherService
    {
        public string GetSha256Hash(string input)
        {
            using var hashAlgorithm = new SHA256CryptoServiceProvider();
            var byteValue = Encoding.UTF8.GetBytes(input);
            var byteHash = hashAlgorithm.ComputeHash(byteValue);
            return Convert.ToBase64String(byteHash);
        }
    }
}
به همین جهت در حین تعریف نگاشت‌های AutoMaper، نیاز خواهیم داشت تا بتوانیم از این سرویس استفاده کنیم.


روش اول: IValueResolver‌ها قابلیت تزریق وابستگی را در سازنده‌ی خود دارند

توسط یک IValueResolver سفارشی، می‌توانیم مشخص کنیم که برای مثال اطلاعات یک خاصیت خاص، چگونه باید از منبع دریافتی تامین شود:
        public class HashedPasswordResolver : IValueResolver<UserDto, User, string>
        {
            private readonly IPasswordHasherService _hasher;

            public HashedPasswordResolver(IPasswordHasherService hasher)
            {
                _hasher = hasher ?? throw new ArgumentNullException(nameof(hasher));
            }

            public string Resolve(UserDto source, User destination, string destMember, ResolutionContext context)
            {
                return _hasher.GetSha256Hash(source.Password);
            }
        }
همانطور که مشاهده می‌کنید، در اینجا می‌توان سرویس مدنظر را به سازنده‌ی این کلاس تزریق کرد و سپس از آن جهت تامین مقدار هش شده‌ی کلمه‌ی عبور استفاده کرد. IValueResolverها تنها تامین کننده‌ی مقدار یک خاصیت، در حین نگاشت هستند.

پس از آن، روش استفاده‌ی از این تامین کننده‌ی مقدار سفارشی، به صورت زیر است:
    public class UserDtoMappingsProfile : Profile
    {
        public UserDtoMappingsProfile()
        {
            // Map from User (entity) to UserDto, and back
            this.CreateMap<User, UserDto>()
                .ReverseMap()
                .ForMember(user => user.HashedPassword, exp => exp.MapFrom<HashedPasswordResolver>());
        }
    }
با این تعاریف، هر زمانیکه قرار است کار var user = _mapper.Map<User>(model) انجام شود، مقدار خاصیت HashedPassword، از طریق HashedPasswordResolver تامین می‌شود که در اینجا کار تزریق وابستگی‌های آن نیز به صورت خودکار توسط AutoMapper مدیریت می‌شود. البته بدیهی است که سرویس IPasswordHasherService باید به نحو زیر پیشتر به سیستم معرفی شده باشد:
namespace AutoMapperInjection
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IPasswordHasherService, PasswordHasherService>();
            services.AddAutoMapper(typeof(UserDtoMappingsProfile).Assembly);
            services.AddControllers();
        }


روش دوم: IMappingAction‌ها قابلیت تزریق وابستگی را در سازنده‌ی خود دارند

می‌توان پیش و یا پس از عملیات نگاشت، منطقی را توسط یک IMappingAction سفارشی بر روی آن اجرا کرد که در اینجا نیز امکان تزریق وابستگی در سازنده‌ی IMappingActionهای پیاده سازی شده، وجود دارد:
        public class UserDtoMappingsAction : IMappingAction<UserDto, User>
        {
            private readonly IPasswordHasherService _hasher;

            public UserDtoMappingsAction(IPasswordHasherService hasher)
            {
                _hasher = hasher ?? throw new ArgumentNullException(nameof(hasher));
            }

            public void Process(UserDto source, User destination, ResolutionContext context)
            {
                destination.HashedPassword = _hasher.GetSha256Hash(source.Password);
            }
        }
در اینجا می‌خواهیم مقدار یک یا چندین خاصیت از مقصد نگاشت را (شیء User در اینجا) پس از نگاشت ابتدایی از طریق مقدار دریافتی از کاربر، با منطق خاصی تغییر دهیم. برای مثال کلمه‌ی عبور ساده‌ی دریافتی را هش کنیم و به خاصیت خاصی نسبت دهیم (و یا حتی مقدار خواص دیگری را نیز پس از نگاشت، تغییر دهیم).

در این حالت روش استفاده‌ی از کلاس UserDtoMappingsAction به صورت زیر است:
    public class UserDtoMappingsProfile : Profile
    {
        public UserDtoMappingsProfile()
        {
            // Map from User (entity) to UserDto, and back
            this.CreateMap<User, UserDto>()
                .ReverseMap()
                .AfterMap<UserDtoMappingsAction>();
        }
    }


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: AutoMapperInjection.zip
مطالب
Vue CLI
تیم Vue یک ابزار را جهت scaffold سریع یک پروژه Vue، به صورت رسمی ارائه کرده‌است. توسط این ابزار به صورت سریع می‌توانیم ساختار یک پروژه استاندارد Vue را ایجاد کنیم.

چرا نیاز به Vue CLI داریم؟
  • زیرا نیاز به build processهایی داریم که به ما امکان استفاده از ES6, SCSS و دیگر ویژگیهای عالی را خواهند داد.
  • جهت ساخت و یکی‌سازی فایل‌های تمپلیت
  • بارگذاری نکردن تمامی فایل‌ها به صورت یکجا در زمان Startup 
  • می‌توانیم تسک‌هایی از قبیل Server-side rendering, code-splitting را انجام دهیم.
 
نصب Vue CLI 
ابتدا مطمئن شوید که آخرین نگارش Node.js را نصب کرده‌اید. سپس جهت نصب Vue CLI، خط فرمان را گشوده و دستور زیر ذیل را صادر کنید: 
npm install -g vue-cli

با اجرای فرمان فوق، ابزار CLI به صورت global و عمومی نصب خواهد شد. در ادامه می‌توانیم با دستور vue list، لیستی از قالب‌های رسمی را که توسط CLI قابل ایجاد هستند، مشاهده نمائید: 

در اینجا ما از قالب webpack-simple استفاده خواهیم کرد. برای اینکار دستور زیر را جهت ایجاد یک پروژه بر اساس این قالب صادر کنید: 
vue init webpack-simple dntVue

به این ترتیب در سریعترین زمان ممکن توانستیم یک برنامه‌ی Vue را ایجاد کنیم: 

در اینجا ساختار یک پروژه جدید Vue را مشاهده می‌کنید:

index.html: کار شروع و ارائه برنامه را انجام می‌دهد.
package.json: وابستگی‌های npm برنامه را به همراه دارد.
src/App.vue: کامپوننت اصلی برنامه است.
پوشه src/assets: حاوی فایل‌های استاتیک پروژه است.
src/main.js: نقطه‌ی آغاز برنامه است.
webpack.config.json: تنظیمات وب‌پک جهت اجرای پروژه و بارگذاری ماژول‌های موردنیاز.


اجرای برنامه
ابتدا نیاز است وابستگی‌های برنامه دریافت شوند. اینکار را توسط دستور npm install و یا دستور yarn (در صورتیکه yarn را از قبل بر روی سیستم خود نصب کرده‌اید) انجام خواهیم داد:
npm install
به این ترتیب تمامی وابستگی‌های پروژه درون پوشه‌ی node_module تشکیل خواهند شد. اکنون می‌توانیم با صدور دستور npm run dev پروژه را اجرا کنیم:

بررسی فایل‌های Vue
درون یک برنامه‌ی Vue واقعی، فایل‌هایی با پسوند vue. وجود دارند. این فایل شامل تمپلیت، کدها و همچنین استایل‌های یک کامپوننت می‌باشند. 
<template>
    <div>
        <!-- Write your HTML with Vue in here -->
    </div>
</template>

<script>
    export default {
        // Write your Vue component logic here
    }
</script>

<style scoped>
    /* Write your styles for the component in here */
</style>

بنابراین درون فایلی با ساختار فوق، تمامی موارد مورد نیاز برای یک کامپوننت ویو را خواهیم داشت و به اصطلاح نیازی به context switching نخواهیم داشت؛ زیرا تمامی قسمت‌ها را به صورت یکجا در یک محل در اختیار داریم و به راحتی می‌توانیم تمرکز خود را بر روی کدها قرار دهیم. درون کامپوننت نیز می‌توانیم کامپوننت‌های موردنیاز را ایمپورت و از آن استفاده کنیم: 
import { New } from "./components/New.vue";
export default {
    components: {
        New
    }
}


Vue CLI 3
تا اینجا از نسخه‌ی پایدار Vue CLI استفاده کردیم. نسخه‌ی 3 آن هنوز در مرحله‌ی beta قرار دارد. در این نسخه امکانات و دستورات بیشتری اضافه شده‌است؛ از ایجاد یک پروژه ساده تا ایجاد یک پروژه مبتنی بر TypeScript. برای نصب و یا آپگرید می‌توانید از دستور زیر استفاده کنید:
npm install -g @vue/cli
اکنون می‌توانید با صادر کردن دستور vue --version، شماره نسخه‌ی آن را مشاهده نمائید:
3.0.0-beta.11


ایجاد یک پروژه جدید
برای ایجاد یک پروژه جدید می‌توانید دستور زیر را صادر کنید:
vue create my-project
همانطور که مشاهده می‌کنید در این نسخه بجای استفاده از دستور vue init، از vue create استفاده شده است. در اینحالت می‌توانید نوع ایجاد پروژه را تعیین کنید:
Vue CLI v3.0.0-beta.11
? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)
  Manually select features
حالت پیش‌فرض، چنین ساختاری را برایتان ایجاد خواهد کرد:

بعد از طی کردن مراحل، می‌توانید قالب پروژه‌ی ایجاد شده را به صورت یک preset داشته باشید تا در پروژه‌های آینده مجبور نباشید مراحل قبل را طی کنید. این preset درون یک فایل JSON به صورت زیر ذخیره خواهد شد و حاوی اطلاعات زیر است:
{
  "useConfigFiles": true,
  "router": true,
  "vuex": true,
  "cssPreprocessor": "sass",
  "plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-eslint": {
      "config": "airbnb",
      "lintOn": ["save", "commit"]
    }
  }
}

در حالت manually نیز می‌توانید گزینه‌های بیشتری را برای تعیین نوع قالب پروژه، انتخاب نمائید. به عنوان مثال می‌توان از TypeScript یا اینکه از lintter یا formatter خاصی برای کدها استفاده کرد:

در ادامه دیگر آپشن‌ها را نیز می‌توانید تعیین کرده و در نهایت به صورت یک قالب از پیش تعریف شده نیز پروژه را داشته باشید:


Zero-config Prototyping 
یکی از قابلیت‌های جالب Vue، امکان تهیه سریع prototype یا طرح اولیه می‌باشد. شاید اکثر اوقات نیاز داشته باشید یک ویژگی یا قابلیت خاص را با Vue تست کنید. در این موارد ممکن است از سایتی مانند CodePen استفاده کنید. اما توسط افزونه‌ی cli-service-global می‌توانید به صورت لوکال و بدون نیاز به راه‌اندازی یک پروژه‌ی جدید، کدهای موردنیاز را آزمایش کنید. فرض کنید می‌خواهیم تمپلیت زیر را قبل از افزودن آن به پروژه، مورد تست قرار دهیم:
<!-- MyCard.vue -->
<template>
    <div class="card">
    <h1>Card Title</h1>
    <p>Card content goes here. Make sure it's not Lorem.</p>
    </div>
</template>
در این‌حالت می‌توانیم با نصب افزونه موردنظر، فایل فوق را به راحتی و بدون نیاز به راه‌اندازی یک پروژه جدید، تست کنیم:
npm install -g @vue/cli-service-global
اکنون می‌توانیم خروجی را با صدور فرمان زیر درون مرورگر مشاهده کنیم:
vue serve MyCard.vue
با صدرو فرمان فوق، فایل توسط افزونه‌ی عنوان شده، از طریق مرورگر قابل دسترسی می‌باشد:

خروجی:

مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویس‌ها و تزریق وابستگی‌ها
پیشنیازها (الزامی)

«بررسی مفاهیم معکوس سازی وابستگی‌ها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعه‌ی بیشتر»


تزریق وابستگی‌ها (یا Dependency injection = DI) به معنای ارسال نمونه‌ای/وهله‌ای از وابستگی (یک سرویس) به شیء وابسته‌ی به آن (یک کلاینت) است. در فرآیند تزریق وابستگی‌ها، یک کلاس، وهله‌های کلاس‌های دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت می‌کند. بنابراین بجای نوشتن newها در کلاس جاری، آن‌ها را به صورت وابستگی‌هایی در سازنده‌ی کلاس تعریف می‌کنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورک‌هایی که کار وهله سازی این وابستگی‌ها را انجام می‌دهند، IoC Container و یا DI container می‌گوییم (IoC =  inversion of control ).
چندین نوع تزریق وابستگی‌ها وجود دارند که دو حالت زیر، عمومی‌ترین آن‌ها است:
الف) تزریق در سازنده‌ی کلاس: لیست وابستگی‌های یک کلاس، به عنوان پارامترهای سازنده‌ی آن ذکر می‌شوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی می‌کند و سپس IoC Container با وهله سازی آن‌ها، وابستگی‌های مورد نیاز را تامین خواهد کرد.


تزریق وابستگی‌ها در ASP.NET Core

برخلاف نگارش‌های قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شده‌است و این مفهوم، در سراسر اجزای آن به صورت یکپارچه‌ای پشتیبانی می‌شود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی می‌کند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار می‌گیرد و در اینجا شما هستید که مسئول تعریف نحوه‌ی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهله‌ی جدید از آن توسط IoC Container تولید و ارائه می‌شود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین می‌شود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کننده‌ها قرار می‌گیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیء‌ایی خاص در اختیار تمام مصرف کننده‌های آن قرار گیرد (single instance per web request).

طول عمر singleton، برای سرویس‌ها و کلاس‌های config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنش‌ها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژه‌ای درخواست شود، یک وهله‌ی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهله‌ی از وابستگی مورد نظر، در بین تمام اشیاء وابسته‌ی به آن، در طول عمر آن scope تولید می‌شود.
بنابراین در برنامه‌های وب دو نوع singleton برای معرفی کلاس‌های config و نوع scoped برای پیاده سازی تراکنش‌ها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازه‌ی از کلاس‌های مختلف مورد نیاز)، بیشتر از همه به کار برده می‌شوند.


یک مثال کاربردی: بررسی نحوه‌ی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core


مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شده‌است، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشه‌ی src، کلیک راست کرده و گزینه‌ی Add new project را انتخاب کنید و سپس یک پروژه‌ی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونه‌ی سرویس پیام‌ها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
namespace Core1RtmEmptyTest.Services
{
    public interface IMessagesService
    {
        string GetSiteName();
    }
 
    public class MessagesService : IMessagesService
    {
        public string GetSiteName()
        {
            return "DNT";
        }
    }
}
در ادامه به پروژه‌ی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.Services را انتخاب کنید، تا اسمبلی آن‌را بتوان در پروژه‌ی جاری استفاده کرد.


انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{
    "dependencies": {
        // same as before
        "Core1RtmEmptyTest.Services": "1.0.0-*"
    },
در ادامه قصد داریم این سرویس را به متد Configure کلاس Startup تزریق کرده و سپس خروجی رشته‌ای آن‌را توسط میان افزار Run آن نمایش دهیم. برای این منظور فایل Startup.cs را گشوده و امضای متد Configure را به نحو ذیل تغییر دهید:
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
همانطور که در قسمت قبل نیز عنوان شد، متد Configure دارای امضای ثابتی نیست و هر تعداد سرویسی را که نیاز است، می‌توان در اینجا اضافه کرد. یک سری از سرویس‌ها مانند IApplicationBuilder و IHostingEnvironment پیشتر توسط IoC Container توکار ASP.NET Core معرفی و ثبت شده‌اند. به همین جهت، همینقدر که در اینجا ذکر شوند، کار می‌کنند و نیازی به تنظیمات اضافه‌تری ندارند. اما سرویس IMessagesService ما هنوز به این IoC Container معرفی نشده‌است. بنابراین نمی‌داند که چگونه باید این اینترفیس را وهله سازی کند.
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
{ 
    app.Run(async context =>
    {
        var siteName = messagesService.GetSiteName();
        await context.Response.WriteAsync($"Hello {siteName}");
    });
}
در این حالت اگر برنامه را اجرا کنیم، به این خطا برخواهیم خورد:
 System.InvalidOperationException
No service for type 'Core1RtmEmptyTest.Services.IMessagesService' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)

System.Exception
Could not resolve a service of type 'Core1RtmEmptyTest.Services.IMessagesService' for the parameter 'messagesService' of method 'Configure' on type 'Core1RtmEmptyTest.Startup'.
at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
برای رفع این مشکل، به متد ConfigureServices کلاس Startup مراجعه کرده و سیم کشی‌های مرتبط را انجام می‌دهیم. در اینجا باید اعلام کنیم که «هر زمانیکه به IMessagesService رسیدی، یک وهله‌ی جدید (transient) از کلاس MessagesService را به صورت خودکار تولید کن و سپس در اختیار مصرف کننده قرار بده»:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IMessagesService, MessagesService>();
    }
در اینجا نحوه‌ی ثبت یک سرویس را در IoC Containser توکار ASP.NET Core ملاحظه می‌کنید. تمام حالت‌های طول عمری که در ابتدای بحث عنوان شدند، یک متد ویژه‌ی خاص خود را در اینجا دارند. برای مثال حالت transient دارای متد ویژه‌ی AddTransient است و همینطور برای سایر حالت‌ها. این متدها به صورت جنریک تعریف شده‌اند و آرگومان اول آن‌ها، اینترفیس سرویس و آرگومان دوم، پیاده سازی آن‌ها است (سیم کشی اینترفیس، به کلاس پیاده سازی کننده‌ی آن).

پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:


و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، می‌داند زمانیکه به IMessagesService رسید، چگونه باید آن‌را وهله سازی کند.


چه سرویس‌هایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شده‌اند؟

در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:


همانطور که ملاحظه می‌کنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شده‌اند که تاکنون با دو مورد از آن‌ها کار کرده‌ایم.


امکان تزریق وابستگی‌ها در همه جا!

در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکته‌ی مهم اینجا است که برخلاف نگارش‌های قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمت‌های مختلف برنامه)، می‌توان این تزریق‌ها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکته‌ی ویژه‌ی نحوه‌ی تزریق وابستگی‌ها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام این‌ها از روز اول کار می‌کنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.


نیاز به تعویض IoC Container توکار ASP.NET Core

قابلیت تزریق وابستگی‌های توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندی‌های اصلی آن طراحی شده‌است و نه بیشتر. بنابراین توسط آن قابلیت‌های پیشرفته‌ای را که سایر IoC Containers ارائه می‌دهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگی‌های توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
 • امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
 • پشتیبانی از Setter Injection
 • امکان انتخاب سازنده‌ای خاص (اگر چندین سازنده تعریف شده باشند)
 • سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
 • پشتیبانی توکار از Lazy و Func
 • امکان وهله سازی از نوع‌های concrete (یا همان کلاس‌های معمولی)
 • پشتیبانی از مفاهیمی مانند Interception و AOP
 • امکان اسکن اسمبلی‌های مختلف جهت یافتن اینترفیس‌ها و اتصال خودکار آن‌ها (طراحی‌های افزونه پذیر)


روش تعویض IoC Container توکار ASP.NET Core با StructureMap

جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگی‌ها در ASP.NET Core 1.0» می‌توانید مطالعه کنید.
یا می‌توانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژه‌ی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شده‌است. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بسته‌ی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{
    "dependencies": {
        // same as before  
        "StructureMap.Dnx": "0.5.1-rc2-final"
    },
ب) جایگزین کردن Container استراکچرمپ با Container توکار ASP.NET Core
پس از نصب بسته‌ی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddDirectoryBrowser();
 
        var container = new Container();
        container.Configure(config =>
        {
            config.Scan(_ =>
            {
                _.AssemblyContainingType<IMessagesService>();
                _.WithDefaultConventions();
            });
            //config.For<IMessagesService>().Use<MessagesService>();
 
            config.Populate(services);
        });
        container.Populate(services);
 
        return container.GetInstance<IServiceProvider>();
    }
در اینجا ابتدا خروجی متد ConfigureServices، به IServiceProvider تغییر کرده‌است تا استراکچرمپ این تامین کننده‌ی سرویس‌ها را ارائه دهد. سپس Container مربوط به استراکچرمپ، وهله سازی شده و همانند روال متداول آن، یک سرویس و کلاس پیاده سازی کننده‌ی آن معرفی شده‌اند (و یا هر تنظیم دیگری را که لازم بود باید در اینجا اضافه کنید). در پایان کار متد Configure آن و پس از این متد، نیاز است متدهای Populate فراخوانی شوند (اولی تعاریف را اضافه می‌کند و دومی کار تنظیمات را نهایی خواهد کرد).
سپس وهله‌ای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده می‌شود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شده‌است، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل می‌شود (name هر نامی می‌تواند باشد).
مطالب
نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Tooltip به کمک Twitter bootstrap
این مطلب در ادامه بحث «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» می‌باشد. بنابراین تعاریف مدل و کنترلر آن به همراه توضیحات ذکر شده در آن، در ادامه مورد استفاده قرار خواهند گرفت.


اصول نمایش Tooltip در Bootstrap

Tooltip یکی دیگر از کامپوننت‌های جاوا اسکریپتی Bootstrap است.
<a rel="tooltip" title="یک سری توضیحات در اینجا" href="#">اطلاعات</a>

<script type="text/javascript">  
   $(document).ready(function () {  
     $("[rel='tooltip']").tooltip({placement:'top', trigger : 'hover'});  
   });  
</script>
نحوه استفاده از آن را در مثال فوق مشاهده می‌کنید.
برای المان مورد نظر، یک title تعریف کرده‌ایم. برای اینکه tooltip پیش فرض مرورگر در این حالت ظاهر نشود، یک rel=tooltip را در اینجا به المان اضافه کرده و سپس افزونه tooltip بوت استرپ، بر روی کلیه المان‌هایی با rel=tooltip فراخوانی شده است.
placement، محل قرارگیری tooltip را مشخص می‌کند؛ مانند بالا، پایین، چپ و راست. trigger مشخص می‌کند که این tooltip در چه زمانی ظاهر شود. برای مثال در حالت hover یا در حالت focus یک المان.
در اینجا بجای title، از ویژگی data-original-title نیز می‌توان استفاده کرد.



تبدیل خطاهای اعتبارسنجی ASP.NET MVC به Tooltip

هدف ما در اینجا، نهایتا رسیدن به شکل زیر می‌باشد:


همانطور که ملاحظه می‌کنید، اینبار بجای نمایش خطاها در یک برچسب مقابل کنترل متناظر، این خطا صرفا در حالت فوکوس کنترل، به شکل یک Tooltip در بالای آن ظاهر شده است.


کدهای کامل View برنامه

@model Mvc4TwitterBootStrapTest.Models.User
@{
    ViewBag.Title = "تعریف کاربر جدید";
}
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset class="form-horizontal">
        <legend>تعریف کاربر جدید</legend>
        <div class="control-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label" })
            <div class="controls">
                @Html.EditorFor(model => model.Name)
                @*@Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })*@
            </div>
        </div>
        <div class="control-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
            <div class="controls">
                @Html.EditorFor(model => model.LastName)
                @*@Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })*@
            </div>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">
                ارسال</button>
        </div>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
        $.validator.setDefaults({            
            showErrors: function (errorMap, errorList) {
                this.defaultShowErrors();
                //اگر المانی معتبر است نیاز به نمایش تول‌تیپ ندارد
                $("." + this.settings.validClass).tooltip("destroy");
                //افزودن تول‌تیپ‌ها
                for (var i = 0; i < errorList.length; i++) {
                    var error = errorList[i];
                    $(error.element).tooltip({ trigger: "focus" }) // فقط در حالت فوکوس نمایش داده شود
                                    .attr("data-original-title", error.message);
                }
            },
            // همانند قبل برای رنگی کردن کل ردیف در صورت عدم اعتبار سنجی و برعکس
            highlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                } else {
                    $(element).addClass(errorClass).removeClass(validClass);
                    $(element).closest('.control-group').removeClass('success').addClass('error');
                }
                $(element).trigger('highlited');
            },
            unhighlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                } else {
                    $(element).removeClass(errorClass).addClass(validClass);
                    $(element).closest('.control-group').removeClass('error').addClass('success');
                }
                $(element).trigger('unhighlited');
            }
        });
        //برای حالت پست بک از سرور عمل می‌کند
        $(function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        });
    </script>
}
کدهای مدل و کنترلر همانند مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» می‌باشند و از تکرار مجدد آن‌ها در اینجا صرفنظر گردید.

توضیحات
- با توجه به اینکه دیگر نمی‌خواهیم خطاها به صورت برچسب در مقابل کنترل‌ها نمایش داده شوند، کلیه Html.ValidationMessageFor به صورت کامنت درآورده شده‌اند.
- تغییر دوم مطلب جاری، اضافه شدن متد showErrors به تنظیمات پیش فرض jQuery Validator است. در این متد، اگر المانی معتبر بود، Tooltip آن حذف می‌شود؛ یا در سایر حالات، المان‌هایی که نیاز به اعتبارسنجی سمت کلاینت دارند، یافت شده و سپس ویژگی data-original-title با مقداری معادل خطای اعتبارسنجی متناظر، به این المان افزوده شده و سپس متد tooltip بوت استرپ بر روی آن فراخوانی می‌گردد.
به عبارتی زمانیکه یک input box در ASP.NET MVC به همراه مقادیر مرتبط با اعتبارسنجی آن رندر می‌شود، چنین شکلی را خواهد داشت:
<input class="text-box single-line" data-val="true" data-val-required="لطفا نام را تکمیل کنید"
 id="Name" name="Name" type="text" value="" />
اما در اینجا به صورت پویا data-original-title نیز به آن افزوده می‌گردد:
<input class="text-box single-line input-validation-error" data-val="true" 
data-val-required="لطفا نام را تکمیل کنید" id="Name" name="Name" type="text" value="" 
data-original-title="لطفا نام را تکمیل کنید" title="">
این مقدار توسط افزونه tooltip بوت استرپ شناسایی شده و مورد استفاده قرار می‌گیرد.
مطالب
لیست کردن ایمیل‌های موجود در Global address list

Global Address List یا به اختصار GAL و یا همان Microsoft Exchange Global Address Book ، حاوی اطلاعات تمامی کاربران تعریف شده در Exchange server مایکروسافت است و زمانیکه outlook در شبکه به exchange server متصل می‌شود، کاربران می‌توانند با کمک آن لیست اعضاء را مشاهده کرده ، یک یا چند نفر را انتخاب نموده و به آن‌ها ایمیل ارسال کنند (شکل زیر):


نیاز بود تا این لیست تعریف شده در مایکروسافت اکسچنج، با اطلاعات یک دیتابیس مقایسه شوند که آیا این اطلاعات مطابق رکوردهای موجود تعریف شده یا خیر.
بنابراین اولین قدم، استخراج email های موجود در GAL بود (دسترسی به همین برگه‌ی email address که در شکل فوق ملاحظه می‌کنید از طریق برنامه نویسی) که خلاصه آن تابع زیر است:
جهت استفاده از آن ابتدا باید یک ارجاع به کتابخانه COM ایی به نام Microsoft Outlook Object Library اضافه شود.

using System.Collections.Generic;
using System.Reflection;
using Microsoft.Office.Interop.Outlook;

namespace GAL
{
//add a reference to Microsoft Outlook 12.0 Object Library
class COutLook
{
public struct User
{
public string Name;
public string Email;
}

public static List<User> ExchangeServerEmailAddresses(string userName)
{
List<User> res = new List<User>();
//Create Outlook application
Application outlookApp = new Application();
//Get Mapi NameSpace and Logon
NameSpace ns = outlookApp.GetNamespace("MAPI");
ns.Logon(userName, Missing.Value, false, true);

//Get Global Address List
AddressLists addressLists = ns.AddressLists;
AddressList globalAddressList = addressLists["Global Address List"];
AddressEntries entries = globalAddressList.AddressEntries;
foreach (AddressEntry entry in entries)
{
ExchangeUser user = entry.GetExchangeUser();
if (user != null && user.PrimarySmtpAddress != null && entry.Name != null)
res.Add(new User
{
Name = entry.Name,
Email = user.PrimarySmtpAddress
});
}

ns.Logoff();

// Clean up.
outlookApp = null;
ns = null;
addressLists = null;
globalAddressList = null;
entries = null;

return res;
}
}
}
و نحوه استفاده از آن هم به صورت زیر می‌تواند باشد:

List<COutLook.User> data = COutLook.ExchangeServerEmailAddresses("nasiri");
foreach (var list in data)
{
//....
}
در اینجا Nasiri نام کاربری شخص در دومین است (کاربر جاری لاگین کرده در سیستم).
تنها نکته‌ی مهم این کد، مهیا نبودن فیلد ایمیل در شیء AdderssEntry است که باید از طریق متد GetExchangeUser آن اقدام شود.


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


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


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

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



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

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

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


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

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

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


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

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