مطالب
وظیفه app.UseExceptionHandler("/Error") در Blazor Server
علیرغم اینکه در Program.cs یا Startup.cs کد زیر وجود دارد، اما بازهم استثناءها در Blazor Server در قالب یک نوار زرد رنگ، پایین مرورگر نشان داده می‌شوند؛ حال در محیط توسعه باشد و یا در محیط تولید و پابلیش نهایی محصول!
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

برای آزمایش آن، کد فوق را به شکل زیر کامنت می‌کنیم تا در محیط توسعه نیز در صورتیکه استثنایی رخ داد، ما را به صفحه‌ی Error.cshtml پیش فرض هدایت کند:
            //if (env.IsDevelopment())
            //{
                //app.UseDeveloperExceptionPage();
            //}
            //else
            //{
                app.UseExceptionHandler("/Error");
            //}
حال کامپوننت counter را به شکل زیر ویرایش می‌کنیم تا استثنایی به عمد رخ دهد:
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
        throw new Exception("This is my Exception !!");
    }
}
با اجرای برنامه ملاحظه خواهید کرد که به صفحه‌ی Error.cshtml هدایت نخواهید شد! حتی با اینکه استثنایی رخ داد، خط app.UseExceptionHandler("/Error")   اصلا کاری به مدیریت استثناء نداشت. حال سؤالی اینجا پیش می‌آید: پس چرا مایکروسافت در visual studio به تولید کدهای پیش فرض صفحه Error.cshtml و صدا زدن میان افزار فوق در یک اپلیکیشن Blazor Server می‌پردازد؟
در واقع بسیاری از میان افزارهای Asp.Net Core مانند UseExceptionHandler  در تمام فازهای یک اپلیکیشن Blazor Server به درستی کار نمی‌کنند؛ زیرا Blazor Server با SignalR و هابش کار می‌کند. 
هنگام راه‌اندازی یک برنامه‌ی Blazor Server، ابتدا چند درخواست HTTP وجود دارد که از خط لوله‌ی Asp .Net Core عبور می‌کنند؛ در واقع دقیقا قبل از تشکیل هاب و عملیات websocket. در این فاز اگر استثنایی رخ دهد، آنگاه میان افزار  UseExceptionHandler  وارد عمل خواهد شد و صفحه را به Error.cshtml هدایت خواهد نمود و به این دلیل است که این کدها به صورت پیش فرض وجود دارند. بنابراین باید صفحه‌ی Error.cshtml را نیز در اپلیکیشن‌های تولید شده‌ی خود، به زبان‌های موردنظر پروژه‌تان ترجمه کرده و پیام‌های مناسبی را به کاربر نشان دهید.
باید دقت نمود که دیگر پس از این فاز نمی‌توان به این میان افزار متکی بود. برای مدیریت استثناءها در فازهای بعد از این فاز، می‌توان از ErrorBoundary و یا مدیریت دستی استثناءها استفاده نمود.
نظرات مطالب
اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
ممکن است بیشتر توضیح دهید؟
یعنی من نمی‌توانم از سرویس دیگری داخل سرویس فراخوانی شده توسط کامپوننت استفاده کنم؟
در کل رویکرد پروژه این بوده است که بتوان موقع لاگ گیری کد کاربر را نیز لاگ نمود. یک راه حل دیگر که به نظرم رسید این بود که کد کاربر را موقع فراخوانی سرویس از داخل کامپوننت مقداردهی کنم (با این روش)، اما اگر روش بالا جواب می‌داد کد تمیزتری حاصل می‌شد.
نظرات مطالب
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامه‌های Angular
نکته تکمیلی
برای خلوت کردن قالب‌های مرتبط با فرم‌ها برای نمایش خطاهای اعتبارسنجی و همچنین برای جلوگیری از تکرار، می‌توان کامپوننت ValidationMessage را به شکل زیر نیز توسعه داد:
import { Component, Input, OnInit } from '@angular/core';
import { AbstractControl } from '@angular/forms';

