نظرات مطالب
رمزنگاری Connection String از طریق خط فرمان
البته خیلی‌ها دسترسی به سرور برای انجام این نوع کارها را ندارند. به همین جهت امکان انجام آن از طریق برنامه نویسی هم مهیا شده: (^)
مطالب
الگویی برای مدیریت دسترسی همزمان به ConcurrentDictionary
ConcurrentDictionary، ساختار داده‌ای است که امکان افزودن، دریافت و حذف عناصری را به آن به صورت thread-safe میسر می‌کند. اگر در برنامه‌ای نیاز به کار با یک دیکشنری توسط چندین thread وجود داشته باشد، ConcurrentDictionary راه‌حل مناسبی برای آن است.
اکثر متدهای این کلاس thread-safe طراحی شده‌اند؛ اما با یک استثناء: متد GetOrAdd آن thread-safe نیست:
 TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);


بررسی نحوه‌ی کار با متد GetOrAdd

این متد یک کلید را دریافت کرده و سپس بررسی می‌کند که آیا این کلید در مجموعه‌ی جاری وجود دارد یا خیر؟ اگر کلید وجود داشته باشد، مقدار متناظر با آن بازگشت داده می‌شود و اگر خیر، delegate ایی که به عنوان پارامتر دوم آن معرفی شده‌است، اجرا خواهد شد، سپس مقدار بازگشت داده شده‌ی توسط آن به مجموعه اضافه شده و در آخر این مقدار به فراخوان بازگشت داده می‌شود.
var dictionary = new ConcurrentDictionary<string, string>();
 
var value = dictionary.GetOrAdd("key1", x => "item 1");
Console.WriteLine(value);
 
value = dictionary.GetOrAdd("key1", x => "item 2");
Console.WriteLine(value);
در این مثال زمانیکه اولین GetOrAdd فراخوانی می‌شود، مقدار item 1 بازگشت داده خواهد شد و همچنین این مقدار را در مجموعه‌ی جاری، به کلید key1 انتساب می‌دهد. در دومین فراخوانی، چون key1 در دیکشنری، دارای مقدار است، همان را بازگشت می‌دهد و دیگر به value factory ارائه شده مراجعه نخواهد کرد. بنابراین خروجی این مثال به صورت ذیل است:
item 1
item 1


دسترسی همزمان به متد GetOrAdd امن نیست

ConcurrentDictionary برای اغلب متدهای آن به صورت توکار مباحث قفل‌گذاری چند ریسمانی را اعمال می‌کند؛ اما نه برای متد GetOrAdd. زمانیکه valueFactory آن در حال اجرا است، دسترسی همزمان به آن thread-safe نیست و ممکن است بیش از یکبار فراخوانی شود.
یک مثال:
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new ConcurrentDictionary<int, int>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
            var addStack = new ConcurrentStack<int>();

            Parallel.For(1, 1000, options, i =>
            {
                var key = i % 10;
                dictionary.GetOrAdd(key, k =>
                {
                    addStack.Push(k);
                    return i;
                });
            });

            Console.WriteLine($"dictionary.Count: {dictionary.Count}");
            Console.WriteLine($"addStack.Count: {addStack.Count}");
        }
    }
}
یک نمونه خروجی این مثال می‌تواند به صورت ذیل باشد:
dictionary.Count: 10
addStack.Count: 13
در اینجا هر چند 10 آیتم در دیکشنری ذخیره شده‌اند، اما عملیاتی که در value factory متد GetOrAdd آن صورت گرفته، 13 بار اجرا شده‌است (بجای 10 بار).
علت اینجا است که در این بین، متد GetOrAdd توسط ترد A فراخوانی می‌شود، اما key را در دیکشنری جاری پیدا نمی‌کند. به همین جهت شروع به اجرای valueFactory آن خواهد کرد. در همین زمان ترد B نیز به دنبال همین key است. ترد قبلی هنوز به پایان کار خودش نرسیده‌است که مجددا valueFactory متعلق به همین key اجرا خواهد شد. به همین جهت است که در ConcurrentStack اجرا شده‌ی در valueFactory، بیش از 10 آیتم موجود هستند.


الگویی برای مدیریت دسترسی همزمان امن به متد GetOrAdd‌

یک روش برای دسترسی همزمان امن به متد GetOrAdd، توسط تیم ASP.NET Core به صورت ذیل ارائه شده‌است:
// 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more
// once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple
// threads but only one of the objects succeeds in creating a pipeline.
private readonly ConcurrentDictionary<Type, Lazy<RequestDelegate>> _pipelinesCache = 
new ConcurrentDictionary<Type, Lazy<RequestDelegate>>();
در اینجا با استفاده از کلاس Lazy، از ایجاد چندین pipeline به ازای یک key مشخص جلوگیری شده‌است.
یک مثال:
namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new ConcurrentDictionary<int, Lazy<int>>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
            var addStack = new ConcurrentStack<int>();

            Parallel.For(1, 1000, options, i =>
            {
                var key = i % 10;
                dictionary.GetOrAdd(key, k => new Lazy<int>(() =>
                {
                    addStack.Push(k);
                    return i;
                }));
            });

            // Access the dictionary values to create lazy values.
            foreach (var pair in dictionary)
                Console.WriteLine(pair.Value.Value);

            Console.WriteLine($"dictionary.Count: {dictionary.Count}");
            Console.WriteLine($"addStack.Count: {addStack.Count}");
        }
    }
}
با این خروجی:
10
1
2
3
4
5
6
7
8
9
dictionary.Count: 10
addStack.Count: 10
اینبار، هم dictionary و هم addStack دارای 10 عضو هستند که به معنای تنها اجرای 10 بار value factory است و نه بیشتر.
در این مثال دو تغییر صورت گرفته‌اند:
الف) مقادیر ConcurrentDictionary به صورت Lazy معرفی شده‌اند.
ب) متد GetOrAdd نیز یک مقدار Lazy را بازگشت می‌دهد.

زمانیکه از اشیاء Lazy استفاده می‌شود، خروجی‌های بازگشتی از GetOrAdd، توسط این اشیاء Lazy محصور خواهند شد. اما نکته‌ی مهم اینجا است که هنوز value factory آن‌ها فراخوانی نشده‌است. این فراخوانی تنها زمانی صورت می‌گیرد که به خاصیت Value یک شیء Lazy دسترسی پیدا کنیم و این دسترسی نیز به صورت thread-safe طراحی شده‌است. یعنی حتی اگر چند ترد new Lazy یک key مشخص را بازگشت دهند، تنها یکبار value factory متد GetOrAdd با دسترسی به خاصیت Value این اشیاء Lazy فراخوانی می‌شود و مابقی تردها منتظر مانده و تنها مقدار ذخیره شده‌ی در دیکشنری را دریافت می‌کنند و سبب اجرای مجدد value factory سنگین و زمانبر آن، نخواهند شد.

بر این مبنا می‌توان یک LazyConcurrentDictionary را نیز به صورت ذیل طراحی کرد:
    public class LazyConcurrentDictionary<TKey, TValue>
    {
        private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _concurrentDictionary;
        public LazyConcurrentDictionary()
        {
            _concurrentDictionary = new ConcurrentDictionary<TKey, Lazy<TValue>>();
        }

        public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
        {
            var lazyResult = _concurrentDictionary.GetOrAdd(key,
             k => new Lazy<TValue>(() => valueFactory(k), LazyThreadSafetyMode.ExecutionAndPublication));
            return lazyResult.Value;
        }
    }
در اینجا ممکن است چندین ترد همزمان متد GetOrAdd را دقیقا با یک کلید مشخص فراخوانی کنند؛ اما تنها چندین شیء Lazy بسیار سبک که هنوز اطلاعات محصور شده‌ی توسط آن‌ها اجرا نشده‌است، ایجاد خواهند شد. اولین تردی که به خاصیت Value آن دسترسی پیدا کند، سبب اجرای delegate زمانبر و سنگین آن شده و مابقی تردها مجبور به منتظر ماندن جهت بازگشت این نتیجه از دیکشنری خواهند شد (و نه اجرای مجدد delegate).
در مثال فوق، به صورت صریحی پارامتر LazyThreadSafetyMode نیز مقدار دهی شده‌است. هدف از آن اطمینان حاصل کردن از آغاز این شیء Lazy با دسترسی به خاصیت Value آن، تنها توسط یک ترد است.

نمونه‌ی دیگر کار با خاصیت ویژه‌ی Value شیء Lazy را در مطلب «پشتیبانی توکار از ایجاد کلاس‌های Singleton از دات نت 4 به بعد» پیشتر در این سایت مطالعه کرده‌اید.
مطالب
مرور چند تجربه کوتاه با Microsoft virtual pc

