نظرات مطالب
کاربرد Mixins در Vue.js
یک نکته‌ی تکمیلی: آشنایی با vue-property-decorator در vuejs

اگر با Angular آشنایی داشته باشید، میدانید که برای نوشتن کامپوننت از @Component استفاده می‌کنیم. یعنی با استفاده از decoratorها می‌توانیم کامپوننتهای پیچیده‌ای را بنویسیم.  در پروژه‌های vue.js نیز کتابخانه مشابهی وجود دارد که کار نوشتن کامپوننت‌ها را ساده میکند؛ مانند کتابخانه vue-property-decorator که سورس گیت هاب آن در اینجا  قرار دارد. برای کار با آن ابتدا کتابخانه‌های vue-class-component و vue-property-decorator را به پروژه‌ی خود از طریق دستور زیر اضافه می‌کنیم:
npm install vue-class-component vue-property-decorator --save-dev
برای نوشتن کامپوننت با استفاده از  type-script ابتدا باید کلاسهای مورد نظر را import کنید و کامپوننت را از Vue مشتق کنید:
<template>
  <div>
    <p>Long-form v-model example</p>
    <input :value="myDataProperty" @input="updateMyProperty($event)"/>
  </div>
</template>

<script>
import Vue from 'vue'
import { Component } from 'vue-property-decorator'

@Component
export default class App extends Vue {
  // Data property
  myDataProperty: string;

  // Lifecycle hook
  mounted () {
    this.myDataProperty = 'Boop'
  }

  // Component method
  updateMyProperty ($event) {
    this.myDataProperty = $event.target.value
  }
}
</script>
همانطورکه مشاهده می‌کنید، مانند Angular برای تعریف کامپوننت از @Component استفاده می‌کنیم. @Component(componentConfig) شامل تنظیماتی هست که می‌توانید آن را نیز به کامپوننت مورد نظر اعمال کنید :
@Component({ name: 'App', components: { AppModal } })
که در اینجا نام کامپوننت و کامپوننت‌های استفاده شده در آن را تعریف کردیم.
در این کتابخانه، decoratorهای دیگری نیز برای استفاده وجود دارند؛ شامل:
@Prop
@PropSync
@Provide
@Model
@Watch
@Inject
@Provide
@Emit
به عنوان مثال در صورتیکه بخواهیم در کامپوننت فوق از prop استفاده کنیم، به صورت زیر می‌باشد:
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
که در واقع این کد، معادل کد جاواسکریپتی هست که بدون استفاده از این کتابخانه می‌نویسیم:
export default {
  props: {
    propA: {
      type: Number
    },
    propB: {
      default: 'default value'
    },
    propC: {
      type: [String, Boolean]
    }
  }
}
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 9 - بررسی تغییرات مسیریابی
فعال سازی تنظیمات مسیریابی

یکی دیگر از تغییرات عمده‌ی ASP.NET Core با نگارش‌های قبلی آن، نحوه‌ی مدیریت مسیریابی‌های سیستم است. در نگارش‌های قبلی مبتنی بر HTTP Moduleها، مسیریابی‌ها توسط یک HTTP Module مخصوص، با pipeline اصلی ASP.NET یکپارچه شده‌اند و زمانیکه مسیر درخواستی با تنظیمات سیستم تطابق داشته باشد، پردازش کار به HTTP Handler مخصوص ASP.NET MVC منتقل می‌شود:


اما در ASP.NET Core مبتنی بر میان افزارها، زیر ساخت مسیریابی به صورت زیر تغییر کرده‌است:


میان افزار ASP.NET MVC را که در قسمت قبل فعال کردیم، باید بتواند کنترلر و اکشن متد متناظر با URL درخواستی را مشخص کند. این تصمیم گیری نیز بر اساس تنظیماتی به نام Routing انجام می‌شود. در قسمت قبل، حالت ساده و پیش فرض این تنظیمات را مورد استفاده قرار دادیم
 app.UseMvcWithDefaultRoute();