@Component({
  selector: 'validation-message',
  template: `
    <ng-container *ngIf="control.invalid && control.touched">
      {{ message }}
    </ng-container>
  `
})
export class ValidationMessageComponent implements OnInit {
  @Input() control: AbstractControl;
  @Input() fieldDisplayName: string;
  @Input() rules: { [key: string]: string };

  get message(): string {
    return this.control.hasError('required')
      ? `${this.fieldDisplayName} را وارد نمائید.`
      : this.control.hasError('pattern')
      ? `${this.fieldDisplayName} را به شکل صحیح وارد نمائید.`
      : this.control.hasError('email')
      ? `${this.fieldDisplayName} را به شکل صحیح وارد نمائید.`
      : this.control.hasError('minlength')
      ? `${this.fieldDisplayName} باید بیشتر از  ${
          this.control.errors.minlength.requiredLength
        } کاراکتر باشد.`
      : this.control.hasError('maxlength')
      ? `${this.fieldDisplayName} باید کمتر از  ${
          this.control.errors.maxlength.requiredLength
        } کاراکتر باشد.`
      : this.control.hasError('min')
      ? `${this.fieldDisplayName} باید بیشتر از  ${
          this.control.errors.min.requiredLength
        } باشد.`
      : this.control.hasError('max')
      ? `${this.fieldDisplayName} باید کمتر از  ${
          this.control.errors.max.requiredLength
        } باشد.`
      : this.hasRule()
      ? this.findRule()
      : this.control.hasError('model')
      ? `${this.control.errors.model.messages[0]}`
      : '';
  }
  constructor() {}

  private hasRule() {
    return (
      this.rules &&
      Object.keys(this.control.errors).some(ruleKey =>
        this.rules[ruleKey] ? true : false
      )
    );
  }

  private findRule(): string {
    let message = '';
    Object.keys(this.control.errors).forEach(ruleKey => {
      if (this.rules[ruleKey]) {
        message += `${this.rules[ruleKey]} `;
      }
    });

    return message;
  }

  ngOnInit(): void {}
}

این کامپوننت، کنترل مورد نظر، یک نام نمایشی برای فیلد متناظر و یک شیء تحت عنوان rules را دریافت می‌کند. در بدنه پراپرتی message، به ترتیب اولویت validator، بررسی انجام شده و پیغام مناسب بازگشت داده خواهد شد. همچنین خطاهای سمت سروری هم که با کد "model" به لیست خطاهای کنترل اضافه شده اند نیز مورد بررسی قرار گرفته شده اند. 
 در اینجا اگر لازم باشد با یکسری قواعد سفارشی هم به صورت یکپارچه با اعتبارسنج‌های پیش فرض رفتار کنیم و از یک مسیر این پیغام‌ها نمایش داده شوند، می‌توان از خصوصیت rules بهره برد. به عنوان مثال:
 <mat-error *ngIf="form.controls['userName'].invalid && form.controls['userName'].touched" 
 class="mat-text-warn">
    <validation-message
        [control]="form.controls['userName']"
        fieldDisplayName="نام کاربری"
        [rules]="{rule1:'پیغام متناظر با rule1'}">
    </validation-message>
</mat-error>

به همراه یک Validator سفارشی
this.form = this.formBuilder.group({
      userName: [
        '',
        [Validators.required, UserNameValidators.rule1)]
      ],
      password: ['', Validators.required],
      rememberMe: [false]
    });

export class UserNameValidators{
   static rule1(control: AbstractControl) {
        if (control.value.indexOf(' ') >= 0) {
            return { rule1: true };
        }
        return null;
    }
}

