نظرات مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
سلام؛ اگر قصد داشته باشیم از طریق Angular 2  دانلود رو فراخوانی کنیم ، به شکلی که یک فایل حجیم بر روی مرورگر کاربر دانلود بشه ، پیاده سازی به چه صورت خواهد بود ، الان شما در سمت سرور یک حلقه while دارید که محتوی رو می‌خونه ، این یعنی در هر اجرای حلقه من باید محتوی خوانده شده رو return کنم به کلاینت و دوباره با آفست جدید فراخوانی کنم؟
نحوه فراخوانی من به شکل زیر هست که با FileStreamResult برگشتی از سمت سرور به درستی کار میکنه ولی خب همه فایل رو یکجا برمی گردونه :
 downloadOrder(orderId: number , userId : string) {

    this._http.get(this._config.getApiURI() + '/Download/productfile/' + orderId + '/' + userId, { observe: 'blob'})
      .subscribe(
        (data) => {
          if(this._functionService.isNullOrEmpty(data.body)){
            this._snackBarService.error('فایل پیدا نشد');
            return;
          }
          var contentType = data.body.type || "application/octet-stream";

          var fileInfo = JSON.parse(data.headers.get('FileInfo'));          
          var blob = new Blob([data.body], { type: contentType });
          var url = window.URL.createObjectURL(blob);
          var anchor = document.createElement("a");
          anchor.setAttribute("href", url)
          anchor.setAttribute("download", fileInfo.fileName + fileInfo.fileType);
          anchor.setAttribute("target", "_blank");
          document.body.appendChild(anchor);
          anchor.click();
          setTimeout(function () {
            document.body.removeChild(anchor);
            window.URL.revokeObjectURL(url);
          }, 200);
        },
        error => {
          this._snackBarService.error(error);
        });
  }
ولی حالا که قراره فایل رو در چندین مرحله و با حلقه while برگردونیم ، با این سناریو یعنی باید از طرف کلاینت چندین بار فراخوانی باشه ، مگر اینکه کلا یک window جدید باز کنیم  (که احتمالا با مشکل popup blocker مواجه میشیم) و ... آیا باید در سمت سرور بایت‌های خوانده شده رو تو Response بنویسیم ...
نحوه پیاده سازی کد پایین به چه شکل میشه
             while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)  
                    {                        
                        await Response.Body.WriteAsync(buffer);                        
                        //return File(buffer, GetContentType(fullPath), newFileName, true);                        
                        //return new FileStreamResult(stream,"application/octet-stream");
                    }
اشتراک‌ها
خداحافظی با حملات CSRF و cross-site leak برای همیشه از کروم 80 به بعد

In Chrome 80 and later, cookies will default to SameSite=Lax. This means that cookies will automatically be sent only in a first party context unless they opt-out by explicitly setting a directive of None.
But if you’re a web developer, you should start testing your sites and services now to help ensure a smooth transition. 

خداحافظی با حملات CSRF و cross-site leak برای همیشه از کروم 80 به بعد
اشتراک‌ها
استایل دهی به برنامه های Xamarin.Forms با CSS

Some months ago a feature landed in Xamarin.Forms that seemed to truly polarize the Xamarin.Forms community: support for styling applications using CSS. Some argued that it was an unnecessary introduction to "Web" technology to the native development experience, and others that it simply isn't the right solution to the problem.  While I sympathize with the latter opinion and think there's plenty of room for some good debate on the right path forward, I count myself as part of a third camp: I think that CSS is a powerful (and frequently maligned) solution to the problem of styling native mobile applications.

استایل دهی به برنامه های Xamarin.Forms با CSS
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت نهم - مسیریابی
به روز رسانی

تمام مسیریابی‌های  این سری به نگارش سوم روتر AngularJS 2.0 به روز رسانی شدند.
ریز جزئیات تغییرات

توضیحات:

ابتدا نیاز است وابستگی‌های روتر جدید را به نحو ذیل به فایل package.json اضافه کنید:
 "dependencies": {
    // ...
    "@angular/router": "^3.0.0-alpha.7",
    // ...  
},
سپس
یک فایل جدید را به نام app.routes.ts به ریشه‌ی پروژه اضافه کنید، با این محتوا
import { provideRouter, RouterConfig } from '@angular/router';

