نظرات مطالب
استفاده از چند فرم در کنار هم در ASP.NET MVC
به نظرتون راه حل چیست؟
در مثال این اموزش, بعد از پایان یافتن عملیات بصورت موفق, فقط "پیام با موفقیت ثبت شد" نمایش داده می‌شه , ولی بنده می‌خواهم بعد از پایان صحیح عملیات اکشن دیگه ای فعال بشه , مثلا بعد از ثبت نظر مربوط به یک خبر در سایت , خبر مورد نظر دوباره نمایش داده بشه تا نظر جدید لود بشه.
ولی این اتفاق با خطای مذکور همراه,  حتی به صورت دستی هم صفحه را reload می‌کنم عملیات قبلی دوباره انجام می‌گیره(پی اپی نظر قبلی ثبت می‌شود)
شما فرمودید هنوز وسط کاره,حق با شماست, ایا راه حلی هست؟ یا باید از این شیوه برای اینجور کارها استفاده نکنم؟ در صورت عدم استفاده از این روش پیشنهادتون چیست؟
مطالب
OpenCVSharp #8
بررسی morphology (ریخت شناسی) تصاویر

به تصویر زیر دقت کنید:




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


متدهایی که مورفولوژی تصاویر را تغییر می‌دهند

در OpenCV سه متد یا فیلتر مهم، کار تغییر مورفولوژی تصاویر را انجام می‌دهند:
1) Cv2.Erode
تحلیل/فرسایش یا erosion سبب می‌شود تا نواحی تیره‌ی تصویر «رشد» کنند.


در اینجا فیلتر Erode کار یکی کردن اجزای جدای توپ‌های رنگی را انجام داده‌است.


2) Cv2.Dilate
اتساع یا dilation سبب خواهد شد تا نواحی روشن تصویر «رشد» کنند.


بکارگیری فیلتر Dilate سبب شده‌‌است تا نویزهای تصویر محو شوند و اشیاء به هم پیوسته از هم جدا گردند.


3) Cv2.MorphologyEx
کار این متد انجام اعمال پیشرفته‌ی مورفولوژی بر روی تصاویر است و در اینجا ترکیبی از erosion و dilation، با هم انجام می‌شوند.
اگر پارامتر سوم آن به MorphologyOperation.Open تنظیم شود، ابتدا erosion و سپس dilation انجام خواهد شد:


و اگر این پارامتر به MorphologyOperation.Close مقدار دهی شود، ابتدا dilation و سپس erosion انجام می‌شود:


در تمام این حالات، پارامتر آخر که Structuring Element نام دارد، یکی از مقادیر اشیاء مستطیل، به علاوه و بیضی را می‌تواند داشته باشد. این اشیاء و اندازه‌ی آن‌ها، مشخص کننده‌ی میزان تحلیل و یا اتساع نهایی هستند.



استفاده از متدهای مورفولوژی در عمل
 