نظرات مطالب
پَرباد - راهنمای اتصال و پیاده‌سازی درگاه‌های پرداخت اینترنتی (شبکه شتاب)
درست شد
در صورت پرداخت موفق فیلد TransactionID  اطلاعاتش پر می‌شود
<?xml version="1.0" encoding="utf-8"?>
<Log xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Type>Verify</Type>
  <Gateway>Mellat</Gateway>
  <OrderNumber>636696338698017740</OrderNumber>
  <Amount>50000</Amount>
  <ReferenceId>63610A4322A08FDD</ReferenceId>
  <TransactionId>139032371054</TransactionId>
  <Status>Success</Status>
  <Message>تراکنش با موفقیت انجام شد</Message>
  <CreatedOn>2018-08-12T01:25:31.1869631+04:30</CreatedOn>
</Log>


نظرات مطالب
نمایش فرم‌های مودال Ajax ایی در ASP.NET MVC به کمک Twitter Bootstrap
با سلام و تشکر
موقعی که اطلاعات اینجوری باشه درست کار میکنه و نتیجه رو نمایش میده 
return Json( "test");
completeHandler: function (data) {
alert(data)
}

ولی موقعی که مثلا اینجوری مینویسم 
            return Json(
            new
            {
                success = true,
                message = Resources.Global.Texts.Successful
            });
completeHandler: function (data) {
alert(data.success)
}
خطای مربوطه هم Cannot read property 'message' of undefine  میده
تو فایر باگ هم میبینم اطلاعات در response وجود داره ولی اینجا نمایش نمیده
مطالب
شروع کار با webpack - قسمت چهارم
در سه قسمت قبلی با مفاهیم گوناگونی از وبپک آشنا شدیم که می‌توان به ساخت باندل‌های ساده و پیشرفته، استفاده از لودرها، فایل‌های پیکربندی  و ... اشاره کرد.

بارگذاری فایل‌های css به کمک وبپک

همان طور که قبلا اشاره شد، هسته‌ی وبپک به خودی خود بجز باندل کردن اسکریپت‌های خام جاوا اسکریپت و در نهایت Minify کردن آنها، قادر به انجام کار دیگری نیست. همین طور ذکر شد، برای اینکه وبپک فوت و فن جدیدی را یاد بگیرد، از Loader‌ها استفاده می‌کنیم. وارد کردن فایل‌های css به باندل نیز به کمک Loader‌ها خواهد بود. برای اینکار به دو Loader احتیاج داریم و به کمک npm آنها را به عنوان وابستگی برای توسعه‌ی پروژه نصب خواهیم کرد .
// نصب لودر‌های مورد نظر
npm install css-loader style-loader -D
لودر css وظیفه‌ی بارگذاری فایل‌های css و همچنین وابستگی آنها ( import هایی که در این فایل‌ها انجام شده) را دارد و در نهایت css نهایی را بر می‌گرداند.
لودر style نیز وظیفه‌ی اضافه کردن ماژول اکسپورت شده را به DOM، به عنوان یک style خواهد داشت.
در ساختار پروژه نیز تغییراتی انجام شده و فایل‌ها و لودر‌های اضافی حذف شده‌اند تا تمرکز بر روی مطلب بیشتر باشد. مانند قسمت اول، یک تک صفحه‌ی index.html را خواهیم داشت که دارای محتوای زیر است:
//index.html file
<html>
  <head>
    <title>webpack part 4</title>

  </head>
  <body>
    <h1>webpack is awesome</h1>
    <p>part 4 of tutorial</p>
    <div>i have a background</div>
    <h1>تست فونت !</h1>

    <script src="/assets/js/bundle.js">
    </script>
  </body>
</html>
اگر دقت کنید، در این فایل لینکی به فایل‌های استایل، مانند css وجود ندارد و هدف این است که این فایل‌ها توسط وبپک پردازش شوند.
برای شروع به فایل پیکربندی وبپک مراجعه کرده و دو Loader ی را که برای این کار، بالاتر در پروژه نصب کردیم، به وبپک معرفی می‌کنیم.
//webpack.config.js
var path = require("path");
var webpack = require("webpack");

