فعال سازی پردازش فایلهای استاتیک در برنامههای ASP.NET Core 1.0
در مورد پوشهی جدید wwwroot در «قسمت 2 - بررسی ساختار جدید Solution» مطالبی عنوان شدند. جهت یادآوری:
اگر فایل Program.cs را بررسی کنید، یک چنین تعاریفی را مشاهده خواهید کرد:
public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); } }
یک مثال: زمانیکه فایل استاتیک images/banner3.svg در پوشهی wwwroot قرار میگیرد، با آدرس http://localhost:9189/images/banner3.svg توسط عموم قابل دسترسی خواهد بود.
یک نکتهی امنیتی مهم
در برنامههای ASP.NET Core، هنوز فایل web.config را نیز مشاهده میکنید. این فایل تنها کاربردی که در اینجا دارد، تنظیم ماژول AspNetCoreModule برای IIS است تا IIS static file handler آن، راسا اقدام به توزیع فایلهای یک برنامهی ASP.NET Core نکند. بنابراین توزیع این فایل را بر روی سرورهای IIS فراموش نکنید. همچنین بهتر است در ویندوزهای سرور، به قسمت Modules feature مراجعه کرده و StaticFileModule را از لیست ویژگیهای موجود حذف کرد.
نصب Middleware مخصوص پردازش فایلهای استاتیک
در قسمت قبل با نحوهی نصب و فعال سازی middleware مخصوص WelcomePage آشنا شدیم. روال کار در اینجا نیز دقیقا به همان صورت است:
الف) نصب بستهی نیوگت Microsoft.AspNetCore.StaticFiles
برای اینکار میتوان بر روی گرهی references کلیک راست کرده و سپس از منوی ظاهر شده،گزینهی manage nuget packages را انتخاب کرد. سپس ابتدا برگهی browse را انتخاب کنید و در اینجا نام Microsoft.AspNetCore.StaticFiles را جستجو کرده و سپس نصب کنید.
انجام این کارها معادل افزودن یک سطر ذیل به فایل project.json است و سپس ذخیرهی آن که کار بازیابی بستهها را به صورت خودکار آغاز میکند:
"dependencies": { // same as before "Microsoft.AspNetCore.StaticFiles": "1.0.0" },
برای اینکار به فایل Startup.cs مراجعه کرده و سطر UseStaticFiles را به متد Configure اضافه کنید (به UseWelcomePage هم دیگر نیازی نداریم):
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); //app.UseWelcomePage(); app.Run(async context => { await context.Response.WriteAsync("Hello DNT!"); }); } }
یک مثال: بر روی پوشهی wwwroot کلیک راست کرده و گزینهی add->new item را انتخاب کنید. سپس یک HTML page جدید را به نام index.html به این پوشه اضافه کنید.
با این محتوا:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hello World</title> </head> <body> Hello World! </body> </html>
که این خروجی دقیقا خروجی app.Run برنامه است و نه محتوای فایل index.html ایی که اضافه کردیم.
در ادامه اگر مسیر کامل این فایل را (http://localhost:7742/index.html) درخواست دهیم، آنگاه میتوان خروجی این فایل استاتیک را مشاهده کرد:
این رفتار اندکی متفاوت است نسبت به نگارشهای قبلی ASP.NET که فایل index.html را به عنوان فایل پیش فرض، درنظر میگرفت و محتوای آنرا نمایش میداد. منظور از فایل پیش فرض، فایلی است که با درخواست ریشهی یک مسیر، به کاربر ارائه داده میشود و index.html یکی از آنها است.
برای رفع این مشکل، نیاز است Middleware مخصوص آنرا به نام Default Files نیز به برنامه معرفی کرد:
public void Configure(IApplicationBuilder app) { app.UseDefaultFiles(); app.UseStaticFiles();
فعال سازی Default Files، سبب جستجوی یکی از 4 فایل ذیل به صورت پیش فرض میشود (اگر تنها ریشهی پوشهای درخواست شود):
default.htm
default.html
index.htm
index.html
اگر خواستید فایل سفارشی خاص دیگری را معرفی کنید، نیاز است پارامتر DefaultFilesOptions آنرا مقدار دهی نمائید:
// Serve my app-specific default file, if present. DefaultFilesOptions options = new DefaultFilesOptions(); options.DefaultFileNames.Clear(); options.DefaultFileNames.Add("mydefault.html"); app.UseDefaultFiles(options);
ترتیب معرفی Middlewares مهم است
در قسمت قبل، در حین معرفی تفاوتهای Middlewareها با HTTP Modules، عنوان شد که اینبار برنامه نویس میتواند بر روی ترتیب اجرای Middlewareها کنترل کاملی داشته باشد و این ترتیب معادل است با ترتیب معرفی آنها در متد Configure، به نحوی که مشاهده میکنید. برای آزمایش این مطلب، متد معرفی middleware فایلهای پیش فرض را پس از متد معرفی فایلهای استاتیک قرار دهید:
public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); app.UseDefaultFiles();
بله. اینبار تعریف فایلهای پیش فرض، هیچ تاثیری نداشته و درخواست ریشهی سایت، بدون ذکر صریح نام فایلی، مجددا به app.Run ختم شدهاست.
توزیع فایلهای استاتیک خارج از wwwroot
همانطور که در ابتدای بحث عنوان شد، با فعال سازی UseStaticFiles به صورت پیش فرض مسیر content root/wwwroot در معرض دید دنیای خارج قرار میگیرد و توسط وب سرور قابل توزیع خواهد شد:
○ wwwroot § css § images § ... ○ MyStaticFiles § test.png
برای این منظور میتوان از پارامتر StaticFileOptions متد UseStaticFiles به نحو ذیل جهت معرفی پوشهی MyStaticFiles استفاده کرد:
app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles") });
فعال سازی مشاهدهی مرور فایلهای استاتیک بر روی سرور
فرض کنید پوشهی تصاویر را به پوشهی عمومی wwwroot اضافه کردهایم. برای فعال سازی مرور محتوای این پوشه میتوان از Middleware دیگری به نام DirectoryBrowser استفاده کرد:
app.UseDirectoryBrowser(new DirectoryBrowserOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") });
Unable to resolve service for type 'System.Text.Encodings.Web.HtmlEncoder' while attempting to activate 'Microsoft.AspNetCore.StaticFiles.DirectoryBrowserMiddleware'.
public void ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); }
مشکل! در این حالت که DirectoryBrowser را فعال کردهایم، اگر بر روی لینک فایل تصویر نمایش داده شده کلیک کنیم، باز پیام Hello DNT یا اجرای app.Run را شاهد خواهیم بود.
به این دلیل که UseStaticFiles پیش فرض، مسیر درخواستی MyImages را که بر روی file system وجود ندارد، نمیشناسد. برای رفع این مشکل تنها کافی است مسیریابی این Request Path خاص را نیز فعال کنیم:
app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") });
بررسی خلاصهی تنظیماتی که به فایل آغازین برنامه اضافه شدند
تا اینجا اگر توضیحات را قدم به قدم دنبال و اجرا کرده باشید، یک چنین تنظیماتی را خواهید داشت:
using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; namespace Core1RtmEmptyTest { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); } public void Configure(IApplicationBuilder app) { app.UseDefaultFiles(); app.UseStaticFiles(); // For the wwwroot folder // For the files outside of the wwwroot app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles") }); // For DirectoryBrowser app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") }); app.UseDirectoryBrowser(new DirectoryBrowserOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") }); //app.UseWelcomePage(); app.Run(async context => { await context.Response.WriteAsync("Hello DNT!"); }); } } }
UseDefaultFiles کار فعال سازی شناسایی فایلهای پیش فرضی مانند index.html را در صورت ذکر نام ریشهی یک پوشه، انجام میدهد.
اولین UseStaticFiles تعریف شده، تمام مسیرهای فیزیکی ذیل wwwroot را عمومی میکند.
دومین UseStaticFiles تعریف شده، پوشهی MyStaticFiles واقع در خارج از wwwroot را عمومی میکند.
سومین UseStaticFiles تعریف شده، پوشهی فیزیکی wwwroot\images را به مسیر درخواستهای MyImages نگاشت میکند (http://localhost:7742/myimages) تا توسط DirectoryBrowser تعریف شده، قابل استفاده شود.
در آخر هم DirectoryBrowser تعریف شدهاست.
یک نکتهی امنیتی مهم
یک چنین قابلیتی (مرور فایلهای درون یک پوشه) به صورت پیش فرض بر روی تمام IISها به دلایل امنیتی غیرفعال است. به همین جهت بهتر است Middleware فوق را هیچگاه استفاده نکنید و به این قسمت صرفا از دیدگاه اطلاعات عمومی نگاه کنید.
ساده سازی تعاریف توزیع فایلهای استاتیک
Middleware دیگری به نام FileServer کار تعریف توزیع فایلهای استاتیک را ساده میکند. اگر آنرا تعریف کنید:
app.UseFileServer();
اگر خواستید DirectoryBrowsing آنرا نیز فعال کنید، پارامتر ورودی آنرا به true مقدار دهی کنید (که به صورت پیش فرض غیرفعال است):
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseFileServer(new FileServerOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles"), EnableDirectoryBrowsing = false });
توزیع فایلهای ناشناخته
اگر به سورس ASP.NET Core 1.0 دقت کنید، کلاسی را به نام FileExtensionContentTypeProvider خواهید یافت. اینها پسوندها و mime typeهای متناظری هستند که توسط ASP.NET Core شناخته شده و توزیع میشوند. برای مثال اگر فایلی را به نام test.xyz به پوشهی wwwroot اضافه کنید، درخواست آن توسط کاربر، به Hello DNT ختم میشود؛ چون در این کلاس پایه، پسوند xyz تعریف نشدهاست.
برای رفع این مشکل و تکمیل این لیست میتوان به نحو ذیل عمل کرد:
// Set up custom content types -associating file extension to MIME type var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".xyz"] = "text/html"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider }) ; // For the wwwroot folder
و یا اگر خواستید کمی تمیزتر کار کنید، بهتر است از کلاس پایه FileExtensionContentTypeProvider ارث بری کرده و سپس در سازندهی این کلاس، خاصیت Mappings را ویرایش نمود:
public class XyzContentTypeProvider : FileExtensionContentTypeProvider { public XyzContentTypeProvider() { this.Mappings.Add(".xyz", "text/html"); } }
app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = new XyzContentTypeProvider() }) ; // For the wwwroot folder
روش دیگر مدیریت این مساله، تنظیم مقدار خاصیت ServeUnknownFileTypes به true است:
app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true, DefaultContentType = "image/png" });