مطالب
پردازش فایل‌های XML با استفاده از jQuery

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

و در حالت کلی :
http://api.feedburner.com/awareness/1.0/GetFeedData?uri=<feeduri>

که حاصل آن برای مثال یک فایل XML با فرمت زیر می‌باشد:

<rsp stat="ok">
<feed id="fhphjt61bueu08k93ehujpu234" uri="vahidnasiri">
<entry date="2009-01-23" circulation="153" hits="276" reach="10"/>
</feed>
</rsp>

همانطور که مطلع هستید چند روزی است که jQuery 1.3.1 ارائه شده است. جهت استفاده از آخرین نگارش موجود آن تا این زمان، می‌توان از گوگل به عنوان هاست این کتابخانه به صورت زیر استفاده کرد:

<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js' type='text/javascript'></script>

نحوه خواندن مقدار circulation فایل xml ذخیره شده بر روی کامپیوتر:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>FeedBurner API</title>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js' type='text/javascript'>
</script>
<script type="text/javascript">
function parseXml(xml){
//find every entry and print the circulation
$(xml).find("entry").each(function(){
$("#output").append($(this).attr("circulation"));
});
}

$(document).ready(function(){
$.ajax({
type: "GET",
url: "GetFeedData_local.xml",
dataType: "xml",
success: parseXml
});
});
</script>
</head>
<body>
<div dir="rtl" style="font-family:tahoma; font-size:12px;">
تعداد مشترکین تغذیه خبری سایت:
<div id="output">
</div>
</div>
</body>
</html>

با استفاده از قابلیت Ajax کتابخانه jQuery ، اطلاعات فایل محلی GetFeedData_local.xml دریافت شده و محتوای آن به تابع parseXml پاس می‌شود (توسط قسمت success). سپس در این تابع تمام تگ‌های entry یافت شده و مقدار circulation آن‌ها به یک div با ID معادل output اضافه می‌شود.
مثال فوق در مورد خواندن اطلاعات از یک فایل xml می‌تواند برای مثال این کاربرد را در یک سایت داشته باشد:
نمایش اتفاقی سخن روز یا سخن بزرگان و امثال آن بدون برنامه نویسی سمت سرور جهت انجام این کار از یک فایل xml تهیه شده، بدون نیاز به استفاده از دیتابیس خاصی.

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

Access to restricted URI denied

تمام موارد دیگری هم که در jQuery برای دریافت اطلاعات از یک فایل یا url موجود است (مثلا تابع load یا get و امثال آن) فقط به سایت جاری و دومین جاری باید ختم شوند در غیر اینصورت توسط مرورگرهای جدید متوقف خواهند شد.

مطالب
React 16x - قسمت 23 - ارتباط با سرور - بخش 2 - شروع به کار با Axios
پس از نصب Axios در قسمت قبل، جزئیات کار با آن‌را در این بخش مرور می‌کنیم.


دریافت اطلاعات از سرور، توسط Axios