import { ProductListComponent } from './products/product-list.component';
import { WelcomeComponent } from './home/welcome.component';
import { ProductDetailComponent } from './products/product-detail.component';
import { ProductFormComponent }  from './products/product-form.component';
import { SignupFormComponent } from './users/signup-form.component';
import { TypedShaComponent } from './using-third-party-libraries/typed-sha.component';
import { UnTypedShaComponent } from './using-third-party-libraries/untyped-sha.component';
import { UsingJQueryAddonsComponent } from './using-jquery-addons/using-jquery-addons.component';

export const routes: RouterConfig = [
    { path: '', component: WelcomeComponent },
    { path: 'welcome', component: WelcomeComponent },
    { path: 'products', component: ProductListComponent },
    { path: 'product/:id', component: ProductDetailComponent },
    { path: 'addproduct', component: ProductFormComponent },
    { path: 'adduser', component: SignupFormComponent },
    { path: 'typedsha', component: TypedShaComponent },
    { path: 'untypedsha', component: UnTypedShaComponent },
    { path: 'usingjquery', component: UsingJQueryAddonsComponent }
];

export const APP_ROUTER_PROVIDERS = [
  provideRouter(routes)
];
در اینجا مسیریابی‌های قدیمی برنامه از فایل app.component.ts خارج شده و به یک فایل مستقل منتقل شده‌اند.
در سیستم مسیریابی جدید، خاصیت‌های name و useAsDefault وجود ندارند و حذف شده‌اند. همچنین مسیریابی‌ها نباید با / شروع شوند.
به علاوه در فایل index.html، مسیر ریشه به نحو ذیل مشخص می‌شود:
 <base href=".">
پس از تعریف فایل app.routes.ts، نیاز است آن‌را به main.ts معرفی کرد:
 // ...
import { APP_ROUTER_PROVIDERS } from './app.routes';
// ...
bootstrap(AppComponent, [
   // ...
   APP_ROUTER_PROVIDERS
])
.catch(err => console.error(err));
به این ترتیب کار برپایی مسیریابی اصلی سایت به پایان می‌رسد.
البته باید دقت داشت که فایل systemjs.config.js هم کمی نیاز است جهت بارگذاری این مسیریاب جدید اصلاح شود.

در ادامه، در فایل app.component.ts، دایرکتیوهای مرتبط با مسیریابی که در ROUTER_DIRECTIVES قرار دارند، اینبار از ماژول ذیل تامین می‌شوند:
 import { ROUTER_DIRECTIVES } from '@angular/router';

سپس لینک‌های مسیریابی، اینبار بجای نام مسیریابی که در نگارش سوم روتر، حذف شده‌است، به همان مسیر متناظر اشاره می‌کند:
 <a [routerLink]="['/welcome']">Home</a>
این مورد جهت متدهای navigate هم صدق می‌کند و بجای نام، به مسیر مدنظر باید ویرایش شوند:
 this._router.navigate(['/products']);
تغییر مهم دیگر رخ داده، در مورد نحوه‌ی دسترسی به پارامترهای مسیریابی است که نمونه‌ای از آن‌را در مورد product-detail.component.ts در اینجا مشاهده می‌کنید:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
    templateUrl: 'app/products/product-detail.component.html'
    //template: require('./product-detail.component.html')//for webpack
})
export class ProductDetailComponent implements OnInit, OnDestroy {
    private sub: any;
    pageTitle: string = 'Product Detail';
    constructor(private _route: ActivatedRoute, private _router: Router) {
    }

    ngOnInit(): void {
        this.sub = this._route.params
            .subscribe(params => {
                let id = +params['id']; // (+) converts string 'id' to a number
                this.pageTitle += `: ${id}`;
            });
    }

    ngOnDestroy(): void {
        this.sub.unsubscribe(); // we must unsubscribe before Angular destroys the component. Failure to do so could create a memory leak.
    }

    onBack(): void {
        this._router.navigate(['/products']);
    }
}
اینبار سرویس RouteParams حذف شده‌است و بجای آن ActivatedRoute را داریم که خاصیت params آن، یک observable را باز می‌گرداند. به همین جهت باید متد subscribe آن‌را جهت دسترسی به پارامترهای مسیریابی، فراخوانی کرد. این فراخوانی نیز باید در متد ngOnInit باشد و همچنین برای جلوگیری از نشتی حافظه، باید در ngOnDestroy کار unsubscribe آن انجام شود.

