شروع کار با Web Assembly
- «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid»
Kendo UI Grid دارای امکانات ثبت، ویرایش و حذف توکاری است که در ادامه نحوهی فعال سازی آنها را بررسی خواهیم کرد. مثالی که در ادامه بررسی خواهد شد، در تکمیل مطلب «فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid» است.
تنظیمات Data Source سمت کاربر
برای فعال سازی صفحه بندی سمت سرور، با قسمت read منبع داده Kendo UI پیشتر آشنا شده بودیم. جهت فعال سازی قسمتهای ثبت اطلاعات جدید (create)، به روز رسانی رکوردهای موجود (update) و حذف ردیفی مشخص (destroy) نیاز است تعاریف قسمتهای متناظر را که هر کدام به آدرس مشخصی در سمت سرور اشاره میکنند، اضافه کنیم:
var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' }, create: { url: "api/products", contentType: 'application/json; charset=utf-8', type: "POST" }, update: { url: function (product) { return "api/products/" + product.Id; }, contentType: 'application/json; charset=utf-8', type: "PUT" }, destroy: { url: function (product) { return "api/products/" + product.Id; }, contentType: 'application/json; charset=utf-8', type: "DELETE" }, //... }, schema: { //... model: { id: "Id", // define the model of the data source. Required for validation and property types. fields: { "Id": { type: "number", editable: false }, //تعیین نوع فیلد برای جستجوی پویا مهم است "Name": { type: "string", validation: { required: true } }, "IsAvailable": { type: "boolean" }, "Price": { type: "number", validation: { required: true, min: 1 } }, "AddDate": { type: "date", validation: { required: true } } } } }, batch: false, // enable batch editing - changes will be saved when the user clicks the "Save changes" button //... });
- به علاوه در اینجا به ازای هر فیلد، مباحث اعتبارسنجی نیز اضافه شدهاند؛ برای مثال فیلدهای اجباری با required: true مشخص گردیدهاند.
- اگر فیلدی نباید ویرایش شود (مانند فیلد Id)، خاصیت editable آنرا false کنید.
- در data source امکان تعریف خاصیتی به نام batch نیز وجود دارد. حالت پیش فرض آن false است. به این معنا که در حالت ویرایش، تغییرات هر ردیفی، یک درخواست مجزا را به سمت سرور سبب خواهد شد. اگر آنرا true کنید، تغییرات تمام ردیفها در طی یک درخواست به سمت سرور ارسال میشوند. در این حالت باید به خاطر داشت که پارامترهای سمت سرور، از حالت یک شیء مشخص باید به لیستی از آنها تغییر یابند.
مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات
در حالت ثبت، متد Post، توسط آدرس مشخص شده در قسمت create منبع داده گرید، فراخوانی میگردد:
namespace KendoUI06.Controllers { public class ProductsController : ApiController { public HttpResponseMessage Post(Product product) { if (!ModelState.IsValid) return Request.CreateResponse(HttpStatusCode.BadRequest); var id = 1; var lastItem = ProductDataSource.LatestProducts.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } product.Id = id; ProductDataSource.LatestProducts.Add(product); var response = Request.CreateResponse(HttpStatusCode.Created, product); response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.Id })); // گرید آی دی جدید را به این صورت دریافت میکند response.Content = new ObjectContent<DataSourceResult>( new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter()); return response; } } }
var productsDataSource = new kendo.data.DataSource({ //... schema: { data: "Data", total: "Total", } //... });
namespace KendoUI06.Controllers { public class ProductsController : ApiController { public DataSourceResult Get(HttpRequestMessage requestMessage) { var request = JsonConvert.DeserializeObject<DataSourceRequest>( requestMessage.RequestUri.ParseQueryString().GetKey(0) ); var list = ProductDataSource.LatestProducts; return list.AsQueryable() .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter); } } }
response.Content = new ObjectContent<DataSourceResult>( new DataSourceResult { Data = new[] { product } }, new JsonMediaTypeFormatter());
متدهای حذف و به روز رسانی سمت سرور نیز چنین امضایی را خواهند داشت:
namespace KendoUI06.Controllers { public class ProductsController : ApiController { public HttpResponseMessage Delete(int id) { var item = ProductDataSource.LatestProducts.FirstOrDefault(x => x.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); ProductDataSource.LatestProducts.Remove(item); return Request.CreateResponse(HttpStatusCode.OK, item); } [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT' public HttpResponseMessage Update(int id, Product product) { var item = ProductDataSource.LatestProducts .Select( (prod, index) => new { Item = prod, Index = index }) .FirstOrDefault(x => x.Item.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); if (!ModelState.IsValid || id != product.Id) return Request.CreateResponse(HttpStatusCode.BadRequest); ProductDataSource.LatestProducts[item.Index] = product; return Request.CreateResponse(HttpStatusCode.OK); } } }
تنظیمات Kendo UI Grid جهت فعال سازی CRUD
در ادامه کلیه تغییرات مورد نیاز جهت فعال سازی CRUD را در Kendo UI، به همراه مباحث بومی سازی عبارات متناظر با دکمهها و صفحات خودکار مرتبط، مشاهده میکنید:
$("#report-grid").kendoGrid({ //.... editable: { confirmation: "آیا مایل به حذف ردیف انتخابی هستید؟", destroy: true, // whether or not to delete item when button is clicked mode: "popup", // options are "incell", "inline", and "popup" //template: kendo.template($("#popupEditorTemplate").html()), // template to use for pop-up editing update: true, // switch item to edit mode when clicked? window: { title: "مشخصات محصول" // Localization for Edit in the popup window } }, columns: [ //.... { command: [ { name: "edit", text: "ویرایش" }, { name: "destroy", text: "حذف" } ], title: " ", width: "160px" } ], toolbar: [ { name: "create", text: "افزودن ردیف جدید" }, { name: "save", text: "ذخیرهی تمامی تغییرات" }, { name: "cancel", text: "لغو کلیهی تغییرات" }, { template: kendo.template($("#toolbarTemplate").html()) } ], messages: { editable: { cancelDelete: "لغو", confirmation: "آیا مایل به حذف این رکورد هستید؟", confirmDelete: "حذف" }, commands: { create: "افزودن ردیف جدید", cancel: "لغو کلیهی تغییرات", save: "ذخیرهی تمامی تغییرات", destroy: "حذف", edit: "ویرایش", update: "ثبت", canceledit: "لغو" } } });
- برای فعال سازی تولید صفحات خودکار ویرایش و افزودن ردیفها، نیاز است خاصیت editable را به نحوی که ملاحظه میکنید، مقدار دهی کرد. خاصیت mode آن سه حالت incell (پیش فرض)، inline و popup را پشتیبانی میکند.
- اگر حالتهای inline و یا popup را فعال کردید، در انتهای ستونهای تعریف شده، نیاز است ستون ویژهای به نام command را مطابق تعاریف فوق، تعریف کنید. در این حالت دو دکمهی ویرایش و ثبت، فعال میشوند و اطلاعات خود را از تنظیمات data source گرید دریافت میکنند. دکمهی ویرایش در حالت incell کاربردی ندارد (چون در این حالت کاربر با کلیک درون یک سلول میتواند آنرا مانند برنامهی اکسل ویرایش کند). اما دکمهی حذف در هر سه حالت قابل استفاده است.
- به نوار ابزار گرید، سه دکمهی افزودن ردیفهای جدید، ذخیرهی تمامی تغییرات و لغو تغییرات صورت گرفته، اضافه شدهاند. این دکمهها استاندارد بوده و در اینجا نحوهی بومی سازی پیامهای مرتبط را نیز مشاهده میکنید. همانطور که عنوان شد، دکمههای «تمامی تغییرات» در حالت فعال سازی batching در منبع داده و استفاده از incell editing معنا پیدا میکند. در سایر حالات این دو دکمه کاربردی ندارند. اما دکمهی افزودن ردیفهای جدید در هر سه حالت کاربرد دارد و یکسان است.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
KendoUI06.zip
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
app.UseSwaggerUI(x => { x.SwaggerEndpoint("/swagger/v1/swagger.yaml", "Zeipt Dashboard API"); });
معماری N-Tier چالشهای بخصوصی را برای قابلیتهای change-tracking در EF اضافه میکند. در ابتدا دادهها توسط یک آبجکت EF Context بارگذاری میشوند اما این آبجکت پس از ارسال دادهها به کلاینت از بین میرود. تغییراتی که در سمت کلاینت روی دادهها اعمال میشوند ردیابی (track) نخواهند شد. هنگام بروز رسانی، آبجکت Context جدیدی برای پردازش اطلاعات ارسالی باید ایجاد شود. مسلما آبجکت جدید هیچ چیز درباره Context پیشین یا مقادیر اصلی موجودیتها نمیداند.
در نسخههای قبلی Entity Framework توسعه دهندگان با استفاده از قالب ویژه ای بنام Self-Tracking Entities میتوانستند تغییرات موجودیتها را ردیابی کنند. این قابلیت در نسخه EF 6 از رده خارج شده است و گرچه هنوز توسط ObjectContext پشتیبانی میشود، آبجکت DbContext از آن پشتیبانی نمیکند.
در این سری از مقالات روی عملیات پایه CRUD تمرکز میکنیم که در اکثر اپلیکیشنهای n-Tier استفاده میشوند. همچنین خواهیم دید چگونه میتوان تغییرات موجودیتها را ردیابی کرد. مباحثی مانند همزمانی (concurrency) و مرتب سازی (serialization) نیز بررسی خواهند شد. در قسمت یک این سری مقالات، به بروز رسانی موجودیتهای منفصل (disconnected) توسط سرویسهای Web API نگاهی خواهیم داشت.
بروز رسانی موجودیتهای منفصل با Web API
سناریویی را فرض کنید که در آن برای انجام عملیات CRUD از یک سرویس Web API استفاده میشود. همچنین مدیریت دادهها با مدل Code-First پیاده سازی شده است. در مثال جاری یک کلاینت Console Application خواهیم داشت که یک سرویس Web API را فراخوانی میکند. توجه داشته باشید که هر اپلیکیشن در Solution مجزایی قرار دارد. تفکیک پروژهها برای شبیه سازی یک محیط n-Tier انجام شده است.
فرض کنید مدلی مانند تصویر زیر داریم.
همانطور که میبینید مدل جاری، سفارشات یک اپلیکیشن فرضی را معرفی میکند. میخواهیم مدل و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم، تا هر کلاینتی که از HTTP استفاده میکند بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe1.Service تغییر دهید.
- کنترلر جدیدی از نوع WebApi Controller با نام OrderController به پروژه اضافه کنید.
- کلاس جدیدی با نام Order در پوشه مدلها ایجاد کنید و کد زیر را به آن اضافه نمایید.
public class Order { public int OrderId { get; set; } public string Product { get; set; } public int Quantity { get; set; } public string Status { get; set; } public byte[] TimeStamp { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- حال کلاسی با نام Recipe1Context ایجاد کنید و کد زیر را به آن اضافه نمایید.
public class Recipe1Context : DbContext { public Recipe1Context() : base("Recipe1ConnectionString") { } public DbSet<Order> Orders { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Order>().ToTable("Orders"); // Following configuration enables timestamp to be concurrency token modelBuilder.Entity<Order>().Property(x => x.TimeStamp) .IsConcurrencyToken() .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings> <add name="Recipe1ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به آن اضافه نمایید. این کد بررسی Entity Framework Compatibility را غیرفعال میکند.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); ... }
- در آخر کد کنترلر Order را با لیست زیر جایگزین کنید.
public class OrderController : ApiController { // GET api/order public IEnumerable<Order> Get() { using (var context = new Recipe1Context()) { return context.Orders.ToList(); } } // GET api/order/5 public Order Get(int id) { using (var context = new Recipe1Context()) { return context.Orders.FirstOrDefault(x => x.OrderId == id); } } // POST api/order public HttpResponseMessage Post(Order order) { // Cleanup data from previous requests Cleanup(); using (var context = new Recipe1Context()) { context.Orders.Add(order); context.SaveChanges(); // create HttpResponseMessage to wrap result, assigning Http Status code of 201, // which informs client that resource created successfully var response = Request.CreateResponse(HttpStatusCode.Created, order); // add location of newly-created resource to response header response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = order.OrderId })); return response; } } // PUT api/order/5 public HttpResponseMessage Put(Order order) { using (var context = new Recipe1Context()) { context.Entry(order).State = EntityState.Modified; context.SaveChanges(); // return Http Status code of 200, informing client that resouce updated successfully return Request.CreateResponse(HttpStatusCode.OK, order); } } // DELETE api/order/5 public HttpResponseMessage Delete(int id) { using (var context = new Recipe1Context()) { var order = context.Orders.FirstOrDefault(x => x.OrderId == id); context.Orders.Remove(order); context.SaveChanges(); // Return Http Status code of 200, informing client that resouce removed successfully return Request.CreateResponse(HttpStatusCode.OK); } } private void Cleanup() { using (var context = new Recipe1Context()) { context.Database.ExecuteSqlCommand("delete from [orders]"); } } }
در قدم بعدی اپلیکیشن کلاینت را میسازیم که از سرویس Web API استفاده میکند.
- در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe1.Client تغییر دهید.
- کلاس موجودیت Order را به پروژه اضافه کنید. همان کلاسی که در سرویس Web API ساختیم.
نکته: قسمت هایی از اپلیکیشن که باید در لایههای مختلف مورد استفاده قرار گیرند - مانند کلاسهای موجودیتها - بهتر است در لایه مجزایی قرار داده شده و به اشتراک گذاشته شوند. مثلا میتوانید پروژه ای از نوع Class Library بسازید و تمام موجودیتها را در آن تعریف کنید. سپس لایههای مختلف این پروژه را ارجاع خواهند کرد.
فایل program.cs را باز کنید و کد زیر را به آن اضافه نمایید.
private HttpClient _client; private Order _order; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { // create instance of the program class var program = new Program(); program.ServiceSetup(); program.CreateOrder(); // do not proceed until order is added await program.PostOrderAsync(); program.ChangeOrder(); // do not proceed until order is changed await program.PutOrderAsync(); // do not proceed until order is removed await program.RemoveOrderAsync(); } private void ServiceSetup() { // map URL for Web API cal _client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") }; // add Accept Header to request Web API content // negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept. Add(new MediaTypeWithQualityHeaderValue("application/json")); } private void CreateOrder() { // Create new order _order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" }; } private async Task PostOrderAsync() { // leverage Web API client side API to call service var response = await _client.PostAsJsonAsync("api/order", _order); Uri newOrderUri; if (response.IsSuccessStatusCode) { // Capture Uri of new resource newOrderUri = response.Headers.Location; // capture newly-created order returned from service, // which will now include the database-generated Id value _order = await response.Content.ReadAsAsync<Order>(); Console.WriteLine("Successfully created order. Here is URL to new resource: {0}", newOrderUri); } else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); } private void ChangeOrder() { // update order _order.Quantity = 10; } private async Task PutOrderAsync() { // construct call to generate HttpPut verb and dispatch // to corresponding Put method in the Web API Service var response = await _client.PutAsJsonAsync("api/order", _order); if (response.IsSuccessStatusCode) { // capture updated order returned from service, which will include new quanity _order = await response.Content.ReadAsAsync<Order>(); Console.WriteLine("Successfully updated order: {0}", response.StatusCode); } else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); } private async Task RemoveOrderAsync() { // remove order var uri = "api/order/" + _order.OrderId; var response = await _client.DeleteAsync(uri); if (response.IsSuccessStatusCode) Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode); else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); }
Successfully updated order: OK
Sucessfully deleted order: OK
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر Web API دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله اپلیکیشن در حال اجرا است و سرویسهای ما قابل دسترسی هستند.
حال اپلیکیشن کنسول را باز کنید. روی خط اول کد program.cs یک breakpoint تعریف کرده و اپلیکیشن را اجرا کنید. ابتدا آدرس سرویس Web API را پیکربندی کرده و خاصیت Accept Header را مقدار دهی میکنیم. با این کار از سرویس مورد نظر درخواست میکنیم که دادهها را با فرمت JSON بازگرداند. سپس یک آبجکت Order میسازیم و با فراخوانی متد PostAsJsonAsync آن را به سرویس ارسال میکنیم. این متد روی آبجکت HttpClient تعریف شده است. اگر به اکشن متد Post در کنترلر Order یک breakpoint اضافه کنید، خواهید دید که این متد سفارش جدید را بعنوان یک پارامتر دریافت میکند و آن را به لیست موجودیتها در Context جاری اضافه مینماید. این عمل باعث میشود که آبجکت جدید بعنوان Added علامت گذاری شود، در این مرحله Context جاری شروع به ردیابی تغییرات میکند. در آخر با فراخوانی متد SaveChanges دادهها را ذخیره میکنیم. در قدم بعدی کد وضعیت 201 (Created) و آدرس منبع جدید را در یک آبجکت HttpResponseMessage قرار میدهیم و به کلاینت ارسال میکنیم. هنگام استفاده از Web API باید اطمینان حاصل کنیم که کلاینتها درخواستهای ایجاد رکورد جدید را بصورت POST ارسال میکنند. درخواستهای HTTP Post بصورت خودکار به اکشن متد متناظر نگاشت میشوند.
در مرحله بعد عملیات بعدی را اجرا میکنیم، تعداد سفارش را تغییر میدهیم و موجودیت جاری را با فراخوانی متد PutAsJsonAsync به سرویس Web API ارسال میکنیم. اگر به اکشن متد Put در کنترلر سرویس یک breakpoint اضافه کنید، خواهید دید که آبجکت سفارش بصورت یک پارامتر دریافت میشود. سپس با فراخوانی متد Entry و پاس دادن موجودیت جاری بعنوان رفرنس، خاصیت State را به Modified تغییر میدهیم، که این کار موجودیت را به Context جاری میچسباند. حال فراخوانی متد SaveChanges یک اسکریپت بروز رسانی تولید خواهد کرد. در مثال جاری تمام فیلدهای آبجکت Order را بروز رسانی میکنیم. در شمارههای بعدی این سری از مقالات، خواهیم دید چگونه میتوان تنها فیلدهایی را بروز رسانی کرد که تغییر کرده اند. در آخر عملیات را با بازگرداندن کد وضعیت 200 (OK) به اتمام میرسانیم.
در مرحله بعد، عملیات نهایی را اجرا میکنیم که موجودیت Order را از منبع داده حذف میکند. برای اینکار شناسه (Id) رکورد مورد نظر را به آدرس سرویس اضافه میکنیم و متد DeleteAsync را فراخوانی میکنیم. در سرویس Web API رکورد مورد نظر را از دیتابیس دریافت کرده و متد Remove را روی Context جاری فراخوانی میکنیم. این کار موجودیت مورد نظر را بعنوان Deleted علامت گذاری میکند. فراخوانی متد SaveChanges یک اسکریپت Delete تولید خواهد کرد که نهایتا منجر به حذف شدن رکورد میشود.
در یک اپلیکیشن واقعی بهتر است کد دسترسی دادهها از سرویس Web API تفکیک شود و در لایه مجزایی قرار گیرد.
معرفی تعدادی آنالیز کنندهی کد که به عنوان افزونهی کامپایلر #C قابل استفاده هستند
Microsoft.CodeAnalysis.NetAnalyzers
این افزونه جزئی از SDK دات نت 5 است و نیازی به نصب مجزا را ندارد. البته اگر میخواهید نگارشهای جدیدتر آنرا پیش از یکی شدن با SDKهای بعدی مورد آزمایش قرار دهید، میتوان آنرا به صورت صریحی نیز به کامپایلر معرفی کرد. این افزونهی جایگزین FxCop است و پس از ارائهی آن، FxCop را منسوخ شده اعلام کردند.
Meziantou.Analyzer
یکسری نکات بهبود کیفیت کدها که توسط Meziantou در طی سالهای متمادی جمع آوری شدهاند، تبدیل به افزونهی فوق شدهاند.
Microsoft.VisualStudio.Threading.Analyzers
این افزونه نکاتی را در مورد مشکلات Threading موجود در کدها، گوشزد میکند.
Microsoft.CodeAnalysis.BannedApiAnalyzers
با استفاده از این افزونه میتوان استفادهی از یکسری کدها را ممنوع کرد. برای مثال استفادهی از System.DateTimeOffset.DateTime، در سراسر کدها ممنوع شده و استفادهی از System.DateTimeOffset.UtcDateTime پیشنهاد شود.
AsyncFixer و Asyncify
این دو افزونه، مشکلات متداول در حین کار با کدهای async را گوشزد میکنند.
ClrHeapAllocationAnalyzer
این افزونه مکانهایی از کد را مشخص میکنند که در آنها تخصیص حافظه صورت گرفتهاست. کاهش این مکانها میتواند به بالا رفتن کارآیی برنامه کمک کنند.
SonarAnalyzer.CSharp
مجموعهی معروف Sonar، که تعداد قابل ملاحظهای بررسی کنندهی کد را به پروژهی شما اضافه میکنند.
روش معرفی سراسری افزونههای فوق به تمام پروژههای یک Solution
میتوان تنظیمات زیر را به یک تک پروژه اعمال کرد که برای اینکار نیاز است فایل csproj آنرا ویرایش نمود و یا میتوان یک تک فایل ویژه را به نام Directory.Build.props ایجاد کرد و آنرا به صورت زیر تکمیل نمود. محل قرارگیری این فایل، در ریشهی Solution و در کنار فایل sln میباشد.
<Project> <PropertyGroup> <AnalysisLevel>latest</AnalysisLevel> <AnalysisMode>AllEnabledByDefault</AnalysisMode> <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors> <EnableNETAnalyzers>true</EnableNETAnalyzers> <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> <Nullable>enable</Nullable> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild> <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis> <!-- CA2007: Consider calling ConfigureAwait on the awaited task MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed CA1056: Change the type of property 'Url' from 'string' to 'System.Uri' CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object CA1055: Change the return type of method from 'string' to 'System.Uri' --> <NoWarn>$(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004</NoWarn> <NoError>$(NoError);CA2007;CA1056;CA1054;CA1055;MA0004</NoError> <Deterministic>true</Deterministic> <Features>strict</Features> <ReportAnalyzer>true</ReportAnalyzer> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Meziantou.Analyzer" Version="1.0.639"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.8.55"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="AsyncFixer" Version="1.3.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Asyncify" Version="0.9.7"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="ClrHeapAllocationAnalyzer" Version="3.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="SonarAnalyzer.CSharp" Version="8.16.0.25740"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> <ItemGroup> <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt" /> </ItemGroup> </Project>
- در تنظیمات فوق، مواردی مانند AnalysisLevel، در مطلب «کامپایلر C# 9.0، خطاها و اخطارهای بیشتری را نمایش میدهد» پیشتر بررسی شدهاند.
- در اینجا Nullable به true تنظیم شدهاست. اگر قرار است یک پروژهی جدید را شروع کنید، بهتر است این ویژگی را نیز فعال کنید. بسیاری از APIهای دات نت 5 جهت مشخص سازی خروجی نال و یا غیرنال آنها، بازنویسی و تکمیل شدهاند و بدون استفاده از این ویژگی، بسیاری از راهنماییهای ارزندهی دات نت 5 را از دست خواهید داد. اساسا بدون فعالسازی این ویژگی، از قابلیتهای #C مدرن استفاده نمیکنید.
- وجود این PackageReference ها، به معنای بالا رفتن حجم نهایی قابل ارائهی پروژه نیست؛ چون به صورت PrivateAssets و analyzers تعریف شدهاند و فقط در حین پروسهی کامپایل، جهت ارائهی راهنماییهای بیشتر، تاثیرگذار خواهند بود.
- این تنظیمات طوری چیده شدهاند که تا حد ممکن «درد آور» باشند! برای اینکار CodeAnalysisTreatWarningsAsErrors و TreatWarningsAsErrors به true تظیم شدهاند تا حتی اخطارها نیز به صورت خطای کامپایلر گزارش شوند؛ تا مجبور به رفع آنها شویم.
- در اینجا فایل BannedSymbols.txt را نیز مشاهده میکنید که مرتبط است به BannedApiAnalyzers. میتوان در کنار فایل Directory.Build.props، فایل جدید BannedSymbols.txt را با این محتوا ایجاد کرد:
# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md P:System.DateTime.Now;Use System.DateTime.UtcNow instead P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead
روش کاهش تعداد خطاهای نمایش داده شده
اگر از فایل Directory.Build.props فوق استفاده کرده و یکبار دستور dotnet restore را جهت بازیابی وابستگیهای آن اجرا کنید، با تعداد خطاهایی که در IDE خود مشاهده خواهید کرد، شگفتزده خواهید شد! به همین جهت برای کنترل آنها میتوان فایل جدید editorconfig. را به نحو زیر در کنار فایل Directory.Build.props ایجاد و تکمیل کرد:
[*.cs] # MA0026 : Complete the task dotnet_diagnostic.MA0026.severity = suggestion # CA1308: In method 'urlToLower', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant' (CA1308) dotnet_diagnostic.CA1308.severity = suggestion # CA1040: Avoid empty interfaces dotnet_diagnostic.CA1040.severity = suggestion # CA1829 Use the "Count" property instead of Enumerable.Count() dotnet_diagnostic.CA1829.severity = suggestion # Use 'Count' property here instead. dotnet_diagnostic.S2971.severity = suggestion # S1135 : Complete the task dotnet_diagnostic.S1135.severity = suggestion # S2479: Replace the control character at position 7 by its escape sequence dotnet_diagnostic.S2479.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = none # MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed dotnet_diagnostic.MA0004.severity = none # CA1056: Change the type of property 'Url' from 'string' to 'System.Uri' dotnet_diagnostic.CA1056.severity = suggestion # CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object dotnet_diagnostic.CA1054.severity = suggestion # CA1055: Change the return type of method from 'string' to 'System.Uri' dotnet_diagnostic.CA1055.severity = suggestion # S4457: Split this method into two, one handling parameters check and the other handling the asynchronous code. dotnet_diagnostic.S4457.severity = none # AsyncFixer01: Unnecessary async/await usage dotnet_diagnostic.AsyncFixer01.severity = suggestion # AsyncFixer02: Long-running or blocking operations inside an async method dotnet_diagnostic.AsyncFixer02.severity = error # VSTHRD103: Call async methods when in an async method dotnet_diagnostic.VSTHRD103.severity = error # AsyncFixer03: Fire & forget async void methods dotnet_diagnostic.AsyncFixer03.severity = error # VSTHRD100: Avoid async void methods dotnet_diagnostic.VSTHRD100.severity = error # VSTHRD101: Avoid unsupported async delegates dotnet_diagnostic.VSTHRD101.severity = error # VSTHRD107: Await Task within using expression dotnet_diagnostic.VSTHRD107.severity = error # AsyncFixer04: Fire & forget async call inside a using block dotnet_diagnostic.AsyncFixer04.severity = error # VSTHRD110: Observe result of async calls dotnet_diagnostic.VSTHRD110.severity = error # VSTHRD002: Avoid problematic synchronous waits dotnet_diagnostic.VSTHRD002.severity = suggestion # MA0045: Do not use blocking call (make method async) dotnet_diagnostic.MA0045.severity = suggestion # AsyncifyInvocation: Use Task Async dotnet_diagnostic.AsyncifyInvocation.severity = error # AsyncifyVariable: Use Task Async dotnet_diagnostic.AsyncifyVariable.severity = error # VSTHRD111: Use ConfigureAwait(bool) dotnet_diagnostic.VSTHRD111.severity = none # MA0022: Return Task.FromResult instead of returning null dotnet_diagnostic.MA0022.severity = error # VSTHRD114: Avoid returning a null Task dotnet_diagnostic.VSTHRD114.severity = error # VSTHRD200: Use "Async" suffix for async methods dotnet_diagnostic.VSTHRD200.severity = suggestion # MA0040: Specify a cancellation token dotnet_diagnostic.MA0032.severity = suggestion # MA0040: Flow the cancellation token when available dotnet_diagnostic.MA0040.severity = suggestion # MA0079: Use a cancellation token using .WithCancellation() dotnet_diagnostic.MA0079.severity = suggestion # MA0080: Use a cancellation token using .WithCancellation() dotnet_diagnostic.MA0080.severity = error #AsyncFixer05: Downcasting from a nested task to an outer task. dotnet_diagnostic.AsyncFixer05.severity = error # ClrHeapAllocationAnalyzer ---------------------------------------------------- # HAA0301: Closure Allocation Source dotnet_diagnostic.HAA0301.severity = suggestion # HAA0601: Value type to reference type conversion causing boxing allocation dotnet_diagnostic.HAA0601.severity = suggestion # HAA0302: Display class allocation to capture closure dotnet_diagnostic.HAA0302.severity = suggestion # HAA0101: Array allocation for params parameter dotnet_diagnostic.HAA0101.severity = suggestion # HAA0603: Delegate allocation from a method group dotnet_diagnostic.HAA0603.severity = suggestion # HAA0602: Delegate on struct instance caused a boxing allocation dotnet_diagnostic.HAA0602.severity = suggestion # HAA0401: Possible allocation of reference type enumerator dotnet_diagnostic.HAA0401.severity = silent # HAA0303: Lambda or anonymous method in a generic method allocates a delegate instance dotnet_diagnostic.HAA0303.severity = silent # HAA0102: Non-overridden virtual method call on value type dotnet_diagnostic.HAA0102.severity = silent # HAA0502: Explicit new reference type allocation dotnet_diagnostic.HAA0502.severity = none # HAA0505: Initializer reference type allocation dotnet_diagnostic.HAA0505.severity = silent
dotnet_diagnostic.CA1308.severity = suggestion
یک نکته: در ویندوز نمیتوانید یک فایل تنها پسوند دار را به صورت معمولی در windows explorer ایجاد کنید. نام این فایل را به صورت .editorconfig. با دو نقطهی ابتدایی و انتهایی وارد کنید. خود ویندوز نقطهی پایانی را حذف میکند.
روش صرفنظر کردن از یک خطا، تنها در یک قسمت از کد
فرض کنید نمیخواهید خطای CA1052 را تبدیل به یک suggestion سراسری کنید و فقط میخواهید که در قطعهی خاصی از کدهای خود، آنرا خاموش کنید. به همین جهت بجای اضافه کردن آن به فایل editorconfig.، باید از ویژگی SuppressMessage به صورت زیر استفاده نمائید:
[SuppressMessage("Microsoft.Usage", "CA1052:Type 'Program' is a static holder type but is neither static nor NotInheritable", Justification = "We need it for our integration tests this way.")] [SuppressMessage("Microsoft.Usage", "RCS1102:Type 'Program' is a static holder type but is neither static nor NotInheritable", Justification = "We need it for our integration tests this way.")] [SuppressMessage("Microsoft.Usage", "S1118:Type 'Program' is a static holder type but is neither static nor NotInheritable", Justification = "We need it for our integration tests this way.")] public class Program { }
مسیر راه NET Core 2.1.
[CLI] Build-time performance improvements.
[CLI] Global tools; replaces .NET CLI Tools (DotNetCliToolReference).
[CoreCLR] Minor-version roll-forward.
[CoreCLR] No-copy array slicing with Span<T>.
[CoreFX] HttpClient performance improvements.
[CoreFX] Windows Compatibility Pack.
[ASP.NET] SignalR is available for .NET Core.
[ASP.NET] HTTPS is on by default for ASP.NET.
[EF] Basic lazy loading support.
[EF] Support for Azure Cosmos DB.