- ابتدا به پوشه‌ی sample-22-backend ای که در قسمت قبل ایجاد کردیم، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن، قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts در مرورگر دسترسی یافت (و یا همانطور که عنوان شد، از آدرس https://jsonplaceholder.typicode.com/posts نیز می‌توانید استفاده کنید؛ چون ساختار یکسانی دارند).

-سپس در برنامه‌ی React ای که در قسمت قبل ایجاد کردیم، فایل app.js آن‌را گشوده و ابتدا کتابخانه‌ی Axios را import می‌کنیم:
import axios from "axios";
در قسمت 9 که Lifecycle hooks را در آن بررسی کردیم، عنوان شد که در اولین بار نمایش یک کامپوننت، بهترین مکان دریافت اطلاعات از سرور و سپس به روز رسانی UI، متد componentDidMount است. به همین جهت میانبر cdm را در VSCode نوشته و دکمه‌ی tab را فشار می‌دهیم تا به صورت خودکار این متد را ایجاد کند. در ادامه این متد را به صورت زیر تکمیل می‌کنیم:
  componentDidMount() {
    const promise = axios.get("https://localhost:5001/api/posts");
    console.log(promise);
  }
متد axios.get، کار دریافت اطلاعات از سرور را انجام می‌دهد و اولین آرگومان آن، URL مدنظر است. این متد، یک Promise را بازگشت می‌دهد. یک Promise، شیءای است که نتیجه‌ی یک عملیات async را نگهداری می‌کند و یک عملیات async، عملیاتی است که قرار است در آینده تکمیل شود. زمانیکه یک HTTP GET را ارسال می‌کنیم، وقفه‌ای تا زمان بازگشت اطلاعات از سرور وجود خواهد داشت و این عملیات، آنی نیست. بنابراین حالت آغازین یک Promise، در وضعیت pending قرار می‌گیرد. پس از پایان عملیات async، این وضعیت به یکی از حالات resolved (در حالت موفقیت آمیز بودن عملیات) و یا rejected (در حالت شکست عملیات) تغییر پیدا می‌کند.



تنظیمات CORS مخصوص React در برنامه‌های ASP.NET Core 3x

همانطور که مشاهده می‌کنید، پس از ذخیره سازی تغییرات، با اجرای برنامه، این Promise در حالت pending قرار گرفته و همچنین پس از پایان آن، حاوی نتیجه‌ی عملیات نیز می‌باشد که در اینجا rejected است. علت شکست عملیات را در سطر بعدی آن ملاحظه می‌کنید که عنوان کرده‌است «CORS policy» مناسبی در سمت سرور، برای این درخواست وجود ندارد؛ چرا؟ چون برنامه‌ی React ما در مسیر http://localhost:3000/ اجرا می‌شود و برنامه‌ی Web API در مسیر دیگری https://localhost:5001/ که شماره‌ی پورت این‌دو یکی نیست. به همین جهت عنوان می‌کند که نیاز است در سمت سرور، هدرهای خاصی برای پردازش این نوع درخواست‌های با Origin متفاوت وجود داشته باشد، تا مرورگر اجازه‌ی دسترسی به آن‌را بدهد. برای رفع این مشکل، برنامه‌ی sample-22-backend را گشوده و تغییرات زیر را اعمال می‌کنیم:
ابتدا تنظیمات AddCors را با تعریف یک CORS policy جدید مخصوص آدرس http://localhost:3000، به متد ConfigureServices کلاس آغازین برنامه اضافه می‌کنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
       options.AddPolicy("ReactCorsPolicy",
          builder => builder
            .AllowAnyMethod()
            .AllowAnyHeader()
            .WithOrigins("http://localhost:3000")
            .AllowCredentials()
            .Build());
    });
    services.AddSingleton<IPostsDataSource, PostsDataSource>();
    services.AddControllers();
}
سپس میان‌افزار آن‌را با فراخوانی UseCors که باید بین UseRouting و UseEndpoints تعریف شود، فعال می‌کنیم:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    //app.UseAuthentication();
    //app.UseAuthorization();

    app.UseCors("ReactCorsPolicy");

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
       endpoints.MapControllers();
    });
}
اکنون اگر صفحه‌ی برنامه‌ی React را ریفرش کنیم، به نتیجه‌ی زیر خواهیم رسید:


اینبار Promise بازگشت داده شده، در حالت resolved قرار گرفته‌است که به معنای موفقیت آمیز بودن عملیات async است. وجود [[PromiseStatus]] به معنای یک internal property است که توسط dot notation قابل دسترسی نیست. در اینجا [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است که نتیجه‌ی عملیات (response دریافتی از سرور) در آن قرار می‌گیرد. برای مثال در data آن، آرایه‌ی مطالب دریافتی از سرور، قابل مشاهده‌است و یا status=200 به معنای موفقیت آمیز بودن پردازش درخواست، از سمت سرور است.

البته زمانیکه درخواست افزودن رکورد جدیدی را به سمت سرور ارسال می‌کنیم، می‌توان دو درخواست را در برگه‌ی network ابزارهای توسعه دهندگان مرورگر، مشاهده کرد:


در اولین درخواست، Request Method: OPTIONS را داریم که دقیقا مرتبط است با بررسی CORS توسط مرورگر.


دریافت اطلاعات شیء response از یک Promise و نمایش آن

همانطور که عنوان شد، [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است. بنابراین اکنون این سؤال مطرح می‌شود که چگونه می‌توان به اطلاعات آن دسترسی یافت؟
این شیء Promise، دارای متدی است به نام then است که نتیجه‌ی عملیات async را بازگشت می‌دهد. البته این روش قدیمی کار کردن با Promiseها است و ما از آن در اینجا استفاده نخواهیم کرد. در جاوا اسکریپت مدرن، می‌توان از واژه‌ی کلیدی await برای دسترسی به شیء response دریافتی از سرور، استفاده کرد:
  async componentDidMount() {
    const promise = axios.get("https://localhost:5001/api/posts");
    console.log(promise);
    const response = await promise;
    console.log(response);
  }
هر جائیکه از واژه‌ی کلیدی await استفاده می‌شود، متد جاری را باید با واژه‌ی کلیدی async نیز مزین کرد. پس از این تغییرات، اکنون شیء response، حاوی اطلاعات اصلی و واقعی دریافتی از سرور است؛ برای مثال خاصیت data آن، حاوی آرایه‌ی مطالب می‌باشد:



البته قطعه کد نوشته شده، صرفا جهت توضیح مراحل مختلف عملیات، به این صورت چند مرحله‌ای نوشته شد، وگرنه می‌توان واژه‌ی کلیدی await را پیش از فراخوانی متدهای Axios نیز قرار داد:
  async componentDidMount() {
    const response = await axios.get("https://localhost:5001/api/posts");
    console.log(response);
  }
با توجه به اینکه اطلاعات اصلی شیء response، در خاصیت data آن قرار دارد، می‌توان با استفاده از Object Destructuring، خاصیت data آن‌را دریافت و سپس تغییر نام داد:
class App extends Component {
  state = {
    posts: []
  };

  async componentDidMount() {
    const { data: posts } = await axios.get("https://localhost:5001/api/posts");
    this.setState({ posts }); // = { posts: posts }
  }
پس از مشخص شدن آرایه‌ی posts دریافتی از سرور، اکنون می‌توان با فراخوانی متد setState و به روز رسانی خاصیت posts آن، سبب رندر مجدد این کامپوننت و در نتیجه نمایش اطلاعات نهایی شد:



ایجاد یک مطلب جدید توسط Axios

در برنامه‌ی React ای ایجاد شده، یک دکمه‌ی Add نیز برای افزودن مطلبی جدید درنظر گرفته شده‌است. در یک برنامه‌ی واقعی‌تر، معمولا فرمی وجود دارد و نتیجه‌ی آن در حین submit، به سمت سرور ارسال می‌شود. در اینجا این سناریو را شبیه سازی خواهیم کرد:
const apiEndpoint = "https://localhost:5001/api/posts";

class App extends Component {
  state = {
    posts: []
  };

  async componentDidMount() {
    const { data: posts } = await axios.get(apiEndpoint);
    this.setState({ posts });
  }

  handleAdd = async () => {
    const newPost = {
      title: "new Title ...",
      body: "new Body  ...",
      userId: 1
    };
    const { data: post } = await axios.post(apiEndpoint, newPost);
    console.log(post);

    const posts = [post, ...this.state.posts];
    this.setState({ posts });
  };
توضیحات:
- چون قرار است از آدرس https://localhost:5001/api/posts در قسمت‌های مختلف برنامه استفاده کنیم، فعلا آن‌را به صورت یک ثابت تعریف کرده و در متدهای get و post استفاده کردیم.
- در متد منتسب به خاصیت handleAdd، یک شیء جدید post را با ساختاری مشابه آن ایجاد کرده‌ایم. این شیء جدید، دارای Id نیست؛ چون قرار است از سمت سرور پس از ثبت در بانک اطلاعاتی دریافت شود.
- سپس این شیء جدید را توسط متد post کتابخانه‌ی Axios، به سمت سرور ارسال کرده‌ایم. این متد نیز یک Promise را باز می‌گرداند. به همین جهت از واژه‌ی کلیدی await برای دریافت نتیجه‌ی واقعی آن استفاده شده‌است. همچنین هر زمانیکه await داریم، نیاز به ذکر واژه‌ی کلیدی async نیز هست. اینبار این واژه باید پیش از قسمت تعریف پارامتر متد قرار گیرد و نه پیش از نام handleAdd؛ چون handleAdd در واقع یک خاصیت است که متدی به آن انتساب داده شده‌است.
- نتیجه‌ی دریافتی از متد axios.post را اینبار به post، بجای posts تغییر نام داده‌ایم و همانطور که در تصویر زیر مشاهده می‌کنید، خاصیت id آن در سمت سرور مقدار دهی شده‌است:


- در آخر برای افزودن این رکورد، به مجموعه‌ی رکوردهای موجود، از روش spread operator استفاده کرده‌ایم تا ابتدا شیء post دریافتی از سمت سرور درج شود و سپس مابقی اعضای آرایه‌ی posts موجود در state، در این آرایه گسترده شده و یک آرایه‌ی جدید را تشکیل دهند. سپس این آرایه‌ی جدید را جهت به روز رسانی state و در نتیجه‌ی آن، به روز رسانی UI، به متد setState ارسال کرده‌ایم، که نتیجه‌ی آن درج این رکورد جدید، در ابتدای لیست است:


 
به روز رسانی اطلاعات در سمت سرور

در اینجا پیاده سازی متد put را مشاهده می‌کنید:
  handleUpdate = async post => {
    post.title = "Updated";
    const { data: updatedPost } = await axios.put(
      `${apiEndpoint}/${post.id}`,
      post
    );
    console.log(updatedPost);

    const posts = [...this.state.posts];
    const index = posts.indexOf(post);
    posts[index] = { ...post };
    this.setState({ posts });
  };
- با کلیک بر روی دکمه‌ی update هر ردیف نمایش داده شده، شیء post آن ردیف را در اینجا دریافت و سپس برای مثال خاصیت title آن‌را به مقداری جدید به روز رسانی می‌کنیم.
- اکنون امضای متد axios.put هرچند مانند متد post است، اما متد Update تعریف شده‌ی در سمت API سرور، یک چنین مسیری را نیاز دارد api/Posts/{id}. به همین جهت ذکر id مطلب، در URL نهایی نیز ضروری است.
- در اینجا نیز از واژه‌های await و async برای دریافت نتیجه‌ی واقعی عملیات put و همچنین عملیات گذاری این متد به صورت async، استفاده شده‌است.
- در آخر، ابتدا آرایه‌ی posts موجود در state را clone می‌کنیم. چون می‌خواهیم در آن، در ایندکسی که شیء post جاری قرار دارد، مقدار به روز رسانی شده‌ی آن‌را قرار دهیم. سپس این آرایه‌ی جدید را جهت به روز رسانی state و در نتیجه‌ی آن، به روز رسانی UI، به متد setState ارسال کرده‌ایم:



حذف اطلاعات در سمت سرور

برای حذف اطلاعات در سمت سرور، نیاز است یک HTTP Delete را به آن ارسال کنیم که اینکار را می‌توان توسط متد axios.delete انجام داد. URL ای را که دریافت می‌کند، شبیه به URL ای است که برای حالت put ایجاد کردیم:
  handleDelete = async post => {
    await axios.delete(`${apiEndpoint}/${post.id}`);

    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts });
  };
پس از به روز رسانی وضعیت سرور، در چند سطر بعدی، کار فیلتر سمت کلاینت مطالبی را انجام می‌دهیم که id مطلب حذف شده، در آن‌ها نباشد. سپس state را جهت به روز رسانی UI، با این آرایه‌ی جدید posts، به روز رسانی می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-02.zip و sample-22-frontend-part-02.zip
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
یک نکته‌ی تکمیلی
شبیه سازی customErrors در نگارش‌های دیگر ASP.NET که در فایل web.config قابل تنظیم است:
<customErrors mode="On" defaultRedirect="error">
        <error statusCode="404" redirect="error/notfound" />
        <error statusCode="403" redirect="error/forbidden" />
</customErrors>
در ASP.NET Core چنین شکلی را پیدا می‌کند. ابتدا در متد Configure کلاس آغازین برنامه، میان افزارهای مطلب فوق را اضافه می‌کنیم:
        public void Configure(IApplicationBuilder app)
        {
            if (env.IsDevelopment())
            {
                app.UseDatabaseErrorPage();
                app.UseDeveloperExceptionPage();
            }
            app.UseExceptionHandler("/error/index/500");
            app.UseStatusCodePagesWithReExecute("/error/index/{0}");
در اینجا ذکر مسیر کامل اکشن متد Index و کنترلر Error ضروری هستند. سپس این کنترلر چنین محتوایی را خواهد داشت:
    public class ErrorController : Controller
    {
        private readonly ILogger<ErrorController> _logger;

        public ErrorController(ILogger<ErrorController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index(int? id)
        {
            var logBuilder = new StringBuilder();

            var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
            logBuilder.AppendLine($"Error {id} for {Request.Method} {statusCodeReExecuteFeature?.OriginalPath ?? Request.Path.Value}{Request.QueryString.Value}\n");

            var exceptionHandlerFeature = this.HttpContext.Features.Get<IExceptionHandlerFeature>();
            if (exceptionHandlerFeature?.Error != null)
            {
                var exception = exceptionHandlerFeature.Error;
                logBuilder.AppendLine($"<h1>Exception: {exception.Message}</h1>{exception.StackTrace}");
            }

            foreach (var header in Request.Headers)
            {
                var headerValues = string.Join(",", value: header.Value);
                logBuilder.AppendLine($"{header.Key}: {headerValues}");
            }
            _logger.LogError(logBuilder.ToString());

            if (id == null)
            {
                return View("Error");
            }

            switch (id.Value)
            {
                case 401:
                case 403:
                    return View("AccessDenied");
                case 404:
                    return View("NotFound");

                default:
                    return View("Error");
            }
        }
    }
- در اینجا اگر UseExceptionHandler فعال شده باشد، امکان دسترسی به سرویس IExceptionHandlerFeature خواهد بود.
- و اگر UseStatusCodePagesWithReExecute فعال شده باشد، سرویس IStatusCodeReExecuteFeature اطلاعات مسیر اصلی درخواستی را ارائه می‌دهد.
- سپس بر اساس id ارسالی به این اکشن متد می‌توان برای مثال صفحه‌ی 404 (یافت نشد) و یا سایر صفحات دلخواه دیگری را به صورت انتخابی نمایش داد.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
با سلام؛ من از DistributedCacheTicketStore استفاده کردم و به ازای هر درخواست یک دستور آپدیت بر روی جدول AppSqlCache انجام میشود:
exec sp_executesql
    N'UPDATE [dbo].[AppSqlCache] SET ExpiresAtTime = (CASE WHEN DATEDIFF(SECOND, @UtcNow, AbsoluteExpiration) <= SlidingExpirationInSeconds THEN AbsoluteExpiration ELSE DATEADD(SECOND, SlidingExpirationInSeconds, @UtcNow) END) WHERE Id = @Id AND @UtcNow <= ExpiresAtTime AND SlidingExpirationInSeconds IS NOT NULL AND (AbsoluteExpiration IS NULL OR AbsoluteExpiration <> ExpiresAtTime) ;SELECT Id, ExpiresAtTime, SlidingExpirationInSeconds, AbsoluteExpiration, Value FROM [app].[SqlCache] WHERE Id = @Id AND @UtcNow <= ExpiresAtTime;',
    N'@Id nvarchar(449),@UtcNow datetimeoffset(7)',
    @Id = N'AuthSessionStore-380ca866e7744f569659a3c160eca7ef',
    @UtcNow = '2020-05-24 21:30:11.0837838 +00:00';
و زمان ترافیک سایت آپدیت این جدول باعث کندی می‌شود. آیا ایده‌ی برای حال این مشکل هست؟
نظرات مطالب
ایجاد نصاب یک قالب پروژه جدید چند پروژه‌ای در ویژوال استودیو
- ممنون. افزونه خیلی کاربردی و مفیدی است.
- البته در حالت دستی عنوان شده امکان تعریف آیکون و غیره هم هست. در متن، لینک داده شده به مراجع تولید فایل‌های vstemplate و vscontent که برای نمونه یک مدخل اضافه‌تر برای آیکون پیدا می‌کند:
<Icon>__Template_small.png</Icon>
در کل بد نیست یک برنامه نویس بدونه پشت صحنه این اعمال به چه صورتی هست.
نظرات مطالب
ASP.NET MVC #7
به مقدار رندر شده‌ی کد زیر توجه کنید :
@{
    string htmlContent = "&";
}

<span title="@Html.Raw(htmlContent)" id="@Html.Raw(htmlContent)">@Html.Raw(htmlContent)</span>

مقدار رندر شده :
<span title="&amp;" id="&amp;">&</span>

چرا مقادیر Attributeها encode شده اما مقدار بین تگ encode نشده است؟
نظرات مطالب
راه اندازی StimulSoft Report در ASP.NET MVC
با تشکر بابت مطلب جاری، همچنین بابت تکمیل پست جاری ما بجای استفاده از
TempData["id"]=id;
می‌توان از ارسال مقدارآی‌دی  به صورت پارامتر استفاده نمود و همچنین در اینجا فقط به یک موجودیت برای مثال Person اشاره شده است، با این حال می‌توان بجای استفاده صریح
report.RegBusinessObject("driverReport", driver);
و
report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
از TempData که برای ارسال آی‌دی استفاده کردید، بهره برد، بدین معنی در گزارش‌های متفاوت کافیست از TempData برای ارسال مسیر فایل MRT و آبجکت استفاده نمود و در متد LoadReportSnapshot به مقادیر ارسالی دسترسی و جایگزاری نمود، با این عمل + فعال سازی طراحی گزارش استیمول به صورت آنلاین یک گزارش داینامیک خواهید داشت.
البته راه‌های دیگری به منظور داینامیک سازی موجود است اما این روش در پروژه‌های معمولی جوابگو خواهد بود.
نظرات مطالب
Blazor 5x - قسمت هشتم - مبانی Blazor - بخش 5 - تامین محتوای نمایشی کامپوننت‌های فرزند توسط کامپوننت والد
یک نکته‌ی تکمیلی: روش ارسال داده‌ها به RenderFragmentها

در مباحث اعتبارسنجی و احراز هویت Blazor در قسمت‌های بعدی، به قطعه کد context@ داری در داخل یک RenderFragment خواهیم رسید:
<AuthorizeView>
    <Authorized>
                Hello, @context.User.Identity.Name!
    </Authorized>
</AuthorizeView>
اکنون این سؤال مطرح است که این context@ از کجا آمده‌است و چه مفهومی را دارد؟
برای پاسخ به این سؤال نیاز است با مفهوم «Templated components» در برنامه‌های Blazor آشنا شد. تا اینجا از RenderFragmentها صرفا جهت فراهم آوردن قسمت ثابتی از قالب کامپوننت جاری، توسط استفاده کننده‌ی از آن، کمک گرفتیم. اما در همان سمت استفاده کننده، امکان دسترسی به اطلاعات مهیای داخل آن فرگمنت نیز وجود دارد. برای نمونه به کدهای کامپوننت TableTemplate.razor دقت کنید:
@typeparam Titem

<table class="table">
    <thead>
        <tr>@TableHeader</tr>
    </thead>
    <tbody>
        @foreach (var item in Items)
        {
            <tr>@RowTemplate(item)</tr>
        }
    </tbody>
</table>

@code {
    [Parameter]
    public RenderFragment TableHeader { get; set; }

    [Parameter]
    public RenderFragment<TItem> RowTemplate { get; set; }

    [Parameter]
    public IReadOnlyList<TItem> Items { get; set; }
}
- این کامپوننت، قالب سفارشی ثابت هدر جدول را توسط یک RenderFragment، از بکارگیرنده‌ی خود دریافت می‌کند.
- همچنین یک RenderFragment جنریک را نیز مشاهده می‌کنید که قالب ردیف‌های جدول را تامین می‌کند. نوع جنریک قابل دسترسی در این کامپوننت، توسط دایرکتیو typeparam Titem@ تعریف شده‌ی در ابتدای آن، مشخص شده‌است.
- بنابراین هربار که ردیفی از بدنه‌ی جدول در حال رندر است، یک شیء item از نوع TItem را به قالب سفارشی تامین شده‌ی توسط بکارگیرنده‌ی خود ارسال می‌کند.
اکنون این سؤال مطرح می‌شود که چگونه می‌توان به شیء item، در سمت والد یا بکارگیرنده‌ی کامپوننت TableTemplate فوق دسترسی یافت؟
برای اینکار می‌توان از پارامتر ویژه‌ای به نام context که یک implicit parameter است و به صورت پیش‌فرض تعریف شده و مهیا است، در سمت کدهای بکارگیرند‌ه‌ی کامپوننت، استفاده کرد:
@page "/pets"
<h1>Pets</h1>
<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string Name { get; set; }
    }
}
- در اینجا روش بکارگیری کامپوننت TableTemplate را در کامپوننتی دیگر مشاهده می‌کنید. توسط فرگمنت TableHeader، قالب ثابت هدر این جدول تامین شده‌است. توسط فرگمنت RowTemplate قالب پویای ردیف‌های جدول ارائه شده‌است.
- همچنین در اینجا پارامتر ویژه‌ای به نام context@ را نیز مشاهده می‌کنید. این پارامتر همان شیء item ای است که در حین رندر هر ردیف جدول، به فرگمنت RowTemplate ارسال می‌شود. به این ترتیب کامپوننت والد می‌تواند از اطلاعات در حال رندر توسط کامپوننت فرگمنت دار، مطلع شود و از آن استفاده کند. در این مثال، نوع context@، از نوع کلاس Pet است که سعی شده بر اساس نوع پارامتر Items ارسالی به آن، توسط کامپایلر تشخیص داده شود. حتی می‌توان این نوع را به صورت صریحی نیز مشخص کرد:
<TableTemplate Items="pets" TItem="Pet">
این مورد زمانی مفید است که چندین پارامتر جنریک را در کامپوننت تعریف کرده باشیم:
<SomeGenericComponent TParam1="Person" TParam2="Supplier" TItem=etc/>
و یا می‌توان نام پارامتر ویژه‌ی context@ را در تمام RenderFragmentهای جنریک، با ذکر ویژگی Context برای مثال به pet تغییر داد:
<TableTemplate Items="pets" Context="pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>
و یا حتی می‌توان این نام را به صورت اختصاصی به ازای هر RenderFragment جنریک، مشخص کرد. برای اینکار ویژگی Context را دقیقا بر روی RenderFragment مدنظر قرار می‌دهیم:
<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate Context="pet">
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>
این تغییرنام بهتر است زمانی صورت گیرد که نام پیش‌فرض context، با نام مشابه دیگری در کامپوننت جاری، تداخل پیدا کرده‌است.
نظرات مطالب
تهیه قالب برای ارسال ایمیل‌ها در ASP.NET Core توسط Razor Viewها
درود
آیا امکان دارد کد داخل فایل view را بصورت string (بعنوان مثال از بانک اطلاعاتی یا ساخته شده بصورت دستی ) مانند همین کد رندر کنیم .
مثال :
کد html ای بصورت string داریم که در بانک اطلاعاتی ذخیره شده است :
@model string
<div>@Model</div>
<div><CutumTagHelper asp-id="1000" /></div>
حال در قسمتی از View اصلی میخواهیم این مقدار string خوانده شده از بانک را ابتدا بصورت یک view مجزا رندر و نتیجه آن را بصورت Html نمایش دهیم :
بجای
...

@Html.Raw(Model.HtmlData)

....
چنین کدی داشته باشیم :

...

@Html.SomeNameLikeRenderedRaw(Model.HtmlData,Model.SomeDataAsDynamicViewModel)

....

مطالب
ساخت یک سایت ساده‌‌ی نمایش لیست فیلم با استفاده از Vue.js - قسمت اول
Vue.js  یکی از محبوب ترین فریم ورک‌های  SPA است و سایت جاری نیز دارای مقالات خوبی درباره‌ی Vue.js می‌باشد. قصد داریم طی چند مقاله با استفاده از Vue.js و چندین پلاگین مطرح آن، یک سایت ساده‌ی نمایش فیلم را ایجاد کنیم. ابتدا Node.js  را بر روی سیستم خود نصب کنید (پیشنهاد ما نسخه‌ی LTS می‌باشد). مراحل نصب آن ساده است و بصورت Nextهایی پی در پی می‌باشد؛ بصورت پیش فرض npm نیز همراه آن نصب میشود. سپس دو دستور زیر را جهت صحت انجام مراحل نصب، تست نمایید.

در این مقاله با ادیتور VS Code کار میکنیم. بعد از نصب آن، از منوی Terminal، گزینه‌ی New Terminal را کلیک کنید تا پنجره‌ی PowerShell نمایش داده شود؛ برای سرعت و دقت بیشتر در برنامه‌های  vue.js ای. با دستور زیر vue cli را  نصب میکنیم  (فقط یک مرتبه و برای برنامه‌های بعدی vue.jsای، نیازی به اجرای این دستور نداریم):

npm install -g @vue/cli

جهت راه اندازی یک برنامه‌ی پیش فرض Vue.js ای، کافیست دستور زیر را اجرا نماییم تا پکیج‌های مورد نیاز، به همراه کانفیگ اولیه (Zero config) برای ما ایجاد شوند:

vue create movie-app

بعد از ایجاد برنامه در vs code، از طریق منوی File، گزینه Open Folder را کلیک کرده و پوشه برنامه‌ای را که ایجاد کردیم، Select Folder میکنیم. ساختار اولیه‌ی برنامه‌ی ایجاد شده، به شکل زیر می‌باشد:

نیازمندیهای مثال جاری

A) برای گرفتن اطلاعات مورد نمایش در مثال جاری، از سایت omdbapi.com استفاده میکنیم که با دریافت یک api key آن بصورت رایگان، میتوانیم web serviceهای آن را Call نماییم.

B) از  vuetify برای ui استفاده میکنیم که بصورت Material Design و دارای کامپوننت‌های غنی می‌باشد؛ ضمن اینکه RTL را هم پشتیبانی میکند.

