مطالب
حذف سریع رکورد های یک لیست SharePoint با NET. در PowerShell
لطفا توجه فرمایید که جالب‌ترین قسمت این مقاله قابلیت استفاده از کلاس‌های دات نت در دل PowerShell می‌باشد. که در قسمت چهارم کد‌ها مشاهده می‌فرمایید.
حذف تمام رکورد‌های یک لیست شیرپوینت از طریق رابط کاربری SharePoint  مسیر نمیباشد و لازم است برای آن چند خط کد نوشته شود که می‌توانید آن را با console و جالب‌تر از آن با  PowerShell   اجرا کنید.
1- ساده‌ترین روش حذف رکورد‌های شیرپوینت را در روبرو مشاهده می‌فرمایید که به ازای حذف هر رکورد یک رفت و برگشت به پایگاه انجام می‌شود
SPList list = mWeb.GetList(strUrl);
if (list != null)
{
    for (int i = list.ItemCount - 1; i >= 0; i--)
    {
        list.Items[i].Delete();
    }
    list.Update();
}
2- با استفاده از  SPWeb.ProcessBatchData در کد زیر می‌توانیم با سرعت بیشتر و هوشمندانه‌تری، حذف تمام رکورد‌ها را در یک عمل انجام دهیم
public static void DeleteAllItems(string site, string list)
{
    using (SPSite spSite = new SPSite(site))
    {
        using (SPWeb spWeb = spSite.OpenWeb())
        {
            StringBuilder deletebuilder = BatchCommand(spWeb.Lists[list]);
            spSite.RootWeb.ProcessBatchData(deletebuilder.ToString());
        }
    }
}

private static StringBuilder BatchCommand(SPList spList)
{
    StringBuilder deletebuilder= new StringBuilder();
    deletebuilder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");
    string command = "<Method><SetList Scope=\"Request\">" + spList.ID +
        "</SetList><SetVar Name=\"ID\">{0}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar></Method>";

    foreach (SPListItem item in spList.Items)
    {
        deletebuilder.Append(string.Format(command, item.ID.ToString()));
    }
    deletebuilder.Append("</Batch>");
    return deletebuilder;
}
3- در قسمت زیر همان روش batch  قبلی را مشاهده می‌فرمایید که با تقسیم کردن batch  ها به تکه‌های 1000 تایی کارایی آن را بالا برده ایم
// We prepare a String.Format with a String.Format, this is why we have a {{0}} 
   string command = String.Format("<Method><SetList Scope=\"Request\">{0}</SetList><SetVar Name=\"ID\">{{0}}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar><SetVar Name=\"owsfileref\">{{1}}</SetVar></Method>", list.ID);
   // We get everything but we limit the result to 100 rows 
   SPQuery q = new SPQuery();
   q.RowLimit = 100;

   // While there's something left 
   while (list.ItemCount > 0)
   {
    // We get the results 
    SPListItemCollection coll = list.GetItems(q);

    StringBuilder sbDelete = new StringBuilder();
    sbDelete.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");

    Guid[] ids = new Guid[coll.Count];
    for (int i=0;i<coll.Count;i++)
    {
     SPListItem item = coll[i];
     sbDelete.Append(string.Format(command, item.ID.ToString(), item.File.ServerRelativeUrl));
     ids[i] = item.UniqueId;
    }
    sbDelete.Append("</Batch>");

    // We execute it 
    web.ProcessBatchData(sbDelete.ToString());

    //We remove items from recyclebin
    web.RecycleBin.Delete(ids);

    list.Update();
   }
  }
4- در این قسمت به جالب‌ترین و آموزنده‌ترین قسمت این مطلب می‌پردازیم و آن import کردن namespace‌ها و ساختن object‌های دات نت در دل PowerShell هست که می‌توانید به راحتی با مقایسه با کد قسمت قبلی که در console نوشته شده است، آن‌را فرا بگیرید.
برای فهم script پاور شل زیر کافیست به چند نکته ساده زیر دقت کنید 
  • ایجاد متغیر‌ها به سادگی با شروع نوشتن نام متغیر با $ و بدون تعریف نوع آن‌ها انجام می‌شود
  • write-host حکم  write را دارد و واضح است که نوشتن تنهای آن برای ایجاد یک line break می‌باشد. 
  • کامنت کردن با # 
  • عدم وجود semi colon  برای اتمام فرامین
