مطالب
Accord.NET #1
Accord.NET کتابخانه‌ای است متن‌باز و بسیار کارآمد که در آن توابع بسیار زیادی در حوزه‌ی تحلیل آماری (statistical analysis)، یادگیری ماشین (machine learning)، پردازش تصویر (Image processing) و بینایی ماشین (computer vision) قرار گرفته‌اند تا در برنامه‌های NET. ایی مورد استفاده قرار گیرند.


چارچوب Accord.NET توسط آقای سزار سوزا بر پایه کتابخانه‌ی مشهور و محبوب AForge.NET (که توسط آقای اندرو کریلو ایجاد شده بود) بنا شده و البته ابزار‌های جدید زیادی به همراه یک محیط کامل برای محاسبات علمی (scientific computing) در NET. به آن اضافه شده است.

این چارچوب متشکل از چندین کتابخانه است که می‌توان آن را از طریق NuGet دریافت و نصب کرد.

کتابخانه‌های Accord.NET را می‌توان به سه دسته‌ی کلی تقسیم کرد :

1. محاسبات علمی (scientific computing) 


1.1. Accord.Math 
جهت کار با ماتریس‌ها عددی
تجزیه ماتریس‌ها (decomposition matrix)
الگوریتم‌های بهینه سازی عددی برای مسائل محدود و نامحدود
توابع و ابزار‌های خاص جهت استفاده در کاربردهای علمی
1.2.  Accord.Statistics  شامل توابعی جهت توزیع‌های احتمال (probability distributions)
آزمایش فرضیات (hypothesis testing)
مدل‌های آماری (statistical models)
و توابعی شامل : رگرسیون خطی، مدل پنهان مارکوف (Hidden Markov Models)، آنالیز اجزای اساسی   (Principal Component Analysis) و خیلی از تکنیک‌های مرتبط دیگر.

1.3. Accord.MachineLearning
شامل دسته بند‌های معروف از جمله :
ماشین برداری پشتیبان - Support Vector Machines
درخت تصمیم - Decision Trees
مدل نیو بیز - Naive Bayesian models
K-means
مدل ترکیبی گوسین - Gaussian Mixture models
و الگوریتم‌های متدوال دیگری مانند : Ransac, Cross-validation و Grid-Search 
1.4. Accord.Neuro 
شامل الگوریتم‌های معروف در حوزه شبکه‌های عصبی مصنوعی مانند:
لونبرگ مارکوارت - Levenberg-Marquardt
Parallel Resilient Back-propagation
شبکه باور عمیق - Deep Belief Networks
ماشین بولتزمن - Restrictured Boltzmann Machines
و تعدادی از شبکه‌های عصبی دیگر  

2. پردازش تصویر و سیگنال

2.1. Accord.Imaging  شامل آشکارسازهای نقاط از جمله Harris, SURF, FAST و FREAK  
فیلتر‌هایی برای تصاویر
توابعی جهت انطباق (matching) و دوخت (stitching) تصاویر
استخراج ویژگی‌های خوبی مانند - ﻫﯿﺴﺘﻮﮔﺮام ﮔﺮادﯾﺎن‌ﻫﺎی ﺷﯿﺐ‌ﮔﺮا و یا هاگ (Histograms of Oriented Gradients) و ویژگی‌های توصیفی بافتی هارلیک (Haralick’s textural)

2.2. Accord.Vision   تشخیص و ردیابی بی‌درنگ چهره
توابعی برای تشخیص، ردیابی و تبدیل اشیایی که در جریانی(streams) از تصاویر هستند
2.3. Accord.Audio  شامل توابعی جهت پردازش صدا از جمله اسپکتروم آنالایزر 

3. سایر کتابخانه‌های پشتیبانی

3.1. Accord.Controls  شامل نمودار هیستوگرام، پلات‌ها و نمایشگر‌ها و نمودارهایی برای داده‌های جدولی جهت کاربردهای علمی.
3.2. Accord.Controls.Imaging  شامل ابزاری برای نمایش سریع تصاویر برای برنامه‌های Windows Forms 
3.3. Accord.Controls.Audio 
شامل کنترل‌های Windows Forms برای نمایش شکل موج صوت و اطلاعات آن
3.4. Accord.Controls.Vision 
شامل اجزاء و کنترل‌های Windows Forms برای ردیابی حرکات سر، صورت، دست و سایر کارهای مرتبط با بینایی ماشین

اگر با مفاهیم یادگیری ماشین و هوش موصنوعی کمتر آشنا هستید و در این قسمت کمی کلمات تخصصی به کار رفته نگران نباشید؛ در مطالب آتی به صورت کاربردی به استفاده‌ی از آنها خواهیم پرداخت.
پروژه‌ها
CorMon: سیستم مدیریت محتوای مبتنی بر ASP.NET Core و MongoDB

توضیح 

پروژه‌ی CorMon یک CMS رایگان و سورس باز برپایه ی ASP.NET Core و MongoDB می‌باشد که سورس آن را بر روی Github میتوانید دنبال کرده و در صورت تمایل در توسعه آن مشارکت داشته باشید.
هدف
این پروژه در اصل تلاش و تمرینی است برای اینکه چگونه یک پروژه را در بستر ASP.NET Core  پیاده کنیم و آن را با دیتابیس‌های NoSQL از جمله MongoDB و Redis به کار بگیریم.
معماری
معماری این پروژه تا حدود زیادی برگرفته از Onion Architecture و نیز ASP.NET Boilerplate می‌باشد و تا حد امکان طراحی آن ساده و خوانا در نظر گرفته شده تا مشارکت در توسعه و یا استفاده از آن راحت باشد.
ویژگی ها
در اینجا بخشی از ویژگی‌های این پروژه را مشاهده میکنید که به ترتیب در حال اجرا هستند :
- استفاده از دیتابیس MongoDB
- پیاده سازی Redis Cache
- استفاده از الگوی ریپازیتوری
- استفاده از ابزار DI پیش فرض
- پیاده سازی REST API
- پیاده سازی JWT
- پیاده سازی ASP.NET Identity با پروایدر Mongo
- پیاده سازی Unit Testing 
- پیاده سازی  Automated UI Testing با Selenium
و ...
تصاویر