در اینجا مثالی را از نحوه‌ی بکارگیری متدهای اتساع و فرسایش، ملاحظه می‌کنید:
using (var src = new Mat(@"..\..\Images\cvmorph.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
using (var dst = new Mat())
{
    src.CopyTo(dst);
 
    var elementShape = StructuringElementShape.Rect;
    var maxIterations = 10; 

    var erodeDilateWindow = new Window("Erode/Dilate", image: dst);
    var erodeDilateTrackbar = erodeDilateWindow.CreateTrackbar(
        name: "Iterations", value: 0, max: maxIterations * 2 + 1,
        callback: pos =>
        {
            var n = pos - maxIterations;
            var an = n > 0 ? n : -n;
            var element = Cv2.GetStructuringElement(
                    elementShape,
                    new Size(an * 2 + 1, an * 2 + 1),
                    new Point(an, an));
            if (n < 0)
            {
                Cv2.Erode(src, dst, element);
            }
            else
            {
                Cv2.Dilate(src, dst, element);
            }
 
            Cv2.PutText(dst, (n < 0) ?
                string.Format("Erode[{0}]", elementShape) :
                string.Format("Dilate[{0}]", elementShape),
                new Point(10, 15), FontFace.HersheyPlain, 1, Scalar.Black);
            erodeDilateWindow.Image = dst;
        });  
 
    Cv2.WaitKey(); 
    erodeDilateWindow.Dispose();
}
اینترفیس به کار گرفته شده، همان C++ API است و در اینجا ابتدا یک تصویر و کپی آن تهیه می‌شوند. سپس پنجره‌ی سازگار با C++ API ایجاد شده و به این پنجره یک شیء tracker اضافه می‌شود. این tracker یا slider، چندسکویی است. بدیهی است اگر قرار بود چنین کاری را صرفا با یک برنامه‌ی دات نتی انجام داد، می‌شد قسمت ایجاد پنجره و tracker آن‌را حذف کرد و بجای آن‌ها از یک picture box و یک slider به همراه مدیریت روال رخدادگردان تغییر مقادیر slider کمک گرفت. اما در اینجا تنها جهت آشنایی با این امکانات توکار OpenCV، از همان متدهای native آن استفاده شده‌است. در این مثال، روال رخدادگردان تغییر مقادیر tracker یا slider، یک function pointer است که معادل آن در دات نت یک delegate می‌باشد که در پارامتر callback متد ایجاد tracker قابل مشاهده است. هر بار که مقدار tracker تغییر می‌کند، مقدار pos را به callback خود ارسال خواهد کرد. از این مقدار جهت ایجاد شیء ساختاری و همچنین انتخاب بین حالات اتساع و فرسایش، کمک گرفته شده‌است.
کار متد PutText، نوشتن یک متن ساده بر روی پنجره‌ی native مربوط به OpenCV است.

همچنین در ادامه کدهای بکارگیری متد MorphologyEx را که کار ترکیب اتساع و فرسایش را با هم انجام می‌دهد، ذکر شده‌است و نکات بکارگیری آن همانند مثال اول بحث است:
    using (var src = new Mat(@"..\..\Images\cvmorph.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
    using (var dst = new Mat())
    {
        src.CopyTo(dst);
 
        var elementShape = StructuringElementShape.Rect;
        var maxIterations = 10;
 
        var openCloseWindow = new Window("Open/Close", image: dst);
        var openCloseTrackbar = openCloseWindow.CreateTrackbar(
            name: "Iterations", value: 0, max: maxIterations * 2 + 1,
            callback: pos =>
            {
                var n = pos - maxIterations;
                var an = n > 0 ? n : -n;
                var element = Cv2.GetStructuringElement(
                        elementShape,
                        new Size(an * 2 + 1, an * 2 + 1),
                        new Point(an, an));
 
                if (n < 0)
                {
                    Cv2.MorphologyEx(src, dst, MorphologyOperation.Open, element);
                }
                else
                {
                    Cv2.MorphologyEx(src, dst, MorphologyOperation.Close, element);
                }
 
                Cv2.PutText(dst, (n < 0) ?
                    string.Format("Open/Erosion followed by Dilation[{0}]", elementShape)
                    : string.Format("Close/Dilation followed by Erosion[{0}]", elementShape),
                    new Point(10, 15), FontFace.HersheyPlain, 1, Scalar.Black);
                openCloseWindow.Image = dst;
            }); 

        Cv2.WaitKey();  
        openCloseWindow.Dispose();
    }

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
مروری بر چند تجربه‌ی کاری با SQLite




SQLite بانک اطلاعاتی سریع، کم حجم و سورس بازی است که استفاده از آن در دات نت فریم ورک بسیار ساده است. فقط کافی است پروایدر مربوط به آن را دریافت کنید و در کدهای قدیمی خود هر جایی مثلا sqlconnection داشتید آن‌را تبدیل به sqliteconnection کنید و امثال آن (به بیان دیگر، پروایدر تهیه شده از معماری ADO.NET پیروی می‌کند و عملا دانش قبلی شما به سادگی قابل استفاده و ارتقاء است). علاوه بر آن پروایدر ADO.NET‌ تهیه شده برای آن، پشتیبانی از Entity framework را هم ارائه می‌دهد.
این دیتابیس تحت سیستم عامل‌های مختلف مهیا است و مهم‌ترین مزیت آن عدم نیاز به نصب آن می‌باشد.

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


ملاحظات:
الف) مرتب سازی SQLite حساس به حروف کوچک و بزرگ است.
برای برگشت به عادت متداولی که وجود دارد می‌شود به صورت زیر عمل کرد:
select f1 from tbl1 order by f1 COLLATE NOCASE
یک COLLATE NOCASE اضافه شده است.

ب) رعایت نکات مرتبط با سیستم‌های 64 بیتی
در مورد سیستم‌های 64 بیتی و دات نت قبلا مطلبی را نوشته بودم : {+}. این مطلب دقیقا اینجا کاربرد پیدا می‌کند، از این لحاظ که SQLite یک بانک اطلاعاتی Native است. اگر برنامه‌ی دات نت شما برای حالت Any CPU تهیه شده است، در سیستم‌های 32 بیتی نیاز است تا DLL مرتبط SQLite را توزیع کنید و در سیستم‌های 64 بیتی DLL مرتبط 64 بیتی آن نیاز خواهد بود. در غیراینصورت برنامه‌ی شما در بدو امر کرش کرده و اجرا نخواهد شد.

مشکلات:
الف) کلید خارجی بی خاصیت!
SQLite از کلید خارجی پشتیبانی می‌کند اما آن‌را اعمال نمی‌کند! برای اینکه کلید خارجی را اعمال کنید باید خودتان تریگر بنویسید تا این‌کار را انجام دهد.

ب) پشتیبانی در حد صفر از مباحث همزمانی و تردینگ.
اگر برنامه شما مالتی ترد است، در بد مخمصه‌ای گرفتار شده‌اید. مدام با پیغام database is locked مواجه خواهید شد. (چه انتظاری داشتید؟ یک dll کمتر از 2 مگابایت که قرار نیست کار غول‌های دیتابیسی را انجام دهد)
بنابراین اصلا تصورش را هم نکنید که از این دیتابیس به عنوان بانک اطلاعاتی یک سایت (و محیط‌های چند کاربره) بتوان استفاده کرد و کاربران دچار مشکل نشوند.

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