که مطابق سورس ASP.NET Core، معادل است با فراخوانی متد app.UseMvc، با قالب پیش فرضی به صورت زیر:
    public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app)
    {
      if (app == null)
        throw new ArgumentNullException("app");
      return app.UseMvc((Action<IRouteBuilder>) (routes => routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}")));
    }
قالب مشخص شده‌ی در اینجا به ASP.NET MVC می‌گوید که از کدام قسمت‌های URL باید نام کلاس کنترلر (کلاس ویژه‌ای که به کلمه‌ی Controller ختم می‌شود) و نام اکشن متد متناظر با آن‌را انتخاب کند (اکشن متد، متدی است عمومی در آن کلاس).
روش دیگر معرفی این تنظیمات، استفاده از Attribute routing است:
 [Route("[controller]/[action]")]


مسیریابی‌های قراردادی

در قسمت قبل، یک POCO Controller را به صورت ذیل تعریف کردیم و این کنترلر، بدون تعریف هیچ نوع مسیریابی خاصی در دسترس بود:
namespace Core1RtmEmptyTest.Controllers
{
  public class HomeController
  {
   public string Index()
   {
    return "Running a POCO controller!";
   }
  }
}
علت کار کردن مسیریابی آن نیز به ذکر متد app.UseMvcWithDefaultRoute در کلاس آغازین برنامه بر می‌گردد و همانطور که عنوان شد، این فراخوانی را می‌توان با فراخوانی واضح‌تر ذیل جایگزین کرد:
app.UseMvc(routes =>
{
  routes.MapRoute(
   name: "default",
   template: "{controller=Home}/{action=Index}/{id?}");
});
پارامتر این متد که جایگزین متد ConfigureRoutes، در نگارش‌های قبلی ASP.NET MVC شده‌است، از نوع IRouteBuilder می‌باشد.
در این تعاریف، هر کدام از قسمت‌های قرارگرفته‌ی داخل {}، مشخص کننده‌ی قسمتی از URL دریافتی بوده و نام‌های controller و action در اینجا جزو نام‌های از پیش مشخص شده هستند و برای نگاشت اطلاعات مورد استفاده قرار می‌گیرند. برای مثال اگر آدرس home/index/ درخواست شد، برنامه به کلاس HomeController و متد عمومی Index آن هدایت می‌شود. همچنین قسمت آخر این پردازش به ?id ختم شده‌است. وجود  ?، به معنای اختیاری بودن این پارامتر است و اگر در URL ذکر شود، به پارامتر id این اکشن متد، نگاشت خواهد شد. مواردی که پس از = ذکر شده‌اند، مقادیر پیش فرض مسیریابی هستند. برای مثال اگر صرفا آدرس home/ درخواست شود، مقدار اکشن متد آن با مقدار پیش فرض index جایگزین خواهد شد و اگر تنها مسیر / درخواست شود، کنترل Home و اکشن متد Index آن پردازش می‌شوند.
در اینجا به هر تعدادی که نیاز است می‌توان متدهای routes.MapRoute را فراخوانی و استفاده کرد؛ اما ترتیب تعریف آن‌ها حائز اهمیت است. هر مسیریابی که در ابتدای لیست اضافه شود، حق تقدم بالاتری خواهد داشت و هر تطابقی با یکی از مسیریابی‌های تعریف شده، در همان سطح سبب خاتمه‌ی پردازش سایر مسیریابی‌ها می‌شود.


استفاده از Attributes برای تعریف مسیریابی‌ها

بجای تعریف قرار دادهای پیش فرض مسیریابی در کلاس آغازین برنامه، می‌توان از ویژگی Route نیز استفاده کرد. هرچند روش تعریف مسیریابی‌های قراردادی، از نگارش‌های آغازین ASP.NET MVC به همراه آن بوده‌اند، اما با زیاد شدن تعداد کنترلرها و مسیریابی‌های سفارشی هر کدام، اینبار با نگاه کردن به یک کنترلر، سریع نمی‌توان تشخیص داد که چه مسیریابی‌های خاصی به آن مرتبط هستند. برای ساده سازی مدیریت برنامه‌های بزرگ و ساده سازی تعاریف مسیریابی‌های خاص آن‌ها، استفاده از ویژگی Route نیز به ASP.NET MVC اضافه شده‌است.
یک مثال: کنترلر About را درنظر بگیرید:
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
  public class AboutController : Controller
  {
   public ActionResult Hello()
   {
    return Content("Hello from DNT!");
   }
 
   public ActionResult SiteName()
   {
    return Content("DNT");
   }
  }
}
این کلاس و کنترلر، به صورت پیش فرض نیاز به تعریف هیچ نوع مسیریابی جدیدی ندارد. همان مسیریابی پیش فرض ثبت شده‌ی در کلاس آغازین برنامه، تمام متدهای عمومی آن یا همان اکشن متدهای آن‌را پوشش می‌دهد. برای مثال جهت رسیدن به اکشن متد SiteName آن، می‌توان آدرس /About/SiteName/ را درخواست داد.
اما اگر آدرس /About/ را درخواست دهیم چطور؟ چون در مسیریابی پیش فرض، تعریف {action=Index} را داریم، یعنی هر زمانیکه در URL درخواستی، قسمت action آن ذکر نشد، آن‌را با index جایگزین کن و این کنترلر دارای متد Index نیست. در ادامه اگر بخواهیم متد Hello را تبدیل به متد پیش فرض این کنترلر کنیم، می‌توان با استفاده از ویژگی Route به صورت ذیل عمل کرد:
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
  [Route("About")]
  public class AboutController : Controller
  {
   [Route("")]
   public ActionResult Hello()
   {
    return Content("Hello from DNT!");
   }
 
   [Route("SiteName")]
   public ActionResult SiteName()
   {
    return Content("DNT");
   }
  }
}
در اینجا با اولین Route تعریف شده، مشخص کرده‌ایم که اگر قسمت اول URL درخواستی معادل about بود، پردازش برنامه باید به این کنترلر هدایت شود. بدیهی است الزامی به یکی بودن نام Route، با نام کنترلر، وجود ندارد. همچنین Route تعریف شده‌ی با رشته‌ی خالی، به معنای مسیریابی پیش فرض است. یعنی اگر آدرس /about/ درخواست داده شد، اکشن متد پیش فرض آن، متد Hello خواهد بود. در این حالت، ذکر Route بعدی برای اکشن متد SiteName الزامی است و اگر این‌کار صورت نگیرد، به استثنای ذیل خواهیم رسید:
 AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:

Core1RtmEmptyTest.Controllers.AboutController.Hello (Core1RtmEmptyTest)
Core1RtmEmptyTest.Controllers.AboutController.SiteName (Core1RtmEmptyTest)
که عنوان کرده‌است در این حالت مشخص نیست که اکشن متد پیش فرض، کدام است.

روش بهتر و refactoring friendly آن نیز به صورت ذیل است:
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
  [Route("[controller]")]
  public class AboutController : Controller
  {
   [Route("")]
   public ActionResult Hello()
   {
    return Content("Hello from DNT!");
   }
 
   [Route("[action]")]
   public ActionResult SiteName()
   {
    return Content("DNT");
   }
  }
}
عموما مرسوم است که نام مسیریابی کنترلر همان نام کنترلر باشد و نام مسیریابی اکشن متد، همان نام اکشن متد مربوطه. به همین جهت می‌توان از توکن‌های ویژه‌ی [controller] و [action] نیز در اینجا استفاده کرد که دقیقا به همان نام کنترلر و اکشن متد متناظر با آن‌ها تفسیر خواهند شد. مزیت این‌کار این است که در صورت تغییر نام متدها یا کنترلرها، دیگر نیازی نیست تا نام‌های تعریف شده‌ی در ویژگی‌های Route را نیز تغییر داد.

یک نکته: در حین تعریف مسیریابی یک کنترلر می‌توان پیشوندهایی را نیز ذکر کرد؛ برای مثال:
 [Route("api/[controller]")]
وجود api در اینجا به این معنا است که از این پس تنها آدرس /api/about/ پردازش خواهد شد و اگر صرفا آدرس /about/ درخواست شود، با خطای 404 و یا یافت نشد، کار خاتمه می‌یابد.


تعریف قیود، برای مسیریابی‌های تعریف شده

فرض کنید به کنترلر About فوق، اکشن متد ذیل را که یک خروجی JSON را بازگشت می‌دهد، اضافه کرده‌ایم:
//[Route("/Users/{userid}")]
[Route("Users/{userid}")]
public IActionResult GetUsers(int userId)
{
    return Json(new { userId = userId });
}
در اینجا تعریف مسیریابی آن با users/ و user معانی کاملا متفاوتی را دارند. اگر مسیریابی Users/{userid}/ را تعریف کنیم، یعنی مسیر ذیل از ریشه‌ی سایت باید درخواست شود: http://localhost:7742/users/1
و اگر مسیریابی Users/{userid} را تعریف کنیم، یعنی این مسیریابی پس از ذکر کنترلر about، به عنوان یک اکشن متد آن مفهوم پیدا می‌کند:
http://localhost:7742/about/users/1
در هر دو حالت، ذکر پارامتر userid الزامی است (چون با ? مشخص نشده‌است)؛ مانند:
[Route("/Users/{userid:int?}")]
در اینجا اگر بخواهیم نوع پارامتر درخواستی را نیز دقیقا مشخص و مقید کنیم، می‌توان به صورت ذیل عمل کرد:
 [Route("Users/{userid:int}")]
اگر این کار را انجام ندهیم، با درخواست مسیر http://localhost:7742/dnt/about/users/test مقدار صفر به userId ارسال می‌شود (چون پارامتر test عددی نیست). اگر تنظیم فوق را انجام دهیم، کاربر خطای 404 را دریافت می‌کند.