از virtual pc که یک ویندوز اکس پی بر روی آن نصب کرده‌ام برای اتصال به VPN‌ استفاده می‌کنم. (متاسفانه آخرین نگارش vmware برای این‌کار جواب نداد)
زمان اتصال به VPN کل سیستم وارد شبکه مورد نظر خواهد شد و این مورد شاید به‌دلایلی برای مثال قطع اینترنت و یا اعمال پالیسی‌های شبکه بر روی کامپیوتر کاری جالب نباشد (استفاده از VPN برای اتصال به یک شبکه دومین ویندوزی). اما با نصب ماشین مجازی و اجرای یک سیستم عامل دیگر به موازات سیستم عامل اصلی، کار اتصال به VPN از داخل ماشین مجازی صورت خواهد گرفت و تمام این اعمال هم از سیستم عامل مادر مجزا و ایزوله خواهند بود.
یک ویندوز اکس پی با اختصاص 200 مگ رم هم کار می‌کند و عملا باری را بر روی سیستم عامل مادر تحمیل نخواهد کرد. همچنین حتما از منوی action گزینه install or update virtual machine additions را انتخاب کنید تا کارآیی سیستم عامل مجازی را بهبود بخشید. حداقل فایده آن این است که اشاره‌گر ماوس را به سادگی می‌توان از ماشین مجازی خارج کرد و هر بار نیازی به فشردن دکمه‌ ALT سمت راست نخواهد بود!
اولین مشکلی که هنگام کار با یک ماشین مجازی خود نمایی می‌کند بحث انتقال فایل بین سیستم عامل مادر (ویندوزی که شما ماشین مجازی را روی آن نصب کرده‌اید) و ماشین مجازی است. عمومی‌ترین راه، ایجاد یک فایل iso از فایل‌های مورد نظر است و سپس انتخاب منوی CD و گزینه capture iso image . این روش بر روی vmware هم جواب می‌دهد (معرفی فایل iso بعنوان CD-ROM آن). خوشبختانه عمل drag & drop (از سیستم عامل مادر به ماشین مجازی) که شاید در وحله اول به ذهن نرسد اینجا بخوبی کار خواهد کرد و مشکل ساخت فایل‌های iso را برطرف می‌کند. (البته vmware کمی پیشرفته‌تر است و حتی copy و Paste را نیز پشتیبانی می‌کند. اما خوب، رایگان نیست!)
مشکل بعدی با ms virtual pc افزایش تدریجی حجم آن است. روز اول 2 گیگ، روز سوم 4 گیگ، هفته بعد می‌شود 6 گیگ! برای فشرده سازی آن می‌توان به روش زیر عمل کرد:
به مسیر زیر مراجعه کنید: (اگر پیش‌فرض‌های نصب را پذیرفته‌اید)
C:\Program Files\Microsoft Virtual PC\Virtual Machine Additions
فایل Virtual Disk Precompactor.iso را از طریق منوی CD و گزینه capture iso image باز کنید. برنامه‌ای به صورت خودکار اجرا خواهد شد که سیستم عامل مجازی را آماده فشرده سازی می‌کند. پس از پایان کار، سیستم عامل مجازی را خاموش کنید. سپس به منوی file گزینه virtual disk wizard مراجعه نمائید. در صفحه بعدی گزینه ویرایش یک ماشین مجازی موجود را انتخاب کرده و فایل ماشین مجازی مورد نظر را که پیشتر برای فشرده سازی آماده کردیم به آن معرفی کنید. در صفحه بعد گزینه compact it را انتخاب کرده و در ادامه می‌توانید مسیر جدیدی را مشخص کنید یا انتخاب کنید که فایل نهایی فشرده شده جایگزین فایل موجود شود.
با اینکار یک ماشین مجازی 6 گیگابایتی به 3 گیگ کاهش حجم یافت که قابل توجه است.
برای استفاده از اینترنت سیستم عامل مادر در ms virtual pc می‌شود از منوی edit ، گزینه setting و انتخاب networking در صفحه ظاهر شده، تنظیم اولین adapter شبکه را بر روی shared networking NAT قرار داد و همه چیز به خوبی کار خواهد کرد. (البته برای استفاده از اینترنت در vmware باید روی کانکشن اینترنت خود در سیستم عامل مادر کلیک راست کرد و سپس انتخاب گزینه advanced و فعال سازی internet connection sharing بر روی کارت شبکه مجازی نصب شده آن ضروری خواهد بود)



مطالب
breeze js به همراه ایجاد سایت آگهی قسمت سوم
در مطلب قبلی، پیش نیازهای مربوطه را نصب کردیم. در این قسمت به ساخت صفحات ورود و خروج، ثبت نام کاربران و تغییر رمز عبور خواهیم پرداخت.
در اینجا ما از Account Controller پیش فرض Asp.net Mvc استفاده میکنیم که متدهای مورد استفاده ما در آن قرار دارد و به BreezeController مزین شده است.
 [BreezeController]
    public class AccountController : ApiController
    {
      ...
    }

 اینترفیس IAuthService:
module Interfaces {
    export interface IAuthService {
        user: Models.IUserToken
        getUserInfo(accessToken);
        login(data);
        logOut();
        register(data);
        changePassword(data);
        accessToken(accessToken, data);
    }
}

سرویس AuthService 
پیاده سازی اینترفیس IAuthService را برعهده دارد. در سازنده آن، وابستگی‌های آن مقداردهی شده‌است و همچنین تنظیمات manager را انجام داده‌ایم.
متد accessToken: وظیفه ارسال توکن  را به سرور و همچنین نگهداری آن‌را در local storage، برعهده دارد.
متد getUserInfo: اطلاعات کاربر لاگین شده را  از سرور دریافت مینماید.
متد login: فرمت مورد قبول سرور به نحو زیر میباشد. در صورت موفقیت آمیز بودن، توکن را به متد accessToken  پاس میدهیم و آبجکت user را با مقادیر دریافتی پر می‌نماییم.
"grant_type=password & username=myusername & password=mypassword";
برای فراخوانی متدهای post، همانطوری که در مطلب ارسال کوئری‌های پست به آن اشاره شده‌است، عمل می‌نماییم. در ابتدا فایل breeze.ajaxpost.js را اضافه می‌کنیم سپس در فایل breeze.angular  قطعه کد زیر را در متد useNgHttp اضافه می‌کنیم.
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
breeze.ajaxpost(ajaxAdapter);
با این تنظیمات میتوانیم توسط withParameters، متدهای post را فراخوانی کنیم. 
.withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: newData
                }
متد logOut: خروج از برنامه را برای ما انجام می‌دهد و درصورت موفقیت آمیز بودن، به صفحه اصلی هدایت می‌شود.
متد register: ثبت نام کاربران را بر عهده دارد.
متد changePassword: تغییر رمز عبور کاربران را برعهده دارد.
module AdApps {
    var securityUrls = {
        site: '/',
        login: '/token',
        logout: 'logout',
        register: 'register',
        userInfo: 'getUserInfo',
        changePassword: 'changePassword',
    }
    export class AuthService implements Interfaces.IAuthService {
        private manager: breeze.EntityManager;

        constructor(
            private _breeze: typeof breeze,
            private $http: ng.IHttpProvider,
            private toaster: ngtoaster.IToasterService,
            private $location: ng.ILocationService) {

            var dataService = new _breeze.DataService({
                serviceName: "/breeze/Account",
                hasServerMetadata: false
            });
            var metadataStore = new _breeze.MetadataStore({
                namingConvention: _breeze.NamingConvention.camelCase
            });
            this.manager = new _breeze.EntityManager({
                dataService: dataService,
                metadataStore: metadataStore,
                saveOptions: new _breeze.SaveOptions({
                    allowConcurrentSaves: true, tag: [{}]
                })
            });
        }

        user: Models.IUserToken;
        accessToken(accessToken, data): string {
            if (accessToken === 'clear') {
                localStorage.removeItem('accessToken');
                delete this.$http.defaults.headers.common.Authorization;
            }
            else {
                window.localStorage.setItem("accessToken", accessToken);
                this.$http.defaults.headers.common.Authorization = 'Bearer ' + accessToken;
            }
            return accessToken;
        }
        getUserInfo(): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.userInfo);
            return this.manager.executeQuery(query).then(data => {
                return data.results[0];
            });
        }
        login(data: any): ng.IPromise<any> {
            var newData = "grant_type=password&username=" + data.userName + "&password=" + data.password;
            var query = this._breeze.EntityQuery.from(securityUrls.login)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: newData
                });
            return this.manager.executeQuery(query).then(data => {
                var self = this;
                var result = data.results[0] as any;
                self.accessToken(result.access_token, data.results[0]);
                self.user = <Models.IUserToken>{};
                self.user = <Models.IUserToken>result;
                return result;
            });
        }
        logOut(): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.logout)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                });
            return this.manager.executeQuery(query).then(data => {
                this.user = null;
                this.accessToken('clear', null);
                this.$location.path("/");
            });
        }
        register(data: Object): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.register)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: data
                });
            return this.manager.executeQuery(query).then(data => { });
        }
        changePassword(data: Object): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.changePassword)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: data
                });
            return this.manager.executeQuery(query).then(data => { });
        }
    }
}

