مطالب
اعتبارسنجی از راه دور در فرم‌های مبتنی بر قالب‌های Angular
در پروژه angular2-validations، یک نمونه پیاده سازی اعتبارسنجی از راه دور یا RemoteValidation را می‌توانید مشاهده کنید. این پیاده سازی مبتنی بر Promiseها است. در مطلب جاری پیاده سازی دیگری را بر اساس Observableها مشاهده خواهید کرد و همچنین ساختار آن شبیه به ساختار remote validation در ASP.NET MVC و jQuery Validator طراحی شده‌است.


نگاهی به ساختار طراحی اعتبارسنجی از راه دور در ASP.NET MVC و jQuery Validator

در نگارش‌های مختلف ASP.NET MVC و ASP.NET Core، ویژگی Remote سمت سرور، سبب درج یک چنین ویژگی‌هایی در سمت کلاینت می‌شود:
data-val-remote="کلمه عبور وارد شده را راحت می‌توان حدس زد!" 
data-val-remote-additionalfields="*.Password1" 
data-val-remote-type="POST" 
data-val-remote-url="/register/checkpassword"
که شامل موارد ذیل است:
- متن نمایشی خطای اعتبارسنجی.
- تعدادی فیلد اضافی که در صورت نیز از فرم استخراج می‌شوند و به سمت سرور ارسال خواهند شد.
- نوع روش ارسال اطلاعات به سمت سرور.
- یک URL که مشخص می‌کند، این اطلاعات باید به کدام اکشن متد در سمت سرور ارسال شوند.

سمت سرور هم می‌تواند یک true یا false را بازگشت دهد و مشخص کند که آیا اطلاعات مدنظر معتبر نیستند یا هستند.
شبیه به یک چنین ساختاری را در ادامه با ایجاد یک دایرکتیو سفارشی اعتبارسنجی برنامه‌های Angular تدارک خواهیم دید.


ساختار اعتبارسنج‌های سفارشی async در Angular

در مطلب «نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular» جزئیات نوشتن اعتبارسنج‌های متداول فرم‌های Angular را بررسی کردیم. این نوع اعتبارسنج‌ها چون اطلاعاتی را به صورت Ajax ایی به سمت سرور ارسال نمی‌کنند، با پیاده سازی اینترفیس Validator تهیه خواهند شد:
 export class EmailValidatorDirective implements Validator {
اما زمانیکه نیاز است اطلاعاتی مانند نام کاربری یا ایمیل او را به سرور ارسال کنیم و در سمت سرور، پس از جستجوی در بانک اطلاعاتی، منحصربفرد بودن آن‌ها مشخص شود یا خیر، دیگر این روش همزمان پاسخگو نخواهد بود. به همین جهت اینبار اینترفیس دیگری به نام AsyncValidator برای انجام اعمال async و Ajax ایی در Angular تدارک دیده شده‌است:
 export class RemoteValidatorDirective implements AsyncValidator {
در این حالت امضای متد validate این اینترفیس به صورت ذیل است:
validate(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;
یعنی در اینجا هم می‌توان یک Promise را بازگشت داد (مانند پیاده سازی که در ابتدای بحث عنوان شد) و یا می‌توان یک Observable را بازگشت داد که در ادامه نمونه‌ای از پیاده سازی این روش دوم را بررسی می‌کنیم؛ چون امکانات بیشتری را نسبت به Promiseها به همراه دارد. برای مثال در اینجا می‌توان اندکی صبر کرد تا کاربر تعدادی حرف را وارد کند و سپس این اطلاعات را به سرور ارسال کرد. به این ترتیب ترافیک ارسالی به سمت سرور کاهش پیدا می‌کند.


پیاده سازی یک اعتبارسنج از راه دور مبتنی بر Observableها در Angular

ابتدا یک دایرکتیو جدید را به نام RemoteValidator به ماژول custom-validators اضافه کرده‌ایم:
 >ng g d CustomValidators/RemoteValidator -m custom-validators.module
در ادامه کدهای کامل این اعتبارسنج را مشاهده می‌کنید:
import { Directive, Input } from "@angular/core";
import {
  AsyncValidator,
  AbstractControl,
  NG_ASYNC_VALIDATORS
} from "@angular/forms";
import { Http, RequestOptions, Response, Headers } from "@angular/http";
import "rxjs/add/operator/map";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/takeUntil";
import "rxjs/add/operator/take";
import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";

@Directive({
  selector:
    "[appRemoteValidator][formControlName],[appRemoteValidator][formControl],[appRemoteValidator][ngModel]",
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: RemoteValidatorDirective,
      multi: true
    }
  ]
})
export class RemoteValidatorDirective implements AsyncValidator {
  @Input("remote-url") remoteUrl: string;
  @Input("remote-field") remoteField: string;
  @Input("remote-additional-fields") remoteAdditionalFields: string;

  constructor(private http: Http) {}

  validate(control: AbstractControl): Observable<{ [key: string]: any }> {
    if (!this.remoteUrl || this.remoteUrl === undefined) {
      return Observable.throw("`remoteUrl` is undefined.");
    }

    if (!this.remoteField || this.remoteField === undefined) {
      return Observable.throw("`remoteField` is undefined.");
    }

    const dataObject = {};
    if (
      this.remoteAdditionalFields &&
      this.remoteAdditionalFields !== undefined
    ) {
      const otherFields = this.remoteAdditionalFields.split(",");
      otherFields.forEach(field => {
        const name = field.trim();
        const otherControl = control.root.get(name);
        if (otherControl) {
          dataObject[name] = otherControl.value;
        }
      });
    }

    // This is used to signal the streams to terminate.
    const changed$ = new Subject<any>();
    changed$.next(); // This will signal the previous stream (if any) to terminate.

    const debounceTime = 400;

    return new Observable((obs: any) => {
      control.valueChanges
        .takeUntil(changed$)
        .take(1)
        .debounceTime(debounceTime)
        .distinctUntilChanged()
        .flatMap(term => {
          dataObject[this.remoteField] = term;
          return this.doRemoteValidation(dataObject);
        })
        .subscribe(
          (result: IRemoteValidationResult) => {
            if (result.result) {
              obs.next(null);
            } else {
              obs.next({
                remoteValidation: {
                  remoteValidationMessage: result.message
                }
              });
            }

            obs.complete();
          },
          error => {
            obs.next(null);
            obs.complete();
          }
        );
    });
  }

  private doRemoteValidation(data: any): Observable<IRemoteValidationResult> {
    const headers = new Headers({ "Content-Type": "application/json" }); // for ASP.NET MVC
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(this.remoteUrl, JSON.stringify(data), options)
      .map(this.extractData)
      .do(result => console.log("remoteValidation result: ", result))
      .catch(this.handleError);
  }

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

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

export interface IRemoteValidationResult {
  result: boolean;
  message: string;
}
توضیحات تکمیلی

ساختار Directive تهیه شده مانند همان مطلب «نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular» است، تنها با یک تفاوت:
@Directive({
  selector:
    "[appRemoteValidator][formControlName],[appRemoteValidator][formControl],[appRemoteValidator][ngModel]",
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: RemoteValidatorDirective,
      multi: true
    }
  ]
})
در اینجا بجای NG_VALIDATORS، از NG_ASYNC_VALIDATORS استفاده شده‌است.

سپس ورودی‌های این دایرکتیو را مشاهده می‌کنید:
export class RemoteValidatorDirective implements AsyncValidator {
  @Input("remote-url") remoteUrl: string;
  @Input("remote-field") remoteField: string;
  @Input("remote-additional-fields") remoteAdditionalFields: string;
به این ترتیب زمانیکه appRemoteValidator به المانی اضافه می‌شود (نام selector این دایرکتیو)، سبب فعالسازی این اعتبارسنج می‌گردد.
<input #username="ngModel" required maxlength="8" minlength="4" type="text"
        appRemoteValidator [remote-url]="remoteUsernameValidationUrl" remote-field="FirstName"
        remote-additional-fields="email,password" class="form-control" name="username"
        [(ngModel)]="model.username">
- در اینجا توسط ویژگی remote-url، آدرس اکشن متد سمت سرور دریافت می‌شود.
- ویژگی remote-field مشخص می‌کند که اطلاعات المان جاری با چه کلیدی به سمت سرور ارسال شود.
- ویژگی remote-additional-fields مشخص می‌کند که علاوه بر اطلاعات کنترل جاری، اطلاعات کدامیک از کنترل‌های دیگر را نیز می‌توان به سمت سرور ارسال کرد.

یک نکته:
ذکر "remote-field="FirstName به معنای انتساب مقدار رشته‌ای FirstName به خاصیت متناظر با ویژگی remote-field است.
انتساب ویژه‌ی "remoteUsernameValidationUrl" به [remote-url]، به معنای انتساب مقدار متغیر remoteUsernameValidationUrl که در کامپوننت متناظر این قالب مقدار دهی می‌شود، به خاصیت متصل به ویژگی remote-url است.
export class UserRegisterComponent implements OnInit {
   remoteUsernameValidationUrl = "api/Employee/CheckUser";
بنابراین اگر remote-field را نیز می‌خواستیم به همین نحو تعریف کنیم، ذکر '' جهت مشخص سازی انتساب یک رشته، ضروری می‌بود؛ یعنی درج آن به صورت:
 [remote-field]="'FirstName'"


ساختار مورد انتظار بازگشتی از سمت سرور

در کدهای فوق، یک چنین ساختاری باید از سمت سرور بازگشت داده شود:
export interface IRemoteValidationResult {
   result: boolean;
   message: string;
}
برای نمونه این ساختار را می‌توان توسط یک anonymous object ایجاد کرد و بازگشت داد:
namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class EmployeeController : Controller
    {
        [HttpPost("[action]")]
        [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult CheckUser([FromBody] Employee model)
        {
            var remoteValidationResult = new { result = true, message = $"{model.FirstName} is fine!" };
            if (model.FirstName?.Equals("Vahid", StringComparison.OrdinalIgnoreCase) ?? false)
            {
                remoteValidationResult = new { result = false, message = "username:`Vahid` is already taken." };
            }

            return Json(remoteValidationResult);
        }
    }
}
در اینجا برای مثال بررسی می‌شود که آیا FirstName ارسالی از سمت کاربر، معادل Vahid است یا خیر؟ اگر بله، result به false تنظیم شده و همچنین پیام خطایی نیز بازگشت داده می‌شود.
همچنین اعتبارسنج سفارشی از راه دور فوق، پیام‌ها را تنها از طریق HttpPost ارسال می‌کند. علت اینجا است که در حالت POST، برخلاف حالت GET می‌توان اطلاعات بیشتری را بدون نگرانی از طول URL، ارسال کرد و همچنین کل درخواست، به علت وجود کاراکترهای غیرمجاز در URL (حالت GET، به درخواست یک URL از سرور تفسیر می‌شود)، برگشت نمی‌خورد.


تکمیل کامپوننت فرم ثبت نام کاربران

در ادامه تکمیل قالب user-register.component.html را مشاهده می‌کنید:
    <div class="form-group" [class.has-error]="username.invalid && username.touched">
      <label class="control-label">User Name</label>
      <input #username="ngModel" required maxlength="8" minlength="4" type="text"
        appRemoteValidator [remote-url]="remoteUsernameValidationUrl" remote-field="FirstName"
        remote-additional-fields="email,password" class="form-control" name="username"
        [(ngModel)]="model.username">
      <div *ngIf="username.pending" class="alert alert-warning">
        Checking server, Please wait ...
      </div>
      <div *ngIf="username.invalid && username.touched">
        <div class="alert alert-danger"  *ngIf="username.errors.remoteValidation">
          {{username.errors.remoteValidation.remoteValidationMessage}}
        </div>
      </div>
    </div>
در مورد ویژگی‌های appRemoteValidator پیشتر بحث شد. در اینجا تنها یک نکته‌ی جدید وجود دارد:
زمانیکه یک async validator مشغول به کار است و هنوز پاسخی را دریافت نکرده‌است، خاصیت pending را به true تنظیم می‌کند. به این ترتیب می‌توان پیام اتصال به سرور را نمایش داد:


همچنین چون در اینجا نحوه‌ی طراحی شکست اعتبارسنجی به صورت ذیل است:
obs.next({
                remoteValidation: {
                  remoteValidationMessage: result.message
                }
              });
وجود کلید remoteValidation در مجموعه‌ی username.errors، بیانگر وجود خطای اعتبارسنجی از راه دور است و به این ترتیب می‌توان پیام دریافتی از سمت سرور را نمایش داد:



مزایای استفاده از Observableها در حین طراحی async validators

در کدهای فوق چنین مواردی را هم مشاهده می‌کنید:
    // This is used to signal the streams to terminate.
    const changed$ = new Subject<any>();
    changed$.next(); // This will signal the previous stream (if any) to terminate.

