نظرات مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
یک نکته‌ی تکمیلی: نشان دادن لیست API‌ها در swagger فقط برای کاربرانی که لاگین کرده اند

در هنگام توسعه‌ی پروژه شاید برای شما مهم باشد که لیست api‌های شما برای افرادی که لاگین نکرده‌اند، قابل مشاهده نباشد. برای این منظور ابتدا باید سه کتابخانه مربوط به swagger را نصب نمایید:
    <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="4.5.2" />
سپس یک کلاس را همراه با دو اکستنشن متد برای کانفیگ swagger میسازیم :
    public static class ServiceCollectionExtensions
    {
        public static void AddCustomSwagger(this IServiceCollection services)
        {
            services.AddSwaggerGen(options =>
            {
                options.EnableAnnotations();
                options.DocumentFilter<AuthenticationDocumentFilter>();
                options.SwaggerDoc("v1", new Info { Version = "v1", Title = "Test API" });
            });
        }
        public static void UseSwaggerAndUI(this IApplicationBuilder app)
        {
            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                options.DocExpansion(DocExpansion.None);
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API Docs");
            });
        }
    }
در متد AddSwaggerGen از DocumentFilter استفاده کرده‌ایم. با استفاده از Document FIlter‌ها میتوانید خروجی api‌ها را در swagger، توسعه دهید. DocumentFilter که از نوع جنریک است، یک کلاس را به عنوان تایپ قبول میکند که باید از اینترفیس IDocumentFilter ارث بری کرده باشد. اینترفیس IDocumentFilter حاوی یک متد Apply است که دارای دو ورودی از نوع SwaggerDocument  و DocumentFilterContext میباشد. کلاس SwaggerDocument  مستندات api‌ها را در اختیار شما قرار میدهد و میتوانید آنهارا تغییر دهید.
سپس کلاس AuthenticationDocumentFilter را پیاده سازی میکنیم:
  public class AuthenticationDocumentFilter : IDocumentFilter
    {
        private readonly IHttpContextAccessor httpContextAccessor;

        public AuthenticationDocumentFilter(IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor;
        }

        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            if (!httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
            {
                swaggerDoc.Definitions = new Dictionary<string, Schema>();
                swaggerDoc.Paths = new Dictionary<string, PathItem>();
            }
        }
    }
در کلاس AuthenticationDocumentFilter از IHttpContextAccessor برای دسترسی به هویت کاربر استفاده کرده ایم که بعدا باید در متد ConfigureService متد AddHttpContextAccessor را جهت دسترسی به IHttpContextAccessor فراخوانی کنیم. در ادامه اگر کاربر لاگین نکرده باشد، تمامی api‌ها پاک شده و در سمت کاربر هیچ api ای مشاهده نمیشود.
در صورت نیاز میتوان مشخص کرد کدام نوع api هارا نشان ندهد؛ به عنوان مثال Post و Put را نشان ندهد :
        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            if (!httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
            {
                foreach (var item in swaggerDoc.Paths)
                {
                    item.Value.Post = null;
                    item.Value.Put = null;
                }
            }
        }
در ادامه برای ثبت سرویس‌ها در کلاس StartUp 
    public void ConfigureServices(IServiceCollection services)
        {
            services.AddHttpContextAccessor();
            services.AddAuthorization();
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options=>
            {
                options.AccessDeniedPath = "/Login";
                options.Cookie.HttpOnly = true;
                options.LoginPath = "/Login";
                options.LogoutPath = "/Login";
                options.ExpireTimeSpan = TimeSpan.FromDays(15);
                options.SlidingExpiration = true;
                options.Cookie.IsEssential = true;
                options.ReturnUrlParameter = "returnUrl";
            });
            services.AddMvc();
            services.AddCustomSwagger();
        }
و اضافه کردن میان افزار swagger :
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseAuthentication();
            app.UseSwaggerAndUI();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                          name: "default",
                          template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
مطالب
ارتباط بین کامپوننت ها در Vue.js - قسمت چهارم کاربرد Vuex - بخش اول
در قسمت‌های قبلی (^ ,^ ,^ ) نحوه‌ی ارتباط بین کامپوننت‌ها بررسی شد؛ روش دیگری هم برای به اشتراک گذاری داده‌ها بین کامپوننت‌ها وجود دارد که با استفاده از کتابخانه‌ای بنام Vuex پیاده سازی میشود. وقتی برنامه‌ی شما وسعت پیدا میکند و ارتباط بین کامپوننت‌ها بیشتر و پیچیده‌تر می‌شود، روشهای قبلی (^ ,^ ,^ ) کارایی لازم را ندارند و یا اینکه به سختی میشود داده‌های به اشتراک گذاشته شده‌ی بین کامپوننت‌ها را مدیریت نمود. در اینجا میتوان از Vuex  استفاده کرد و به‌راحتی ارتباط‌های پیچیده‌ی بین کامپوننت‌ها را مدیریت کرد.


کد زیر را در نظر بگیرید:
new Vue({
  // state
  // داده‌ها در اینجا قرار میگیرند
  data () {
    return {
      count: 0
    }
  },
  // view
  // ویوها برای نمایش داده‌ها مورد استفاده قرار میگیرند
  template: `<div>{{ count }}</div>`,
  // actions
  // برای تغییر داده‌ها از متدها استفاده میکنیم
  methods: {
    increment () {
      this.count++
    }
  }
})


در یک کامپوننت ساده، از طریق Actionها، داده‌ها (State) تغییر داده میشوند و سپس این تغییر در view مشاهده میشود. اما فرض کنید بیش از صد کامپوننت در برنامه دارید که بسیاری از آنها از داده‌های واحدی استفاده میکنند. روشهای قبلی (^ ,^ ,^) برای چنین سناریویی جوابگو نخواهند بود (آن‌را به سختی میتوان مدیریت کرد و بسیار طاقت فرسا خواهد بود).

راه حل Vuex:
با استفاده از Vuex میتوان برای داده‌ها (State)، یک منبع در نظر گرفت تا کامپوننت‌ها قادر باشند از داده‌های واحدی استفاده کنند و اشتراک گذاری داده‌ها ساده شود.


یک برنامه ساده با استفاده از Vuex:

یک پروژه Vuejs را ایجاد کنید و مطابق تصویر زیر، گزینه دوم را انتخاب و Enter را فشار دهید:


سپس گزینه Vuex را طبق تصویر زیر با دکمه‌ی space انتخاب کنید و برای مابقی گزینه‌های بعدی با زدن Enter، پیش فرض‌ها را بپذیرید تا پروژه ساخته شود:


دو کامپوننت را به برنامه اضافه میکنیم.

کامپوننت اول با نام increase-counter-component.vue  

<template>
  <div>
    <!--  نمایش شمارشگر  -->
    <h1>{{count}}</h1>
    <!--  افزودن یک واحد به شمارشگر  -->
    <button @click="add">Add 1</button>
    <!--  افزودن مقداری دلخواه به شمارشگر  -->
    <button @click="add2">Add 2</button>
  </div>
</template>

<script>
// یا همان منبع ذخیره داده‌ها store کردن  import
import store from "../store";

export default {
  // You can consider computed properties another view into your data.
  // https://css-tricks.com/methods-computed-and-watchers-in-vue-js/
  computed: { count: () => store.state.count },

  // به دو طریق فراخوانی شده  add تابع
  methods: {
    // بدون پارامتر
    add: () => store.commit("add"),
    // با  پارامتر
    // برای مقدار مورد نظر استفاده کنیم input میتوانیم بجای مقدار ثابت از یک
    add2: () => store.commit("add", 2)
  }
};
</script>


کامپوننت دوم با نام decrease-counter-component.vue  

<template>
  <div>
    <h1>{{count}}</h1>
    <button @click="subtract">Subtract 1</button>
    <button @click="subtract(3)">Subtract 3</button>
  </div>
</template>

<script>
import store from "../store";

export default {
  computed: { count: () => store.state.count },

  methods: {
    subtract: payload => store.commit("subtract", +payload)
  }
};
</script>
درون کامپوننت اصلی برنامه App.vue، هر دو کامپوننت را فراخوانی میکنیم:
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <counter-plus></counter-plus>
    <hr />
    <hr />
    <counter-minus></counter-minus>
  </div>
</template>