د) امنیت
روی بانک‌های اطلاعاتی اکسس حداقل می‌توان یک کلمه‌ی عبور را قرار داد (که در کسری از ثانیه قابل شکستن است!). در SQLite استاندارد هیچ خبری از این مباحث نبوده و امنیت را باید خودتان تامین کنید. (البته یک نسخه‌ی تجاری هم از این بانک اطلاعاتی با پشتیبانی از رمزنگاری اطلاعات موجود است : +)

ه) مرتب سازی فارسی
هر چند SQLite هیچ مشکلی در ثبت اطلاعات یونیکد و خصوصا متون فارسی ندارد، اما با مرتب سازی کلمات یونیکد مشکل داشته و بر اساس کد اسکی آن‌ها عمل می‌کند. هر چند امکان تعریف Collation سفارشی در آن ممکن است : + (البته ممکن بودن با موجود بودن متفاوت است)


نتیجه گیری:
- SQLite برای دیتابیسی تا حدود یک گیگ که فقط یک نفر قرار باشد از آن استفاده کند انتخاب بسیار مناسبی است (برای مثال فایرفاکس از آن برای ذخیره سازی تنظیمات خودش استفاده می‌کند).

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
من از این میدلور استفاده کردم و زمانیکه خطایی رخ دهد فرضا آیدی درخواست بشه که توی رنج ایدیهای ثبت شده نباشد وارد پیج ارور میشود .
اما زمانیکه کاربر درخواست یک صفحه بیهوده را داشته باشد فرضا https://localhost:44330/mmmnbb  یک صفحه 404 نمایش میدهد و به صفحه error/index هدایت نمی‌شود .
مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت سوم - ورود به سیستم
پس از ایجاد AuthService در قسمت قبل، اکنون می‌خواهیم از آن برای تکمیل صفحه‌ی ورود به سیستم و همچنین تغییر منوی بالای برنامه یا همان کامپوننت header استفاده کنیم.


ایجاد ماژول Dashboard و تعریف کامپوننت صفحه‌ی محافظت شده

قصد داریم پس از لاگین موفق، کاربر را به یک صفحه‌ی محافظت شده هدایت کنیم. به همین جهت ماژول جدید Dashboard را به همراه کامپوننت یاد شده، به برنامه اضافه می‌کنیم:
>ng g m Dashboard -m app.module --routing
>ng g c Dashboard/ProtectedPage
پس از اجرای این دستورات، ابتدا به فایل app.module.ts مراجعه کرده و تعریف این ماژول را که به صورت خودکار به قسمت imports اضافه شده‌است، به قبل از AppRoutingModule منتقل می‌کنیم تا مسیریابی‌های آن توسط catch all ماژول AppRouting لغو نشوند:
import { DashboardModule } from "./dashboard/dashboard.module";

@NgModule({
  imports: [
    //...
    DashboardModule,
    AppRoutingModule
  ]
})
export class AppModule { }
همچنین به فایل dashboard-routing.module.ts ایجاد شده مراجعه کرده و مسیریابی کامپوننت جدید ProtectedPage را اضافه می‌کنیم:
import { ProtectedPageComponent } from "./protected-page/protected-page.component";

const routes: Routes = [
  { path: "protectedPage", component: ProtectedPageComponent }
];


ایجاد صفحه‌ی ورود به سیستم

در قسمت اول این سری، کارهای «ایجاد ماژول Authentication و تعریف کامپوننت لاگین» انجام شدند. اکنون می‌خواهیم کامپوننت خالی لاگین را به نحو ذیل تکمیل کنیم:
export class LoginComponent implements OnInit {

  model: Credentials = { username: "", password: "", rememberMe: false };
  error = "";
  returnUrl: string;
مدل login را همان اینترفیس Credentials تعریف شده‌ی در قسمت قبل درنظر گرفته‌ایم. به همراه دو خاصیت error جهت نمایش خطاها در ذیل قسمت لاگین و همچنین ذخیره سازی returnUrl در صورت وجود:
  constructor(
    private authService: AuthService,
    private router: Router,
    private route: ActivatedRoute) { }