یک اصلاح دیگر هم در اینجا داریم. لینک به صفحه‌ی جزئیات هر محصول اینبار به صورت زیر ویرایش می‌شود (در فایل product-list.component.html):
 <a [routerLink]="['/product', product.productId]">
  {{product.productName}}
</a>
اشتراک‌ها
چه تفاوتی بین Update و Upgrade است؟

Update and upgrade are two different ways to make a change to an app or operating system. But the prime difference lies in a number of modifications made and the importance of those modifications. A software update includes bug fixes, and other small improvements, while a software upgrade changes the version of a software

چه تفاوتی بین Update و Upgrade است؟
اشتراک‌ها
بحثی در مورد آینده‌ی #C از Mads Torgersen

What’s Next in C#? - Mads Torgersen - Copenhagen DevFest 2023
NDC Conferences
Join Mads on a tour of upcoming language features in C#. While still very much in the works, C# 12 is starting to take shape. We touch on some of the ways, big and small, that C# is striving to make your life easier in the coming years. 

بحثی در مورد آینده‌ی #C از Mads Torgersen
نظرات مطالب
بهبود کارآیی حلقه‌های foreach در دات نت 7
یک نکته‌ی تکمیلی: آشنایی با مفهوم «C# Lowering»


در این مطلب، جهت بررسی درک علت یکسان بودن کارآیی حلقه‌هایی که از دیدگاه ما یکی نیستند، از قابلیت نمایش #Low-level C استفاده شد که نام اصلی آن «C# Lowering» است. Lowering به معنای ترجمه‌ی امکانات سطح بالای یک زبان به امکانات سطح پایین آن است. یعنی حاصل عملیات صورت گرفته نیز باز به همان زبان اولیه است که نمونه‌ی آن، تبدیل یک حلقه‌ی foreach سطح بالا به نمونه‌ی سطح پایینی است که توسط NET Runtime. بهتر درک شده و ساده‌تر اجرا می‌شود.

مزایای Lowering
- بهبود کارآیی برنامه: برای مثال یکی از کارهایی که در این بین عموما انجام می‌شود «Loop unrolling» است. یعنی یک حلقه به چندین حلقه‌ی کوچکتر تقسیم می‌شود تا سربار instructions کنترلی حلقه کاهش پیدا کنند.
- طراحی ساده‌تر زبان: اینکار به تیم طراحی زبان امکان نوشتن کدهای اضافه‌تری را می‌دهد که کار برنامه نویس‌ها را کمتر می‌کند. برای مثال یک record واقعا چیزی نیست بجز یک کلاس پیاده سازی کننده‌ی IEquatable به صورت خودکار و در پشت صحنه.

Lowering چه زمانی رخ می‌دهد؟
Lowering جزئی از عملیات صورت گرفته‌ی در حین کامپایل است. زمانیکه دستور dotnet build را صادر می‌کنیم، ابتدا semantics & syntax analysis صورت می‌گیرد تا اگر برای مثال خطای دستوری وجود دارد، مشخص شود. سپس کدها به CIL یا Common intermediate language تبدیل می‌شوند. در حین این قسمت است که عملیات lowering نیز انجام می‌شود.
اگر علاقمند به مشاهده‌ی این کد #C ثانویه‌ی تولید شده‌ی توسط کامپایلر هستید، می‌توان از ابزار https://sharplab.io نیز استفاده کرد. برای مثال در سمت چپ آن کدهای زیر را قرار دهید:
using System;
using System.Collections.Generic;

var list = new List<int> { 1, 2 };

foreach(var item in list)
    Console.Write(item);
سپس در سمت راست آن، گزینه‌ی #Results C را انتخاب کنید تا بتوانید نمونه‌ی معادل تبدیل شده‌ی توسط کامپایلر را مشاهده نمائید.
مطالب
تبدیل اعداد صحیح و اعشاری به حروف در T-SQL با استفاده از Join
استفاده شده از SQL 2008 

روش کار :