پرسش‌ها
چگونه میتوانم یک پلاک خودرو را در سی شارپ بخوانم

درود بر همه عزیزان؛ زبان سی شارپ. اندازه پروژه کوچک.

برای خواندن متن (پلاک ماشین) از داخل تصویری که با OpenCVsharp گرفتم. (کیفیت تصاویر خوبه)

راه حل های طی شده :

1 :

با IronOCR و Tesseract کار کردم جواب نداد یا شاید بشه گفت من بلد نیستم چجوری از اینا استفاده کنم.

void Test1()
{
    var Ocr = new IronTesseract();
    Ocr.Language = OcrLanguage.Persian;
    using (var Input = new OcrInput("car-plate-iran.jpg"))
    {
       var Result = Ocr.Read(Input);
       var AllText = Result.Text;
       Console.WriteLine(AllText);
    }
}

2 :

امکانات آنلاین مثل api.ocr.space رو نمیتونم استفاده کنم

راه های مختلفی رو تست کردم، چت جی پی تی هم یسری کد میده همشون بدون خطا هستن ولی هیچ کدوم خروجی نمیدن. شاید قبلش کانفیگی چیزی لازمه و من بلد نیستم یا اینکه کلا سورس در رابطه با OCR بسیار کم هست؟! چیزی نمیدونم. ممنون میشم راهنمایی کنید

مطالب
PowerShell 7.x - قسمت سیزدهم - ساخت یک Static Site Generator ساده توسط PowerShell و GitHub Actions
در این مطلب میخواهیم یک مثال دیگر از PowerShell را به همراه GitHub Actions را بررسی کنیم. هدف ایجاد یک Static Site Generator و در نهایت پابلیش خروجی استاتیک بر روی GitHub Pages است. روالی که در ادامه بررسی میکنیم صرفاً یک مثال از ترکیب این تکنولوژی‌ها است و قاعدتاً روش‌های ساده‌تر و سرراست‌تری نیز برای اینکار وجود دارد. به عنوان مثال میتوانید از Jekyll که یک SSG مبتنی بر Ruby است نیز استفاده کنید که GitHub Pages، به صورت پیش‌فرض از آن پشتیبانی میکند. در اینحالت به محض پوش کردن سایت بر روی ریپوزیتوری (با فرض اینکه این امکان را فعال کرده باشید) به صورت خودکار سایت بیلد شده و خروجی بر روی یک برنچ دیگر قرار خواهد گرفت و در نهایت برنچ بیلد شده توسط GitHub Pages میزبانی خواهد شد (البته امکان تغییر برنچ پیش‌فرض را نیز دارید). اما اگر بخواهیم کل فرآیند بیلد را به صورت سفارشی انجام دهیم، میتوانیم از GitHub Actions استفاده کنیم؛ یعنی مشابه کاری که Jekyll انجام میدهد. به محض پوش کردن محتوا، یک اسکریپت PowerShell برای اینکار فراخوانی شود و خروجی نهایی بر روی یک برنچ دیگر منتشر شود. خروجی نهایی این چنین قالبی خواهد داشت:

نکته: در اینجا از فونت آقای راستی‌کردار استفاده شده است؛ با آرزوی بهبودی و سلامتی ایشان.

ساختار پروژه
ساختاری که برای پروژه در نظر گرفته‌ام به صورت زیر است:
├── _layout
│   ├── _footer.html
│   ├── _header.html
│   ├── _nav.html
│   └── main.html
├── build
├── img
├── posts
└── set-posts.ps1
  • دایرکتوری layout_: درون این دایرکتوری، ساختار اصلی بلاگ را قرار داده‌ایم. در ادامه محتویات هر فایل را مشاهده خواهید کرد: 
<!--main.html-->
<!DOCTYPE html>
<html dir="rtl">

{{header}}

<body>
    {{nav}}
    <main>
        {{content}}
    </main>
    {{footer}}
</body>



<!--_header.html-->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{title}}</title>
    <link href="https://cdn.jsdelivr.net/gh/rastikerdar/samim-font@v4.0.5/dist/font-face.css" rel="stylesheet"
        type="text/css" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
    <style>
        * {
        }
    </style>
</head>

<!--_nav.html-->
<header>
    <nav>
        <div>
            <div>
                <a href="#">بلاگ من</a>
            </div>
            <div>
                <ul>
                    {{nav}}
                </ul>
            </div>
        </div>
    </nav>
</header>


<!--_footer.html-->
<footer>
    <div>
        <div>
            <p>
                تمامی حقوق محفوظ است
            </p>
        </div>
    </div>
</footer>
  • دایرکتوری build: درون این دایرکتوری، خروجی‌های HTML که قرار است توسط اسکریپت PowerShell جنریت شوند، قرار خواهند گرفت. این پوشه در واقع قرار است توسط GitHub Pages میزبانی شود.
  • دایرکتوری img: درون این دایرکتوری، تصاویر مربوط به هر بلاگ‌پست را قرار خواهیم داد.
  • دایرکتوری posts: درون این دایرکتوری، مطالب‌مان را با فرمت Markdown، قرار خواهیم داد. به عنوان مثال در ادامه یک نمونه از آن را مشاهده خواهید کرد (در کد زیر از Front Matter برای اضافه کردن یکسری متادیتای موردنیاز که حین بیلد شدن ضروری هستند استفاده شده‌است) 
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

# اولین پست من

## اولین پست من

### اولین پست من

#### اولین پست من

##### اولین پست من

###### اولین پست من

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

<img src="/img/graphql.jpg"/>
همانطور که مشاهده میکنید، مسیر تصویر استفاده شده در بلاگ‌پست، از دایرکتوری img خوانده شده‌است.
  • فایل set_post.ps1: موتور اصلی جنریت کردن صفحات HTML این فایل میباشد. در ادامه محتویات آن را مشاهده خواهید کرد. سپس هر کدام از توابع استفاده شده را یکی‌یکی توضیح خواهیم داد: 