  ngOnInit() {
    // reset the login status
    this.authService.logout(false);

    // get the return url from route parameters
    this.returnUrl = this.route.snapshot.queryParams["returnUrl"];
  }
AuthService را به سازنده‌ی کامپوننت لاگین تزریق کرده‌ایم تا بتوان از متدهای login و Logout آن استفاده کرد. همچنین از سرویس Router برای استفاده‌ی از متد navigate آن استفاده می‌کنیم و از سرویس ActivatedRoute برای دریافت کوئری استرینگ returnUrl، در صورت وجود، کمک خواهیم گرفت.
اکنون متد ارسال این فرم چنین شکلی را پیدا می‌کند:
  submitForm(form: NgForm) {
    this.error = "";
    this.authService.login(this.model)
      .subscribe(isLoggedIn => {
        if (isLoggedIn) {
          if (this.returnUrl) {
            this.router.navigate([this.returnUrl]);
          } else {
            this.router.navigate(["/protectedPage"]);
          }
        }
      },
      (error: HttpErrorResponse) => {
        console.log("Login error", error);
        if (error.status === 401) {
          this.error = "Invalid User name or Password. Please try again.";
        } else {
          this.error = `${error.statusText}: ${error.message}`;
        }
      });
  }
در اینجا توسط وهله‌ی تزریق شده‌ی this.authService، کار فراخوانی متد login و ارسال شیء Credentials به سمت سرور صورت می‌گیرد. خروجی متد login یک Observable از نوع boolean است. بنابراین در صورت true بودن این مقدار و یا موفقیت آمیز بودن عملیات لاگین، کاربر را به یکی از دو صفحه‌ی protectedPage و یا this.returnUrl (در صورت وجود)، هدایت خواهیم کرد.
صفحه‌ی خالی protectedPage را در ابتدای بحث، در ذیل ماژول Dashboard ایجاد کردیم.
در سمت سرور هم در صورت شکست اعتبارسنجی کاربر، یک return Unauthorized صورت می‌گیرد که معادل error.status === 401 کدهای فوق است و در اینجا در قسمت خطای عملیات بررسی شده‌است.

قالب این کامپوننت نیز به صورت ذیل به model از نوع Credentials آن متصل شده‌است:
<div class="panel panel-default">
  <div class="panel-heading">
    <h2 class="panel-title">Login</h2>
  </div>
  <div class="panel-body">
    <form #form="ngForm" (submit)="submitForm(form)" novalidate>
      <div class="form-group" [class.has-error]="username.invalid && username.touched">
        <label for="username">User name</label>
        <input id="username" type="text" required name="username" [(ngModel)]="model.username"
          #username="ngModel" class="form-control" placeholder="User name">
        <div *ngIf="username.invalid && username.touched">
          <div class="alert alert-danger"  *ngIf="username.errors['required']">
            Name is required.
          </div>
        </div>
      </div>

      <div class="form-group" [class.has-error]="password.invalid && password.touched">
        <label for="password">Password</label>
        <input id="password" type="password" required name="password" [(ngModel)]="model.password"
          #password="ngModel" class="form-control" placeholder="Password">
        <div *ngIf="password.invalid && password.touched">
          <div class="alert alert-danger"  *ngIf="password.errors['required']">
            Password is required.
          </div>
        </div>
      </div>

      <div class="checkbox">
        <label>
          <input type="checkbox" name="rememberMe" [(ngModel)]="model.rememberMe"> Remember me
        </label>
      </div>

      <div class="form-group">
        <button type="submit" class="btn btn-primary" [disabled]="form.invalid ">Login</button>
      </div>

      <div *ngIf="error" class="alert alert-danger " role="alert ">
        {{error}}
      </div>
    </form>
  </div>
</div>
در اینجا خاصیت‌های نام کاربری، کلمه‌ی عبور و همچنین rememberMe مدل، از کاربر دریافت می‌شوند؛ به همراه بررسی اعتبارسنجی سمت کلاینت آن‌ها؛ با این شکل:


برای آزمایش برنامه، نام کاربری Vahid و کلمه‌ی عبور 1234 را وارد کنید.


تکمیل کامپوننت Header برنامه

در ادامه، پس از لاگین موفق شخص، می‌خواهیم صفحه‌ی protectedPage را نمایش دهیم:


در این صفحه، Login از منوی سایت حذف شده‌است و بجای آن Logout به همراه «نام نمایشی کاربر» ظاهر شده‌اند. همچنین توکن decode شده به همراه تاریخ انقضای آن نمایش داده شده‌اند.
برای پیاده سازی این موارد، ابتدا از کامپوننت Header شروع می‌کنیم:
export class HeaderComponent implements OnInit, OnDestroy {

  title = "Angular.Jwt.Core";

  isLoggedIn: boolean;
  subscription: Subscription;
  displayName: string;

