private StateMachine<string, string> stateMachine; private StateMachineCOM source; private string startState; public delegate void UnhandledTriggerDelegate(State state, StateConfig trigger); public delegate void EntryExitDelegate(); public delegate bool GuardClauseDelegate(); public string Id; public EntryExitDelegate OnEntry = null; public EntryExitDelegate OnExit = null; public GuardClauseDelegate GuardClauseFromToTrigger = null; public UnhandledTriggerDelegate OnUnhandledTrigger = null; public StateMachineRequest(StateMachineCOM source, string startStateId) { this.source = source; this.startState = startStateId; } public void Configure() { this.stateMachine = new StateMachine<string, string>(startState); var states = source.States; states.ForEach(state => { var triggers = source.StateConfigs.AsQueryable() .Where(config => config.FromStateId == state.StateId) .Select(config => new {Id=config.TransitionId.ToString(), From= config.FromStateId.ToString(), To= config.ToStateId.ToString(), Permit=config.PermiteAction }) .ToList(); triggers.ForEach(trig => { this.stateMachine.Configure(state.StateId.ToString()) }); }); } public bool TryFireTrigger(string TrigerId) { if (!stateMachine.CanFire(TrigerId)) { return false; } stateMachine.Fire(TrigerId); return true; } public string GetCurrentState() { return this.stateMachine.State; }
باشد یعنی Stateها Transitionها و ... را بعد از Fetch کردن از بانک اطلاعاتی به State Machine ارسال کنیم. حالا برای در نظر گرفتن شروط مربوط به OnEntry و OnExit یا GuardClauseFromToTrigger پیشنهاد شما توجه به اینکه براساس State میبایست این متدها ساخته شوند چیست؟
- آیا بهتر است delegate پارامتر دریافت کند؟
اگر بله پیاده سازی آن در هنگام کانفیگ به چه صورت است؟ به این صورت ؟
this.stateMachine.Configure(state.StateId.ToString()) .OnEntry(() => { if (state.OnEnter) OnEntry(trig.Id);}) .OnExit(() => { if (state.OnExit) OnExit(trig.Id); }) .PermitIf(trig.From, trig.To, () => { if (trig.Permit) return GuardClauseFromToTrigger(); return true; });
[Transactional] public async Task CreateAsync(OrganizationalUnitCreateModel model) { Guard.ArgumentNotNull(model, nameof(model)); if (model.ParentId.HasValue) await _manager.CheckIsDeactiveAsync(model.ParentId.Value).ConfigureAwait(false); //... }
به این چنین کدهایی معمولاً The null cancer گفته میشود (سرطان نال!) زیرا اجازه دادهایم متد، خروجی null را بازگشت دهد.
Unit of Work یا الگوی کار در واقع یک الگو، جهت جمع آوری عملیات کار با دیتابیس است که همه عملیات را تحت یک تراکنش به سمت دیتابیس ارسال میکند تا مبحث Atomic بودن عملیات، به مرحله اجرا گذاشته شود. در صورتیکه یکی از عملیات با نقص یا خطایی روبرو شود، کل عملیات Roll back یا برگشت میخورد. از آنجا که دیتابیسهای معدودی چون Ravendb این مراحل را تا حدی پیاده سازی میکنند نباید از مونگو هم چنین انتظاری نداشته باشید. مونگو برخورد تراکنشی یا اتمیک ندارد؛ پس پیاده سازی الگوی واحد کاری تاثیری بر روی روند کاری آن ندارد. هر چند تعدادی مثال بدین شکل پیاده شدهاند، ولی در عمل حقیقی نیستند و تنها یک حرکت مشابه داشتهاند.
ولی الگوی repository برای پرهیز از تکرار کد، قابلیت به روزرسانی کد و همچنین عملیاتی چون آزمونهای واحد و چهارچوب تقلید به کار میرود. وابستگی بین اشیاء را کاهش داده و باعث ایجاد یک کد با دوامتر میگردد.
ابتدا قبل از هر چیزی نیاز است تا اتصالات یا ساخت کانکشن به سرور و همچنین دریافت دیتابیس مورد نظر را در قالب یک کلاس تعریف نماییم. نام آن را MongoDbContext میگذارم:
public class MongoDbContext : IMongoDbContext { public const string DatabaseName = "MongoDbTest"; private static readonly IMongoClient _client; private static readonly IMongoDatabase Database; static MongoDbContext() { _client = new MongoClient(); Database = _client.GetDatabase(DatabaseName); } public IMongoCollection<TEntity> GetCollection<TEntity>() { return Database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s"); } }
public interface IMongoDbContext { IMongoCollection<TEntity> GetCollection<TEntity>(); }
در مرحله بعد یک IMongoDbRepositry ساخته و محتوای آن را به شکل زیر پر میکنیم:
public interface IMongoDbRepository { Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new(); }
public class MongoRepository : IMongoDbRepository { private IMongoDbContext _mongoDbContext ; public MongoRepository(IMongoDbContext mongoDbContext) { _mongoDbContext = mongoDbContext; } public async Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new() { var collection = GetCollection<TEntity>(); var entities = await collection.Find(filter).ToListAsync(); return entities; } private IMongoCollection<TEntity> GetCollection<TEntity>() { return _mongoDbContext.GetCollection<TEntity>(); } }
الگوی بالا در یک کنترلر به شرح زیر استفاده شده است:
public class HomeController : Controller { private IMongoDbRepository _mongoDbRepository; public HomeController(IMongoDbRepository mongoDbRepository) { this._mongoDbRepository = mongoDbRepository; } // GET: Home public async Task<ActionResult> Index() { var filter = Builders<Resturant>.Filter.Gte("Capacity", 400); var c =await _mongoDbRepository.GetMany<Resturant>(filter); return View(c); } }
MongoRepository.zip
تزریق وابستگیهای رایج ASP.NET MVC به برنامه
ioc.For<IIdentity>() .Use( () => (HttpContext.Current != null && HttpContext.Current.User != null) ? HttpContext.Current.User.Identity : null);
- جزئیات نحوه پیاده سازی یک Storage Provider برای ASP.NET Identity
- تشریح اینترفیس هایی که باید پیاده سازی شوند، و نحوه استفاده از آنها در ASP.NET Identity
- ایجاد یک دیتابیس MySQL روی Windows Azure
- نحوه استفاده از یک ابزار کلاینت (MySQL Workbench) برای مدیریت دیتابیس مذکور
- نحوه جایگزینی پیاده سازی سفارشی با نسخه پیش فرض در یک اپلیکیشن ASP.NET MVC
پیاده سازی یک Storage Provider سفارشی برای ASP.NET Identity
- <UserStore<TUser
- IdentityUser
- <RoleStore<TRole
- IdentityRole
Roles
در مخزن پیش فرض ASP.NET Identity EntityFramework کلاسهای بیشتری برای موجودیتها مشاهده میکنید.
- IdentityUserClaim
- IdentityUserLogin
- IdentityUserRole
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user) { ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id); return Task.FromResult<IList<Claim>>(identity.Claims.ToList()); }
public class IdentityUser : IUser { public IdentityUser(){...} public IdentityUser(string userName) (){...} public string Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public string SecurityStamp { get; set; } }
public class UserStore : IUserStore<IdentityUser>, IUserClaimStore<IdentityUser>, IUserLoginStore<IdentityUser>, IUserRoleStore<IdentityUser>, IUserPasswordStore<IdentityUser> { public UserStore(){...} public Task CreateAsync(IdentityUser user){...} public Task<IdentityUser> FindByIdAsync(string userId){...} ... }
public class IdentityRole : IRole { public IdentityRole(){...} public IdentityRole(string roleName) (){...} public string Id { get; set; } public string Name { get; set; } }
public class RoleStore : IRoleStore<IdentityRole> { public RoleStore(){...} public Task CreateAsync(IdentityRole role){...} public Task<IdentityRole> FindByIdAsync(string roleId){...} .... }
- MySQLDatabase: این کلاس اتصال دیتابیس MySql و کوئریها را کپسوله میکند. کلاسهای UserStore و RoleStore توسط نمونه ای از این کلاس وهله سازی میشوند.
- RoleTable: این کلاس جدول Roles و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserClaimsTable: این کلاس جدول UserClaims و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserLoginsTable: این کلاس جدول UserLogins و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserRolesTable: این کلاس جدول UserRoles و عملیات CRUD مربوطه به آن را کپسوله میکند.
- UserTable: این کلاس جدول Users و عملیات CRUD مربوط به آن را کپسوله میکند.
ایجاد یک دیتابیس MySQL روی Windows Azure
در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.
4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیکترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.
5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد میتوانید از قسمت ADD-ONS آن را مدیریت کنید.
6. همانطور که در تصویر بالا میبینید، میتوانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.
7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.
ایجاد جداول ASP.NET Identity در یک دیتابیس MySQL
CREATE TABLE `IdentityMySQLDatabase`.`users` ( `Id` VARCHAR(45) NOT NULL, `UserName` VARCHAR(45) NULL, `PasswordHash` VARCHAR(100) NULL, `SecurityStamp` VARCHAR(45) NULL, PRIMARY KEY (`id`)); CREATE TABLE `IdentityMySQLDatabase`.`roles` ( `Id` VARCHAR(45) NOT NULL, `Name` VARCHAR(45) NULL, PRIMARY KEY (`Id`)); CREATE TABLE `IdentityMySQLDatabase`.`userclaims` ( `Id` INT NOT NULL AUTO_INCREMENT, `UserId` VARCHAR(45) NULL, `ClaimType` VARCHAR(100) NULL, `ClaimValue` VARCHAR(100) NULL, PRIMARY KEY (`Id`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userlogins` ( `UserId` VARCHAR(45) NOT NULL, `ProviderKey` VARCHAR(100) NULL, `LoginProvider` VARCHAR(100) NULL, FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userroles` ( `UserId` VARCHAR(45) NOT NULL, `RoleId` VARCHAR(45) NOT NULL, PRIMARY KEY (`UserId`, `RoleId`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade on update cascade, FOREIGN KEY (`RoleId`) REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`) on delete cascade on update cascade);
ایجاد یک اپلیکیشن ASP.NET MVC و پیکربندی آن برای استفاده از MySQL Provider
6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.
7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگیها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیجها را حذف میکنیم.
8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.
9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.
10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.
11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.
12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.
public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase()))) { }
13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.
<add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />
مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.
اجرای اپلیکیشن و اتصال به دیتابیس MySQL
5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.
6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.
برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.
هم چنین در قسمت Add folders and core references تیک گزینهی Web Api را نیز فعال مینماییم.
حال احتیاج به نصب پکیج OData با استفاده از nuget package manager داریم. کافیست دستور زیر را در package manager console وارد نماییم.
Install-Package Microsoft.AspNet.Odata
این دستور آخرین ورژن Odata package را از nuget دانلود مینماید.
بعد از نصب شدن OData نیاز به اضافه کردن یک Model داریم. کلاسی را به نام Product در پوشهی Models میسازیم.
کلاس Product.cs حاوی فیلدهای زیر است.
namespace ProductService.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Category { get; set; } } }
پراپرتی Id، کلید این entity است و کلاینت میتواند کوئری را بر روی entity، به وسیلهی key بزند. برای مثال برای گرفتن Product با Id برابر 2، باید این url را ارسال نمود "(2)Products/"
پرواضح است که Id در Database به عنوان Primary key در نظر گرفته شده است.
حال احتیاج به نصب Entity Framework داریم که با ارسال دستور زیر از طریق nuget نصب خواهد شد
Install-Package EntityFramework
بعد از نصب کردن ef نیاز به اضافه کردن connection string در web config داریم.
<connectionStrings> <add name="ProductsContext" connectionString="Data Source=.; Initial Catalog=ProductsContext; Integrated Security=True;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" /> </connectionStrings>
الان میتوانیم کلاس ProductsContext را درون پوشهی Models ایجاد نماییم. محتویات آن را به صورت زیر وارد مینماییم
using System.Data.Entity; namespace ProductService.Models { public class ProductsContext : DbContext { public ProductsContext() : base("name=ProductsContext") { } public DbSet<Product> Products { get; set; } } }
درون Constructor کلاس ProductsContext، داریم name=ProductsContext که باید برابر name درون connection string باشد.
حال نیاز به کانفیگ OData داریم. درون پوشهی App_Start و کلاس WebApiConfig.cs محتویات زیر را جایگزین متد register نمایید:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Product>("Products"); config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: null, model: builder.GetEdmModel()); } }
این کد دو فرآیند زیر را انجام میدهد
1) ساخت Entity Data Model (EDM)
2) اضافه کردن route
EDM یک مدل انتزاعی از data است. EDM برای تولید سند metadata استفاده میشود. کلاس ODataModelBuilder برای ساخت EDM با استفاده از default naming convention میباشد که باعث کاهش کدها میشود. ضمنا کلاس MapODataServiceRoute برای ساخت OData v4 route میباشد. همانگونه که اطلاع دارید، تعریف route برای مدیریت کردن WebApi و چگونگی مسیریابی درخواستهای http میباشد.
اگر application شما احتیاج به چند OData endpoint داشته باشد، میتوانید برای هر کدام routeهای جدا و همچنین نام یکتایی را برای routeName و routePrefix آن در نظر بگیرید.
اضافه کردن OData Controller
یک Controller، کلاسی برای مدیریت کردن درخواستهای http میباشد. شما باید Controllerهای مجزایی را برای هر entity set در OData service خود بسازید. در این مقاله Controller مربوط به موجودیت Product را میسازیم.
در Solution Explorer با کلیک راست بر روی پوشهی Controller، کلاسی به نام ProducsController را میسازیم. دقت کنید نام آن حتما باید به Controller ختم شود.
در OData V3 میتوانیم Controller را با استفاده از Scaffolding بسازیم؛ ولی در V4 این ویژگی وجود ندارد!
محتویات زیر را در این کنترلر اضافه مینماییم:
using ProductService.Models; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web.Http; using System.Web.OData; namespace ProductService.Controllers { public class ProductsController : ODataController { ProductsContext db = new ProductsContext(); private bool ProductExists(int key) { return db.Products.Any(p => p.Id == key); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } }
این مرحلهی ابتدایی از پیاده سازی کنترلر میباشد و در قسمت بعد به پیاده سازی CRUD مربوط به آن میپردازیم.
Querying The Entity Set
این 2 متد را به کنترلر خود اضافه مینماییم
[EnableQuery] public IQueryable<Product> Get() { return db.Products; } [EnableQuery] public SingleResult<Product> Get([FromODataUri] int key) { IQueryable<Product> result = db.Products.Where(p => p.Id == key); return SingleResult.Create(result); }
ویژگی EnableQuery به معنای امکان Query زدن از سمت کلاینت به آن میباشد. FromODataUri نیز برای امکان پاس دادن پارامتر از طریق Uri است.
متد Get بدون پارامتر، قادر به برگرداندن تمامی Productها میباشد و متد Get با پارامتر، قادر به برگرداندن آن Product خاص با استفاده از unique Id است.
در صورت داشتن EnableQuery با استفاده از Query Option هایی مثل filter$ و sort$ و غیره از سمت کلاینت قادر به تغییر دادن کوئریهای خود هستیم.
Adding and Entity to Entity Set
برای اجازه دادن به کلاینت، جهت اضافه کردن یک Product به دیتابیس، متد Post زیر را اضافه مینماییم
public async Task<IHttpActionResult> Post(Product product) { if (!ModelState.IsValid) { return BadRequest(ModelState); } db.Products.Add(product); await db.SaveChangesAsync(); return Created(product); }
Updation an Entity
OData از دو روش متفاوت برای Update کردن یک موجودیت استفاده مینماید.
1) Patch : امکان partial update برای موجودیت مربوطه را فراهم میسازد.
2) Put : موجودیت جدید را به صورت کامل جایگزین مینماید.
مشکل روش Put این است که کلاینت مجبور به ارسال تمامی فیلدهای مربوطه میباشد. حتی آن هایی که اساسا تغییری نکردهاند. بنابراین روش Patch ترجیح داده میشود.
در هر صورت ما به پیاده سازی هر دو روش میپردازیم:
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var entity = await db.Products.FindAsync(key); if (entity == null) { return NotFound(); } product.Patch(entity); try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ProductExists(key)) { return NotFound(); } else { throw; } } return Updated(entity); } public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (key != update.Id) { return BadRequest(); } db.Entry(update).State = EntityState.Modified; try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ProductExists(key)) { return NotFound(); } else { throw; } } return Updated(update); }
در قسمت Patch کنترلر از <Delta<T استفاده میکند که typeی است برای track کردن تغییرات در مدل مربوطه.
Deleting an Entity
برای حذف هر موجودیت نیز کافیست متد زیر را به کنترلر خود اضافه نمایید:
public async Task<IHttpActionResult> Delete([FromODataUri] int key) { var product = await db.Products.FindAsync(key); if (product == null) { return NotFound(); } db.Products.Remove(product); await db.SaveChangesAsync(); return StatusCode(HttpStatusCode.NoContent); }
من چند رکورد تستی را به صورت زیر وارد کردهام:
حال پروژهی خود را run نموده و آدرس زیر را وارد نمایید:
http://localhost:YourPort/Products
پاسخ، مجموعهای از entityهای زیر خواهد بود:
{ "@odata.context":"http://localhost:4516/$metadata#Products","value":[ { "Id":1,"Name":"Ali","Price":2.00,"Category":"aaa" },{ "Id":2,"Name":"Reza","Price":1.00,"Category":"bbb" },{ "Id":3,"Name":"Ahmad","Price":0.00,"Category":"ccc" } ] }
شما میتوانید از هر کدام از فیلترهای زیر برای کوئری زدن از کلاینت به سمت سرور استفاده نمایید. بطور مثال هر کدام از اینها پاسخ متفاوت و مربوط به خود را برگشت میدهد:
/Products(2)
Productی با آی دی 2 را بر میگرداند.
/Products?$filter=Id gt 1
محصولی را با آی دی بزرگتر از 1، بر میگرداند.
Products?$select=Name
روی محصولات select زده و فقط فیلد Name آنها را بر میگرداند.
Products?$select=Name,Price
آرایهای از objectهایی با پراپرتی Name و Price را بر میگرداند.
/Products?$top=3
فقط 3 رکورد اول را بر میگرداند.
همانطور که ملاحظه میفرمایید، استفاده از OData باعث کمتر شدن کدهای سمت سرور و همچنین امکان کوئری زدن از سمت کلاینت به سمت سرور را مهیا میکند.
بعد از خواندن این مقاله ممکن است به این مساله فکر کنید که این کار باعث کاهش امنیت میشود. باید عرض کنم که امکانات زیادی برای محدود کردن کوئریها، فراهم شده است و هیچ نگرانی از این بابت وجود ندارد. بطور مثال میتوانید تعیین کنید که از entity مربوطه فقط حداکثر 3 پراپرتی قابلیت کوئری زدن را دارند؛ یا اینکه حداکثر در هر کوئری، 10 رکورد قابلیت پاسخ دادن خواهد داشت.
پس بدین صورت میباشد که شما حداکثر امکانات ممکن را به سمت کلاینت میدهید و اختیار بدان واگذار شده که آیا از این امکانات حداکثری، استفاده نماید یا خیر.
امکانات این پروتکل منحصر به فرد است و در مقالههای بعدی به جزئیات بیشتر و دقیقتری خواهیم پرداخت.
var store = GetStore(); string postCode = null; if (store != null && store.Address != null && store.Address.PostCode != null) postCode = store.Address.PostCode.ToString();
public static TResult IfNotNull<TResult, TSource>( this TSource source, Func<TSource, TResult> onNotDefault) where TSource : class { if (onNotDefault == null) throw new ArgumentNullException("onNotDefault"); return source == null ? default(TResult) : onNotDefault(source); }
var postCode = GetStore() .IfNotNull(x => x.Address) .IfNotNull(x => x.PostCode) .IfNotNull(x => x.ToString());
- این متد فقط با انواع ارجاعی (reference types) کار میکند و میبایست برای کار با انواع مقداری (value types) اصلاح شود.
- با انواع داده ای مثل string چه باید کرد؟ در مورد این نوع دادهها تنها مطمئن شدن از null نبودن کافی نیست. برای مثال در مورد string ، گاهی اوقات ما میخواهیم از خالی نبودن آن نیز مطمئن شویم. و یا در مورد collectionها تنها null نبودن کافی نیست بلکه زمانی که نیاز به محاسبه مجموع و یا یافتن بزرگترین عضو است، باید از خالی نبودن مجموعه و وجود حداقل یک عضو در آن مطمئن باشیم.
public static TResult IfNotDefault<TResult, TSource>( this TSource source, Func<TSource, TResult> onNotDefault, Predicate<TSource> isNotDefault = null) { if (onNotDefault == null) throw new ArgumentNullException("onNotDefault"); var isDefault = isNotDefault == null ? EqualityComparer<TSource>.Default.Equals(source, default(TSource)) : !isNotDefault(source); return isDefault ? default(TResult) : onNotDefault(source); }
return person . IfNotDefault(x => x.Name) . IfNotDefault(SomeOperation, x => !string.IsNullOrEmpty(x));
var avg = students .Where(IsNotAGraduate) .FirstOrDefault() .IfNotDefault(s => s.Grades) .IfNotDefault(g => g.Average(), g => g != null && g.Length > 0);
برای مطالعه بیشتر
Get rid of deep null checks
Chained null checks and the Maybe monad
Maybe or IfNotNull using lambdas for deep expressions
Dynamically Check Nested Values for IsNull Values