1-  دریافت پارامتر ورودی به صورت رشته
2-  درج عناوین اعداد، ارزش مکانی اعداد صحیح و اعشاری  هرکدام در یک جدول
3-  جدا کردن ارقام صحیح و اعشاری
4-  جداکردن سه رقم سه رقم اعداد صحیح و انتقال آنها به جدول مربوطه
5-  Join  جداول عناوین و ارقام جدا شده
6-  ارسال ارقام اعشاری به همین تابع
7-  مشخص کردن ارزش مکانی رقم اعشار
8-  اتصال رشته حروف صحیح و اعشاری

در آخر این مطلب کد این تابع را به صورت کامل، برای دانلود قرار داده ام.

بررسی قسمت‌های مختلف کد


برای اینکه محدودیتی در تعداد ارقام صحیح و اعشاری نداشته باشیم، پارامتر ورودی را از نوع VARCHAR می‌گیریم. پس باید ورودی را بررسی کنیم تا رشته عددی باشد.

بررسی رشته ورودی:

-- @pNumber   پارامتر ورودی

IF LEN(ISNULL(@pNumber, '')) = 0  RETURN NULL

IF (PATINDEX('%[^0-9.-]%', @pNumber) > 0)
   OR (LEN(@pNumber) -LEN(REPLACE(@pNumber, '-', '')) > 1)
   OR (LEN(@pNumber) -LEN(REPLACE(@pNumber, '.', '')) > 1)
   OR (CHARINDEX('-', @pNumber) > 1)
RETURN 'خطا'

IF PATINDEX('%[^0]%', @pNumber) = 0  RETURN 'صفر'
IF (CHARINDEX('.', @pNumber) = 1) SET @pNumber='0'+@pNumber

DECLARE @Negative AS VARCHAR(5) = '';
IF LEFT(@pNumber, 1) = '-'
BEGIN
    SET @pNumber = SUBSTRING(@pNumber, 2, 100)
    SET @Negative = 'منفی '
END
- بررسی NULL  ، خالی بودن و یا داشتن فاصله در رشته،  با دانستن اینکه تابع LEN  فاصله‌های آخر یک رشته را درنظر نمی‌گیرد.
- بررسی رشته ورودی برای پیدا کردن کاراکتر غیر عددی، نقطه و منفی. بررسی تعداد علامت منفی و نقطه که بیشتر از یک مورد نباشند، و در نهایت بررسی اینکه علامت منفی در ابتدای رشته ورودی باشد.
- بررسی صفر بودن ورودی(0)، مقدار ورودی شروع شونده با ممیز(0213. ) و مقدار عددی منفی(21210.0021-).
چیز دیگری به ذهنم نرسید!

درج عناوین در جداول مربوطه:

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

DECLARE @NumberTitle TABLE (val  INT,Title NVARCHAR(100));
INSERT INTO @NumberTitle (val,Title)
VALUES(0, ''),(1, 'یک') ,(2, 'دو'),(3, 'سه'),(4, 'چهار')
,(5, 'پنج'),(6, 'شش'),(7, 'هفت'),(8, 'هشت')
,(9, 'نه'),(10, 'ده'),(11, 'یازده'),(12, 'دوازده')
,(13, 'سیزده'),(14, 'چهارده'),(15, 'پانزده'),(16, 'شانزده')
,(17, 'هفده'),(18, 'هجده'),(19, 'نوزده'),(20, 'بیست')
,(30, 'سی'),(40, 'چهل'),(50, 'پنجاه'),(60, 'شصت')
,(70, 'هفتاد'),(80, 'هشتاد'),(90, 'نود'),(100, 'صد')
,(200, 'دویست'),(300, 'سیصد'),(400, 'چهارصد'),(500, 'پانصد')
,(600, 'ششصد'),(700, 'هفتصد'),(800, 'هشتصد'),(900, 'نهصد')