    const debounceTime = 400;

    return new Observable((obs: any) => {
      control.valueChanges
        .takeUntil(changed$)
        .take(1)
        .debounceTime(debounceTime)
        .distinctUntilChanged()
در اینجا بجای کار مستقیم با control.value (روش متداول دسترسی به مقدار کنترل دریافتی در یک اعتبارسنج)، به رخ‌داد valueChanges آن متصل شده و سپس پس از 400 میلی‌ثانیه، جمع نهایی ورودی کاربر، در اختیار متد http.post برای ارسال به سمت سرور قرار می‌گیرد. به این ترتیب می‌توان تعداد رفت و برگشت‌های به سمت سرور را کاهش داد و به ازای هر یکبار فشرده شدن دکمه‌ای توسط کاربر، سبب بروز یکبار رفت و برگشت به سرور نشد.
همچنین وجود و تعریف new Subject، دراینجا ضروری است و از نشتی حافظه و همچنین رفت و برگشت‌های اضافه‌ی دیگری به سمت سرور، جلوگیری می‌کند. این subject سبب می‌شود تا کلیه اعمال ناتمام پیشین، لغو شده (takeUntil) و تنها آخرین درخواست جدید رسیده‌ی پس از 400 میلی‌ثانیه، به سمت سرور ارسال شود.

بنابراین همانطور که مشاهده می‌کنید، Observableها فراتر هستند از صرفا ارسال اطلاعات به سرور و بازگشت آن‌ها به سمت کلاینت (استفاده‌ی متداولی که از آن‌ها در برنامه‌های Angular وجود دارد).


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

انشاالله در چندین ماه اینده بعد از کامل شدن سیستم و انتشار اون میتونید بررسیش کنید اگر مایل بودید. کیت‌های توسعه‌ی اون هم به صورت سورس باز منتشر خواهد شد و در همین سایت هم معرفی میشن. به نظر من ارزشش رو داره که روی جزئیات این سیستم کار کنم و امیدوارم بتونه جای تامل داشته باشه . در اخر من رو ببخشید برادر من هیچوقت قصد جسارت نداشتم به شما و دیگر دوستان . این سایت محیط مقدسی هست چون واقعا مطالب خوبی در اون قرار میگیره امیدوارم بتونم از اطلاعات شما و دیگر دوستان استفاده کنم . یا حق
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت پنجم - پیاده سازی ورود و خروج از سیستم
پس از راه اندازی IdentityServer، نوبت به امن سازی برنامه‌ی Mvc Client توسط آن می‌رسد و اولین قسمت آن، ورود به سیستم و خروج از آن می‌باشد.


بررسی اجزای Hybrid Flow

در قسمت سوم در حین «انتخاب OpenID Connect Flow مناسب برای یک برنامه‌ی کلاینت از نوع ASP.NET Core» به این نتیجه رسیدیم که Flow مناسب یک برنامه‌ی Mvc Client از نوع Hybrid است. در اینجا هر Flow، شروع به ارسال درخواستی به سمت Authorization Endpoint می‌کند؛ با یک چنین قالبی:
https://idpHostAddress/connect/authorize? 
client_id=imagegalleryclient 
&redirect_uri=https://clientapphostaddress/signin-oidcoidc 
&scope=openid profile 
&response_type=code id_token 
&response_mode=form_post
&nonce=63626...n2eNMxA0
- در سطر اول، Authorization Endpoint مشخص شده‌است. این آدرس از discovery endpoint که یک نمونه تصویر محتوای آن‌را در قسمت قبل مشاهده کردید، استخراج می‌شود.
- سپس client_id جهت تعیین برنامه‌ای که درخواست را ارسال می‌کند، ذکر شده‌است؛ از این جهت که یک IDP جهت کار با چندین نوع کلاینت مختلف طراحی شده‌است.
- redirect_uri همان Redirect Endpoint است که در سطح برنامه‌ی کلاینت تنظیم می‌شود.
- در مورد scope در قسمت قبل در حین راه اندازی IdentityServer توضیح دادیم. در اینجا برنامه‌ی کلاینت، درخواست scopeهای openid و profile را داده‌است. به این معنا که نیاز دارد تا Id کاربر وارد شده‌ی به سیستم و همچنین Claims منتسب به او را در اختیار داشته باشد.
- response_type نیز به code id_token تنظیم شده‌است. توسط response_type، نوع Flow مورد استفاده مشخص می‌شود. ذکر code به معنای بکارگیری Authorization code flow است. ذکر id_token و یا id_token token هر دو به معنای استفاده‌ی از implicit flow است. اما برای مشخص سازی Hybrid flow یکی از سه مقدار code id_token و یا code token و یا code id_token token با هم ذکر می‌شوند:


- در اینجا response_mode مشخص می‌کند که اطلاعات بازگشتی از سمت IDP که توسط response_type مشخص شده‌اند، با چه قالبی به سمت کلاینت بازگشت داده شوند که می‌تواند از طریق Form POST و یا URI باشد.


در Hybrid flow با response_type از نوع code id_token، ابتدا کلاینت یک درخواست Authentication را به Authorization Endpoint ارسال می‌کند (با همان قالب URL فوق). سپس در سطح IDP، کاربر برای مثال با ارائه‌ی کلمه‌ی عبور و نام کاربری، تعیین اعتبار می‌شود. همچنین در اینجا IDP ممکن است رضایت کاربر را از دسترسی به اطلاعات پروفایل او نیز سؤال بپرسد (تحت عنوان مفهوم Consent). سپس IDP توسط یک Redirection و یا Form POST، اطلاعات authorization code و identity token را به سمت برنامه‌ی کلاینت ارسال می‌کند. این همان اطلاعات مرتبط با response_type ای است که درخواست کرد‌ه‌ایم. سپس برنامه‌ی کلاینت این اطلاعات را تعیین اعتبار کرده و در صورت موفقیت آمیز بودن این عملیات، اکنون درخواست تولید توکن هویت را به token endpoint ارسال می‌کند. برای این منظور کلاینت سه مشخصه‌ی authorization code ،client-id و client-secret را به سمت token endpoint ارسال می‌کند. در پاسخ یک identity token را دریافت می‌کنیم. در اینجا مجددا این توکن تعیین اعتبار شده و سپس Id کاربر را از آن استخراج می‌کند که در برنامه‌ی کلاینت قابل استفاده خواهد بود. این مراحل را در تصویر زیر می‌توانید ملاحظه کنید.
البته اگر دقت کرده باشید، یک identity token در همان ابتدای کار از Authorization Endpoint دریافت می‌شود. اما چرا از آن استفاده نمی‌کنیم؟ علت اینجا است که token endpoint نیاز به اعتبارسنجی client را نیز دارد. به این ترتیب یک لایه‌ی امنیتی دیگر نیز در اینجا بکار گرفته می‌شود. همچنین access token و refresh token نیز از همین token endpoint قابل دریافت هستند.




تنظیم IdentityServer جهت انجام عملیات ورود به سیستم بر اساس جزئیات Hybrid Flow

برای افزودن قسمت لاگین به برنامه‌ی MVC قسمت دوم، نیاز است تغییراتی را در برنامه‌ی کلاینت و همچنین IDP اعمال کنیم. برای این منظور کلاس Config پروژه‌ی IDP را که در قسمت قبل ایجاد کردیم، به صورت زیر تکمیل می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientName = "Image Gallery",
                    ClientId = "imagegalleryclient",
                    AllowedGrantTypes = GrantTypes.Hybrid,
                    RedirectUris = new List<string>
                    {
                        "https://localhost:5001/signin-oidc"
                    },
                    PostLogoutRedirectUris = new List<string>
                    {
                        "https://localhost:5001/signout-callback-oidc"
                    },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    },
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    }
                }
             };
        }
    }
}
در اینجا بجای بازگشت لیست خالی کلاینت‌ها، یک کلاینت جدید را تعریف و تکمیل کرده‌ایم.
- ابتدا نام کلاینت را مشخص می‌کنیم. این نام و عنوان، در صفحه‌ی لاگین و Consent (رضایت دسترسی به اطلاعات پروفایل کاربر)، ظاهر می‌شود.
- همچنین نیاز است یک Id دلخواه را نیز برای آن مشخص کنیم؛ مانند imagegalleryclient در اینجا.
- AllowedGrantTypes را نیز به Hybrid Flow تنظیم کرده‌ایم. علت آن‌را در قسمت سوم این سری بررسی کردیم.
- با توجه به اینکه Hybrid Flow از Redirectها استفاده می‌کند و اطلاعات نهایی را به کلاینت از طریق Redirection ارسال می‌کند، به همین جهت آدرس RedirectUris را به آدرس برنامه‌ی Mvc Client تنظیم کرده‌ایم (که در اینجا بر روی پورت 5001 کار می‌کند). قسمت signin-oidc آن‌را در ادامه تکمیل خواهیم کرد.
- در قسمت AllowedScopes، لیست scopeهای مجاز قابل دسترسی توسط این کلاینت مشخص شده‌اند که شامل دسترسی به ID کاربر و Claims آن است.
- به ClientSecrets نیز جهت client authenticating نیاز داریم.