<script>
import counterPlus from "./components/increase-counter-component";
import counterMinus from "./components/decrease-counter-component";
export default {
  name: "app",
  components: {
    "counter-plus": counterPlus,
    "counter-minus": counterMinus
  }
};
</script>
محتویات فایل  store.js  که تنظیمات Vuex در آن لحاظ شده‌است به شکل زیر می‌باشد:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  //  داده‌های به اشتراک گذاشته شده
  state: {
    count: 0
  },
  // تعریف متدها
  mutations: {
    add(state, payload) {
      // If we get a payload, add it to count
      // Else, just add one to count
      payload ? (state.count += payload) : state.count++;
    },
    subtract(state, payload) {
      payload ? (state.count -= payload) : state.count--;
    }
  }
});
در Terminal دستور زیر را تایپ و اجرا کنید تا نتیجه رویت گردد:
npm run serve
در این برنامه از دو کامپوننت مجزا با داده‌ی واحد، استفاده میکنیم و دیگر خبری از emit$ و on$ و EventBus و تزریق وابستگی نخواهد بود.

چگونه کار میکند؟

در Vuex، متدها در قسمت mutation در فایل store.js نوشته میشوند و در methods  درون کامپوننت‌ها فراخوانی میشوند. اگر با سی شارپ آشنا باشید، این فراخوانی تقریبا  شبیه delegate می‌باشد. داده‌ها در store.js تعریف میشوند و در سراسر برنامه در تمام کامپوننت‌ها قابل دسترس می‌باشند. بدین ترتیب اشتراک گذاری داده‌ها بین کامپوننت‌ها بسیار ساده می‌باشد.


نکته:    برای دریافت پکیج‌های مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:  
 
npm install
سپس برنامه را با دستور زیر اجرا کنید: 
npm run serve

بازخوردهای پروژه‌ها
خطا و راهنمایی
سلام جناب نصیری
در هنگام build پروژه چند خطا داشتم که نتونستم مشکلش رو برطرف کنم لطفا راهنمایی بفرمائید
اول اینکه این سه خطای زیر در حالتی رخ می‌دهد که dll مربوط در رفرنس هر کدام از پروژه‌ها add شده است
Error1The type or namespace name 'PdfReportSamples' could not be found (are you missing a using directive or an assembly reference?)...\pdfreport6797\Samples\WebAppTests\Default.aspx.cs37WebAppTests

Error2The type or namespace name 'PdfReportSamples' could not be found (are you missing a using directive or an assembly reference?)...\pdfreport26797\Samples\WpfAppTests\MainWindow.xaml.cs27WpfAppTests

Error3The type or namespace name 'PdfReportSamples' could not be found (are you missing a using directive or an assembly reference?)...\pdfreport26797\Samples\WindowsFormsAppTests\Form1.cs27WindowsFormsAppTests
خطای دوم اینکه فایل AppManifest.xml در سیلور پیدا نمیشه لذا مشکلی از بابت نبودش ایجاد نمیشه ؟ یک فایل مشابه میشه درست کرد ؟
خطای زیر رو هم متوجه نمیشم
Warning4The primary reference "...\pdfreport26797\Samples\PdfReportSamples\bin\Debug\PdfReportSamples.dll" could not be resolved because it has an indirect dependency on the framework assembly "System.Windows.Forms.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "...\pdfreport-26797\Samples\PdfReportSamples\bin\Debug\PdfReportSamples.dll" or retarget your application to a framework version which contains "System.Windows.Forms.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35".WindowsFormsAppTests
و در آخر یک سوال 
به نظر شما طراحی یک فیش بانکی با استفاده از این کتابخانه توصیه میشه یا استفاده از open office ؟
با تشکر
مطالب
استفاده از چند Routing در یک پروژه ASP.NET MVC بدون درد و خونریزی

کار کردن با مسیریابی برای یک پروژه ساده ، نیاز به طراحی پیچیده ندارد. مسیریابی پیش فرض موجود در فایل RoutConfig.cs برای کارهای ابتدایی کافیست. اما اگر کمی کار پیچیده شود و صفحات مختلفی با منطق‌های متفاوتی ایجاد کنیم، ممکن است با مشکل روبرو شویم. در MVC5 به کمک دخالت ویژگی‌ها در مسیریابی، کار ساده شده است اما در MVC4 و قبل از آن چه باید کرد؟ پیش از بسط مساله، ابتدا این سوال را پاسخ میدهیم که چگونه صفحه‌ی start پروژه انتخاب میشود؟ 

مسیریابی پیش فرض یک پروژه MVC به شکل زیر است : 

routes.MapRoute(
           name: "Default",
           url: "{controller}/{action}/{id}",
           defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
       );

وقتی یک پروژه MVC را بررسی کنید، مشاهده می‌کنید که در شاخه‌ی اصلی آن، فایل index یا default وجود ندارد و اصولا منطق کار با اکشن‌ها و صدا زدن آنها و دیدن پاسخ‌ها توسط View، این اتفاق را توجیه میکند. پس وقتی درخواستی به سمت شاخه‌ی اصلی ما فرستاده میشود، مسیریابی وارد عمل می‌شود. وقتی پروژه را اجرا میکنید، متد RegisterRoutes از شیءایی که از کلاس RoutingConfig.cs ساخته شده (به فایل global.asax نگاه کنید) فراخوانی می‌شود و شیء Routes از " قالب آدرس " هایی که ما تعیین کرده ایم پُر می‌شود. بخشی از فایل RoutConfig.cs را در تصویر زیر می‌بینیم. 

به Url کد فوق نگاه کنید که در حقیقت آدرسی نسبی است. آدرس ما به طور کامل به شکل زیر قابل تعریف است: 

http://mydomain.com/{controller}/{action}/{id}

واضح است که درخواست اولیه به سایت ما دارای بخش‌های controller و action و بخش اختیاری id نیست. پس به شکل پیشفرض بر اساس آنچه در جلوی خصوصیت defaults نوشته شده است، فراخوانی خواهد شد. یعنی اکشن Index از داخل کنترلر Home صدا زده می‌شود و چون id نداریم، هیچ نوع id به متد (اکشن) index ارسال نخواهد شد.

یک روتینگ دیگر به شکل زیر در بالای کد فوق اضافه کنید : 

routes.MapRoute(
          name: "Default1",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "News", action = "Index", id = UrlParameter.Optional }
      );

حتما متوجه شدید که اینبار با اجرای پروژه، متد(اکشن) Index از کنترلر News فراخوانی خواهد شد. در حقیقت اولین روتینگ ساخته شده همان چیزیست که Asp.NET MVC پس از دریافت ریکوئست به آن رجوع میکند. دقت کنید که مقدار name در دو MapRouting فوق متفاوت است. 

اما این اسم‌ها(name ها) به چه دردی می‌خورند؟

 قبل از توضیح در این مورد، default1 را از پروژه حذف میکنیم و با همان MapRouting پیش فرض به کار خود ادامه می‌دهیم. ابتدا مروری بر عملکرد MapRouting انجام میدهیم. 

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

<a href="@Url.Action("post", "News", new { id = @item.PostName }, null)"> @item.PostTitle </a>

فرض کنید یک سایت خبری داریم و گروه‌های مختلف آن شامل مطالب متفاوتی هستند. کنترلری به نام News ایجاد کرده‌ایم، که دارای یک اکشن به نام post است که نام مطلب را دریافت کرده و یک View به ما برمی‌گرداند. بخش هایی از صفحه اول را در تصاویر زیر می‌بینید. تصویر آخر، بخش پایین صفحه اخبار است، که بوسیله لینک (لینک های) موجود صفحات جابجا میشوند. 

وقتی کد فوق در یک صفحه فراخوانی میشود، کالکشن Routes بررسی می‌شود و لینک مورد نظر، با توجه به ورودی‌های Url.Action ساخته می‌شود. ما یک MapRouting به نام Default در کالکشن Routes داریم، پس Url.Action لینک زیر را می‌سازد: 

http://mydomain.com/news/post/نام-مقاله

استفاده از MapRouting مثل یک جعبه است که ورودی آن مقادیر موجود در Url.Action می‌باشد و آنچه به ما بر می‌گرداند یک آدرس برای فراخوانی اکشن مورد نیاز است. (آخرین پارامتر، null قرار داده شده که برای تعیین نوع پروتکل استفاده میشود که http یا https میتواند باشد.)

برای فراخوانی صفحه‌ی اخبار (لیست اخبار) می‌توانید دستور زیر را بنویسید : 

@Html.ActionLink("اخبار", "Index", "News", null, new { @class="btn btn-success"})