DECLARE @PositionTitle TABLE (id  INT,Title NVARCHAR(100));
INSERT INTO @PositionTitle (id,title)
VALUES (1, ''),(2, 'هزار'),(3, 'میلیون'),(4, 'میلیارد'),(5, 'تریلیون')
,(6, 'کوادریلیون'),(7, 'کوینتیلیون'),(8, 'سیکستیلون'),(9, 'سپتیلیون')
,(10, 'اکتیلیون'),(11, 'نونیلیون'),(12, 'دسیلیون')
,(13, 'آندسیلیون'),(14, 'دودسیلیون'),(15, 'تریدسیلیون')
,(16, 'کواتردسیلیون'),(17, 'کویندسیلیون'),(18, 'سیکسدسیلیون')
,(19, 'سپتندسیلیون'),(20, 'اکتودسیلیوم'),(21, 'نومدسیلیون')

DECLARE @DecimalTitle TABLE (id  INT,Title NVARCHAR(100));
INSERT INTO @DecimalTitle (id,Title)
VALUES( 1 ,'دهم' ),(2 , 'صدم'),(3 , 'هزارم')
,(4 , 'ده-هزارم'),(5 , 'صد-هزارم'),(6 , 'میلیون ام')
,(7 , 'ده-میلیون ام'),(8 , 'صد-میلیون ام'),(9 , 'میلیاردم')
,(10 , 'ده-میلیاردم')


جداسازی رقم صحیح و اعشاری:

عدد ورودی ممکن است حالت‌های مختلفی داشته باشد مثل:         .00002 , 0.000000 , 234.434400000000 , 123.
بنابراین براساس ممیز، قسمت صحیح را از اعشاری جدا می‌کنیم. برای ورودی که با ممیز شروع شود، در ابتدا تابع بررسی می‌کنیم و عدد صفر را به رشته اضافه می‌کنیم.

بعد از ممیز و اعداد بزرگتر از یک، با صفرهای بی ارزش چه کنیم؟ شاید اولین چیزی که به ذهن برسد استفاده از حلقه (WHILE) برای حذف صفرهای بی ارزش قسمت ممیز باشد؛ ولی من ترجیح می‌دهم که از روش دیگری استفاده کنم :

برعکس کردن رشته قسمت اعشاری، پیدا کردن مکان اولین عدد غیر صفر منهای یک ، و کم کردن عدد بدست آمده از طول رشته اعشاری، قسمت مورد نظر ما را برخواهد گرداند:
SUBSTRING(@DecimalNumber,1, len(@DecimalNumber )-PATINDEX('%[^0]%', REVERSE (@DecimalNumber))-1)
 اما اگر عدد ورودی 20.0 باشد همچنان صفر بی ارزش بعداز ممیز را خواهیم داشت. برای رفع این مشکل کافی است که کاراکتری غیر از صفر را به اول رشته اعشاری اضافه کنیم. من از علامت '?' استفاده کردم. پس به علت اضافه کردن کاراکتر، استارت را از 2 شروع کرده و دیگر نیازی به -1 نخواهیم داشت. با کد زیر قسمت صحیح و اعشاری را بدست می‌آوریم:
DECLARE @IntegerNumber NVARCHAR(100),
@DecimalNumber NVARCHAR(100),
@PointPosition INT =case CHARINDEX('.', @pNumber) WHEN 0 THEN LEN(@pNumber)+1 ELSE CHARINDEX('.', @pNumber) END

SET @IntegerNumber= LEFT(@pNumber, @PointPosition - 1)
SET @DecimalNumber= '?' + SUBSTRING(@pNumber, @PointPosition + 1, LEN(@pNumber))
SET @DecimalNumber=  SUBSTRING(@DecimalNumber,2, len(@DecimalNumber )-PATINDEX('%[^0]%', REVERSE (@DecimalNumber)))

SET @pNumber= @IntegerNumber


جداد کردن سه رقم سه رقم :

- بدست آوردن یکان، دهگان و صدگان
- برای قسمت دهگان، اگر عددی بین 10 تا 19 باشد به صورت کامل (مثلا 15) و در غیر این صورت فقط رقم دهگان. برای بدست آوردن یکان اگر دو رقم آخر بین 10 و 19 بود صفر و در غیر این صورت یکان برگردانده می‌شود و در جدول MyNumbers درج می‌گردد. 
DECLARE @Number AS INT
DECLARE @MyNumbers TABLE (id INT IDENTITY(1, 1), Val1 INT, Val2 INT, Val3 INT)