سرویس HttpInterceptor 
: رهگیری و پیگیری کردن نتیجه درخواست‌های http را بر عهده دارد.
درrequest : توکن امنیتی را به هدر درخواست‌ها اضافه میکنیم.
در response : در صورت موفقیت درخواست http، پیغام مناسبی را نمایش میدهیم.
در responseError : در صورت عدم موفقیت درخواست http، پیغام مناسبی را نمایش میدهیم.
module AdApps {
    export class HttpInterceptor {
        private static _toaster: ngtoaster.IToasterService;
        private static _$q: ng.IQService;
        constructor(
            private $q: ng.IQService,
            private toaster: ngtoaster.IToasterService,
            private $location: ng.ILocationService) {
            HttpInterceptor._toaster = toaster;
            HttpInterceptor._$q = $q;
        }
        request(config): string {
            config.headers = config.headers || {};
            var authData = window.localStorage.getItem("accessToken");
            if (authData) {
                config.headers.Authorization = "Bearer " + authData;
            }
            return config;
        };
        response(response): ng.IPromise<any> {
            if (response.data && response.data.message && response.status === 200) {
                HttpInterceptor._toaster.success(response.data.message)
            }
            return HttpInterceptor._$q.resolve(response);
        };
        responseError(response): ng.IPromise<any> {
            var self = this;
            var data = response.data;
            var title = "خطا";
            var messages = [];
            if (data) {
                if (data.error) {
                    title = data.error;
                }
                if (data.message) {
                    messages.push(data.message);
                }
                if (data.Message) {
                    messages.push(data.Message);
                }
                if (data.ModelState) {
                    angular.forEach(data.ModelState, function (errors, key) {
                        if (key.substr(0, 1) != "$")
                        { messages.push(errors); }

                    });
                }
                if (data.exceptionMessage) {
                    messages.push(data.exceptionMessage);
                }
                if (data.ExceptionMessage) {
                    messages.push(data.ExceptionMessage);
                }
                if (data.error_description) {
                    messages.push(data.error_description);
                }
                if (messages.length > 0) {
                    HttpInterceptor._toaster.error(title, messages.join("<br/>"));
                }
                if (response.status === "401") {
                    self.$location.path("/ورود");
                }
            }
            return HttpInterceptor._$q.reject(response);
        }
    }
}

معرفی کردن مسیرهای ورود، ثبت نام و تغییر رمز عبور به انگولار
module AdApps {
    class SecurityCtrl {
        constructor(private $scope: Interfaces.IAuthScope, private authService: AuthService) {
            $scope.authService = authService;
            if (window.localStorage.getItem("accessToken") != null) {
                authService.getUserInfo().then(function (data) {
                    $scope.authService.user = data;
                });
            }
            $scope.logOut = function () {
                return authService.logOut().then(function () { });
            }
        }
    }
    define(["angularAmd", "angular", "factory/AuthService", "factory/httpInterceptor"], (angularAmd, ng) => {
        angularAmd = angularAmd.__proto__;
        var app = ng.module("AngularTypeScript", ['ngRoute', 'breeze.angular', 'toaster']);
        var viewPath = "app/views/";
        var controllerPath = "app/controller/";
        app.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
            $httpProvider.interceptors.push("HttpInterceptor");
            $routeProvider
                .when("/", angularAmd.route({
                    templateUrl: viewPath + "home.html",
                    controllerUrl: controllerPath + "home.js"
                }))
                .when("/login", angularAmd.route({
                    templateUrl: viewPath + "login.html",
                    controllerUrl: controllerPath + "login.js"
                }))
                .when("/register", angularAmd.route({
                    templateUrl: viewPath + "register.html",
                    controllerUrl: controllerPath + "register.js"
                }))
                .when("/changePassword", angularAmd.route({
                    templateUrl: viewPath + "change-password.html",
                    controllerUrl: controllerPath + "changePassword.js"
                }))
                .otherwise({ redirectTo: '/' });
        }
        ]);
        app.service('AuthService', ['breeze', '$http', 'toaster', '$location', AuthService]);
        app.service("HttpInterceptor", ["$q", "toaster", "$location", HttpInterceptor]);
        app.controller('SecurityCtrl', ['$scope', 'AuthService', SecurityCtrl]);
        return angularAmd.bootstrap(app);
    });
}

ایجاد کنترلر .login.ts و ارسال سرویس‌های لازم به کلاس LoginCtrl
در صورت صحیح بودن نام کاربری و رمز عبور به صفحه اصلی هدایت خواهد شد.
module AdApps {
    define(['app'], function (app) {
        app.controller('LoginCtrl', ["$scope", "AuthService", "$location", LoginCtrl]);
    });
   export  class LoginCtrl {
        constructor($scope: Interfaces.ILoginScope, authService: AuthService, $location: ng.ILocationService) {
           $scope.submit = function () {
               authService.login(angular.copy($scope.form))
                    .then(function (data) {
                        this.$location.path("/");
                    })
            };
        }
    }
}

ایجاد login.html
<div  ng-controller="LoginCtrl">
    <div>
        <i></i>
        <span>ورود</span>
        <div>
            <div>
            </div>
        </div>
    </div>
    <div>
        <div>
            <div>
                <form name="Form" id="form1">
                    <fieldset>
                        <div>
                            <div>
                                <input
                                       name="username"
                                       ng-model="form.userName"
                                       placeholder="نام کاربری"
                                       required>
                                <span>
                                    <i></i>
                                </span>
                            </div>
                        </div>
                        <div>
                            <div>
                                <input name="password"
                                       type="password"
                                       ng-model="form.password"
                                       placeholder="{{'Password'}}"
                                       validator="required">
                                <span>
                                    <i></i>
                                </span>
                            </div>
                        </div>

                    </fieldset>
                    <div>
                        <button type="submit" ng-click="submit()">ورود</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
فایل‌های مربوط به ثبت نام و تغییر رمز عبور، مشابه لاگین می‌باشد و از ذکر آن خودداری می‌نماییم و فایل‌های مربوطه در پروژه قرار دارند.
با تغییرات بالا، فایل main.ts دارای محتویات زیر میباشد:
requirejs.config({
    paths: {
        "app": "app",
        "angularAmd": "/Scripts/angularAmd",
        "angular": "/Scripts/angular",
        "breezeAjaxpost": "/Scripts/breeze/breeze.ajaxpost",
        "breeze": "/Scripts/breeze/breeze.debug",
        "breezeAngular": "/Scripts/breeze/breeze.angular",
        "bootstrap": "/Scripts/bootstrap",
        "angularRoute": "/Scripts/angular-route",
        "jquery": "/Scripts/jquery-2.2.2",
        "entityManagerService": "factory/entityManagerService",
        "toaster": "/Scripts/toaster",
    },
    waitSeconds: 0,
    shim: {
        "angular": { exports: "angular" },
        "angularRoute": { deps: ["angular"] },
        "bootstrap": { deps: ["jquery"] },
        "breeze": { deps: ["jquery"] },
        "breezeAngular": {
            deps: ["angular", "breeze"]
        },
        "toaster": { deps: ["angular"] },
        "app": {
            deps: ["bootstrap", "angularRoute", "toaster", "breezeAngular", "breezeAjaxpost"]
        }
    }
});
require(["app"]);
فایل پروژه :AngularTypeScript.zip
در قسمت‌های بعدی به ثبت و نمایش آگهی در سایت خواهیم پرداخت.  
مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت اول - نصب و راه اندازی
VS Code یک محیط توسعه‌ی یکپارچه است که توسط مایکروسافت توسعه پیدا می‌کند و دارای مزایای ذیل است:
 - سبک وزن است
 - بسیار سریع است
 - به صورت سورس باز توسعه پیدا می‌کند
 - رایگان است
 - چندسکویی است
 - انواع و اقسام زبان‌های برنامه نویسی را پشتیبانی می‌کند
 - پشتیبانی بسیار مناسبی را از طرف جامعه‌ی برنامه نویسان به همراه دارد
 - به همراه تعداد زیادی افزونه است