این دستور تگ anchor را نیز می‌سازد و به کمک MapRouting  آدرسی شبیه آدرس زیر را به ما بر می‌گرداند. (به تفاوت دو دستور دقت کنید): 

<a href="/News">اخبار</a>

واضح است که چون MapRouting یک حالت پیش فرض درونی دارد که دارای اکشن دیفالت index است، پس ActionLink اگر ببیند لینکش در صفحه قرار است به شکل /news/index تعریف شود خود بخود بخش index را حذف میکند.

فرض کنید بعد از کلیک روی لینک فوق، اکشن index ، یک آبجکتِ لیستی با 10 خبر آخر، ایجاد میکند. پس ما میخواهیم قابلیت صفحه بندی را برای لیست اخبار فعال کنیم و در هر صفحه 10 مطلب را نمایش دهیم. مشکل از همینجا آغاز میشود. MapRouting فعلی جوابگوی ما نخواهد بود و آدرس را به شکل زیر نمایش میدهد. 

<a href="/News/index?pid=2">صفحه بعد</a>

و آنچه ما در View نوشته‌ایم چیزی شبیه کد زیر است :

@Html.ActionLink("صفحه بعد", "index", "News", new { pid = …. }, null)

مشکلی در ارجاع به صفحات وجود ندارد و با کلیک روی لینک "صفحه بعد" مقدار عدد 2 به اکشن index ارسال میشود و اگر کد نویسی را برای take و skip کردن لیست، درست انجام شده باشد، نتیجه مورد نظر نمایش داده خواهد شد. اما آدرس فوق آدرس زیبایی نیست. اولین فکری که به ذهن برنامه نویس میرسد، ایجاد یک مسیریابی دیگر است. فکر درستیست؛ اما اگر چند بار دیگر این اتفاق بیفتد و در بخش هایی از برنامه نیاز به روتینگ پیدا کنید و روتینگ‌های جدید ایجاد کنید متوجه خواهید شد که مدیریت این MapRouting ‌ها کار خسته کننده و طاقت فرسایی خواهد شد، مخصوصا اگر بدانید که فقط مجاز به استفاده از یک  پارامتر optional در هر MapRouting هستید! دست شما کاملا بسته است. لینک‌های بالای سایت را اصلاح میکنید ولی لینک‌های پایین سایت خراب میشوند و بالعکس.

به هر حال MapRouting زیر را به RoutConfig.cs اضافه میکنیم : 

           routes.MapRoute("PostPaging", "{controller}/{action}/{id}/{pid}",
            defaults: new
            {
                controller = "News",
                action = "Index",
                id = "all",
                pid = UrlParameter.Optional

            }
            );
-  اسم این MapRouting ، دیگر Default نیست.
-  یک پارامتر pid اضافه‌تر از MapRouting اولی دارد.
-   pid به عنوان  یک پارامتر اختیاری تعریف شده است، پس "قالب آدرس" بسیار شبیه مپ روتینگ قبلی است.
-  مقدار id اختیاری نیست، چون قرار است در آینده بتوانیم گروه‌های مختلف موجود در بخش اخبار را صفحه بندی کنیم و قرار نیست پشت سر هم MapRouting ایجاد کنیم و کافیست به جای id اسم گروه را بنویسیم. در حالتیکه اسمی از گروه درلینکهایمان نبرده باشیم به شکل پیشفرض all قرار داده میشود که یعنی کل اخبار مد نظر است. (در اکشن مربوطه باید این تصمیمات را لحاظ کنیم)
-  حتما این MapRouting را بعد از MapRouting اولیه بنویسید، کمی پیشتر، علت این امر توضیح داده شد و گفته شد اولین چیزی که MVC پس از درخواست ما میبیند به عنوان Routing بررسی می‌کند (درخواست اولیه) و چون ساختار  MapRouting فوق تا اندازه ای شبیه ساختار Default MapRouting است ممکن است با فراخوانی سایت مشکل ایجاد شود.
- میتوانید MapRouting
را کمی خاص‌تر هم بنویسیم : 
routes.MapRoute("NewsPaging", "News/index/{id}/{pid}",
            defaults: new
            {
                controller = "News",
                action = "Index",
                id = "all",
                pid = UrlParameter.Optional
            }
            );
اینکار بستگی به پروژه‌ی شما دارد، مپ روتینگ فوق این مزیت را دارد که با مپ روتینگ‌های دیگر به سختی قاطی میشود! یعنی حتا اگر قبل از مپ روتینگ دیفالت نوشته شود برنامه با مشکل مواجه نخواهد شد، چون اصلا شکل درخواست اولیه به سایت، چیزی شبیه این آدرس نیست. اما خاص بودن آن و همچنین نوع بهره گیری از آن با کمک Action یا ActioLink شاید شما را سردرگم خواهد کند.  
اما مشکل این MapRouting ‌ها چیست؟
درخواست به سایت آمده و قرار است سایت بارگذاری شود؛ ترتیب زیر در شیء routes ثبت شده است :  

در صفحه اول ما لینکی به شکل زیر گذاشته ایم : 
@Html.ActionLink("اخبار", "Index", "News", null, new { @class="btn btn-success"})
مشکلی پیش نخواهد آمد. روند فوق تکرار میشود و ActionLink برای ساختن لینک نهایی از MapRouting اول استفاده میکند (بدون اینکه به MapRouting دوم نگاه کند).

در صفحه اخبار، لینک "صفحه بعد" وجود دارد ، آیا این لینک به شکل صحیح نمایش داده میشود؟ خیر! نتیجه کار را ببینید : 
<a href="/News/index?pid=2">صفحه بعد</a>
 علت واضح است، ActionLink اصلا MapRouting دومی را نمی‌بیند و با همان MapRouting اولی لینک فوق را ساخته است. جای MapRouting ‌ها را عوض کنیم؟ مطمئنید با اینکار درخواست مربوط به صفحه اول شما سلامت به مقصد میرسد؟ مطمئنید با اینکار بقیه Action ‌ها و ActionLink ‌های موجود در صفحه اول شما به درستی تبدیل میشوند؟
به نظر میرسد یک طرح دقیق برای آدرس دهی شاید این مسائل را حل کند ولی نه وقت داریم و نه اعصاب. 


دقت کنید ما برای کار با روتینگ با دو مساله مواجه هستیم :
1. ساخته شدن لینک‌ها توسط هلپر‌ها (که از مپ روتینگ‌های ثبت شده ما پیروی میکند)
2. واکنش پروژه به درخواست‌های دریافتی و هدایت آن به اکشن‌های مورد نظر که کاملا به ترتیب ثبت مپ روتینگ‌ها در کالکشن Routes بستگی دارد .


 خوشبختانه یک هلپر به شکل زیر در MVC وجود دارد : 
 @Html.RouteLink("صفحه بعد", "PostPaging", new { action = "cat", controller = "News", id =. .., pid = ...})
مقدار اول متن لینک، مقدار دوم نام MapRouting ،مقدار سوم یک آبجکت است که نوع اکشن و کنترلر و پارامترهای مورد نظر MapRouting را تعیین میکند. به همین سادگی! این پاسخ به همان سوالیست که در ابتدای مقاله مطرح شد. "نام گذاری مپ روتینگ‌ها به چه درد میخورد؟"
نتیجه برای لینک موجود در صفحه اخبار چیزی شبیه شکل زیر خواهد شد :
<a href="/News/cat/all/2">صفحه بعد</a>
چند شکل دیگر از این هلپر را ببینید : 
@Html.RouteLink("اخبار", "Default", new { controller = "news" })

@Html.RouteLink("درباره ما", "pages", new RouteValueDictionary(new { controller="Page", action="Index", pagename="درباره-ما"}), new Dictionary<string, Object> { { "data-toggle", "popover" }, { "data-placement", "top" } })

همانطور که میبینید در RoutLink اولی، اخبار را به کمک MapRouting با نام default بازنویسی میکنیم و نتیجه چیزی شبیه کد زیر خواهد شد : 

<a href="/News">اخبار</a>

در RoutLink دومی اولا از یک RoutValueDictionary به جای یک آبجکت ساده استفاده کرده ایم و مقادیر را به شکل فوق به کنترلر و اکشن و ...نسبت داده ایم ثانیا برای بخش HTML نیز پراپرتی‌ها را به کمک یک دیکشنری ارسال میکنیم، به خاطر وجود "-" در یکی از خواص، راه دیگری غیر از اینکار نداریم.