Function Get-Layouts {
    $headerLayout = Get-Content -Path ./_layout/_header.html -Raw
    $homeLayout = Get-Content -Path ./_layout/main.html -Raw
    $footerLayout = Get-Content -Path ./_layout/_footer.html -Raw

    Return @{
        Header = $headerLayout
        Home   = $homeLayout
        Footer = $footerLayout
    }
}

Function Get-PostFrontMatter($postContent) {
    $frontMatter = [regex]::Match($postContent, "(---(?:\r?\n(?!--|\s*$).*)*)\s*((?:\r?\n(?!---).*)*\r?\n---)")
    Return $frontMatter
}

Function Set-Headings($postHtml) {
    Return $postHtml -Replace '<h(\d) id="(.*)">', {
        $level = $_.Groups[1].Value
        $id = $_.Groups[2].Value
        $class = Switch ($level) {
            '1' { 'text-4xl font-bold mb-2' }
            '2' { 'text-3xl font-bold mb-2' }
            '3' { 'text-2xl font-bold mb-2' }
            '4' { 'text-xl font-bold mb-2' }
            '5' { 'text-lg font-bold mb-2' }
            '6' { 'text-base font-bold mb-2' }
        }
        "<h$level class='$class' id='$id'>"
    }
}

Function ConvertTo-Slug {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$String
    )
    process {
        $slug = $String -replace '[^\w\s-]', '' # remove non-word characters except hyphens
        $slug = $slug -replace '\s+', '-' # replace whitespace with a single hyphen
        $slug = $slug -replace '^-+|-+$', '' # remove leading/trailing hyphens
        $slug = $slug.ToLower() # convert to lowercase
        Write-Output $slug
    }
}

Function Get-Posts {
    $markdownPosts = Get-ChildItem -Path ./posts -Filter *.md
    $posts = @()
    Foreach ($post in $markdownPosts) {
        $postContent = Get-Content -Path $post.FullName -Raw
        $frontMatter = Get-PostFrontMatter $postContent
        $frontMatterObject = $frontMatter | ConvertFrom-Yaml

        $slug = $frontMatterObject.slug ?? (ConvertTo-Slug "$($frontMatterObject.date)-$($frontMatterObject.title)")
        $body = $postContent.Replace($frontMatter.Value, "") | ConvertFrom-Markdown
        $postHtml = $layouts.Home -replace '{{header}}', $layouts.Header `
            -replace '{{title}}', $frontMatterObject.title `
            -replace '{{nav}}', (Set-Navs) `
            -replace '{{content}}', $body.Html `
            -replace '{{footer}}', $layouts.Footer

        $postHtml = Set-Headings $postHtml
        $postHtml | Out-File -FilePath ./build/$slug.html

        $posts += @{
            title   = $frontMatterObject.title
            slug    = $slug
            excerpt = $frontMatterObject.excerpt
            date    = $frontMatterObject.date
            author  = $frontMatterObject.author
            body    = $body.Html
        }
    }
    Return $posts
}

Function Set-Archive {
    $posts = Get-Posts
    $archive = @()
    $archive = @"
        <ul>
            $($posts | ForEach-Object { "<li><a href='$($_.slug).html'>$($_.title)</a></li>" })
        </ul>
"@
    Return $archive -join "`r`n"
}

Function Copy-ToBuild {
    $layouts = Get-Layouts
    $latestPosts = Get-Posts | ForEach-Object { @"
    <div>
        <img src="https://via.placeholder.com/300x200" alt="$($_.title)">
        <h2>$($_.title)</h2>
        <p>$($_.excerpt)</p>
            <a href="$($_.slug).html">ادامه مطلب</a>
        </div>
"@
    }

    $homeLayout = $layouts.Home -replace '{{header}}', $layouts.Header `
        -replace '{{nav}}', (Set-Navs) `
        -replace '{{title}}', 'بلاگ من' `
        -replace '{{content}}', ('<div>' + $latestPosts + '</div>') `
        -replace '{{footer}}', $layouts.Footer

    $homeLayout | Out-File -FilePath ./build/index.html

    Copy-Item -Path ./img -Destination ./build -Recurse -Force

}

Function Set-Navs {
    $navs = @(
        @{
            title = "صفحه اصلی"
            url   = "/sample"
        },
        @{
            title = "درباره ما"
            url   = "/sample/about.html"
        },
        @{
            title = "تماس با ما"
            url   = "/sample/contact.html"
        }
    )
    $navLayout = Get-Content -Path ./_layout/_nav.html -Raw
    $navLayout -replace '{{nav}}', ($navs | ForEach-Object { "<li><a href=""$($_.url)""text-gray-700 hover:text-gray-800 m-2"">$($_.title)</a></li>" })
}


Copy-ToBuild
همانطور که در کد فوق مشاهده میکنید، تابع Copy-ToBuild فراخوانی شده است. درون این تابع، ابتدا لی‌اوت‌های موردنیاز برای تولید صفحات HTML را درون یک متغییر با نام layouts قرار داده‌ایم. درون لی‌اوت‌ها یکسری placeholder برای قرارگیری قسمت‌های مختلف سایت تعریف کرده‌ایم که قرار است توسط تابع عنوان شده، جایگزین شوند. در ادامه توسط تابع Get-Posts، تمامی مطالب درون دایرکتوری posts را واکشی کرده و برای هر کدام، صفحه‌ی HTML معادل آن را تولید کرده‌ایم. برای پارز کردن قسمت Front Matter هر بلاگ‌پست نیز از پکیج powershell-yaml استفاده شده‌است. درون تابع Get-Posts باید هر مطلب را همراه با لی‌اوت اصلی سایت، به HTML تبدیل کنیم. بنابراین یکسری عملیات string replacement درون تابع عنوان شده انجام گرفته است. در نهایت، مطالب همراه با لی‌اوت مناسبی درون دایرکتوری build ذخیره خواهند شد و سپس یک لیست از مطالب پارز شده به عنوان خروجی تابع برگردانده خواهد شد. در نهایت درون تابع Copy-ToBuild، یکسری card برای نمایش آخرین مطالب بلاگ تهیه شده، سپس خروجی نهایی درون فایل index.html همراه با قالب اصلی ذخیره خواهد شد.