تنظیم برنامه‌ی MVC Client جهت انجام عملیات ورود به سیستم بر اساس جزئیات Hybrid Flow

برای افزودن قسمت لاگین به سیستم، کلاس آغازین پروژه‌ی MVC Client را به نحو زیر تکمیل می‌کنیم:
namespace ImageGallery.MvcClient.WebApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            }).AddCookie("Cookies")
              .AddOpenIdConnect("oidc", options =>
              {
                  options.SignInScheme = "Cookies";
                  options.Authority = "https://localhost:6001";
                  options.ClientId = "imagegalleryclient";
                  options.ResponseType = "code id_token";
                  //options.CallbackPath = new PathString("...")
                  //options.SignedOutCallbackPath = new PathString("...")
                  options.Scope.Add("openid");
                  options.Scope.Add("profile");
                  options.SaveTokens = true;
                  options.ClientSecret = "secret";
                  options.GetClaimsFromUserInfoEndpoint = true;
              });
این قسمت تنظیمات، سمت کلاینت OpenID Connect Flow را مدیریت می‌کند.

- ابتدا با فراخوانی AddAuthentication، کار تنظیمات میان‌افزار استاندارد Authentication برنامه‌های ASP.NET Core انجام می‌شود. در اینجا DefaultScheme آن به Cookies تنظیم شده‌است تا عملیات Sign-in و Sign-out سمت کلاینت را میسر کند. سپس DefaultChallengeScheme به oidc تنظیم شده‌است. این مقدار با Scheme ای که در ادامه آن‌را تنظیم خواهیم کرد، تطابق دارد.

- سپس متد AddCookie فراخوانی شده‌است که authentication-Scheme را به عنوان پارامتر قبول می‌کند. به این ترتیب cookie based authentication در برنامه میسر می‌شود. پس از اعتبارسنجی توکن هویت دریافتی و تبدیل آن به Claims Identity، در یک کوکی رمزنگاری شده برای استفاده‌های بعدی ذخیره می‌شود.

- در آخر تنظیمات پروتکل OpenID Connect را ملاحظه می‌کنید. به این ترتیب مراحل اعتبارسنجی توسط این پروتکل در اینجا که Hybrid flow است، پشتیبانی خواهد شد.  اینجا است که کار درخواست Authorization، دریافت و اعتبارسنجی توکن هویت صورت می‌گیرد. اولین پارامتر آن authentication-Scheme است که به oidc تنظیم شده‌است. به این ترتیب اگر قسمتی از برنامه نیاز به Authentication داشته باشد، OpenID Connect به صورت پیش‌فرض مورد استفاده قرار می‌گیرد. به همین جهت DefaultChallengeScheme را نیز به oidc تنظیم کردیم. در اینجا SignInScheme به Cookies تنظیم شده‌است که با DefaultScheme اعتبارسنجی تطابق دارد. به این ترتیب نتیجه‌ی موفقیت آمیز عملیات اعتبارسنجی در یک کوکی رمزنگاری شده ذخیره خواهد شد. مقدار خاصیت Authority به آدرس IDP تنظیم می‌شود که بر روی پورت 6001 قرار دارد. تنظیم این مسیر سبب خواهد شد تا این میان‌افزار سمت کلاینت، به discovery endpoint دسترسی یافته و بتواند مقادیر سایر endpoints برنامه‌ی IDP را به صورت خودکار دریافت و استفاده کند. سپس ClientId تنظیم شده‌است که باید با مقدار تنظیم شده‌ی آن در سمت IDP یکی باشد و همچنین مقدار ClientSecret در اینجا نیز باید با ClientSecrets سمت IDP یکی باشد. ResponseType تنظیم شده‌ی در اینجا با AllowedGrantTypes سمت IDP تطابق دارد که از نوع Hybrid است. سپس دو scope درخواستی توسط این برنامه‌ی کلاینت که openid و profile هستند در اینجا اضافه شده‌اند. به این ترتیب می‌توان به مقادیر Id کاربر و claims او دسترسی داشت. مقدار CallbackPath در اینجا به RedirectUris سمت IDP اشاره می‌کند که مقدار پیش‌فرض آن همان signin-oidc است. با تنظیم SaveTokens به true امکان استفاده‌ی مجدد از آن‌ها را میسر می‌کند.

پس از تکمیل قسمت ConfigureServices و انجام تنظیمات میان‌افزار اعتبارسنجی، نیاز است این میان‌افزار را نیز به برنامه افزود که توسط متد UseAuthentication انجام می‌شود:
namespace ImageGallery.MvcClient.WebApp
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseAuthentication();

پس از این تنظیمات، با اعمال ویژگی Authorize، دسترسی به کنترلر گالری برنامه‌ی MVC Client را صرفا محدود به کاربران وارد شده‌ی به سیستم می‌کنیم:
namespace ImageGallery.MvcClient.WebApp.Controllers
{
    [Authorize]
    public class GalleryController : Controller
    {
    // .... 
   