module.exports = {
    context: path.resolve("js"),
    entry: ['./main.js']
    , output: {
        path: path.resolve("build/js"),
        publicPath: "assets/js",
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: "assets"
    }
    , watch: true
    , module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader'
            }
        ]
    }
}
در قطعه کد بالا محتوای فایل پیکربندی قابل مشاهده است. مشخص است که لودر جدیدی را به وبپک معرفی کرده‌ایم که به دنبال فایل‌هایی با پسوند css در پروژه می‌گردد. ولی در کلید loader که وظیفه‌ی فراخوانی لودر مورد نظر را در مواجهه با این فایل‌ها دارد، کمی با قبل تفاوت وجود دارد.
loader:'style-loader!css-loader'
در قطعه کد بالا، نشانگر ! در مواقعی استفاده می‌شود که قصد داریم لودر‌ها را به صورت زنجیره‌ای استفاده کنیم. مثلن در اینجا به وبپک می‌گوییم که برای فایل‌های css ابتدا لودر css را فراخوانی کن و سپس با خروجی که از این لودر می‌گیری، لودر style را صدا بزن و منتظر جواب آن باش. ترتیب فراخوانی نیز از آخر به اول می‌باشد (اول لودر css سپس لودر style).
در مطالب قبلی ذکر شد که دو حالت برای معرفی فایل‌ها به وبپک وجود دارد؛ یکی معرفی آنها در کلید entry فایل پیکربندی و دیگری استفاده از تابع require در اسکریپت‌ها، برای بارگذاری پویای ماژول‌های دیگر. استفاده از استایل‌های css نیز به همین صورت است. برای بارگذاری فایل‌های استایل، از روش پویا استفاده کرده و در فایل main.js استایل مورد نظر را با کمک require وارد می‌کنیم.
محتوای فایل main.js بدین صورت است:
// main.js file
require("./../assets/main.css");
console.log(`i'm bundled by webpack`);
محتوای فایل main.css نیز بدین شکل می‌باشد:
// main.css
body{
    background-color: #DAA520;
}
با راه اندازی وبپک و باز کردن صفحه‌ی index می‌توان دید که فایل استایل ما به همراه باندل وارد شده است:

در تصویر بالا مشخص است که در تگ Head صفحه، یک تگ جدید style، توسط وبپک ایجاد شده و استایل ما به صفحه تزریق شده‌است. همچنین اگر وبپک را به حالت Minify کردن باندل ببریم (در مطلب قبلی نحوه‌ی این کار ذکر شد)، باندل نهایی برای فایل‌های css نیز Minify خواهد شد.


استفاده از Sass با کمک وبپک 


روش استفاده از Sass نیز تفاوتی با css نخواهد داشت و فقط کافی است Loader آن را در پروژه نصب کنیم و در نهایت آن را در فایل پیکربندی، به وبپک معرفی کنیم. با دستور زیر لودر Sass را در پروژه وارد می‌کنیم:

// نصب لودر sass
npm install -D sass-loader node-sass

( node-sass به عنوان وابستگی لودر sass، در کنار آن نصب شده است)

حال به فایل پیکربندی می‌رویم و لودر جدید را به قسمت لودر‌ها اضافه می‌کنیم:

// webpack.config.js 
module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader'
            }
            ,{
                test:/\.scss$/
                ,exclude:/node_modules/
                ,loader:'style-loader!css-loader!sass-loader'
            }
        ]
    }

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

// main.scss
$background-color:#DAA520;

body{
    background-color: $background-color;
}

سپس در فایل main.js به جای وارد کردن فایل css قبلی، فایل scss جدید را با کمک require وارد می‌کنیم و در ادامه وبپک را اجرا می‌کنیم. خواهیم دید که مانند قبل بدون مشکلی وبپک اجرا شده، فایل scss را به css ترجمه کرده و سپس به کمک بقیه لودر‌ها، به باندل اضافه می‌کند. استفاده از بقیه‌ی فریمورک‌های css مانند Less و ... نیز با کمک لودر آنها به همین صورت قابل انجام است.


استفاده از Autoprefixer 

