نظرات مطالب
نظرات مطالب
قابلیت Attribute Routing در ASP.NET MVC 5
دیباگش کنید تا مشخص شه کدوم مسیریابی اعمال شده: چطور مسیریابیهای ASP.NET MVC را دیباگ کنیم؟
نظرات مطالب
قابلیت Attribute Routing در ASP.NET MVC 5
با تشکر از شما. آیا در این حالت ذکر مسیریابی پیش فرض الزامی است؟ یعنی باید بالای تمام کنترلرها مثال default route شما را لحاظ کرد؟
اشتراکها
فرمساز پویا مخصوص Blazor
نظرات نظرسنجیها
به عنوان توسعه دهنده اندرویدی چه گوشی را انتخاب می کنید؟
یکی از مسائلی که بسیار مهم است صد در صد میزان محبوبیت گوشی مورد نظر در سطح جامعه است که باعث میشود تست گوشی بهتر صورت بگیرد.
البته راحتی تست گوشی هم بی تاثیر نیست، به عنوان مثال: هوآوی که خودم به عنوان گوشی شخصی هم ازش استفاده میکنم به این صورت هست که صداقت خیلی زیادی از خود در هنگام لاگ زدن نشان میدهد و حتی مسائلی که اصلا برای من اهمیتی ندارد هم لاگ میکند به طوری که در اکلیپس آن قدر سریع لاگ صورت میگیرد که بافر خالی شده و فرصت دیدن چیزی دست نمیدهد ولی در اندروید استادیو این مورد کنترل بیشتری دارد ولی با این حال عملیات لاگ در سطح برنامه زیاد اتفاق میافتد ولی در گوشی سامسونگ قبلی که داشتم یا سونی که چند روز پیش تست کردم تنها لاگ خطای نمایشی توسط Force close نمایش داده شد.
با توجه به جست و جوهای اندکی که داشتم متوجه شدم این مشکل بسیاری از کاربران این گوشی است. با این حال این گوشی مزایایی هم داشته است.
نکته بعدی که مهم میدانم این هست که به روز بودن گوشی در هنگام تست بسیار کاربردی است به عنوان نمونه زمانی که شما قصد دسترسی به حافظه خارجی داشته باشید یا تنطیمات سیستم را بخواهید تغییر دهید از api 23 به بعد این مورد نیاز به یک اجازه مجدد هنگام انجام عمل را دارد و تنها به مجوز داخل مانیفست اکتفا نمیکند. در صورتی که ورژن گوشی از این پایینتر باشد با چنین مسئله ای روبرو نخواهید شد مگر اینکه از قبل درباره این مورد اطلاع داشته باشید و حالا برای مسائل دیگر هم به همین صورت.
البته راحتی تست گوشی هم بی تاثیر نیست، به عنوان مثال: هوآوی که خودم به عنوان گوشی شخصی هم ازش استفاده میکنم به این صورت هست که صداقت خیلی زیادی از خود در هنگام لاگ زدن نشان میدهد و حتی مسائلی که اصلا برای من اهمیتی ندارد هم لاگ میکند به طوری که در اکلیپس آن قدر سریع لاگ صورت میگیرد که بافر خالی شده و فرصت دیدن چیزی دست نمیدهد ولی در اندروید استادیو این مورد کنترل بیشتری دارد ولی با این حال عملیات لاگ در سطح برنامه زیاد اتفاق میافتد ولی در گوشی سامسونگ قبلی که داشتم یا سونی که چند روز پیش تست کردم تنها لاگ خطای نمایشی توسط Force close نمایش داده شد.
با توجه به جست و جوهای اندکی که داشتم متوجه شدم این مشکل بسیاری از کاربران این گوشی است. با این حال این گوشی مزایایی هم داشته است.
نکته بعدی که مهم میدانم این هست که به روز بودن گوشی در هنگام تست بسیار کاربردی است به عنوان نمونه زمانی که شما قصد دسترسی به حافظه خارجی داشته باشید یا تنطیمات سیستم را بخواهید تغییر دهید از api 23 به بعد این مورد نیاز به یک اجازه مجدد هنگام انجام عمل را دارد و تنها به مجوز داخل مانیفست اکتفا نمیکند. در صورتی که ورژن گوشی از این پایینتر باشد با چنین مسئله ای روبرو نخواهید شد مگر اینکه از قبل درباره این مورد اطلاع داشته باشید و حالا برای مسائل دیگر هم به همین صورت.
یک روش کار کردن با پروژههای SPA، توسعهی مجزای قسمتهای front-end و back-end است. برای مثال پروژهی React را به صورت جداگانهای توسعه میدهیم، پروژهی ASP.NET Core را نیز به همین صورت. هنگام آزمایش برنامه، در یکی دستور npm start را اجرا میکنیم تا وب سرور آزمایشی React، آنرا در آدرس http://localhost:3000 قابل دسترسی کند و در دیگری دستور dotnet watch run را صادر میکنیم تا برنامهی وب ASP.NET Core را بر روی آدرس https://localhost:5001 مهیا کند. سپس برای اینکه از پورت 3000 بتوان با پورت 5001 کار کرد، نیاز خواهد بود تا CORS را در برنامهی ASP.NET Core فعالسازی کنیم. در حین ارائهی نهایی برنامه نیز هر کدام را به صورت مجزا publish کرده و بعد هم خروجی نهایی پروژهی SPA را در پوشهی wwwroot برنامهی وب کپی میکنیم تا قابل دسترسی و استفاده شود. روش دیگری نیز برای یکی/ساده سازی این تجربه وجود دارد که در این مطلب به آن خواهیم پرداخت.
پیشنیاز: ایجاد یک برنامهی خالی React و ASP.NET Core
یک پوشهی خالی را ایجاد کرده و در آن دستور dotnet new react را صادر کنید، تا قالب خاص پروژههای React یکی سازی شدهی با پروژههای ASP.NET Core، یک پروژهی جدید را ایجاد کند.
همانطور که در تصویر فوق نیز مشاهده میکنید، این پروژه از دو برنامه تشکیل شدهاست:
الف) برنامهی SPA که در پوشهی ClientApp قرار گرفتهاست و شامل کدهای کامل یک برنامهی React است.
ب) برنامهی سمت سرور ASP.NET Core که یک برنامهی متداول وب، به همراه فایل Startup.cs و سایر فایلهای مورد نیاز آن است.
در ادامه نکات ویژهی ساختار این پروژه را بررسی خواهیم کرد.
تجربهی توسعهی برنامهها توسط این قالب ویژه
اکنون اگر این پروژهی وب را برای مثال با فشردن دکمهی F5 و یا اجرای دستور dotnet run، اجرا کنیم، چه اتفاقی رخ میدهد؟
- به صورت خلاصه برنامهی ASP.NET Core شروع به کار کرده و سبب ارائه همزمان برنامهی SPA نیز خواهد شد.
- پورتی که برنامهی وب بر روی آن قرار دارد، با پورتی که برنامهی React بر روی روی آن ارائه میشود، یکی است. یعنی نیازی به تنظیمات CORS را ندارد.
- در این حالت اگر در برنامهی React تغییری را ایجاد کنیم (در هر قسمتی از آن)، hot reloading آن هنوز هم برقرار است و سبب بارگذاری مجدد برنامهی SPA در مرورگر خواهد شد و برای اینکار نیازی به توقف و راه اندازی مجدد برنامهی ASP.NET Core نیست.
اما این تجربهی روان کاربری و توسعه، چگونه حاصل شدهاست؟
بررسی ساختار فایل Startup.cs یک پروژهی مبتنی بر dotnet new react
برای درک نحوهی عملکرد این قالب ویژه، نیاز است از فایل Startup.cs آن شروع کرد.
در ابتدا تعریف فضای نام SpaServices را مشاهده میکنید. بستهی متناظر با آن در فایل csproj برنامه به صورت زیر ثبت شدهاست:
این بسته، همان بستهی جدید SpaServices است و در NET 5x. نیز پشتیبانی خواهد شد .
در متد ConfigureServices، ثبت سرویسهای مرتبط با فایلهای استاتیک پروژهی SPA، توسط متد AddSpaStaticFiles صورت گرفتهاست. در اینجا RootPath آن، به پوشهی ClientApp/build اشاره میکند. البته این پوشه هنوز در این ساختار، قابل مشاهده نیست؛ اما زمانیکه پروژهی ASP.NET Core را برای ارائهی نهایی، publish کردیم، به صورت خودکار ایجاد شده و حاوی فایلهای قابل ارائهی برنامهی React نیز خواهد بود.
قسمت مهم دیگر کلاس آغازین برنامه، متد Configure آن است:
در اینجا ثبت سه میان افزار جدید را مشاهده میکنید:
- متد UseSpaStaticFiles، سبب ثبت میانافزاری میشود که امکان دسترسی به فایلهای استاتیک پوشهی ClientApp حاوی برنامهی React را میسر میکند؛ مسیر این پوشه را در متد ConfigureServices تنظیم کردیم.
- متد UseSpa، سبب ثبت میانافزاری میشود که دو کار مهم را انجام میدهد:
1- کار اصلی آن، ثبت مسیریابی معروف catch all است تا مسیریابیهایی را که توسط کنترلرهای برنامهی ASP.NET Core مدیریت نمیشوند، به سمت برنامهی React هدایت کند. برای مثال مسیر https://localhost:5001/api/users به یک کنترلر API برنامهی سمت سرور ختم میشود، اما سایر مسیرها مانند https://localhost:5001/login قرار است صفحهی login برنامهی سمت کلاینت SPA را نمایش دهند و متناظر با اکشن متد خاصی در کنترلرهای برنامهی وب ما نیستند. در این حالت، کار این مسیریابی catch all، نمایش صفحهی پیشفرض برنامهی SPA است.
2- بررسی میکند که آیا شرایط IsDevelopment برقرار است؟ آیا در حال توسعهی برنامه هستیم؟ اگر بله، میانافزار دیگری را به نام UseReactDevelopmentServer، اجرا و ثبت میکند.
برای درک عملکرد میانافزار ReactDevelopmentServer نیاز است به سورس آن مراجعه کرد. این میانافزار بر اساس پارامتر start ای که دریافت میکند، سبب اجرای npm run start خواهد شد. به این ترتیب دیگر نیازی به اجرای جداگانهی این دستور نخواهد بود و همچنین این اجرا، به همراه تنظیمات proxy مخصوصی نیز هست تا پورت اجرایی برنامهی React و برنامهی ASP.NET Core یکی شده و دیگر نیازی به تنظیمات CORS مخصوص برنامههای React نباشد. بنابراین hot reloading ای که از آن صحبت شد، توسط ASP.NET Core مدیریت نمیشود. در پشت صحنه همان npm run start اصلی برنامههای React، در حال اجرای وب سرور آزمایشی React است که از hot reloading پشتیبانی میکند.
یک مشکل: با این تنظیم، هربار که برنامهی ASP.NET Core اجرا میشود (به علت تغییرات در کدها و فایلهای پروژه)، سبب اجرای مجدد و پشت صحنهی react development server نیز خواهد شد که ... آغاز برنامه را در حالت توسعه، کند میکند. برای رفع این مشکل میتوان این وب سرور توسعهی برنامههای React را به صورت جداگانهای اجرا کرد و فقط تنظیمات پروکسی آنرا در اینجا ذکر نمود:
در اینجا فقط کافی است سطر UseReactDevelopmentServer را با تنظیم UseProxyToSpaDevelopmentServer که به آدرس وب سرور توسعهی برنامههای React اشاره میکند، تنظیم کنیم. بدیهی است در اینجا حالت باید از طریق خط فرمان به پوشهی clientApp وارد شد و دستور npm start را یکبار به صورت دستی اجرا کرد، تا این وب سرور، راه اندازی شود.
تغییرات ویژهی فایل csproj برنامه
اگر به فایل csproj برنامه دقت کنیم، دو تغییر جدید نیز در آن قابل مشاهده هستند:
الف) نصب خودکار وابستگیهای برنامهی client
در این تنظیم، در حالت build و debug، ابتدا بررسی میکند که آیا پوشهی node_modules برنامهی SPA وجود دارد؟ اگر خیر، ابتدا مطمئن میشود که node.js بر روی سیستم نصب است و سپس دستور npm install را صادر میکند تا تمام وابستگیهای برنامهی client، دریافت و نصب شوند.
ب) یکی کردن تجربهی publish برنامهی ASP.NET Core با publish پروژههای React
میانافزار ReactDevelopmentServer کار اجرا و پروکسی دستور npm run start را در حالت توسعه انجام میدهد. اما در حالت ارائهی نهایی چطور؟ در اینجا نیاز است دستور npm run build اجرا شده و فایلهای مخصوص ارائهی نهایی برنامهی React تولید و سپس به پوشهی wwwroot، کپی شوند. تنظیم فوق، دقیقا همین کار را در حین publish برنامهی ASP.NET Core، به صورت خودکار انجام میدهد و شامل این مراحل است:
- ابتدا npm install را جهت اطمینان از به روز بودن وابستگیهای برنامه مجددا اجرا میکند.
- سپس npm run build را برای تولید فایلهای قابل ارائهی برنامهی React اجرا میکند.
- در آخر تمام فایلهای پوشهی ClientApp/build تولیدی را به بستهی نهایی توزیعی برنامهی ASP.NET Core، اضافه میکند.
پیشنیاز: ایجاد یک برنامهی خالی React و ASP.NET Core
یک پوشهی خالی را ایجاد کرده و در آن دستور dotnet new react را صادر کنید، تا قالب خاص پروژههای React یکی سازی شدهی با پروژههای ASP.NET Core، یک پروژهی جدید را ایجاد کند.
همانطور که در تصویر فوق نیز مشاهده میکنید، این پروژه از دو برنامه تشکیل شدهاست:
الف) برنامهی SPA که در پوشهی ClientApp قرار گرفتهاست و شامل کدهای کامل یک برنامهی React است.
ب) برنامهی سمت سرور ASP.NET Core که یک برنامهی متداول وب، به همراه فایل Startup.cs و سایر فایلهای مورد نیاز آن است.
در ادامه نکات ویژهی ساختار این پروژه را بررسی خواهیم کرد.
تجربهی توسعهی برنامهها توسط این قالب ویژه
اکنون اگر این پروژهی وب را برای مثال با فشردن دکمهی F5 و یا اجرای دستور dotnet run، اجرا کنیم، چه اتفاقی رخ میدهد؟
- به صورت خلاصه برنامهی ASP.NET Core شروع به کار کرده و سبب ارائه همزمان برنامهی SPA نیز خواهد شد.
- پورتی که برنامهی وب بر روی آن قرار دارد، با پورتی که برنامهی React بر روی روی آن ارائه میشود، یکی است. یعنی نیازی به تنظیمات CORS را ندارد.
- در این حالت اگر در برنامهی React تغییری را ایجاد کنیم (در هر قسمتی از آن)، hot reloading آن هنوز هم برقرار است و سبب بارگذاری مجدد برنامهی SPA در مرورگر خواهد شد و برای اینکار نیازی به توقف و راه اندازی مجدد برنامهی ASP.NET Core نیست.
اما این تجربهی روان کاربری و توسعه، چگونه حاصل شدهاست؟
بررسی ساختار فایل Startup.cs یک پروژهی مبتنی بر dotnet new react
برای درک نحوهی عملکرد این قالب ویژه، نیاز است از فایل Startup.cs آن شروع کرد.
// ... using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; namespace dotnet_template_sample { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); // In production, the React files will be served from this directory services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; }); }
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.2" /> </ItemGroup>
در متد ConfigureServices، ثبت سرویسهای مرتبط با فایلهای استاتیک پروژهی SPA، توسط متد AddSpaStaticFiles صورت گرفتهاست. در اینجا RootPath آن، به پوشهی ClientApp/build اشاره میکند. البته این پوشه هنوز در این ساختار، قابل مشاهده نیست؛ اما زمانیکه پروژهی ASP.NET Core را برای ارائهی نهایی، publish کردیم، به صورت خودکار ایجاد شده و حاوی فایلهای قابل ارائهی برنامهی React نیز خواهد بود.
قسمت مهم دیگر کلاس آغازین برنامه، متد Configure آن است:
// ... using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; namespace dotnet_template_sample { public class Startup { // ... public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseStaticFiles(); app.UseSpaStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller}/{action=Index}/{id?}"); }); app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseReactDevelopmentServer(npmScript: "start"); } }); } } }
- متد UseSpaStaticFiles، سبب ثبت میانافزاری میشود که امکان دسترسی به فایلهای استاتیک پوشهی ClientApp حاوی برنامهی React را میسر میکند؛ مسیر این پوشه را در متد ConfigureServices تنظیم کردیم.
- متد UseSpa، سبب ثبت میانافزاری میشود که دو کار مهم را انجام میدهد:
1- کار اصلی آن، ثبت مسیریابی معروف catch all است تا مسیریابیهایی را که توسط کنترلرهای برنامهی ASP.NET Core مدیریت نمیشوند، به سمت برنامهی React هدایت کند. برای مثال مسیر https://localhost:5001/api/users به یک کنترلر API برنامهی سمت سرور ختم میشود، اما سایر مسیرها مانند https://localhost:5001/login قرار است صفحهی login برنامهی سمت کلاینت SPA را نمایش دهند و متناظر با اکشن متد خاصی در کنترلرهای برنامهی وب ما نیستند. در این حالت، کار این مسیریابی catch all، نمایش صفحهی پیشفرض برنامهی SPA است.
2- بررسی میکند که آیا شرایط IsDevelopment برقرار است؟ آیا در حال توسعهی برنامه هستیم؟ اگر بله، میانافزار دیگری را به نام UseReactDevelopmentServer، اجرا و ثبت میکند.
برای درک عملکرد میانافزار ReactDevelopmentServer نیاز است به سورس آن مراجعه کرد. این میانافزار بر اساس پارامتر start ای که دریافت میکند، سبب اجرای npm run start خواهد شد. به این ترتیب دیگر نیازی به اجرای جداگانهی این دستور نخواهد بود و همچنین این اجرا، به همراه تنظیمات proxy مخصوصی نیز هست تا پورت اجرایی برنامهی React و برنامهی ASP.NET Core یکی شده و دیگر نیازی به تنظیمات CORS مخصوص برنامههای React نباشد. بنابراین hot reloading ای که از آن صحبت شد، توسط ASP.NET Core مدیریت نمیشود. در پشت صحنه همان npm run start اصلی برنامههای React، در حال اجرای وب سرور آزمایشی React است که از hot reloading پشتیبانی میکند.
یک مشکل: با این تنظیم، هربار که برنامهی ASP.NET Core اجرا میشود (به علت تغییرات در کدها و فایلهای پروژه)، سبب اجرای مجدد و پشت صحنهی react development server نیز خواهد شد که ... آغاز برنامه را در حالت توسعه، کند میکند. برای رفع این مشکل میتوان این وب سرور توسعهی برنامههای React را به صورت جداگانهای اجرا کرد و فقط تنظیمات پروکسی آنرا در اینجا ذکر نمود:
// replace spa.UseReactDevelopmentServer(npmScript: "start"); // with spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
تغییرات ویژهی فایل csproj برنامه
اگر به فایل csproj برنامه دقت کنیم، دو تغییر جدید نیز در آن قابل مشاهده هستند:
الف) نصب خودکار وابستگیهای برنامهی client
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') "> <!-- Ensure Node.js is installed --> <Exec Command="node --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> </Target>
ب) یکی کردن تجربهی publish برنامهی ASP.NET Core با publish پروژههای React
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> <!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" /> <!-- Include the newly-built files in the publish output --> <ItemGroup> <DistFiles Include="$(SpaRoot)build\**" /> <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"> <RelativePath>%(DistFiles.Identity)</RelativePath> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <ExcludeFromSingleFile>true</ExcludeFromSingleFile> </ResolvedFileToPublish> </ItemGroup> </Target>
- ابتدا npm install را جهت اطمینان از به روز بودن وابستگیهای برنامه مجددا اجرا میکند.
- سپس npm run build را برای تولید فایلهای قابل ارائهی برنامهی React اجرا میکند.
- در آخر تمام فایلهای پوشهی ClientApp/build تولیدی را به بستهی نهایی توزیعی برنامهی ASP.NET Core، اضافه میکند.
کار با Areas را تا ASP.NET MVC 5.x میتوانید در مطلب «ASP.NET MVC #14» مطالعه کنید. در ASP.NET Core، کلیات آن ثابت ماندهاست و تنظیمات ابتدایی آن اندکی تغییر کردهاند.
مفهوم Areas
Areas یکی از روشهای ساماندهی برنامههای بزرگ، به نواحی کوچکتری مانند قسمتهای مدیریتی، پشتیبانی از کاربران و غیره است. به این ترتیب میتوان کنترلرها، Viewها و مدلهای هر قسمت را از قسمتی دیگر، جدا کرد و مدیریت پروژه را سادهتر نمود. هر Area دارای ساختار پوشههای مرتبط به خود میباشد و به این نحو است که جداسازی این نواحی مختلف را میسر میکند؛ تا بهتر مشخص باشد که هر المانی متعلق است به چه ناحیهای. به علاوه در این حالت میتوان پروژه را بین چندین توسعه دهندهی مختلف نیز تقسیم کرد؛ بدون اینکه در کار یکدیگر تداخلی ایجاد کنند.
ایجاد Areas
اگر با ASP.NET MVC 5.x کار کرده باشید، میدانید که ویژوال استودیو با کلیک راست بر روی پروژهی جاری، گزینه افزودن یک Area جدید را به همراه دارد. یک چنین قابلیتی تا ASP.NET Core 1.1 به ابزارهای همراه آن افزوده نشدهاست. بنابراین تمام مراحل ذیل را باید دستی ایجاد کنید و هنوز قالب از پیش تعریف شده و ساده کنندهای برای اینکار وجود ندارد:
همانطور که در تصویر نیز ملاحظه میکنید، نیاز است در ریشهی پروژه، پوشهی جدیدی را به نام Areas ایجاد کرد. سپس در داخل این پوشه میتوان نواحی مختلفی را با پوشه بندیهای مجزایی ایجاد نمود. برای مثال در اینجا ناحیهی Blog ایجاد شدهاست که در این ناحیه نیز پوشههای Controllers و Views آن باید به صورت دستی ایجاد شوند.
افزودن مسیریابی مرتبط با Areas
پس از اضافه کردن دستی پوشههای Areas و ناحیهی جدید، به همراه ساختار پوشههای کنترلرها و Viewهای آن، اکنون نیاز است این ناحیهی جدید را به سیستم مسیریابی معرفی نمود. برای این منظور به فایل آغازین برنامه مراجعه کرده و در متد Configure آن، تعریف جدید ذیل را اضافه میکنیم:
مسیریابی پیشفرض را در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 9 - بررسی تغییرات مسیریابی» پیشتر بررسی کردهایم. در اینجا پیش از این مسیریابی، مسیریابی جدید areas تعریف شدهاست. قید exists در اینجا به معنای تنها استفادهی از نواحی تعریف شدهی در برنامهی جاری است و الگوی تعریف شده، تمام آنها را شامل میشود و دیگر نیازی به تعریف مسیریابی جداگانهای به ازای ایجاد هر Area جدید نیست (برخلاف ASP.NET MVC 5.x).
علامتگذاری کنترلرهای یک ناحیه
تمام اصول کار کردن با کنترلرهای یک ناحیه، مانند سایر کنترلرهای دیگر برنامهاست؛ با یک تفاوت:
در ASP.NET Core حتما نیاز است توسط ویژگی جدید Area، نام ناحیهی کنترلر را صریحا مشخص کرد؛ در غیراینصورت، صرفنظر از محل تعریف این کنترلر، اطلاعات آن متعلق به هیچ ناحیهای نبوده و وارد سیستم مسیریابی Areas نمیشود.
یک نکته: اگر از Attribute routing استفاده میکنید، توکن مرتبط با نواحی، [area] نام دارد:
فعالسازی Layout و Tag Helpers در Areas
اگر در همین حال، برنامه را اجرا و به مسیر http://localhost/blog مراجعه کنید، هرچند اطلاعات View متناظر با کنترلر Home و اکشن متد Index آن نمایش داده میشوند، اما این View نه layout دارد و نه Tag helpers آن پردازش شدهاند. برای فعالسازی این دو مورد، دو فایل ViewStart.cshtml_ و ViewImports.cshtml_ را از پوشهی views اصلی پروژه، به پوشهی views این Area جدید کپی کنید. فایل ViewStart، نام و مسیر فایل layout پیش فرض ناحیه را مشخص میکند و فایل ViewImports حاوی تعاریف فعالسازی Tag helpers است:
هر Area میتواند layout خاص خودش را داشته باشد؛ اما اگر فایل ViewStart آن به نحو ذیل مقدار دهی شود، به فایل اصلی واقع در پوشهی Views/Shared/_Layout.cshtml ریشهی پروژه، اشاره میکند.
و برای تغییر و یا مقداردهی صریح آن میتوان به صورت ذیل عمل کرد:
Areas و تاثیر آنها در حین لینک دهی به قسمتهای مختلف برنامه
اگر قرار است لینکی به قسمتی واقع در همان Area جاری مرتبط شود، نیازی نیست تا هیچ نکتهی خاصی را درنظر گرفت و تولید لینکها به نحو صحیحی صورت میگیرند:
اما اگر میخواهیم به ناحیهی جدیدی به نام Services و کنترلر و اکشن متد خاصی از آن، از یک ناحیهی دیگر لینک دهیم، نیاز است asp-area را صریحا ذکر کرد:
به علاوه اگر قصد تعریف لینکی را به یک اکشن متد واقع در کنترلری که در هیچ ناحیهای قرار ندارد، داشته باشیم، باید asp-area آنرا خالی ذکر کنیم:
تاثیر Areas بر روی تنظیمات توزیع برنامه
فایلهای View موجود در Areas نیز باید در حین توزیع نهایی برنامه ارائه شوند؛ مگر اینکه آنها را از پیش کامپایل کرده باشیم. اگر از حالت از پیش کامپایل کردن Viewها استفاده نمیشود، نیاز است قسمت publishOptions فایل project.json را به نحو ذیل در جهت الحاق فایلهای Viewهای نواحی مختلف، ویرایش و تکمیل کرد:
مفهوم Areas
Areas یکی از روشهای ساماندهی برنامههای بزرگ، به نواحی کوچکتری مانند قسمتهای مدیریتی، پشتیبانی از کاربران و غیره است. به این ترتیب میتوان کنترلرها، Viewها و مدلهای هر قسمت را از قسمتی دیگر، جدا کرد و مدیریت پروژه را سادهتر نمود. هر Area دارای ساختار پوشههای مرتبط به خود میباشد و به این نحو است که جداسازی این نواحی مختلف را میسر میکند؛ تا بهتر مشخص باشد که هر المانی متعلق است به چه ناحیهای. به علاوه در این حالت میتوان پروژه را بین چندین توسعه دهندهی مختلف نیز تقسیم کرد؛ بدون اینکه در کار یکدیگر تداخلی ایجاد کنند.
ایجاد Areas
اگر با ASP.NET MVC 5.x کار کرده باشید، میدانید که ویژوال استودیو با کلیک راست بر روی پروژهی جاری، گزینه افزودن یک Area جدید را به همراه دارد. یک چنین قابلیتی تا ASP.NET Core 1.1 به ابزارهای همراه آن افزوده نشدهاست. بنابراین تمام مراحل ذیل را باید دستی ایجاد کنید و هنوز قالب از پیش تعریف شده و ساده کنندهای برای اینکار وجود ندارد:
همانطور که در تصویر نیز ملاحظه میکنید، نیاز است در ریشهی پروژه، پوشهی جدیدی را به نام Areas ایجاد کرد. سپس در داخل این پوشه میتوان نواحی مختلفی را با پوشه بندیهای مجزایی ایجاد نمود. برای مثال در اینجا ناحیهی Blog ایجاد شدهاست که در این ناحیه نیز پوشههای Controllers و Views آن باید به صورت دستی ایجاد شوند.
افزودن مسیریابی مرتبط با Areas
پس از اضافه کردن دستی پوشههای Areas و ناحیهی جدید، به همراه ساختار پوشههای کنترلرها و Viewهای آن، اکنون نیاز است این ناحیهی جدید را به سیستم مسیریابی معرفی نمود. برای این منظور به فایل آغازین برنامه مراجعه کرده و در متد Configure آن، تعریف جدید ذیل را اضافه میکنیم:
app.UseMvc(routes => { routes.MapRoute( name: "areas", template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
علامتگذاری کنترلرهای یک ناحیه
تمام اصول کار کردن با کنترلرهای یک ناحیه، مانند سایر کنترلرهای دیگر برنامهاست؛ با یک تفاوت:
[Area("Blog")] public class HomeController : Controller { public IActionResult Index() { return View(); } }
یک نکته: اگر از Attribute routing استفاده میکنید، توکن مرتبط با نواحی، [area] نام دارد:
[Route("[area]/app/[controller]/actions/[action]/{id:weekday?}")]
فعالسازی Layout و Tag Helpers در Areas
اگر در همین حال، برنامه را اجرا و به مسیر http://localhost/blog مراجعه کنید، هرچند اطلاعات View متناظر با کنترلر Home و اکشن متد Index آن نمایش داده میشوند، اما این View نه layout دارد و نه Tag helpers آن پردازش شدهاند. برای فعالسازی این دو مورد، دو فایل ViewStart.cshtml_ و ViewImports.cshtml_ را از پوشهی views اصلی پروژه، به پوشهی views این Area جدید کپی کنید. فایل ViewStart، نام و مسیر فایل layout پیش فرض ناحیه را مشخص میکند و فایل ViewImports حاوی تعاریف فعالسازی Tag helpers است:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{ Layout = "_Layout"; }
@{ Layout = "~/Areas/Blog/Views/Shared/_Layout.cshtml"; }
Areas و تاثیر آنها در حین لینک دهی به قسمتهای مختلف برنامه
اگر قرار است لینکی به قسمتی واقع در همان Area جاری مرتبط شود، نیازی نیست تا هیچ نکتهی خاصی را درنظر گرفت و تولید لینکها به نحو صحیحی صورت میگیرند:
<a asp-action="Index" asp-controller="Home">Link</a>
<a asp-area="Services" asp-controller="Home" asp-action="Index">Go to Services’ Home Page</a>
<a asp-action="Index" asp-controller="Home" asp-area="">Link</a>
تاثیر Areas بر روی تنظیمات توزیع برنامه
فایلهای View موجود در Areas نیز باید در حین توزیع نهایی برنامه ارائه شوند؛ مگر اینکه آنها را از پیش کامپایل کرده باشیم. اگر از حالت از پیش کامپایل کردن Viewها استفاده نمیشود، نیاز است قسمت publishOptions فایل project.json را به نحو ذیل در جهت الحاق فایلهای Viewهای نواحی مختلف، ویرایش و تکمیل کرد:
"publishOptions": { "include": [ "Areas/**/*.cshtml", .... .... ]
سالهای مدیدی است که به طراحی پایگاههای sql پرداخته و تجاربی آموختهایم. کتابها و مقالات زیادی در اینباره منتشر شدهاند. از اینرو در نحوه طراحی دیتابیسهای رابطهای اطلاعات زیادی کسب و مسائل زیادی را از این راه حل نمودهایم؛ ولی با ورود دیتابیسهای NoSql و تنوع زیاد آنها و روشهای متنوعی که هر کدام از آنها به طور جداگانه دارند باعث شد تجربه سالها فعالیت و مدل ذهنی که داشتیم به یکباره تغییر کند و گاها بیشتر باعث گیج شدن میگردد. از اینرو در این مقاله سعی داریم تکنیکها مدل سازی اسناد را در دیتابیس مونگو، بررسی کنیم و مزایا و معایب هر یک را برشماریم.
در دیتابیسهای قدیم، تمرکز بر روی نوشتن بود تا با کمترین افزونگی و تکرار و رعایت اصول ACID، اطلاعات را ذخیره نماییم. ولی در حال حاضر به دلیل دسترسی به فضاهای ذخیره سازی بزرگتر و همچنین افزایش ترافیک شبکه در واکشی دیتاها، قضیه عکس شده و تمرکز دیتابیسهای NoSql بر روی خواندن میباشد. پس باید فاکتورهای مدل سازی طوری باشد تا خواندن در سریعترین حد امکان قرار بگیرد. البته مواردی چون حذف و به روزرسانی هم باید در این مورد بررسی شوند.
ارتباط اسناد با یکدیگر:
ارتباط اسناد از دو طریق امکان پذیر است:
- حالت ارجاع : شماره سند یا Object Id را شامل شده و در صورتیکه به اطلاعاتی نیاز داشتید، باید اطلاعات آن را در یک درخواست جداگانه واکشی نمایید. چون مونگو شامل جوین نبوده و جوینها باید در سطح اپلیکیشن مدیریت شوند.
{ fname:'ali', lname:'yeganeh', accounts:[454354353,3455435] }
- حالت جاسازی سند (یا اسناد تو در تو) Embed : در این حالت سند مورد نظر اطلاعات سند دیگری را در درون خود نگه میدارد. در این حالت به هیچ جوینی نیازی نیست و اطلاعات وابسته، به همراه خود سند اصلی واکشی میشوند. این نکته باید مورد توجه قرار بگیرد که مونگو یک دیتابیس غیر اتمیک هست و در صورتیکه اصل دیتا تغییر کند، تغییر یا به روزرسانی در سندهای Embed انجام نخواهد شد و در صورت نیاز باید خودتان به طور دستی آن را کنترل نمایید.
{ fname:'ali', lname:'yeganeh', accounts:[ { username:"ali", password:"123" }, { username:"reza", password:"456" } ] }
مدل هایی با ارتباط یک به یک :
در این نوع مدل سازی، دو سند داریم که یکی از آنها Principle و دیگری Dependent محسوب میشود. برای ذخیره سازی آنها عموما از حالت Embed استفاده میشود. در این حالت چون ارتباط بین دو سند به صورت یک به یک میباشد، در واقع این امکان وجود دارد تا سند مادری به طور جداگانه وجود نداشته باشد و همان سند به صورت Embed ذخیره میشود. در این حالت مشکلی از لحاظ اتمیک نبودن مونگو پیش نمیاید و ویرایش راحتتری خواهد داشت.
مدلهایی با ارتباط یک به چند:
این اسناد را میتوان به دو حالت بالا بر حسب نیازمندی سیستم ذخیره کرد. فرض کنید مثال زیر را که در سایت مونگو هم عنوان شدهاست، داریم:
book { name:'Scarlet Letter", Language:"English", Pages:124, ... } publisher { name : "Orielly", ... }
book { name:'Scarlet Letter", Language:"English", Pages:124, ..., publisher: { name : "Orielly", ... } }
نکات مثبت:
- در این حالت در صورتیکه واکشی هر کتاب به همراه اطلاعات ناشر را نیاز داشته باشیم و یا پرس وجوهای ترکیبی نیاز باشد، در سریعترین زمان ممکن واکشی انجام خواهد شد.
- درج و مدیریت آن راحتتر خواهد بود.
نکات منفی:
- در صورتیکه اطلاعات ناشر نیاز به تغییرات اساسی داشته باشد و باید در تمامی سندها اصلاح گردد، باید تمامی اسناد مربوط به اطلاعات کتاب به روزرسانی شوند که هزینه سنگینتری را خواهد داشت.
- دیتای تکراری زیادی ذخیره خواهد شد و در نتیجه حافظه بیشتری را میطلبد.
- در صورتیکه تنها به اطلاعات ناشر نیاز باشد و اطلاعات ناشر در سند دیگری وجود نداشته باشد و فقط در سند کتاب وجود داشته باشد، واکشی آن هزینه سنگینتری را خواهد طلبید. به همین جهت توصیه میشود در صورتیکه دیتای شما میتواند به صورت یک موجودیت مستقل هم عمل کند، اطلاعات آن در سند دیگری که من به آن سند اصلی میگویم ذخیره شوند تا نمونهها از روی آخرین ویرایش آن ساخته شوند و موقعیکه تنها به واکشی آن اطلاعات نیاز است، همانها بیرون کشیده شوند.
در روشی دیگری میتوان ارجاعی از ناشر را به شکل زیر در کتاب نگهداری کرد:
book { name:'Scarlet Letter", Language:"English", Pages:124, ..., publisher:1212121 }
- عدم وجود تکرار اطلاعات
- چون تنها یک سند برای ویرایش وجود دارد، نیازی به اصلاح اسناد توکار نیست و ویرایش، هزینه کمتری خواهد داشت.
نکات منفی:
- عدم وجود جوین: در صورتیکه نیاز به جوین بزرگی باشد، این نوع جوین باید در سطح برنامه شما انجام شود و هزینه بر خواهد بود.
نگهداری نام کتابها در ناشر
انعطاف مونگو برای ایجاد مدل، گزینههای زیادی را پیش رو میگذارد و واقعا مدلسازی را بیشتر از قبل، چالش برانگیز میکند. در حالت دیگر میتوان اطلاعات کتاب را به صورت ارجاع، در سند ناشر نگهداری کرد. به عنوان مثال زمانیکه نیاز داریم کتب منتشرشده یک ناشر را ببینیم، شاید این گزینه بهتر باشد. البته در این حالت باید بتوان ارجاعات به کتاب را در تعداد محدودی نگهداری کرد؛ در غیر این صورت با تعداد زیادی ارجاع که شاید هیچگاه نیازی هم به آنها نیست، خواهیم رسید و در این حالت شاید ارجاع به ناشر در سند کتاب بسیار بهتر به نظر برسد. البته میتوان در این حالت ناشر تنها به تعداد معدودی از آخرین کتابهایش دسترسی داشته باشد تا کاربر بتواند آخرین کتابهای منتشر شدهی ناشر را ببیند.
حال با اطلاعات بالا چگونه مدلسازی کنیم؟
همانطور که گفتیم ابتدا تمرکز شما باید برای خواندن اطلاعات باشد و سپس معیارهایی چون به روزرسانی نیز بررسی گردند. به عنوان نمونه اطلاعات یک پست در وبلاگ را در نظر بگیرید. این سند شامل سندهای توکاری چون دسته بندی، اطلاعات نویسنده، معیارهایی چون امتیازدهی و بخش نظرات میباشد. در این حالت چون همه عناصر قرار است با یکدیگر بیرون کشیده شوند و در واقع تنها با یک سند سروکار داریم، کار بسیار سریعتر و راحتتر است. پس این ساختار گزینه مناسبی برای نمایش است:
Post { title:"C#", body:"About C#", tags:['C#','.Net','microsoft'], Categories:[{name:'Programming'}], votes:[{rate:3,user:42342},{rate:5,user:423445},...], comments:[ { text:"my comment1", time:"10/2/1396",...}, ... ] }
حال این تصور را داشته باشید که ما تنها یک پست را نشان نمیدهیم و بلکه پستها به صورت یک لیست قرار است نمایش داده شوند و با گزینهی مشاهدهی مطلب میتوانیم یک پست را به صورت کامل ببینیم. در این صورت همه اطلاعات همانند قبل هستند، بجز بخش نظرات که دیگر در این حالت کاربردی ندارد و دیتای اضافی است که به ناچار باید خوانده شود. پس در این حالت میگوییم این مدل برای خواندن مناسب نیست، چون باید تمام نظرات اسنادی که در لیست قرار دارند هم خوانده شوند. پس باید بخش نظرات را از سند پست وبلاگ جدا کنیم.
{ POST:45453, count:35, comments:[...] }
سپس میگوییم هر سند نهایتا 16 مگابایت اطلاعات را نگهداری میکند و هم اینکه تعداد نظرات ممکن است بسیار زیاد باشند. پس هر سند را به تعدادی نظر محدود میکنیم به این حالت میگویند داریم یک Bucket میسازیم و مثلا هر باکت را به 100 کامنت محدود میکنیم. تا به الان وضعیت طراحی بهتری نسبت به قبل پیدا کردیم:
{ post:345345, capacity:100, count:35, bucket:2, comments:[...] }
نکاتی که باید در حین طراحی در نظر بگیرید:
- همیشه به این نکته توجه داشته باشید که نباید بگذارید تعداد آرایههای یک سند خیلی بزرگ شوند. در غیر اینصورت کارآیی مونگو به خصوص در حین ویرایش سند پایین خواهد آمد. در حین ویرایش، اگر سندی از اندازهی خود بزرگتر نشود، مشکلی پیش نمیاید ولی اگر فضایی بیش از آنچه که قبلا داشته به آن اضافه شود، سند نیاز به جابجایی و گسترش فضا خواهد داشت. در این حالت باید مونگو سند را به جای دیگری که فضای کافی برای آن وجود دارد، انتقال بدهد و میزان Disk Fragment به طبع بالا خواهد رفت. همچنین اندیسهای آرایهای هم با جابجا شدن دیتا نیاز به، به روزرسانی خواهند داشت و زمانی هم صرف به روزرسانی اندیسها خواهد شد.
- مدیر محصول مونگو اظهار نظر صریحی در این مورد نکردهاست، ولی به نظر میرسد نوع فرمت BSON از یک اسکن خطی در حافظه استفاده میکند و زمان بیشتری صرف پیدا کردن المانهای انتهایی در آرایه خواهد شد؛ پس بیشتر عملیات در این نوع سند، با کندی مواجه خواهند شد. با توجه به کامنتهایی که در سایتها و شبکههای اجتماعی یافت شدهاست، آرایه ای با بیش از صدهزار آیتم ساده میتواند آسیب زا باشد؛ به همین دلیل توصیه میشود که اگر بیش از صدهزار آیتم نیاز است، از همان حالت Bucket استفاده شود.
- استفاده از اندیسها هم سابقهی دیرینهای داشته و سعی کنید کوئری هایی بزنید که بر اساس اندیسهای تعریف شده باشند تا واکشی دیتا سریعتر شود. پس نحوه کوئری نویسی و انتخاب فیلدی که اندیس میشود بسیار مهم است.
- استفاده از Projection تاثیری بر خواندن اسناد ندارد و هر سند به طور کامل واکشی میشود. projection تنها در بارهی ترافیک یا انتقال حجم کمتری از اطلاعات به سمت کلاینت تاثیرگذار میباشد. پس استفاده از projection بجای جدا سازی اسناد را دنبال نکنید.
- میتوان دسترسی داشت. پس از لاگین، برنامه را در چند برگهی مجزا باز کنید، باز هم کار میکند و کاربر جاری تشخیص داده میشود. این محدودیت دسترسی مربوط به دومین جاری برنامه است و نه برگههای آن (مفهوم پیاده سازی sand box یا قرنطینهی امنیتی).
- در مورد جزئیات محلهای ذخیره سازی:
- در مورد علت استفادهی از local storage و مزایا و معایب آن (نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن ) در اینجا : معرفی JSON Web Token
کلیات و نکات آن با مطلب «افزودن و اعتبارسنجی خودکار Anti-Forgery Tokens در برنامههای Angular مبتنی بر ASP.NET Core» یکی است:
- ابتدا باید هدر مورد انتظار را مشخص کنید:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAntiforgery(options => options.HeaderName = "__RequestVerificationToken"); }
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf @functions{ public string GetAntiXsrfRequestToken() { return Xsrf.GetAndStoreTokens(Context).RequestToken; } }
<script type="text/javascript"> $.ajaxPrefilter(function (options, originalOptions, jqXHR) { jqXHR.setRequestHeader("__RequestVerificationToken", '@GetAntiXsrfRequestToken()'); }); </script>