        public async Task WriteOutIdentityInformation()
        {
            var identityToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
            Debug.WriteLine($"Identity token: {identityToken}");

            foreach (var claim in User.Claims)
            {
                Debug.WriteLine($"Claim type: {claim.Type} - Claim value: {claim.Value}");
            }
        }
در اینجا علاوه بر اعمال فیلتر Authorize به کل اکشن متدهای این کنترلر، یک اکشن متد جدید دیگر را نیز به انتهای آن اضافه کرده‌ایم تا صرفا جهت دیباگ برنامه، اطلاعات دریافتی از IDP را در Debug Window، برای بررسی بیشتر درج کند. البته این روش با Debug Window مخصوص Visual Studio کار می‌کند. اگر می‌خواهید آن‌را در صفحه‌ی کنسول dotnet run مشاهده کنید، بجای Debug باید از ILogger استفاده کرد.

فراخوانی متد GetTokenAsync با پارامتر IdToken، همان Identity token دریافتی از IDP را بازگشت می‌دهد. این توکن با تنظیم SaveTokens به true در تنظیمات AddOpenIdConnect که پیشتر انجام دادیم، قابل استخراج از کوکی اعتبارسنجی برنامه شده‌است.
این متد را در ابتدای اکشن متد Index فراخوانی می‌کنیم:
        public async Task<IActionResult> Index()
        {
            await WriteOutIdentityInformation();
            // ....


اجرای برنامه جهت آزمایش تنظیمات انجام شده

برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.

اکنون که هر سه برنامه با هم در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید:


در این حالت چون فیلتر Authorize به کل اکشن متدهای کنترلر گالری اعمال شده، میان‌افزار Authentication که در فایل آغازین برنامه‌ی کلاینت MVC تنظیم شده‌است، وارد عمل شده و کاربر را به صفحه‌ی لاگین سمت IDP هدایت می‌کند (شماره پورت آن 6001 است). لاگ این اعمال را هم در برگه‌ی network مرورگر می‌تواند مشاهده کنید.

در اینجا نام کاربری و کلمه‌ی عبور اولین کاربر تعریف شده‌ی در فایل Config.cs برنامه‌ی IDP را که User 1 و password است، وارد می‌کنیم. پس از آن صفحه‌ی Consent ظاهر می‌شود:


در اینجا از کاربر سؤال می‌پرسد که آیا به برنامه‌ی کلاینت اجازه می‌دهید تا به Id و اطلاعات پروفایل و یا همان Claims شما دسترسی پیدا کند؟
فعلا گزینه‌ی remember my design را انتخاب نکنید تا همواره بتوان این صفحه را در دفعات بعدی نیز مشاهده کرد. سپس بر روی گزینه‌ی Yes, Allow کلیک کنید.
اکنون به صورت خودکار به سمت برنامه‌ی MVC Client هدایت شده و می‌توانیم اطلاعات صفحه‌ی اول سایت را کاملا مشاهده کنیم (چون کاربر اعتبارسنجی شده‌است، از فیلتر Authorize رد خواهد شد).


همچنین در اینجا اطلاعات زیادی نیز جهت دیباگ برنامه لاگ می‌شوند که در آینده جهت عیب یابی آن می‌توانند بسیار مفید باشند:


با دنبال کردن این لاگ می‌توانید مراحل Hybrid Flow را مرحله به مرحله با مشاهده‌ی ریز جزئیات آن بررسی کنید. این مراحل به صورت خودکار توسط میان‌افزار Authentication انجام می‌شوند و در نهایت اطلاعات توکن‌های دریافتی به صورت خودکار در اختیار برنامه برای استفاده قرار می‌گیرند. یعنی هم اکنون کوکی رمزنگاری شده‌ی اطلاعات اعتبارسنجی کاربر در دسترس است و به اطلاعات آن می‌توان توسط شیء this.User، در اکشن متدهای برنامه‌ی MVC، دسترسی داشت.


تنظیم برنامه‌ی MVC Client جهت انجام عملیات خروج از سیستم

ابتدا نیاز است یک لینک خروج از سیستم را به برنامه‌ی کلاینت اضافه کنیم. برای این منظور به فایل Views\Shared\_Layout.cshtml مراجعه کرده و لینک logout را در صورت IsAuthenticated بودن کاربر جاری وارد شده‌ی به سیستم، نمایش می‌دهیم:
<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li><a asp-area="" asp-controller="Gallery" asp-action="Index">Home</a></li>
        <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li>
        @if (User.Identity.IsAuthenticated)
        {
            <li><a asp-area="" asp-controller="Gallery" asp-action="Logout">Logout</a></li>
        }
    </ul>
</div>


شیء this.User، هم در اکشن متدها و هم در Viewهای برنامه، جهت دسترسی به اطلاعات کاربر اعتبارسنجی شده، در دسترس است.
این لینک به اکشن متد Logout، در کنترلر گالری اشاره می‌کند که آن‌را به صورت زیر تکمیل خواهیم کرد:
namespace ImageGallery.MvcClient.WebApp.Controllers
{
    [Authorize]
    public class GalleryController : Controller
    {
        public async Task Logout()
        {
            // Clears the  local cookie ("Cookies" must match the name of the scheme)
            await HttpContext.SignOutAsync("Cookies");
            await HttpContext.SignOutAsync("oidc");
        }
در اینجا ابتدا کوکی Authentication حذف می‌شود. نامی که در اینجا انتخاب می‌شود باید با نام scheme انتخابی مرتبط در فایل آغازین برنامه یکی باشد.
سپس نیاز است از برنامه‌ی IDP نیز logout شویم. به همین جهت سطر دوم SignOutAsync با پارامتر oidc را مشاهده می‌کنید. بدون وجود این سطر، کاربر فقط از برنامه‌ی کلاینت logout می‌شود؛ اما اگر به IDP مجددا هدایت شود، مشاهده خواهد کرد که در آن سمت، هنوز نام کاربری او توسط IDP شناسایی می‌شود.


بهبود تجربه‌ی کاربری Logout

پس از logout، بدون انجام یکسری از تنظیمات، کاربر مجددا به برنامه‌ی کلاینت به صورت خودکار هدایت نخواهد شد و در همان سمت IDP متوقف می‌شد. برای بهبود این وضعیت و بازگشت مجدد به برنامه‌ی کلاینت، اینکار را یا توسط مقدار دهی خاصیت SignedOutCallbackPath مربوط به متد AddOpenIdConnect می‌توان انجام داد و یا بهتر است مقدار پیش‌فرض آن‌را به تنظیمات IDP نسبت داد که پیشتر در تنظیمات متد GetClients آن‌را ذکر کرده بودیم:
PostLogoutRedirectUris = new List<string>
{
     "https://localhost:5001/signout-callback-oidc"
},
با وجود این تنظیم، اکنون IDP می‌داند که پس از logout، چه آدرسی را باید به کاربر جهت بازگشت به سیستم قبلی ارائه دهد:


البته هنوز یک مرحله‌ی انتخاب و کلیک بر روی لینک بازگشت وجود دارد. برای حذف آن و خودکار کردن Redirect نهایی آن، می‌توان کدهای IdentityServer4.Quickstart.UI را که در قسمت قبل به برنامه‌ی IDP اضافه کردیم، اندکی تغییر دهیم. برای این منظور فایل src\IDP\DNT.IDP\Quickstart\Account\AccountOptions.cs را گشوده و سپس فیلد AutomaticRedirectAfterSignOut را که false است، به true تغییر دهید.

 
تنظیمات بازگشت Claims کاربر به برنامه‌ی کلاینت

به صورت پیش‌فرض، Identity Server اطلاعات Claims کاربر را ارسال نمی‌کند و Identity token صرفا به همراه اطلاعات Id کاربر است. برای تنظیم آن می‌توان در سمت تنظیمات IDP، در متد GetClients، زمانیکه new Client صورت می‌گیرد، خاصیت AlwaysIncludeUserClaimsInIdToken هر کلاینت را به true تنظیم کرد؛ اما ایده خوبی نیست. Identity token از طریق Authorization endpoint دریافت می‌شود. در اینجا اگر این اطلاعات از طریق URI دریافت شود و Claims به Identity token افزوده شوند، به مشکل بیش از حد طولانی شدن URL نهایی خواهیم رسید و ممکن است از طرف وب سرور یک چنین درخواستی برگشت بخورد. به همین جهت به صورت پیش‌فرض اطلاعات Claims به Identity token اضافه نمی‌شوند.
در اینجا برای دریافت Claims، یک endpoint دیگر در IDP به نام UserInfo endpoint درنظر گرفته شده‌است. در این حالت برنامه‌ی کلاینت، مقدار Access token دریافتی را که به همراه اطلاعات scopes متناظر با Claims است، به سمت UserInfo endpoint ارسال می‌کند. باید دقت داشت زمانیکه Identity token دوم از Token endpoint دریافت می‌شود (تصویر ابتدای بحث)، به همراه آن یک Access token نیز صادر و ارسال می‌گردد. اینجا است که میان‌افزار oidc، این توکن دسترسی را به سمت UserInfo endpoint ارسال می‌کند تا user claims را دریافت کند:


در تنظیمات سمت کلاینت AddOpenIdConnect، درخواست openid و profile، یعنی درخواست Id کاربر و Claims آن وجود دارند:
options.Scope.Add("openid");
options.Scope.Add("profile");
برای بازگشت آن‌ها به سمت کلاینت، درخواست دریافت claims از UserInfo Endpoint را در سمت کلاینت تنظیم می‌کنیم:
options.GetClaimsFromUserInfoEndpoint = true;
همین اندازه تنظیم میان‌افزار oidc، برای انجام خودکار کل گردش کاری یاد شده کافی است.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه با هم در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
مطالب
خلاصه‌ای از روش‌های ارسال داده‌های سمت سرور به کدهای جاوا اسکریپتی در ASP.NET MVC
روش‌های زیادی برای ارسال داده‌های سمت سرور تهیه شده در یک برنامه‌ی ASP.NET به کدهای سمت کاربر JavaScript ایی وجود دارند که تعدادی از مهم‌ترین‌های آن‌ها را در این مطلب بررسی خواهیم کرد.

روش اول: دریافت اطلاعات سمت سرور به کمک درخواست‌های Ajax

استفاده از Ajax یکی از روش‌های کلاسیک دریافت اطلاعات سمت سرور در کدهای جاوا اسکریپتی است.
<script type="text/javascript">
var products = [];
$(function() {
    $.getJSON("/home/products", function(response) {
        products = response.products;
    });
});
</script>
برای نمونه در اینجا با استفاده از امکانات jQuery، درخواست Ajax ایی به سرور ارسال شده و سپس نتیجه‌ی دریافتی، به آرایه‌ی جاوا اسکریپتی products نسبت داده شده‌است.
- مزایا: استفاده از Ajax، روشی بسیار متداول و شناخته شده‌است و به کمک انواع و اقسام روش‌های بازگشت JSON از سرور، می‌توان با آن کار کرد.
- معایب: درخواست Ajax، صرفا پس از بارگذاری اولیه‌ی صفحه به سمت سرور ارسال خواهد شد و در این بین، کاربر وقفه‌ای را مشاهده خواهد کرد. همچنین در اینجا بجای یک درخواست از سرور، حداقل دو درخواست باید ارسال شوند؛ یکی برای بارگذاری صفحه‌ی اصلی و دیگری برای دریافت اطلاعات Ajax ایی از سرور به صورت غیرهمزمان.


روش دوم: دریافت اطلاعات از یک فایل جاوا اسکریپتی خارجی

اطلاعات سمت کاربر را از یک فایل جاوا اسکریپتی خارجی الحاق شده‌ی به صفحه‌ی جاری نیز می‌توان تهیه کرد:
 <script src="/file.js"></script>
یک چنین فایلی را می‌توان توسط کدهای سمت سرور نیز بازگشت داد و اطلاعات آن‌را تهیه و پر کرد. در این حالت فقط کافی است که ContentType شیء Response را از نوع application/javascript مشخص کنیم و سپس یک خروجی متنی جاوا اسکریپتی را از سمت سرور بازگشت دهیم.
این روش نیز تقریبا مانند حالت یک درخواست Ajax ایی کار می‌کند و اطلاعات مورد نیاز را در طی یک درخواست جداگانه، پس از بارگذاری صفحه‌ی اصلی، از سرور دریافت خواهد کرد. البته در حالت کار با Ajax، می‌توان در طی یک callback، نتیجه را دریافت کرد و سپس عکس العمل نشان داد؛ اما در اینجا callback ایی وجود ندارد.


روش سوم: استفاده از SignalR

در SignalR ابتدا سعی می‌شود تا با استفاده از Web Sockets ارتباطی ماندگار بین کلاینت و سرور برقرار شود و سپس در این حالت، سرور می‌تواند مدام اطلاعاتی، مانند تغییرات داده‌های خود را به سمت کاربر، جهت نمایش و یا محاسبات خاص خود ارسال کند. اگر حالت Web Socket میسر نباشد (توسط سرور یا کلاینت پشتیبانی نشود)، به حالت‌های دیگری مانند server events, forever frames, long polling سوئیچ خواهد کرد. اطلاعات بیشتر


روش چهارم: قرار دادن اطلاعات سمت سرور در کدهای HTML صفحه

روش متداول دیگری جهت تامین اطلاعات جاوا اسکریپتی سمت کاربر، قرار دادن آن‌ها در ویژگی‌های data-* ارائه شده در HTML5 است.
<ul>
@foreach (var product in products)
{
  <li id="product@product.Id" data-rank="@product.Rank">@product.Name</li>
}
</ul>
در اینجا لیستی از محصولات، به صورت متداولی از کنترلر دریافت گردیده و سپس ویژگی data-rank هر ردیف نمایش داده شده نیز توسط این اطلاعات سمت سرور مقدار دهی شده‌اند.
اکنون برای دسترسی به مقدار data-rank سطری مانند product1، در کدهای جاوا اسکریپتی صفحه می‌توان نوشت:
<script type="text/javascript">
var product1Rank = $("#product1").data("rank");
</script>
این روش برای قرار دادن اطلاعات ثابت اشیاء، روش مرسومی است.


روش پنجم: قرار دادن اطلاعات سمت سرور در کدهای جاوا اسکریپتی صفحه

این روش همانند روش چهارم است، با این تفاوت که اینبار اطلاعات مورد نیاز، مستقیما به یک متغیر جاوا اسکریپتی انتساب داده شده‌است:
 <script type="text/javascript">
var product1Name = "@product1.Name";
</script>
مزیت این روش، عدم ارسال درخواست اضافه‌تری به سرور برای دریافت اطلاعات مورد نیاز است. مقدار product1Name در همان بار اول رندر صفحه از سمت سرور، مشخص می‌گردد.


روش ششم: انتساب یک شیء دات نتی به یک متغیر جاوا اسکریپتی

این روش همانند روش پنجم است، با این تفاوت که اینبار قصد داریم بجای یک مقدار ثابت رشته‌ای یا عددی، برای مثال، آرایه‌ای از اشیاء را به یک متغیر جاوا اسکریپتی انتساب دهیم. در اینجا ابتدا اطلاعات مورد نظر را به فرمت JSON تبدیل می‌کنیم:
//سمت سرور
[HttpGet]
public ActionResult Index()
{
    var array = new[]
    {
        "Afghanistan",
        "Albania",
        "Algeria",
        "American Samoa",
        "Andorra",
        "Angola",
        "Anguilla",
        "Antarctica",
        "Antigua and/or Barbuda"
     };
     ViewBag.JsonString = new JavaScriptSerializer().Serialize(array);
     return View();
}
سپس توسط متد Html.Raw می‌توان این رشته‌ی JSON را که اکنون حاوی آرایه جاوا اسکریپتی سمت سرور است، به یک متغیر جاوا اسکریپتی نسبت داد:
 //سمت کلاینت
<script type="text/javascript">
var jsonArray = @Html.Raw(@ViewBag.JsonString);
</script>
استفاده از Html.Raw در این حالت از این جهت ضروری است که اطلاعاتی مانند [] به صورت encode شده در سمت کاربر نمایش داده نشوند؛ چون Razor به صورت پیش فرض اطلاعات را Encode می‌کند.
و یا اینکار را به صورت خلاصه به شکل زیر نیز می‌توان در سمت کاربر انجام داد:
 <script type="text/javascript">
  var model = @Html.Raw(Json.Encode(Model));
  // your js code here
</script>
در اینجا کار تبدیل اطلاعات مدل به JSON، در همان سمت RazorView انجام شده‌است.
مطالب
جلوگیری از ورود همزمان کاربران با نام کاربری و رمز عبور یکسان
در اکثر برنامه‌های وب، کاربر قادر است با یک نام کاربری و رمز عبور در چند Session همزمان لاگین کند. ممکن است سیاست برخی مدیران محصول این باشد که جلوی این مورد را بگیرند تا به عنوان مثال کاربران را به جای استفاده‌ی همزمان از یک نام کاربری و رمز عبور، مجبور به خرید مجوز‌های بیشتری کنند. ASP.NET Identity به صورت پیش فرض این مورد را پشتیبانی نمی‌کند؛ اما به کمک استفاده از امکانات درونی آن می‌توان این پشتیبانی را اضافه کرد.
یکی از فیلد‌های جدول AspNetUsers فیلد SecurityStamp می‌باشد. SecurityStamp یک مقدار تصادفی است:


Security Stamp باید با هربار تغییر اطلاعات احراز هویت (مانند رمز عبور) و اختیارات کاربر(Role) تغییر کند. به عنوان مثال کاربری در چند مرورگر لاگین کرده و گزینه‌ی مرا به خاطر داشته باش را انتخاب کرده است. اگر این کاربر رمز عبورش از هر جایی عوض شود، باید لاگین او در همه‌ی Session‌ها غیر معتبر شود. این مورد با تغییر کردن SecurityStamp بعد از تغییر رمز عبور صورت می‌گیرد. ASP.NET مقدار SecurityStamp را در کوکی کاربر نگه می‌دارد و در بازه‌های زمانی، این مقدار را با مقدار درون دیتابیس مقایسه می‌کند و در صورت عدم برابری، کاربر را احراز هویت نمی‌کند. بازه‌ی زمانی این بررسی در متد ConfigureAuth قابل تنظیم است که در ادامه شرح داده خواهد شد.
صورت مساله یافتن راه حلی جهت جلوگیری از ورود همزمان چند کاربر با یک نام کاربری و رمز عبور به سیستم می‌باشد. یکی از راه‌حل هایی که در ابتدا به ذهن می‌آید استفاده از Session و نگهداری کاربران لاگین کرده در حافظه می‌باشد. پیاده سازی این راه حل می‌تواند به کمک یک کلاس Static صورت پذیرد، اما قسمت چالشی این موضوع این است که چه زمانی باید کاربر از لیست حذف گردد؟ اگر اتصال کاربر قطع شود چه عملی باید صورت گیرد؟
راه حل دیگر استفاده از SecurityStamp هست؛ به این صورت که با هربار لاگین کاربر این مقدار تصادفی به‌روز گردد و ASP.NET Identity به گونه‌ای تنظیم شود که با هر درخواست HTTP، صحت SecurityStamp بررسی گردد. مقدار پیش فرض بازه‌ی زمانی بررسی، هر 30 دقیقه یک بار است.
در مثال‌های رسمی ASP.NET Identity لاگین به صورت ذیل پیاده سازی شده است:
   
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {
                case SignInStatus.Success:
                    return RedirectToLocal(returnUrl);
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
        }
این کد را باید به گونه‌ای تغییر داد که اگر نام کاربری و رمز عبور معتبر بودند، مقدار SeucrityStamp به‌روز گردد. به همین منظور قبل از فراخوانی PasswordSignInAsync کد ذیل اضافه می‌گردد:
       var loggedinUser = await UserManager.FindAsync(model.Email, model.Password);
            if (loggedinUser != null)
            {
                await UserManager.UpdateSecurityStampAsync(loggedinUser.Id);
            }
همانطور که مشاهده می‌شود، جهت بروز رسانی SecurityStamp از سازوکار درونی ASP.NET Identity، در واقع متد UpdateSecurityStampAsync بهره گرفته شده است.
اکنون باید تنظیمات پیش‌فرض بازه‌ی زمانی بررسی صحت SecurityStamp تغییر داده شود. این تنظیمات در فایل Startup.Auth.cs در پوشه‌ی App_Start قرار دارند:
     app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.  
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });
در کد بالا OnValidateIdentity باید مقدار ذیل را بگیرد:
OnValidateIdentity =
                        SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                            TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero
                            (manager, user) => user.GenerateUserIdentityAsync(manager))
اکنون Framework موظف است در هر درخواست HTTP، صحت SeucirtyStamp را بررسی کند. بنابراین اگر کاربری در سیستم لاگین باشد و کاربر دیگری با همان نام کاربری و رمز عبور لاگین کند، کاربر اول از سیستم لاگ اوت می‌شود؛ چرا که مقدار SecurityStamp او دیگر معتبر نیست. باید در نظر گرفته شود این عمل در سیستم‌های با تعداد کاربر زیاد باعث افزایش درخواست‌های دیتابیس می‌شود. 
اکنون جهت تست، اگر با مرورگر اول در سیستم لاگین صورت گیرد، سپس با همان اطلاعات در مرورگری دیگر، لاگین صورت گیرد، کاربر اول پس از درخواست بعدی، از سیستم لاگ اوت می‌شود. در مثال انتهای مطلب، صفحه‌ی About به صورت غیر عمومی درآمده که می‌توان بررسی راه حل جاری را در آن صفحه صورت داد. 
اگر بخواهیم لاگ اوت شدن کاربر را آنی کنیم، می‌توان در فواصل زمانی مشخصی، یک درخواست Ajax از سمت کلاینت به سرور ارسال کرد و تصدیق هویت کاربر را بررسی کرد:
        window.setInterval(function() {
            $.ajax({
                url:,
                type: "Post",
                dataType: "json",
                success: function(data) {
                    if (!data) {
                        alert("Someone has logged in using your username and password!");
                        location.reload();
                    }
                }
            });
        }, 60000);
در کد بالا به کمک window.setInterval، هر یک دقیقه یک بار لاگین بودن کاربر بررسی می‌گردد و در صورت لاگین نبودن، پیغام لازم به کاربر نمایش داده می‌شود. در نظر داشته باشید، این کد تنها باید در صفحات غیر عمومی قرار داده شود.
کد اکشن بررسی لاگین بودن به سادگی ذیل است:
        [AllowAnonymous]
        public virtual ActionResult Authenticated()
        {
            return Json(User.Identity.IsAuthenticated);
        }

نکته‌ی مهم این است که خصیصه‌ی AllowAnonymous بالای اکشن قرار گرفته باشد تا در صورتیکه Controller به صورت عمومی در دسترس نیست، اکشن همیشه و حتی وقتی کاربر لاگ اوت شده در دسترس باشد. در مثال انتهای مطلب صفحه‌ی About تنها در اختیار کاربران احراز هویت شده قرار گرفته است، بنابراین اگر دو کاربر با اطلاعات یکسانی به سیستم لاگین کنند، کاربر اول پیغام خطای ذیل را گرفته و به صفحه‌ی لاگین می‌رود. این کد در صفحه‌ی About در مثال انتهای مطلب قرار گرفته است:

مطالب
Asp.Net Identity #2
پیشتر در اینجا در مورد تاریخچه‌ی سیستم Identity مطالبی را عنوان کردیم. در این مقاله می‌خواهیم نحوه‌ی برپایی سیستم Identity را بحث کنیم.
ASP.NET Identity مانند ASP.NET Membership به اسکیمای SQL Server وابسته نیست؛ اما Relational Storage همچنان واحد ذخیره سازی پیش فرض و آسانی می‌باشد. بدین جهت که تقریبا بین همه‌ی توسعه دهندگان جا افتاده است. ما در این نوشتار از LocalDB جهت ذخیره سازی جداول استفاده می‌کنیم. ذکر این نکته ضروری است که سیستم Identity از Entity Framework Code First جهت ساخت اسکیما استفاده می‌کند.
پیش از هر چیز، ابتدا یک پروژه‌ی وب را ایجاد کنید؛ مانند شکل زیر:

در مرحله‌ی بعد سه پکیج زیر را باید نصب کنیم :

- Microsoft.AspNet.Identity.EntityFramework

-   Microsoft.AspNet.Identity.OWIN

-  Microsoft.Owin.Host.SystemWeb

بعد از اینکه پکیج‌های بالا را نصب کردیم، باید فایل Web.config را بروز کنیم. اولین مورد، تعریف یک Connection String می‌باشد:
<connectionStrings>
 <add name="IdentityDb" 
      providerName="System.Data.SqlClient"
      connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/>
 </connectionStrings>
بعد از آن، تعریف یک کلید در قسمت AppSettings تحت عنوان Owin:AppStartup است:
<appSettings>
 <add key="webpages:Version" value="3.0.0.0" />
 <add key="webpages:Enabled" value="false" />
 <add key="ClientValidationEnabled" value="true" />
 <add key="UnobtrusiveJavaScriptEnabled" value="true" />
 <add key="owin:AppStartup" value="Users.IdentityConfig" />
 </appSettings>
Owin مدل شروع برنامه (Application Startup Model) خودش را تعریف می‌کند که از کلاس کلی برنامه (منظور Global.asax) جداست. AppSetting تعریف شده با نام owin:Startup شناخته می‌شود و مشخص کننده کلاسی است که Owin وهله سازی خواهد کرد. وقتی برنامه شروع به کار می‌کند، تنظیمات خودش را از این کلاس دریافت خواهد کرد (در این نوشتار کلاس IdentityConfig می‌باشد).

ساخت کلاس User
مرحله‌ی بعد ساخت کلاس User می‌باشد. این کلاس بیانگر یک کاربر می‌باشد. کلاس User باید از کلاس IdentityUser ارث بری کند که این کلاس در فضای نام Microsoft.AspNet.Identity.EntityFramework قرار دارد. کلاس IdentityUser فراهم کننده‌ی یک کاربر عمومی و ابتدایی است. اگر بخواهیم اطلاعات اضافی مربوط به کاربر را ذخیره کنیم باید آنها در کلاسی که از کلاس IdentityUser ارث بری می‌کند قرار دهیم. کلاس ما در اینجا AppUser نام دارد.
using System;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models 
{
   public class AppUser : IdentityUser 
  {
      // پروپرتی‌های اضافی در اینجا
  }
}

ساخت کلاس Database Context برنامه
مرحله‌ی بعد ساخت کلاس DbContext برنامه می‌باشد که بر روی کلاس AppUser عمل میکند. کلاس Context برنامه که ما در اینجا آن را AppIdentityDbContext تعریف کرده‌ایم، از کلاس <IdentityDbContext<T ارث بری می‌کند که T همان کلاس User می‌باشد (در مثال ما AppUser) .
using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models;
namespace Users.Infrastructure {
 public class AppIdentityDbContext : IdentityDbContext<AppUser> 
{
   public AppIdentityDbContext() 
              : base("IdentityDb") { }

