چند نکته کاربردی درباره Entity Framework
در متد Updateایی که نوشتید، قسمت Find حتما اتفاق میافته. چون Tracking خاموش هست (مطابق تنظیماتی که عنوان کردید)، بنابراین Find چیزی رو از کشی که وجود نداره نمیتونه دریافت کنه و میره سراغ دیتابیس. ماخذ :
The Find method on DbSet uses the primary key value to attempt to find an entity tracked by the context. If the entity is not found in the context then a query will be sent to the database to find the entity there. Null is returned if the entity is not found in the context or in the database.
قسمت Find متد Update شما در حالت detached اضافی است. یعنی اگر میدونید که این Id در دیتابیس وجود داره نیازی به Findاش نیست. فقط State اون رو تغییر بدید کار میکنه.
در حالت نه آنچنان Detached ! (دریافت یک لیست از Context ایی که ردیابی نداره)
با خاموش کردن Tracking حتما نیاز خواهید داشت تا متد context.ChangeTracker.DetectChanges رو هم پیش از ذخیره سازی یک لیست دریافت شده از بانک اطلاعاتی فراخوانی کنید. وگرنه چون این اطلاعات ردیابی نمیشوند، هر تغییری در آنها، وضعیت Unchanged رو خواهد داشت و نه Detached. بنابراین SaveChanges عمل نمیکنه؛ مگر اینکه DetectChanges فراخوانی بشه.
سؤال: این سربار که میگن چقدر هست؟ ارزشش رو داره که راسا خاموشش کنیم؟ یا بهتره فقط برای گزارشگیری این کار رو انجام بدیم؟
یک آزمایش:
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Diagnostics; using System.Linq; namespace EF_General.Models.Ex21 { public abstract class BaseEntity { public int Id { set; get; } } public class Factor : BaseEntity { public int TotalPrice { set; get; } } public class MyContext : DbContext { public DbSet<Factor> Factors { get; set; } public MyContext() { } public MyContext(bool withTracking) { if (withTracking) return; this.Configuration.ProxyCreationEnabled = false; this.Configuration.LazyLoadingEnabled = false; this.Configuration.AutoDetectChangesEnabled = false; } public void CustomUpdate<T>(T entity) where T : BaseEntity { if (entity == null) throw new ArgumentException("Cannot add a null entity."); var entry = this.Entry<T>(entity); if (entry.State != EntityState.Detached) return; /*var set = this.Set<T>(); // اینها اضافی است //متد فایند اگر اینجا باشه حتما به بانک اطلاعاتی رجوع میکنه در حالت منقطع از زمینه و در یک حلقه به روز رسانی کارآیی مطلوبی نخواهد داشت T attachedEntity = set.Find(entity.Id); if (attachedEntity != null) { var attachedEntry = this.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else {*/ entry.State = EntityState.Modified; //} } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { if (!context.Factors.Any()) { for (int i = 0; i < 20; i++) { context.Factors.Add(new Factor { TotalPrice = i }); } } base.Seed(context); } } public class Performance { public TimeSpan ListDisabledTracking { set; get; } public TimeSpan ListNormal { set; get; } public TimeSpan DetachedEntityDisabledTracking { set; get; } public TimeSpan DetachedEntityNormal { set; get; } } public static class Test { public static void RunTests() { startDb(); var results = new List<Performance>(); var runs = 20; for (int i = 0; i < runs; i++) { Console.WriteLine("\nRun {0}", i + 1); var tsListDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceDisabledTracking()); var tsListNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceNormal()); var tsDetachedEntityDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceDisabledTracking()); var tsDetachedEntityNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceNormal()); results.Add(new Performance { ListDisabledTracking = tsListDisabledTracking, ListNormal = tsListNormal, DetachedEntityDisabledTracking = tsDetachedEntityDisabledTracking, DetachedEntityNormal = tsDetachedEntityNormal }); } var detachedEntityDisabledTrackingAvg = results.Average(x => x.DetachedEntityDisabledTracking.TotalMilliseconds); Console.WriteLine("detachedEntityDisabledTrackingAvg: {0} ms.", detachedEntityDisabledTrackingAvg); var detachedEntityNormalAvg = results.Average(x => x.DetachedEntityNormal.TotalMilliseconds); Console.WriteLine("detachedEntityNormalAvg: {0} ms.", detachedEntityNormalAvg); var listDisabledTrackingAvg = results.Average(x => x.ListDisabledTracking.TotalMilliseconds); Console.WriteLine("listDisabledTrackingAvg: {0} ms.", listDisabledTrackingAvg); var listNormalAvg = results.Average(x => x.ListNormal.TotalMilliseconds); Console.WriteLine("listNormalAvg: {0} ms.", listNormalAvg); } private static void updateDetachedEntityTotalPriceNormal() { using (var context = new MyContext(withTracking: true)) { var detachedEntity = new Factor { Id = 1, TotalPrice = 10 }; var attachedEntity = context.Factors.Find(detachedEntity.Id); if (attachedEntity != null) { attachedEntity.TotalPrice = 100; context.SaveChanges(); } } } private static void updateDetachedEntityTotalPriceDisabledTracking() { using (var context = new MyContext(withTracking: false)) { var detachedEntity = new Factor { Id = 2, TotalPrice = 10 }; detachedEntity.TotalPrice = 200; context.CustomUpdate(detachedEntity); // custom update with change tracking disabled. context.SaveChanges(); } } private static void updateListTotalPriceNormal() { using (var context = new MyContext(withTracking: true)) { foreach (var item in context.Factors) { item.TotalPrice += 10; // normal update with change tracking enabled. } context.SaveChanges(); } } private static void updateListTotalPriceDisabledTracking() { using (var context = new MyContext(withTracking: false)) { foreach (var item in context.Factors) { item.TotalPrice += 10; //نیازی به این دو سطر نیست //context.ChangeTracker.DetectChanges(); // هربار باید محاسبه صورت گیرد در غیراینصورت وضعیت تغییر نیافته گزارش میشود //context.CustomUpdate(item); // custom update with change tracking disabled. } context.ChangeTracker.DetectChanges(); // در غیراینصورت وضعیت تغییر نیافته گزارش میشود context.SaveChanges(); } } private static void startDb() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); // Forces initialization of database on model changes. using (var context = new MyContext()) { context.Database.Initialize(force: true); } } } public class PerformanceHelper { public static TimeSpan RunActionMeasurePerformance(Action action) { var stopwatch = new Stopwatch(); stopwatch.Start(); action(); stopwatch.Stop(); return stopwatch.Elapsed; } } }
detachedEntityDisabledTrackingAvg: 22.32089 ms. detachedEntityNormalAvg: 54.546815 ms. listDisabledTrackingAvg: 413.615445 ms. listNormalAvg: 393.194625 ms.
در حالت کار با لیستی از اشیاء دریافت شده از بانک اطلاعاتی، به روز رسانی حالت متصل به Context سریعتر است.
استفاده از Froala WYSIWYG Editor در ASP.NET
.on('froalaEditor.file.unlink', function (e, editor, link) { $.ajax({ method: "POST", url: "@Url.Action("FroalaDeleteFile", "YeChizis")", data: { src:link.href } }) .done(function (data) { console.log('file was deleted'); }) .fail(function () { console.log('file delete problem'); }) })
/// <summary> /// حذف فایلهای ادیتور /// </summary> [HttpPost] public void FroalaDeleteFile(string src) { string relativePath = new Uri(src).PathAndQuery; string physicalFilePath = Server.MapPath(relativePath); if (System.IO.File.Exists(physicalFilePath)) System.IO.File.Delete(physicalFilePath); }
.on('froalaEditor.image.removed', function (e, editor, $img) { $.ajax({ // Request method. method: "POST", // Request URL. url: "@Url.Action("FroalaDeleteImageAction", "ControllerName")", // Request params. data: { src: $img.attr('src') } }) .done(function (data) { console.log('image was deleted'); }) .fail(function () { console.log('image delete problem'); }) })
/// <summary> /// حذف عکسهای ادیتور /// </summary> [HttpPost] public void FroalaDeleteImageAction(string src) { string physicalFilePath = Server.MapPath(src); if (System.IO.File.Exists(physicalFilePath)) System.IO.File.Delete(physicalFilePath); }
ابتدا مثال کامل ذیل را درنظر بگیرید:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static int Calculate(int a, int b, int c) { var result = a * b; return result - c; } static void Main(string[] args) { //روش متداول Console.WriteLine(Calculate(10, 2, 3)); //تعریف امضای متد var myMethod = new DynamicMethod( name: "CalculateMethod", returnType: typeof(int), parameterTypes: new[] { typeof(int), typeof(int), typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(opcode: OpCodes.Ldarg_0); // بارگذاری اولین آرگومان بر روی پشته ارزیابی il.Emit(opcode: OpCodes.Ldarg_1); // بارگذاری دومین آرگومان بر روی پشته ارزیابی il.Emit(opcode: OpCodes.Mul); // انجام عملیات ضرب il.Emit(opcode: OpCodes.Stloc_0); // ذخیره سازی نتیجه عملیات ضرب در یک متغیر محلی il.Emit(opcode: OpCodes.Ldloc_0); // متغیر محلی را بر روی پشته ارزیابی قرار میدهد تا در عملیات بعدی قابل استفاده باشد il.Emit(opcode: OpCodes.Ldarg_2); // آرگومان سوم را بر روی پشته ارزیابی قرار میدهد il.Emit(opcode: OpCodes.Sub); // انجام عملیات تفریق il.Emit(opcode: OpCodes.Ret); // بازگشت نتیجه //فراخوانی متد پویا var method = (Func<int, int, int, int>)myMethod.CreateDelegate(typeof(Func<int, int, int, int>)); Console.WriteLine(method(10, 2, 3)); } } }
در ادامه توسط ILGenerator، آرگومانهای دریافتی بارگذاری شده، در هم ضرب میشوند. سپس نتیجه در یک متغیر محلی ذخیره شده و سپس از آرگومان سوم کسر میگردد. در آخر هم این نتیجه بازگشت داده خواهد شد.
در اینجا روش سومی را برای کار با متدهای پویا مشاهده میکنید. بجای تعریف یک delegate به صورت صریح همانند قسمت قبل، از یک Func یا حتی Action نیز بنابر امضای متد مد نظر، میتوان استفاده کرد. در اینجا از یک Func که سه پارامتر int را قبول کرده و خروجی int نیز دارد، استفاده شده است.
اگر برنامه را اجرا کنید ... کرش خواهد کرد! با استثنای ذیل:
System.InvalidProgramException was unhandled Message=Common Language Runtime detected an invalid program.
il.DeclareLocal(typeof(int));
نحوه تعریف برچسبها در Reflection.Emit
در ادامه قصد داریم یک مثال پیشرفتهتر را بررسی کنیم.
static int Calculate(int x) { int result = 0; for (int i = 0; i < 10; i++) { result += i * x; } return result; }
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static int Calculate(int x) { int result = 0; for (int i = 0; i < 10; i++) { result += i * x; } return result; } static void Main(string[] args) { //روش متداول Console.WriteLine(Calculate(10)); //تعریف امضای متد var myMethod = new DynamicMethod( name: "CalculateMethod", returnType: typeof(int), // خروجی متد عدد صحیح است parameterTypes: new[] { typeof(int) }, // یک پارامتر عدد صحیح دارد m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); // از برچسبها برای انتقال کنترل استفاده میشود // در اینجا به دو برچسب برای تعریف ابتدای حلقه // و همچنین برای پرش به جایی که متد خاتمه مییابد نیاز داریم var loopStart = il.DefineLabel(); var methodEnd = il.DefineLabel(); // variable 0; result = 0 il.DeclareLocal(typeof(int)); // برای تعریف متغیر محلی نتیجه عملیات il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار میدهد il.Emit(OpCodes.Stloc_0); // و نهایتا این عدد ثابت به متغیر محلی انتساب داده خواهد شد // variable 1; i = 0 il.DeclareLocal(typeof(int)); // در اینجا کار تعریف و مقدار دهی متغیر حلقه انجام میشود il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار میدهد il.Emit(OpCodes.Stloc_1); // و نهایتا این عدد ثابت به متغیر حلقه در ایندکس یک انتساب داده خواهد شد // در اینجا کار تعریف بدنه حلقه شروع میشود il.MarkLabel(loopStart); // شروع حلقه را علامتگذاری میکنیم تا بعدا بتوانیم به این نقطه پرش نمائیم il.Emit(OpCodes.Ldloc_1); // در ادامه میخواهیم بررسی کنیم که آیا مقدار متغیر حلقه از عدد 10 کوچکتر است یا خیر il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت ده را بر روی پشته ارزیابی قرار میدهد // برای انجام بررسیهای تساوی یا کوچکتر یا بزرگتر نیاز است ابتدا دو متغیر مدنظر بر روی پشته قرار گیرند il.Emit(OpCodes.Bge, methodEnd); // اگر اینطور نیست و مقدار متغیر از 10 کمتر نیست، کنترل برنامه را به انتهای متد هدایت خواهیم کرد // i * x il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه را بر روی پشته قرار میدهد il.Emit(OpCodes.Ldarg_0); // مقدار اولین آرگومان متد را بر روی پشته قرار میدهد il.Emit(OpCodes.Mul); // انجام عملیات ضرب // نتیجه این عملیات اکنون بر روی پشته قرار گرفته است // result += il.Emit(OpCodes.Ldloc_0); // متغیر نتیجه را بر روی پشته قرار میدهد il.Emit(OpCodes.Add); // اکنون عملیات جمع بر روی نتیجه ضرب قسمت قبل که بر روی پشته قرار دارد و همچنین متغیر نتیجه انجام میشود il.Emit(OpCodes.Stloc_0); // ذخیره سازی نتیجه در متغیر محلی // i++ // در اینجا کار افزایش متغیر حلقه انجام میشود il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه بر روی پشته قرار میگیرد il.Emit(OpCodes.Ldc_I4_1); // عدد ثابت یک بر روی پشته قرار میگیرد il.Emit(OpCodes.Add); // سپس این دو عدد بارگذاری شده با هم جمع خواهند شد il.Emit(OpCodes.Stloc_1); // نتیجه در متغیر حلقه ذخیره خواهد شد // مرحله بعد شبیه سازی حلقه با پرش به ابتدای برچسب آن است il.Emit(OpCodes.Br, loopStart); //در اینجا انتهای متد علامتگذاری شده است il.MarkLabel(methodEnd); il.Emit(OpCodes.Ldloc_0); // مقدار نتیجه بر روی پشته قرار داده شده il.Emit(OpCodes.Ret); // و بازگشت داده میشود //فراخوانی متد پویا var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>)); Console.WriteLine(method(10)); } } }
فراخوانی متدها توسط کدهای پویای Reflection.Emit
در ادامه کدهای کامل یک مثال متد پویا را که متد print را فراخوانی میکند، ملاحظه میکنید:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { public static void print(int i) { Console.WriteLine("i: {0}", i); } static void Main(string[] args) { //روش متداول print(10); //تعریف امضای متد var myMethod = new DynamicMethod( name: "myMethod", returnType: typeof(void), parameterTypes: null, // پارامتری ندارد m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت 10 را بر روی پشته قرار میدهد // اکنون این مقدار بر روی پشته است و از آن میتوان برای فراخوانی متد پرینت استفاده کرد il.Emit(OpCodes.Call, typeof(Program).GetMethod("print")); il.Emit(OpCodes.Ret); //فراخوانی متد پویا var method = (Action)myMethod.CreateDelegate(typeof(Action)); method(); } } }
به علاوه چون خروجی امضای متد ما از نوع void است، اینبار delegate تعریف شده را از نوع Action تعریف کردهایم و نه از نوع Func.
فراخوانی متدهای پویای Reflection.Emit توسط سایر متدهای پویای Reflection.Emit
فراخوانی یک متد پویای مشخص از طریق متدهای پویای دیگر نیز همانند مثال قبل است:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static void Main(string[] args) { //تعریف امضای متد var myMethod = new DynamicMethod( name: "mulMethod", returnType: typeof(int), parameterTypes: new[] { typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // اولین آرگومان متد را بر روی پشته قرار میدهد il.Emit(OpCodes.Ldc_I4, 42); // عدد ثابت 42 را بر روی پشته قرار میدهد il.Emit(OpCodes.Mul); // ضرب این دو در هم il.Emit(OpCodes.Ret); // بازگشت نتیجه //فراخوانی متد پویا var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>)); Console.WriteLine(method(10)); // فراخوانی متد پویای فوق در یک متد پویای دیگر var callerMethod = new DynamicMethod( name: "callerMethod", returnType: typeof(int), parameterTypes: new[] { typeof(int), typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var callerMethodIL = callerMethod.GetILGenerator(); callerMethodIL.Emit(OpCodes.Ldarg_0); // پارامتر اول متد را بر روی پشته قرار میدهد callerMethodIL.Emit(OpCodes.Ldarg_1); // پارامتر دوم متد را بر روی پشته قرار میدهد callerMethodIL.Emit(OpCodes.Mul); // ضرب این دو در هم //حاصل ضرب اکنون بر روی پشته است که در فراخوانی بعدی استفاده میشود callerMethodIL.Emit(OpCodes.Call, myMethod); // فراخوانی یک متد پویای دیگر callerMethodIL.Emit(OpCodes.Ret); //فراخوانی متد پویای جدید var method2 = (Func<int, int, int>)callerMethod.CreateDelegate(typeof(Func<int, int, int>)); Console.WriteLine(method2(10, 2)); } } }
سپس متد پویای دومی تعریف شده است که دو عدد صحیح را دریافت و این دو را در هم ضرب کرده و سپس نتیجه را به عنوان پارامتر به متد پویای اول ارسال میکند.
هنگام فراخوانی OpCodes.Call، پارامتر دوم باید از نوع MethodInfo باشد. نوع یک DynamicMethod نیز همان MethodInfo است. بنابراین برای فراخوانی آن، کار خاصی نباید انجام شود و صرفا ذکر نام متغیر مرتبط با مد پویای مدنظر کفایت میکند.
نحوه صحیح تولید Url در ASP.NET MVC
ممنون از زحماتتون بابت مطالبی که به اشتراک میگذارید
یه سوال از خدمتتون داشتم
من روی یه پروژه MVC کار میکنم. به این صورت کار کردم که یک View کلی دارم که توی اون نمایش اطلاعات و "ایجاد" و "حذف" و "ویرایش" همه یکجا انجام میشن. این View را کاملا با Jquery کار کردم و Insert , Update , Delete کلا توسط Jquery انجام میشه. اما توی یکی از Viewهای دیگه که Strongly Type هم هستش، نمیخام به طور کامل از Jquery استفاده کنم. به این صورت که من اطلاعات جدول مربوطه رو توسط Jquery از پایگاه داده میگیرم و در یک گرید نمایش میدم. توی این گرید برای هر ردیف دوتا لینک "حذف" و "ویرایش" وجود داره. برای حذف هم با Jquery کارمو انجام میدم. اما برای "ایجاد" یک فرم از نوع فرمهای MVC دارم که داخل یه dialog از نوع Jquery قرارشون دادم. برای ایجاد هم مشکلی نیست. اما برای ویرایش، نمیدونم که چطوری باید اطلاعات رو از پایگاه داده لود کنم که خودش مستقیما داخل TextBoxها قرار بگیره. البته توسط Jquery اومدم تک تک textboxها رو به صورت زیر مقدار دادم. حالا نمیدونم کارم درسته یا روش بهتری هست واسه این کار. اگر نیاز هست تا سورس برنامه رو واستون بفرستم
function editmode(val) { $.ajax({ url: "/User/SelectUser", data: { Username: val }, type: "post", dataType: "json", success: function (data) { if (data != "timeout") { if (data.isRedirect) { window.location.href = data.redirectUrl; return; } try { $("#dvEdit").dialog({ modal: 'true', title: 'ویرایش', hide: 'clip' }); //alert($("#dvEdit").html()); $("#UsernameEdit").val(data.Username); $("#FirstNameEdit").val(data.FirstName); $("#LastNameEdit").val(data.LastName); $("#NationalCodeEdit").val(data.NationalCode); $("#EmailEdit").val(data.Email); $("#PhoneNoEdit").val(data.PhoneNo); $("#MobileNoEdit").val(data.MobileNo); $("#CreationDateEdit").val(data.CreationDate); $("#CreationDateEdit").prop('disabled', true); $("#LastActivityDateEdit").val(data.LastActivityDate); $("#LastActivityDateEdit").prop('disabled', true); $("#LastLoginDateEdit").val(data.LastLoginDate); $("#LastLoginDateEdit").prop('disabled', true); $("#IsLockedOutEdit").val(data.IsLockedOut); $("#AddressEdit").val(data.Address); } catch (err) { $("#dverr").show(); $("#lblErr").html(err); } } else AjaxTimeout(); } , error: function (req, textstatus, errorthrown) { AjaxError(req, textstatus, "#dverr", "#lblErr"); } , complete: function (xhr, e) { AjaxComplete(xhr, "#dverr", "#lblErr"); } }); }
آموزش LINQ بخش چهارم
انواع JOIN :
• Inner JOIN
• Group JOIN
• Left JOIN
کلاسهای زیر را در نظر بگیرید:
/// <summary> /// دستور العمل /// </summary> class Recipe { public int Id { get; set; } public string Name { get; set; } } /// <summary> /// بازخورد /// </summary> class Review { public int RecipeId { get; set; } public string ReviewText { get; set; } }
Inner Join
این دستور عنصری از توالی اول را که متناظر با آن عنصری در توالی دوم وجود داشته باشد، به خروجی میبرد.
مثال:
Recipe[] recipes = { new Recipe {Id = 1, Name = "Mashed Potato"}, new Recipe {Id = 2, Name = "Crispy Duck"}, new Recipe {Id = 3, Name = "Sachertorte"} }; Review[] reviews = { new Review {RecipeId = 1, ReviewText = "Tasty!"}, new Review {RecipeId = 1, ReviewText = "Not nice :("}, new Review {RecipeId = 1, ReviewText = "Pretty good"}, new Review {RecipeId = 2, ReviewText = "Too hard"}, new Review {RecipeId = 2, ReviewText = "Loved it"} }; var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId select new //anonymous type { RecipeName = recipe.Name, RecipeReview = review.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it
Group Join
بکارگیری into به همراه join، دستور Group Join را میسازد.
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup select new //anonymous type { RecipeName = recipe.Name, Reviews = reviewGroup//collection of related reviews }; foreach (var item in query) { Console.WriteLine($"Review for {item.RecipeName}"); foreach (var review in item.Reviews) { Console.WriteLine($"-{review.ReviewText}"); } }
متغیر reviewGroup توالی حاصل از اجرای join را نمایش میدهد. برای ایجاد توالی خروجی، نتیجه به یک نوع بی نام، بازتاب شده است. هر عنصر در نوع بی نام یک گروه را نشان میدهد. نوع بی نام شامل دو خصوصیت RecipeName که مقدار آن از توالی اول میآید و Reviews که حاصل خروجی Join است میباشد.
خروجی مثال بالا به شکل زیر است:
Review for Mashed Potato -Tasty! -Not nice :( -Pretty good Review for Crispy Duck -Too hard -Loved it Review for Sachertorte
Left outer join
در این مثال از مجموعههای تعریف شدهی در بخش اول مطلب استفاده کردهایم:
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty() select new //anonymous type { RecipeName = recipe.Name, //RecipeReview = rg.ReviewText SystemNullException RecipeReview = (rg == null ? "n/a" : rg.ReviewText) }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
خروجی مثال فوق به شکل زیر است:
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-n/a
مثال:
var query = from recipe in recipes join review in reviews on recipe.Id equals review.RecipeId into reviewGroup from rg in reviewGroup.DefaultIfEmpty(new Review { ReviewText = "N/A" }) select new //anonymous type { RecipeName = recipe.Name, RecipeReview = rg.ReviewText }; foreach (var item in query) { Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}"); }
Mashed Potato-Tasty! Mashed Potato-Not nice :( Mashed Potato-Pretty good Crispy Duck-Too hard Crispy Duck-Loved it Sachertorte-N/A
شبیه به نحوهی به دام انداختن خطاهای مدیریت نشده در Web forms و روال استاندارد Application_Error ، در برنامههای Windows forms نیز این امر به صورت زیر ممکن است:
using System;
using System.Threading;
using System.Windows.Forms;
namespace testWinForms87
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// handling UI thread exceptions
Application.ThreadException += uIThreadException;
// force all Windows Forms errors to go through our handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// handling non-UI thread exceptions.
AppDomain.CurrentDomain.UnhandledException += currentDomainUnhandledException;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
private static void currentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
MessageBox.Show(((Exception)e.ExceptionObject).Message, "currentDomainUnhandledException");
}
private static void uIThreadException(object sender, ThreadExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message, "uIThreadException");
}
}
}
الف)همانطور که ملاحظه میکنید سطرهای فوق باید قبل از Application.Run در روال اصلی برنامه تعریف شوند.
ب) این متدها استاتیک هستند و توصیه شده است در پایان برنامه ارجاعات آنها را حذف کنید تا نشتی حافظه رخ ندهد. دقیقا به همین صورت =+ که اضافه شدند با =- هم قابل حذف هستند.
ج) در حالت اجرا شدن uIThreadException ، برنامه بسته نخواهد شد (و بدیهی است در صورت عدم بکار گیری این روش، حتما برنامه کرش خواهد کرد). برای مثال شاید علاقمند نباشید که بخاطر عدم دسترسی نوشتن در پوشهای خاص، خطای حاصل سبب بسته شدن کل برنامه شود. به این صورت این موارد را میتوان به دام انداخت. اما currentDomainUnhandledException که حاصل از خطاهای ایجاد شده برای مثال در یک ترد دیگر بجز ترد اصلی برنامه هستند، حتما سبب بسته شدن برنامه خواهند شد. بنابراین اینجا تنها شانس لاگ کردن خطای مدیریت نشده حاصل را خواهیم داشت. به همین منظور همیشه توصیه میشود که در تردهای ایجاد شده در برنامه، حتما موارد مدیریت خطاها را لحاظ نمائید، زیرا خطاهای حاصل شده در آنها غیرقابل اغماض بوده و حتما سبب کرش برنامه میشوند.
پ.ن.
دقیقا در برنامههای Win32 دلفی هم چنین قابلیتی به همین شکل و تقریبا با همین نامها وجود دارد. فقط کافی است روالی را جهت Application.OnException ایجاد کنید: ;)
procedure TmyFrmMain.FormCreate(Sender: TObject);
begin
Application.OnException := MyExceptionHandler;
end;
procedure TmyFrmMain.MyExceptionHandler(Sender: TObject; E: Exception);
begin
ShowMessage(e.Message);
end;
- Workflow: یکی از مفاهیمی که باید با آن آشنا باشیم workflowها هستند. یک workflow مجموعهایی از jobهایی هستند که در رخدادهای خاصی اجرا میشوند. در واقع یک workflow یک CI pipeline است که با کمک YAML آنها را تعریف میکنیم.
- Runner: اینها به اصطلاح compute machineهایی هستند که workflowها را اجرا میکنند. این runnerها هم میتوانند به صورت سفارشی باشند و هم سرویسهای ارائه شده توسط GitHub باشند.
- Job: مجموعهایی از مراحلی که درون یک runner workspace اجرا میشوند.
- Step: در نهایت stepها هستند که کوچکترین بخش GitHub Actions هستند. stepها میتوانند فایل اسکریپت، Dockerfile یا یک community action باشند.
name: Build Application Code on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 - name: Install Libraries uses: pip install -r requirements.txt -t . test: runs-on: ubuntu-latest needs: build steps: ...
├── .github │ ├── scripts │ └── workflows ├── README.md ├── assets └── deps
name: Update Recent Blog Posts on: schedule: - cron: "0 0 * * 0" # Run once a week at 00:00 (midnight) on Sunday workflow_dispatch: jobs: update_posts: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching latest blog posts shell: pwsh run: | . ./.github/scripts/Get-Posts.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Get-Posts { Param ( [Parameter(Mandatory = $false)] [string]$rssUrl ) $posts = @() $feed = [xml](Invoke-WebRequest -Uri $rssUrl).Content $feed.rss.channel.item | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.title."#cdata-section" ?? $_.title Link = $_.link Description = $_.description."#cdata-section" ?? $_.description PubDate = $_.pubDate } $posts += $post } $posts } Function Get-DntipsPosts { $assemblyPath = "$(Get-Location)/deps/CodeHollow.FeedReader.dll" [Reflection.Assembly]::LoadFile($assemblyPath) $feed = [CodeHollow.FeedReader.FeedReader]::ReadAsync("https://www.dntips.ir/feed/author/%d8%b3%db%8c%d8%b1%d9%88%d8%a7%d9%86%20%d8%b9%d9%81%db%8c%d9%81%db%8c").Result $posts = @() $feed.Items | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.Title Link = $_.Link Description = $_.Description PubDate = $_.PublishingDate } $posts += $post } $posts } Function Set-Posts { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSCustomObject[]]$posts, [Parameter(Mandatory = $false)] [string]$marker = "## Recent Blog Posts - English" ) Begin { $readMePath = "./README.md" $readmeContents = Get-Content -Path $readMePath -Raw $markdownTable = "| Link | Published At |`n" $markdownTable += "| --- | --- |`n" } Process { if ($null -eq $_.Title) { return } $date = Get-Date -Date $_.PubDate $link = "[$($_.Title)]($($_.Link))" $markdownTable += "| $($link) | $($date.ToString("dd/MM/yy")) |`n" } End { $updatedContent = $readmeContents -replace "$marker\n([\s\S]*?)(?=#| $)", "$marker`n$($markdownTable)`n" $updatedContent | Set-Content -Path $readMePath } } Function Set-Blogs { $recentBlogPostsStr = "## Recent blog posts -" Get-Posts("https://dev.to/feed/sirwanafifi") | Set-Posts -marker "$recentBlogPostsStr dev.to" Get-Posts("https://sirwan.infohttps://www.dntips.ir/rss.xml") | Set-Posts -marker "$recentBlogPostsStr sirwan.info" Get-DntipsPosts | Set-Posts -marker "$recentBlogPostsStr dntips.ir" } Set-Blogs
name: Update Step Component on: schedule: - cron: "0 18 * * *" workflow_dispatch: jobs: update_steps: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching my latest steps shell: pwsh env: STEPS_URI: ${{ secrets.STEPS_URI }} run: | . ./.github/scripts/Get-Steps.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Set-Steps { Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject]$json ) Write-Host ($json | ConvertTo-Json) $SvgPath = "$(Get-Location)/assets/step.svg" $SvgContent = Get-Content -Path $SvgPath -Raw $TextTags = @" <tspan id="step-count" font-weight="bold">$([System.String]::Format("{0:n0}", [int]$json.steps))</tspan> "@ $DatetimeTags = "<text id=""datetime"" x=""800"" y=""72"" font-size=""39"" fill="#99989E"">$($json.date)</text>" $SvgContent = $SvgContent -Replace '<tspan id="step-count" font-weight="bold">.*?</tspan>', $TextTags $SvgContent = $SvgContent -Replace '<text id="datetime" x="800" y="72" font-size="39" fill="#99989E">.*?</text>', $DatetimeTags $SvgContent | Set-Content -Path $SvgPath } Function Get-LatestSteps { Try { $Uri = $env:STEPS_URI Write-Host "Uri: $Uri" $JsonResult = (Invoke-WebRequest -Uri $Uri).Content | ConvertFrom-Json Write-Host "Steps: $($JsonResult.steps)" Return $JsonResult } Catch { Return @{ steps = 0 date = Get-Date -Format "yyyy-MM-dd" } } } Write-Host "Getting latest steps..." Get-LatestSteps | Set-Steps Write-Host "Done!"
string data = null; var result = data ?? "value";
if (data == null) { data = "value"; } var result = data;
برای مثال بسیاری از نتایج بازگشتی از متدها، چند سطحی هستند:
class Response { public string Result { set; get; } public int Code { set; get; } } class WebRequest { public Response GetDataFromWeb(string url) { // ... return new Response { Result = null }; } }
var webData = new WebRequest().GetDataFromWeb("https://www.dntips.ir/"); if (webData != null && webData.Result != null) { Console.WriteLine(webData.Result); }
در این حالت اگر اشارهگر را به محل && انتقال دهیم، افزونهی ReSharper پیشنهاد یکی کردن این بررسیها را ارائه میدهد:
به این ترتیب تمام چند سطح بررسی نال، به یک عبارت بررسی .? دار، خلاصه خواهد شد:
if (webData?.Result != null) { Console.WriteLine(webData.Result); }
البته باید دقت داشت که برای تمام سطوح باید از .? استفاده کرد (برای مثال response?.Results?.Status)؛ در غیر اینصورت همانند سابق در صورت استفادهی از دات معمولی، به یک null reference exception میرسیم.
کار با متدها و Delegates
این عملگر جدید مقایسهی با نال را بر روی متدها (علاوه بر خواص و فیلدها) نیز میتوان بکار برد. برای مثال خلاصه شدهی فراخوانی ذیل:
if (x != null) { x.Dispose(); }
x?.Dispose();
و یا بکار گیری آن بر روی delegates (روش قدیمی):
var copy = OnMyEvent; if (copy != null) { copy(this, new EventArgs()); }
OnMyEvent?.Invoke(this, new EventArgs());
استفاده از Null Conditional Operator بر روی Value types
الف) مقایسه با نال
کد ذیل را درنظر بگیرید:
var code = webData?.Code;
if (webData?.Code > 0) { }
ب) بازگشت مقدار پیش فرض دیگری بجای نال
اگر نیاز بود بجای null مقدار پیش فرض دیگری را بازگشت دهیم، میتوان از null-coalescing operator سابق استفاده کرد:
int count = response?.Results?.Count ?? 0;
ج) دسترسی به مقدار Value یک متغیر nullable
نمونهی دیگر آن قطعه کد ذیل است:
int? x = 10; //var value = x?.Value; // invalid Console.WriteLine(x?.ToString());
کار با indexer property و بررسی نال
اگر به عنوان بحث دقت کرده باشید، یک s جمع در انتهای Null-conditional operators ذکر شدهاست. به این معنا که این عملگر مقایسهی با نال، صرفا یک شکل و فرم .? را ندارد. مثال ذیل در حین کار با آرایهها و لیستها بسیار مشاهده میشود:
if (response != null && response.Results != null && response.Results.Addresses != null && response.Results.Addresses[0] != null && response.Results.Addresses[0].Zip == "63368") { }
if(response?.Results?.Addresses?[0]?.Zip == "63368") { }
موارد استفادهی ناصحیح از عملگرهای مقایسهی با نال
خوب، عملگر .? کار مقایسهی با نال را خصوصا در دسترسیهای چند سطحی به خواص و متدها بسیار ساده میکند. اما آیا باید در همه جا از آن استفاده کرد؟ آیا باید از این پس کلا استفاده از دات را فراموش کرد و بجای آن از .? در همه جا استفاده کرد؟
مثال ذیل را درنظر بگیرید:
public void DoSomething(Customer customer) { string address = customer?.Employees ?.SingleOrDefault(x => x.IsAdmin)?.Address?.ToString(); SendPackage(address); }
روش بهتر انجام اینکار، بررسی وضعیت customer و انتقال مابقی زنجیرهی LINQ به یک متد مجزای دیگر است:
public void DoSomething(Customer customer) { Contract.Requires(customer != null); string address = customer.GetAdminAddress(); SendPackage(address); }
سرویس ما متدهای زیر را در دسترس قرار میدهد.
Relative URl | HTTP method | Action |
api/products/ | GET | گرفتن لیست تمام محصولات |
api/products/id/ | GET | گرفتن یک محصول بر اساس شناسه |
api/products?category=category/ | GET | گرفتن یک محصول بر اساس طبقه بندی |
api/products/ | POST | ایجاد یک محصول جدید |
api/products/id/ | PUT | بروز رسانی یک محصول |
api/products/id/ | DELETE | حذف یک محصول |
همانطور که مشاهده میکنید برخی از آدرس ها، شامل شناسه محصول هم میشوند. بعنوان مثال برای گرفتن محصولی با شناسه 28، کلاینت یک درخواست GET را به آدرس زیر ارسال میکند:
http://hostname/api/products/28
منابع
سرویس ما آدرس هایی برای دستیابی به دو نوع منبع (resource) را تعریف میکند:
URI | Resource |
api/products/ | لیست تمام محصولات |
api/products/id/ | یک محصول مشخص |
متد ها
چهار متد اصلی HTTP یعنی همان GET, PUT, POST, DELETE میتوانند بصورت زیر به عملیات CRUD نگاشت شوند:
- متد GET یک منبع (resource) را از آدرس تعریف شده دریافت میکند. متدهای GET هیچگونه تاثیری روی سرور نباید داشته باشند. مثلا حذف رکوردها با متد اکیدا اشتباه است.
- متد PUT یک منبع را در آدرس تعریف شده بروز رسانی میکند. این متد برای ساختن منابع جدید هم میتواند استفاده شود، البته در صورتی که سرور به کلاینتها اجازه مشخص کردن آدرسهای جدید را بدهد. در مثال جاری پشتیبانی از ایجاد منابع توسط متد PUT را بررسی نخواهیم کرد.
- متد POST منبع جدیدی میسازد. سرور آدرس آبجکت جدید را تعیین میکند و آن را بعنوان بخشی از پیام Response بر میگرداند.
- متد DELETE منبعی را در آدرس تعریف شده حذف میکند.
نکته: متد PUT موجودیت محصول (product entity) را کاملا جایگزین میکند. به بیان دیگر، از کلاینت انتظار میرود که آبجکت کامل محصول را برای بروز رسانی ارسال کند. اگر میخواهید از بروز رسانیهای جزئی/پاره ای (partial) پشتیبانی کنید متد PATCH توصیه میشود. مثال جاری متد PATCH را پیاده سازی نمیکند.
یک پروژه Web API جدید بسازید
ویژوال استودیو را باز کنید و پروژه جدیدی از نوع ASP.NET MVC Web Application بسازید. نام پروژه را به "ProductStore" تغییر دهید و OK کنید.
در دیالوگ New ASP.NET Project قالب Web API را انتخاب کرده و تایید کنید.
افزودن یک مدل
یک مدل، آبجکتی است که داده اپلیکیشن شما را نمایندگی میکند. در ASP.NET Web API میتوانید از آبجکتهای Strongly-typed بعنوان مدل هایتان استفاده کنید که بصورت خودکار برای کلاینت به فرمتهای JSON, XML مرتب (Serialize) میشوند. در مثال جاری، دادههای ما محصولات هستند. پس کلاس جدیدی بنام Product میسازیم.
در پوشه Models کلاس جدیدی با نام Product بسازید.
حال خواص زیر را به این کلاس اضافه کنید.
namespace ProductStore.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک مخزن
ما نیاز به ذخیره کردن کلکسیونی از محصولات داریم، و بهتر است این کلکسیون از پیاده سازی سرویس تفکیک شود. در این صورت بدون نیاز به بازنویسی کلاس سرویس میتوانیم منبع دادهها را تغییر دهیم. این نوع طراحی با نام الگوی مخزن یا Repository Pattern شناخته میشود. برای شروع نیاز به یک قرارداد جنریک برای مخزنها داریم.
روی پوشه Models کلیک راست کنید و گزینه Add, New Item را انتخاب نمایید.
نوع آیتم جدید را Interface انتخاب کنید و نام آن را به IProductRepository تغییر دهید.
حال کد زیر را به این اینترفیس اضافه کنید.
namespace ProductStore.Models { public interface IProductRepository { IEnumerable<Product> GetAll(); Product Get(int id); Product Add(Product item); void Remove(int id); bool Update(Product item); } }
namespace ProductStore.Models { public class ProductRepository : IProductRepository { private List<Product> products = new List<Product>(); private int _nextId = 1; public ProductRepository() { Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M }); Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M }); Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M }); } public IEnumerable<Product> GetAll() { return products; } public Product Get(int id) { return products.Find(p => p.Id == id); } public Product Add(Product item) { if (item == null) { throw new ArgumentNullException("item"); } item.Id = _nextId++; products.Add(item); return item; } public void Remove(int id) { products.RemoveAll(p => p.Id == id); } public bool Update(Product item) { if (item == null) { throw new ArgumentNullException("item"); } int index = products.FindIndex(p => p.Id == item.Id); if (index == -1) { return false; } products.RemoveAt(index); products.Add(item); return true; } } }
مخزن ما لیست محصولات را در حافظه محلی نگهداری میکند. برای مثال جاری این طراحی کافی است، اما در یک اپلیکیشن واقعی دادههای شما در یک دیتابیس یا منبع داده ابری ذخیره خواهند شد. همچنین استفاده از الگوی مخزن، تغییر منبع دادهها در آینده را راحتتر میکند.
افزودن یک کنترلر Web API
اگر قبلا با ASP.NET MVC کار کرده باشید، با مفهوم کنترلرها آشنایی دارید. در ASP.NET Web API کنترلرها کلاس هایی هستند که درخواستهای HTTP دریافتی از کلاینت را به اکشن متدها نگاشت میکنند. ویژوال استودیو هنگام ساختن پروژه شما دو کنترلر به آن اضافه کرده است. برای مشاهد آنها پوشه Controllers را باز کنید.
- HomeController یک کنترلر مرسوم در ASP.NET MVC است. این کنترلر مسئول بکار گرفتن صفحات وب است و مستقیما ربطی به Web API ما ندارد.
- ValuesController یک کنترلر نمونه WebAPI است.
کنترلر ValuesController را حذف کنید، نیازی به این آیتم نخواهیم داشت. حال برای اضافه کردن کنترلری جدید مراحل زیر را دنبال کنید.
در پنجره Solution Explorer روی پوشه Controllers کلیک راست کرده و گزینه Add, Controller را انتخاب کنید.
در دیالوگ Add Controller نام کنترلر را به ProductsController تغییر داده و در قسمت Scaffolding Options گزینه Empty API Controller را انتخاب کنید.
حال فایل کنترلر جدید را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.
using ProductStore.Models;
public class ProductsController : ApiController { static readonly IProductRepository repository = new ProductRepository(); }
فراخوانی ()new ProductRepository طراحی جالبی نیست، چرا که کنترلر را به پیاده سازی بخصوصی از این اینترفیس گره میزند. بهتر است از تزریق وابستگی (Dependency Injection) استفاده کنید. برای اطلاعات بیشتر درباره تکنیک DI در Web API به این لینک مراجعه کنید.
گرفتن منابع
ProductStore API اکشنهای متعددی در قالب متدهای HTTP GET در دسترس قرار میدهد. هر اکشن به متدی در کلاس ProductsController مرتبط است.
Relative URl | HTTP Method | Action |
api/products/ | GET | دریافت لیست تمام محصولات |
api/products/id/ | GET | دریافت محصولی مشخص بر اساس شناسه |
api/products?category=category/ | GET | دریافت محصولات بر اساس طبقه بندی |
برای دریافت لیست تمام محصولات متد زیر را به کلاس ProductsController اضافه کنید.
public class ProductsController : ApiController { public IEnumerable<Product> GetAllProducts() { return repository.GetAll(); } // .... }
برای دریافت محصولی مشخص بر اساس شناسه آن متد زیر را اضافه کنید.
public Product GetProduct(int id) { Product item = repository.Get(id); if (item == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return item; }
نام این متد هم با "Get" شروع میشود اما پارامتری با نام id دارد. این پارامتر به قسمت id مسیر درخواست شده (request URl) نگاشت میشود. تبدیل پارامتر به نوع داده مناسب (در اینجا int) هم بصورت خودکار توسط فریم ورک ASP.NET Web API انجام میشود.
متد GetProduct در صورت نامعتبر بودن پارامتر id استثنایی از نوع HttpResponseException تولید میکند. این استثنا بصورت خودکار توسط فریم ورک Web API به خطای 404 (Not Found) ترجمه میشود.
در آخر متدی برای دریافت محصولات بر اساس طبقه بندی اضافه کنید.
public IEnumerable<Product> GetProductsByCategory(string category) { return repository.GetAll().Where( p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); }
اگر آدرس درخواستی پارامترهای query string داشته باشد، Web API سعی میکند پارامترها را با پارامترهای متد کنترلر تطبیق دهد. بنابراین درخواستی به آدرس "api/products?category=category" به این متد نگاشت میشود.
ایجاد منبع جدید
قدم بعدی افزودن متدی به ProductsController برای ایجاد یک محصول جدید است. لیست زیر پیاده سازی ساده ای از این متد را نشان میدهد.
// Not the final implementation! public Product PostProduct(Product item) { item = repository.Add(item); return item; }
- نام این متد با "Post" شروع میشود. برای ساختن محصولی جدید کلاینت یک درخواست HTTP POST ارسال میکند.
- این متد پارامتری از نوع Product میپذیرد. در Web API پارامترهای پیچیده (complex types) بصورت خودکار با deserialize کردن بدنه درخواست بدست میآیند. بنابراین در اینجا از کلاینت انتظار داریم که آبجکتی از نوع Product را با فرمت XML یا JSON ارسال کند.
پیاده سازی فعلی این متد کار میکند، اما هنوز کامل نیست. در حالت ایده آل ما میخواهیم پیام HTTP Response موارد زیر را هم در بر گیرد:
- Response code: بصورت پیش فرض فریم ورک Web API کد وضعیت را به 200 (OK) تنظیم میکند. اما طبق پروتکل HTTP/1.1 هنگامی که یک درخواست POST منجر به ساخته شدن منبعی جدید میشود، سرور باید با کد وضعیت 201 (Created) پاسخ دهد.
- Location: هنگامی که سرور منبع جدیدی میسازد، باید آدرس منبع جدید را در قسمت Location header پاسخ درج کند.
ASP.NET Web API دستکاری پیام HTTP response را آسان میکند. لیست زیر پیاده سازی بهتری از این متد را نشان میدهد.
public HttpResponseMessage PostProduct(Product item) { item = repository.Add(item); var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item); string uri = Url.Link("DefaultApi", new { id = item.Id }); response.Headers.Location = new Uri(uri); return response; }
متد CreateResponse آبجکتی از نوع HttpResponseMessage میسازد و بصورت خودکار آبجکت Product را مرتب (serialize) کرده و در بدنه پاسخ مینویسد. نکته دیگر آنکه مثال جاری، مدل را اعتبارسنجی نمیکند. برای اطلاعات بیشتر درباره اعتبارسنجی مدلها در Web API به این لینک مراجعه کنید.
بروز رسانی یک منبع
بروز رسانی یک محصول با PUT ساده است.
public void PutProduct(int id, Product product) { product.Id = id; if (!repository.Update(product)) { throw new HttpResponseException(HttpStatusCode.NotFound); } }
حذف یک منبع
برای حذف یک محصول متد زیر را به کلاس ProductsController اضافه کنید.
public void DeleteProduct(int id) { Product item = repository.Get(id); if (item == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } repository.Remove(id); }
در مثال جاری متد DeleteProduct نوع void را بر میگرداند، که فریم ورک Web API آن را بصورت خودکار به کد وضعیت 204 (No Content) ترجمه میکند.