ایجاد GitHub Actions Workflow
در ادامه برای ساختن workflow نهایی باید با کمک GitHub Actions، اسکریپت PowerShellی را که ساختیم، اجرا کنیم. این اسکریپت ابتدا پروژه را clone کرده، سپس وابستگی موردنیاز را نصب کرده و در نهایت اسکریپت را اجرا خواهد کرد: 
name: Deploy static content to Pages

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Install powershell-yaml module
        shell: pwsh
        run: |
          Set-PSRepository PSGallery -InstallationPolicy Trusted
          Install-Module powershell-yaml -ErrorAction Stop

      - name: Setup Pages
        uses: actions/configure-pages@v3

      - name: Build Static Site
        shell: pwsh
        run: |
          . ./set-posts.ps1

      - name: Upload Static Site Artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: build

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

نحوه انتشار یک مطلب جدید
  • درون دایرکتوری posts، مطلب موردنظر را به همراه Front Matter زیر ایجاد کرده و سپس محتویات مطلب را بعد از آن وارد کنید:
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

content
  • تغییرات ایجاد شده را کامیت و سپس پوش کنید. به محض پوش کردن تغییرات، GitHub Actions پروسه بیلد را انجام خواهد داد و بلافاصله میتوانید تغییرات را مشاهده نمائید.
نکته: قابلیت GitHub Pages به صورت پیش‌فرض فعال نیست. برای فعال کردن آن ابتدا باید به قسمت تنظیمات ریپوزیتوری GitHubتان مراجعه کرده و سپس از تب Pages، فیلد Source را بر روی GitHub Actions قرار دهید:


بنابراین بعد از پوش کردن تغییرات workflowایی که ایجاد کردیم توسط Source شناسایی خواهد شد و سپس وب‌سایت از طریق آدرس https://<username>.github.io/<repository> در دسترس قرار خواهد گرفت:

کدهای این مطلب را میتوانید از اینجا دریافت کنید.

بازخوردهای دوره
پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC
در حالت استفاده‌ی از MVC، قسمت رندر خود pager یک کد سمت سرور هست. جائیکه لینک به صفحات مختلف رندر می‌شود، هش‌تگ مربوط به افزونه‌ی pathjs را هم خودتان اضافه کنید:
 var path = "#/page/" + (page + 1) + "/" + $(options.pagerSortById).val() + "/" + $(options.pagerSortOrderId).val();