  static AppIdentityDbContext() 
 {
    Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());
  }
 public static AppIdentityDbContext Create() {
 return new AppIdentityDbContext();
 }
 }
public class IdentityDbInit
 : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> {
 protected override void Seed(AppIdentityDbContext context) {
 PerformInitialSetup(context);
 base.Seed(context);
 }
 public void PerformInitialSetup(AppIdentityDbContext context) {
 // initial configuration will go here
 }
 }
}

ساخت کلاس User Manager
یکی از مهمترین کلاسهای Identity کلاس User Manager (مدیر کاربر) می‌باشد که نمونه‌هایی از کلاس User را مدیریت می‌کند. کلاسی را که تعریف می‌کنیم، باید از کلاس <UserManager<T ارث بری کند که T همان کلاس User می‌باشد. کلاس UserManager خاص EF نمی‌باشد و ویژگی‌های عمومی بیشتری برای ساخت و انجام عملیات بر روی داده‌های کاربر را فراهم می‌نماید.

کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، می‌بینید که تمامی متدهای بالا به کلمه‌ی Async ختم می‌شوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیت‌ها را بلوکه نکنند.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;

namespace Users.Infrastructure 
{
 public class AppUserManager : UserManager<AppUser> 
{

 public AppUserManager(IUserStore<AppUser> store)
 : base(store) {
 }
 public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) 
{
 AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
 AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
 return manager;
 }
 }
}

