فیلترها در MVC
فیلتر، یک کلاس سفارشی است که شما میتوانید منطق برنامه را جهت اجرا، قبل یا بعد از اجرای یک اکشن متد، در آن پیاده سازی نمایید. فیلترها میتوانند به یک اکشن متد و یا کنترلری منتسب شوند که در ادامه با این روشها آشنا خواهید شد.
در لیست زیر انواع فیلترها و اینترفیسهایی که باید توسط کلاس سفارشی شما پیاده سازی شوند، معرفی شده است.
نوع | توضیح | فیلتر توکار | اینترفیس |
Authorization | انجام عملیات احراز هویت و سطح دسترسی، قبل از اجرای کد اکشن متد | [Authorize] و [RequireHttps] | IAuthorizationFilter |
Action | اجرای کدهایی قبل از اجرای کدهای اکشن متد | IActionFilter | |
Result | اجرای کدهایی قبل یا بعد از تولید ویو (View result) | [OutputCache] | IResultFilter |
Exception | اجرای کدهایی در صورت وجود استثنای مدیریت نشده | [HandleError] | IExceptionFilter |
در تکه کد زیر نحوهی استفاده از این فیلتر را مشاهده میکنید:
[HandleError] public class HomeController : Controller { public ActionResult Index() { //throw exception for demo throw new Exception("This is unhandled exception"); return View(); } public ActionResult About() { return View(); } public ActionResult Contact() { return View(); } }
نکته: فیلترهای اعمال شدهی به یک کنترلر، به تمام اکشن متدهای آن نیز اعمال میگردند.
در کد بالا خصیصهی HandleError به HomeController اعمال شده است. بنابراین در صورت بروز خطایی در هر کدام از اکشنها، صفحهی Error.cshtml نمایش داده خواهد شد و در تظر داشته باشید که خطاها توسط try-catch هندل نشدهاند.
باید جهت عملکرد صحیح فیلتر توکار HandleErrorAttribute، مقدار customErrors در قسمت System.web فایل web.config مساوی on باشد.
<customErrors mode="On" />
مهیا کنندگان فیلترها
بصورت پیش فرض MVC از سه طریق زیر فیلترها را جهت استفادهی در برنامه فراهم میکند:
- خصیصهی GlobalFilters.Filters برای فیلترهای سراسری
- کلاس FilterAttributeFilterProvider برای فیلترهای خصیصهای
- کلاس ControllerInstanceFilterProvider جهت افزودن کنترلر به یک وهله از FilterProviderCollection
در ادامه با نحوهی ایجاد یک فیلتر، بوسیلهی هر یک از روشهای بالا، با ذکر مثالی بیشتر آشنا خواهید شد.
ترتیب اجرای فیلترها
همانطور که در ابتدا اشاره شد، در MVC چهار نوع فیلتر معرفی شده است که امکان استفادهی از آنها بهصورت همزمان در سطح کنترلر و یا اکشن متد وجود دارد. اما ترتیب اجرای آنها متفاوت و به ترتیب زیر است:
- فیلترهای Authorization
- فیلترهای Action
- فیلترهای Result یا Response
- فیلترهای Exception
فیلترها براساس ترتیب اشاره شدهی در بالا اجرا خواهند شد. در صورتیکه چند فیلتر از یک نوع استفاده شود، جهت تقدم و تاخر در اجرا، از خاصیت Order استفاده خواهد شد. بعنوان مثال در کد زیر بدلیل خاصیت Order=1 ابتدا AuthorizationFilterB و سپس AuthorizationFilterA اجرا میشود.
[AuthorizationFilterA(Order=2)] [AuthorizationFilterB(Order=1)] public ActionResult Index() { return View(); }
public enum FilterScope { First = 0, Global = 10, Controller = 20, Action = 30, Last = 100, }
نکته: مقدار Scope فیلترهای Authorization برابر 0 و فیلترهای Exception برابر 100 میباشد.
ایجاد فیلتر سفارشی
روش اول: پیاده سازی اینترفیس یکی از انواع فیلترها و ارث بری از کلاس FilterAttribute
در این روش متدهایی که باید پیاده سازی شوند متفاوت خواهد بود. به همین جهت متدهای هر نوع بشرح زیر معرفی میشود:
- IAuthorizationFilter
// Called when authorization is required void OnAuthorization(AuthorizationContext filterContext)
- IActionFilter
// Called after the action method executes void OnActionExecuted(ActionExecutedContext filterContext) // Called before an action method executes void OnActionExecuting(ActionExecutingContext filterContext)
- IResultFilter
// Called after an action result executes void OnResultExecuted(ResultExecutedContext filterContext) // Called before an action result executes void OnResultExecuting(ResultExecutedContext filterContext)
- IException
// Called when an exception occurs void OnException(ExceptionContext filterContext)
یادآوری: همانطور که در ابتدای مقاله اشاره شد، فیلترها قبل یا بعد از اجرای اکشن متدها فراخوانی خواهند شد. بنابراین به کامنت بالای متد فیلترها دقت داشته باشید.
مثال: پیاده سازی اینترفیس IExceptionFilter و ارث بری از کلاس FilterAttribute جهت تهیهی فیلتری سفارشی از نوع Exception
class CustomErrorHandler : FilterAttribute, IExceptionFilter { public override void IExceptionFilter.OnException(ExceptionContext filterContext) { Log(filterContext.Exception); base.OnException(filterContext); } private void Log(Exception exception) { //log exception here.. } }
روش دوم: ارث بری از ActionFilterAttribute
کلاس abstract فوق دارای چهار متد زیر جهت تحریف است. همانطور که مشاهده میکنید این کلاس علاوه بر دو متد OnActionExecuted و OnActionExecuting دارای دو متد دیگر OnResultExecuting و OnResultExecuted که بهترتیب قبل و بعد خروجی (Result) اکشن متد اجرا میشوند، نیز میباشد. این نوع فیلترها عموما جنبهی استفاده عمومی داشته و میتوان از آنها جهت logging ،caching و یا authorization استفاده کرد.
// Called by MVC after the action method executes void OnActionExecuted(ActionExecutedContext filterContext) // Called by MVC before the action method executes void OnActionExecuted(ActionExecutedContext filterContext) // Called by MVC after the action result executes void OnResultExecuted(ResultExecutedContext filterContext) // Called by MVC before the action result executes void OnResultExecuting(ResultExecutingContext filterContext)
مثال: کلاس LogAttribute که از کلاس ActionFilterAttribute ارث بری کرده است، عملیات قبل و بعد از اجرای اکشن متد را لاگ میکند.
public class LogAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { Log("OnActionExecuted", filterContext.RouteData); } public override void OnActionExecuting(ActionExecutingContext filterContext) { Log("OnActionExecuting", filterContext.RouteData); } public override void OnResultExecuted(ResultExecutedContext filterContext) { Log("OnResultExecuted", filterContext.RouteData); } public override void OnResultExecuting(ResultExecutingContext filterContext) { Log("OnResultExecuting ", filterContext.RouteData); } private void Log(string methodName, RouteData routeData) { var controllerName = routeData.Values["controller"]; var actionName = routeData.Values["action"]; var message = String.Format("{0}- controller:{1} action:{2}", methodName, controllerName, actionName); Debug.WriteLine(message); } }
روش سوم: پیاده سازی داخل کنترلر
کلاس Controller میتواند هر یک از اینترفیسهای فیلترها را پیاده سازی نماید. به عبارت دیگر در هر کلاس کنترلر میتوانید متدهای زیر را تحریف نمایید.
- OnAuthorization ^
- OnException ^
- OnActionExecuting ^
- OnActionExecuted ^
- OnResultExecuting ^
- OnResultExecuted ^
روش چهارم: ارث بری از کلاس فیلترهای توکار و مهیای در MVC و تحریف متدهای آن
در کد زیر با تحریف و سفارشی سازی متد OnException مخصوص فیلتر توکار HandleError، قابلیتهای آن افزایش یافته است:
class CustomErrorHandler : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { Log(filterContext.Exception); base.OnException(filterContext); } private void Log(Exception exception) { //log exception here.. } }
رجیستر فیلترها
- سراسری:
درصورتی که قصد داشته باشید فیلتری بصورت سراسری و در کل برنامه فعال گردد باید آن را در رویداد Application_Start فایل Global.asax.cs بوسیلهی متد RegisterGlobalFilters کلاس FiterConfig رجیستر نمایید. بعد از آن فیلتر به کلیهی کنترلرها و اکشن متدها اعمال میگردد.
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); } } // FilterConfig.cs located in App_Start folder public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); // add your new custom filters filters.Add(new LogAttribute()); filters.Add(new CustomErrorHandler()); } }
در کد بالا فیلتر توکار HandleError و البته فیلترهای سفارشی دیگری نیز به صورت سراسری به تمام اکشن متدهای کنترلرها اعمال گردیده است.
- کنترلر: در صورتی که فقط بخواهید یک فیلتر به کل اکشنهای یک کنترلر اعمال گردد. همانند آنچه که در مثال ابتدایی بدان اشاره شد.
[HandleError] public class HomeController : Controller
- اکشن متد: اعمال یک فیلتر به یک اکشن متد خاص کنترلر. در کد زیر فیلتر HandleError فقط به اکشن متد Index کنترلر Home اعمال خواهد شد.
public class HomeController : Controller { [HandleError] public ActionResult Index() { return View(); } }
public class Customer { public int Id { get; set; } public string Name { get; set; } = null!; public CustomerType Type { get; set; } } public enum CustomerType { Individual, Institution, }
void ManyQueriesManyCalls() { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); var baseQuery = context.Customers.Select(customer => new { customer.Name, customer.Type, customer.Id, }); var total = baseQuery.Count(); var types = baseQuery.GroupBy(x => x.Type) .Select(x => x.Key).ToList(); var pageSize = 10; var pageIndex = 0; var results = baseQuery .OrderBy(x => x.Id) .Skip(pageSize * pageIndex) .Take(pageSize) .ToList(); Console.WriteLine($"Total:{total}, First Type: {types.First()}, First Item: {results.First().Name}"); }
SELECT COUNT(*) FROM [Customers] AS [c] SELECT [c].[Type] FROM [Customers] AS [c] GROUP BY [c].[Type] SELECT [c].[Name], [c].[Type], [c].[Id] FROM [Customers] AS [c] ORDER BY [c].[Id] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
اگر بخواهیم این سه کوئری را یکبار به سمت بانک اطلاعاتی ارسال کنیم، میتوان از همان ترفند گروه بندی مطرح شدهی در این مثال برای ترکیب کوئریها استفاده کرد:
void ManyQueriesOnCall() { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); var baseQuery = context.Customers.Select(customer => new { customer.Name, customer.Type, customer.Id, }); var pageSize = 10; var pageIndex = 0; var allTogether = baseQuery .GroupBy(x => 1) .Select(bq => new { Total = baseQuery.Count(), Types = baseQuery.GroupBy(x => x.Type) .Select(x => x.Key) .ToList(), Results = baseQuery .OrderBy(x => x.Id) .Skip(pageSize * pageIndex) .Take(pageSize) .ToList(), }) .FirstOrDefault(); Console.WriteLine($"Total:{allTogether.Total}, First Type: {allTogether.Types.First()}, First Item: {allTogether.Results.First().Name}"); }
SELECT [t0].[Key], [t1].[Type], [t2].[Name], [t2].[Type], [t2].[Id] FROM ( SELECT TOP(1) [t].[Key] FROM ( SELECT 1 AS [Key] FROM [Customers] AS [c] ) AS [t] GROUP BY [t].[Key] ) AS [t0] OUTER APPLY ( SELECT [c0].[Type] FROM [Customers] AS [c0] GROUP BY [c0].[Type] ) AS [t1] OUTER APPLY ( SELECT [c1].[Name], [c1].[Type], [c1].[Id] FROM [Customers] AS [c1] ORDER BY [c1].[Id] OFFSET @__p_1 ROWS FETCH NEXT @__pageSize_2 ROWS ONLY ) AS [t2] ORDER BY [t0].[Key], [t1].[Type], [t2].[Id]
کدهای این مثال را از اینجا میتوانید دریافت کنید: EF7ManyQueriesOneCall.zip
private static void seedDb(ApplicationDbContext context) { if (!context.Chapters.Any()) { var user1 = context.Users.Add(new User { Name = "Test User" }); context.Chapters.Add(new Chapter { Title = "Learn SQlite FTS5", Text = "This tutorial teaches you how to perform full-text search in SQLite using FTS5", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Advanced SQlite Full-text Search", Text = "Show you some advanced techniques in SQLite full-text searching", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "SQLite Tutorial", Text = "Help you learn SQLite quickly and effectively", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Handle markup in text", Text = "<p>Isn't this <font face=\"Comic Sans\">funny</font>?", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "آزمایش متن فارسی", Text = "برای نمونه تهیه شدهاست", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Exclude test 1", Text = "in the years 2018-2019 something happened.", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Exclude test 2", Text = "It was 2018 and then it was 2019", User = user1.Entity }); context.SaveChanges(); } }
ثبت اطلاعات فوق، چنین رکوردهایی را در جدول Chapters به وجود میآورد که شامل اطلاعات یونیکد، HTML ای و غیره است:
اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS به صورت مستقیم
کوئریهای Full-text در SQLite، چنین شکل کلی را دارند و توسط تابع match انجام میشوند:
select * from Chapters_FTS where Chapters_FTS match "fts5"
همانطور که مشاهده میکنید در اینجا تنها دو ستونی که ایندکس شدهاند، در خروجی نهایی ظاهر میشوند؛ اما این جدول به همراه ستونهای مخفی توکار دیگری نیز هست:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"
- Rowid با توجه به تعریفی که در قسمت قبل انجام دادیم:
CREATE VIRTUAL TABLE "Chapters_FTS" USING fts5("Text", "Title", content="Chapters", content_rowid="Id")
- تمام جداول مجازی FTS، به همراه ستون مخفی rank نیز هستند که میزان نزدیک بودن خروجی حاصل را به کوئری درخواستی مشخص میکنند. این عدد توسط تابعی به نام bm25 تهیه میشود. اگر کوئری FTS به همراه قسمت where نباشد، مقدار rank همواره نال خواهد بود. اما اگر قسمت where به همراه match قید شود، مقدار rank، مقدار از پیش محاسبه شدهی تابع توکار bm25 است. به همین جهت کار با این مقدار از پیش محاسبه شده، سریعتر از فراخوانی مستقیم متد bm25 است. برای مثال دو کوئری زیر اساسا یکی هستند؛ اما دومی سریعتر است:
select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY bm25(fts); select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY rank;
یک نکته: کوئری FTS فوق بر روی هر دو ستون title و text اجرا میشود (و یا هر ستون موجود دیگری که پیشتر ایندکس شده باشد).
اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS توسط EF Core
پس از آشنایی مقدماتی با کوئری نویسی FTS در SQLite، بر انجام یک چنین کوئری در EF Core میتوان به صورت زیر عمل کرد:
- ابتدا باید یک موجودیت بدون کلید را مطابق ستونهای مخفی و ایندکس شدهی بازگشتی تهیه کنیم:
namespace EFCoreSQLiteFTS.Entities { public class ChapterFTS { public int RowId { get; set; } public decimal? Rank { get; set; } public string Title { get; set; } public string Text { get; set; } } }
- سپس نیاز است این موجودیت بدون کلید را به EF معرفی کنیم:
namespace EFCoreSQLiteFTS.DataLayer { public class ApplicationDbContext : DbContext { //... protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<ChapterFTS>().HasNoKey().ToView(null); } //... } }
- و در آخر روش کوئری گرفتن از جدول مجازی FTS در EF Core به صورت زیر میباشد که توسط متد FromSqlRaw به صورت پارامتری (مقاوم در برابر حملات تزریق اسکیوال)، قابل انجام است:
const string ftsSql = "SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH {0}"; foreach (var chapter in context.Set<ChapterFTS>().FromSqlRaw(ftsSql, "fts5")) { Console.WriteLine($"Title: {chapter.Title}"); Console.WriteLine($"Text: {chapter.Text}"); }
بررسی قابلیتهای ویژهی کوئریهای FTS در SQLite
اکنون که با روش کلی کوئری گرفتن از جدول مجازی FTS آشنا شدیم، نکات ویژهی آنرا بررسی میکنیم و در اینجا بیشتر پارامتر ذکر شدهی پس از عملگر match تغییر خواهد کرد و مابقی قسمتهای آن ثابت و مانند قبل هستند.
بجای عملگر match میتوان از = نیز استفاده کرد
دو کوئری زیر دقیقا به یک معنا هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"; SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS = "fts5";
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER by rank;
جستجوهایی به همراه واژههایی در کنار هم
از دیدگاه FTS، دو کوئری زیر که در قسمت match آنها، واژهها با فاصله در کنار هم قرار گرفتهاند، یکی هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn SQLite" ORDER by rank; SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn + SQLite" ORDER by rank;
علت اینجا است که یک full-text search بر اساس ایندکس شدن واژهها تولید میشود و هر کدام از این واژهها به یک توکن نگاشت خواهند شد. به همین جهت است که در اینجا تفاوتی بین + و فاصله در عبارت جستجو شده وجود ندارد. در این حالت اگر در یکی از ستونهای ایندکس شده، واژهی learn و یا واژهی SQLite بکار رفته باشد، در خروجی نهایی لیست خواهد شد.
امکان جستجو بر اساس پیشوندها
میتوان با استفاده از *، تمام توکنهای ایندکس شده و شروع شدهی با واژهی مشخصی را جستجو کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search*" ORDER by rank;
امکان استفاده از عملگرهای بولی NOT، AND و OR
اگر learn text را جستجو کنیم:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn text" ORDER by rank;
رکوردی با ID مساوی 1 بازگشت داده میشود. اما اگر نیاز باشد رکوردی بازگشت داده شود که حاوی learn باشد، اما text خیر، میتوان از عملگر NOT استفاده کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn NOT text" ORDER by rank;
که اینبار رکوردی با ID مساوی 3 را بازگشت دادهاست.
نکتهی مهم: عملگرهای بولی FTS مانند AND، OR، NOT و غیره باید با حروف بزرگ قید شوند.
در ادامه مثال دیگری از ترکیب عملگرهای بولی را مشاهده میکنید:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND sqlite OR help" ORDER by rank;
که تقدم و تاخر این عملگرها را میتوان توسط پرانتزها به صورت صریحی نیز مشخص کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND (sqlite OR help)" ORDER by rank;
امکان ذکر صریح ستونهای مدنظر در کوئری
همانطور که عنوان شد، حالت پیشفرض جستجوهای تمام متنی، جستجوی واژهی مدنظر در تمام ستونهای ایندکس شدهاست؛ اما شاید این مورد مدنظر شما نباشد. به همین منظور میتوان ابتدا نام ستون مدنظر را ذکر کرد و پس از آن یک : را قرار داد تا فقط جستجو بر روی آن ستون خاص صورت گیرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "text:some AND title:sqlite" ORDER by rank;
امکان ترکیب نام ستونها به صورت {col2 col1 col3} نیز وجود دارد.
نکتهی مهم! در جستجوهای FTS در SQLite، ذکر - به معنای قید صریح نام یک ستون خاص است (و یا لیست ستونهایی به صورت {col2 col1 col3}-) که قرار نیست چیزی با آن(ها) انطباق داده شود (- شبیه به عملگر NOT عمل میکند؛ اینبار در مورد ستونها) و این مورد عموما تازهکاران را به اشتباه میاندازد. برای مثال در ابتدای بحث، دو رکورد را که دارای text ای مساوی عبارات زیر هستند، ثبت کردیم:
"in the years 2018-2019 something happened" "It was 2018 and then it was 2019"
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "2018-2019" ORDER by rank;
Execution finished with errors. Result: no such column: 2019
و یا میتوان عبارت جستجو شده را بین "" قرار داد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH '"2018-2019"' ORDER by rank;
و یا حتی میتوان '"2018 2019"' را نیز جستجو کرد که نتیجهی مشابهی را ارائه میدهد.
امکان جستجوی بر روی عبارات یونیکد
FTS5 و آخرین نگارش SQLite، به همراه tokenizer مخصوص یونیکد نیز هست و با اینگونه جستجوهای تمام متنی، مشکلی ندارد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "آزمایش" ORDER by rank;
توابع کمکی FTS در SQLite برای متمایز سازی عبارات یافت شدهی در متن
فرض کنید میخواهیم واژهی fts5 را جستجو کرده و همچنین در خروجی نهایی، هرجائیکه fts5 قرار دارد، آنرا به صورت bold نمایش دهیم. برای اینکار، تابع توکار highlight قابل استفادهاست. اما اگر در این بین خواستیم فقط قسمت کوتاهی از متن مورد نظر را که به جستجوی ما نزدیک است نمایش دهیم، میتوان از متد توکار snippet استفاده کرد:
SELECT rowid, highlight(Chapters_FTS, title, '<b>', '</b>') as title, snippet(Chapters_FTS, text, '<b>', '</b>', '...', 64) as text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER BY rank
نکتهی مهم: چون بر اساس نکات قسمت قبل، متنی که به Chapters_FTS ارسال میشود، نرمال سازی شدهاست، متدهای فوق کارآیی خودشان را از دست میدهند. برای مثال اگر در کوئری فوق، واژهی funny را که به یک رکورد HTML ای اشاره میکند، جستجو کنیم، خروجی زیر را دریافت خواهیم کرد:
خروجی نهایی، چون به جدول اصلی chapters متصل است، اصل متن را بازگشت میدهد، اما چون اطلاعاتی را که به Chapters_FTS ارسال کردهایم، فاقد تگهای HTML هستند، تا خروجی دقیقی حاصل شود، متدهای highlight و snippet دیگر قادر به علامتگذاری خروجی نهایی نبوده و اینکار را باید خودمان به صورت دستی در سمت کلاینت انجام دهیم.
WPF allows you to build modern desktop applications for Windows, and part of building an application is debugging code and optimizing performance. In Alessandro Del Sole’s WPF Debugging and Performance Succinctly, you will learn how to debug a WPF application by leveraging all the powerful tools in Visual Studio, including the most recent additions that allow you to investigate the behavior of the UI at runtime. Also, you will learn how to analyze and improve an application’s performance in order to provide your customers with the best possible experience and thereby make them happy.
- Debugging WPF Applications
- Stepping Through Code
- Working with Debug Windows
- Debugger Visualizers and Trace Listeners
- XAML Debugging
- Analyzing the UI Performances
- Analyzing the Application Performances
Input Validation for me is about validating the user input. Some people call "Name must not be empty" a business rule, I think about it as input validation. Business Rules validation is more complex, because a business rule for me is not "Name must not be empty", it is a definition of a state in the system that requires an action. Here is a definition of a business rule:
An order should be payed within 30 days, this duration can be extended, to a maximum of three times.
دوره آموزشی Blazor از مایکروسافت
SELECT * FROM table ORDER BY NEWID()
پاسخ:
یک مثال کامل را در این زمینه در ادامه ملاحظه میکنید:
using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; namespace Sample { public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } public class MyContext : DbContext { public DbSet<User> Users { get; set; } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { context.Users.Add(new User { Name = "User 1", Age = 20 }); context.Users.Add(new User { Name = "User 2", Age = 25 }); context.Users.Add(new User { Name = "User 3", Age = 30 }); context.Users.Add(new User { Name = "User 4", Age = 35 }); context.Users.Add(new User { Name = "User 5", Age = 40 }); base.Seed(context); } } public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var context = new MyContext()) { var randomListOfUsers = context.Users .Where(person => person.Age >= 25 && person.Age < 40) .OrderBy(person => Guid.NewGuid()) .ToList(); foreach (var person in randomListOfUsers) Console.WriteLine("{0}:{1}", person.Name, person.Age); } } } }
.OrderBy(person => Guid.NewGuid())
ORDER BY NEWID()
خروجی SQL تولیدی کوئری LINQ فوق را نیز در ادامه مشاهده میکنید:
SELECT [Project1].[Id] AS [Id], [Project1].[Name] AS [Name], [Project1].[Age] AS [Age] FROM ( SELECT NEWID() AS [C1], ------ Guid created here [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Age] AS [Age] FROM [dbo].[Users] AS [Extent1] WHERE ([Extent1].[Age] >= 25) AND ([Extent1].[Age] < 40) ) AS [Project1] ORDER BY [Project1].[C1] ASC ------ Used for sorting here
برنامه نویس 40 ساله
I think you might want to be a software developer for a long time, in the same way that some people are musicians for a long time, or artists for a long time, or roofers for a long time. If not, you can hit “back” in your browser. It’s cool, no harm no foul. But I think maybe you would like to be a twenty-year programmer, or forty-year, or more.
4.Visual Studio 2017 15.6 منتشر شد
These are the customer-reported issues addressed in this release:
- Assets File 'c:\xxxxxx\obj\project.assets.json' doesn't have a target for '.NETStandard,Version=v2.0'.
- VS2017 fails to persist settings on first launch if there is another VS instance running from another user.
- Android LLVM libc++ static library STL broken with VS15.6.0.
- Unit tests no longer sorted alphabetically.
- Test explorer tests grouped by class, namespace no longer sorted alphabetically in 15.6.2.
حجم تقریبی بروزرسانی از نسخه 15.6.3 به 15.6.4 برابر 400MB میباشد