هدایت درخواست فایل‌های استاتیک در ASP.NET MVC به یک کنترلر
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

فرض کنید یک پوشه Export در ریشه سایت دارید که حاوی تعدادی فایل PDF عمومی است.
سؤال: آیا می‌شود دسترسی به فایل‌های قرار گرفته در این پوشه عمومی را کنترل کرد؟ به نحوی که فقط کاربران عضو سایت پس از اعتبارسنجی بتوانند آن‌ها را دریافت کنند؟
پاسخ: شاید عنوان کنید که می‌توان از تگ location در فایل web.config برای اینکار استفاده کرد:
<location path="Export">
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
  </location>
این تنظیمات هیچ اثری بر روی فایل‌های استاتیک PDF ندارند؛ چون در IIS 6 از موتور ASP.NET رد نخواهند شد. مگر اینکه این نوع پسوند‌ها به aspnet_isapi.dll انتساب داده شوند. در IIS 7 به بعد این وضع بهبود یافته است. اگر تنظیم runAllManagedModulesForAllRequests در وب کانفیگ برنامه به true تنظیم شده باشد و برنامه در حالت Integrated pipeline بجای Classic mode اجرا شود، امکان مدیریت فایل‌های استاتیک نیز در برنامه‌های ASP.NET وجود دارد .

سؤال: آیا راه حلی وجود دارد که بتوان فایل‌های استاتیک را صرفنظر از نوع، نگارش و حالت اجرای IIS توسط موتور ASP.NET مدیریت کرد؟
پاسخ: بلی. در ASP.NET MVC با تنظیم یک سطر ذیل، اینکار انجام می‌شود:
public static void RegisterRoutes(RouteCollection routes)
{
   // ...
   routes.RouteExistingFiles = true;
   // ...
}
توضیحات:
RouteCollection در ASP.NET MVC به کمک امکانات MapPathBasedVirtualPathProvider خود، ابتدا درخواست رسیده را بررسی می‌کند. اگر این درخواست به یک فایل عمومی اشاره کند، کل سیستم مسیریابی را غیرفعال می‌کند. اما با تنظیم RouteExistingFiles دیگر این بررسی صورت نخواهد گرفت (به عبارتی در بالا بردن سرعت نمایشی سایت نیز تاثیر گذار خواهد بود؛ چون یکی از بررسی‌ها را حذف می‌کند).


ایجاد کنترلری به نام پوشه‌ای که قرار است محافظت شود

نام پوشه قرار گرفته در ریشه سایت، Export است. بنابراین برای هدایت درخواست‌های رسیده به آن (پس از تنظیم فوق)، نیاز است یک کنترلر جدید را به نام Export نیز ایجاد کنیم:
using System.IO;
using System.Web.Mvc;

namespace Mvc4RouteExistingFiles.Controllers
{
    public class ExportController : Controller
    {
        public ActionResult Index(string id)
        {
            if (string.IsNullOrWhiteSpace(id))
            {
                return Redirect("/");
            }

            var fileName= Path.GetFileName(id);
            var path = Server.MapPath("~/export/"+ fileName);
            return File(path, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
        }
    }
}
البته بدیهی است در اینجا می‌توان فیلتر Authorize را به کل کنترلر اعمال کرد، یا هر تنظیم دیگری که نیاز است.
برای اینکه بررسی کنیم، آیا واقعا فایل‌های استاتیک قرار گرفته در پوشه Export به این کنترلر هدایت می‌شود یا خیر، یک breakpoint را بر روی سطر اول اکشن متد Index قرار می‌دهیم. برنامه را اجرا کنید ... کار نخواهد کرد، زیرا مسیر یک فایل فرضی به صورت ذیل:
 http://localhost/export/test.pdf
به اکشن متد Index کنترلر Export، نگاشت نخواهد شد (index در این مسیر ذکر نشده است).
برای حل این مشکل فقط کافی است مسیر یابی متناظری را تعریف کنیم:
using System.Web.Mvc;
using System.Web.Routing;

namespace Mvc4RouteExistingFiles
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.RouteExistingFiles = true;