زمانی که Identity نیاز به وهله‌ای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق می‌افتد که عملیاتی بر روی داده‌های کاربر انجام گیرد. برای ساخت وهله‌ای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T  توسط  EF میباشد که وظیفه‌ی آن فراهم کننده‌ی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) می‌باشد. برای ساخت <UserStore<T نیاز به وهله‌ای از کلاس AppIdentityDbContext می‌باشد که از طریق Owin به صورت زیر قابل دریافت است:

AppIdentityDbContext db = context.Get<AppIdentityDbContext>();

یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده می‌شود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شده‌ی در کلاس شروع Owin می‌نماید.


ساخت کلاس شروع Owin

اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:

<add key="owin:AppStartup" value="Users.IdentityConfig" />

قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامه‌ی شماست و IdentityConfig بیانگر کلاس شروع می‌باشد. (البته شما می‌توانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف می‌کنید یکی باشد)

Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، می‌باشد. به طور پیش فرض این کلاس Start نام دارد و در پوشه‌ی App_Start تعریف می‌شود. این کلاس حاوی یک متد به نام Configuration می‌باشد که بوسیله زیرساخت Owin فراخوانی می‌شود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده می‌شود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure;
namespace Users 
{
 public class IdentityConfig 
{
 public void Configuration(IAppBuilder app) 
{
 app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
 app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
 app.UseCookieAuthentication(new CookieAuthenticationOptions {
 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
 LoginPath = new PathString("/Account/Login"),
 });
 }
 }
}

رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شده‌اند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهله‌ای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین می‌کند که هر درخواست، به یک داده‌ی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity می‌گوید که چگونه از کوکی‌ها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص می‌شود. مهمترین قسمت در اینجا پروپرتی LoginPath می‌باشد و مشخص می‌کند که کلاینت‌های تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده می‌شود.

خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفاده‌ی از این سیستم خواهیم پرداخت.

مطالب
آموزش MDX Query - قسمت اول

در طول این سری آموزش‌های MDX (البته هنوز نمی‌دانم چند قسمت خواهد بود) تلاش خواهم کرد تمامی موارد موجود در MDX‌ها را به طور کامل با شرح و توضیح مناسب پوشش دهم.

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

در قسمت اول این آموزش‌ها در نظر دارم در ابتدا مفاهیم اولیه OLAP و همچنین مفاهیم مورد نیاز در Multi Dimentional Data Base  ها برای شما عزیزان توضیح دهم و در قسمت‌های بعدی این مجموعه در خصوص MDX Query‌ها صحبت خواهم کرد.

انباره داده (Data Warehouse)

عملا یک یا چند پایگاه داده می‌باشد که اطلاعات تجمیع شده از دیگر پایگاه‌های داده را درخود نگه داری می‌کند. برای ارایه گزارشاتی که از پایگاه داده‌های OLTP نمی‌توانیم به راحتی بگیریم.