هدف اصلی از توسعه‌ی آن نیز ارائه‌ی تجربه‌ی کاربری یکسانی در سکوهای کاری مختلف و زبان‌های متفاوت برنامه نویسی است. در اینجا مهم نیست که از ویندوز، مک یا لینوکس استفاده می‌کنید. نحوه‌ی کار کردن با آن در این سکوهای کاری تفاوتی نداشته و یکسان است. همچنین برای آن تفاوتی نمی‌کند که با PHP کار می‌کنید یا ASP.NET. تمام گروه‌های مختلف برنامه نویسان دسترسی به یک IDE بسیار سریع و سبک وزن را خواهند داشت.
برخلاف نگارش کامل ویژوال استودیو که این روزها حجم دریافت آن به بالای 20 گیگابایت رسیده‌است، VS Code با هدف سبک وزن بودن و سادگی دریافت و نصب، طراحی و توسعه پیدا می‌کند. این مورد، مزیت دریافت به روز رسانی‌های منظم را بدون نگرانی از دریافت حجم‌های بالا، برای بسیاری از علاقمندان مسیر می‌کند.
همچنین برای کار با نگارش‌های جدیدتر ASP.NET Core، دیگر نیازی به دریافت آخرین به روز رسانی‌های چندگیگابایتی ویژوال استودیوی کامل نبوده و می‌توان کاملا مستقل از آن، از آخرین نگارش NET Core. و ASP.NET Core به سادگی در VSCode استفاده کرد.


نصب VS Code بر روی ویندوز

آخرین نگارش این محصول را از آدرس https://code.visualstudio.com می‌توانید دریافت کنید. نصب آن نیز بسیار ساده‌است؛ فقط گزینه‌ی Add to PATH را نیز در حین نصب حتما انتخاب نمائید (هرچند به صورت پیش فرض نیز انتخاب شده‌است). به این ترتیب امکان استفاده‌ی از آن در کنسول‌های متفاوتی مسیر خواهد شد.
در ادامه فرض کنید که مسیر D:\vs-code-examples\sample01 حاوی اولین برنامه‌ی ما خواهد بود. برای اینکه در اینجا بتوانیم، تجربه‌ی کاربری یکسانی را مشاهده کنیم، از طریق خط فرمان به این پوشه وارد شده و دستور ذیل را صادر می‌کنیم:
 D:\vs-code-examples\sample01>code .
به این ترتیب کل پوشه‌ی sample01 در VS Code باز خواهد شد.


نصب VS Code بر روی Mac

نصب VS Code بر روی مک یا لینوکس نیز به همین ترتیب است و زمانیکه به آدرس فوق مراجعه می‌کنید، به صورت خودکار نوع سیستم عامل را تشخیص داده و بسته‌ی متناسبی را به شما پیشنهاد می‌کند. پس از دریافت بسته‌ی آن برای مک، یک application را دریافت خواهید کرد که آن‌را می‌توان به مجموعه‌ی Applications سیستم اضافه کرد. تنها تفاوت تجربه‌ی نصب آن با ویندوز، انتخاب گزینه‌ی Add to PATH آن است و به صورت پیش فرض نمی‌توان آن‌را از طریق ترمینال در هر مکانی اجرا کرد. برای این منظور، پس از اجرای اولیه‌ی VS Code، دکمه‌های Ctrl/Command+Shift+P را در VS Code فشرده و سپس path را جستجو کنید (در دستور یاد شده، Ctrl برای ویندوز و لینوکس است و Command برای Mac):


در اینجا گزینه‌ی install 'code' command path را انتخاب کنید تا بتوان VS Code را از طریق ترمینال نیز به سادگی اجرا کرد. به این ترتیب امکان اجرای دستور . code که بر روی ویندوز نیز ذکر شد، در اینجا نیز میسر خواهد بود.


نصب VS Code بر روی لینوکس

در اینجا نیز با مراجعه‌ی به آدرس https://code.visualstudio.com، بسته‌ی متناسب با لینوکس، جهت دریافت پیشنهاد خواهد شد؛ برای مثال بسته‌های deb. برای توزیع‌هایی مانند اوبونتو و یا rpm. برای ردهت. به علاوه اگر بر روی علامت ^ کنار بسته‌های دانلود کلیک کنید، یک بسته‌ی tar.gz. نیز قابل دریافت خواهد بود. تجربه‌ی نصب آن نیز همانند نمونه‌ی ویندوز است و Add to PATH آن به صورت خودکار انجام خواهد شد.


بررسی ابتدایی محیط VS Code

VS Code بر اساس فایل‌های قرار گرفته‌ی در یک پوشه و زیر پوشه‌های آن کار می‌کند. به همین جهت پس از صدور دستور . code، آن پوشه را در IDE خود نمایش خواهد داد. در اینجا برخلاف نگارش کامل ویژوال استودیو، روش کار، مبتنی بر یک فایل پروژه نیست و اگر خارج از VS Code نیز فایلی را به پوشه‌ی باز شده اضافه کنید، بلافاصله تشخیص داده شده و در اینجا لیست می‌شود. هرچند یک چنین تجربه‌ی کاربری با پروژه‌های ASP.NET Core نیز در نگارش‌های جدیدتر ویژوال استودیوی کامل، سعی شده‌است شبیه سازی شود؛ برخلاف سایر پروژه‌های ویژوال استودیو که اگر فایلی در فایل پروژه‌ی آن مدخلی نداشته باشد، به صورت پیش فرض نمایش داده نشده و درنظر گرفته نمی‌شود.

در ادامه برای نمونه از طریق منوی File->New File، یک فایل جدید را اضافه می‌کنیم. هرچند می‌توان اشاره‌گر ماوس را بر روی نام پوشه نیز برده و از دکمه‌های نوار ابزار آن نیز برای ایجاد یک فایل و یا پوشه‌ی جدید نیز استفاده کرد:


در اینجا فرمت ابتدایی فایل جدید را plain text تشخیص می‌دهد:


برای تغییر این حالت یا می‌توان فایل را ذخیره کرد و پسوند مناسبی را برای آن انتخاب نمود و یا در همان status bar پایین صفحه، بر روی plain text کلیک کنید تا منوی انتخاب زبان ظاهر شود:


به این ترتیب پیش از ذخیره‌ی فایل با پسوندی مناسب نیز می‌توان زبان مدنظر را تنظیم کرد. پس از آن، intellisense و syntax highlighting متناسب با آن زبان در دسترس خواهند بود.


بررسی تنظیمات VS Code

از طریق منوی File->Preferences->Settings می‌توان به تنظیمات VS Code دسترسی یافت.


در اینجا در سمت چپ، لیست تنظیمات مهیا و پیش فرض این محیط قرار دارند و در سمت راست می‌توان این پیش فرض‌ها را (پس از بررسی و جستجوی آن‌ها در پنل سمت چپ) بازنویسی و سفارشی سازی کرد.
تنظیمات انجام شده‌ی در اینجا را می‌توان به پوشه‌ی جاری نیز محدود کرد. برای این منظور بر روی لینک work space settings در کنار لینک user settings در تصویر فوق کلیک کنید. در این حالت یک فایل json را در پوشه‌ی vscode. نمای جاری VSCode، ایجاد خواهد کرد (sample01\.vscode\settings.json) که می‌تواند در برگیرنده‌ی تنظیمات سفارشی محدود و مختص به این پروژه و یا نما باشد.

یک نکته: تمام گزینه‌های منوی VS Code را و حتی مواردی را که در منوها لیست نشده‌اند، می‌توانید در Command Pallet آن با فشردن دکمه‌های Ctrl/Command+Shift+P نیز مشاهده کنید و به علاوه جستجوی آن نیز بسیار سریع‌تر است از دسترسی و کار مستقیم با منوها.


همچنین در اینجا اگر قصد یافتن سریع فایلی را داشته باشید، می‌توانید دکمه‌های Ctrl/Command+P را فشرده و سپس نام فایل را جستجو کرد:


این دو دستور، جزو دستورات پایه‌ای این IDE هستند و مدام از آن‌ها استفاده می‌شود.


نصب افزونه‌ی #C

اولین افزونه‌ای را که جهت کار با ASP.NET Core نیاز خواهیم داشت، افزونه‌ی #C است. برای این منظور در نوار ابزار عمودی سمت چپ صفحه، گزینه‌ی Extensions را انتخاب کنید:


در اینجا افزونه‌ی #C مایکروسافت را جستجو کرده و نصب کنید. نصب آن نیز بسیار ساده است. با حرکت اشاره‌گر ماوس بر روی آن، دکمه‌ی install ظاهر می‌شود یا حتی اگر آن‌را در لیست انتخاب کنیم، در سمت راست صفحه علاوه بر مشاهده‌ی جزئیات آن، دکمه‌های نصب و عزل نیز ظاهر خواهند شد.
تجربه‌ی کاربری محیط نصب افزونه‌های آن نیز نسبت به نگارش کامل ویژوال استودیو، بسیار بهتر است. برای نمونه اگر به تصویر فوق دقت کنید، در همینجا می‌توان جزئیات کامل افزونه، نویسنده یا نویسندگان آن و یا لیست تغییرات و وابستگی‌های آن‌را نیز بدون خروج از VSCode مشاهده و بررسی کرد. همچنین در دفعات بعدی اجرای VSCode، کار بررسی و نصب به روز رسانی‌های این افزونه‌ها نیز خودکار بوده و نیازی به بررسی دستی آن‌ها نیست.

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