قیودی را که در اینجا می‌توان ذکر کرد به شرح زیر هستند:
• alpha - معادل است با  (a-z, A-Z).
• bool - برای تطابق با مقادیر بولی.
• datetime - برای تطابق با تاریخ میلادی.
• decimal - برای تطابق با ورودی‌های اعشاری.
• double - برای تطابق با اعداد اعشاری 64 بیتی.
• float - برای تطابق با اعداد اعشاری 32 بیتی.
• guid - برای تطابق با GUID ها
• int - برای تطابق با اعداد صحیح 32 بیتی.
• length - برای تعیین طول رشته.
• long - برای تطابق با اعداد صحیح 64 بیتی.
• max - برای ذکر حداکثر مقدار یک عدد صحیح.
• maxlength - جهت ذکر حداکثر طول رشته‌ی مجاز ورودی.
• min - برای ذکر حداقل مقدار یک عدد صحیح.
• minlength - جهت ذکر حداقل طول رشته‌ی مجاز ورودی.
• range - ذکر بازه‌ی اعداد صحیح مجاز.
• regex - ذکر یک عبارت با قاعده جهت مشخص سازی الگوی قابل پذیرش.

برای ترکیب چندین قید مختلف نیز می‌توان از : استفاده کرد:
 [Route("/Users/{userid:int:max(1000):min(10)}")]


ذکر نام Route برای ساده سازی تعریف آدرسی به آن

در حین تعریف یک Route می‌توان نام دلخواهی را نیز به آن انتساب داد (همانند نام default مسیریابی ثبت شده‌ی در کلاس آغازین برنامه):
 [Route("/Users/{userid:int}", Name="GetUserById")]
مزیت آن این است که اکنون برای اشاره‌ی به این مسیریابی خاص می‌توان از این نام تعریف شده استفاده کرد:
 string uri = Url.Link("GetUserById", new { userid = 1 });
پارامتر اول ذکر شده، نام مسیریابی و پارامتر دوم، پارامترهای مرتبط با این مسیریابی هستند.


مشخص سازی ترتیب پردازش مسیریابی‌ها

ترتیب مسیریابی‌های ثبت شده‌ی در کلاس آغازین برنامه، همان ترتیب افزوده شدن و ذکر آن‌ها است.
در اینجا می‌توان از خاصیت order نیز استفاده کرد و اعداد کوچکتر، ابتدا پردازش می‌شوند (مقدار پیش فرض آن نیز صفر است):
 [Route("/Users/{userid:int}", Name = "GetUserById", Order = 1)]


امکان تعریف قیود سفارشی

اگر قیودی که تا اینجا ذکر شدند، برای کار شما مناسب نبودند و نیاز بود تا الگوریتم خاصی را جهت محدود سازی دسترسی به یک مسیریابی خاص پیاده سازی کنید، می‌توان به صورت ذیل عمل کرد:
using System;
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
 
namespace Core1RtmEmptyTest
{
  public class CustomRouteConstraint : IRouteConstraint
  {
   public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values,
    RouteDirection routeDirection)
   {
    object value;
    if (!values.TryGetValue(routeKey, out value) || value == null)
    {
      return false;
    }
 
    long longValue;
    if (value is long)
    {
      longValue = (long)value;
      return longValue != 10;
    }
 
    var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
    if (long.TryParse(valueString, NumberStyles.Integer,
      CultureInfo.InvariantCulture, out longValue))
    {
      return longValue != 10;
    }
    return false;
   }
  }
}
در اینجا یک کلاس جدید را که اینترفیس IRouteConstraint را پیاده سازی می‌کند تعریف کرده‌ایم:
public class CustomRouteConstraint : IRouteConstraint
سپس در متد match آن بررسی کرده‌ایم که اگر userid=10 بود، خطای 404 صادر شود.
در آخر برای ثبت و معرفی آن باید به متد ConfigureServices کلاس آغازین برنامه مراجعه کرد:
public void ConfigureServices(IServiceCollection services)
{
    services.AddRouting(options =>options.ConstraintMap.Add("Custom", typeof(CustomRouteConstraint)));
پس از آن، این نام جدید ثبت شده‌ی در اینجا، به نحو ذیل قابل استفاده است:
 [Route("/Users/{userid:int:custom}")]
به این ترتیب userid باید از نوع int بوده و همچنین قید custom را نیز پوشش دهد (یعنی userid=10 نباشد).

یک نکته:  اگر به سورس ASP.NET Core مراجعه کنید ، تمام قیودی را که پیشتر نام بردیم (مانند int، guid و امثال آن) نیز به همین روش تعریف و پیشتر ثبت شده‌اند.


معرفی بسته‌ی نیوگت Microsoft.AspNetCore.SpaServices

مسیریابی‌های پیش فرض ASP.NET Core با مسیریابی‌های برنامه‌های SPA مانند AngularJS (و امثال آن) تداخل دارند؛ از این جهت که درخواست‌های رسیده‌ی به سرور، ابتدا به موتور پردازشی ASP.NET وارد می‌شوند و اگر یافت نشدند، کاربر با پیام 404 مواجه خواهد شد و دیگر در اینجا برنامه به مسیریابی خاص مثلا AngularJS 2.0 هدایت نمی‌شود.
برای این موارد مرسوم است که یک fallback route را در انتهای مسیریابی‌های موجود اضافه کنند (به آن catch all هم می‌گویند)
app.UseMvc(routes =>
{
  routes.MapRoute(
   name: "default",
   template: "{controller=Home}/{action=Index}/{id?}");
 
  routes.MapRoute(
   name: "spa-fallback",
   template: "{*url}",
   defaults: new { controller = "Home", action = "Index" });
});
در اینجا هر درخواستی که با مسیریابی default تطابق نداشت، توسط الگوی عمومی {url*} پردازش می‌شود و این پردازش در نهایت سبب راه اندازی برنامه‌ی SPA می‌گردد. اما مشکل اینجا است که برای فایل‌های استاتیک غیرموجود مانند تصاویر، فایل‌های js و css نیز خروجی HTML ایی خواهیم داشت؛ بجای خروجی 404 و یافت نشد.
برای حل این مشکل مایکروسافت بسته‌ای را به نام Microsoft.AspNetCore.SpaServices ارائه داده است.
برای افزودن آن بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن Microsoft.AspNetCore.SpaServices را جستجو کرده و نصب نمائید:


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{
    "dependencies": {
       //same as before  
       "Microsoft.AspNetCore.SpaServices": "1.0.0-beta-000007"
 },
پس از بازیابی و نصب آن، اکنون catch all را حذف کرده و با یک سطر routes.MapSpaFallbackRoute ذیل جایگزین کنید:
app.UseMvc(routes =>
{
  routes.MapRoute(
   name: "default",
   template: "{controller=Home}/{action=Index}/{id?}");
 
  routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});
و برای یادآوری مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک» در AngularJS 2.0، علاوه بر عمومی کردن پوشه‌ی wwwroot توسط UseFileServer نیاز است پوشه‌ی node_modules را هم با تنظیمات ذیل عمومی کرد و در معرض دید عموم قرار داد (جایی که بسته‌های node.js نصب می‌شوند):
// Serve wwwroot as root
app.UseFileServer();
 
// Serve /node_modules as a separate root (for packages that use other npm modules client side)
app.UseFileServer(new FileServerOptions
{
  // Set root of file server
  FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "node_modules")),
  // Only react to requests that match this path
  RequestPath = "/node_modules",
  // Don't expose file system
  EnableDirectoryBrowsing = false
});
نظرات مطالب
سفارشی کردن صفحه بندی WebGrid در ASP.NET MVC
سلام .
لطفا از تابع Table به جای GetHtml استفاده کنید و خط زیر را هم از آن حذف کنید :
 mode: WebGridPagerModes.All,