(OLTP (Online transaction processing

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

(OLAP (OnLine Analysis Processing 

این سیستم‌ها خدماتی در نقش تحلیل‌گر داده و تصمیم گیرنده ارائه می‌‌کند. چنین سیستمهایی می‌‌توانند، داده را در قالبهای مختلف برای هماهنگ کردن نیازهای مختلف کاربران مختلف، سازماندهی کنند.

تفاوت انبار داده (Data Warehouse) و پایگاه داده(Data Base)

وظیفه اصلی سیستم‌های پایگاه‌داده کاربردی OnLine ،پشتیبانی از تراکنش‌های بر‌خط و  پردازش کوئری است. این سیستم‌ها، سیستم پردازش تراکنش بر‌خط(OLTP)  نامیده می‌شوند و بیشتر عملیات روزمره یک سازمان را پوشش می‌‌دهند. از سوی دیگر انبار‌داده، خدماتی در نقش تحلیل‌گر داده و تصمیم گیرنده ارائه می‌‌کند. چنین سیستمهایی می‌‌توانند داده را در قالبهای مختلف برای هماهنگ کردن نیازهای مختلف کاربران مختلف، سازماندهی و ارائه می‌کند. این سیستم‌ها با نام سیستم‌های پردازش تحلیلی بر‌خط (OLAP) شناخته‌می‌شوند.

موارد تفاوت انبار داده (Data Warehouse) و پایگاه داده(Data Base)

• از لحاظ مدل‌های داده: پایگاه‌های داده برای مدل  OLTP بهینه سازی شده‌است. که بر اساس مدل داده رابطه‌ای امکان پردازش تعداد زیادی تراکنش همروند، که اغلب حاوی رکورد‌های اندکی هستند را دارد. اما در انبارهای داده که برای پردازش تحلیلی بر خط، طراحی شده‌اند امکان پردازش تعداد کمی کوئری پیچیده بر روی تعداد بسیار زیادی رکورد داده فراهم می‌شود. سرورهای OLAP می‌توانند از دو نوع رابطه‌ای  (ROLAP)  یا چند‌بعدی باشند (MOLAP).
• از لحاظ کاربران: کاربران پایگاه‌داده کارمندان دفتری و مسؤولان هستند در حالی‌که کاربران انبار‌داده مدیران و تصمیم‌گیرنده‌ها هستند.
• از لحاظ عملیات قابل اجرا بر روی آن‌ها: عملیات انجام شده برروی پایگاه‌های داده عمدتا عملیات (Select/Insert/Update/Delete) می‌باشد ، در حالی که عملیات روی انبار داده عمدتا Select  ها می‌باشند.
• از لحاظ مقدار داده‌ها: مقدار داده‌های یک پایگاه‌داده در حدود چند مگابایت تا چند گیگابایت است در حالی که این مقدار در انبار داده در حدود چند گیگابایت تا چند ترابایت است.
• از لحاظ زمان پرس و جو : به طور کلی سرعت پرس و جو  ها روی انباره‌ی داده بسیار بالاتر از کوئری مشابه آن روی پایگاه داده می‌باشد.
مراحل ساخت یک انباره‌ی داده (Data WareHouse) به شرح زیر می‌باشد 



• پاکسازی داده (Data Cleansing)

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

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

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

در این بخش عملیات مختلفی برای پاکسازی داده‌ها  قابل انجام است:

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

یکپارچه سازی داده‌ها از سه فاز کلی تشکیل شده است:
• شناسایی فیلدهای یکسان: فیلدهای یکسان که در جدول‌ها ی مختلف دارای نامهای مختلف میباشند. 

• شناسایی افزونگی‌ها ی موجود در داده‌ها ی ورودی:  داده‌های ورودی گاهی دارای افزونگی است. مثلا بخشی از رکورد در جداول مختلف وجود دارد.

• مشخص کردن برخورد‌های داده ای: مثالی از  برخوردهای داده ای یکسان نبودن واحدهای نمایش داده ای است. مثلا فیلد وزن در یک جدول بر حسب کیلوگرم و در جدولی دیگر بر حسب گرم ذخیره شده است.


• تبدیل داده‌ها(Data Transformation)
در این فاز، داده‌های ورودی طی مراحل زیر به شکلی که مناسب عمل داده کاوی باشند، در می‌آیند:
• از بین بردن نویز داده¬ها(Smoothing)
• تجمیع داده¬ها(Aggregation)
• کلی¬سازی(Generalization)
• نرمال¬سازی(Normalization)
• افزودن فیلدهای جدید
در ادامه به شرح  هر یک می‌پردازیم:
1. از بین بردن نویزهای داده ای(Smoothing): منظور از  داده‌های نویزی، داده هایی هستند که در خارج از بازه مورد نظر قرار می‌گیرند. مثلا اگر بازه حقوقی کارمندان بین یک صد هزار تومان و یک میلیون تومان باشد، داده‌های خارج از این بازه به عنوان داده‌های نویزی شناخته شده و در این مرحله اصلاح می‌گردند. برای اصلاح داده‌های نویزی از روشهای زیر استفاده می‌شود:
• استفاده از مقادیر مجاور برای تعیین یک مقدار مناسب برای فیلدهای دارای نویز
• دسته بندی داده‌های موجود و مقداردهی فیلد دارای داده نویزی با استفاده از دسته نزدیکتر
• ترکیب روشهای فوق با ملاحظات انسانی، در این روش، اصلاح مقادیر نویزی با استفاده از یکی از روشهای فوق انجام می‌گیرد اما افرادی برای بررسی و اصلاح نیز وجود دارند
2. تجمیع داده ها(Aggregation): تجمیع داده‌ها به معنی بدست آوردن اطلاعات جدید از ترکیب داده‌های موجود می‌باشد. به عنوان مثال بدست فروش ماهانه از حساب فروش‌های روزانه.
3. کلی سازی(Generalization): کلی سازی به معنی دسته بندی داده‌های موجود براساس ماهیت و نوع آنها است. به عنوان مثال می‌توان اطلاع رده‌های سنی خاص (جوان، بزرگسال، سالخورده) را از فیلد سن استخراج کرد. 
4. نرمال سازی(Normalization): منظور از نرمال سازی، تغییر مقیاس داده‌ها است. به عنوان مثالی از نرمال سازی، می‌توان به تغییر بازه یک فیلد از مقادیر موجود به بازه 0 تا 1 اشاره کرد.

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

• کاهش داده‌ها(Reduction)

در این مرحله، عملیات کاهش داده‌ها انجام می‌گیرد که شامل تکنیکهایی برای نمایش کمینه اطلاعات موجود است

. این فاز از سه بخش  تشکیل می‌شود:

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

• فشرده سازی داده ها: از تکنیکهای فشرده سازی برای کاهش اندازه داده‌ها استفاده می‌شود.
• کدکردن داده ها: داده‌ها در صورت امکان با پارامترها و اطلاعات کوچکتر جایگزین می‌شوند.

مدل داده‌ای رابطه‌ای (Relational) وچند بعدی (Multidimensional)  :

1. مدل داده رابطه‌ای (Relational data modeling)  بر اساس دو مفهوم اساسی موجودیت (entity)  و رابطه (relation) بنا نهاده شده است. از این رو آن را با نام مدل ER نیز می‌شناسند.

• موجودیت (entity) : نمایانگر همه چیزهایی که در پایگاه داده وجود خارجی دارند یا به تصور در می‌آیند. پدیده‌ها دارای مشخصاتی هستندکه به آن‌ها صفت (attribute) گفته می‌شود.

• رابطه (relation) : پدیده‌ها را به هم می‌پیوندد و چگونگی در ارتباط قرار گرفتن آن‌ها با یکدیگر را مشخص می‌کند.

2. مدل داده چند‌بعدی ( Multidimensional modeling ) یا MD بر پایه دو ساختار جدولی اصلی بنا نهاده شده است: 



• جدول حقایق (Fact Table)

• جداول ابعاد (Dimension Table)


این ساختار امکان داشتن یک نگرش مدیریتی و تصمیم‌گیری به داده‌های موجود در پایگاه داده را تسهیل می‌کند. 

جدول حقایق : قلب حجم داده‌ای ما را تشکیل می‌دهد و شامل دو سری فیلد است : کلیدهای خارجی به ابعاد و شاخص‌ها (Measure). 

شاخص‌ها (Measure) : معیارهایی هستند که بر روی آن‌ها تحلیل انجام می‌گیرد و درون جدول حقایق قرار دارند. شاخص‌ها قبل از شکل‌گیری انبار داده توسط مدیران و تحلیل‌گران به دقت مشخص می‌‌شوند. چون در مرحله کار با انبار اطلاعات اساسی هر تحلیل بر اساس همین شاخص‌ها شکل می‌گیرد. شاخص‌‌ها تقریباً همیشه مقادیر عددی را شامل می‌شوند. مثلا برای یک فروشگاه زنجیره‌ای این شاخص‌ها می‌توانند واحدهای فروخته‌شده کالاها و مبلغ فروش به تومان باشند.

بعد (Dimension) : هر موجودیت در این مدل می‌تواند با یک بعد تعریف شود. ولی بعدها با موجودیت‌های مدل ER متفاوتند زیرا آن‌ها سازمان شاخص‌ها را تعیین می‌کنند. علاوه بر این دارای یک ساختار سلسله مراتبی هستند و به طور کلی برای حمایت از سیستم‌های تصمیم گیری سازمان‌دهی شده‌اند.

اجزای بعدها member نام دارند و تقریباٌ همه بعدها، memberهای خود را در یک یا چند سطح سلسله مراتبی (hierarchies) سازمان‌دهی می‌نمایند، که این سلسله مراتب نمایانگر مسیر تجمیع (integration) و ارتباط بین سطوح پایین‌تر (مثل روز) و سطوح بالاتر (مثل ماه و سال) است. وقتی یک دسته از memberهای خاص با هم مفهوم جدیدی را ایجاد می‌‌کنند، به آنها یک سطح (Level) می‌گوییم. ( مثلاٌ هر سی روز را ماه می‌‌گوییم. در این حالت ماه یک سطح است. ) 

حجم‌های داده‌ای (Data Cube)

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

     

شماهای داده‌ای (Data Schema)  : سه نوع Schema در طراحی Data Warehouse وجود دارد 

1. Stare
2. Snowflake
3. Galaxy
1. شمای ستاره‌ای (Star Schema) : متداولترین شما، همین شمای‌ستاره‌ای است. که در آن انبار‌داده با استفاده از اجزای زیر تعریف می‌شود:
• یک جدول مرکزی بزرگ به نام جدول حقایق که شامل حجم زیادی از داده‌های بدون تکرار است.

• مجموعه‌ای از جدول‌های کمکی کوچک‌تر به نام‏ جدول بعد ، که به ازای هر بعد یکی از این جداول موجود خواهد بود.

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

مشکل این مدل احتمال پیشامد افزونگی در آن است.

2. شمای دانه‌برفی ( Snowflake Schema ) : در واقع شمای دانه‌برفی، نوعی از شمای ستاره‌ای است که در آن بعضی از ‏ جداول بعد نرمال شده‌اند. و به همین خاطر دارای تقسیمات بیشتری به شکل جداول اضافی می‌باشد که از ‏ جداول بعد‏ جدا شده‌اند. 

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

3. شمای کهکشانی (galaxy schema) : در کاربرد‌های پیچیده برای به اشتراک گذاشتن ابعاد نیاز به جداول حقایق چندگانه احساس می‌شود که یک یا چند ‏ جدول بعد‏ را در بین خود به اشتراک می‌گذارند. این نوع شما به صورت مجموعه‌ای از شماهای ستاره‌ای است و به همین دلیل شمای کهکشان یا شمای منظومه‌ای نامیده‌می‌شود. این شما به ما این امکان را می‌دهد که جداول بعد بین جداول حقایق مختلف به اشتراک گذاشته شوند.

عملیات بر روی حجم‌های داده‌ای :

• Roll Up  (یا Drill-up) : با بالا رفتن در ساختار سلسله مراتبی مفهومی یک حجم داده‌ای، یا با کاهش دادن بعد، یک مجموعه با جزئیات کمتر (خلاصه شده) ایجاد می‌نماید. بالا رفتن در ساختار سلسله مراتبی به معنای حذف قسمتی از جزئیات است. برای مثال اگر قبلاٌ بعد مکان بر حسب شهر بوده آن را با بالا رفتن در ساختار سلسله مراتبی بر حسب کشور درمی‌آوریم. ولی وقتی با کاهش دادن بعد سروکار داریم منظور حذف یکی از ابعاد و جایگزین کردن مقادیر کل است. در واقع همان عمل تجمیع (aggregation) است. 
• Drill Down : بر عکس عملRoll-up است و از موقعیتی با جزئیات داده‌ای کم به جزئیات زیاد می‌رود. این کار با پایین آمدن در ساختار سلسله مراتبی( به سمت جزئیات بیشتر) یا با ایجاد ابعاد اضافی انجام می‌گیرد.

نمونه‌ای از عملیات Drill Down و Roll Up

• Slice : با انتخاب و اعمال شرط بر روی یکی از ابعاد یک subcube به شکل یک برش دو بعدی ایجاد می‌کند. در واقع همان عمل انتخاب (select) است.

• Dice : با انتخاب قسمتی از ساختار سلسله مراتبی بر روی دو یا چند بعد یک subcube ایجاد می‌نماید.

نمونه‌ای از عملیات Dice و Slice

• Pivot (یا Rotate) : این عملیات بردارهای بعد را در ظاهر می‌چرخاند.

نمونه‌ای از عملیات pivot

• Drill-across : نتیجه اجرای کوئری‌هایی که نتیجه اجرای آنها حجم‌های داده‌ایهای مرکب با بیش از یک fact-table است.

• Ranking : سلول‌هایی را باز می‌گرداند که در بالا یا پایین شرط خاصی واقع هستند. مثلاٌ ده محصولی که بهترین فروش را داشته‌اند.

سرورهای OLAP :

در تکنولوژیOALP داده‌ها به دو صورت چند‌بعدی (Multidimensional OLAP) (MOLAP) و رابطه‌ای (Relational OLAP) (ROLAP) ذخیره می‌شوند. OLAP پیوندی(HOLAP) تکنولوژیی است که دو نوع قبل را با هم ترکیب می‌کند.

MOLAP : روشی است که معمولاٌ برای تحلیل‌های OLAP در تجارت مورد استفاده قرار می‌گیرد. در MOLAP، داده‌ها با ساختار یک حجم داده‌ای ( Data Cube ) چند بعدی ذخیره می‌شوند. ذخیره‌سازی در پایگاه‌داده‌های رابطه‌ای انجام نمی‌گیرد، بلکه با یک فرمت خاص انجام می‌شود. اغلب محصولات موفق MOLAP از یک روش چند‌بعدی استفاده می‌نمایند که در آن یک سری حجم‌های داده‌ای کوچک، انبوه و از پیش محاسبه‌شده، یک حجم داده‌ای بزرگ (hypercube  ) را می‌سازند. 

علاوه بر‌این MOLAP به شما امکان می‌دهد داده‌های دیدهای (View) تحلیل‌گران را دسته بندی کنید، که این در حذف اشتباهات و برخورد با ترجمه‌های پرغلط کمک بزرگی است.

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

از آنجا که یک کپی از داده‌های منبع در کامپیوتر Analysis server ذخیره‌می‌شود، کوئری‌‌ها می‌توانند بدون مراجعه به منابع مجدداً محاسبه شوند. کامپیوتر Analysis server ممکن است کامپیوترسرور که تقسیم بندی‌ها در آن انجام شده یا کامپیوتر دیگری باشد. این امر بستگی به این دارد که تقسیم‌بندی‌ها در کجا تعریف شده‌اند. حتی اگر پاسخ کوئری‌ها از روی تقسیمات تجمیع (integration) شده قابل دستیابی نباشند، MOLAP سریع‌ترین پاسخ را فراهم می‌کند. سرعت انجام این کار به طراحی و درصد تجمیع تقسیم‌بندی‌ها بستگی دارد. 

مزایا : کارایی عالی-  حجم‌های داده‌ای MOLAP برای بازیابی سریع داده‌ها ساخته شده‌اند و در فعالیت‌های slice و dice به صورت بهینه پاسخ می‌دهند. ترکیب سادگی و سرعت مزیت اصلی MOLAP است.

در ضمنMOLAP  قابلیت محاسبه محاسبات پیچیده را فراهم می‌کند. همه محاسبات از پیش وقتی که حجم‌های داده‌ای ساخته می‌‌شود، ایجاد می‌شوند. بنابراین نه تنها محاسبات پیچیده انجام شدنی هستند بلکه بسیار سریع هم پاسخ می‌دهند.

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

ROLAP : محدودیت MOLAP در حجم داده‌های قابل پرس‌و‌جو و نیاز به روشی که از داده‌های ذخیره‌شده به روش رابطه‌ای حمایت کند، موجب پیشرفت ROLAP شد.

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

مزایا : با این روش می‌توان به حجم زیادی از داده‌ها را رسیدگی کرد. محدودیت حجم داده در تکنولوژی ROLAP مربوط به محدودیت حجم داده‌های قابل ذخیره‌سازی در پایگاه‌داده‌های رابطه‌ای است. به بیان دیگر، خود ROLAP هیچ محدودیتی بر روی حجم داده‌ها اعمال نمی‌کند.

معایب : ممکن است کارایی پایین بیاید. زیرا هر گزارش ROLAP در واقع یک کواِری SQL (یا چند کواِری SQL )در پایگاه داده‌های رابطه‌ای است و اگر حجم داده‌ها زیاد باشد ممکن است زمان پاسخ کواِری طولانی شود. در مجموع ROLAP سنگین است، نگهداری آن سخت است و کند هم هست. بخصوص زمانی که نیاز به آدرس دهی جدول‌های ذخیره شده در سیستم چند بعدی داریم.

این محدودیت ناشی از عملکرد SQL است. زیرا تکنولوژی ROLAP بر پایه عبارات مولد SQL برای پرسش و پاسخ بر روی پایگاه داده رابطه‌ای است و عبارات SQL به همه نیازها پاسخ نمی‌دهند (مثلاٌ محاسبه حساب‌های پیچیده در SQL مشکل است)، بنابراین فعالیت‌های ROLAP به آن چه SQL قادر به انجام آن است محدود می‌گردد. 

تفاوت ROALP و MOLAP : تفاوت اصلی این دو در معماری آن‌ها است. محصولات MOLAP داده‌های مورد نیاز را در یک حافظه نهان (cache) مخصوص می‌گذارد. ولی ROLAP تحلیل‌های خود را بدون استفاده از یک حافظه میانی انجام می‌دهد، بدون آن که از یک مرحله میانی برای گذاشتن داده‌ها در یک سرور خاص استفاده کند. 

با توجه به کند بودن ROLAP در مقایسه باMOLAP ، باید توجه داشت که کاربرد این روش بیشتر در پایگاه داده‌های بسیار بزرگی است که  گاه‌گاهی پرس و جویی بر روی آن‌ها شکل می‌گیرد، مثل داده‌های تاریخی و کمتر جدید سال‌‌های گذشته.

نکته: اگر از Analysis Services که به وسیله Microsoft OLE DB Provider مهیا شده استفاده می‌کنید، تجمیع‌ها نمی‌توانند برای تقسیم‌بندی از روش ROLAP استفاده نمایند.

HOLAP : با توجه به نیاز رو به رشدی که برای کارکردن با داده‌های بلادرنگ (real time) در بخش‌های مختلف در صنعت و تجارت احساس می‌شود، مدیران تجاری انتظار دارند بتوانند با دامنه وسیعی از اطلاعات که فوراً و بدون حتی لحظه‌ای تأخیر در دسترس باشند، کار کنند. در حال حاضر شبکه اینترنت و سایر کاربرد‌ها یی که به داده‌هایی از منابع مختلف مراجعه دارند و نیاز به فعالیت با یک سیستم بلادرنگ هم دارند، همگی از سیستم HOLAP بهره می‌گیرند.

named set :

Named Set مجموعه‌ای از memberهای بعد یا مجموعه‌ای از عبارات است که برای استفاده مجدد ایجاد می‌شود.

Calculated member 

Calculated Memberها memberهایی هستند که بر اساس داده‌ها نیستند بلکه بر اساس عبارات ارزیابی MDX هستند. آنها دقیقاَ به سبک سایر memberهای معمولی هستند. MDX یک مجموعه قوی از عملیاتی را تامین میکند که میتوانند برای ساختCalculated Memberها مورد استفاده قرار گیرند به طوری که به شما امکان داشتن انعطاف زیاد در کار کردن با داده‌های چند بعدی را بدهد. 

امیدوارم در این قسمت با مفاهیم نخستین OLAP آشنا شده باشید.

تلاش خواهم کرد در قسمت بعدی در خصوص نصب SQL Server Analysis Services و نصب پایگاه داده‌ی Adventure Work DW 2008 شرح کاملی را ارایه کنم.

 

نظرات مطالب
Blazor 5x - قسمت 33 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 3- بهبود تجربه‌ی کاربری عدم دسترسی‌ها
یا اگر نمونه‌ی AuthorizationHandler سفارشی آن‌را نیاز داشتید، به صورت زیر است:
- ابتدا یک IAuthorizationRequirement و AuthorizationHandler سفارشی را ایجاد می‌کنیم که در هندلر آن دسترسی کاملی به اطلاعات کاربر وارد شده‌ی به سیستم وجود دارد:
public class UserCanSeeProjectRequirement : IAuthorizationRequirement
{
    public UserCanSeeProjectRequirement() { }

}


public class UserCanSeeProjectHandler : AuthorizationHandler<UserCanSeeProjectRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                   UserCanSeeProjectRequirement requirement)
    {
        //claim-based validation
        if (context.User.HasClaim("permission.cansee", "CanSee"))
                context.Succeed(requirement);

        //role-based validation
        if (context.User.IsInRole("admin") || context.User.IsInRole("user"))
                context.Succeed(requirement);

        return Task.CompletedTask;
    }
}
سپس نیاز است این اطلاعات را به برنامه‌ی کلاینت معرفی کرد:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            // ...

            services.AddScoped<IAuthorizationHandler, UserCanSeeProjectHandler>();
            services.AddAuthorizationCore(options => {
                options.AddPolicy("UserCanSeeProjectPolicy", policy => policy.Requirements.Add(new UserCanSeeProjectRequirement()));
            });

            // ...
        }
    }
}
و در نهایت می‌توان Policy جدید فوق را که با نام UserCanSeeProjectPolicy معرفی شده، یا به صورت زیر در ابتدای یک صفحه:
@attribute [Authorize(Policy = "UserCanSeeProjectPolicy")]
و یا در قسمتی از آن صفحه استفاده کرد:
<AuthorizeView Policy="UserCanSeeProjectPolicy">
    <NotAuthorized>
       <h2 class="mt-5">You are not authorized to view this page</h2>
    </NotAuthorized>
    <Authorized>
      <div class="container my-profile">
        --- Place here all the content you want your user to view ----
      </div>
    </Authorized>
