من در پروژم Controller ها رو در یک لایه و Viewها را در لایهی دیگری دارم. از T4MVC هم در لایهی Controller استفاده کردم. اما بعد از زدن Run Custom Tool مسیرهایی که منتهی به یک view میشه ساخته نمیشه و Error میده! (آنهایی که RedirectToAction هستند به درستی ساخته میشه)
VDoc.Web.Controllers.TopicController.ViewsClass' does not contain a definition for '_Edit' and no extension method '_Edit' accepting a first argument of type 'VDoc.Web.Controllers.TopicController.ViewsClass' could be found (are you missing a using directive or an assembly reference?)
آیا برای استفاده از T4 در لایههای مختلف باید کار خاصی انجام بدم؟
نظرات مطالب
مروری بر قابلیت جدید ASP.NET FriendlyUrls
سلام؛ من کتابخانه رو به پروژه اضافه کردم. اما برای فعال سازیش به مشکل برخوردم. EnableFriendlyUrls رو نمیشناسه و خطا میده
System.Web.Routing.RouteCollection' does not contain a definition for 'EnableFriendlyUrls' and no extension method 'EnableFriendlyUrls' accepting a first argument of type 'System.Web.Routing.RouteCollection' could be found (are you missing a using directive or an assembly reference?)
استفاده هم زمان این روش با url routing که مشکلی به وجود نمیاره؟
نظرات مطالب
یکی کردن اسمبلیهای یک پروژهی WPF
راه حل بسیار جامع و ساده ای ارائه کردید که مشکلات روشهای قبل را ندارد، برنامه به خوبی اجرا میشود ولی هنگام گرفتن گزارش با استفاده از stimulsoft خطای زیر ظاهر میشود:
(ساختار try-catch نادیده گرفته میشود و یک Unhandled Exception رخ میدهد.)
The type or namespace name 'Stimulsoft' could not be found (are you missing a using directive or an assembly reference?)
چرا برای اسمبلیهای تلریک چنین مشکلی به وجود نمیآید و اینکه علاوه بر اسمبلیهای زیپ شده خود اسمبلیها نیز در فایل قرار داده شد؟
اشتراکها
سری eShopSupport
eShopSupport Series
Starting next week, I will be digging into the eShopSupport codebase and blogging about it. Since it isn’t a RAG application - I don’t think it fits into the RAG Demo Series so I am going to be creating a new blog series focusing on this one codebase.
I have been waiting for the eShopSupport repo to be made public ever since I watched Steve’s NDC talk so I am really looking forward to this! Stay tuned.
Blogs in this series (to be updated as they get published):
DataGenerator Project
DataIngestor Project
Evaluator Project
Aspire Projects (AppHost and ServiceDefaults)
… more coming soon.
معرفی کامپوننت Error Boundaries در Blazor 6x
شبیه به کامپوننت Alert ای که در اینجا ملاحظه میکنید و امکان دسترسی به آن توسط یک CascadingValue در سایر کامپوننتها میسر شده، در Blazor 6x، کامپوننت جدید ErrorBoundary اضافه شدهاست که میتوان همانند مثال فوق، آنرا در بالاترین سطح ممکن در فایل MainLayout.razor، جهت محصور سازی Body به صورت زیر معرفی کرد:
<ErrorBoundary> <ChildContent> @Body </ChildContent> <ErrorContent> <p>Whoa, sorry about that! While we fix this problem, buy some shirts!</p> </ErrorContent> </ErrorBoundary>
روش دسترسی به اصل استثناء: قالب محتوای آن به صورت جنریک، یعنی <RenderFragment<Exception تعریف شدهاست. بنابراین میتوان به اصل استثنای رخ داده به صورت زیر دسترسی یافت:
<ErrorContent Context="exception"> @exception </ErrorContent>
روش پاک کردن استثنای نمایش داده شده: اطلاعات نمایش داده شده چون در بالاترین سطح (در layout برنامه) رندر میشوند، تا زمانیکه مرورگر باز است به همان نحو باقی میمانند و تغییر نمیکنند. البته خود این کامپوننت به همراه خاصیت MaximumErrorCount است که به صورت پیشفرض به 100 تنظیم شدهاست و پس از وقوع 100 استثناء، ریست میشود. اگر میخواهید این ریست، پس از هدایت به صفحات دیگر صورت گیرد و در صفحهی جدید، خطای صفحهی قبلی را مشاهده نکنید، خودتان باید به صورت دستی آنرا ریست کنید:
<ErrorBoundary @ref="errorBoundary"> @Body </ErrorBoundary> @code { ErrorBoundary errorBoundary; protected override void OnParametersSet() { // On each page navigation, reset any error state errorBoundary?.Recover(); } }
پ.ن.
این کامپوننت جدید از امکان مشابهی در React ایده گرفته شدهاست.
In Defense of Lazy Loading
I don’t know how this happened but for the last couple years (at least), whenever I read an author who writes about ORMs, I often see a sentiment like this: “ORMs are fine, just make sure you disable this pesky feature called Lazy Loading”.
It’s like this feature is not even needed and only brings confusion and performance issues to everyone who chooses to use it. Well, as you may guess from the title of this article, I disagree with this point of view completely.
عنوان میشود که HTML over Web socket آیندهی توسعهی برنامههای وب است و این آینده هم اکنون توسط Blazor Server در دسترس است. در این مدل توسعه، ابتدا یک اتصال SignalR برقرار شده و سپس تمام تعاملات بین سرور و کلاینت، از طریق همین اتصال که عموما web socket است، مدیریت میشود. به همین جهت در ادامه قصد داریم یک پروژهی Blazor Server را تکمیل کنیم. پس از آن یک پروژهی Blazor WASM را نیز بررسی خواهیم کرد. بنابراین هر دو مدل توسعهی برنامههای Blazor را پوشش خواهیم داد. برای این منظور در ابتدا مبانی Blazor را بررسی میکنیم که در هر دو مدل یکی است.
تعریف مدل برنامه
در همان پروژهی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشهی Models را افزوده و کلاس BlazorRoom را در آن تعریف میکنیم:
سپس برای اینکه مدام نیاز به تعریف فضای نام آن در فایلهای مختلف razor. برنامه نباشد، به فایل Imports.razor_ مراجعه کرده و سطر زیر را به انتهای آن اضافه میکنیم:
برنامه را نیز توسط دستور dotnet watch run اجرا میکنیم.
Data binding یک طرفه
در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آنرا حذف میکنیم. در اینجا میخواهیم مقادیر نمونهای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات قسمت قبل)، ایجاد میکنیم:
در اینجا در ابتدا شیء Room را در قسمت قطعه کد فایل razor جاری ایجاد کرده و سپس اطلاعات آنرا با استفاده از زبان Razor نمایش دادهایم.
به این روش نمایش اطلاعات، one-way data-binding نیز گفته میشود. اما چطور میتوان یک طرفه بودن آنرا متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه میکنیم که مقدارش را از Room.Price دریافت میکند:
اکنون اگر این مقدار را تغییر دهیم، عدد جدید قیمت اتاق، به خاصیت Room.Price منعکس نمیشود و تغییری نمیکند:
Data binding دو طرفه
اکنون میخواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجهی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
ویژگی bind-value@ سبب برقراری data-binding دو طرفه میشود. یعنی در ابتدا مقدار اولیهی خاصیت Room.Price را نمایش میدهد. در ادامهی اگر کاربر، مقدار این text-box را تغییر داد، نتیجهی نهایی را به خاصیت Room.Price نیز اعمال میکند و همچنین این تغییر، سبب به روز رسانی UI نیز میشود؛ یعنی در جائیکه پیشتر مقدار اولیهی Room.Price را نمایش داده بودیم، اکنون مقدار جدید آن نمایش داده خواهد شد:
البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر میخواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، میتوان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیشفرض آن onchange است:
اکنون اگر برنامه را اجرا کرده و درون text-box اطلاعاتی را وارد کنیم، بلافاصله UI نیز به روز رسانی خواهد شد.
لیست کامل رخدادها را در اینجا میتوانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.
یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.
تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آنرا نیز در یک برچسب نمایش دهید.
در اینجا میخواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
با استفاده از bind-value@، وضعیت جاری خاصیت Room.IsActive را به یک checkbox متصل کردهایم. همچنین در ادامه توسط یک عبارت شرطی، این وضعیت را نمایش دادهایم.
بار اولی که برنامه نمایش داده میشود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی میماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
در این حالت اگر اتاقی فعال باشد، مقدار ویژگی checked، به checked و در غیراینصورت به null تنظیم میشود. به این ترتیب مشکل عدم نمایش checkbox انتخاب شده در بار اول نمایش کامپوننت جاری، برطرف میشود.
اتصال خواص مدلها به dropdownها
اکنون میخواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
برای مثال یک اتاق میتواند ویژگیهایی مانند مساحت، تعداد نفرات مجاز و غیره را داشته باشد. هدف از ویژگی جدید RoomProps، تعیین لیست این نوع موارد است.
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی میکنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
در ادامه میخواهیم این خواص را در یک dropdown نمایش دهیم. همچنین با انتخاب یک خاصیت از دراپداون، مقدار خاصیت انتخابی را در یک برچسب نیز به صورت پویا نمایش خواهیم داد:
همانطور که مشاهده میکنید، انجام یک چنین کاری با Blazor بسیار سادهاست و نیازی به استفاده از جاوا اسکریپت و یا جیکوئری ندارد.
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کردهایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقهی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت optionsهای یک select استاندارد، نمایش میدهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش دادهایم که این مقدار به صورت پویا تغییر میکند:
تعریف لیستی از اتاقها
عموما در یک برنامهی واقعی، با یک تک اتاق کار نمیکنیم. به همین جهت در ادامه لیستی از اتاقها را تعریف و مقدار دهی اولیه خواهیم کرد:
در ابتدا فیلد Rooms تعریف شده که لیستی از BlazorRoomها است. در ادامه بجای مقدار دهی مستقیم آن در همان سطح قطعه کد، آنرا در یک متد life-cycle کامپوننت جاری به نام OnInitialized که مخصوص این نوع مقدار دهیهای اولیه است، مقدار دهی کردهایم.
نمایش لیست قابل ویرایش اتاقها
اکنون میخواهیم به عنوان تمرین 2، لیست جزئیات اتاقهای تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستونهایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافهتر نیز برای نمایش آنها وجود داشته باشد. به علاوه از آنجائیکه میخواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسبهایی نمایش خواهیم داد.
برای رسیدن به تصویر فوق میتوان به صورت زیر عمل کرد:
در اینجا یک حلقهی تو در تو را مشاهده میکنید. حلقهی بیرونی، ردیفهای جدول را که شامل نام و قیمت هر اتاق است، به صورت input-boxهای متصل به خواص متناظر با آنها نمایش میدهد. سپس برای اینکه بتوانیم خواص هر ردیف را نیز نمایش دهیم، حلقهی دومی را بر روی room.RoomProps تشکیل دادهایم.
هدف از foreach پس از جدول، نمایش تغییرات انجام شدهی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسبهای ذیل جدول، منعکس میشود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-04.zip
تعریف مدل برنامه
در همان پروژهی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشهی Models را افزوده و کلاس BlazorRoom را در آن تعریف میکنیم:
namespace BlazorServerSample.Models { public class BlazorRoom { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsActive { set; get; } } }
@using BlazorServerSample.Models
Data binding یک طرفه
در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آنرا حذف میکنیم. در اینجا میخواهیم مقادیر نمونهای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات قسمت قبل)، ایجاد میکنیم:
@page "/" <h2 class="bg-light border p-2"> First Room </h2> Room: @Room.Name <br/> Price: @Room.Price @code { BlazorRoom Room = new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499 }; }
به این روش نمایش اطلاعات، one-way data-binding نیز گفته میشود. اما چطور میتوان یک طرفه بودن آنرا متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه میکنیم که مقدارش را از Room.Price دریافت میکند:
<input type="number" value="@Room.Price" />
Data binding دو طرفه
اکنون میخواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجهی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
<input type="number" @bind-value="@Room.Price" />
البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر میخواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، میتوان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیشفرض آن onchange است:
<input type="number" @bind-value="@Room.Price" @bind-value:event="oninput" />
لیست کامل رخدادها را در اینجا میتوانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.
یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.
تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آنرا نیز در یک برچسب نمایش دهید.
در اینجا میخواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
<input type="checkbox" @bind-value="Room.IsActive" /> <br/> This room is @(Room.IsActive? "Active" : "Inactive").
بار اولی که برنامه نمایش داده میشود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی میماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
<input type="checkbox" @bind-value="Room.IsActive" checked="@(Room.IsActive? "cheked" : null)" />
اتصال خواص مدلها به dropdownها
اکنون میخواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
using System.Collections.Generic; namespace BlazorServerSample.Models { public class BlazorRoom { // ... public List<BlazorRoomProp> RoomProps { set; get; } } public class BlazorRoomProp { public int Id { set; get; } public string Name { set; get; } public string Value { set; get; } } }
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی میکنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
@code { BlazorRoom Room = new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "100" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "3" } } }; }
<select @bind="SelectedRoomPropValue"> @foreach (var prop in Room.RoomProps) { <option value="@prop.Value">@prop.Name</option> } </select> <span>The value of the selected room prop is: @SelectedRoomPropValue</span> @code { string SelectedRoomPropValue = ""; // ...
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کردهایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقهی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت optionsهای یک select استاندارد، نمایش میدهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش دادهایم که این مقدار به صورت پویا تغییر میکند:
تعریف لیستی از اتاقها
عموما در یک برنامهی واقعی، با یک تک اتاق کار نمیکنیم. به همین جهت در ادامه لیستی از اتاقها را تعریف و مقدار دهی اولیه خواهیم کرد:
@code { string SelectedRoomPropValue = ""; List<BlazorRoom> Rooms = new List<BlazorRoom>(); protected override void OnInitialized() { base.OnInitialized(); Rooms.Add(new BlazorRoom { Id = 1, Name = "Room 1", IsActive = true, Price = 499, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "100" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "3" } } }); Rooms.Add(new BlazorRoom { Id = 2, Name = "Room 2", IsActive = true, Price = 399, RoomProps = new List<BlazorRoomProp> { new BlazorRoomProp { Id = 1, Name = "Sq Ft", Value = "250" }, new BlazorRoomProp { Id = 2, Name = "Occupancy", Value = "4" } } }); } }
نمایش لیست قابل ویرایش اتاقها
اکنون میخواهیم به عنوان تمرین 2، لیست جزئیات اتاقهای تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستونهایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافهتر نیز برای نمایش آنها وجود داشته باشد. به علاوه از آنجائیکه میخواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسبهایی نمایش خواهیم داد.
برای رسیدن به تصویر فوق میتوان به صورت زیر عمل کرد:
<div class="border p-2 mt-3"> <h2 class="text-info">Rooms List</h2> <table class="table table-dark"> @foreach(var room in Rooms) { <tr> <td> <input type="text" @bind-value="room.Name" @bind-value:event="oninput"/> </td> <td> <input type="text" @bind-value="room.Price" @bind-value:event="oninput"/> </td> @foreach (var roomProp in room.RoomProps) { <td> @roomProp.Name, @roomProp.Value </td> } </tr> } </table> @foreach(var room in Rooms) { <p>@room.Name's price is @room.Price.</p> } </div>
هدف از foreach پس از جدول، نمایش تغییرات انجام شدهی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسبهای ذیل جدول، منعکس میشود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-04.zip
LINQPad یک برنامهی نیمه رایگان است. به این معنا که دریافت آن رایگان است، استفاده از آن هیچ محدودیتی ندارد. فقط هنگام نوشتن کوئریها intellisense ظاهر نخواهد شد. این یک مورد رایگان نیست و برای فعال شدن آن باید مقداری هزینه کنید. کیفیت intellisense آن هم قابل مقایسه است با VS.NET و بسیار مطلوب است.
LINQPad برای تست کردن سریع عبارات LINQ فوق العاده است. با استفاده از آن بدون نیاز به VS.NET خیلی سریع و در عرض چند ثانیه میتونید عبارات LINQ خودتون رو نوشته و تست کنید. این LINQ میتونه LINQ to Objects باشه یا LINQ to SQL یا LINQ to Entities و غیره.
خلاصه چیزی شبیه به management studio مخصوص SQL Server را تصور کنید که اینبار بجای SQL نویسی، LINQ مینویسید، حاصل را نمایش میدهد؛ علاوه بر آن خروجی SQL تولیدی و حتی IL نهایی را هم نمایش میدهد که برای دیباگ بسیار مفید است.
به همراه آن یک سری مثال هم وجود دارد که جهت فراگیری LINQ یا حتی استفاده از آنها به عنوان مرجع بینظیر است.
LINQPad برای تست کردن سریع عبارات LINQ فوق العاده است. با استفاده از آن بدون نیاز به VS.NET خیلی سریع و در عرض چند ثانیه میتونید عبارات LINQ خودتون رو نوشته و تست کنید. این LINQ میتونه LINQ to Objects باشه یا LINQ to SQL یا LINQ to Entities و غیره.
خلاصه چیزی شبیه به management studio مخصوص SQL Server را تصور کنید که اینبار بجای SQL نویسی، LINQ مینویسید، حاصل را نمایش میدهد؛ علاوه بر آن خروجی SQL تولیدی و حتی IL نهایی را هم نمایش میدهد که برای دیباگ بسیار مفید است.
به همراه آن یک سری مثال هم وجود دارد که جهت فراگیری LINQ یا حتی استفاده از آنها به عنوان مرجع بینظیر است.