اما دقت کنید که از یک MapRouting جدید استفاده کردیم که نامش pages است، 

 این MapRoutnig را قبل از دیگر Routing ‌ها می‌نویسیم؟ وسط دو MapRouting قبلی مینویسیم؟ آخر MapRouting ‌ها مینویسیم؟ آیا فرقی میکند؟ اگر سریع بگوییم خــیر! اشتباه کرده ایم. واقعا فرق میکند.

دقت کنید موضوع MapRouting فقط ایجاد یک لینک‌تر و تمیز نیست؛ RoutLink یک لینک تمیز بر اساس مپ روتینگی که نامش برده شده ایجاد میکند اما تضمین نمیکند که با کلیک بر روی لینک به هدف برسیم و به خطای 404 برخورد نکنیم! اگر روی لینک کلیک کنید آدرس شروع به تفسیر شدن میکند و این تفسیر اصلا ربطی به نامی که به RoutLink داده ایم ندارد و ترتیب موجود در کالکشن ایجاد شده در RoutConfig تعیین کننده است.(آبجکت Routes ) اگر MapRouting فوق را در انتهای بقیه بگذاریم صفحه اول لود میشود ولی با کلیک روی "درباره ما" صفحه پیغام خطا خواهد داد.
 باید به یاد داشته باشیم برای اجرای درخواست (کلیک روی لینک)، آنچه برای ASP.NET MVC اهمیت دارد، ترتیب قرار گیری MapRouting ‌ها در RouteRegister است  و ما به کمک RoutLink تنها مشکل ساخت لینک‌ها بر اساس قالب MapRouting مورد نظرمان را حل کردیم و این به ما تضمینی برای هدایت آن لینک به مکان درست را نخواهد داد. 

اگر ترتیب به شکل زیر باشد :

1
2
3
باشد. درخواست اولیه برای بالا آمدن سایت به مشکل برخورد نمی‌کند چون همان مپ روتینگ 1 اجرا میشود. اما مشکل فوق به وجود خواهد آمد و خطای 404 با کلیک بر روی "درباره ما" نمایش داده خواهد شد چون با کلیک روی "درباره ما" مپ روتینگ شماره 1 وارد عمل میشود.

اگر ترتیب به شکل زیر باشد :

3
2
1
آیا اصلا صفحه اول سالم لود خواهد شد؟ خیر! درخواست نسبی " / " (یا به طور کامل http://mydomain.com ) شماره 3 را به خیر پشت سر میگذارد، چون اصلا چیزی به نام page در آدرس وجود ندارد که از این MapRouting بخواهد پیروی کند. اما در شماره 2 گیر می‌افتد چون این فرمت را حفظ کرده است :
"{controller}/{action}/{id}/{pid}"
Pid که اختیاریست (بر اساس قوانین تعریف شده در مپ روتینگ شماره 2) بقیه موارد نیز حالت دیفالت دارند، یعنی اگر تعریف نشده باشند (مثل همین درخواست) خودبخود جایگذاری میشوند. پس به طور کلی صفحه اول ما تغییر می‌کند و اکشن index از کنترلر Home اجرا نمیشود و به جایش اکشن Index از کنترلر News اجرا میشود و صفحه اول را از دست میدهیم و صفحه اخبار را میبینیم! نتیجه اینکه نباید هرگز 2 را قبل از 1 قرار دهیم.
اگر ترتیب به شکل زیر باشد :
3
1
2
این همان چیزیست که مد نظر ماست. اولا 1 قبل از 2 است و صفحه اول برای لود شدن به مشکل برخورد نمیکند.
ثانیا وقتی روی "درباره ما" کلیک میکنیم همان شماره 3 فراخوانی میشود و بقیه مپ روتینگ‌ها اعمال نمیشوند.

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

نظرات مطالب
نوع‌های نال نپذیر در TypeScript
یک نکته‌ی تکمیلی: اضافه شدن strictPropertyInitialization به TypeScript 2.7

در نگارش 2.7 اگر یک چنین تعریفی را داشته باشید:
export class MovieComponent {

  @Input() movie: Movie;

}
خطای زیر را دریافت خواهید کرد:
Error! Property movie has no initializer and is not assigned directly in the constructor.
یک روش برطرف کردن این خطا، نال‌پذیر تعریف کردن این خاصیت و سپس مقدار دهی اولیه‌ی به آن است:
@Input() movie: Movie | null = null;
و یا در مثال زیر می‌توان مشخص کرد که baz می‌تواند undefined هم باشد:
class C {
    baz: boolean | undefined;
}
روش دیگر، مقدار دهی اولیه، حین تعریف یک خاصیت است:
class C {
    bar = "hello";
}
و یا می‌توان این بررسی را به صورت زیر خاموش کرد:
class C {
    foo!: number;

    ngOnInit() {
        this.foo = 0;
    }
}
با استفاده از عملگر ! به کامپایلر اعلام می‌کنیم که این خاصیت حتما قرار است توسط روشی کمکی مقدار دهی اولیه شود و نال یا بدون مقدار نیست و استفاده‌ی از آن در این کلاس امن است.
نظرات مطالب
نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular
با تشکر از مطلب مفیدتون.
در اینجا یکسری دایرکتیو جهت اعتبار سنجی سفارشی بنده پیاده سازی کرده ام (فعلا فقط دو تا!) که نیازمند همکاری دوستان علاقمند دارد. (میتوان اعتبار سنجی‌های مربوط به فرمهای فارسی را نیز در اینجا به عنوان مرجعی مناسب برای استفاده سایرین نیز ایجاد کرد. از جمله اعتبار سنجی کدملی، شناسه ثبت شرکت، فارسی بودن اعداد در ورودی و ... که بخشی از این توابع در اینجا پیاده سازی شده است.)
همچنین در این دایرکتیوها دایرکتیوی به نام RemoteValidation پیاده سازی شده است که اعتبار سنجی نامتقارن را انجام میدهد. مشاهده کدهای این قسمت، برای آشنایی با اعتبار سنجی نامتقارن در Angular، خالی از لطف نیست.
نظرات مطالب
Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
یک نکته تکمیلی: امکان انتقال کدهای #C یک کامپوننت، به یک فایل مجزای code behind نیز وجود دارد

روال متداول کار با کامپوننت‌های Razor، قرار دادن کدهای #C مربوط به آن‌ها، در قسمت {}code@ همان فایل، با پسوند razor. است. می‌توان جهت بالا بردن قابلیت نگهداری کدهای کامپوننت‌ها، برخوردار شدن از intellisense بهتری و همچنین کاهش حجم فایل‌های razor. که در نهایت به خوانایی بیشتر و ساده‌تر آن‌ها منتهی می‌شود، قطعه‌ی {}code@ را به یک فایل سی‌شارپ مجزا منتقل کرد؛ با این شرایط و نکات خاص:
- اگر برای مثال نام یک کامپوننت، login.razor است، فایل code behind آن باید با همان نام شروع شود و ختم به cs. شود؛ یعنی در این مثال باید دقیقا نام login.razor.cs را داشته باشد و محتوای ابتدایی آن اکنون به صورت زیر خواهد بود:
namespace ProjectName.Folder1.Folder2
{
    public class Login
    {

    }
}
- پس از آن، یک چنین قطعه کدی، کامپایل نخواهد شد؛ چون کامپوننت login.razor که در پوشه‌ی folder1/folder2 واقع شده‌است نیز توسط کامپایلر به یک چنین کلاسی ترجمه می‌شود. برای رفع این مشکل، باید کلاس را به صورت partial تعریف کرد تا کدهای آن جزئی از کدهای کامپوننت login.razor شوند:
namespace ProjectName.Folder1.Folder2
{
    public partial class Login
    {

    }
}
- اکنون جهت انتقال کدهای قطعه {}code@، فقط کافی است آن‌ها را انتخاب و cut کرده و سپس در بدنه‌ی کلاس فوق، paste کنیم. در این حالت نیازی نیست سطوح دسترسی قسمت‌های مختلف کد، مانند private و protected را تغییر داد و همه چیز مانند قبل خواهد بود.

چند نکته:
- سرویس‌هایی که با دایرکتیو inject@ تعریف می‌شوند، در اینجا به صورت زیر توسط Attributes تعریف خواهند شد:
namespace ProjectName.Folder1.Folder2
{
    public partial class Login
    {
        [Inject]
        public NavigationManager NavigationManager { set; get; }

        // ...
    }
}
- فایل سراسری Imports.razor_ که جهت تعریف فضاهای نام تکراری مورد استفاده قرار می‌گرفت، در اینجا کاربردی نداشته و نیاز است فضاهای نام را همانند کدهای متداول #C، در ابتدای فایل cs. جاری ذکر کرد.
- جهت بهبود Intellisense متناظر با قابلیت‌های Blazor، بهتر است کلاس partial تعریف شده، از کلاس پایه ComponentBase نیز ارث بری کند:
namespace ProjectName.Folder1.Folder2
{
    public partial class Login : ComponentBase
    {
        [Inject]
        public NavigationManager NavigationManager { set; get; }

        // ...
    }
}
مطالب
بررسی تغییرات Blazor 8x - قسمت سوم - روش ارتقاء برنامه‌های Blazor Server قدیمی به دات نت 8
در قسمت قبل، با نحوه‌ی رندر سمت سرور و روش فعالسازی قابلیت‌های تعاملی در این حالت، آشنا شدیم. از این نکات می‌توان جهت ارتقاء ساختار پروژه‌های قدیمی Blazor Server به Blazor Server 8x استفاده کرد. البته همانطور که پیشتر نیز عنوان شد، در دات نت 8 دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست و اگر دقیقا همین موارد مدنظر هستند، آن‌ها را می‌توان با تنظیم سطح رندر و میزان تعاملی که مدنظر است، شبیه سازی کرد و یا حتی هر دو را هم با هم در یک پروژه داشت.


1) به‌روز رسانی شماره نگارش دات‌نت