  constructor(private authService: AuthService) { }
این کامپوننت وضعیت گزارش شده‌ی ورود شخص را توسط خاصیت isLoggedIn در اختیار قالب خود قرار می‌دهد. برای این منظور به سرویس AuthService تزریق شده‌ی در سازنده‌ی خود نیاز دارد.
اکنون در روال رخ‌دادگردان ngOnInit، مشترک authStatus می‌شود که یک BehaviorSubject است و از آن جهت صدور رخ‌دادهای authService به تمام کامپوننت‌های مشترک به آن استفاده کرده‌ایم:
  ngOnInit() {
    this.subscription = this.authService.authStatus$.subscribe(status => {
      this.isLoggedIn = status;
      if (status) {
        this.displayName = this.authService.getDisplayName();
      }
    });
  }
Status بازگشت داده شده‌ی توسط آن از نوع boolean است و در صورت true بودن، خاصیت isLoggedIn را نیز true می‌کند که از آن در قالب این کامپوننت برای نمایش و یا مخفی کردن لینک‌های login و logout استفاده خواهیم کرد.
همچنین اگر این وضعیت true باشد، مقدار DisplayName کاربر را نیز از سرویس authService دریافت کرده و توسط خاصیت this.displayName در اختیار قالب Header قرار می‌دهیم.
در آخر برای جلوگیری از نشتی حافظه، ضروری است اشتراک به authStatus، در روال رخ‌دادگردان ngOnDestroy لغو شود:
  ngOnDestroy() {
    // prevent memory leak when component is destroyed
    this.subscription.unsubscribe();
  }

همچنین در قالب Header، مدیریت دکمه‌ی Logout را نیز انجام خواهیم داد:
  logout() {
    this.authService.logout(true);
  }

با این مقدمات، قالب Header اکنون به صورت ذیل تغییر می‌کند:
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" [routerLink]="['/']">{{title}}</a>
    </div>
    <ul class="nav navbar-nav">
      <li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a class="nav-link" [routerLink]="['/welcome']">Home</a>
      </li>
      <li *ngIf="!isLoggedIn" class="nav-item" routerLinkActive="active">
        <a class="nav-link" queryParamsHandling="merge" [routerLink]="['/login']">Login</a>
      </li>
      <li *ngIf="isLoggedIn" class="nav-item" routerLinkActive="active">
        <a class="nav-link" (click)="logout()">Logoff [{{displayName}}]</a>
      </li>
      <li *ngIf="isLoggedIn" class="nav-item" routerLinkActive="active">
        <a class="nav-link" [routerLink]="['/protectedPage']">Protected Page</a>
      </li>
    </ul>
  </div>
</nav>
در اینجا توسط ngIfها وضعیت خاصیت isLoggedIn این کامپوننت را بررسی می‌کنیم. اگر true باشد، Logoff به همراه نام نمایشی کاربر را در منوی راهبری سایت ظاهر خواهیم کرد و در غیراینصورت لینک به صفحه‌ی Login را نمایش می‌دهیم.


تکمیل کامپوننت صفحه‌ی محافظت شده

در تصویر قبل، نمایش توکن decode شده را نیز مشاهده کردید. این نمایش توسط کامپوننت صفحه‌ی محافظت شده، مدیریت می‌شود:
import { Component, OnInit } from "@angular/core";
import { AuthService } from "../../core/services/auth.service";

@Component({
  selector: "app-protected-page",
  templateUrl: "./protected-page.component.html",
  styleUrls: ["./protected-page.component.css"]
})
export class ProtectedPageComponent implements OnInit {