همان طور که تمامی قابلیت‌های نسخه‌ی جدید جاوااسکریپت در همه‌ی مرورگرها به صورت سراسری پشتیبانی نمی‌شود، برای css نیز چنین مشکل مشابهی وجود دارد و برای استفاده‌ی بهینه‌ی از برخی قابلیت‌ها نیاز داریم تا prefix‌های مورد نیاز مرورگرهای مختلف را به فایل‌های css مان اضافه کنیم. می‌توانیم این روند را با کمک یک لودر وبپک، ساده و به صورت خودکار کرد. برای نصب این لودر دستور زیر را وارد می‌کنیم:

npm install -D autoprefixer-loader

و بعد از نصب شدن آن، در فایل پیکربندی وبپک به لودرهایی که برای فایل‌های css و scss اضافه کرده بودیم، این لودر را نیز به صورت زنجیر وار اضافه می‌کنیم:

//webpack.config.js
module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader!autoprefixer-loader'
            }
            ,{
                test:/\.scss$/
                ,exclude:/node_modules/
                ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader'
            }
        ]
    }

در هر دو لودری که برای css و scss ساخته بودیم، از لودر autoprefixer استفاده کردیم. برای تست اینکه این لودر بدون مشکل کار می‌کند، در فایل main.scss تغییر زیر را ایجاد می‌کنیم:

//main.scss
$background-color:#DAA520;

body{
    background-color: $background-color;
    display: flex;
}

حال با اجرای وبپک خواهیم دید که prefix‌های مورد نیاز توسط لودر اضافه شده اند ( این لودر از کتابخانه‌ی postcss کمک می‌گیرد).




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


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

جهت باندل کردن تصاویر و فونت‌ها، به لودر جدیدی با نام url-loader احتیاج داریم. قبل از هر چیزی این لودر را در پروژه با کمک npm نصب می‌کنیم:

npm install -D url-loader file-loader

(لودر file-loader به عنوان وابستگی مورد نیاز است)

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

//webpack.config.js file
module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader!autoprefixer-loader'
            }
            ,{
                test:/\.scss$/
                ,exclude:/node_modules/
                ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader'
            },{
                test:/\.(png|jpg|ttf|eot)$/
                ,exclude:/node_modules/
                ,loader:'url-loader?limit=100000'
            }
        ]
    }

در لودر اضافه شده، پسوند فایلهایی را که قصد داریم به باندل وارد شوند، معرفی می‌کنیم. در اینجا فرمت‌های png , jpg ,ttf, eot ذکر شده‌اند.

تنها نکته‌ی جدید، در مشخص کردن نام لودر وجود دارد و آن نیز قسمت پس از علامت ؟ می‌باشد. هنگام مشخص کردن اینکه از چه لودری قصد استفاده داریم، می‌توانیم با استفاده از ؟ پارامترهایی را به لودر مورد نظر ارسال کنیم. در اینجا به پارامتر limit، مقدار 100000 را داده‌ایم که برای این لودر به این معناست که اگر حجم فایل در حال پردازش، حجمی بیشتر از این مقدار را داشت، این فایل را به صورت یک لینک جدا از باندل قرار بده. ولی اگر حجمی کمتر از این مقدار داشت، لودر به صورت خودکار فایل را به فرمت Base64 انکود می‌کند و در درون باندل قرار می‌دهد.


برای تست اینکه آیا این لودر به درستی کار می‌کند یا نه، یک تصویر نمونه را در فولدر assets قرار می‌دهیم و سپس در فایل main.scss تغییرات زیر را انجام می‌دهیم.

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



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


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


در تعریف بالا دیدیم که فرمت‌های مورد نیاز برای وارد کردن فونت را نیز علاوه بر تصاویر، برای وبپک مشخص کرده‌ایم. روند وارد کردن فونت‌ها به باندل نیز تفاوتی با تصاویر ندارد و کافی است تعاریف مورد نیاز را در فایل‌های css داشته باشیم.

برای مثال فونت ساحل در پوشه‌ی assets قرار داده شده و در فایل main.scss تغییرات زیر انجام شده‌اند:

// main.scss