نمونه :
@{
    var grid = new WebGrid(
        source: null,
        canPage: true,
        rowsPerPage: 10,
        canSort: true,
        defaultSort: "Title"
    );
    grid.Bind(Model, rowCount: (int)ViewBag.PageCount, autoSortAndPage: false);
    var rowIndex = ((grid.PageIndex + 1) * grid.RowsPerPage) - (grid.RowsPerPage - 1);
}
@grid.Table(
            tableStyle: "table table-striped table-hover",
            headerStyle: "webgrid-header",
            footerStyle: "webgrid-footer",
            alternatingRowStyle: "webgrid-alternating-row",
            selectedRowStyle: "webgrid-selected-row",
            rowStyle: "webgrid-row-style",
            
            columns: grid.Columns(
                 grid.Column(header: "#",
                             style: "text-align-center-col",
                             format: @<text>@(rowIndex++)</text>),
                             grid.Column(columnName: "Title", header: "عنوان", style: "myfont"),
                             grid.Column(columnName: "URL", header: "آدرس", style: "myfont"),
                             
                 grid.Column(header: "",
                             style: "text-align-center-col smallcell",
                             format: item => @Html.ActionLink(linkText: "ویرایش", actionName: "Edit",
                              controllerName: "Link", routeValues: new { area = "Admin", id = item.Code },
                              htmlAttributes: new { @class = "btn-sm btn-info vertical-center" })),
                 grid.Column(header: "",
                             format: @<form action="Link/Delete/@item.Code" method="post">
                    <input type="submit" class="btn-sm btn-danger submitlink"
                           onclick="return confirm('آیا از حذف این آیتم مطمئن هستید ؟');"

                           value="حذف" />
                </form>)))
<div class="text-center">
  @grid.PagerList(mode: WebGridPagerModes.All)
</div>
اگر شما تمام دیتا را به یکباره می‌خواهید نمایش دهید باید به صورت زیر تغییر دهید :
@{
    var grid = new WebGrid(
        source: Model,
        canPage: true,
        rowsPerPage: 10,
        canSort: true,
        defaultSort: "Title"
    );
//    grid.Bind(Model, rowCount: (int)ViewBag.PageCount, autoSortAndPage: false); // Delete this Line
    var rowIndex = ((grid.PageIndex + 1) * grid.RowsPerPage) - (grid.RowsPerPage - 1);
}

مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت ششم - تعیین نوع هوک useEffect و هوک‌های سفارشی
در این قسمت قصد داریم یک هوک سفارشی را ایجاد کرده و نوع‌های آن‌را توسط TypeScript مشخص کنیم. همچنین در این بین از هوک useEffect هم استفاده خواهیم کرد؛ هرچند این هوک، نکات تایپ‌اسکریپتی خاصی را به همراه ندارد.


ایجاد هوک سفارشی useClickOutside

برای این منظور فایل جدید src\components\useClickOutside.tsx را ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
import { useEffect } from "react";

const useClickOutside = (ref, handler) => {
  useEffect(() => {
    const listener = (event) => {
      if (!ref.current || ref.current.contains(event.target)) {
        return;
      }
      handler(event);
    };
    document.addEventListener("mousedown", listener);
    document.addEventListener("touchstart", listener);

    return () => {
      document.removeEventListener("mousedown", listener);
      document.removeEventListener("touchstart", listener);
    };
  }, [handler, ref]);
};

export { useClickOutside };
توضیحات:

- متد هوک سفارشی ما، دو پارامتر ref و handler را دریافت می‌کند. ref به DOM Element جاری اشاره می‌کند و handler تابعی است که هنگام کلیک در خارج از ناحیه‌ی یک DOM Element خاص، اجرا می‌شود.
- سپس یک listener را تعریف کرده‌ایم که این تابع handler را اجرا می‌کند؛ البته به شرطی‌که DOM Element ارسالی وجود داشته باشد و خود target هم نباشد.
- در ادامه این listener را به رخ‌دادهای mousedown و touchstart متصل کرده و پاکسازی آن‌ها را هم در قسمت return متد useEffect انجام داده‌ایم.
- همچنین چون می‌خواهیم تنها در صورت تغییر پارامترهای ارسالی به هوک سفارشی جاری، این useEffect به روز رسانی شود، این پارامترها را در قسمت Dependency List مربوط به متد useEffect نیز ذکر کرده‌ایم.

تا اینجا اگر کدهای فوق را دنبال کنید، چون پسوند این فایل tsx است، خطاهای تایپ‌اسکریپتی زیر را مشاهده خواهید کرد که به دلیل انتساب ضمنی نوع any، به این پارامترهای بدون نوع است:



استفاده از هوک سفارشی useClickOutside

بنابراین قدم بعدی کار، تکمیل نوع‌های مرتبط با این پارامترها است. برای این منظور، ابتدا سعی می‌کنیم تا این هوک را در کامپوننت src\components\ReducerButtons.tsx قسمت قبلی استفاده کنیم تا نسبت به نوع پارامترهای ارسالی به این هوک، درک بهتری را پیدا کنیم:
import { useClickOutside } from "./useClickOutside";

// ...

export const ReducerButtons = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const ref = useRef<HTMLDivElement>(null);
  useClickOutside(ref, () => {
    console.log("clicked outside");
  });

  return (
    <div ref={ref}>
      // ...
    </div>
  );
};
برای این منظور، سه تغییر را انجام داده‌ایم:
- ابتدا import‌های لازم را به ابتدای ماژول افزوده‌ایم.
- سپس با استفاده از هوک useRef که در قسمت چهارم آن‌را بررسی کردیم، ارجاعی را به المان div رندر شده، بدست آورده‌ایم.
- در آخر هوک سفارشی جدید useClickOutside را فراخوانی کرده‌ایم که آرگومان اول آن به DOM Element مربوط به div اشاره می‌کند و پارامتر دوم آن، تابعی است که پس از کلیک در خارج از ناحیه‌ی آن، اجرا خواهد شد.


تعیین نوع‌های پارامترهای هوک سفارشی

تا اینجا متوجه شدیم که handler، چیزی بجز یک تابع که void را بازگشت می‌دهد (void <= ())، نیست. همچنین نوع شیء ref را هم می‌توان با نزدیک کردن اشاره‌گر ماوس، به متغیر ref در کامپوننت ReducerButtons، مشاهده کرد:


بر این اساس، تعاریف نوع‌های پارامترهای هوک سفارشی useClickOutside به صورت زیر مشخص می‌شوند:
const useClickOutside = (
  ref: React.RefObject<HTMLDivElement>,
  handler: () => void
) => {
همچنین بر اساس نکات قسمت سوم، نوع event را نیز به React.MouseEvent تنظیم می‌کنیم:
const listener = (event: React.MouseEvent<HTMLElement>) => {
پس از آن، اولین خطایی که ظاهر می‌شود به صورت زیر است:


عنوان می‌کند که نوع event.target، از نوع Node، که مورد نظر متد contains است، نیست. برای رفع آن فقط کافی است تبدیل نوع زیر را انجام داد:
ref.current.contains(event.target as Node)
مشکل بعدی، بدون پارامتر تعریف کردن نوع تابع handler است:


برای رفع این خطا، نوع پارامتر تابع handler را نیز بر اساس رویداد ارسالی به آن، مشخص می‌کنیم:
const useClickOutside = (
  ref: React.RefObject<HTMLDivElement>,
  handler: (event: React.MouseEvent<HTMLElement>) => void
) => {
مرحله‌ی آخر، عدم تطابق React.MouseEvent تعریف شده، با پارامترهای متد addEventListener است:


برای درک بهتر این خطا، اشاره‌گر ماوس را به محل تعریف این متد نزدیک می‌کنیم، تا بتوان امضای آن‌را مشاهده کرد. در حالت mousedown، پارامتر دوم این متد، از نوع MouseEvent است:


(method) Document.addEventListener<"mousedown">(type: "mousedown", listener: (this: Document, ev: MouseEvent) 
=> any, options?: boolean | AddEventListenerOptions | undefined): void (+1 overload)
و در حالت touchstart، پارامتر دوم آن به TouchEvent تغییر کرده‌است:
(method) Document.addEventListener<"touchstart">(type: "touchstart", listener: (this: Document, ev: TouchEvent)
 => any, options?: boolean | AddEventListenerOptions | undefined): void (+1 overload)
به همین جهت است که نوع <React.MouseEvent<HTMLElement تعریف شده، با این دو سازگار نیست و خطا رخ‌داده‌است. برای رفع این خطا، با استفاده از union types، هر دو رخ‌داد MouseEvent و TouchEvent را باید به عنوان نوع پارامترهای ورودی تعریف کرد:
const useClickOutside = (
  ref: React.RefObject<HTMLDivElement>,
  handler: (event: MouseEvent | TouchEvent) => void
) => {
  useEffect(() => {
    const listener = (event: MouseEvent | TouchEvent) => {
بنابراین با کمی دقت به تعاریف استانداردی که به همراه متدهای مورد استفاده هستند، می‌توان نوع‌های مرتبط را تشخیص داد و از آن‌ها استفاده کرد.


یک نکته‌ی تکمیلی: در اینجا با تعریف <ref: React.RefObject<HTMLDivElement، دیگر ref ارسالی، هیچ المان دیگری را بجز div نمی‌تواند بپذیرد. برای عمومی‌تر کردن آن، می‌توان بر روی آن کلیک راست کرد و گزینه‌ی Go to definition را انتخاب نمود:


بنابراین حالت عمومی‌تر آن، استفاده از HTMLElement ای است که HTMLDivElement از آن ارث بری کرده‌است:
const useClickOutside = (
  ref: React.RefObject<HTMLElement>,
  handler: (event: MouseEvent | TouchEvent) => void
) => {

با این تغییرات، کدهای نهایی این قسمت، به صورت زیر در خواهند آمد:
import { useEffect } from "react";

const useClickOutside = (
  ref: React.RefObject<HTMLElement>,
  handler: (event: MouseEvent | TouchEvent) => void
) => {
  useEffect(() => {
    const listener = (event: MouseEvent | TouchEvent) => {
      if (!ref.current || ref.current.contains(event.target as Node)) {
        return;
      }
      handler(event);
    };
    document.addEventListener("mousedown", listener);
    document.addEventListener("touchstart", listener);

    return () => {
      document.removeEventListener("mousedown", listener);
      document.removeEventListener("touchstart", listener);
    };
  }, [handler, ref]);
};

export { useClickOutside };
نظرات مطالب
نمایش فرم‌های مودال Ajax ایی در ASP.NET MVC به کمک Twitter Bootstrap
تغییر کدهای T4 برای حالت فرم‌های Modal به صورت زیر است :
<#
    if (!mvcHost.IsContentPage) {
#>


<#
    }
}
#>
<div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
        &times;</button>
    <#= mvcHost.ViewDataType.Name #>
</div>
@using (Html.BeginForm()) {
<div class="modal-body">
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset class="form-horizontal">
        <legend><#= mvcHost.ViewDataType.Name #></legend>

<#
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
    if (!property.IsPrimaryKey && !property.IsReadOnly && property.Scaffold) {
#>
        <div class="control-group">
<#
        if (property.IsForeignKey) {
#>
            @Html.LabelFor(model => model.<#= property.Name #>, "<#= property.AssociationName #>",new {@class="control-label"})
<#
        } else {
#>
            @Html.LabelFor(model => model.<#= property.Name #>,new {@class="control-label"})
<#
        }
#>
        
           <div class="controls">
<#
        if (property.IsForeignKey) {
#>
            @Html.DropDownList("<#= property.Name #>", String.Empty)
<#
        } else {
#>
            @Html.EditorFor(model => model.<#= property.Name #>)
<#
        }
#>
            @Html.ValidationMessageFor(model => model.<#= property.Name #>,null,new{@class="help-inline"})
</div>
        </div>

<#
    }
}
#>
 </fieldset>
</div>
   
<div class="modal-footer">
        <button class="btn btn-primary" type="submit">
            ارسال</button>
        <button class="btn" data-dismiss="modal" aria-hidden="true">
            انصراف</button>
    </div>
    
}

حالا تنها مشکلم نحوه نمایش گروه محصولات داخل DropDownList است.
مطالب
آشنایی با Column Store Index در SQL Server 2012

 Column Store Index یکی از ویژگیهای جدید SQL Server 2012 می باشد، که کارایی Query های قایل اجرا روی دیتابیس‌های با حجم داده ای بسیار بالا را (که اصطلاحا به آنها Data Warehouse یا انبار داده گویند)، چندین برابر بهبود بخشیده است

 قبل از توضیح در مورد Column Store مختصری در مورد نحوه ذخیره سازی داده‌ها در SQL Server می پردازیم. می‌توان گفت در SQL Server دو روش ذخیره سازی وجود دارد،یکی بصورت ردیفی که اصطلاحا به آن  Row Storeیا  Row-Wise گویند، و دیگری بصورت ستونی که اصطلاحا به آن Column Store گویند

در روش ذخیره سازی Row Store، مقادیر ستونها در یک سطر بصورت متوالی ذخیره می‌شوند، در این روش ذخیره سازی از ساختار B-Tree یا Heap استفاده می‌شود.

یادآوری: در ساختار B-Tree، یک گره Root وجود دارد، و گره بعد از Root گره ای است که آدرس گره راست بعدی و آدرس گره چپ بعدی را در خود نگه می‌دارد.

شکل زیر نمای یک درخت B-Tree می‌باشد:

جهت کسب اطلاعات بیشتر درمورد ساختار B-Tree 

یادآوری: وقتی در یک جدول، ایندکسی از نوع Clustered ایجاد نماییم، SQL Server، در ابتدا یک کپی از جدول ایجاد و داده‌های جدول را از نو مرتب می‌نماید، و ساختار صفحه ریشه و دیگر صفحات را ایجاد می‌کند و سپس جدول اصلی را حذف می‌نماید. به جدولی که Clustered Index ندارد، اصطلاحا Heap گویند.

  برخلاف ذخیره سازی Row Store، در ذخیره سازی Column Store، داده‌ها بصورت ستونی ذخیره می‌شوند،در این روش داده ها، فشرده سازی می‌شوند و اینکار باعث می‌شود،در زمان درخواست یک Query، نیاز به Disk I/o به حداقل برسد، در نتیجه، زمان و سرعت پاسخگویی به پرس و جو‌ها بسیار افزایش می‌یابد.

شکل زیر نحوه ذخیره سازی داده ها،بصورت Row Store را نمایش می‌دهد:

  شکل بالا ذخیره سازی داده ها، در ساختار B-Tree یا Heap را نمایش می‌دهد، در شکل فوق یک جدول چهار ستونی با N سطر (Row) در نظر گرفته شده است.بطوریکه ستونهای هر Row بطور متوالی در یک صفحه (Page) یکسان ذخیره می‌شوند.

 شکل زیر نحوه ذخیره سازی داده ها،بصورت Column Store را نمایش می‌دهد:

  مطابق شکل،ستونهای مربوط به هر Row،همگی در یک صفحه (Page) یکسان ذخیره شده اند. به عنوان مثال ستون C1 که مربوط به سطر اول (Row1) می‌باشد، با ستون C1 که مربوط به سطر دوم (Row2) می‌باشد، در یک ستون و در یک صفحه (Page1) ذخیره شده اند، و الی آخر ...

   سئوال: یکبار دیگر به هردو شکل با دقت نگاهی بیاندازید، عمده تفاوت آنها در چیست؟

   جواب: درست حدس زدید، تفاوت بارز بین دو روش Column Store و Row Store در نحوه ذخیره سازی داده‌ها می‌باشد. بطور مثال، فرض کنید،در روش ذخیره سازی Row Store، به دنبال مقادیری از ستون C2 می‌باشید، SQL Server می‌بایست کل رکورد‌های جدول (منظور همه Row‌ها در همه Page ها)را Scan نماید، تا مقادیر مربوط به ستون C2 را بدست آورد.درحالیکه در روش ذخیره سازی Column Store، جهت یافتن مقادیر ستون C2، نیازی به Scan نمودن کل جدول نیست،بلکه SQL Server فقط به Scan نمودن ستون دوم (C2) یا Page2 بسنده می‌نماید.همین امر باعث افزایش چندین برابری، زمان پاسخگویی به هر Query می‌شود.

  سئوال: در روش ذخیره سازی Column Store، چگونه مصرف حافظه بهینه می‌شود؟

  جواب: واضح است،  که در روش SQL Server، Row Store مجبور است، برای بدست آوردن داده‌های مورد نظرتان،کل اطلاعات جدول را وارد حافظه نماید(اطلاعات اضافه ای که به هیچ وجه بدرد، نتیجه پرس و جوی شما نمی‌خورد)، و شروع به Scan داده‌های مد نظر شما می‌نماید.بطوریکه در روش SQL Server، Column Store، فقط ستون داده‌های مورد پرس و جو را در حافظه قرار می‌دهد.(در واقع فقط داده هایی را در حافظه قرار می‌دهد، که شما به آن نیاز دارید)،بنابراین،طبیعی است که در روش Column Store مقدار حافظه کمتری نسبت به روش Row Store در هنگام اجرای Query استفاده می‌شود. به عبارت دیگر می‌توان گفت که در روش Column Store به دلیل، به حداقل رساندن استفاده از Disk I/o سرعت و زمان پاسخگویی به پرس و جو‌ها چندین برابر می‌شود.

  برای درک بیشتر Row Store و Column Store مثالی می‌زنیم:

   فرض کنید،قصد بدست آوردن ستونهای C1 و C2 از جدول A را داریم، بنابراین خواهیم داشت:

Select C1, C2 from A

روش Row Store:

    در این روش همه صفحات دیسک (مربوط به جدول A) درون حافظه قرار داده می‌شود، یعنی علاوه بر ستونهای C1 و C2، اطلاعات مربوط به ستونهای C3 و C4 نیز درون حافظه قرار می‌گیرد،بطوریکه مقادیر ستونهای C3 و C4 به هیچ وجه مورد قبول ما نیست، و در خروجی پرس و جوی ما تاثیری ندارد، و فقط بی جهت حافظه اشغال می‌نماید.

روش Column Store:

  در این روش فقط صفحات مروبط به ستون C1 و C2 در حافظه قرار می‌گیرد.(منظور Page1 و Page2 می‌باشد) بنابراین فقط اطلاعات مورد نیاز در خروجی، در حافظه قرار می‌گیرد.

  •  از دیگر مزایای استفاده از روش Column Store، فشرده سازی داده می‌باشد،برای درک بیشتر توضیح می‌دهم:
      همانطور که در اوایل مطلبم به عرض رساندم، در روش Row Store ، داده‌ها در یک سطر و در یک Page ذخیره می‌شوند، بنابراین امکان وجود داده‌های تکراری در یک سطر به حداقل می‌رسد، چرا که، اگر فرض کنیم چهار ستون  به نام‌های ID،FirstName،LastName و City، داشته باشیم،در آن صورت بطور حتم،در یک سطر، داده تکراری وجود نخواهد داشت، اما ممکن است در تعداد سطرهای زیاد داده‌های تکراری مانند Firstname یا City و غیرو بوجود بیاید، این موضوع را بیان کردم، چون می‌خواستم عنوان کنم،بسیاری از الگوریتم‌های فشرده سازی از الگوی تکراری بودن داده، جهت فشرده سازی داده‌ها استفاده می‌کنند، به همین جهت فشرده سازی در روش Row Store به حداقل می‌رسد و فضای اشغال شده در حافظه دراین روش بسیار زیاد خواهد بود. اما در روش Column Store ، امکان تکراری بودن مقادیر یک ستون بسیار زیاد  است، بطور مثال ممکن است تعداد افرادی را که نام شهر  آنها "تهران" باشد مثلا 20 بار تکرار شده باشد، و چون در روش Column Store، ستون‌ها در یک Page ذخیره می‌شوند، بنابراین امکان استفاده از الگوریتمهای فشرده سازی در این روش بسیار بالا می‌باشد، در نتیجه مقدار فضایی را که در حافظه یا دیسک سخت توسط این روش اشغال می‌شود، بسیار کمتر از روش Row Store است.

چه موقع می‌توانیم از Column Store استفاده نماییم:

   در تعریف Column Store گفته بودم، روش فوق، جهت بهبود بخشیدن به زمان و سرعت پاسخگویی به Query‌های اجرا شده روی دیتابیس‌های با حجم داده ای بسیار بالا(Data Warehouse ) می‌باشد، به بیان ساده‌تر Column Store را روی دیتابیس‌های offline یا دیتابیسهایی که صرفا جهت گزارش گیری مورد استفاده قرار می‌گیرند، تنظیم می‌نمایند.در واقع با تنظیم Column Store Index روی Database‌های بزرگ مانند Database‌های بانک‌ها که حجم داده ای میلیونی در جداول آنها وجود دارد، سرعت پاسخگویی Query ها، چندین برابر افزایش می‌یابد.

  •      در یک جدول می‌توانید، هم Column Store Index داشته باشید و هم یک Row Store Index (منظور یک  Clustered Index می باشد)
  • Syntax برای ایجاد  Column Store Index به شرح ذیل می‌باشد:
CREATE [ NONCLUSTERED ] COLUMNSTORE INDEX index_name 
    ON <object> ( column  [ ,...n ] )
    [ WITH ( <column_index_option> [ ,...n ] ) ]
    [ ON {
           { partition_scheme_name ( column_name ) } 
           | filegroup_name 
           | "default" 
         }
    ]
[ ; ]

<object> ::=
{
    [database_name. [schema_name ] . | schema_name . ]
     table_name
{

<column_index_option> ::=
{
      DROP_EXISTING = { ON | OFF }
    | MAXDOP = max_degree_of_parallelism
 }
  • یک Column Store Index می‌بایست از نوع NONCLUSTERED باشد.
مثال از یک Column Store Index :
CREATE NONCLUSTERED COLUMNSTORE INDEX [IX_MyFirstName_ColumnStore]
ON [Test]
(Firstname)
در قطعه کد بالا، یک Column Store Index به نام Ix_MyFirstname_ColumnStore روی فیلد Firstname از جدول Test ایجاد شده است.
محدودیت‌های استفاده از Column Store Index به اختصار به شرح ذیل می‌باشد:
  • زمانی که در یک جدول، یک Column Store Index ایجاد نماییم، جدول ما  در حالت Read-only قرار می‌گیرد، بطوریکه از آن پس  اختیار Delete،Update و Insert روی جدول فوق را نخواهیم داشت. برای اینکه بتوانید عملیات Insert، Update یا Delete را انجام دهید، میبایست Column Store Index جدول مربوطه را Disable نمایید، و برای فعال نمودن Column Store Index، می‌بایست آن را Rebuild نمایید، با کلیک راست روی ایندکس ایجاد شده در SQL Server2012 موارد Disable و Rebuild قابل مشاهده می‌باشد.

یا بوسیله Script‌های زیر می‌توانید، عملیات Disable یا Rebuild را روی Column Store Index انجام دهید:
ALTER INDEX [IX_MyFirstName_ColumnStore] ON [Test] DISABLE

ALTER INDEX [IX_MyFirstName_ColumnStore] ON [Test] Rebuild
  • بیشتر از یک Column Store Index نمی‌توانید روی یک جدول ایجاد نمایید.
  • در صورتی که تمایل داشته باشید بوسیله Alter ، نوع فیلدی (Type)، را که Column Store Index روی آنها اعمال گردیده است، تغییر دهید، در ابتدا می‌بایست Column Store Index، خود را Drop یا حذف نمایید، سپس عملیات Alter را اعمال کنید، در غیر اینصورت با خطای SQL Server مواجه می‌شوید.
  • یک Column Store Index می‌تواند روی 1024 ستون در یک جدول اعمال گردد.
  • یک Column Store Index  نمی توانند، Unique باشد و نمی‌توان از آن به عنوان Primary Key یا Foreign Key استفاده نمود.
یاد آوری: با توجه به مزایای استفاده از Column Store Index، باید بگویم که در حجم‌های داده ای کم استفاده از Row Store Index بهتر می‌باشد. پیشنهاد مایکروسافت برای استفاده از Column Store Index برای دیتابیس‌های با حجم داده ای بسیار بالا می‌باشد.
موفق باشید
منابع:

مطالب
ارسال PingBack در ASP.NET
Pingback یکی از روش‌های اطلاع رسانی به سایت‌های دیگر در مورد لینک دادن به آن‌ها در سایت خود است. برای مثال من لینکی از یکی از مطالب شما را در متن جاری خودم قرار می‌دهم. سپس به وسیله‌ی ارسال یک ping، در مورد انجام اینکار به شما اطلاع رسانی می‌کنم. حاصل آن عموما قسمت معروف ping-backs سایت‌ها است. این مورد نیز یکی از روش‌های مؤثر SEO در گرفتن backlink است و تبلیغ محتوا.
کار کردن با پروتکل Ping-back آنچنان ساده نیست؛ از این جهت که تبادل ارتباطات آن با پروتکل XML-RPC انجام می‌شود. XML-RPC نیز توسط PHP کارها بیشتر مورد استفاده قرار می‌گیرد؛ بجای استفاده از پروتکل‌های استاندارد وب سرویس‌ها مانند Soap و امثال آن. پیاده سازی‌های ابتدایی Pingback نیز مرتبط است به Wordpress معروف که با PHP تهیه شده‌است. در ادامه نگاهی خواهیم داشت به جزئیات پیاده سازی ارسال ping-back توسط برنامه‌های ASP.NET.


یافتن آدرس وب سرویس سایت پذیرای Pingback

اولین قدم در پیاده سازی Pingback، یافتن آدرسی است که باید اطلاعات مورد نظر را به آن ارسال کرد. این آدرس عموما به دو طریق ارائه می‌شود:
الف) در هدری به نام x-pingback و یا pingback
ب) در قسمتی از کدهای HTML صفحه به شکل
 <link rel="pingback" href="pingback server">
برای مثال اگر به وبلاگ‌های MSDN دقت کنید، هدر x-pingback را می‌توانید در خروجی وب سرور آن‌ها مشاهده کنید:


همانطور که ملاحظه می‌کنید، نیاز است Response header را آنالیز کنیم.
        private Uri findPingbackServiceUri()
        {
            var request = (HttpWebRequest)WebRequest.Create(_targetUri);
            request.UserAgent = UserAgent;
            request.Timeout = Timeout;
            request.ReadWriteTimeout = Timeout;
            request.Method = WebRequestMethods.Http.Get;
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            using (var response = request.GetResponse() as HttpWebResponse)
            {
                if (response == null) return null;

                var url = extractPingbackServiceUriFormHeaders(response);
                if (url != null)
                    return url;

                if (!isResponseHtml(response))
                    return null;

                using (var reader = new StreamReader(response.GetResponseStream()))
                {
                    return extractPingbackServiceUriFormPage(reader.ReadToEnd());
                }
            }
        }

        private static Uri extractPingbackServiceUriFormHeaders(WebResponse response)
        {
            var pingUrl = response.Headers.AllKeys.FirstOrDefault(header =>
                                header.Equals("x-pingback", StringComparison.OrdinalIgnoreCase) ||
                                header.Equals("pingback", StringComparison.OrdinalIgnoreCase));

            return getValidAbsoluteUri(pingUrl);
        }

        private static Uri extractPingbackServiceUriFormPage(string content)
        {
            if (string.IsNullOrWhiteSpace(content)) return null;
            var regex = new Regex(@"(?s)<link\srel=""pingback""\shref=""(.+?)""", RegexOptions.IgnoreCase);
            var match = regex.Match(content);
            return (!match.Success || match.Groups.Count < 2) ? null : getValidAbsoluteUri(match.Groups[1].Value);
        }

        private static Uri getValidAbsoluteUri(string url)
        {
            Uri absoluteUri;
            return string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out absoluteUri) ? null : absoluteUri;
        }

        private static bool isResponseHtml(WebResponse response)
        {
            var contentTypeKey = response.Headers.AllKeys.FirstOrDefault(header =>
                                        header.Equals("content-type", StringComparison.OrdinalIgnoreCase));
            return !string.IsNullOrWhiteSpace(contentTypeKey) &&
                    response.Headers[contentTypeKey].StartsWith("text/html", StringComparison.OrdinalIgnoreCase);
        }
نحوه‌ی استخراج آدرس سرویس Pingback یک سایت را در کدهای فوق ملاحظه می‌کنید.
targetUri، آدرسی است از یک سایت دیگر که در سایت ما درج شده‌است. زمانیکه این صفحه را درخواست می‌کنیم، response.Headers.AllKeys حاصل می‌تواند حاوی کلید x-pingback باشد یا خیر. اگر بلی، همینجا کار پایان می‌یابد. فقط باید مطمئن شد که این آدرس مطلق است و نه نسبی. به همین جهت در متد getValidAbsoluteUri، بررسی بر روی UriKind.Absolute انجام شده‌است.
اگر هدر فاقد کلید x-pingback باشد، قسمت ب را باید بررسی کرد. یعنی نیاز است محتوای Html صفحه را برای یافتن link rel=pingback بررسی کنیم. همچنین باید دقت داشت که پیش از اینکار نیاز است حتما بررسی isResponseHtml صورت گیرد. برای مثال در سایت شما لینکی به یک فایل 2 گیگابایتی SQL Server درج شده‌است. در این حالت نباید ابتدا 2 گیگابایت فایل دریافت شود و سپس بررسی کنیم که آیا محتوای آن حاوی link rel=pingback است یا خیر. اگر محتوای ارسالی از نوع text/html بود، آنگاه کار دریافت محتوای لینک انجام خواهد شد.


ارسال Ping به آدرس سرویس Pingback

اکنون که آدرس سرویس pingback یک سایت را یافته‌ایم، کافی است ping ایی را به آن ارسال کنیم:
        public void Send()
        {
            var pingUrl = findPingbackServiceUri();
            if (pingUrl == null)
                throw new NotSupportedException(string.Format("{0} doesn't support pingback.", _targetUri.Host));

            sendPing(pingUrl);
        }

        private void sendPing(Uri pingUrl)
        {
            var request = (HttpWebRequest)WebRequest.Create(pingUrl);
            request.UserAgent = UserAgent;
            request.Timeout = Timeout;
            request.ReadWriteTimeout = Timeout;
            request.Method = WebRequestMethods.Http.Post;
            request.ContentType = "text/xml";
            request.ProtocolVersion = HttpVersion.Version11;
            makeXmlRpcRequest(request);
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                response.Close();
            }
        }

        private void makeXmlRpcRequest(WebRequest request)
        {
            var stream = request.GetRequestStream();
            using (var writer = new XmlTextWriter(stream, Encoding.ASCII))
            {
                writer.WriteStartDocument(true);
                writer.WriteStartElement("methodCall");
                writer.WriteElementString("methodName", "pingback.ping");
                writer.WriteStartElement("params");

                writer.WriteStartElement("param");
                writer.WriteStartElement("value");
                writer.WriteElementString("string", Uri.EscapeUriString(_sourceUri.ToString()));
                writer.WriteEndElement();
                writer.WriteEndElement();

                writer.WriteStartElement("param");
                writer.WriteStartElement("value");
                writer.WriteElementString("string", Uri.EscapeUriString(_targetUri.ToString()));
                writer.WriteEndElement();
                writer.WriteEndElement();

                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }
اینبار HttpWebRequest تشکیل شده از نوع post است و نه get. همچنین مقداری را که باید ارسال کنیم نیاز است مطابق پروتکل XML-RPC باشد. برای کار با XML-RPC در دات نت یا می‌توان از کتابخانه‌ی Cook Computing's XML-RPC.Net استفاده کرد و یا مطابق کدهای فوق، دستورات آن‌را توسط یک XmlTextWriter کنار هم قرار داد و نهایتا در درخواست Post ارسالی درج کرد.
در اینجا sourceUri آدرس صفحه‌ای در سایت ما است که targetUri ایی (آدرسی از سایت دیگر) در آن درج شده‌است. در یک pinback، صرفا این دو آدرس به سرویس دریافت کننده‌ی pingback ارسال می‌شوند.
سپس سایت دریافت کننده‌ی ping، ابتدا sourceUri را دریافت می‌کند تا عنوان آن‌را استخراج کند و همچنین بررسی می‌کند که آیا targetUri، در آن درج شده‌است یا خیر (آیا spam است یا خیر)؟
تا اینجا اگر این مراحل را کنار هم قرار دهیم به کلاس Pingback ذیل خواهیم رسید:
Pingback.cs


نحوه‌ی استفاده از کلاس Pingback تهیه شده

کار ارسال Pingback عموما به این نحو است: هر زمانیکه مطلبی یا یکی از نظرات آن، ثبت یا ویرایش می‌شوند، نیاز است Pingbackهای آن ارسال شوند. بنابراین تنها کاری که باید انجام شود، استخراج لینک‌های خارجی یک صفحه و سپس فراخوانی متد Send کلاس فوق است.
یافتن لینک‌های یک محتوا را نیز می‌توان مانند متد extractPingbackServiceUriFormPage فوق، توسط یک Regex انجام داد و یا حتی با استفاده از کتابخانه‌ی معروف HTML Agility Pack:
var doc = new HtmlWeb().Load(url);
var linkTags = doc.DocumentNode.Descendants("link");
var linkedPages = doc.DocumentNode.Descendants("a")
                                  .Select(a => a.GetAttributeValue("href", null))
                                  .Where(u => !String.IsNullOrEmpty(u));
اشتراک‌ها
PowerShell 7.3 منتشر شد

PowerShell 7.3 is built on top of .NET 7 and as a non-LTS (Long Term Support) release will be supported for 18 months. PowerShell 7.2 is still the current LTS (3-year supported) release of PowerShell. 

PowerShell 7.3 منتشر شد