  decodedAccessToken: any = {};
  accessTokenExpirationDate: Date = null;

  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.decodedAccessToken = this.authService.getDecodedAccessToken();
    this.accessTokenExpirationDate = this.authService.getAccessTokenExpirationDate();
  }
}
در اینجا به کمک سرویس تزریقی AuthService، یکبار با استفاده از متد getDecodedAccessToken آن، مقدار اصلی توکن را و بار دیگر توسط متد getAccessTokenExpirationDate آن، تاریخ انقضای توکن دریافتی از سمت سرور را نمایش می‌دهیم؛ با این قالب:
<h1>
  Decoded Access Token
</h1>

<div class="alert alert-info">
  <label> Access Token Expiration Date:</label> {{accessTokenExpirationDate}}
</div>

<div>
  <pre>{{decodedAccessToken | json}}</pre>
</div>


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
نظرات مطالب
جلوگیری از ارسال Spam در ASP.NET MVC
به نکته بسیار خوبی اشاره کردین ولی بهتره به جای چک کردن ModelState یک خصوصیت به جدید به ActionFilter خودمون اضافه کنیم و بعد در صورتیکه نتیجه عملیات موفقیت آمیز بود اون رو True کنیم در نهایت در خود ActionFilter پس از اجرای اکشن چک میکنیم اگر این خصوصیت false بود یعنی چیزی ثبت نشده و کلید رو از کش حذف می‌کنیم و کاربر میتونه باز فرم رو ارسال کنه و تنها زمانی محدود میشه که واقعا چیزی ثبت شده باشه.
در کل روند رو می‌تونیم در دست خودمون بگیرم و هروقت که بخواهیم بگیم که محدودیت اعمال بشه یا خیر.
با توجه به نکاتی که در کامنت‌ها ذکر شد در مکانیزم تغییراتی داده شد و به پست اصلی ضمیمه کردم.
مطالب
نمایش یک پیغام به کاربر در ASP.Net

عموما در برنامه‌های وب مرسوم است که پیغام به کاربر را در همان لابلای html صفحه نمایش می‌دهند. مثلا یک برچسب و سپس تنظیم متن آن در کد برنامه به صورت پویا.
با استفاده از پلاگین‌های jQuery این‌کار را به صورت شکیل‌تری می‌توان انجام داد. برای مثال:


پلاگین کم حجمی برای این منظور موجود است به نام jQuery Notice (یکی از چند ده نمونه موجود)
<script type="text/javascript">
$(document).ready(function()
{
jQuery.noticeAdd({
text: 'پیغامی به کاربر',
stay: false
});
});
</script>
کمی این پلاگین را اصلاح کردم تا مشکل عدم نمایش آن هنگام اسکرول طولانی صفحه در IE حل شود (به صورت پیش فرض با فایرفاکس مشکلی ندارد). برای مثال این div را در نظر بگیرید:
<div id="myElement" style="position: absolute">This stays at the top</div>
قصد داریم مکان آن‌را در بالای صفحه ثابت کنیم (حتی با یک اسکرول طولانی مانند تصویر فوق، باز هم همان بالا باقی بماند و قابل مشاهده باشد).
با استفاده از jQuery این‌کار به صورت زیر قابل انجام است:
<script type="text/javascript">
$(document).ready(function()
{
$(window).scroll(function() {
$('#myElement').css('top', $(this).scrollTop() + "px");
});
});
</script>
زمانیکه scroll ایی در window جاری صورت ‌گیرد، div ایی با id مساوی myElement یافت شده و سپس مقدار top آن تنظیم شده و در بالای صفحه نمایش داده می‌شود.

ولی این روش جهت نمایش پیغامی پویا به کاربر مشکل دارد.
نیاز است به ازای هر پیغام پویا یکبار به نحوی این اسکریپت به صفحه تزریق شود که روش انجام کار در ASP.Net به صورت زیر می‌تواند باشد:

using System;
using System.Web.UI;
using System.Web;

public class CAddJqueryNotice
{
/// <summary>
/// نمایش یک پیغام بر اساس پلاگین نوتیس
/// </summary>
/// <param name="title">عنوان</param>
/// <param name="msg">پیغام</param>
/// <param name="rtl">راست به چپ؟</param>
/// <param name="duration">مدت زمان نمایش</param>
/// <param name="autoHide">به صورت خودکار بسته شود؟</param>
public static void Show(string title, string msg, bool rtl, int duration, bool autoHide)
{
string scriptBlock
= string.Format(@"<script type=""text/javascript"">
$(document).ready(function() {{
jQuery.noticeAdd({{
text: '<b>{0}</b><br/><div align=left dir={1}>{2}</div>',
stay: {3},
stayTime: {4}
}});
}});
</script>",
title,
(rtl ? "rtl" : "ltr"),
msg,
(autoHide ? "false" : "true"),
duration);

if (HttpContext.Current == null || HttpContext.Current.Handler == null) return;
Page page = HttpContext.Current.Handler as Page;
if (page != null)
page.ClientScript.RegisterStartupScript(
page.GetType(),
"script" + new Guid().ToString("N"),
scriptBlock,
false);
}

}
از آنجائیکه در یک کلاس دیگر خارج از صفحه اصلی مشغول به کار هستیم، دسترسی مستقیم به شیء Page و سپس متد ClientScript.RegisterStartupScript آن جهت تزریق اسکریپت خود به صفحه نداریم. اما با استفاده از HttpContext.Current.Handler می‌توان به این مقصود رسید و مشکل حل می‌شود.

برای آزمایش آن یک دکمه را در صفحه قرار داده و در روال رخ‌داد گردان کلیک آن کد زیر را اضافه کنید:
CAddJqueryNotice.Show( "لطفا دوباره سعی کنید", "مشکلی رخ داده است", true, 2000, true);

بدیهی است قبل از استفاده از کد فوق، باید چند سطر زیر را به هدر master page سایت خود اضافه کنید:
<script src="jquery-1.3.2.js" type="text/javascript"></script>
<link href="jquery.notice.css" type="text/css" media="screen" rel="stylesheet" />
<script src="jquery.notice.js" type="text/javascript"></script>

نظرات مطالب
EF Code First #12
این قسمت کمی مشکل بود.کمی گیج شدم
تصویر زیر را ببینید.
در یک Win Application
جای تعاریف درست است؟؟
چرا عبارت "HibernatingRhinos" شناخته نمیشود؟
مطالب
افزودن یک صفحه‌ی جدید و دریافت و نمایش اطلاعات از سرور به کمک Ember.js
در قسمت قبل با مقدمات برپایی یک برنامه‌ی تک صفحه‌ای وب مبتنی بر Ember.js آشنا شدیم. مثال انتهای بحث آن نیز یک لیست ساده را نمایش می‌دهد. در ادامه همین برنامه را جهت نمایش لیستی از اشیاء JSON دریافتی از سرور تغییر خواهیم داد. همچنین یک صفحه‌ی about را نیز به آن اضافه خواهیم کرد.


پیشنیازهای سمت سرور

- ابتدا یک پروژه‌ی خالی ASP.NET را ایجاد کنید. نوع آن مهم نیست که Web Forms باشد یا MVC.
- سپس قصد داریم مدل کاربران سیستم را توسط یک ASP.NET Web API Controller در اختیار Ember.js قرار دهیم. مباحث پایه‌ای Web API نیز در وب فرم‌ها و MVC یکی است.
مدل سمت سرور برنامه چنین شکلی را دارد:
namespace EmberJS02.Models
{
    public class User
    {
        public int Id { set; get; }
        public string UserName { set; get; }
        public string Email { set; get; }
    }
}
کنترلر Web API ایی که این اطلاعات را در ختیار کلاینت‌ها قرار می‌دهد، به نحو ذیل تعریف می‌شود:
using System.Collections.Generic;
using System.Web.Http;
using EmberJS02.Models;
 
namespace EmberJS02.Controllers
{
    public class UsersController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<User> Get()
        {
            return UsersDataSource.UsersList;
        }
    }
}
در اینجا UsersDataSource.UsersList صرفا یک لیست جنریک ساده از کلاس User است و کدهای کامل آن‌را می‌توانید از فایل پیوست انتهای بحث دریافت کنید.