</AuthorizeView>
نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
برنامه‌های هاست شده‌ی در IIS، از لحاظ خواندن و نوشتن فایل‌ها، تفاوتی با سایر برنامه‌های ویندوزی ندارند. هر برنامه‌ی ویندوزی بر اساس سطوح دسترسی کاربری که تحت مجوز آن اجرا می‌شود، یکسری از دسترسی‌ها را پیدا می‌کند، یا خیر. برای مثال به صورت پیش‌فرض کاربری که برنامه‌های ASP.NET، بر اساس آن اجرا می‌شوند، همان کاربر Application pool آن‌ها است و این کاربر هیچ نوع دسترسی نوشتنی را در هیچکدام از پوشه‌های سیستم، ندارد. به همین جهت حتی اگر سعی کنید در پوشه‌ی خود برنامه هم فایلی را آپلود کنید، با خطای access is denied متوقف خواهید شد. همان کاری را که برای دادن دسترسی نوشتن، به کاربر Application pool برنامه‌ی خود جهت آپلود فایل‌های فعلی انجام داده‌اید، باید برای هر پوشه‌ی دیگری که مدنظر شما است نیز انجام دهید و از این لحاظ تفاوتی نمی‌کند (مهم نیست که در این درایو باشد یا در آن درایو؛ مهم دسترسی نوشتن است):
Right click on the folder -> Properties -> Security tab -> Click at Edit button ->
Enter `IIS AppPool\DefaultAppPool` user (IIS AppPool\<app_pool_name>) -> Click at Check names -> OK ->
Then give it `write` or other permissions.