$background-color:#DAA520;
 
div{
    background-image: url("galaxy.jpg");
}

@font-face {
  font-family: Sahel;
  src: url('Sahel.eot');
  src: url('Sahel.eot?#iefix') format('embedded-opentype'),
       url('Sahel.woff') format('woff'),
       url('Sahel.ttf') format('truetype');
  font-weight: normal;
}

@font-face {
  font-family: Sahel;
  src: url('Sahel-Bold.eot');
  src: url('Sahel-Bold.eot?#iefix') format('embedded-opentype'),
       url('Sahel-Bold.woff') format('woff'),
       url('Sahel-Bold.ttf') format('truetype');
  font-weight: bold;
}

@font-face {
  font-family: Sahel;
  src: url('Sahel-Black.eot');
  src: url('Sahel-Black.eot?#iefix') format('embedded-opentype'),
       url('Sahel-Black.woff') format('woff');
    
  font-weight: 900;
} 

body{
    background-color: $background-color;
    font-family: 'Sahel';
    display: flex;
}

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



روش دیگری برای وارد کردن تصاویر نیز موجود است؛ به این صورت که به فرض مثال یک تگ img در اسکریپت ساخته و سپس پروپرتی src آن را با کمک require برابر با آدرس تصویر مورد نظر قرار می‌دهیم. این روش نیز برای وبپک قابل فهم بوده و فایل وارد باندل می‌شود. در ادامه مثالی از این روش آورده شده است:

var img = document.createElement("img");
img.width="200px";
img.height="200px";
img.src= require("path to some image");


چند نکته‌ی پایانی :

1. در فایل پیکربندی همیشه پسوند فایل‌هایی را که در کلید entry قرار داشتند، مشخص کردیم:

entry:['./main.js','./shared.ts']

با کلیدی با نام resolve در فایل پیکربندی می‌توان مشخص کرد در صورتیکه پسوند فایلی مشخص نبود، به ترتیب مشخص شده به دنبال آن بگردد. به طور مثال:

// webpack.config.js
resolve:{
            extensions:['','.js','.ts']
  }

در تعریف بالا ذکر می‌شود در صورتیکه پسوند فایل ورودی مشخص نبود، ابتدا به دنبال فایل بدون پسوند، سپس فایل‌هایی با پسوند js و در نهایت به دنبال فایل‌هایی با پسوند ts بگرد. توجه داشته باشید که ترتیب مشخص کردن پسوند فایل‌ها مهم است و وبپک بر اساس این ترتیب به دنبال فایل مورد نظر خواهد گشت.

حال می‌توان مقدار کلید entry را اینطور تعریف کرد:

entry:['./main','./shared']


2.استفاده از فایل‌های css ی که در درونشان فونت‌های مورد نیاز لینک شده‌اند تنها با استفاده از لودر css قابل انجام نیست. به طور مثال استفاده از کتابخانه‌ی بوت استرپ تنها با این لودر ممکن نیست و بایستی لودر url-loader نیز در پروژه نصب شده باشد تا در هنگامیکه وبپک به فونت‌ها برخورد کرد، بتواند آنها را وابسته به شرایط، وارد باندل نهایی کند.


فایل‌های پروژه: dntwebpack-part4.zip 

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران
یک نکته‌ی تکمیلی: جایگزینی bower با npm