همچنین فرض بر این است که مسیریابی سمت سرور ذیل را نیز به فایل global.asax.cs، جهت فعال سازی دسترسی به متدهای کنترلر UsersController تعریف کرده‌اید:
using System;
using System.Web.Http;
using System.Web.Routing;
 
namespace EmberJS02
{
    public class Global : System.Web.HttpApplication
    { 
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}",
               defaults: new { id = RouteParameter.Optional }
               );
        }
    }
}

پیشنیازهای سمت کاربر

پیشنیازهای سمت کاربر این قسمت با قسمت «تهیه‌ی اولین برنامه‌ی Ember.js» دقیقا یکی است.
ابتدا فایل‌های مورد نیاز Ember.js به برنامه اضافه شده‌اند:
 PM> Install-Package EmberJS
سپس یک فایل app.js با محتوای ذیل به پوشه‌ی Scripts اضافه شده‌است:
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
    setupController:function(controller) {
        controller.set('content', ['red', 'yellow', 'blue']);
    }
});
و  در آخر یک فایل index.html با محتوای ذیل کار برپایی اولیه‌ی یک برنامه‌ی مبتنی بر Ember.js را انجام می‌دهد:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="Scripts/jquery-2.1.1.js" type="text/javascript"></script>
    <script src="Scripts/handlebars.js" type="text/javascript"></script>
    <script src="Scripts/ember.js" type="text/javascript"></script>
    <script src="Scripts/app.js" type="text/javascript"></script>