اولین قدم در جهت ارتقاء پروژه‌های قدیمی، تغییر شماره نگارش TargetFramework موجود در فایل csproj. به net8.0 است. پس از اینکار نیاز است تمام بسته‌های نیوگت موجود را نیز به نگارش‌های جدیدتر آن‌ها ارتقاء دهید.


2) فعالسازی حالت SSR تعاملی سمت سرور

پایه‌ی تمام تغییرات انجام شده‌ی در Blazor 8x، قابلیت SSR است و تمام امکانات دیگر برفراز آن اجرا می‌شوند. به همین جهت پس از ارتقاء شماره نگارش دات‌نت، نیاز است SSR را فعال کنیم و برای اینکار باید به هاست ASP.NET Core بگوئیم که درخواست‌های رسیده را به کامپوننت‌های Razor هدایت کند. بنابراین، به فایل Program.cs مراجعه کرده و دو تغییر زیر را به آن اعمال کنید:
// ...
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
// ...
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
یک نمونه‌ی کامل از فایل Program.cs را در قسمت قبل مشاهده کردید و یا حتی می‌توانید دستور dotnet new blazor --interactivity Server را جهت ساخت یک پروژه‌ی آزمایشی جدید بر اساس SDK دات نت 8 و ایده گیری از آن، اجرا کنید.

در اینجا ترکیب کامپوننت‌های تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.

یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آن‌را به None تنظیم کنیم، کلیات ساختار پروژه‌ای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینه‌های فوق را نداریم و به صورت زیر ساده شده‌است:
// ...

builder.Services.AddRazorComponents();

// ...

app.MapRazorComponents<App>();
در این نوع برنامه‌ها نمی‌توان جزایر/قسمت‌های تعاملی Blazor Server را در صفحات و کامپوننت‌های SSR، تعریف کرد. در مورد جزایر تعاملی، در مطالب بعدی بیشتر بحث خواهیم کرد.


3) ایجاد فایل جدید App.razor

در دات نت 8، دیگر خبری از فایل آغازین Host.cshtml_ پروژه‌های Blazor Server قدیمی نیست و کدهای آن با تغییراتی، به فایل جدید App.razor منتقل شده‌اند. در این قسمت، کار هدایت درخواست‌های رسیده به کامپوننت‌های برنامه رخ می‌دهد و از این پس، صفحه‌ی ریشه‌ی برنامه خواهد بود.


در این تصویر، مقایسه‌ای را بین جریان پردازش یک درخواست رسیده در دات نت 8، با نگارش قبلی Blazor Server مشاهده می‌کنید. در دات نت 8، فایل Host.cshtml_ (یک Razor Page آغازین برنامه) با یک کامپوننت Razor به نام App.razor جایگزین شده‌است و فایل قدیمی App.razor این پروژه‌ها به Routes.razor، تغییر نام یافته‌است.

نمونه‌ای از فایل App.razor جدید را که در قسمت قبل نیز معرفی کردیم، در اینجا با جزئیات بیشتری بررسی می‌کنیم:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="MyApp.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>
در این فایل جدید تغییرات زیر رخ داده‌اند:
- تمام دایرکتیوهای تعریف شده مانند page ،@addTagHelper@ و غیره حذف شده‌اند.
- base href تعریف شده اینبار فقط با یک / شروع می‌شود و نه با /~. این مورد خیلی مهم است! اگر به آن دقت نکنید، هیچکدام از فایل‌های استاتیک برنامه مانند فایل‌های css. و js.، بارگذاری نخواهند شد!
- پیشتر برای رندر HeadOutlet، از یک تگ‌هلپر استفاده می‌شد. این مورد در نگارش جدید با یک کامپوننت ساده جایگزین شده‌است.
- تمام component tag helper‌های پیشین حذف شده‌اند و نیازی به آن‌ها نیست.
- ارجاع پیشین فایل blazor.server.js با فایل جدید blazor.web.js جایگزین شده‌است.

یک نکته: همانطور که مشاهده می‌کنید، فایل App.razor یک کامپوننت است و اینبار به همراه تگ <script> نیز شده‌است. یعنی در این نگارش از Blazor می‌توان اسکریپت‌ها را در کامپوننت‌ها نیز ذکر کرد؛ فقط با یک شرط! این کامپوننت حتما باید SSR باشد. اگر این تگ اسکریپتی را در یک کامپوننت تعاملی ذکر کنید، همانند قابل (و نگارش‌های پیشین Blazor) با خطا مواجه خواهید شد.


4) ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده

همانطور که عنوان شد، فایل قدیمی App.razor این پروژه‌ها به Routes.razor تغییر نام یافته‌است که درج آن‌را در قسمت body مشاهده می‌کنید. محتوای این فایل نیز به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
</Router>
این محتوای جدید، فاقد ذکر کامپوننت NotFound قبلی است؛ از این جهت که سیستم مسیریابی جدید Blazor 8x با ASP.NET Core 8x یکپارچه است و نیازی به این کامپوننت نیست. یعنی اگر قصد مدیریت آدرس‌های یافت نشده‌ی برنامه را دارید، باید همانند ASP.NET Core به صورت زیر در فایل Program.cs عمل کرده:
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
و سپس کامپوننت جدید StatusCode.razor را برای مدیریت آن به نحو زیر به برنامه اضافه کنید و بر اساس responseCode دریافتی، واکنش‌های متفاوتی را ارائه دهید:
@page "/StatusCode/{responseCode}"

<h3>StatusCode @ResponseCode</h3>

@code {
    [Parameter] public string? ResponseCode { get; set; }
}

یک نکته: اگر پروژه‌ای را بر اساس قالب dotnet new blazor --interactivity Server ایجاد کنیم، در فایل Program.cs آن، چنین تنظیمی اضافه شده‌است:
if (!app.Environment.IsDevelopment())
{
   app.UseExceptionHandler("/Error", createScopeForErrors: true);
}
که دقیقا معادل رفتاری است که در برنامه‌های ASP.NET Core قابل مشاهده‌است. این مسیر Error/، به کامپوننت جدید Components\Pages\Error.razor نگاشت می‌شود. بنابراین اگر در برنامه‌های جدید Blazor Server، استثنائی رخ دهد، با استفاده از میان‌افزار ExceptionHandler فوق، کامپوننت Error.razor نمایش داده خواهد شد.  باید دقت داشت که این کامپوننت ویژه، تحت هر شرایطی در حالت یک static server component رندر می‌شود.

سؤال: در اینجا (برنامه‌های Blazor Server) چه تفاوتی بین UseExceptionHandler و UseStatusCodePagesWithRedirects وجود دارد؟
میان‌افزار UseExceptionHandler برای مدیریت استثناءهای آغازین برنامه، پیش از تشکیل اتصال دائم SignalR وارد عمل می‌شود. پس از آن و تشکیل اتصال وب‌سوکت مورد نیاز، فقط از میان‌افزار UseStatusCodePagesWithRedirects استفاده می‌کند.
اگر علاقمند نیستید تا تمام خطاهای رسیده را همانند مثال فوق در یک صفحه مدیریت کنید، می‌توانید حداقل سه فایل زیر را به برنامه اضافه کنید تا خطاهای متداول یافت نشدن آدرسی، بروز خطایی و یا عدم دسترسی را مدیریت کنند:

404.razor
@page "/StatusCode/404"

<PageTitle>Not found</PageTitle>

<h1>Not found</h1>
<p role="alert">Sorry, there's nothing at this address.</p>

500.razor
@page "/StatusCode/500"

<PageTitle>Unexpected error</PageTitle>

<h1>Unexpected error</h1>
<p role="alert">There was an unexpected error.</p>

401.razor
@page "/StatusCode/401"

<PageTitle>Not Authorized</PageTitle>

<h1>Not Authorized</h1>
<p role="alert">Sorry, you are not authorized to access this page.</p>


5) تعاملی کردن سراسری برنامه

پس از این تغییرات اگر برنامه را اجرا کنید، بر اساس روش جدید static server-side rendering کار می‌کند و تعاملی نیست. یعنی تمام کامپوننت‌های آن به صورت پیش‌فرض، یکبار بر روی سرور رندر شده و خروجی آن‌ها به مرورگر کاربر ارسال می‌شوند و هیچ اتصال دائم SignalR ای برقرار نخواهد شد. برای فعالسازی سراسری قابلیت‌های تعاملی برنامه و بازگشت به حالت Blazor Server قبلی، به فایل App.razor مراجعه کرده و دو تغییر زیر را اعمال کنید تا به صورت خودکار به تمام زیرکامپوننت‌ها، یعنی کل برنامه، اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" />
...
<Routes @rendermode="@InteractiveServer" />

نکته 1: اجرای دستور زیر در دات‌نت 8، قالب پروژه‌ای را ایجاد می‌کند که رفتار آن همانند پروژه‌های Blazor Server نگارش‌های قبلی دات‌نت است (این مورد را در قسمت قبل بررسی کردیم)؛ یعنی همه‌جای آن به صورت پیش‌فرض، تعاملی است:
dotnet new blazor --interactivity Server --all-interactive

نکته 2: البته ...  InteractiveServer، دقیقا همان حالت پیش‌فرض برنامه‌های Blazor Server قبلی نیست! این حالت رندر، به صورت پیش‌فرض به همراه پیش‌رندر (pre-rendering) هم هست. یعنی در این حالت، روال رویدادگردان OnInitializedAsync یک کامپوننت، دوبار فراخوانی می‌شود (که باید به آن دقت داشت و عدم توجه به آن می‌تواند سبب انجام دوباره‌ی کارهای سنگین آغازین یک کامپوننت شود)؛ یکبار برای پیش‌رندر صفحه به صورت یک HTML استاتیک (بدون فعال سازی هیچ قابلیت تعاملی) که برای موتورهای جستجو و بهبود SEO مفید است و بار دیگر برای فعالسازی قسمت‌های تعاملی آن، درست پس از زمانیکه اتصال SignalR صفحه، برقرار شد (البته امکان فعالسازی حالت پیش‌رندر در Blazor Server قبلی هم وجود داشت؛ ولی مانند Blazor 8x، به صورت پیش‌فرض فعال نبود). در صورت نیاز، برای سفارشی سازی و لغو آن می‌توان به صورت زیر عمل کرد:
@rendermode InteractiveServerRenderModeWithoutPrerendering

@code{
  static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = 
        new InteractiveServerRenderMode(false);
}

در مورد پیش‌رندر و روش مدیریت دوبار فراخوانی شدن روال رویدادگردان OnInitializedAsync یک کامپوننت در این حالت، در قسمت‌های بعدی این سری بیشتر بحث خواهد شد.
 
مطالب
نحوه ایجاد الگوی Singleton به صورت جنریک
در برخی از مواقع، ایجاد یک وهله از یک کلاس کاری هزینه بر می‌باشد. بنابراین نیاز است تا فقط یک وهله از آن کلاس را ایجاد و تا آخر اجرای برنامه از آن استفاده کرد. این راه حل در قالب یک الگوی طراحی به نام Singleton معرفی شده است. حال می‌خواهیم با استفاده از امکانات جنریک، کلاسی را طراحی کنیم تا عملیات ساخت وهله‌ها را انجام دهد.
نکاتی که در طراحی یک الگوی Singleton باید مد نظر داشت این است که:
  1. دسترسی سازنده کلاس Singleton را از نوع Private تعیین کنیم.
  2. یک فیلد استاتیک از نوع کلاس Singleton تعریف کنیم.
  3. یک خاصیت از نوع استاتیک فقط خواندنی (یعنی فقط get داشته باشد) تعریف کرده تا فیلد استاتیک را مقداردهی و Return کند. به جای پروپرتی میتوان از یک متد استاتیک نیز استفاده کرد.
public class SingletonClassCreator<T> where T:class , new()
    {
        private static T _singletoneInstance;
        private static readonly object Lock = new object();

        public static T SingletoneInstance
        {
            get
            {
                lock (Lock)
                {
                    if (_singletoneInstance == null)
                    {
                        _singletoneInstance = new T();                        
                    }
                }
                return _singletoneInstance;
            }            
        }

        private SingletonClassCreator()
        {            
        }
    }
برای ایجاد حالت Tread-Safe در برنامه هایی که امکان دسترسی همزمان به یک شیء (مثلا در برنامه‌های وب) وجود دارد، از یک بلاک Lock استفاده شده است تا در هر لحظه فقی یک نخ قادر به ایجاد Singleton شود.
حال برای ایجاد وهله‌های Singleton از کلاسهای مورد نظر به صورت زیر عمل میکنیم
public class FirstSingleton
    {
        public int Square(int input)
        {
            return input*input;
        }
    }
static void Main(string[] args)
        {            
            var firstSingletone = SingletonClassCreator<FirstSingleton>.SingletoneInstance ;
            Console.WriteLine(firstSingletone.Square(12));            
            Console.ReadKey();
        }
در خط اول، با تعریف یک متغیر و قرار دادن وهله استاتیک که بوسیله پروپرتی استاتیک SingletoneInstance برگشت داده میشود، یک شی Singleton از کلاس FirstSingleton را ایجاد میکنیم.
مطالب
شروع کار با webpack - قسمت سوم
در مطلب قبلی با فایل‌های پیکربندی وبپک، وب سرور وبپک، لودر‌ها و ... آشنا شدیم .


استفاده از preLoader‌ها در وبپک 
پیش‌تر با Loader‌ها آشنا شدیم و دلیل استفاده‌ی از آنها نیز ذکر و Loader تایپ اسکریپت را نیز نصب کرده و با استفاده از آن فایل‌های پروژه را ترنسپایل کردیم. اما ممکن است که همه‌ی کارها در استفاده از یک Loader خلاصه نشوند. ممکن است بخواهید از یک ابزار Linting مانند jsHint  قبل از اجرای Loader ها بهره ببرید و این دقیقا کاری است که به preLoader‌ها سپرده می‌شود. به عنوان مثال پیش لودر jsHint را نصب خواهیم کرد. اضافه کردن preLoader‌ها در فایل پیکربندی وبپک تفاوتی با Loader‌ها نخواهد داشت و دارای همان قسمت‌هایی است که برای Loader‌ها تعریف کردیم.
پیش از هر کاری ابتدا jsHint را نصب کرده و سپس loader آن را نیز نصب می‌کنیم. دستورات مورد نیاز، در ادامه آورده شده اند:
npm install -D jsHint jsHint-loader
سپس در ادامه به فایل پیکربندی وبپک مراجعه کرده و قسمت preLoader را به آن اضافه می‌کنیم:
module.exports = {
    entry:['./shared.js','./main.ts']
    ,output:{
        filename:'bundle.js'
    }
    ,watch :true
    ,module:{
        preLoaders:[
            {
                test:/\.js$/
                ,exclude:/node_modules/
                ,loader:'jshint-loader'
            }
        ],
        loaders:[
            {
                test:/\.ts$/
                ,exclude:/node_modules/
                ,loader:'ts-loader'
            }
        ]
    }
    
}

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


همچنین برای تکمیل قابل ذکر است که وبپک دارای postLoaders نیز می‌باشد که پس از Loader‌های اصلی اجرا می‌شوند.
لیست کاملی از Loader‌ها را می‌توانید در اینجا مشاهده کنید .تمامی لودرهای وبپک

Minify کردن باندل‌ها با استفاده از وبپک