[System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")

write-host 

# Enter your configuration here
$siteUrl = "http://mysharepointsite.example.com/"
$listName = "Name of my list"
$batchSize = 1000

write-host "Opening web at $siteUrl..."

$site = new-object Microsoft.SharePoint.SPSite($siteUrl)
$web = $site.OpenWeb()
write-host "Web is: $($web.Title)"

$list = $web.Lists[$listName];
write-host "List is: $($list.Title)"

while ($list.ItemCount -gt 0)
{
  write-host "Item count: $($list.ItemCount)"

  $batch = "<?xml version=`"1.0`" encoding=`"UTF-8`"?><Batch>"
  $i = 0

  foreach ($item in $list.Items)
  {
    $i++
    write-host "`rProcessing ID: $($item.ID) ($i of $batchSize)" -nonewline

    $batch += "<Method><SetList Scope=`"Request`">$($list.ID)</SetList><SetVar Name=`"ID`">$($item.ID)</SetVar><SetVar Name=`"Cmd`">Delete</SetVar><SetVar Name=`"owsfileref`">$($item.File.ServerRelativeUrl)</SetVar></Method>"

    if ($i -ge $batchSize) { break }
  }

  $batch += "</Batch>"

  write-host

  write-host "Sending batch..."

  # We execute it 
  $result = $web.ProcessBatchData($batch)

  write-host "Emptying Recycle Bin..."

  # We remove items from recyclebin
  $web.RecycleBin.DeleteAll()

  write-host

  $list.Update()
}

write-host "Done."


مطالب
کاربرد Action ها در Github - خودکار سازی فرآیند کامپایل و آپلود فایل در Release گیت‌هاب
نکته: این آموزش مبتنی بر دات نت نسخه 5 می‌باشد (قابل استفاده در نسخه 3.0 و 3.1 نیز می‌باشد اما تست نشده است). در این آموزش فرض شده‌است که شما توانایی کار کردن با git و گیت‌هاب را دارید. همچنین دقت کنید که گزینه‌های زیر در فایل csproj شما موجود باشد، در غیر این صورت ممکن است با خطا مواجه شوید:
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>

چند وقتی می‌شود که گیت‌هاب اکشن‌ها را معرفی کرده که با استفاده از این اکشن‌ها میتوان برخی فعالیت‌های تکراری را، خودکار سازی کرد و در وقت و انرژی صرفه جویی کرد. ما چه کارهایی را میتوانیم با کمک اکشن‌ها انجام بدهیم؟ تقریبا میشود گفت هرکاری را میتوان با اکشن‌ها انجام داد. کامپایل کردن پروژه، تست کردن، پابلیش کردن و...
من معمولا فایل‌های خروجی که برای پروژه‌هایم میگیرم، بدلیل استفاده از ویژگی SelfContained و ایمیج‌های ReadyToRun برای اجرای سریعتر، معمولا حجمی حدود 140 مگ دارند. حالا وقتی آنها را فشرده میکنم، به حدود 50 مگ کاهش پیدا میکنند که اگر من بخواهم هر دفعه که از برنامه خروجی میگیرم، فایل خروجی را فشرده کنم و آن را در گیت‌هاب آپلود کنم، هم فرآیندی زمانبر هست و هم اینکه تکراری و خسته کننده؛ در نتیجه تصمیم گرفتم که از اکشن گیت‌هاب برای خودکارسازی این فرآیند استفاده کنم. 
در این نوشته میخواهیم یک اکشن بنویسیم که بر اساس سورس کد موجود بر روی گیت‌هاب، برنامه را کامپایل کند، فایل اجرایی را بصورت فایل فشرده Zip ایجاد کرده و در نهایت این فایل فشرده را در قسمت Release گیت‌هاب منتشر کند.
برای شروع کار اول باید در مخزن پروژه، در سایت گیت‌هاب، به قسمت Action برویم:

حالا روی قسمت New workflow کلیک میکنیم
 
شما میتونید از قالب‌های پیش‌فرض موجود، هر کدام را که خواستید انتخاب کنید. من ترجیح میدهم با یک قالب خالی شروع کنم. پس بر روی set up a workflow yourself کلیک میکنیم:
 
کدهای پیش‌فرضی که وجود دارند را پاک کنید، تا مثل تصویر زیر، یک فایل کاملا تمیز و خالی را داشته باشیم. نکته‌ای که باید دقت کنید، اسم و مسیر فایل می‌باشد. مسیر فایل را اصلا نباید تغییر داد، ولی اسم فایل را میتوانید اصلاح کنید؛ ولی توجه کنید که پسوند فایل باید yml باشد.
حالا نوبت نوشتن کدهای اکشن رسیده:
اول از همه کد زیر را مینویسیم:
name: "Publish"

این خط، اسم اکشن مارا مشخص میکند که قرار است در لیست workflow‌ها نمایش داده شود:
در کد بعدی که باید بنویسیم (در خط بعدی) باید مشخص بکنیم که این اکشن چه زمانی اجرا بشود. گزینه‌های زیر استفاده بیشتری دارند:
  • push = هر زمان که کامیتی روی گیتهاب پوش شود، اکشن اجرا میشود.
  • pull_request = هر زمانی که یک پول ریکوئست مرج شود، اکشن اجرا میشود.
  • workflow_dispatch = برنامه نویس خودش میتواند با کلیک بر روی دکمه‌ی مشخصی در قسمت اکشن‌ها، اکشن موردنظر را اجرا کند.
لیست کامل تریگر‌ها را میتوانید اینجا مطالعه کنید.
ما از push استفاده میکنیم؛ البته مقداری آن را تغییر میدهیم تا زمانیکه شامل تگ بود، اکشن اجرا شود.
on:
  push:
    tags:
      - "v*"
نکته‌ای که وجود دارد، ما در آخر این دستور، از *v استفاده کردیم که اشاره میکند اگر تگی بصورت v1.0.0 بود، اجرا بشود. * میتواند هر عددی باشد.
3 متغیر ایجاد میکنیم تا محل فایل پروژه، اجرایی و فشرده را نگه دارد، تا فایل اکشن زیاد شلوغ نباشد. 
env:
  PROJECT_PATH: src/HandySub/HandySub.csproj
  ZIP_PATH: src/HandySub/bin/Release/net5.0-windows/win-x86/publish/HandySub-x86.zip
  EXE_PATH: src/HandySub/bin/Release/net5.0-windows/win-x86/publish/HandySub.exe
حالا باید دستورات خودکار سازی را بنویسیم. همه دستورات باید در قسمت jobs نوشته شوند:
 jobs:
  deploy:
    runs-on: windows-latest
به قسمت runs-on توجه کنید. این گزینه مشخص میکند که اکشن ما بر روی سرور ویندوزی و آخرین نسخه‌ی از آن اجرا بشود؛ در صورت نیاز میتوانید از linux نیز استفاده کنید.
در خط بعدی باید قدم به قدم دستورات را بنویسیم. ما برای هر قدم، از name استفاده میکنیم که هنگام اجرای اکشن، بصورت مرتب و خوانا بتوانیم بفهمیم که در چه مرحله‌ای از اجرا هستیم.
قدم اول باید اکشن را آماده کنیم. اکثر دستورات مهم در این اکشن موجود است:
 steps:
      - name: Initialize Actions
        uses: actions/checkout@v2
قدم بعدی باید sdk دات نت را بر روی سرور، دانلود و نصب کنیم:
     - name: Initialize .Net
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 5.0.x
قبل از بیلد پروژه، باید کتابخانه‌ها و بسته‌های نیوگت را restore کنیم، تا بیلد، خطا نداشته باشد:
  - name: Restore Project
        run: dotnet restore ${{ env.PROJECT_PATH }}
حالا میتوانیم دستور خروجی گرفتن را بنویسیم (حتما باید در فایل csproj پروژه خود، runtimeidentifier  پروژه مشخص باشد که اینجا ما از win-x86 استفاده کردیم؛ در غیر اینصورت خطا میگیرید)  چون در مرحله قبل پروژه را restore کردیم، هنگام خروجی گرفتن، دستور no-restore را مینویسیم تا دوباره بسته‌های نیوگت ری‌استور نشود.
 - name: Publish Project
        run: dotnet publish ${{ env.PROJECT_PATH }} -c Release --self-contained -r win-x86 --no-restore
حالا که فایل اجرایی را ایجاد کردیم، باید آن را بصورت فشرده و zip در بیاوریم. برای اینکار از یک اکشن دیگر کمک میگیریم. اول آن را به اصطلاح using میکنیم؛ سپس از آن استفاده میکنیم. این اکشن 2 ورودی دارد: files و dest که به ترتیب باید آدرس فایل‌ها و محل ذخیره زیپ را به آن بدهیم که ما از متغیرهایی که قبلا ایجاد کرده‌ایم، استفاده میکنیم. 
 - name: Create Zip File
        uses: papeloto/action-zip@v1
        with:
          files: ${{ env.EXE_PATH }}
          dest: ${{ env.ZIP_PATH }}
در مرحله بعدی باید از یک اکشن دیگر برای ساخت Release در قسمت گیت‌هاب کمک بگیریم. دقت کنید که برای آپلود کردن فایل زیپ داخل این Release، ما باید توکن و id این ریلیز را ذخیره کنیم که در اینجا در متغیر GITHUB_TOKEN و id ذخیره میکنیم. توکن را میتوانیم از قسمت secrets خود گیت‌هاب دریافت کنیم. همچنین ما اسم تگ را از طریق github.ref دریافت میکنیم (که مقدار v1.0.0 را به عنوان مثال برای ما برگشت خواهد داد (شماره نسخه همان تگی است که پوش کرده‌ایم) 
 - name: Initialize Release
        uses: actions/create-release@v1
        id: create_release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
حالا نوبت این است که فایل زیپ را آپلود کنیم. دوباره باید از اکشن دیگری برای این امر کمک بگیریم. توکنی را که قبلا ذخیره کردیم، همراه با فایل زیپ، به این اکشن میدهیم و در پایان به کمک id که قبلا ذخیره کردیم، لینک فایل آپلود شده را از آن دریافت میکنیم.  
 - name: Create Release 
        uses: csexton/release-asset-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          file: ${{ env.ZIP_PATH }}
          release-url: ${{ steps.create_release.outputs.upload_url }}
کد کامل را اینجا ببینید: 
name: "Publish"

on:
  push:
    tags:
      - "v*"

env:
  PROJECT_PATH: src/HandySub/HandySub.csproj
  ZIP_PATH: src/HandySub/bin/Release/net5.0-windows/win-x86/publish/HandySub-x86.zip
  EXE_PATH: src/HandySub/bin/Release/net5.0-windows/win-x86/publish/HandySub.exe
  

jobs:
  deploy:
    runs-on: windows-latest
    steps:
      - name: Initialize Actions
        uses: actions/checkout@v2

      - name: Initialize .Net
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 5.0.x
      
      - name: Restore Project
        run: dotnet restore ${{ env.PROJECT_PATH }}

      - name: Publish Project
        run: dotnet publish ${{ env.PROJECT_PATH }} -c Release --self-contained -r win-x86 --no-restore

      - name: Create Zip File
        uses: papeloto/action-zip@v1
        with:
          files: ${{ env.EXE_PATH }}
          dest: ${{ env.ZIP_PATH }}
          
      - name: Initialize Release
        uses: actions/create-release@v1
        id: create_release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
      
      - name: Create Release    
        uses: csexton/release-asset-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          file: ${{ env.ZIP_PATH }}
          release-url: ${{ steps.create_release.outputs.upload_url }}

در آخر بر روی دکمه سبز رنگ بالا سمت راست start commit کلیک کنید تا تغییرات ثبت شوند.
به پروژه خود برگردید و ترمینال را باز کنید و یک تگ جدید را ایجاد کنید. 
git tag v1.0.0
سپس تگ ایجاد شده را به مخزن گیت‌هاب پوش کنید:
git push origin tag v1.0.0

 به مخزن گیت‌هاب مراجعه کنید تا ببینید که اکشن شما بصورت خودکار اجرا شده و در پایان، یک ریلیز برای شما ایجاد میکند.
 در قسمت ریلیز میتوانید ببینید که ریلیز توسط ربات گیت‌هاب ایجاد شده‌است:
مطالب
6# آموزش سیستم مدیریت کد Git : استفاده به صورت محلی (بخش دوم)
در قسمت قبل برخی از دستورات مورد نیاز برای کار با git به صورت محلی گفته شد. در اینجا به بخشی دیگر از این دستورات خواهیم پرداخت:

مشاهده تغییرات فایل‏ ها:

در بسیاری از موارد نیاز است تا بتوانیم تفاوت فایل‌های موجود در working tree و فایل‌های موجود در stage و repository را دریابیم. بدین منظور می‌توان از دستورات زیر استفاده کرد:
git log
برای مشاهده تغییرات فایل‌ها بین دو commit دلخواه از کد زیر استفاده می‌کنیم:
git diff

تذکر: در اغلب موارد می‌توانید تنها از چند مقدار اول SHA-1 برای آدرس‌دهی استفاده نمود. چون معمولا این کد به اندازه کافی دارای تغییرات است.البته کار کردن با کد‌های SHA-1 ممکن است مشکل باشد؛ به همین جهت می‌توان از دستور زیر نیز برای مشاهده تغییرات استفاده نمود:
git diff HEAD~[number]..HEAD~[number]

توجه کنید که کلمه HEAD اشاره به وضعیت جاری head دارد و عدد number اختلاف آن را با وضعیت جاری مشخص می‌نماید. به عنوان مثال در شکل زیر ما می‌خواهیم اختلاف فایل‌ها را بین ۲ دستور commit با مقادیر 9da  و  e0e را مشخص نماییم. همانطور که ملاحظه می‌کنید اولی اشاره به وضعیت جاری head و دومی وضعیت قبلی head است. بنابراین ما از دستور زیر استفاده می‌کنیم:
git diff HEAD~1..HEAD

همچنین اگر بخواهیم اختلاف فایلی را در working tree و stage ببینیم، کافی است که از دستور زیر استفاده کنیم:
git diff --staged [filename]
در صورتی‌که در تنظیمات git، نرم افزار پیش فرضی را برای نمایش اختلاف فایل‌ها تعیین نکرده باشید، git اختلاف فایل‌ها را خود نمایش می‌دهد. اما از آنجاییکه این نمایش چندان مطلوب نیست، بهتر است از دستور زیر برای تنظیم نمایش اختلاف فایل‌ها در نرم افزار دیگری استفاده کنید: 
git config --global diff.external <path_to_wrapper_script>
تنظیمات مورد نیاز برای این کار در اینجا گفته شده است.
تذکر: راه حل ساده برای این منظور نصب git extension است که در آموزش نصب گفته شد.

تنظمیم git برای صرفنظر کردن از برخی فایل‌ها:

اگراز دستوراتی نظیر . add استفاده کنید متوجه خواهید شد در بعضی موارد نیازی ندارید که تمامی فایل‌های موجود در working tree به repository اضافه شوند. فایل‌ها در git به دو دسته تقسیم می‌شوند؛ برخی که در حال حاضر دنبال شده و برخی که git تغییرات آنها را دنبال نمی‌کند. در صورتیکه بخواهید فایلی که تغییرات آن دنبال نمی‌شود را به طور کلی حذف کنید، می‌توانید از دستور clean استفاده کنید. دو اصلاح کننده معروف این دستور n- برای نمایش آنکه چه فایل هایی حذف خواهند شد و -f برای اجبار در حذف آنها:

git clean -n [filename]

git clean -f [filename]

اما در برخی موارد نیاز است که فایل‌ها وجود داشته باشند، اما تنها git تغییرات آن‌ها را دنبال نکند، نه آنکه مانند دستور بالا آنها را از working tree نیز حذف نماید. بدین منظور git از فایل بی‌نامی با پسوند gitignore. استفاده می‌کند این فایل از عبارات منظم به شکل بسیار محدودی پشتیبانی می‌کند. در ادامه برخی از دستوراتی را که می‌توان برای حذف برخی فایل‌ها در این فایل نوشت را مشاهده خواهید کرد:
۱ مجموعه: مثال [adgJHn]
۲ بازه: [9-0] یا [a-z]
۳ حذف یک دایرکتوری با نوشتن آدرس آن و قرار دادن / (البته توجه کنید که با این کار sub directory‌ها هنوز هم track خواهند شد)
می‌توان با استفاده از علامت !  برخی از فایل‌ها و یا دایرکتوری‌ها را مستثنی کرد
می‌توان این تنظیمات را در فایلی با نام دلخواه ذخیره کرد و سپس با استفاده از دستور زیر آن‌ها را به صورت global یا سراسری اعمال نمود:
git config global core.excludesfile [path and filename]
توجه کنید که git تغییرات پوشه‌های خالی را دنبال نمی‌کند بنابراین اگر قصد دارید پوشه‌ای در repository ذخیره شود یک فایل temp در آن ایجاد کنید
چند مثال:
اگر بخواهید فایل‌های باینری داخل فولدر bin در repository ذخیره نشوند این خط را در این فایل اضافه می‌کنیم:
bin/
هیچ فایلی با پسوند txt را در نظر نگیر:
*.txt
هیچ فایلی را با پسوند txt در فولدر bin در نظر نگیر
/bin/*.txt
هیچ فایلی با پسوند txt را در نظر نگیر به جز readme1.txt

 *.txt
!readme1.txt
توجه کنید که هر آنچه بین دو علامت # قرار گیرد به عنوان توضیح در نظر گرفته می‌شود 
نظرات مطالب
ASP.NET MVC #11
با سلام و خسته نباشید
کاملاً مشهوده که رفته رفته ViewModel ها بیشتر مورد استقبال قرار می گیرند. اما به نظر من استفاده از ViewModel ها در MVC صحیح نیست. دلیل اصلی اش اینه که ساختار MVC و اهداف اون رو تحت تأثیر قرار می ده. بقیه دلایل هم نهایتاً به همین بر می گردند.
معمولا ظاهر نرم افزارهای کاربردی تحت وب بیشتر از منطق درونی آنها تغییر می کنند. مثلا برای تنوع، یک کار ثابت رو به شکل های جدید انجام می دهند. در واقع برای یک کاری که توسط کنترلر بر روی مدل انجام می گیرد، view های جدید درست می شود. در این حالت برای اینکه بتوانیم تغییرات جدید رو اعمال کنیم، در هر بار علاوه بر اینکه باید ViewModel رو تصحیح کنیم (قطعات جدید اضافه یا حذف کنیم)، باید کنترلرهای مربوطه رو هم هماهنگ کنیم که این باعث کاهش استقلال لایه ها می شود.
دلیل محبوبیت ViewModel ها همانا سادگی آن ها و راحتی طراحی است. یعنی در یک صفحه نسبتاً پیچیده که قسمت های مختلفی دارد، خیلی زود می شه یک ViewModel درست کرد و همه قسمت های صفحه رو توش پوشش داد. اما به نظر من این بعداً مشکل ساز خواهد شد. بهتر است به دور از عجله کمی به ساختار صفحه و راه حل های جایگزین فکر کرد. به عنوان نمونه:
1- می شه از امکانات خود MVC برای انتقال اطلاعات بین کنترلر و ویو استفاده کرد. می تونیم نمونه ای از مدل رو به ویو بفرستیم و برای موارد اضافی از ViewBag استفاده کنیم. مثلاً می شه اسامی استان ها رو که به یک DropDownList بایند خواهند شد، به شکل زیر بفرستیم:
ViewBag.StatesList = new SelectList(_bll.GetStatesList(), "Id", "Title"); //ltr
2- این توضیح رو بدم که MVC ما رو تشویق می کنه که منطق نرم افزارمون رو به ماژول های کوچک تقسیم کنیم و هر ماژول رو به تنهایی مدیریت کنیم. همه جا این تقسیم بندی ها هست. اول کل ساختار نرم افزار رو به سه لایه M و V و C تقسیم می کنه. بعد View ها و کنترلر های هر مدل رو که (که در عالم واقعیت هم مستقل هستند) از هم تفکیک می کنه و یاد میده اینها هر کدوم باید تو فولدر جداگانه ای باشند. Area ها هم در همین راستا هستند. حتی در داخل کنترلر ها، هر کدوم از اکشن ها یک کار واحد کوچک رو انجام می دهند. اما ViewModel این ساختار رو می شکنه و ما رو مجبور می کنه با مفاهیم مرکب سرو کار داشته باشیم. شاید سؤال این باشه که در عمل ما صفحاتی داریم که باید چند ماهیت رو در اونها نمایش بدیم. مثلاً ممکنه در یک صفحه واحد، هم لیست دانش آموز ها رو به همراه امکانات حذف، اضافه و ویرایش داشته باشیم و هم لیست کلاس ها رو. علاوه بر اون یک فرم ارسال نظر در پایین صفحه، یک textbox برای فیلتر کردن و ... داشته باشیم. درسته که می شه با زحمت کمی یک ViewModel حاوی تمام اینها درست کرد و بعد یک View از اون ساخت و در نگاه  اول همه چی به خوبی کار کنه. ولی بهتره هر کدوم از اینها مستقلاً پیاده سازی شوند. مثلاً صفحه ای برای دانش آموزان، صفحه ای برای کلاس ها و ... غصه اون صفحه بزرگ رو هم نخورید. می شه هر کدوم از View های مستقل رو در داخل اون نمایش داد و همون ظاهر رو بدست آورد (مثلاً با استفاده از Html.Action یا Ajax). حسن بزرگی که داره اینه که هر کدوم از این قطعات در قسمت های مختلف قابل استفاده می شوند. به این ترتیب می توانیم با جابجایی آن ها در صفحات مختلف، ظاهر نرم افزارمون رو به دلخواه تغییر بدیم بدون اینکه مجبور باشیم تغییری در کنترلر ها و قسمت های دیگه ایجاد کنیم.
در کل ViewModel ها استقلال لایه ها را کاهش می دهند، شدیداً موجب افزایش کدها می شوند، قابلیت گسترش منطقی ندارند و رفته رفته بزرگتر و بزرگتر می شوند، ماژولار نیستند و ...
بنابراین استفاده از ViewModel مکروه بوده و دوری از آن احتیاط واجب است! منتظر فتوای شما در این زمینه هستم. چون به نظر من ارجحه و فصل الخطاب.
با اینکه سعی کردم کوتاه بشه نشد. ببخشید. دوستون دارم. داود زینی
مطالب
چرا به الگوهای طراحی JavaScript نیاز داریم ؟
امروزه اهمیت یادگیری JavaScript بر هیچ کس پوشیده نیست ، API‌های جدید HTML 5 و امکانات جدید وب مثل Geo Location ، Offline storage ، Canvas ، Offline Web applications و ... همه نیاز به دانش JavaScript دارند. در نتیجه نیاز است علاوه بر یادگیری JavaScript به تمیز کد نوشتن جهت سهولت نگهداری آگاه بود.  همانطور که در کدهای سمت سرور مثل C# و یا PHP نیاز به استفاده از الگوهای طراحی (Design Patterns)است در JavaScript هم اوضاع به همین منوال است.
الگوی طراحی یک راه حل قابل استفاده مجدد است که برای حل مشکلات متداول در طراحی نرم افزار به کار می‌رود.
چرا به الگوی‌های طراحی JavaScript نیازمندیم ؟ 
  1. می خواهیم کد هایی با قابلیت استفاده‌ی مجدد بنویسیم ، استفاده از عملکرد‌های مشابه در سطح صفحات یک Web application یا چند Web Application.
  2. می خواهیم کد هایی با قابلیت نگهداری بنویسیم ، هر چه قدر در فاز توسعه کد‌های با کیفیت بنویسیم در فاز نگهداری از آن بهره می‌بریم. باید کد هایی بنویسیم که قابل Debug و خواندن توسط دیگر افراد تیم باشند.
  3. کدهای ما نباید با توابع و متغیر‌های دیگر پلاگین‌ها تداخل نامگزاری داشته باشند. در برنامه‌های امروزی بسیار مرسوم است که از پلاگین‌های Third party استفاده شود. می‌خواهیم با رعایت Encapsulation and modularization در کدهایمان از این تداخل جلوگیری کنیم.
معمولا کد‌های JavaScript که توسط اکثر ما نوشته می‌شود یک سری تابع پشت سرهم هست ، بدون هیچ کپسوله سازی : 
function getDate() {
    var now = new Date();
    var utc = now.getTime() + (now.getTimezoneOffset() * 60000);
    var est;
    est = new Date(utc + (3600000 * -4));
    return dateFormat(est, "dddd, mmmm dS, yyyy, h:MM:ss TT") + " EST";

}
function initiate_geolocationToTextbox() {
    navigator.geolocation.getCurrentPosition(handle_geolocation_queryToTextBox);
}

function handle_geolocation_queryToTextBox(position) {
    var longitude = position.coords.longitude;
    var latitude = position.coords.latitude;
    $("#IncidentLocation").val(latitude + " " + longitude);
}
به این روش کدنویسی Function Spaghetti Code گفته می‌شود که معایبی دارد : 
  • توابع و متغیر‌ها به Global scope برنامه افزوده می‌شوند.
  • کد Modular نیست.
  • احتمال رخ دادن Conflict در اسامی متغیر‌ها و توابع بالا می‌رود.
  • نگهداری کد به مرور زمان سخت می‌شود.
با شبیه سازی یک مثال مشکلات احتمالی را بررسی می‌کنیم : 
// file1.js
function saveState(obj) {
    // write code here to saveState of some object
    alert('file1 saveState');
}
// file2.js (remote team or some third party scripts)
function saveState(obj, obj2) {
     // further code...
    alert('file2 saveState");
}
همانطور که می‌بینید در این مثال در 2 فایل متفاوت در برنامه مان از 2 تابع با اسامی یکسان و امضای متفاوت استفاده کرده ایم . اگر فایل‌ها را اینگونه در برنامه آدرس دهی کنیم : 
<script src="file1.js" type="text/javascript"></script>
<script src="file2.js" type="text/javascript"></script>
متد saveState در فایلی که دیرتر آدرس داده شده (file2.js) ، متد saveState در file1.js را Override می‌کند ، در نتیجه عملکردی که از متد saveState در فایل اول انتظار داریم اتفاق نمی‌افتد.
 در پست بعدی به راه حل این مشکلات و کپسوله سازی خواهم پرداخت.
برای مطالعه‌ی بیشتر کتاب (Learning JavaScript Design Patterns) را از دست ندهید.
مطالب
بررسی Source Generators در #C - قسمت دوم - یک مثال
یک مثال: پیاده سازی INotifyPropertyChanged توسط Source Generators

هدف از اینترفیس INotifyPropertyChanged که به همراه یک رخ‌داد است:
public interface INotifyPropertyChanged  
{ 
   event PropertyChangedEventHandler PropertyChanged;  
}
مطلع سازی استفاده کننده‌ی از یک شیء، از تغییرات رخ‌داده‌ی در مقادیر خواص آن است که نمونه‌ی آن، در برنامه‌های WPF، جهت به روز رسانی UI، زیاد مورد استفاده قرار می‌گیرد. البته این رخ‌داد به خودی خود کار خاصی را انجام نمی‌دهد و برای استفاده‌ی از آن، باید مقدار زیادی کد نوشت و این مقدار کد نیز باید به ازای تک تک خواص یک کلاس مدل، تکرار شوند:
  partial class CarModel : INotifyPropertyChanged
  {

    private double _speedKmPerHour;
    
    public double SpeedKmPerHour
    {
      get => _speedKmPerHour;
      set
      {
        _speedKmPerHour = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SpeedKmPerHour)));
      }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
  }
همچنین باید درنظر داشت که با تغییر نام خاصیتی، میزان قابل ملاحظه‌ای از این کدهای تکراری نیز باید به روز رسانی شوند که این عملیات می‌تواند ایده‌ی خوبی برای استفاده‌ی از Source Generators باشد.
اگر بخواهیم تولید این کدهای تکراری را به Source Generators محول کنیم، می‌توان برای مثال فیلد خصوصی مرتبط را نگه داشت و تولید مابقی کدها را خودکار کرد:
  partial class CarModel : INotifyPropertyChanged
  {
    private double _speedKmPerHour;    
  }
در این حالت کلاس مدل، به صورت partial تعریف می‌شود و فقط فیلد خصوصی، در کدهای ما حضور خواهد داشت. مابقی کدهای این کلاس partial به صورت خودکار توسط یک Source Generator سفارشی تولید خواهد شد. همانطور که ملاحظه می‌کنید، کاهش حجم قابل ملاحظه‌ای حاصل شده و همچنین اگر فیلد خصوصی دیگری نیز در اینجا اضافه شود، واکنش Source Generator ما آنی خواهد بود و بلافاصله کدهای مرتبط را تولید می‌کند و برنامه، بدون مشکلی کامپایل خواهد شد؛ هرچند به ظاهر INotifyPropertyChanged ذکر شده، در این کلاس اصلا پیاده سازی نشده‌است.


ایجاد پروژه‌ی Source Generator

در ابتدا برای ایجاد تولید کننده‌ی خودکار کدهای INotifyPropertyChanged، یک class library را به solution جاری اضافه می‌کنیم. سپس نیاز است ارجاعاتی را به دو بسته‌ی نیوگت زیر نیز افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
  </ItemGroup>
</Project>
سپس کلاس جدید NotifyPropertyChangedGenerator را به نحو زیر به آن اضافه می‌کنیم:
  [Generator]
  public class NotifyPropertyChangedGenerator : ISourceGenerator
  {
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
- این کلاس باید اینترفیس ISourceGenerator را پیاده سازی کرده و همچنین مزین به ویژگی Generator باشد.
- اینترفیس ISourceGenerator به همراه دو متد Initialize و Execute است که در صورت نیاز باید پیاده سازی شوند.

در متد Execute، به خاصیت context.Compilation دسترسی داریم. این خاصیت تمام اطلاعاتی را که کامپایلر از Solution جاری در اختیار دارد، به توسعه دهنده ارائه می‌دهد. برای نمونه پیاده سازی متد Execute تولید کننده‌ی کد مثال جاری، چنین شکلی را دارد:
    public void Execute(GeneratorExecutionContext context)
    {
      // uncomment to debug the actual build of the target project
      // Debugger.Launch();
      var compilation = context.Compilation;
      var notifyInterface = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged");

      foreach (var syntaxTree in compilation.SyntaxTrees)
      {
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        var immutableHashSet = syntaxTree.GetRoot()
          .DescendantNodesAndSelf()
          .OfType<ClassDeclarationSyntax>()
          .Select(x => semanticModel.GetDeclaredSymbol(x))
          .OfType<ITypeSymbol>()
          .Where(x => x.Interfaces.Contains(notifyInterface))
          .ToImmutableHashSet();

        foreach (var typeSymbol in immutableHashSet)
        {
          var source = GeneratePropertyChanged(typeSymbol);
          context.AddSource($"{typeSymbol.Name}.Notify.cs", source);
        }
      }
    }
در اینجا با استفاده از context.Compilation به اطلاعات کامپایلر دسترسی پیدا کرده و سپس SyntaxTrees آن‌را یکی یکی، جهت یافتن کلاس‌ها و یا همان ClassDeclarationSyntax ها، پیمایش و بررسی می‌کنیم. سپس از بین این کلاس‌ها، کلاس‌هایی که INotifyPropertyChanged را پیاده سازی کرده باشند، انتخاب می‌کنیم که اطلاعات آن در پایان کار، به متد GeneratePropertyChanged جهت تولید مابقی کدهای partial class ارسال شده و کد تولیدی، به context اضافه می‌شود تا به نحو متداولی همانند سایر کدهای برنامه، به مجموعه کدهای مورد بررسی کامپایلر اضافه شود.

نکته‌ی مهم و جالب در اینجا این است که نیازی نیست تا قطعه کد جدید را به صورت SyntaxTrees در آورد و به کامپایلر اضافه کرد. می‌توان این قطعه کد را به نحو متداولی، به صورت یک قطعه رشته‌ی استاندارد #C، تولید و به کامپایلر با متد context.AddSource ارائه کرد که نمونه‌ای از آن‌را در ذیل مشاهده می‌کنید:
    private string GeneratePropertyChanged(ITypeSymbol typeSymbol)
    {
      return $@"
using System.ComponentModel;

namespace {typeSymbol.ContainingNamespace}
{{
  partial class {typeSymbol.Name}
  {{
    {GenerateProperties(typeSymbol)}
    public event PropertyChangedEventHandler? PropertyChanged;
  }}
}}";
    }

    private static string GenerateProperties(ITypeSymbol typeSymbol)
    {
      var sb = new StringBuilder();
      var suffix = "BackingField";

      foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>()
        .Where(x=>x.Name.EndsWith(suffix)))
      {
        var propertyName = fieldSymbol.Name[..^suffix.Length];
        sb.AppendLine($@"
    public {fieldSymbol.Type} {propertyName}
    {{
      get => {fieldSymbol.Name};
      set
      {{
        {fieldSymbol.Name} = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));
      }}
    }}");
      }

      return sb.ToString();
    }
در اینجا در ابتدا بدنه‌ی کلاس partial تکمیل می‌شود. سپس خواص عمومی آن بر اساس فیلدهای خصوصی تعریف شده، تکمیل می‌شوند. در این مثال اگر یک فیلد خصوصی به عبارت BackingField ختم شود، به عنوان فیلدی که قرار است معادل کدهای INotifyPropertyChanged را داشته باشد، شناسایی می‌شود و به همراه کدهای تولید شده‌ی خودکار خواهد بود.

کدهای source generator ما همین مقدار بیش‌تر نیست. اکنون می‌خواهیم از آن در یک برنامه‌ی کنسول جدید (برای مثال به نام NotifyPropertyChangedGenerator.Demo) استفاده کنیم. برای اینکار نیاز است ارجاعی را به آن اضافه کنیم؛ اما این ارجاع، یک ارجاع متداول نیست و نیاز به ذکر چنین ویژگی خاصی وجود دارد:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
  </ItemGroup>
</Project>
در اینجا میسر دهی پروژه‌ی تولید کننده‌ی کد، همانند سایر پروژه‌ها است؛ اما نوع آن باید آنالایزر معرفی شود. به همین جهت از خاصیت OutputItemType با مقدار Analyzer استفاده شده‌است. همچنین تنظیم ReferenceOutputAssembly به false به این معنا است که این اسمبلی ویژه، یک وابستگی و dependency واقعی پروژه‌ی جاری نیست و ما قرار نیست به صورت مستقیمی از کدهای آن استفاده کنیم.

برای آزمایش این تولید کننده‌ی کد، کلاس CarModel را به صورت زیر به پروژه‌ی کنسول آزمایشی اضافه می‌کنیم:
using System.ComponentModel;

namespace NotifyPropertyChangedGenerator.Demo
{
  public partial class CarModel : INotifyPropertyChanged
  {
    private double SpeedKmPerHourBackingField;
    private int NumberOfDoorsBackingField;
    private string ModelBackingField = "";

    public void SpeedUp() => SpeedKmPerHour *= 1.1;
  }
}
این کلاس پیاده سازی کننده‌ی INotifyPropertyChanged است؛ اما به همراه هیچ خاصیت عمومی نیست. فقط به همراه یکسری فیلد خصوصی ختم شده‌ی به «BackingField» است که توسط تولید کننده‌ی کد شناسایی شده و اطلاعات آن‌ها تکمیل می‌شود. فقط باید دقت داشت که این کلاس حتما باید به صورت partial تعریف شود تا امکان تکمیل خودکار کدهای آن وجود داشته باشد.

یک نکته:   در این حالت هرچند برنامه بدون مشکل کامپایل و اجرا می‌شود، ممکن است خطوط قرمزی را در IDE خود مشاهده کنید که عنوان می‌کند این قطعه از کد قابل کامپایل نیست. اگر با چنین صحنه‌ای مواجه شدید، یکبار solution را بسته و مجددا باز کنید تا تولید کننده‌ی کد، به خوبی شناسایی شود. البته نگارش‌های جدیدتر Visual Studio و Rider به همراه قابلیت auto reload پروژه برای کار با تولید کننده‌‌های کد هستند و دیگر شاهد چنین صحنه‌هایی نیستیم و حتی اگر برای مثال فیلد جدیدی را به CarModel اضافه کنیم، نه فقط بلافاصله کدهای متناظر آن تولید می‌شوند، بلکه خواص عمومی تولید شده در Intellisense نیز قابل دسترسی هستند.


نحوه‌ی مشاهده‌ی کدهای خودکار تولید شده

اگر علاقمند باشید تا کدهای خودکار تولید شده را مشاهده کنید، در Visual Studio، در قسمت و درخت نمایشی dependencies پروژه، گره‌ای به نام Analyzers وجود دارد که در آن برای مثال نام NotifyPropertyChangedGenerator و ذیل آن، کلاس‌های تولید شده‌ی توسط آن، قابل مشاهده و دسترسی هستند و حتی قابل دیباگ نیز می‌باشند؛ یعنی می‌توان بر روی سطور مختلف آن، break-point قرار داد.


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

معرفی تعدادی منبع تکمیلی
- برنامه Source generator playground
در اینجا تعدادی مثال را که توسط مایکروسافت توسعه یافته‌است، مشاهده می‌کنید که اتفاقا یکی از آن‌ها پیاده سازی تولید کننده‌ی کد اینترفیس INotifyPropertyChanged است. در این برنامه، خروجی کدهای تولیدی نیز به سادگی قابل مشاهده‌است.

- برنامه SharpLab
برای توسعه‌ی تولید کننده‌های کد، عموما نیاز است تا با Roslyn API آشنا بود. در این برنامه اگر از منوی بالای صفحه قسمت results، گزینه‌ی «syntax tree» را انتخاب کنید و سپس قسمتی از کد خود را انتخاب کنید، بلافاصله معادل Roslyn API آن، در سمت راست صفحه نمایش داده می‌شود.

- معرفی مجموعه‌ای از Source Generators
در اینجا می‌توان مجموعه‌ای از پروژه‌های سورس باز Source Generators را مشاهده و کدهای آن‌ها را مطالعه کنید و یا از آن‌ها در پروژه‌های خود استفاده نمائید.

- معرفی یک cookbook در مورد Source Generators
این cookbook توسط خود مایکروسافت تهیه شده‌است و جهت شروع به کار با این فناوری، بسیار مفید است.

- مجموعه مثال‌های Source generators از مایکروسافت
در اینجا می‌توانید مجموعه مثال‌هایی از Source generators را که توسط مایکروسافت تهیه شده‌است، مشاهده کنید. شرح و توضیحات تعدادی از آن‌ها را هم در اینجا مطالعه کنید.
اشتراک‌ها
ISE - یک محیط مجتمع برای کار کردن با پاور شل

یک محیط مجتمع برای کار کردن با پاور شل. از نسخه های ۲ تا ۴ رو هم پشتیبانی می‌کنه. اگر از ویندوز ۸.۱ یا Windows Server 2012 R2 استفاده می‌کنید به صورت پیش فرض روی سیستم شما وجود داره (البته چون یه محیط گرافیکی تو Server Core قابل دسترسی نیست). برای دسترسی به اون فقط کافیه تو پاورشل بنویسید ise بعد هم یه اینتر. ولی برای نسخه‌های ویندوز پایین‌تر نیاز به یک بروز رسانی دارید.

اطلاعات بیشتر:  + 

ISE - یک محیط مجتمع برای کار کردن با پاور شل
مطالب
آشنایی با OWIN و بررسی نقش آن در ASP.NET Core
در این مطلب می‌خواهیم نگاهی به قسمت‌های کلیدی OWIN و همچنین پروژه‌ی Katana بیندازیم و در نهایت نیز نقش OWIN را در ASP.NET Core بررسی خواهیم کرد.



OWIN چیست؟

همانطور که می‌دانید OWIN یک specification است که استانداری را بین وب‌سرور و وب‌اپلیکیشن‌ها تعریف کرده است. در واقع OWIN یکسری لایه‌ی انتزاعی را جهت ایجاد اپلیکیشن‌هایی که نحوه‌ی میزبانی آنها اهمیتی ندارد، تعریف خواهد کرد. به صورت خلاصه توسط این لایه‌ی انتزاعی می‌توانیم وابستگی بین وب‌سرور و وب‌اپلیکیشن را حذف کنیم. در این specification منظور از وب‌سرور یک delegate و همچنین یک دیکشنری است. در واقع هدف این است که وقتی درخواستی به این وب‌سرور ارسال شد، درخواست به قسمت‌های کوچکی تقسیم‌بندی شده و درون این دیکشنری قرار خواهند گرفت (این دیکشنری حاوی کلیدهای از پیش‌تعریف شده‌ای است که توسط OWIN تعریف شده‌اند). سپس این دیکشنری از طریق یک application function به درون pipeline ارسال خواهد شد و از یکسری middleware عبور خواهد کرد. در اینحالت می‌توانیم کنترلی را بر روی درخواست‌های وارده و صادره داشته باشیم. ایده‌ی middleware خیلی شبیه به HTTP moduleها در IIS است؛ اما تفاوت آن این است که middlewareها وابستگی‌ایی به IIS ندارند و همچنین مبتنی بر رویداد نیستند. هر middleware بعد از انجام تغییرات بر روی درخواست، تا زمان رسیدن دیکشنری به آخرین middleware، آن را به middleware بعدی ارسال خواهد کرد. در این حین می‌توانیم به response streams اطلاعاتی را append کنیم. وقتی دیکشنری از تمامی middlewareها عبور کرد، سرور مطلع خواهد شد و نتیجه را به کلاینت ارسال می‌کند.


استاندارد OWIN تعدادی کلید را درون یک دیکشنری تعریف کرده است که بعد از ورود به هر middleware مقداردهی خواهند شد. این کلیدها را می‌توانیم در دو دسته‌ی Request و Response بررسی کنیم.

کلیدهای مربوط به Request

ضروری؟

نام کلید

مقدار

بله

"owin.RequestBody"

یک Stream همراه با request body. اگر body برای request وجود نداشته باشد، Stream.Null به عنوان placeholder قابل استفاده است.

بله

"owin.RequestHeaders"

یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای درخواست.

بله

"owin.RequestMethod"

رشته‌ایی حاوی نوع فعل متد HTTP مربوط به درخواست (مانند GET and POST )

بله

"owin.RequestPath"

path درخواست شده به صورت string

بله

"owin.RequestPathBase"

قسمتی از path درخواست به صورت string

بله

"owin.RequestProtocol"

نام و نسخه‌ی پروتکل (مانند HTTP/1.0 or HTTP/1.1 )

بله

"owin.RequestQueryString"

رشته‌ای حاوی query string ؛ بدون علامت ? (مانند foo=bar&baz=quux )

بله

"owin.RequestScheme"

رشته‌ایی حاوی URL scheme استفاده شده در درخواست (مانند HTTP or HTTPS )



کلیدهای مربوط به Response

ضروری؟

نام کلید

مقدار

بله

"owin.ResponseBody"

یک Stream جهت نوشتن response body در خروجی

بله

"owin.ResponseHeaders"

یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای response

خیر

"owin.ResponseStatusCode"

یک عدد صحیح حاوی کد وضعیت HTTP response ؛ حالت پیش‌فرض 200 است.

خیر

"owin.ResponseReasonPhrase"

یک رشته حاوی reason phrase مربوط به status code ؛ اگر خالی باشد در نتیجه سرور بهتر است آن را مقداردهی کند.

خیر

"owin.ResponseProtocol"

یک رشته حاوی نام و نسخه‌ی پروتکل (مانند HTTP/1.0 or HTTP/1.1 )؛ اگر خالی باشد؛ “owin.RequestProtocol” به عنوان مقدار پیش‌فرض در نظر گرفته خواهد شد.


Katana
پروژه‌ی Katana یک پیاده‌سازی از استاندارد OWIN است که توسط مایکروسافت ایجاد شده است. مایکروسافت علاوه بر پیاده‌سازی OWIN، یکسری قابلیت دیگر را نیز به آن اضافه کرده است. برای شروع کار با Katana یک پروژه خالی از نوع ASP.NET Web Application را ایجاد کنید. در ادامه لازم است پکیج Microsoft.Owin.Host.SystemWeb را نیز نصب کنیم. همراه با نصب این پکیج، دو وابستگی دیگر نیز نصب خواهند شد؛ زیرا پیاده‌سازی OWIN درون پکیج Microsoft.Owin قرار دارد:
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net461" />
<package id="Owin" version="1.0" targetFramework="net461" />
در ادامه نیاز به یک نقطه‌ی شروع برای اپلیکیشن‌مان داریم. طبق convention باید یک فایل را با نام Startup.cs با محتویات زیر ایجاد کنیم:
using Owin;
namespace SimpleOwinWebApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {

        } 
    }
}
توسط IAppBuilder می‌توانیم middlewareها را به pipeline اضافه کنیم:
using Owin;
namespace SimpleOwinWebApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.Use(async (ctx, next) =>
            {
                await ctx.Response.WriteAsync("Hello");
            });
        } 
    }
توسط متد Use، یک middleware را به صورت inline تعریف کرده‌ایم. متد Use یک delegate را از ورودی دریافت خواهد کرد و امضای آن به اینصورت است:
Func<IOwinContext, Func<Task>, Task> handler

IOwinContext در واقع یک wrapper برای environment dictionaryایی است که در ابتدا به آن اشاره کردیم. در مثال قبل، از پراپرتی Response، جهت ارسال خروجی به کلاینت استفاده شده است. این پراپرتی در واقع معادل کلید owin.ResponseBody درون دیکشنری است. اما در اینجا به صورت strongly-typed و ساده به آن دسترسی داریم؛ هر چند که امکان کار با دیکشنری خام نیز وجود دارد. به عنوان مثال معادل مثال قبل بدون استفاده از پراپرتی Response، اینچنین خواهد بود:
app.Use(async (ctx, next) =>
{
   var response = ctx.Environment["owin.ResponseBody"] as Stream;
   using (var writer = new StreamWriter(response))
   {
      await writer.WriteAsync("Hello");
   }
});
اکنون اگر پروژه را اجرا کنید، با وارد کردن هر آدرسی، پیام Hello درون مرورگر برایتان نمایش داده خواهد شد:


به هر تعداد middleware که خواستید می‌توانید به pipeline اضافه کنید؛ اما باید دقت داشته باشید که ترتیب قرار دادن آنها اهمیت دارد.

Self-hosting OWIN
در مثال قبلی، اپلیکیشن توسط IIS Express اجرا می‌شد. برای میزبانی درون یک کنسول اپلیکیشن، ابتدا یک پروژه‌ی Console Application را ایجاد کرده و پکیج Microsoft.Owin.SelfHost را نصب کنید. سپس کلاس Startup موردنظرتان را ایجاد کرده و در نهایت درون متد Main، کار راه‌اندازی سرور را انجام خواهیم داد:
using System;
using Microsoft.Owin.Hosting;

namespace SimpleOwinConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (WebApp.Start<Startup>("http://localhost:12345"))
            {
                Console.WriteLine("Listening to port 12345");
                Console.WriteLine("Press Enter to end...");
                Console.ReadLine();
            }
        }
    }
}

OWIN در ASP.NET Core
ASP.NET Core دارای مفهومی با عنوان pipeline است. این pipeline خیلی شبیه به OWIN است اما OWIN نیست؛ بلکه عملکرد آن شبیه به OWIN است. به عنوان مثال اینبار به جای دیکشنری، شیء HttpContext را داریم. در ادامه یک پروژه‌ی ASP.NET Core Web Application از نوع Empty را شروع خواهیم کرد. اگر دقت کنید اینبار برای کلاس Startup باید دو متد را پیاده‌سازی کنیم:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace SimpleOwinCoreApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

متد Configure: همانطور که در ابتدای مطلب مشاهده کردید این متد قبلاً در پروژه‌های مبتنی بر کاتانا Configuration نام داشت؛ همچنین به جای IAppBuilder اینبار IApplicationBuilder را داریم. مزیت ASP.NET Core این است که در هر جایی از اپلیکیشن می‌توانیم از سیستم DI توکار آن استفاده کنیم؛ در نتیجه علاوه بر IApplicationBuilder وابستگی‌های دیگری مانند IHostingEnvironment و ILoggerFactory را نیز می‌توانیم تزریق کنیم.
متد ConfigureServices: در اینجا می‌توانیم سرویس‌های موردنیاز درون اپلیکیشن را برای IoC ریجستر کنیم.
در کد فوق استفاده از متد Use به معنای آخرین نقطه در pipeline است. یعنی جایی که response برگردانده خواهد شد و چیزی بعد از آن اجرا نخواهد شد؛ در واقع ارجاعی به middleware بعدی وجود ندارد.

ایجاد یک Middleware جدید
تا اینجا تمامی کدها را به صورت inline نوشتیم. اما اگر بخواهیم middlewareمان قابلیت استفاده‌ی مجدد داشته باشد می‌توانیم تعاریف آن را به یک کلاس با ساختار زیر منتقل نمائیم:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace SimpleOwinCoreApp.Middlewares
{
    public class SimpleMiddleware
    {
        private readonly RequestDelegate _next;

        public SimpleMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext ctx)
        {
            // قبل از فراخوانی میان‌افزار بعدی

            await ctx.Response.WriteAsync("Hello DNT!");

            await _next(ctx);

            // بعد از فراخوانی میان‌افزار بعدی
        }
    }
}

درون متد Invoke بعد از پردازش درخواست باید متد middleware بعدی را همراه با context جاری فراخوانی کنیم. در نتیجه قبل و بعد از فراخوانی middleware بعدی فرصت این را خواهیم داشت تا درخواست را پردازش کنیم. در نهایت برای استفاده از middleware فوق می‌توانیم از متد الحاقی UseMiddleware استفاده کنیم:
app.UseMiddleware<SimpleMiddleware>();

استفاده از middlewareهای مبتنی بر Katana در ASP.NET Core
middlewareهایی را که برای Katana نوشته‌اید، درون یک اپلیکیشن ASP.NET Core نیز قابل استفاده هستند. برای اینکار با مراجعه به فایل project.json می‌توانید پکیج زیر را نصب کنید:
"Microsoft.AspNetCore.Owin": "1.0.0"
سپس درون متد Configure می‌توانید Owin را به pipeline اضافه کرده و middleware خود را ریجستر کنید:
app.UseOwin(pipeline =>
{
pipeline(next => new MyKatanaBasedMiddleware(next).Invoke)
});

مثال تکمیلی:
در ادامه می‌خواهیم ماژول مطرح شده در این مطلب  را به صورت یک middleware با قابلیت پذیرفتن تنظیمات، نوشته و سپس درون pipeline استفاده کنیم. برای شروع یک کلاس با نام IpBlockerMiddleware با محتویات زیر ایجاد خواهیم کرد:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace SimpleOwinAspNetCore.Middleware
{
    public class IpBlockerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IpBlockerOptions _options;

        public IpBlockerMiddleware(RequestDelegate next, IpBlockerOptions options)
        {
            _next = next;
            _options = options;
        }

        public async Task Invoke(HttpContext context)
        {
            var ipAddress = context.Request.Host.Host;
            if (IsBlockedIpAddress(ipAddress))
            {
                context.Response.StatusCode = 403;
                await context.Response.WriteAsync("Forbidden : The server understood the request, but It is refusing to fulfill it.");
                return;
            }
            await _next.Invoke(context);
        }

        private bool IsBlockedIpAddress(string ipAddress)
        {
            return _options.Ips.Any(ip => ip == ipAddress);
        }
    }
}
در کدهای فوق لیست Ipها از پراپرتی Ips درون کلاس IpBlockerOptions دریافت خواهد شد:
using System.Collections.Generic;

namespace SimpleOwinAspNetCore.Middleware
{
    public class IpBlockerOptions
    {
        public IpBlockerOptions()
        {
            Ips = new[] { "192.168.1.1" };
        }
        public IList<string> Ips { get; set; }
    }
}
همچنین برای استفاده راحت‌تر از middleware، یک متد الحاقی را برای آن ایجاد کرده‌ایم و سپس پراپرتی Ips را توسط اینترفیس IConfigurationRoot دریافت کرده‌ایم:
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;

namespace SimpleOwinAspNetCore.Middleware
{
    public static class IpBlockerExtensions
    {
        public static IApplicationBuilder UseIpBlocker(this IApplicationBuilder builder, IConfigurationRoot configuration, IpBlockerOptions options = null)
        {
            return builder.UseMiddleware<IpBlockerMiddleware>(options ?? new IpBlockerOptions
            {
                Ips = configuration.GetSection("block_list").GetChildren().Select(p => p.Value).ToArray()
            });
        }
    }
}
قبلاً در رابطه با فایل‌های کانفیگ مطلبی را مطالعه کرده‌اید؛ در نتیجه نیازی به توضیح اضافه‌تری ندارد. تنها کاری که در اینجا انجام شده است، دریافت محتویات کلید block_list از فایل کانفیگ است. 
محتویات فایل blockedIps.json:
{
  "block_list": [
    "192.168.1.1",
    "localhost",
    "127.0.0.1",
    "172.16.132.151"
  ]
}

برای خواندن فایل فوق در برنامه نیز خواهیم داشت:
public IConfigurationRoot Configuration { set; get; }

public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("blockedIps.json");
Configuration = builder.Build();
}
در نهایت برای استفاده از middleware فوق خواهیم داشت:
app.UseIpBlocker(Configuration);
اکنون هر درخواستی که با آدرس‌های تعیین شده درون فایل blockedIps.json وارد pipeline شود، امکان استفاده‌ی از سایت را نخواهد داشت.

کدهای این مطلب را می‌توانید از اینجا دریافت کنید.
مطالب
بررسی چند کتابخانه آپلود با پشتیبانی از DragDrop
برای یکی از پروژه‌ها نیاز به یک آپلودر داشتم که قابلیت  Drag&Drop را نیز داشته باشد و در ضمن پیاده سازی آسانی هم داشته باشد. در این بین به تعدادی از کتابخانه‌های جی کوئری می‌پردازیم.
FileDrop
اولین کتابخانه‌ای که با آن آشنا شدم و از آن استفاده کردم، کتابخانه‌ی FileDrop است که بسیار ساده و در عین حال قابلیت‌های خوبی را می‌دهد و از فناوری Filereader  (+) در Html5 برای اینکار استفاده می‌کند. مرورگرهای کروم، فایرفاکس 3.6 به بعد، IE10 به بعد و Opera 12 به بعد از آن پشتیبانی می‌کنند.
فایل‌های مورد نیاز را از اینجا دانلود کنید . فایل اسکریت آن را ابتدا صدا بزنید:
 <script src="~/scripts/jquery.filedrop.js" type="text/javascript"></script>
سپس المان‌های زیر را به کد HTML خود اضافه کنید:
 <div id="dropZone">فایل برنامه را به داخل این کادر بکشانید</div>
        <br>
        فایل یا فایل‌های آپلود شده:
        <ul id="uploadResult"></ul>
تگ اول، محلی است که فایل‌ها به سمت آن درگ و روی آن دراپ می‌شوند که از این به بعد به آن محل آپلود می‌گوییم. المان بعدی جهت گزارش فایل‌هایی است که آپلود شده‌اند. با آپلود شدن هر تعداد فایل، اسم آن به لیست اضافه می‌گردد.
کدهای css زیر را هم به صفحه اضافه کنید تا محل آپلود زیباتر شود:
        .files {
            min-height: 42px;
            background: #CCC none repeat scroll 0% 0%;
            border-top: 1px solid #FFF;
            margin: 11px 0px;
            padding: 11px 13px;
            border-radius: 6px;
        }

        #dropZone.mouse-over {
            background-color: #1d4257;
        }

کد جی کوئری زیر را به صفحه اضافه کنید:
      $('#dropZone').filedrop({
                url: uploadAddress,
                paramname: 'files',
                maxFiles: 1,
                dragOver: function() {
                    $('#dropZone').addClass('mouse-over');
                },
                dragLeave: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                drop: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                afterAll: function() {
                    $('#dropZone').html('آپلود با موفقیت انجام شد');
                },
                uploadFinished: function(i, file, response, time) {
                    $('#uploadResult').append('<li>' + file.name + '</li>');
                }
            });
ابتدا پلاگین جی کوئری را روی تگ مربوطه اعمال می‌کنیم و سپس پارامترها را با مشخصات زیر اعمال می‌کنیم:
Url  آدرسی که قرار است فایل‌ها به آن سمت ارسال شوند. 
 Paramname  در سمت سرور باید فایل‌ها را با استفاده از این نام پارامتر دریافت  کنید.
 maxFiles  تعداد فایلهایی که میتوان با درگ و دراپ کردن روی آن به دست آورد. در بالا به یک فایل محدود شده است.
 dragOver  این رویداد زمانی اجرا خواهد شد که اشاره گر با حالت درگ کرده فایل‌ها را به محل آپلود آورده است.
 dragLeave  موقعی که ماوس از محل آپلود خارج می‌شود
 drop  موقعی که شما فایل‌ها را روی محل  آپلود رها می‌کنید.
 afterAll  بعد از اینکه همه کارها تمام شد اجرا می‌شود.(آخرین رویداد)
 uploadFinished  کار آپلود به پایان رسیده است. در مثال بالا پس از پایان آپلود، نام فایل آپلود شده را به کاربر نشان داده‌ایم.

نحوه‌ی دریافت آن در سمت سرور, در یک اکشن متد به صورت زیر است:

[HttpPost]
        public virtual ActionResult UpdateApp(IEnumerable<HttpPostedFileBase>files)
        {
            foreach (HttpPostedFileBase file in files)
            {
                string filePath = Path.Combine(TempPath, file.FileName);
                file.SaveAs(filePath);
            }

            return Json(new {state = "success", message = "با موفقیت عملیات ارسال فایل انجام شد"}, JsonRequestBehavior.AllowGet);
        }

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

این موارد از اصلی‌ترین‌ها هستند که به کار می‌آیند. به غیر این‌ها یک سری خصوصیات اضافه‌تری هم برای آن وجود دارد.


fallback_id 
 اگر دوست دارید این آپلودر را نیر به یک آپلودر معمولی اتصال دهید از این شناسه استفاده کنید.
 withCredentials   با استفاده از کوکی‌ها یک درخواست cross-origin ایجاد می‌کند.
 data  اگر دوست دارید به همراه فایل‌ها اطلاعات دیگری هم به همراه آن ارسال و پست شوند از این طریق اقدام نمایید. می‌تواند در قالب یک متغیر باشد یا خروجی یک تابع.
data: {
        param1: 'value1',           
        param2: function(){
            return calculated_data; 
        }
 headers برای ارسال مقدار اضافه‌تر در هدر درخواست به کار میرود و صدا زدن آن همانند کد data می‌باشد. 
 error   در صورتیکه در فرایند آپلود خطایی رخ دهد، اجرا می‌گردد. نحوه‌ی کدنویسی آن و بررسی خطاهای آن به شرح زیر است:
error: function(err, file) {
        switch(err) {
            case 'BrowserNotSupported':
                alert('مرورگر از این فناوری پشتیبانی نمی‌کند')
                break;
            case 'TooManyFiles':
                // قصد آپلود همزمان فایل‌های بیشتری از حد مجاز تعیین شده دارید
                break;
            case 'FileTooLarge':
                //حداقل حجم یکی از فایل‌ها از حجم مجاز تعیین شده بیشتر است
                //برای دسترسی به نام آن فایل از کد زیر استفاده کنید
              //file.name
                break;
            case 'FileTypeNotAllowed':
                // نوع حداقل یکی از فایل‌ها با نوع‌ها مشخص شده ما یکی نیست
                break;
            case 'FileExtensionNotAllowed':
                // پسوند حداقل یکی از فایل‌ها مورد تایید نیست
                break;
            default:
                break;
        }
    }
 allowedfiletypes   نوع فایل‌های مجاز را تعیین می‌کند:
allowedfiletypes: 
['image/jpeg','image/png','image/gif']
 allowedfileextensions   پسوند فایل هایی که برای آپلود مجاز هستند را معرفی می‌کند.
allowedfileextensions:
 ['.apk','.jar']
 maxfilesize   حداکثر حجم مجاز برای هر فایل که به مگابایت بیان می‌شود.
 docOver   این رویداد زمانی اجرا می‌شود که فایل‌های درگ شده شما وارد محیط یا پنجره مرورگر می‌شود.
 uploadStarted 
 این رویداد زمانی اجرا میگردد که فرایند آپلود هر فایل به طور جداگانه در حال آغاز شدن است:
متغیر i در کد زیر شامل اندیس فایلی است که آپلودش آغاز شده است و این اندیس از صفر آغاز می‌شود.
متغیر file دسترسی شما را به اطلاعات یک فایل باز میکند مانند نام فایل.
متغیر len تعداد فایل هایی را که کاربر در محل آپلود رها کرده است، باز میگرداند.
function(i, file, len){

    },
uploadFinished 
با اتمام آپلود هر فایل، این رویداد فراخوانی می‌گردد. دو پارامتر اول آن، همانند سابق هستند. پارامتر response خروجی json ایی را که در سمت سرور برگرداندیم، به ما باز می‌گرداند. پارامتر بعدی، زمانی را که برای آپلود طول کشیده است، بر می‌گرداند.
 function(i, file, response, time) {
    }
progressUpdated 
این رویداد برای نمایش پیشرفت یک آپلود مناسب است که آخرین پارامتر آن یک عدد صحیح از پیشرفت فایل را بر می‌گرداند.
function(i, file, progress) {
    },
globalProgressUpdated 
این رویداد میزان پیشرفت کلیه فایل‌ها را به درصد باز می‌گرداند:
function(progress) {
        $('#progress div')
.width(progress+"%"); }
speedUpdated 
سرعت آپلود هر فایل را با کیلوبیت بر ثانیه مشخص می‌کند.
function(i, file, speed) {
    }
rename
در صورتی که قصد تغییر نام فایل ارسالی را دارید می‌توانید از این رویداد استفاده کنید. پارامتر name، نام اصلی فایل را بر می‌گرداند که می‌توانید آن را دستکاری کنید و نام جدیدی را به عنوان خروجی برگردانید. نمونه کاربردی از این رویداد
 rename: function(name) {
    }

beforeEach 
این رویداد قبل از آپلود هر فایل آغاز می‌گردد و برگرداندن مقدار false در آن باعث جلوگیری و کنسل شدن آپلود آن فایل می‌گردد.
function(file) {
    }

beforeSend 
پارامترهای اولی تکراری هستند ولی آخرین پارامتر یک تابع done را می‌توان به آن پاس کرد که قبل از اجرای کل عملیات آپلود صدا زده می‌شود.
 function(file, i, done) {
    }
رویدادی به اسم queuefiles هم هست تعداد فایل‌هایی را که میتوانند به طور موازی و همزمان آپلود گردند، مشخص می‌کند. ولی دراین حالت maxfiles مورد استفاده قرار نمی‌گیرد. جهت بررسی یک مثال عملی و همچنین کدهای سمت سرور در PHP میتوانید از این آموزش استفاده کنید.
با تستی که به صورت لوکال رو آن انجام دادم به نظر نمی‌رسد برای فایل‌های با حجم متوسط به بالا مناسب باشد و برای فایل‌های با حجم کم مناسب می‌باشد. یک فایل 8 مگابایتی در حالت لوکال 9 ثانیه آپلود آن زمان برد و برای فایل‌های بزرگتر، فایرفاکس دیالوگ Stop Script را نشان داد.

PlUpload
این کتابخانه متن باز هم بسیار کارآمد و ساده و قابل انعطاف است و مثالهای آماده زیادی دارد. سایت سابسن هم در بخش آپلود زیرنویس‌ها از این کتابخانه استفاده می‌کند. از آنجا که آموزش این کتابخانه در سایت جاری آمده است از ذکر نکات بیشتر در مورد آن خودداری می‌نماییم.

Bootstrap FileStyle
اگر از قالب بوت استراپ استفاده می‌کنید و دوست دارید روی المان input file  قدیمی، ولی به شکلی مدرن کار کنید این کتابخانه هم فراموش نشود.

DropZoneJS
این کتابخانه به نسبت DropFile امکانات بیشتری را دارد و در سایت اختصاصی آن مثال‌ها و مستندات خوبی قرار گرفته است. در ساده‌ترین حالت آن ابتدا فایل کتابخانه  را صدا زده و سپس تگ فرم را به آن نسبت دهید:
<script src="https://rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script>

<form action="/upload-target" class="dropzone"></form>
ولی اگر بخواهید آن را به سمت سرور ارسال کنید و  از آنجا آن را کنترل کنید، کد فرم را به شکل زیر تغییر دهید:
ابتدا بسته‌ی نیوگت آن را صدا بزنید:
Install-Package dropzone

با نصب این کتابخانه یک سری فایل CSS هم به سیستم اضافه می‌شود که می‌توانید برای استایل دهی هر چه بیشتر از آن بهره ببرید. کد فرم را به شکل زیر تغییر دهید:
    <form action="~/Home/SaveUploadedFile" method="post" enctype="multipart/form-data" class="dropzone" id="dropzoneForm" style="width: 50px; background: none; border: none;">
        <div class="fallback">
            <input name="file" type="file" multiple />
            <input type="submit" value="Upload" />
        </div>
    </form>
تگی که با کلاس fallback مزین شده است موقعی به کار می‌آید که مرورگر از این کتابخانه پشتیبانی نکرده و مجبور به نمایش یک آپلود معمولی می‌شویم.
با استفاده از کدنویسی هم می‌توان یک المان را به یک آپلودر تبدیل کرد:
var myDropzone = new Dropzone("div#myId", { url: "/file/post"});

//============ OR ====================
$("div#myId").dropzone({ url: "/file/post" });
همانطور که می‌بینید الزامی برای اینکه از یک تگ فرم استفاده کنید ندارد.
برای کانفیگ آپلودرهایی که از طریف المانهای Html ایجاد می‌شوند، می‌توان از کد زیر استفاده کرد و یک تنظیم عمومی برای تمامی آپلودرهای html آن صفحه ایجاد کرد.
Dropzone.options.myId= {
  paramName: "file", //نام پارامتری که فایل از طریق آن انتقال می‌بابد
  maxFilesize: 2, // MB
  accept: function(file, done) {
    if (file.name == "justinbieber.jpg") {
      done("Naha, you don't.");
    }
    else { done(); }
  }
};
تابع بالا یک آرگومان از نوع file را برگردانده و اگر این تابع، تابع done را با پارامتری رشته‌ای صدا بزند، عملیات آپلود آن فایل کنسل شده و پیام خطایی را نمایش می‌دهد و در صورتیکه بدون پارامتر صدا زده شود، عمل آپلود بدون مشکل انجام می‌شود.
ازآنجا که این کتابخانه از تنظیمات وسیعی استفاده می‌کند و از حوصله‌ی این مقاله خارج است، بهتر هست که صفحه‌ی مستندات آن را که کامل هم هست، مطالعه بفرمایید. از سری قابلیت‌هایی که پشتیبانی می‌کند: موارد پوشش داده شده در FileDrop، ساخت layout، ایجاد صف، متد حذف و اضافه و از این قبیل، ایجاد تصویر تمبر مانند و ...

یک نکته تکمیلی در مورد آپلود: در ASP.net به طور پیش فرض نهایت حجم فایل آپلودی 4 مگابایتی تعیین شده است که میتوانید آن را از طریق web.config تغییر دهید:
<configuration>
    <system.web>
        <httpRuntime maxRequestLength="1048576" />
    </system.web>
</configuration>
برای IIS 7 به بعد هم از تکه کد زیر استفاده کنید:
<system.webServer>
   <security>
      <requestFiltering>
         <requestLimits maxAllowedContentLength="1073741824" />
      </requestFiltering>
   </security>
 </system.webServer>
در هر دو کد بالا نهایت حجم بر روی یک گیگابایت تعیین شده است که maxRequestLength به صورت کیلوبایت و maxAllowContentLength به صورت بایت تعیین شده است. توصیه می‌شود هر دو شکل آن را وارد کنید. به خصوص که IIS Express از کد ابتدایی استفاده می‌کند و بخواهید نتیجه‌ی آن را در تست‌ها ببینید.