</head>
<body>
    <script type="text/x-handlebars" data-template-name="application">
        <h1>Header</h1>
        {{outlet}}
    </script>
    <script type="text/x-handlebars" data-template-name="index">
        Hello,
        <strong>Welcome to Ember.js</strong>!
        <ul>
            {{#each item in content}}
            <li>
                {{item}}
            </li>
            {{/each}}
        </ul>
    </script>
</body>
</html>
تا اینجا را در قسمت قبل مطالعه کرده بودید.
در ادامه قصد داریم به هدر صفحه، دو لینک Home و About را اضافه کنیم؛ به نحوی که لینک Home به مسیریابی index و لینک About به مسیریابی about که صفحه‌ی جدید «درباره‌ی برنامه» را نمایش می‌دهد، اشاره کنند.


تعریف صفحه‌ی جدید About

برنامه‌های Ember.js، برنامه‌های تک صفحه‌ای وب هستند و صفحات جدید در آن‌ها به صورت یک template جدید تعریف می‌شوند که نهایتا متناظر با یک مسیریابی مشخص خواهند بود.
به همین جهت ابتدا در فایل app.js مسیریابی about را اضافه خواهیم کرد:
App.Router.map(function() {
    this.resource('about');
});
به این ترتیب با فراخوانی آدرس about/ در مرورگر توسط کاربر، منابع مرتبط با این آدرس و قالب مخصوص آن، توسط Ember.js پردازش خواهند شد.
بنابراین به صفحه‌ی index.html برنامه مراجعه کرده و صفحه‌ی about را توسط یک قالب جدید تعریف می‌کنیم:
<script type="text/x-handlebars" data-template-name="about">
    <h2>Our about page</h2>
</script>
تنها نکته‌ی مهم در اینجا مقدار data-template-name است که سبب خواهد شد تا به مسیریابی about، به صورت خودکار متصل و مرتبط شود.

در این حالت اگر برنامه را در حالت معمولی اجرا کنید، خروجی خاصی را مشاهده نخواهید کرد. بنابراین نیاز است تا لینکی را جهت اشاره به این مسیر جدید به صفحه اضافه کنیم:
<script type="text/x-handlebars" data-template-name="application">
    <h1>Ember Demo App</h1>
    <ul class="nav">
        <li>{{#linkTo 'index'}}Home{{/linkTo}}</li>
        <li>{{#linkTo 'about'}}About{{/linkTo}}</li>
    </ul>
    {{outlet}}
</script>
اگر از قسمت قبل به خاطر داشته باشید، عنوان شد که قالب ویژه‌ی application به صورت خودکار با وهله سازی Ember.Application.create به صفحه اضافه می‌شود. اگر نیاز به سفارشی سازی آن وجود داشت، خصوصا جهت تعریف عناصری که باید در تمام صفحات حضور داشته باشند (مانند منوها)، می‌توان آن‌را به نحو فوق سفارشی سازی کرد.
در اینجا با استفاده از امکان یا directive ویژه‌ای به نام linkTo، لینک‌هایی به مسیریابی‌های index و about اضافه شده‌اند. به این ترتیب اگر کاربری برای مثال بر روی لینک About کلیک کند، کتابخانه‌ی Ember.js او را به صورت خودکار به مسیریابی about و سپس نمایش قالب مرتبط با آن (قالب about ایی که پیشتر تعریف کردیم) هدایت خواهد کرد؛ مانند تصویر ذیل:


همانطور که در آدرس صفحه نیز مشخص است، هرچند صفحه‌ی about نمایش داده شده‌است، اما هنوز نیز در همان صفحه‌ی اصلی برنامه قرار داریم. به علاوه در این قسمت جدید، همچنان منوی بالای صفحه نمایان است؛ از این جهت که تعاریف آن به قالب application اضافه شده‌اند.


دریافت و نمایش اطلاعات از سرور

اکنون که با نحوه‌ی تعریف یک صفحه‌ی جدید و برپایی سیم کشی‌های مرتبط با آن آشنا شدیم، می‌خواهیم صفحه‌ی دیگری را به نام Users به برنامه اضافه کنیم و در آن لیست کاربران ارائه شده توسط کنترلر Web API سمت سرور ابتدای بحث را نمایش دهیم.
بنابراین ابتدا مسیریابی جدید users را به صفحه اضافه می‌کنیم تا لیست کاربران، در آدرس users/ قابل دسترسی شود:
App.Router.map(function() {
    this.resource('about');
    this.resource('users');
});
سپس نیاز است مدلی را توسط فراخوانی Ember.Object.extend ایجاد کرده و به کمک متد reopenClass آن‌را توسعه دهیم:
App.UsersLink = Ember.Object.extend({});
App.UsersLink.reopenClass({
    findAll: function () {
        var users = [];
        $.getJSON('/api/users').then(function(response) {
            response.forEach(function(item) {
                users.pushObject(App.UsersLink.create(item));
            });
        });
        return users;
    }
});
در اینجا متد دلخواهی را به نام findAll اضافه کرده‌ایم که توسط متد getJSON جی‌کوئری، به مسیر /api/users سمت سرور متصل شده و لیست کاربران را از سرور به صورت JSON دریافت می‌کند. در اینجا خروجی دریافتی از سرور به کمک متد pushObject به آرایه کاربران اضافه خواهد شد. همچنین نحوه‌ی فراخوانی متد create مدل UsersLink را نیز در اینجا مشاهده می‌کنید (App.UsersLink.create).

پس از اینکه نحوه‌ی دریافت اطلاعات از سرور مشخص شد، باید اطلاعات این مدل را در اختیار مسیریابی Users قرار داد:
App.UsersRoute = Ember.Route.extend({
    model: function() {
        return App.UsersLink.findAll();
    }
});
 
App.UsersController = Ember.ObjectController.extend({
    customHeader : 'Our Users List'
});
به این ترتیب زمانیکه کاربر به مسیر users/ مراجعه می‌کند، سیستم مسیریابی می‌داند که اطلاعات مدل خود را باید از کجا تهیه نماید.
همچنین در کنترلری که تعریف شده، صرفا یک خاصیت سفارشی و دلخواه جدید، به نام customHeader برای نمایش در ابتدای صفحه تعریف و مقدار دهی گردیده‌است.
اکنون قالبی که قرار است اطلاعات مدل را نمایش دهد، چنین شکلی را خواهد داشت:
<script type="text/x-handlebars" data-template-name="users">
    <h2>{{customHeader}}</h2>
    <ul>
        {{#each item in model}}
        <li>
            {{item.Id}}-{{item.UserName}} ({{item.Email}})
        </li>
        {{/each}}
    </ul>
</script>
با تنظیم data-template-name به users سبب خواهیم شد تا این قالب اطلاعات خودش را از مسیریابی users دریافت کند. سپس یک حلقه نوشته‌ایم تا کلیه عناصر موجود در مدل را خوانده و در صفحه نمایش دهد. همچنین در عنوان قالب نیز از خاصیت سفارشی customHeader استفاده شده‌است:




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
EmberJS02.zip