در صورتی که باندل ساخته شده تا به اینجای کار را باز کرده باشید، مشاهده کرده‌اید که باندل ساخته شده Minify شده نیست. ساده‌ترین روش جهت Minify کردن باندل ساخته شده در هنگام فراخوانی وبپک با استفاده از یک پرچم در خط فرمان می‌باشد. دستور مورد نیاز در ادامه آورده شده است.
// در حالتی که به صورت محلی وبپک نصب شده است
npm run webpack -- -p
// درحالتی که وبپک به صورت سراسری اجرا می‌شود
webpack -p
حال در صورتی که به باندل ساخته شده مراجعه کنید، باندل در حالت Minify شده قرار دارد.

اضافه کردن فایل پیکربندی مخصوص بیلد‌های اصلی پروژه

قطعا شما نیز در حین توسعه‌ی پروژه از دستورات لاگ یا اندازه گیری زمان اجرای یک قطعه کد و ... استفاده می‌کنید و حضور این کد‌ها در باندل نهایی دلیلی ندارد. یک راهکار این است که کدهایی را که فقط جهت توسعه‌ی پروژه و دیباگ بودند و سودی در نتیجه‌ی نهایی ندارند، به صورت دستی پاک کنیم و در صورتی که حجم این طور دستورات بالا باشند، قطعا کار جالبی نخواهد بود و همچنین حذف کلی این دستورات نیز در ادامه برای برگشت به پروژه ممکن است مشکل زا باشد.
راهکاری که با وبپک می‌توان پیش گرفت این است که از یک Loader جهت بیلد‌های اصلی استفاده کرده و مثلن تمامی کامنت‌ها و دستورات لاگ کننده و ... را از بیلد نهایی حذف کنیم. جهت اینکار یک فایل پیکربندی مخصوص را به بیلدهای اصلی به پروژه اضافه می‌کنیم و اسم فایل را webpack.prod.config.js می‌گذاریم.
قدم بعدی نصب یک loader می‌باشد که وظیفه‌ی حذف مواردی را دارد که برای آن مشخص خواهیم کرد. این لودر strip-loader نام دارد و با دستور زیر آن را در پروژه اضافه می‌کنیم.
npm install -D strip-loader
سپس وارد فایل پیکربندی که فقط جهت تولید باندل‌های اصلی پروژه ایجاد کرده‌ایم (webpack.prod.config.js) می‌شویم و کد‌های زیر را وارد می‌کنیم.
// webpack.prod.config.js
 //تنظیمات قبلی را می‌خوانیم
var devConfig = require("./webpack.config.js");
// لودری که وظیفه‌ی حذف کردن دارد را وارد می‌کنیم
var stripLoader = require("strip-loader");

// مانند قبل یک آبجکت با موارد مورد نظر برای لودر می‌سازیم
var stripLoaderConfig = {
    test:[/\.js$/,/\.ts$/],
    exclude :/node_modules/
    ,loader:stripLoader.loader("console.log")
}

// اضافه کردن به لیست لودرهای قبلی
devConfig.module.loaders.push(stripLoaderConfig);

// و در آخر اکسپورت کردن تنظیمات جدید وقبلی
module.exports = devConfig;
در توضیح کد‌های بالا در خط اول ابتدا فایل پیکربندی را که در توسعه‌ی عادی پروژه استفاده می‌شد، می‌خوانیم و در آبجکتی با نام devConfig ذخیره می‌کنیم. مزیت اینکار این می‌باشد که از تعریف تنظیمات تکراری و مورد نیاز جلوگیری می‌کند (نکته : اگر بخاطر داشته باشید در مطلب قبلی ذکر شد که فایل‌های پیکربندی در فرمت commonjs می‌باشند و در نتیجه امکان وارد کردن آنها با استفاده از تابع require امکان پذیر است).
خط بعدی نیز loader ی که وظیفه‌ی حذف موارد مورد نظر ما را دارد وارد می‌کنیم و در ادامه یک آبجکت را با تعاریفی که قبلن از لودر‌ها داشتیم می‌سازیم. تنها نکته در قسمت تعریف اسم لودر می‌باشد.
loader:stripLoader.loader("console.log")
در کد بالا مواردی را که مورد نیاز است از سورس اصلی در سورس نهایی حذف شود، به لودر معرفی می‌کنیم. در اینجا تمامی دستوراتی که شامل console.log می‌شوند از بیلد نهایی باندل حذف خواهند شد.
تنها مرحله‌ی باقی مانده، فراخوانی وبپک با استفاده از فایل پیکربندی جدید می‌باشد. برای اینکار با استفاده از پرچم config محل فایل جدید پیکربندی را به وبپک معرفی می‌کنیم تا از فایل پیکربندی پیش فرض قبلی استفاده نکند.
// در حالتی که وبپک به صورت محلی نصب شده است
npm run webpack -- --config webpack.prod.config.js -p
// در حالتی که وبپک به صورت سراسری نصب شده باشد
webpack --config webpack.prod.config.js -p
پس از اجرای این دستور و باز کردن صفحه‌ی index.html خواهید دید که پیغامی در کنسول مرورگر ظاهر نخواهد شد و دستورات console.log همگی حذف شده‌اند.
توجه داشته باشید که اگر برای ساخت باندل از وبپک استفاده کرده‌اید، برای میزبانی فایل‌های پروژه از وب سرور وبپک استفاده نکنید و از وب سروری دیگر (مانند http-server یا IIS و ...) استفاده کنید؛ چرا که باندل توسط وب سرور وبپک دوباره ساخته می‌شود و تغییرات از بین می‌روند. در صورتی که می‌خواهید از وب سرور وبپک برای میزبانی بیلد نهایی پروژه نیز استفاده کنید، فایل پیکربندی بیلد نهایی را نیز به وب سرور وبپک با استفاده از دستور زیر معرفی کنید:
// زمانی که وبپک به صورت محلی در پروژه نصب شده است
npm run webpackserver -- --config webpack.prod.config.js  -p
//در حالتی که وبپک به صورت گلوبال ( سراسری ) نصب می‌باشد
webpack-dev-server --config webpack.prod.config.js  -p

مدیریت فایل و فولدرها با استفاده وبپک
تا به اینجای کار اگر به ساختار چینش فایل‌ها در پروژه دقت کنید خواهید دید که همگی فایل‌ها در مسیر اصلی پروژه قرار دارند و این روش مناسبی برای مدیریت فایل‌ها و فولدرها در پروژه‌های واقعی و بزرگ نیست. در ادامه قصد داریم این مسئله را حل کرده و ساختار مشخصی را برای محل قرارگیری فایل‌های پروژه با کمک وبپک ایجاد کنیم.
در اولین قدم فولدری را برای اسکریپت‌ها با نام js ایجاد کرده و اسکریپت‌ها را به این فولدر انتقال می‌دهیم.
در قدم دوم برای فایل‌های استاتیک پروژه مانند صفحات html و ... فولدر دیگری را با نام assets ایجاد می‌کنیم و این گونه فایل‌ها را در آن قرار خواهیم داد.
تا اینجای کار در صورتی که وبپک را اجرا کنید، مسیرهای جدید را پیدا نخواهد کرد و دچار خطا خواهد شد. پس به فایل پیکربندی ( webpack.config.js ) مراجعه کرده و وبپک را از ساختار جدید پروژه خبردار می‌کنیم.
// new webpack.config.js file
//ماژول توکار نود جی اس
var path = require("path");