در قسمت بعد، اولین پروژه‌ی ASP.NET Core خود را در VS Code ایجاد خواهیم کرد.
مطالب
آموزش زبان Rust - قسمت 8 - Rust-Based CS Masterclass
مدیریت حافظه، نقش مهمی را در برنامه نویسی ایفا می‌کند و بر عملکرد و کارآیی یک برنامه تاثیر می‌گذارد. این مقاله، مروری را بر سه نوع حافظه‌ی اصلی ارائه می‌کند:  static memory, stack memory, heap . درک تفاوت بین این انواع حافظه‌ها می‌تواند به شما در بهینه سازی کد و جلوگیری از مشکلات احتمالی، کمک کند.


Static Memory

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

مواردی که در حافظه استاتیک قرار میگیرند :
  • Program Binary
  • Static variables
  • String Literals (in Rust)

Size :
  Fixed ( محاسبه در زمان کامپایل )
Lifetime : برابر با طول عمر برنامه
پاکسازی : به صورت خودکار ؛ زمانی که برنامه متوقف میشود .


  Stack Memory

حافظه‌ی پشته، مسئول نگهداری آرگومان‌های تابع و متغیرهای محلی است. پشته، شامل stack frames است که برای هر فراخوانی تابع در زنجیره‌ای از فراخوانی‌های تابع، ایجاد می‌شوند (به عنوان مثال، A B را فرا می‌خواند، B C را فرا می‌خواند). حافظه‌ی پشته به اندازه‌ی مشخصی در زمان کامپایل نیاز دارد؛ به این معنا که آرگومان‌ها و متغیرهای درون  stack frames باید اندازه‌های از پیش تعیین شده‌ای داشته باشند. اندازه‌ی پشته، پویا است؛ اما دارای حد بالایی ثابتی است که در هنگام راه اندازی برنامه تعریف شده‌است. حافظه‌ی پشته، دارای طول عمری برابر با طول عمر عملکرد است و هنگامیکه عملکرد، نتیجه‌ای را بر می‌گرداند، پاکسازی آن خودکار است.  

بیایید نگاهی به یک مثال ساده در Rust بیندازیم تا حافظه‌ی پشته را بهتر درک کنیم:
fn add(x: i32, y: i32) -> i32 {
    let sum = x + y;
    sum
}

fn main() {
    let a = 5;
    let b = 3;
    let result = add(a, b);
    println!("The sum is: {}", result);
}
در این برنامه‌ی Rust، دو عملکرد add و main را داریم. هنگامیکه برنامه شروع به اجرا می‌کند، یک stack frames برای تابع اصلی در حافظه‌ی پشته ایجاد می‌شود. این  stack frames شامل متغیرهای محلی a، b و فراخوانی تابع برای add(a, b) است.
هنگامیکه تابع add فراخوانی می‌شود، یک stack frames دیگر در بالای stack frames main موجود ایجاد می‌شود. این stack frames جدید حاوی متغیرهای محلی x، y و sum است. مقادیر a و b به عنوان آرگومان به تابع add ارسال می‌شوند و به ترتیب در x و y ذخیره می‌شوند. پس از محاسبه‌ی مجموع، تابع add، مقداری را بر می‌گرداند و  stack frames آن به طور خودکار از حافظه‌ی پشته حذف می‌شود.
سپس تابع main، مقدار برگشتی را از تابع add دریافت می‌کند و به نتیجه‌ی متغیر اختصاص می‌یابد. از ماکروی println! برای چاپ نتیجه استفاده می‌شود. پس از اتمام اجرای برنامه و بازگشت تابع اصلی، stack frames آن نیز از حافظه‌ی پشته حذف می‌شود و حافظه به‌طور خودکار پاک می‌شود.
در این مثال، می‌توانید ببینید که چگونه از stack frames برای ذخیره‌ی آرگومان‌های تابع و متغیرهای محلی در Rust استفاده می‌شود. اندازه‌ی این متغیرها در زمان کامپایل مشخص می‌شود و طول عمر حافظه‌ی پشته، برابر با طول عمر تابع است. هنگامیکه تابع برمی‌گردد، فرآیند پاکسازی آن خودکار است و قاب پشته‌ی مربوطه را حذف می‌کند.


Heap Memory

حافظه‌ی Heap، مقادیری را ذخیره می‌کند که باید فراتر از طول عمر یک تابع مانند مقادیر بزرگ و مقادیر قابل دسترسی توسط رشته‌های متعدد، زنده بمانند. از آنجائیکه هر رشته دارای پشته‌ی مخصوص به خود است، همه‌ی آنها یک پشته‌ی مشترک دارند. حافظه‌ی Heap می‌تواند مقادیری با اندازه‌ی ناشناخته را در زمان کامپایل، در خود جای دهد؛ مانند رشته‌های ورودی کاربر. اندازه‌ی پشته نیز پویا است؛ با حد بالایی ثابت که در زمان راه اندازی برنامه تعیین می‌شود. حافظه‌ی Heap طول عمری دارد که توسط برنامه نویس تعیین می‌شود و برنامه نویس تصمیم می‌گیرد که چه زمانی باید حافظه تخصیص داده شود. پاکسازی حافظه‌ی هیپ به صورت دستی است و نیاز به مداخله‌ی برنامه نویس دارد.
در این مثال ساده، روش استفاده از حافظه‌ی پشته نشان داده می‌شود:
use std::rc::Rc;

#[derive(Debug)]
struct LargeData {
    data: Vec<i32>,
}

impl LargeData {
    fn new(size: usize) -> LargeData {
        LargeData {
            data: vec![0; size],
        }
    }
}

fn main() {
    let large_data = Rc::new(LargeData::new(1_000_000));
    let shared_data1 = Rc::clone(&large_data);
    let shared_data2 = Rc::clone(&large_data);

    println!("{:?}", shared_data1);
    println!("{:?}", shared_data2);
}
در این برنامه‌ی Rust، یک ساختار LargeData را تعریف می‌کنیم که حاوی <Vec<i32 است. این روش جدید، یک شیء LargeData را به اندازه‌ی مشخصی مقداردهی اولیه می‌کند. در تابع main، یک شیء LargeData را با اندازه (1,000,000 عنصر) ایجاد می‌کنیم و با استفاده از Rc::new روی پشته ذخیره می‌کنیم. Rc یک اشاره‌گر شمارش مرجع است که به چندین متغیر اجازه می‌دهد تا مالکیت داده‌های تخصیص داده شده را به اشتراک بگذارند (در ادامه‌ی دوره توضیح داده خواهد شد).  
سپس دو متغیر دیگر را به نام‌های shared_data1 و shared_data2 ایجاد می‌کنیم که با استفاده از Rc::clone، یک شیء LargeData تخصیص‌یافته‌ی مشابه را به اشتراک می‌گذارند. این نشان می‌دهد که چگونه حافظه‌ی پشته را می‌توان در بین متغیرهای متعددی به اشتراک گذاشت؛ حتی فراتر از طول عمر تابع اصلی که داده را ایجاد کرده است.
در این مثال، پاکسازی حافظه‌ی پشته به طور خودکار توسط مکانیزم شمارش مرجع Rust مدیریت می‌شود (در ادامه‌ی دوره توضیح داده خواهد شد). هنگامیکه تعداد مرجع نشانگر Rc به صفر می‌رسد (یعنی وقتی همه‌ی متغیرهایی که داده‌ها را به اشتراک می‌گذارند از محدوده خارج می‌شوند)، حافظه‌ی تخصیص داده شده، روی پشته تخصیص داده می‌شود.
این مثال نشان می‌دهد که چگونه می‌توان از حافظه‌ی پشته برای ذخیره‌ی ساختارهای داده یا مقادیر بزرگی استفاده کرد که باید بیشتر از طول عمر یک تابع باشند و چگونه می‌توان حافظه‌ی پشته را بین چندین متغیر به اشتراک گذاشت.
نظرات مطالب
ایجاد helper برای Nivo Slider در Asp.net Mvc
این dll جزو فایل‌های استاندارد یک پروژه ASP.NET MVC4 است. در اینجا پیوست نشده، چون نیازی نیست هربار تمام این فایل‌های تکراری را دریافت کنید.
فایل‌های آن در دو پوشه زیر بر روی سیستم شما موجود هستند: (اگر ASP.NET MVC4 را نصب کرده باشید)
C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Packages
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Packages 
مطالب
آشنایی با Window Function ها در SQL Server بخش اول
Window Function‌ها برای اولین بار در نسخه SQL Server 2005 ارائه گردیدند، و در ورژن‌های جدیدتر SQL Server، به تعداد این فانکشنها افزوده شده است.