bower یک فناوری منسوخ شده‌است و اگر بخواهیم bower.json ذکر شده‌ی در این مطلب را با package.json مربوط به npm جایگزین کنیم، به یک چنین محتوایی خواهیم رسید:
{
  "name": "testwebapp",
  "version": "1.0.0",
  "description": "",
  "scripts": {},
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^3.3.7",
    "jquery": "^2.2.4",
    "jquery-ajax-unobtrusive": "^3.2.4",
    "jquery-validation": "^1.17.0",
    "jquery-validation-unobtrusive": "^3.2.8"
  }
}
پس از ایجاد فایل package.json فوق، کافی است دستور npm install را در ریشه‌ی پروژه وارد کنید تا این بسته‌ها دریافت و نصب شوند.
سپس بر اساس مسیرهای پوشه‌ی node_modules جدید، فایل bundleconfig.json چنین محتوایی را پیدا می‌کند:
[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "node_modules/bootstrap/dist/css/bootstrap.min.css",
      "wwwroot/css/site.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "node_modules/jquery/dist/jquery.min.js",
      "node_modules/jquery-validation/dist/jquery.validate.min.js",
      "node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js",
      "node_modules/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
      "node_modules/bootstrap/dist/js/bootstrap.min.js",
      "wwwroot/js/site.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    },
    "sourceMap": false
  }
]
و در آخر فایل Layout.cshtml_ برنامه به این صورت ساده خواهد شد (فقط خروجی‌های نهایی css و js حاصل از BundlerMinifier در اینجا قید می‌شوند؛ که حاصل یکی کردن و فشرده سازی تمام آن‌ها است):
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - DNTCommon.Web.Core.TestWebApp</title>
    <link href="~/css/site.min.css" rel="stylesheet" asp-append-version="true" />
</head>

<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script> 
    @RenderSection("Scripts", required: false)
</body>
</html>
البته با این شرط که تنظیمات ذیل در فایل csproj برنامه موجود باشند:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <Target Name="PrecompileScript" BeforeTargets="BeforeBuild">
    <Exec Command="dotnet bundle" />
  </Target>
  <ItemGroup>
    <DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />
  </ItemGroup>
</Project>
بازخوردهای دوره
مدیریت نگاشت ConnectionIdها در SignalR به کاربران واقعی سیستم
واقعا سپاسگذارم . ولی مشکلم و حل نمیکنه این موارد.
یکم گیج و سردرگم شدم.
ببینید من یه هاب ایجاد کردم به شکل زیر
 public class User
    {
        public string UserName { get; set; }
        public bool IsRole { get; set; }
        public HashSet<string> ConnectionIds { get; set; }
    }

    [Authorize]
    [HubName("userActivityHub")]
    public class UserActivityHub : Hub
    {
        private static readonly ConcurrentDictionary<string, User> Users = new ConcurrentDictionary<string, User>();
     

        public void AdminJoin()
        {
            Groups.Add(Context.ConnectionId, "admins");
        }

        public void Join()
        {
            var userName = Context.User.Identity.Name;
            var connectionId = Context.ConnectionId;
            var isAdmin = Context.User.IsInRole("Admin");

            var user = Users.GetOrAdd(userName, _ => new User
            {
                UserName = userName,
                IsRole = isAdmin,
                ConnectionIds = new HashSet<string>()
            });

            if (user.IsRole == true)
            {
                Groups.Add(user.ConnectionIds.ToString(), "admins");
            }
            else
            {
                lock (user.ConnectionIds)
                {
                    user.ConnectionIds.Add(connectionId);
                }
                Clients.Group("admins").showUserCount(Users.Count(a => a.Value.IsRole != true));
            }

        }

        public void GetUserCount()
        {
            Clients.Group("admins").showUserCount(Users.Count(a => a.Value.IsRole != true));
        }

        public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
        {
            if (stopCalled)
            {
                var userName = Context.User.Identity.Name;
                var connectionId = Context.ConnectionId;

                User user;
                Users.TryGetValue(userName, out user);
                if (user != null)
                {
                    lock (user.ConnectionIds)
                    {
                        user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
                        if (!user.ConnectionIds.Any())
                        {
                            User removeUser;
                            Users.TryRemove(userName, out removeUser);
                        }
                    }
                }
                return Clients.Group("admins").showUserCount(Users.Count(a => a.Value.IsRole != true));
            }
            else
            {
                return base.OnDisconnected(false);
            }
        }
    }

اینک کد ویو Index
 <script type="text/javascript">
        var userHub = $.connection.userActivityHub;
        $.connection.hub.logging = true;
        $.connection.hub.start().done(function() {
            userHub.server.join();
        });

        $(function() {
            window.onbeforeunload = function() {
                $.connection.hub.stop();
            };
        });
    </script>