این کد جاوا اسکریپتی هست (در فایل jquery.InfiniteScroll.js) که انتهای یک url به صورت هش‌تگ (page/1/#) اضافه می‌شود. همین هش‌تگ را در حین رندر url شماره صفحات خودتان اضافه کنید.
قسمت بعدی آن، پردازش این هش‌تگ‌ها است (زمانیکه به صورت مستقیم در مرورگر وارد شد) که نیاز به کد جاوا اسکریپتی زیر را دارد:
Path.map("#/page(/:page)(/:sortby)(/:order)").to(function () {
که آن هم جزئیاتش در فایل jQueryMvcSample02_V2.zip موجود است.
نظرات مطالب
چک لیست امنیتی پروژه های نرم افزاری تحت وب
با سلام،
در این روش هکر خودش رو توی مسیر ورود و خروج پکت‌های سرور قرار میده و با کمک ابزارهایی پکت‌های ورودی و خروجی سرور رو شنود می‌کنه و اطلاعات رو ذخیره می‌کنه.
در این موارد اطلاعات حساسی مثل نام کاربری و کلمه عبور کاربران ک هدر صفحات ورود و یا حتی ثبت نام که بصورت Clear Text به سرور ارسال میشن، به راحتی در اختیار هکر قرار می‌گیرن.
بهترین راه حل مقابله با این قضیه استفاده از SSL روی سایت است. بدین صورت وقتی کاربر وارد یکی از صفحات سایت شما میشه، سرور یک پروتوکل امنیتی رو با مرورگر کاربر به اشتراک می‌گذاره و مرورگر کاربر متوجه میشه که باید بر اساس پروتکلی که دریافت کرده داده‌های ارسالیش رو در سمت کلاینت هش کنه و بعد برای سرور ارسال کنه. این روش امنیت بسیار بالایی داره.
نظرات مطالب
ASP.NET MVC #5
- Page Inspector برای اینکار طراحی شده.
- فایل‌های cshtml در تمام ادیتور بصری موجود قابل گشودن و ویرایش هستند.
- اما ... اکثر ادیتورهای بصری قادر نیستند با بسیاری از فریم ورک‌های جدید CSS کار کنند؛ مانند بوت استرپ. طراحی و کار با آن‌ها عموما بدون ادیتورهای بصری و به کمک استفاده از مرورگرها انجام می‌شود. Razor هم به همین نحو است. صفحه را تغییر داده و Save کنید. بعد مرورگر را Refresh کنید (نیازی به کامپایل مجدد نیست).
- صفحات ASP.NET، یک سری صفحات پویا هستند. نیاز به برقراری اتصالات خاصی بین بانک اطلاعاتی، کوئری استرینگ‌ها، مقادیر Post شده به صفحه و غیره، برای نمایش اطلاعات خاصی است. طراح‌های بصری در یک چنین مواردی کارآمد نیستند و باید چرخه‌ی کامل طول عمر صفحه در مرورگر طی شود.
- خیلی از مسایل توسط طراح‌های بصری قابل پیاده سازی نیستند؛ برای مثال نوشتن یک if و else برای نمایش قسمتی از صفحه به کاربران اعتبارسنجی شده یا نمایش داده‌ها در یک حلقه.
نظرات مطالب
PHP سریعتر از ASP.NET! افسانه یا واقعیت؟

چندین علت داره:

- پروسه کامپایل کدهای دات نت یک مرحله‌ای نیست. کلا در دات نت کدها به یک زبان میانی به نام IL ترجمه میشن. بعد این IL توسط  JIT compiler تبدیل به کدهای ماشین میشه. در ASP.NET این مساله هم برای کدهای پشت صحنه برنامه و هم برای خود صفحات رخ می‌ده. بنابراین برای بار اول مشاهده، روند این پروسه الزامی هست. ولی برای دفعات بعدی مشاهده خیر. البته روش برای پیش کامپایل کردن کامل صفحات هم وجود داره و یا در IISهای جدید یک سری مبحث warmup توکار پیش بینی شده.

- همچنین IIS برای مدیریت منابع سرور، یک سایت رو مدام در حافظه نگه نمی‌داره. فقط زمانیکه اولین درخواست به سرور میرسه سایت رو بارگذاری می‌کنه و application pool اون رو استارت. این هم یک زمان اولیه اندکی رو ممکنه به خودش اختصاص بده. بعلاوه پس از مدتی، یک سایت بیکار رو از حافظه خارج می‌کنه. بهش می‌گن ریسایکل کردن. در ASP.NET 4.0 به بعد امکان تنظیم auto-start برای سایت‌ها هست.

مطالب
OpenCVSharp #18
ساخت یک OCR ساده تشخیص اعداد انگلیسی به کمک OpenCV

این مطلب را می‌توان به عنوان جمع بندی مطالبی که تاکنون بررسی شدند درنظر گرفت و در اساس مطلب جدیدی ندارد و صرفا ترکیب یک سری تکنیک است؛ برای مثال:
چطور یک تصویر را به نمونه‌ی سیاه و سفید آن تبدیل کنیم؟
کار با متد Threshold جهت بهبود کیفیت یک تصویر جهت تشخیص اشیاء
تشخیص کانتورها (Contours) و اشیاء موجود در یک تصویر
آشنایی با نحوه‌ی گروه بندی تصاویر مشابه و مفاهیمی مانند برچسب‌های تصاویر که بیانگر یک گروه از تصاویر هستند.


تهیه تصاویر اعداد انگلیسی جهت آموزش دادن به الگوریتم CvKNearest

در اینجا نیز از یکی دیگر از الگوریتم‌های machine learning موجود در OpenCV به نام CvKNearest برای تشخیص اعداد انگلیسی استفاده خواهیم کرد. این الگوریتم نزدیک‌ترین همسایه‌ی اطلاعاتی مفروض را در گروهی از داده‌های آموزش داده شده‌ی به آن پیدا می‌کند. خروجی آن شماره‌ی این گروه است. بنابراین نحوه‌ی طبقه‌ی بندی اطلاعات در اینجا چیزی شبیه به شکل زیر خواهد بود:


مجموعه‌ای از تصاویر 0 تا 9 را جمع آوری کرده‌ایم. هر کدام از پوشه‌ها، بیانگر اعدادی از یک خانواده هستند. این تصویر را با فرمت ذیل جمع آوری می‌کنیم:
public class ImageInfo
{
    public Mat Image { set; get; }
    public int ImageGroupId { set; get; }
    public int ImageId { set; get; }
}
به این ترتیب
public IList<ImageInfo> ReadTrainingImages(string path, string ext)
{
    var images = new List<ImageInfo>();
 
    var imageId = 1;
    foreach (var dir in new DirectoryInfo(path).GetDirectories())
    {
        var groupId = int.Parse(dir.Name);
        foreach (var imageFile in dir.GetFiles(ext))
        {
            var image = processTrainingImage(new Mat(imageFile.FullName, LoadMode.GrayScale));
            if (image == null)
            {
                continue;
            }
 
            images.Add(new ImageInfo
            {
                Image = image,
                ImageId = imageId++,
                ImageGroupId = groupId
            });
        }
    }
 
    return images;
}
در متد خواندن تصاویر آموزشی، ابتدا پوشه‌های اصلی مسیر Numbers تصویر ابتدای بحث دریافت می‌شوند. سپس نام هر پوشه، شماره‌ی گروه تصاویر موجود در آن پوشه را تشکیل خواهد داد. به این نام در الگوریتم‌های machine leaning، کلاس هم گفته می‌شود. سپس هر تصویر را با فرمت سیاه و سفید بارگذاری کرده و به لیست تصاویر موجود اضافه می‌کنیم. در اینجا از متد processTrainingImage نیز استفاده شده‌است. هدف از آن بهبود کیفیت تصویر دریافتی جهت کار تشخیص اشیاء است:
private static Mat processTrainingImage(Mat gray)
{
    var threshImage = new Mat();
    Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour
 
    Point[][] contours;
    HiearchyIndex[] hierarchyIndexes;
    Cv2.FindContours(
        threshImage,
        out contours,
        out hierarchyIndexes,
        mode: ContourRetrieval.CComp,
        method: ContourChain.ApproxSimple);
 
    if (contours.Length == 0)
    {
        return null;
    }
 
    Mat result = null;
 
    var contourIndex = 0;
    while ((contourIndex >= 0))
    {
        var contour = contours[contourIndex];
 
        var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour
        var roi = new Mat(threshImage, boundingRect); //Crop the image
 
        //Cv2.ImShow("src", gray);
        //Cv2.ImShow("roi", roi);
        //Cv.WaitKey(0);
 
        var resizedImage = new Mat();
        var resizedImageFloat = new Mat();
        Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10
        resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float
        result = resizedImageFloat.Reshape(1, 1);
 
        contourIndex = hierarchyIndexes[contourIndex].Next;
    }
 
    return result;
}
عملیات صورت گرفته‌ی در این متد را با تصویر ذیل بهتر می‌توان توضیح داد:


ابتدا تصویر اصلی بارگذاری می‌شود؛ همان تصویر سمت چپ. سپس با استفاده از متد Threshold، شدت نور نواحی مختلف آن یکسان شده و آماده می‌شود برای تشخیص کانتورهای موجود در آن. در ادامه با استفاده از متد FindContours، شیء مرتبط با عدد جاری یافت می‌شود. سپس متد Cv2.BoundingRect مستطیل دربرگیرنده‌ی این شیء را تشخیص می‌دهد (تصویر سمت راست). بر این اساس می‌توان تصویر اصلی ورودی را به یک تصویر کوچکتر که صرفا شامل ناحیه‌ی عدد مدنظر است، تبدیل کرد. در ادامه برای کار با الگوریتم  CvKNearest نیاز است تا این تصویر بهبود یافته را تبدیل به یک ماتریس یک بعدی کردی که روش انجام کار توسط متد Reshape مشاهده می‌کنید.
از همین روش پردازش و بهبود تصویر ورودی، جهت پردازش اعداد یافت شده‌ی در یک تصویر با تعداد زیادی عدد نیز استفاده خواهیم کرد.


آموزش دادن به الگوریتم CvKNearest

تا اینجا تصاویر گروه بندی شده‌ای را خوانده و لیستی از آن‌ها را مطابق فرمت الگوریتم CvKNearest تهیه کردیم. مرحله‌ی بعد، معرفی این لیست به متد Train این الگوریتم است:
public CvKNearest TrainData(IList<ImageInfo> trainingImages)
{
    var samples = new Mat();
    foreach (var trainingImage in trainingImages)
    {
        samples.PushBack(trainingImage.Image);
    }
 
    var labels = trainingImages.Select(x => x.ImageGroupId).ToArray();
    var responses = new Mat(labels.Length, 1, MatType.CV_32SC1, labels);
    var tmp = responses.Reshape(1, 1); //make continuous
    var responseFloat = new Mat();
    tmp.ConvertTo(responseFloat, MatType.CV_32FC1); // Convert  to float
 
 
    var kNearest = new CvKNearest();
    kNearest.Train(samples, responseFloat); // Train with sample and responses
    return kNearest;
}
متد Train دو ورودی دارد. ورودی اول آن یک تصویر است که باید از طریق متد PushBack کلاس Mat تهیه شود. بنابراین لیست تصاویر اصلی را تبدیل به لیستی از Matها خواهیم کرد.
سپس نیاز است لیست گروه‌های متناظر با تصاویر اعداد را تبدیل به فرمت مورد انتظار متد Train کنیم. در اینجا صرفا لیستی از اعداد صحیح را داریم. این لیست نیز باید تبدیل به یک Mat شود که روش انجام آن در متد فوق بیان شده‌است. کلاس Mat سازنده‌ی مخصوصی را جهت تبدیل لیست اعداد، به همراه دارد. این Mat نیز باید تبدیل به یک ماتریس یک بعدی شود که برای این منظور از متد Reshape استفاده شده‌است.


انجام عملیات OCR نهایی

پس از تهیه‌ی لیستی از تصاویر و آموزش دادن آن‌ها به الگوریتم CvKNearest، تنها کاری که باید انجام دهیم، یافتن اعداد در تصویر نمونه‌ی مدنظر و سپس معرفی آن به متد FindNearest الگوریتم CvKNearest است. روش انجام اینکار بسیار شبیه است به روش معرفی شده در متد processTrainingImage که پیشتر بررسی شد:
public void DoOCR(CvKNearest kNearest, string path)
{
    var src = Cv2.ImRead(path);
    Cv2.ImShow("Source", src);
 
    var gray = new Mat();
    Cv2.CvtColor(src, gray, ColorConversion.BgrToGray);
 
    var threshImage = new Mat();
    Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour
 
 
    Point[][] contours;
    HiearchyIndex[] hierarchyIndexes;
    Cv2.FindContours(
        threshImage,
        out contours,
        out hierarchyIndexes,
        mode: ContourRetrieval.CComp,
        method: ContourChain.ApproxSimple);
 
    if (contours.Length == 0)
    {
        throw new NotSupportedException("Couldn't find any object in the image.");
    }
 
    //Create input sample by contour finding and cropping
    var dst = new Mat(src.Rows, src.Cols, MatType.CV_8UC3, Scalar.All(0));
 
    var contourIndex = 0;
    while ((contourIndex >= 0))
    {
        var contour = contours[contourIndex];
 
        var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour
 
        Cv2.Rectangle(src,
            new Point(boundingRect.X, boundingRect.Y),
            new Point(boundingRect.X + boundingRect.Width, boundingRect.Y + boundingRect.Height),
            new Scalar(0, 0, 255),
            2);
 
        var roi = new Mat(threshImage, boundingRect); //Crop the image
 
        var resizedImage = new Mat();
        var resizedImageFloat = new Mat();
        Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10
        resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float
        var result = resizedImageFloat.Reshape(1, 1);
 
 
        var results = new Mat();
        var neighborResponses = new Mat();
        var dists = new Mat();
        var detectedClass = (int)kNearest.FindNearest(result, 1, results, neighborResponses, dists);
 
        //Console.WriteLine("DetectedClass: {0}", detectedClass);
        //Cv2.ImShow("roi", roi);
        //Cv.WaitKey(0);
 
        //Cv2.ImWrite(string.Format("det_{0}_{1}.png",detectedClass, contourIndex), roi);
 
        Cv2.PutText(
            dst,
            detectedClass.ToString(CultureInfo.InvariantCulture),
            new Point(boundingRect.X, boundingRect.Y + boundingRect.Height),
            0,
            1,
            new Scalar(0, 255, 0),
            2);
 
        contourIndex = hierarchyIndexes[contourIndex].Next;
    }
 
    Cv2.ImShow("Segmented Source", src);
    Cv2.ImShow("Detected", dst);
 
    Cv2.ImWrite("dest.jpg", dst);
 
    Cv2.WaitKey();
}
این عملیات به صورت خلاصه در تصویر ذیل مشخص شده‌است:


ابتدا تصویر اصلی که قرار است عملیات OCR روی آن صورت گیرد، بارگذاری می‌شود. سپس کانتورها و اعداد موجود در آن تشخیص داده می‌شوند. مستطیل‌های قرمز رنگ در برگیرنده‌ی این اعداد را در تصویر دوم مشاهده می‌کنید. سپس این کانتور‌های یافت شده را که شامل یکی از اعداد تشخیص داده شده‌است، تبدیل به یک ماتریس یک بعدی کرده و به متد FindNearest ارسال می‌کنیم. خروجی آن نام گروه یا پوشه‌ای است که این عدد در آن قرار دارد. در همینجا این خروجی را تبدیل به یک رشته کرده و در تصویر سوم با رنگ سبز رنگ نمایش می‌دهیم.
بنابراین در این تصویر، پنجره‌ی segmented image، همان اشیاء تشخیص داده شده‌ی از تصویر اصلی هستند.
پنجره‌ی با زمینه‌ی سیاه رنگ، نتیجه‌ی نهایی OCR است که نسبتا هم دقیق عمل کرده‌است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
روش کار با فایل‌های پویای ارائه شده‌ی توسط یک برنامه‌ی ASP.NET Core در برنامه‌های React
پس از آشنایی با «روش کار با فایل‌های ایستا در برنامه‌های React»، اکنون اگر این فایل‌ها ایستا نباشند و توسط یک برنامه‌ی ASP.NET Core بازگشت داده شوند، چطور می‌توان از آن‌ها در برنامه‌های React استفاده کرد؟

برپایی پروژه‌های مورد نیاز

ابتدا یک پوشه‌ی جدید را مانند DownloadFilesSample، ایجاد کرده و در داخل آن دستور زیر را اجرا می‌کنیم:
> dotnet new react
در مورد این قالب که امکان تجربه‌ی توسعه‌ی یکپارچه‌ی ASP.NET Core و React را میسر می‌کند، در مطلب «روش یکی کردن پروژه‌های React و ASP.NET Core» بیشتر بحث کردیم.
سپس در این پوشه، پوشه‌ی ClientApp پیش‌فرض آن‌را حذف می‌کنیم؛ چون کمی قدیمی است. همچنین فایل‌های کنترلر و سرویس آب و هوای پیش‌فرض آن‌را به همراه پوشه‌ی صفحات Razor آن، حذف می‌کنیم.
به علاوه بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
از تنظیم زیر استفاده کرده‌ایم تا با هر بار تغییری در کدهای پروژه‌ی ASP.NET، یکبار دیگر از صفر npm start اجرا نشود:
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
بدیهی است در این حالت باید از طریق خط فرمان به پوشه‌ی clientApp وارد شد و دستور npm start را یکبار به صورت دستی اجرا کرد، تا این وب سرور بر روی پورت 3000، راه اندازی شود.

اکنون در ریشه‌ی پروژه‌ی ASP.NET Core ایجاد شده، دستور زیر را صادر می‌کنیم تا پروژه‌ی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
سپس وارد این پوشه‌ی جدید شده و بسته‌های زیر را نصب می‌کنیم:
> cd clientapp
> npm install --save bootstrap axios
توضیحات:
- برای استفاده از شیوه‌نامه‌های بوت استرپ، بسته‌ی bootstrap نیز در اینجا نصب می‌شود که برای افزودن فایل bootstrap.css آن به پروژه‌ی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
 import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.
- برای دریافت فایل‌ها از سمت سرور، از کتابخانه‌ی معروف axios استفاده خواهیم کرد.


کدهای سمت سرور دریافت فایل‌های پویا

در اینجا کدهای سمت سرور برنامه، یک فایل PDF ساده را بازگشت می‌دهند. این محتوای باینری می‌تواند حاصل اجرای یک گزارش اکسل، PDF و یا کلا هر نوع فایلی باشد:
using Microsoft.AspNetCore.Mvc;

namespace DownloadFilesSample.Controllers
{
    [Route("api/[controller]")]
    public class ReportsController : Controller
    {
        [HttpGet("[action]")]
        public IActionResult GetPdfReport()
        {
            return File(virtualPath: "~/app_data/sample.pdf",
                        contentType: "application/pdf",
                        fileDownloadName: "sample.pdf");
        }
    }
}
فایل بازگشتی فوق که در این مثال در مسیر wwwroot\app_data\sample.pdf برنامه‌ی وب کپی شده‌است، در نهایت با آدرس api/Reports/GetPdfReport در سمت کلاینت قابل دسترسی خواهد بود.


روش دریافت محتوای باینری در برنامه‌های React

برای دریافت یک محتوای باینری از سرور توسط axios مانند تصاویر، فایل‌های PDF و اکسل و غیره، مهم‌ترین نکته، تنظیم ویژگی responseType آن به blob است:
  const getResults = async () => {
      const { headers, data } = await axios.get(apiUrl, {
        responseType: "blob"
      });
  }


ساخت URL برای دسترسی به اطلاعات باینری

تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی می‌کنند. این متد، شیء URL را از شیء window جاری دریافت می‌کند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید می‌کند. حاصل آن، یک URL ویژه‌است مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در ادامه کدهای تبدیل blob دریافت شده‌ی از سرور را به این URL ویژه، مشاهده می‌کنید:
import axios from "axios";
import React, { useEffect, useState } from "react";

export default function DisplayPdf() {
  const apiUrl = "https://localhost:5001/api/Reports/GetPdfReport";

  const [blobInfo, setBlobInfo] = useState({
    blobUrl: "",
    fileName: ""
  });

  useEffect(() => {
    getResults();
  }, []);

  const getResults = async () => {
    try {
      const { headers, data } = await axios.get(apiUrl, {
        responseType: "blob"
      });
      console.log("headers", headers);

      const pdfBlobUrl = window.URL.createObjectURL(data);
      console.log("pdfBlobUrl", pdfBlobUrl);

      const fileName = headers["content-disposition"]
        .split(";")
        .find(n => n.includes("filename="))
        .replace("filename=", "")
        .trim();
      console.log("filename", fileName);

      setBlobInfo({
        blobUrl: pdfBlobUrl,
        fileName: fileName
      });
    } catch (error) {
      console.log(error);
    }
  };
توضیحات:
- توسط useEffect Hook و بدون ذکر وابستگی خاصی در آن، سبب شبیه سازی رویداد componentDidUpdate شده‌ایم. به این معنا که متد getResults فراخوانی شده‌ی در آن، پس از رندر کامپوننت در DOM فراخوانی می‌شود و بهترین محلی است که از آن می‌توان برای ارسال درخواست‌های Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود. معادل setState در اینجا نیز، همان شیء حالتی است که توسط useState Hook و متد setBlobInfo آن تعریف کرده‌ایم.
- پس از دریافت headers و data از سرور، با استفاده از متد createObjectURL، آن‌را تبدیل به یک blob URL کرده‌ایم.
- همچنین در سمت سرور، پارامتر fileDownloadName را نیز تنظیم کرده‌ایم. این نام در سمت کلاینت، توسط هدری با کلید content-disposition ظاهر می‌شود:
ontent-disposition: "attachment; filename=sample.pdf; filename*=UTF-8''sample.pdf"
 بنابراین می‌توان آن‌را تجزیه کرد و سپس filename را از آن استخراج نمود.
- اکنون که نام فایل و URL دسترسی به داده‌ی فایل باینری دریافتی از سرور را استخراج و ایجاد کرده‌ایم. با فراخوانی متد setBlobInfo، سبب تنظیم متغیر حالت blobInfo خواهیم شد. این مورد، رندر مجدد UI را سبب شده و توسط آن می‌توان برای مثال فایل PDF دریافتی را نمایش داد.


نمایش فایل PDF دریافتی از سرور، به همراه دکمه‌های دریافت، چاپ و بازکردن آن در برگه‌ای جدید

در ادامه کدهای کامل قسمت رندر این کامپوننت را مشاهده می‌کنید:
import axios from "axios";
import React, { useEffect, useState } from "react";

export default function DisplayPdf() {

  // ...

  const { blobUrl } = blobInfo;

  return (
    <>
      <h1>Display PDF Files</h1>
      <button className="btn btn-info" onClick={handlePrintPdf}>
        Print PDF
      </button>
      <button className="btn btn-primary ml-2" onClick={handleShowPdfInNewTab}>
        Show PDF in a new tab
      </button>
      <button className="btn btn-success ml-2" onClick={handleDownloadPdf}>
        Download PDF
      </button>

      <section className="card mb-5 mt-3">
        <div className="card-header">
          <h4>using iframe</h4>
        </div>
        <div className="card-body">
          <iframe
            title="PDF Report"
            width="100%"
            height="600"
            src={blobUrl}
            type="application/pdf"
          ></iframe>
        </div>
      </section>

      <section className="card mb-5">
        <div className="card-header">
          <h4>using object</h4>
        </div>
        <div className="card-body">
          <object
            data={blobUrl}
            aria-label="PDF Report"
            type="application/pdf"
            width="100%"
            height="100%"
          ></object>
        </div>
      </section>

      <section className="card mb-5">
        <div className="card-header">
          <h4>using embed</h4>
        </div>
        <div className="card-body">
          <embed
            aria-label="PDF Report"
            src={blobUrl}
            type="application/pdf"
            width="100%"
            height="100%"
          ></embed>
        </div>
      </section>
    </>
  );
}
که چنین خروجی را ایجاد می‌کند:


در اینجا با انتساب مستقیم blob URL ایجاد شده، به خواص src و یا data اشیائی مانند iframe ،object و یا embed، می‌توان سبب نمایش فایل pdf دریافتی از سرور شد. این نمایش نیز توسط قابلیت‌های توکار مرورگر صورت می‌گیرد و نیاز به نصب افزونه‌ی خاصی را ندارد.

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


مدیریت دکمه‌ی چاپ PDF

پس از اینکه به blobUrl دسترسی یافتیم، اکنون می‌توان یک iframe مخفی را ایجاد کرد، سپس src آن‌را به این آدرس ویژه تنظیم نمود و در آخر متد print آن‌را فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر می‌شود:
  const handlePrintPdf = () => {
    const { blobUrl } = blobInfo;
    if (!blobUrl) {
      throw new Error("pdfBlobUrl is null");
    }

    const iframe = document.createElement("iframe");
    iframe.style.display = "none";
    iframe.src = blobUrl;
    document.body.appendChild(iframe);
    if (iframe.contentWindow) {
      iframe.contentWindow.print();
    }
  };


مدیریت دکمه‌ی نمایش فایل PDF در یک برگه‌ی جدید

اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگه‌ای جدید نمایش دهید، می‌توان از متد window.open استفاده کرد:
const handleShowPdfInNewTab = () => {
    const { blobUrl } = blobInfo;
    if (!blobUrl) {
      throw new Error("pdfBlobUrl is null");
    }

    window.open(blobUrl);
  };

مدیریت دکمه‌ی دریافت فایل PDF

بجای نمایش فایل PDF می‌توان دکمه‌ای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
  const handleDownloadPdf = () => {
    const { blobUrl, fileName } = blobInfo;
    if (!blobUrl) {
      throw new Error("pdfBlobUrl is null");
    }

    const anchor = document.createElement("a");
    anchor.style.display = "none";
    anchor.href = blobUrl;
    anchor.download = fileName;
    document.body.appendChild(anchor);
    anchor.click();
  };
در اینجا یک anchor جدید به صورت مخفی به صفحه اضافه می‌شود که href آن به blobUrl تنظیم شده‌است و همچنین از فایل fileName استخراجی نیز در اینجا جهت ارائه‌ی نام اصلی فایل دریافتی از سرور، کمک گرفته شده‌است. سپس متد click آن فراخوانی خواهد شد. این روش در مورد تدارک دکمه‌ی دریافت تمام blobهای دریافتی از سرور کاربرد دارد و منحصر به فایل‌های PDF نیست.
اگر خواستید عملیات axios.get و دریافت فایل، با هم یکی شوند، می‌توان متد handleDownloadPdf را پس از پایان کار await axios.get، فراخوانی کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: DownloadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگی‌های سمت کلاینت نیز می‌شود، ابتدا به پوشه‌ی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار می‌کند. سپس به پوشه‌ی اصلی برنامه‌ی ASP.NET Core بازگشته و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائه‌ی برنامه‌ی React بر روی پورت 5001 می‌شود.