تعریف Window Function :
        معمولا از این نوع فانکشنها روی مجموعه ای از ROW‌های یک جدول، در جهت اعمال عملیاتهای محاسباتی ،ارزیابی داده ها، رتبه بندی و غیرو... استفاده می‌گردد، به بیان ساده‌تر بوسیله Window Function‌ها می‌توان، ROW‌های یک جدول را گروه بندی نمود. و روی گروه‌ها از توابع جمعی (Aggregate Functions ) استفاده کرد. این نوع فانکشنها از قابلیت و انعطاف پذیری زیادی برخوردار می‌باشند، و بوسیله آنها می‌توان نتایج (خروجی) بسیار مفیدی از Query ها، بدست آورد، معمولا از این نوع فانکشنها در            Data Mining (داده کاوی) و گزارشگیری‌ها استفاده می‌گردد. و آگاهی و روش استفاده از Window Function‌ها برای برنامه نویسان و DBA ها، می‌تواند بسیار مفید باشد.
مفهوم Window Function مطابق استاندارد ISO و ANSI می‌باشد، و دیتابیس هایی همچون Oracle،DB2،Sybase از آن پشتیبانی می‌نمایند.برای اطلاعات بیشتر می‌توانید به سایت‌های زیر مراجعه کنید:
کلمه "Window" در  Window Function، به مجموعه ROW هایی اشاره می‌کند، که محاسبات و ارزیابی و غیرو... روی آنها اعمال می‌گردد. 
Window Function‌ها برای ارائه قابلیت‌های خود، از Over Clause استفاده می‌کنند. اگر مقاله آشنایی با Row_Number،Rank،Dense_Rank،NTILE را مطالعه کرده باشید، می‌توان هریک از آنها را یک Window Function دانست.
برای شروع، به بررسی Over Clause می‌پردازیم، و Syntax آن به شرح ذیل می‌باشد:
OVER ( 
       [ <PARTITION BY clause> ]
       [ <ORDER BY clause> ] 
       [ <ROW or RANGE clause> ]
      )

<PARTITION BY clause> ::=
PARTITION BY value_expression , ... [ n ]

<ORDER BY clause> ::=
ORDER BY order_by_expression
    [ COLLATE collation_name ] 
    [ ASC | DESC ] 
    [ ,...n ]

<ROW or RANGE clause> ::=
{ ROWS | RANGE } <window frame extent>

<window frame extent> ::= 
{   <window frame preceding>
  | <window frame between>
}

<window frame between> ::= 
  BETWEEN <window frame bound> AND <window frame bound>

<window frame bound> ::= 
{   <window frame preceding>
  | <window frame following>
}

<window frame preceding> ::= 
{
    UNBOUNDED PRECEDING
  | <unsigned_value_specification> PRECEDING
  | CURRENT ROW
}

<window frame following> ::= 
{
    UNBOUNDED FOLLOWING
  | <unsigned_value_specification> FOLLOWING
  | CURRENT ROW
}

<unsigned value specification> ::= 
{  <unsigned integer literal> }
OVER دارای سه آرگومان اختیاری است که هر کدام را به تفصیل بررسی می‌کنیم:
1- PARTITION BY clause : بوسیله این پارامتر می‌توانیم Row‌های یک جدول را گروه بندی نماییم. این پارامتر یک  value_expression می پذیرد. یک Value_expression می‌تواند نام یک ستون ، یک Scalar Subquery ، Scalar Function و غیرو باشد.
2- ORDER BY clause : از نامش مشخص است و برای Sort استفاده می‌شود، و ویژگی‌های Order By در آن اعمال می‌گردد. به جز Offset.
3- ROW or RANGE clause :این پارامتر بیشتر برای محدود نمودن Row در یک Partition (گروه) مورد استفاده قرار می‌گیرد، به عنوان مثال نقطه شروع و پایان را می‌توان بوسیله پارامتر فوق تعیین نمود.
Row و Range نسبت به هم یک تفاوت عمده دارند،و آن این است که، اگر از ROW Clause استفاده نمایید، ارتباط ROW‌های قبلی یا بعدی، نسبت به Row جاری،بصورت فیزیکی (physical association ) سنجیده می‌شود، بطوریکه با استفاده از Range Clause ارتباط سطرهای قبلی و بعدی، نسبت به سطر جاری بصورت منطقی (logical association ) در نظر گرفته می‌شود. ممکن است درک این مطلب کمی سخت باشد، در ادامه با مثالهایی که بررسی می‌نماییم، براحتی تفاوت این دو را متوجه می‌شوید.
Row یا Range در قالب‌های متفاوتی مقدار می‌پذیرند، که هر کدام را بررسی می‌کنیم:
UNBOUNDED PRECEDING : بیانگر اولین سطر Partition می‌باشد. UNBOUNDED PRECEDING  فقط نقطه شروع را مشخص می‌نماید.
UNBOUNDED FOLLOWING : بیانگر آخرین سطر Partition می‌باشد. UNBOUNDED FOLLOWING فقط نقطه پایانی را مشخص می‌نماید.
CURRENT ROW : اولین سطر جاری یا آخرین سطر جاری را مشخص می‌نماید.
n PRECEDING یا unsigned value specification> PRECEDING> : تعداد سطر‌های قبل از سطر جاری را تعیین می‌کند، n یا <unsigned value specification>تعداد سطر‌های قبل از سطر جاری را تعیین می‌نماید. از n PRECEDING نمی توان برای Range استفاده نمود.  
n FOLLOWING یا unsigned value specification> FOLLOWING> : تعداد سطرهای بعد از سطر جاری را تعیین می‌کند، n یا<unsigned value specification> تعداد سطر  های بعد از سطر جاری را تعیین می‌نماید. از n FOLLOWING نمی توان برای Range استفاده نمود.
<BETWEEN <window frame bound > AND <window frame bound  : از چارچوب فوق برای Range و Row می‌توان استفاده نمود، و نقطه آغازین و نقطه پایانی توسط قالب فوق تعیین می‌گردد. نکته قابل توجه آن است که نقطه پایانی نمی‌تواند، کوچکتر از نقطه آغازین گردد.
در ادامه برای درک هرچه بیشتر تعاریف بیان شده، چندین مثال می‌زنیم و هر کدام را بررسی می‌نماییم:
در ابتدا Script زیر را اجرا نمایید، که شامل جدولی به نام Revenue (سود،درآمد) و درج چند درکورد در آن:
CREATE TABLE REVENUE
(
[DepartmentID] int,
[Revenue] int,
[Year] int
);
 
insert into REVENUE
values (1,10030,1998),(2,20000,1998),(3,40000,1998),
 (1,20000,1999),(2,60000,1999),(3,50000,1999),
 (1,40000,2000),(2,40000,2000),(3,60000,2000),
 (1,30000,2001),(2,30000,2001),(3,70000,2001)
 
مثال اول : می‌خواهیم براساس فیلد DepartmentID جدول Revenue را Partition بندی نماییم و از توابع جمعی AVG و SUM روی فیلد درآمد(Revenue) استفاده کنیم.
ابتدا Script زیر را اجرا می‌کنیم:
 select *,
 avg(Revenue) OVER (PARTITION by DepartmentID) as AverageRevenue,
 sum(Revenue) OVER (PARTITION by DepartmentID) as TotalRevenue
from REVENUE
order by departmentID, year;
خروجی بصورت زیر خواهد بود:

        مطابق شکل، جدول براساس  فیلد DepartmentID به سه Partition تقسیم شده است، و عملیات میانگین و جمع روی فیلد Revenue انجام شده است و عملیات Sort روی هرگروه بطور مستقل انجام گرفته است. چنین کاری را نمی‌توانستیم بوسیله Group By انجام دهیم.
مثال دوم : نحوه استفاده از ROWS PRECEDING،در این مثال قصد داریم عملیات جمع را روی فیلدRevenue انجام دهیم. بطوریکه جمع هر مقدار برابر است با سه مقدار قبلی + مقدار جاری:
 لطفا رکورد‌های زیر را به جدول فوق درج نمایید:
 insert into REVENUE
 values(1,90000,2002),(2,20000,2002),(3,80000,2002),
 (1,10300,2003),(2,1000,2003), (3,90000,2003),
 (1,10000,2004),(2,10000,2004),(3,10000,2004),
 (1,20000,2005),(2,20000,2005),(3,20000,2005),
 (1,40000,2006),(2,30000,2006),(3,30000,2006),
 (1,70000,2007),(2,40000,2007),(3,40000,2007),
 (1,50000,2008),(2,50000,2008),(3,50000,2008),
 (1,20000,2009),(2,60000,2009),(3,60000,2009),
 (1,30000,2010),(2,70000,2010),(3,70000,2010),
 (1,80000,2011),(2,80000,2011),(3,80000,2011),
 (1,10000,2012),(2,90000,2012),(3,90000,2012)