اینم ویو Index مربوط به Area Admin 
    <script type="text/javascript">
        var userHub = $.connection.userActivityHub;
        userHub.client.showUserCount = function (message) {
            $('#userOnlineCount').html(message);
        };
        $.connection.hub.start().done(function() {
            userHub.server.adminJoin().done(function() {
                userHub.server.getUserCount();
            });
        });
    </script>
وقتی کاربر لاگین میکنه یه کانتر تو ویو ادمین دارم که بهش اضافه میشه و برای لاگین کاربران درست کار میکنه.
مشکلم اینه که وقتی کاربر دکمه خروج و میزنه یا مرورگر رو میبنده رویداد OnDisconnected اجرا نمیشه! در نتیجه کانتر موجود در ویو ادمین هم کم نمیشه.
اینم کد دکمه خروج که البته تو یه پارشال ویو وجود داره
<script>
    $('a.btn.btn-danger.btn-block').click(function(e) {
        e.preventDefault();
        $('#logoutForm').submit();
        $.connection.userActivityHub.connection.stop();
    });
    $(function() {
        window.onbeforeunload = function(e) {
            $.connection.hub.stop();
        };
    });

</script>
نمیدونم مشکل کجاست! آیا باید برای دکمه خروج هم یه متد تو هاب ایجاد کنم یا همون Ondisconnected کافیه!
واقعا ممنون از زحمات شما.
نظرات مطالب
AngularJS #2
سلام
بنده طبق فرمایشات شما به روش زیر عمل کردم ولی بهم کار نمیده و پارشال مورد نظر را به من نشان نمیده. میشه بگید مشکل از کجاست؟
public class PostController : Controller
    {
        ApplicationDbContext db;
        // GET: Post
        public ActionResult List()
        {
            using (db=new ApplicationDbContext())
            {
                var query = db.Posts.ToList();
                return PartialView("List",query);
            }
        }
    }

و کدهای ویو:
<div ng-app="postmodule">

    <div ng-controller="PostController">
        <ul>
            <li ng-repeat="item in ListOfItems">
                {{item.Title}}
                <hr />
                {{item.Text}}
            </li>
        </ul>
    </div>

</div>

و کدهایی که در فایل _Layout.cshtml نوشته ام:
    <div class="container body-content" >

        <script src="~/Scripts/angular.js"></script>
        <script src="~/Scripts/angular-route.js"></script>
        <script>
            var PostApp = angular.module('postmodule', []).config(['$routeProvider',
  function ($routeProvider) {
      $routeProvider.
          when('/list', {
              templateUrl: '/Post/List',
              controller: 'PostController'
          });
  }]);
            PostApp.controller('PostController', function ($scope, $http, postServices) {
                //...
                $scope.ListOfItems =
                              postServices.GetPosts()
                                  .success(function (data) {
                                      $scope.ListOfItems = data;
                                  });
                //...
            });
            PostApp.service('postServices', function ($http) {
                this.GetPosts = function () {
                    return $http.get('/Post/List');
                };
            });
        </script>

        <a href="#list">list post</a>
        <div ng-view=""></div>

        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
سلام مجدد
یه سوالی داشتم. آیا Dependencr Resolver برای کنترلرهای از نوع Web Api در این مثال گنجانده شده، چون الان وقتی دو تا اکشن متد از نوع WebApi  و با Verb از نوع httpPostداخل کنترلر AccountApi تعریف میکنیم هنگام فراخوانی و ارسال json به آن‌ها به مشکل برخورده و ارور زیر را نمایش می‌دهد:
exceptionMessage": "An error occurred when trying to create a controller of type 'AccountApiController'. Make sure that the controller has a parameterless public constructor.",
"innerException": {
    "message": "An error has occurred.",
    "exceptionMessage": "Type 'MyProject.Web.Controllers.AccountApiController' does not have a default constructor",
    "exceptionType": "System.ArgumentException",