module.exports = {
    // مشخص کردن زمینه برای فایل‌های ورودی
    context:path.resolve("js"),
    entry:['./shared.js','./main.ts']
    ,output:{
       // مشخص کردن محل قرارگیری باندل ساخته شده
        path:path.resolve("build/js"),
      // درخواست از سمت چه مسیری برای باندل خواهد آمد ؟
        publicPath:"assets/js",
        filename:'bundle.js'
    }
    ,
    devServer:{
        //راهنمایی برای وب سرور جهت اینکه فایل‌ها را از چه محلی سرو کند
        contentBase:"assets"
    }

    ,watch :true
    ,module:{
        
        loaders:[
            {
                test:/\.ts$/
                ,exclude:/node_modules/
                ,loader:'ts-loader'
            }
        ]
    }
    
}
در خط اول فایل پیکربندی جدید، ماژول توکار path از نود جی اس را وارد می‌کنیم و سپس کلید جدیدی را به تنظیمات وبپک با نام context اضافه می‌کنیم که زمینه‌ی فایل‌های ورودی را مشخص خواهد کرد. قبلا ذکر شد که فولدری با نام js را ساخته و اسکریپت‌ها را در آن قرار می‌دهیم. پس با کمک ماژول path این مسیر را به وبپک معرفی می‌کنیم.
context:path.resolve("js")
تغییر بعدی را در تنظیمات برای ساخت باندل داریم که مشخص کردن مسیر قرارگیری جدید باندل می‌باشد که با کلید جدیدی با نام path، مسیر قرارگیری باندل پس از ساخته شدن را به وبپک اطلاع می‌دهیم و در ادامه کلید دیگری با نام publicPath اضافه شده که راهنمایی برای وب سرور وبپک می‌باشد تا با استفاده از آن درخواست‌هایی که به مسیر مشخص شده می‌آیند، از مسیری که در کلید path نامیده شده سرو شوند.
// تغییرات در شی output
// این کلید جدید مسیر قرار گیری جدید باندل را به وبپک اطلاع می‌دهد
path:path.resolve("build/js"),
//  راهنما برای وب سرور وبپک جهت میزبانی مسیر زیر از کلید بالا
publicPath:"assets/js",
آخرین تغییر در فایل پیکربندی، مربوط به اضافه شدن آبجکت جدید devServer می‌باشد که در آن کلیدی اضافه شده که مسیر اصلی فایل‌های میزبانی شده را اعلام می‌کند. به طور مثال فایل html اصلی پروژه را بالاتر اشاره کردیم که در این مسیر قرار می‌دهیم.
در نهایت وارد فایل index.html می‌شویم و مسیر جدید باندل را به آن معرفی میکنیم.
//index.html

<html>
    <head>
        first part of webpack tut!
    </head>
    <body>
        <h1>webpack is awesome !</h1>
        <script src="assets/js/bundle.js"></script>
    </body>
</html>
قابل مشاهده است که مسیر باندل ذکر شده در اینجا وجود خارجی ندارد و وبپک آن را با کمک تنظیماتش، به صورت پویا پیدا خواهد کرد. در تصویر زیر سعی بر روشن‌تر شدن این مسئله شده است.


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


ساخت فایل‌های سورس مپ  (source map)


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

// فعال کردن ساخت سورس مپ‌ها 
npm run webpack -- -d 
// یا  در هنگام نصب گلوبال
webpack -d
// جهت استفاده به همراه وب سرور
npm run webpackserver -- -d
// یا به صورت نصب گلوبال
webpack-dev-server -d
راه دوم با استفاده از انجام تغییرات در فایل پیکربندی وبپک می‌باشد؛ به این صورت که کلیدی را به این فایل اضافه می‌کنیم.
// webpack.config.js
// کلید جدید اضافه شده در فایل پیکربندی
devtool:"#source-map"
حال در هنگام فراخوانی وبپک فایل سورس مپ نیز ساخته خواهد شد و احتیاجی به استفاده از پرچم خط فرمان در هنگام فراخوانی نیست.
با اجرای وب سرور وبپک خواهید دید که سورس مپ‌ها در منوی توسعه دهنده‌ی مرورگر قابل دستیابی می‌باشند.

ساخت چندین باندل گوناگون

قصد داریم به پروژه، دو صفحه‌ی دیگر را نیز با نام‌های aboutme و contact اضافه کنیم. هر یک از این صفحات اسکریپت مخصوص به خود را خواهد داشت و باندل نهایی نیز شامل تمامی آنها خواهد شد. در صورتی که این خروجی مطلوب ما نباشد و به طور مثال بخواهیم مکانیزمی شبیه به lazy loading اسکریپت‌ها را داشته باشیم و فقط زمانی اسکریپت‌ها بارگذاری شوند که به آنها احتیاج باشد، برای انجام این کار با وبپک به صورت زیر عمل خواهیم کرد.
دو صفحه‌ی html جدید را با عناوین ذکر شده‌ی بالا به پوشه‌ی assets اضافه می‌کنیم و برای هریک نیز اسکریپتی با همان نام خواهیم ساخت و در پوشه‌ی js قرار می‌دهیم.
محتوای صفحات بدین شکل می‌باشد.
// index.html file
<html>
<head>

    <title>
        third part of webpack tut!
    </title>
</head>

<body>
    <nav>
        <a href="aboutme.html">about me</a>
        <a href="contact.html">contact</a>
    </nav>
    <h1>webpack is awesome !</h1>
    <script src="assets/js/shared.js"></script>
    <script src="assets/js/index.js"></script>
</body>

</html>

// aboutme.html file
<html>

<head>

    <title>
        about me page !
    </title>
</head>

<body>
    <nav>
        <a href="index.html">index</a>
        <a href="contact.html">contact</a>
    </nav>
    <h1>webpack is awesome !</h1>
    <script src="assets/js/shared.js"></script>
    <script src="assets/js/aboutme.js"></script>
</body>

</html>

// contact.html file

<html>

<head>

    <title>
        contact me page !
    </title>
</head>

<body>
    <nav>
        <a href="index.html">index</a>
        <a href="aboutme.html">about me</a>
    </nav>
    <h1>webpack is awesome !</h1>
    <script src="assets/js/shared.js"></script>
    <script src="assets/js/contact.js"></script>
</body>

</html>
در هر یک از صفحات یک اسکریپت مخصوص آن صفحه و همچنین یک اسکریپت با نام shared.js که قبلا فرض کردیم نقش ماژولی را دارد که در سرتاسر پروژه از آن استفاده می‌شود، اضافه شده است. حال این تغییرات را در فایل پیکربندی وبپک به آن معرفی می‌کنیم.
var path = require("path");
var webpack = require("webpack");
// وارد کردن پلاگینی از وب پک برای ساخت تکه‌های مختلف اسکریپت‌ها 
// معرفی اسکریپت shared.js
var commonChunkPlugin = new webpack.optimize.CommonsChunkPlugin("shared.js");
module.exports = {
    context:path.resolve("js"),
    //entry:['./shared.js','./main.ts']
   // معرفی اسکریپت‌های جدید به وبپک
    entry:{
        index:"./main.js",
        aboutme:"./aboutme.js",
        contact:"./contact.js"
    }
    ,output:{
        path:path.resolve("build/js"),
        publicPath:"assets/js",
     //   filename:'bundle.js'
     // به جای یک باندل کلی از وبپک میخاهیم برای هر ورودی باندلی جدید بسازد
        filename:"[name].js"
    }
    // رجیستر کردن پلاگین 
    ,plugins:[commonChunkPlugin]
    ,
    devServer:{
        contentBase:"assets"
    }
    //,devtool:"#source-map"
    ,watch :true
    ,module:{...
    }
    
}
جهت انجام این کار از یک پلاگین وبپک با نام CommonsChunkPlugin کمک گرفته‌ایم که به ما کمک می‌کند اسکریپت shared.js را به عنوان یک وابستگی در تمامی صفحاتمان داشته باشیم. نحوه‌ی کار این پلاگین نیز از نامش مشخص است و نقاط Common را می‌توان با آن مشخص کرد. تغییر بعدی نیز در کلید entry می‌باشد که اسکریپت‌های جدید را با استفاده از اسمشان به وبپک معرفی کرده‌ایم و سپس در کلید output نیز به وبپک خبر داده‌ایم که برای هر ورودی، باندل جداگانه‌ی خود را بسازد. تکه کد  filename:[name].js به وبپک می‌گوید که باندل‌های جداگانه، با نام خود اسکریپت ساخته شوند و در نهایت کلید جدید plugins به وبپک پلاگین CommonsChunkPlugin را اضافه می‌کند.
حال با اجرای وبپک می‌توان دید که سه باندل ساخته شده که همگی به اسکریپت shared.js وابستگی دارند و اگر این اسکریپت را از صفحات HTML حذف کنید، با خطا رو به رو خواهید شد. این پلاگین قدرت ساخت باندل‌هایی با خاصیت مشخص کردن وابستگی‌ها و همچنین تو در تویی‌ها خاص را نیز دارد. برای مطالعه‌ی بیشتر می‌توانید به اینجا مراجعه کنید: پلاگین commonsChunk

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

فایل‌های مطلب:
سورس تا قبل از قسمت ایجاد تغییرات در ساختار فایل‌های پروژه :dntwebpack-part3-beforeFileAndFolderManagment.zip
سورس برای بعد از ایجاد تغییرات در ساختار فایل‌های پروژه : dntwebpack-part3AfterFileOrganization.zip