سپس Script زیر را اجرا می‌نماییم:
select Year, DepartmentID, Revenue,
sum(Revenue) OVER (PARTITION by DepartmentID ORDER BY [YEAR]
             ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) as Prev3
From REVENUE order by departmentID, year;
خروجی :

        در Script بالا، جدول را براساس فیلد DepartmentID گروه بندی می‌کنیم، که سه گروه ایجاد می‌شود، هر گروه را بطور مستقل، روی فیلد Year بصورت صعودی مرتب می‌نماییم. حال برای آنکه بتوانیم سیاست جمع، روی فیلد Revenue، را پیاده سازی نماییم ، قطعه کد زیر را در Script بالا اضافه کردیم.
ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) as Prev3
     برای شرح چگونگی استفاده از PRECEDING،فقط به شرح گروه اول بسنده می‌کنیم. مقدار جمع فیلد Revenue سطر اول، که قبل از آن سطری وجود ندارد، برابر است با  مقدار خود، یعنی 10030، مقدار جمع فیلد Revenue سطر دوم برابر است با حاصل جمع مقدار فیلدRevenue سطر اول و دوم ، یعنی 30030 . این روند تا سطر چهار ادامه دارد، اما برای بدست آوردن مقدار جمع فیلدRevenue سطر پنجم، مقدار جمع فیلد Revenue سطر دوم،سوم،چهارم و پنجم در نظر گرفته می‌شود، و مقدار فیلدRevenue سطر اول در حاصل جمع در نظر گرفته نمی‌شود،بنابراین مقدار جمع فیلد Revenue سطر پنجم برابر است با 180000. در صورت مسئله گفته بودیم، مقدار جمع فیلد Revenue هر سطر جاری برابر است با حاصل جمع مقدارسطر جاری و مقادیر سه سطر ماقبل خود.

مثال سوم: نحوه استفاده از  ROWS FOLLOWING، این مثال عکس مثال دوم است، یعنی حاصل جمع مقدار فیلد Revenue هر سطر برابر است با حاصل جمع سطر جاری با سه سطر بعد از خود. بنابراین Script زیر را اجرا نمایید:
select Year, DepartmentID, Revenue,
 sum(Revenue) OVER (PARTITION by DepartmentID ORDER BY [YEAR]
              ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING) as Next3
From REVENUE order by departmentID, year;
خروجی :

مطابق شکل مقدار جمع فیلد اول برابراست با حاصل جمع مقدار سطر جاری و سه سطر بعد از آن.
نکته ای که در مثالهای دوم و سوم،می بایست به آن توجه نمود، این است که در زمان استفاده از Row یا Range ، استفاده از Order by در Partition الزامی است، در غیر این صورت با خطا مواجه می‌شوید.


نحوه استفاده از UNBOUNDED PRECEDING ، این امکان در T-SQL Server 2012 افزوده شده است. 
مثال چهار: در این مثال می‌خواهیم کمترین سود بدست آمده در چند سال را بدست آوریم:
ابتدا Script زیر را اجرا نمایید:
select Year, DepartmentID, Revenue,
       min(Revenue) OVER (PARTITION by DepartmentID ORDER BY [YEAR]
                    ROWS UNBOUNDED PRECEDING) as MinRevenueToDate
From REVENUE order by departmentID, year;
خروجی:

طبق تعریف UNBOUNDED PRECEDING اولین سطر هر Partition را مشخص می‌نماید، و چون از PRECEDING استفاده کرده ایم، بنابراین مقایسه همیشه بین سطر جاری و  سطر‌های قبل از آن انجام می‌پذیرد. بنابراین خواهیم داشت، کمترین مقدار فیلد Revenue در سطر اول، برابر با مقدار خود می‌باشد، چون هیچ سطری ماقبل از آن وجود ندارد. در سطر دوم مقایسه کمترین مقدار، بین 20000 و 10030 انجام می‌گیرد، که برابر است با 10030، در سطر سوم، مقایسه بین مقادیر سطر اول،دوم و سطر سوم صورت می‌گیرد، یعنی کمترین مقدار بین 40000،20000 و 10030، بنابراین کمترین مقدار سطر سوم برابر است با 10030. 
به بیان ساده‌تر برای بدست آوردن کمترین مقدار هر سطر، مقدار سطر جاری با مقادیر همه سطرهای ماقبل خود مقایسه می‌گردد.
برای بدست آوردن کمترین مقدار در سطر ششم، مقایسه بین مقادیر سطر‌های اول،دوم،سوم،چهارم،پنجم و ششم صورت می‌گیرد که عدد 10000 بدست می‌آید و الی آخر...
نکنه: اگر در Over Clause شرط Order by را اعمال نماییم، اما از Row یا Range استفاده نکنیم، SQL Server بصورت پیش فرض از قالب زیر استفاده می‌نماید:
RANGE UNBOUNDED PRECEDING AND CURRENT ROW 
برای روشن‌تر شدن مطلب فوق مثالی می‌زنیم:
ابتدا Script زیر را اجرا نمایید، که شامل ایجاد یک جدول و درج چند رکورد در آن می‌باشد:
CREATE TABLE Employees (  
    EmployeeId INT IDENTITY PRIMARY KEY,  
    Name VARCHAR(50),  
    HireDate DATE NOT NULL,  
    Salary INT NOT NULL  
)  
GO  
INSERT INTO Employees (Name, HireDate, Salary)  
VALUES   
    ('Alice', '2011-01-01', 20000),  
    ('Brent', '2011-01-15', 19000),  
    ('Carlos', '2011-02-01', 22000),  
    ('Donna', '2011-03-01', 25000),  
    ('Evan', '2011-04-01', 18500)  
GO  
سپس Script زیر را اجرا نمایید:
SELECT  
    Name,   
    Salary,   
    AVG(Salary) OVER(ORDER BY HireDate) AS avgSalary  
FROM Employees  
GO 
خروجی :