            routes.MapRoute(
                name: "ExportRoute",
                url: "Export/{id}",
                defaults: new { controller = "Export", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}
در اینجا ExportRoute را مشاهده می‌کنید که به آدرس‌هایی به فرم Export/id پاسخ می‌دهد. در این حالت به صورت خودکار با توجه به تنظیمات انجام شده، اکشن متدی که انتخاب می‌شود همان Index خواهد بود و نیازی به ذکر صریح آن نخواهد بود.
اینبار اگر برنامه را اجرا کنیم، breakpoint ما کار خواهد کرد:



تنظیمات ثانویه پس از فعال سازی RouteExistingFiles

در این حالت با فعال سازی مسیریابی فایل‌های موجود، دیگر هیچ فایل استاتیکی به صورت معمول در اختیار کاربران قرار نخواهد گرفت و اگر همانند توضیحات قبل برای آن‌ها کنترلر جداگانه‌ای را تهیه نکنیم، عملا سایت از کار خواهد افتاد.
برای رفع این مشکل، در ابتدای متد RegisterRoutes فوق، تنظیمات ذیل را اضافه کنید تا پوشه‌های content، scripts و همچنین یک سری فایل با پسوند مشخص، همانند سابق و مستقیما توسط سرور ارائه شوند؛ در غیراینصورت کاربر پیغام 404 را پس از درخواست آن‌ها، دریافت خواهد کرد:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("scripts/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.IgnoreRoute("{resource}.ico");
routes.IgnoreRoute("{resource}.png");
routes.IgnoreRoute("{resource}.jpg");
routes.IgnoreRoute("{resource}.gif");
routes.IgnoreRoute("{resource}.txt");
  • #
    ‫۱۱ سال و ۳ ماه قبل، چهارشنبه ۱۲ تیر ۱۳۹۲، ساعت ۲۰:۳۰

    سلام و خسته نباشید

    اکشن تعریف شده تنها برای مقدار Id اجرا خواهد شد اما در صورتی که فولدر Export ما شامل زیرشاخه باشد چه کار باید کرد

    • #
      ‫۱۱ سال و ۳ ماه قبل، چهارشنبه ۱۲ تیر ۱۳۹۲، ساعت ۲۳:۰۰
      یک ستاره قبل از id قرار بدید:
       routes.MapRoute(
                      name: "ExportRoute",
                      url: "Export/{*id}",
                      defaults: new { controller = "Export", action = "Index", id = UrlParameter.Optional }
                  );
      نتیجه آن:


  • #
    ‫۱۰ سال و ۱۰ ماه قبل، جمعه ۱۵ آذر ۱۳۹۲، ساعت ۱۶:۰۷
    بدون اینکه تنظیمات قسمت مسیریابی رو انجام بدم این مسیر باز میشه:
    http://localhost:1431/Export/api.pdf  
    و در حالت پش فرض هم فایلهای موجود در فولدرهای Scripts و Content با تایپ مسیر آنها تو Url،همگی قابل دسترس هستند.

    - تو درک این عبارت مشکل دارم.ممکنه کمی در این باره توضیح بدین:
     routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
    ممنون
    • #
      ‫۱۰ سال و ۱۰ ماه قبل، جمعه ۱۵ آذر ۱۳۹۲، ساعت ۱۶:۱۳
      - بله. این‌ها فایل استاتیک هستند و همانطور که در بحث عنوان شده، به صورت مستقیم و مستقل، توسط IIS مدیریت می‌شوند. موضوع بحث جاری، هدایت این فایل‌های استاتیک، به یک برنامه ASP.NET MVC است؛ پیش از اینکه IIS راسا تصمیم گیری کند.
      - یعنی از هر مسیری که پسوند axd داشت با هر جزء دیگری از مسیر پس از آن، صرفنظر شود.
  • #
    ‫۱۰ سال و ۹ ماه قبل، جمعه ۲۹ آذر ۱۳۹۲، ساعت ۱۶:۱۳
    سلام من هر کاری می‌کنم وقتی تو مرورگر درخواست فایل استاتیکی رو میدم بلافاصله خطای 404 میده و کنترلر اصلا اجرا نمیشه. در متد registerRoutes همه آدرس رو تعریف کرده ام و routeexistingfiles رو هم true کردم یه کنترلر و یه متد هم طبق برنامه بالا تعریف کرده ام ولی به محض درخواست فایل خطای 404 میده در اصل اون فایل وجود نداره ولی من میخوام قبل از اینکه وجود فایل رو بررسی کنه ابتدا کنترلر من رو اجرا کنه همچین چیزی ممکنه؟
    • #
      ‫۱۰ سال و ۹ ماه قبل، جمعه ۲۹ آذر ۱۳۹۲، ساعت ۱۷:۲۵
      بله. به شرطی‌که برای آن route مخصوص بنویسید تا ASP.NET MVC بداند درخواست رسیده را باید به کجا ارسال کند. ضمن اینکه اگر route جدید شما اجرا نمی‌شود، باید مسیریابی‌های موجود را جهت رفع تداخل دیباگ کنید .
  • #
    ‫۱۰ سال و ۷ ماه قبل، شنبه ۳ اسفند ۱۳۹۲، ساعت ۰۱:۳۳
    با سلام و تشکر.
    متد Application_AuthenticateRequest من به ازای هر تقاضا برای فایلهای استاتیک اجرا می‌شود. آیا باید اجرا شود و اگر نه ، به چه نحوی باید آنرا کنترل کرد تا وارد این مرحله نشود؟
    • #
      ‫۱۰ سال و ۷ ماه قبل، شنبه ۳ اسفند ۱۳۹۲، ساعت ۰۱:۴۸
      سؤال و هدف اصلی بحث جاری این است: «آیا می‌شود دسترسی به فایل‌های قرار گرفته در این پوشه عمومی را کنترل کرد؟ به نحوی که فقط کاربران عضو سایت پس از اعتبارسنجی بتوانند آن‌ها را دریافت کنند؟»
      یعنی تمام اینکارها انجام شد تا بتوان دریافت فایل‌های استاتیک را تحت کنترل کامل برنامه و اعتبارسنجی آن قرار داد. اگر نیازی نیست، خوب، مباحث آن‌را پیاده سازی نکنید. همچنین مانند IgnoreRoute نوشته شده در انتهای بحث برای پوشه اسکریپت‌ها یا CSSها، پوشه‌ی مدنظر را از سیستم مسیریابی خارج کنید.
  • #
    ‫۱۰ سال و ۶ ماه قبل، یکشنبه ۲۵ اسفند ۱۳۹۲، ساعت ۲۰:۵۸

    با وجود اضافه کردن routes.RouteExistingFiles = true; و تنظیم مسیردهی RouteConfig اما با صدا زدن http://localhost/export/test جواب می‌گیرم ولی با http://localhost/expor/test.jpg خطای 404 دریافت می‌کنم

    دستورت IgnoreRoute مربوطه را هم اعمال کردم.

    من از mvc5 استفاده می‌کنم ، مطلب فوق در این ورژن هم صادق است؟

    • #
      ‫۱۰ سال و ۶ ماه قبل، دوشنبه ۲۶ اسفند ۱۳۹۲، ساعت ۰۰:۵۱
      فرقی نمی‌کند: Mvc5RouteExistingFiles.zip
      فقط اگر قرار است تمام فایل‌های استاتیک پوشه‌ی export، منهای تصاویر jpg آن route شوند، باید یک سطر زیر را هم اضافه کنید:
      routes.IgnoreRoute("export/{resource}.jpg");
  • #
    ‫۱۰ سال و ۶ ماه قبل، جمعه ۲۲ فروردین ۱۳۹۳، ساعت ۰۳:۵۴
    فایلهایی که به این صورت دانلود میشن قابلیت resume ندارن ، راهی برای resume وجود داره ؟
  • #
    ‫۹ سال و ۱۲ ماه قبل، چهارشنبه ۱۶ مهر ۱۳۹۳، ساعت ۱۷:۰۹
    آیا این کار تاثیر زیادی توی سرعت کار میزاره؟
    من میخوام واسه‌ی همه‌ی فایل‌های تصویری اینکارو بکنم، پیشنهاد میشه؟
    یا مثلا با استفاده از  این لینک
    • #
      ‫۹ سال و ۱۲ ماه قبل، چهارشنبه ۱۶ مهر ۱۳۹۳، ساعت ۱۷:۵۲
      تاثیر قابل توجهی ندارد. تمام تصاویر سایت جاری به صورت مستقیم ارائه نمی‌شوند و return File هستند. به این صورت مباحث caching را بهتر می‌شود اعمال کرد.
  • #
    ‫۹ سال و ۱۲ ماه قبل، چهارشنبه ۱۶ مهر ۱۳۹۳، ساعت ۱۷:۳۴

    من دنبال این کار هستم، چطوری میتونم آدرس تگ img رو به این صورت دربیارم، فایل css رو میتونم با باندل به این حالت دربیارم ولی تگ img رو نه!

    • #
      ‫۹ سال و ۱۲ ماه قبل، چهارشنبه ۱۶ مهر ۱۳۹۳، ساعت ۱۷:۵۱
      این لینک در ASP.NET MVC یک چنین امضایی دارد؛ با خروجی return File:
      public class AvatarController : Controller
      {
          public ActionResult Index(string id, int s, string d, string r)
  • #
    ‫۹ سال و ۸ ماه قبل، دوشنبه ۲۲ دی ۱۳۹۳، ساعت ۰۱:۵۷
    با سلام؛ من وقتی روی اکشن متود Authorize میزارم و بعد میخواد فایل رو از طریق نرم افزاری مثه IDM دانلود کنم میبینم که اونو به صفحه‌ی login منتقل میکنه در حالی که میخوام دیالوگ login در نرم افزار IDM بیاد بالا!
    چجوری میشه این کارو کرد؟
    • #
      ‫۹ سال و ۸ ماه قبل، دوشنبه ۲۲ دی ۱۳۹۳، ساعت ۰۲:۱۹
      - دیالوگ لاگین در IDM برای حالت Basic authentication ظاهر می‌شود. 
      + نرم افزار IDM اگر از طریق افزونه‌های آن (^) اقدام به دریافت این فایل کند، مشکلی نخواهد داشت؛ چون این افزونه‌ها اطلاعات سشن جاری کاربر را به برنامه، جهت شبیه سازی و استفاده‌ی مجدد منتقل می‌کنند (اطلاعاتی مانند کوکی‌ها و تمام مشخصات جاری صفحه‌ی لاگین کرده). به عبارتی اگر شخصی به سایت لاگین کند و از طریق مرورگری که افزونه‌ی IDM بر روی آن نصب است، اقدام به دریافت فایل کند (بر روی لینک کلیک کند تا مرورگر توسط افزونه‌ی مربوطه، درخواست را به برنامه ارسال کند)، دریافت فایل از دید او معمولی و مانند قبل خواهد بود.
  • #
    ‫۹ سال و ۲ ماه قبل، دوشنبه ۲۹ تیر ۱۳۹۴، ساعت ۰۱:۳۵
    با سلام؛ در سرورم فایل‌هایی برای دانلود دارم که عموم مراجعه کنندگان به سایت می‌تونن اون فایل‌ها رو دانلود کنند، فایل‌های عمومی از نسخه‌های آزمایشی نرم‌افزارهایی که به صورت معمول تولید میشه، طبعا به صورت پیش فرض آدرس فایل برای دانلود به صورت ثابت و به شکل http://example.com/trialversion/filename.ext هست، اما من نیاز به این دارم که در زمان درخواست کاربر برای دانلود فایل مسیر دانلود به صورت مجازی یا فیک باشه ولی به یک فایل اصلی مرتبط باشه و کاربر هم وقتی لینک رو می‌بینه در واقع به صورت یه لینک نامشخص ببینه مثلا : http://example.com/6te99ue-0990r/acs9899?dddd یه لینکی شبیه به این یا نزدیک به این .
    ممنون میشم راهنماییم کنید که چه‌طور در mvc می‌تونم یه همچین چیزی داشته باشم .
    • #
      ‫۹ سال و ۲ ماه قبل، دوشنبه ۲۹ تیر ۱۳۹۴، ساعت ۰۳:۰۴
      نکته‌ی «یک ستاره قبل از id قرار بدید» با الگوی مسیریابی شما مطابقت دارد. در این حالت id دریافتی می‌تواند تا n سطح هم باشد. این Id را به صورت random تولید و در بانک اطلاعاتی ذخیره کنید. زمانیکه به اکشن متد مربوطه می‌رسد، آن‌را از بانک اطلاعاتی واکشی کرده و بر اساس مسیر واقعی آن، return File کنید.