WHILE (@pNumber) <> '0'
BEGIN
    SET @number = CAST(SUBSTRING(@pNumber, LEN(@pNumber) -2, 3)AS INT)
    
INSERT INTO @MyNumbers
SELECT (@Number % 1000) -(@Number % 100),
CASE 
WHEN @Number % 100 BETWEEN 10 AND 19 THEN @Number % 100
ELSE (@Number % 100) -(@Number % 10)
END,
CASE 
WHEN @Number % 100 BETWEEN 10 AND 19 THEN 0
ELSE @Number % 10
END
    
    IF LEN(@pNumber) > 2
        SET @pNumber = LEFT(@pNumber, LEN(@pNumber) -3)
    ELSE
        SET @pNumber = '0'
END

سطری که تمام مقادیر آن صفر باشد برای ما بی ارزش محسوب می‌شود، مانند سطر یک در عکس زیر (جدول MyNumbers) برای عدد 1200955000 :
@MyNumbers

استفاده از JOIN :

JOIN  کردن جدول اعداد با عناوین عددی براساس ارزش آن‌ها و JOIN  جدول اعداد با جدول ارزش مکانی براساس ID به صورت نزولی(شماره سطر).
DECLARE @Str AS NVARCHAR(2000) = '';
SELECT @Str += REPLACE(REPLACE(LTRIM(RTRIM(nt1.Title + ' ' + nt2.Title + ' ' + nt3.title)),'  ',' '),' ', ' و ')
       + ' ' + pt.title + ' و '
FROM   @MyNumbers  AS mn
       INNER JOIN @PositionTitle pt
            ON  pt.id = mn.id
       INNER JOIN @NumberTitle nt1
            ON  nt1.val = mn.Val1
       INNER JOIN @NumberTitle nt2
            ON  nt2.val = mn.Val2
       INNER JOIN @NumberTitle nt3
            ON  nt3.val = mn.Val3
WHERE  (nt1.val + nt2.val + nt3.val > 0)
ORDER BY pt.id DESC
Replace داخلی: جایگزین کردن "دو فاصله‌ی خالی" با "یک فاصله‌ی خالی"
Replace بیرونی: جایگزینی فاصله‌های خالی با ' و '
همانطور که در بالا اشاره کردم سطرهایی که val2,val1 و val3 آن صفر باشد برای ما بی ارزش هستند، پس آنها را با شرط نوشته شده حذف می‌کنیم.


بدست آوردن مقدار اعشاری:

خوب! حالا نوبت به عدد اعشاری می‌رسد. برای بدست آوردن حروف، مقدار اعشاری بدست آمده را به همین تابع ارسال می‌کنیم و برای بدست آوردن عنوان ارزش مکانی، براساس طول اعشار (ID) آن را در جدول مربوطه پیدا می‌کنیم.
اگر عدد ورودی مثلا 0.355 باشد، تابع باید صفر اول را شناسایی و قسمت عناوین اعشاری را به آن اضافه کند، که این کار با شرط ذیل انجام می‌شود.
اگر رشته اعشار بدون مقدار باشد، تابع مقدار NULL بر می‌گرداند (قسمت بررسی رشته ورودی) و هر رشته ای که با NULL جمع شود برابر با NULL خواهد بود. در این صورت با توجه به کد زیر مقداری به رشته Str به عنوان قسمت اعشاری، اضافه نمی‌گردد.
IF @IntegerNumber='0'  
SET @Str=CASE WHEN PATINDEX('%[^0]%', @DecimalNumber) > 0 THEN @Negative ELSE '' END + 'صفر'
ELSE
SET @Str = @Negative  + LEFT (@Str, LEN(@Str) -2)

DECLARE @PTitle NVARCHAR(100)=ISNULL((SELECT Title FROM @DecimalTitle WHERE id=LEN(@DecimalNumber)),'')
SET @Str += ISNULL(' ممیز '+[dbo].[fnNumberToWord_Persian](@DecimalNumber) +' '+@PTitle,'')
RETURN @str

مثال: رشته '5445789240.54678000000000'

پنج میلیارد و چهارصد و چهل و پنج میلیون و هفتصد و هشتاد و نه هزار و دویست و چهل  ممیز پنجاه و چهار هزار و ششصد و هفتاد و هشت  صد-هزارم  

دانلود فایل