حال اگر Script زیر را نیز اجرا نمایید، خروجی آن مطابق شکل بالا خواهد بود:
SELECT  
    Name,   
    Salary,   
    AVG(Salary) OVER(ORDER BY HireDate 
                 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS avgSalary  
FROM Employees  
GO
توضیح درباره Script بالا، در این روش برای بدست آوردن میانگین هر سطر، مقدار سطر جاری با مقادیر سطر‌های ماقبل خود جمع و تقسیم بر تعداد سطر می‌شود.
سطر دوم 20000 + 19000 تقسیم بر دو برابر است با 19500
میانگین سطر پنجم، حاصل جمع فیلد Salary همه مقادیر سطرها تقسیم بر 5 
*** اگر بخواهید بوسیله Over Clause ، میانگین همه سطر‌ها یکسان باشد می‌توانید از Script زیر استفاده نمایید:
SELECT  
    Name,   
    Salary,   
    AVG(Salary) OVER(ORDER BY HireDate   
                        RANGE   
                        BETWEEN UNBOUNDED PRECEDING   
                        AND UNBOUNDED FOLLOWING  
                    ) AS avgSalary  
FROM Employees  
GO  
خروجی :

منظور از  ROWS BETWEEN  UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING  یعنی در محاسبه میانگین برای هر سطر تمامی مقادیر سطر‌های دیگر در نظر گرفته شود.
پایان بخش اول
امیدوارم مفید واقع شده باشد.
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 2 - ثبت اولین سرویس
یک پروژه‌ی ASP.NET Core را با قرار دادن نسخه‌ی NET Core. بر روی 3.1 و با استفاده از قالب Model View Controller ایجاد کنید. در اینجا نام پروژه را AspNetCoreDependencyInjection گذاشته‌ام. حالا در  پوشه‌ی Models، فایلی را با نام HomeViewModel.cs با محتویاتی به صورت زیر اضافه کنید:
public class HomeViewModel
{
     public string Id { get; set; }
     public string Message { get; set; }
     public DateTime DateTime { get; set; }
}

اکنون به پوشه‌ی Views بروید و فایل Index.cshtml را به این صورت تغییر دهید:

@model AspNetCoreDependencyInjection.Models.HomeViewModel
@{
ViewData["Title"] = "Home";
}

<div>
<div>
<div>
<p>
<b>Id : </b><span>@Model.Id</span> <br />
<b>Date And Time : </b><span> @Model.DateTime </span> <br/>
<b>Message : </b><span>@Model.Message</span>
</p>
</div>
</div>
</div>
و فایل MessageServiceA.cs را به پروژه اضافه کنید:
using AspNetCoreDependencyInjection.Services;

namespace AspNetCoreDependencyInjection.ServicesImplentaions
{
    public class MessageServiceAA 
    {
        public string Message()
        {
            return "A message from MessageServiceAA";
        }
    }
}
و همچنین فایل GuidHelper.cs را نیز اضافه می‌کنیم:
namespace AspNetCoreDependencyInjection.Helpers
{
    public class GuidProvider
    {
        private readonly Guid _serviceGuid;

        public GuidProvider()
        {
            _serviceGuid = Guid.NewGuid();
        }

        public Guid GetNewGuid() => Guid.NewGuid();

        public string GetGuidAsFormatedString(string prefix = "") => getFormatedGuid(_serviceGuid, prefix);

        private string getFormatedGuid(Guid guid, string prefix = "")
        {
            var guidString = guid.GetHashCode().ToString("x");
            if (string.IsNullOrEmpty(prefix) == false)
                guidString = new StringBuilder($"{prefix}-").Append(guidString).ToString();
            return guidString;
        }
    }
}

حالا درون کنترل HomeController، این تغییرات را انجام می‌دهیم:

private readonly ILogger<HomeController> _logger;
private readonly MessageServiceAA _messageService;
private readonly GuidProvider _ guidProvider;

public HomeController(ILogger<HomeController> logger)
{
            _logger = logger;
            _messageService = new MessageServiceAA();
            _guidProvider = new GuidProvider();
}

public IActionResult Index()
{
            var model = new HomeViewModel()
            {
                Id = _ guidProvider.GetGuidAsFormatedString(),
                Message = _messageService.Message(),
                DateTime = DateTime.Now,
            };
            return View(model);
}

همانطور که می‌بینید، در کد بالا، کنترلر HomeController، به دو شیء از کلاس‌ها و یا سرویس‌های GuidProvider و MessageServiceAA به صورت مستقیم وابسته شده‌است و با هر تغییری در هر کدام از این سرویسها، باید دوباره کامپایل شود. علاوه بر این اگر بخواهیم پیاده سازی‌های مختلفی را برای هر کدام از این موارد، ارائه دهیم، به مشکل بر می‌خوریم. خب بیاید تغییراتی را در کد بالا بدهیم تا مشکلات ذکر شده را حل کنیم.

برای این منظور پوشه‌ای را به نام Services می‌سازیم و اینترفیسی را به نام IMessageBrokerA ایجاد می‌کنیم و سپس کاری می‌کنیم که MessageServiceAA از این اینترفیس ارث بری کند:

namespace AspNetCoreDependencyInjection.Services
{
    public interface IMessageServiceA
    {
        string Message();
    }
}

و حالا می‌خواهیم با استفاده از تزریق وابستگی، وابستگی کنترلر HomeController را از کلاس MessageBrokerAA لغو کرده و آن را به اینترفیس IMessageBrokerA (انتزاع) وابسته کنیم. در اینجا ما از تکنیک تزریق درون سازنده یا Constructor Injection استفاده می‌کنیم.


تزریق درون سازنده

در این تکنیک، ما لیستی از وابستگی‌های مورد نیاز را به عنوان پارامترهای ورودی سازنده‌ی کلاس، تعریف می‌کنیم:
private readonly ILogger<HomeController> _logger;
private readonly IMessageServiceA _messageService;
private readonly GuidProvider _guidHelper;
public HomeController(ILogger<HomeController> logger , IMessageServiceA messageService)
{
        _logger = logger;
        _messageService = messageService;
        _messageService = new MessageServiceAA();
        _guidHelper = new GuidProvider();
}
و حالا اگر برنامه را اجرا کنیم، با خطایی روبه رو می‌شویم که در آن می‌گوید امکان واکشی (Resolve) سرویس‌های مورد نظر وجود ندارد. این خطا به دلیل ثبت نشدن اینترفیس IMessageServiceA و پیاده سازی آن، درون Microsoft Dependency Injection Container است   DI Container‌ها معمولا باید در زمان شروع برنامه، پیکربندی و مقدار دهی شودند، تا در ادامه‌ی چرخه‌ی حیات برنامه، بتوانند سرویس‌ها و اشیاء مورد نیاز را به کلاس‌هایی که نیاز دارند، واکشی و تزریق کنند. اولین مرحله از کار با DI Container‌ها، ثبت کردن سرویس‌ها درون آنهاست.
در ASP.NET Core از IServiceCollection برای ثبت کردن سرویس‌های برنامه‌ی خودمان استفاده می‌کنیم. تمامی سرویس‌هایی را که انتظار داریم توسط DI Container به کلاس‌هایی تزریق شوند، باید درون IServiceCollection ثبت گردند. تمام سرویس‌هایی که به وسیله‌ی IServiceCollection ثبت شده‌اند، پس از ساخته شدن، توسط اینترفیس IServiceProvider قابل واکشی هستند.

بنابراین دو اینترفیس حیاتی برای کار کرد صحیح Microsoft Dependency Injection Container درون ASP.NET Core وجود دارند:
  • IServiceCollection : برای ثبت سرویس‌ها
  • IServiceProvider : برای واکشی سرویس‌ها

در ASP.NET Core معمول‌ترین مکان برای ثبت کردن سرویس‌ها درون Container، به صورت پیش فرض درون کلاس Startup و درون متد ConfigureServices انجام می‌گیرد.
به صورت پیش فرض کلاس Startup دو متد دارد:
  • ConfigureServices : برای پیکربندی و ثبت سرویس‌های درونی DI Container استفاده می‌شود.
  • Configure : برای تنظیمات pipeline میان افزارها ( Middlewares ) بکار می‌رود.

در اینجا پیاده سازی پیش فرض کلاس Startup را می‌بینیم که البته کدهای درون متد Configure را برای درگیر نکردن ذهن شما، مخفی کرده‌ایم: 
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //  کدها جهت خوانایی بیشتر مخفی شده اند
        }
    }

همانطورکه می‌بینید، متد ConfigureService پارامتر IServiceCollection را می‌گیرد که به وسیله‌ی WebHost در زمان اجرای برنامه، مقدار دهی می‌شود.

تعداد زیادی Extension method برای IServiceCollection وجود دارند که برای پشتیبانی از ثبت کردن سرویس‌های مختلف در سناریوهای گوناگون به کار می‌روند. در اینجا ما از نسخه‌ی 3.1 چارچوب ASP.NET Core استفاده می‌کنیم. برای همین هم برای ثبت سرویس‌های پیش فرض فریمورک MVC از متد توسعه‌ی services.AddControllersWithViews()    استفاده می‌کنیم.  متد توسعه‌ی AddControllersWithViews() سرویس‌هایی را که معمولا در فریم ورک MVC استفاده می‌شوند، درون IServiceCollection ثبت می‌کند. در نسخه‌های قبلی چارچوب ASP.NET Core،  مانند نسخه‌های 2.1 و 2.2 برای این کار از متد توسعه‌ی AddMvc() استفاده می‌شد.

در Microsoft Dependency Injection Container ، معمولا  ترتیب ثبت سرویس‌ها مهم نیست.

خب، اولین سرویس اختصاصی برنامه‌ی خودمان را با چرخه‌ی حیات Transient و زیر سرویس پیشین، به شکل زیر ثبت می‌کنیم :

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddTransient<IMessageServiceA, MessageServiceAA>();
        }
همانطور که می‌بینید، در اینجا ما از متد AddTransient() استفاده کرده‌ایم. متد AddTransient() درون فضای نام Microsoft.Extensions.DependencyInjection قرار دارد. این متد Overload ‌های گوناگونی دارد و ما از نوعی از آن استفاده کرده‌ایم که دو نوع generic را می‌پذیرد و تعریف آن به صورت زیر است: 
public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)
در اینجا TService ، اینترفیس سرویس ماست. این نوع، همان نوعی است که کلاس‌های ما می‌توانند به آن وابسته باشند. پارامتر دوم، از نوع TImplemention است که پیاده سازی مورد نظر برای TService را ثبت می‌کند. TImplmention   نوعی است که Container در زمان واکشی و تزریق TService از آن نمونه سازی کرده و به کلاس مورد نظر تزریق می‌کند.

در اینجا وقتی ما برای IMessageServiceA ، پیاده سازی MessageServiceA را ثبت می‌کنیم، از این به بعد DI Container، هر زمانیکه در لیست پارامترهای سازنده‌ی یک کلاس، IMessageServiceA را مشاهده کند، بررسی می‌کند که چه کلاسی به عنوانی پیاده سازی این اینترفیس ثبت شده‌است، سپس از آن نمونه سازی می‌کند و درون سازنده‌ی مورد نظر تزریق می‌کند. خب، حالا برنامه را دوباره اجرا کنید؛ می‌بینید که برنامه اجرا می‌شود.

 
در ادامه ابتدا در مورد روش‌های مختلف ثبت سرویس‌ها و بعد روش‌های واکشی سرویس‌ها را بررسی می‌کنیم.