برای نصب آن در Terminal دستور زیر را اجرا میکنیم:

vue add vuetify

سپس جهت تست و صحت افزوده شدن و کانفیگ درست، با دستور زیر برنامه را اجرا میکنیم:

npm run serve

بعد از اجرای دستور فوق، روی گزینه زیر ctrl+click میکنیم تا نتیجه کار در مرورگر قابل رویت باشد:

نمایش صفحه زیر نشان دهنده‌ی درستی انجام کار تا اینجا است:


نکته: جهت استفاده از امکان RTL کافیست در فایل vuetify.js واقع در پوشه‌ی plugins، تغییرات زیر را انجام دهیم. در مثال جاری بدلیل اینکه اطلاعات انگلیسی می‌باشند، از نسخه LTR آن استفاده میکنیم؛ هر چند یکسری api فارسی نیز موجود می‌باشد که میتوان از آنها استفاده نمود.

import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import 'vuetify/src/stylus/app.styl'

Vue.use(Vuetify, {
  iconfont: 'md',
  rtl: true
})


C) نصب  vue-router : جهت انجام routeهای تودرتو ، مپ کردن کامپوننت ها با آدرسی مشخص، کار با پارامتر و  HTML5 History API  مورد استفاده قرار میگیرد. برای نصب آن، دستور زیر را اجرا میکنیم:

npm install vue-router

برای نوشتن routeهای مورد نیاز، یک فولدر را با نام router، در پوشه src برنامه ایجاد میکنیم و یک فایل جاوا اسکریپتی را در آن با نام index.js، میسازیم (این ساختار برای مدیریت بهتر پروژه می‌باشد):

درون فایل  index.js، محتویات زیر را طبق مستندات آن قرار میدهیم:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

جهت استفاده از این router، نیاز است تا در نمونه‌ی وهله سازی شده‌ی vue برنامه بکار گرفته شود. فایل  main.js  را باز کنید و خط زیر را در قسمت بالای برنامه وارد کنید:

import router from './router'

اکنون محتویات فایل  main.js بشکل زیر می‌باشد:

import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')


D) نصب axios : برای انجام  درخواستهای  HTTP  و عملیات ا‌ی‌جکس در vue.js  ترجیحا بهتر است از axios که یک کتابخانه‌ی محبوب می‌باشد و کار با آن ساده است، استفاده شود. برای نصب آن، دستور زیر را اجرا میکنیم:

npm install axios


E) نصب vuex : کتابخانه‌ای جهت مدیریت حالت (state management) برای  vue.js میباشد و مشابه آن Flux و Redux برای React می‌باشند. برای  نصب، دستور زیر را اجرا میکنیم:

npm install vuex


برای بکارگیری آن یک فولدر را با نام store در پوشه‌ی src برنامه ایجاد میکنیم و یک فایل جاوا اسکریپتی را در آن با نام index.js میسازیم (این ساختار برای مدیریت بهتر پروژه می‌باشد). درون فایل  index.js، محتویات زیر را طبق مستندات آن و ^ قرار میدهیم. 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store()

برای استفاده و کانفیگ آن، محتویات فایل  main.js را بشکل زیر تغییر دهید:

import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'
import {store} from './store'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store,
  router
}).$mount('#app')



دریافت کد قسمت اول 

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

npm install