using System.Collections.Generic; namespace AutoMapperComparison.Models { public class User { public int Id { get; set; } public string Name { get; set; } public ICollection<Address> Addresses { get; set; } } }
using System.ComponentModel.DataAnnotations.Schema; namespace AutoMapperComparison.Models { public class Address { public int Id { get; set; } public double? Code { get; set; } public string Title { get; set; } public int UserId { get; set; } [ForeignKey(nameof(UserId))] public virtual User User { get; set; } } }
using EntityFramework.BulkInsert.Extensions; using System.Collections.Generic; using System.Data.Entity; using System.Data.SqlClient; namespace AutoMapperComparison.Models { public class AppDbContextInitializer : DropCreateDatabaseAlways<AppDbContext> { protected override void Seed(AppDbContext context) { User user = context.Users.Add(new User { Name = "Test" }); context.SaveChanges(); List<Address> addresses = new List<Address>(); for (int i = 0; i < 500000; i++) { addresses.Add(new Address { Id = i, Code = 1, Title = "Test", UserId = user.Id }); } context.BulkInsert(addresses); base.Seed(context); } } public class AppDbContext : DbContext { static AppDbContext() { Database.SetInitializer(new AppDbContextInitializer()); //Database.SetInitializer<AppDbContext>(null); } public AppDbContext() : base(new SqlConnection(@"Data Source=.;Initial Catalog=AppDbContext;Integrated Security=True"), contextOwnsConnection: true) { Configuration.AutoDetectChangesEnabled = false; Configuration.EnsureTransactionsForFunctionsAndCommands = false; Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false; Configuration.ValidateOnSaveEnabled = false; Configuration.UseDatabaseNullSemantics = false; } public DbSet<User> Users { get; set; } public DbSet<Address> Addresses { get; set; } } }
namespace AutoMapperComparison.Models { public class AddressDto { public int Id { get; set; } public double? Code { get; set; } public string Title { get; set; } public int UserId { get; set; } public string UserName { get; set; } } }
using AutoMapper; using AutoMapper.QueryableExtensions; using AutoMapperComparison.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace AutoMapperComparison { public class Program { public static void Main() { Mapper.Initialize(cfg => { cfg.CreateMap<Address, AddressDto>(); }); Console.WriteLine($"Create Db {DateTimeOffset.UtcNow}"); using (AppDbContext db = new AppDbContext()) { db.Database.Initialize(force: true); db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); //Removes all clean buffers from the buffer pool, and columnstore objects from the columnstore object pool Console.WriteLine(db.Addresses.ProjectTo<AddressDto>()); Console.WriteLine(db.Addresses.Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name })); } Console.WriteLine($"Normal Select {DateTimeOffset.UtcNow}"); using (AppDbContext db = new AppDbContext()) { db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); Stopwatch watch = Stopwatch.StartNew(); List<AddressDto> addresses = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList(); List<AddressDto> addresses2 = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList(); watch.Stop(); Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}"); } Console.WriteLine($"AutoMapper Exec {DateTimeOffset.UtcNow}"); using (AppDbContext db = new AppDbContext()) { db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); Stopwatch watch = Stopwatch.StartNew(); List<AddressDto> addresses = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList(); List<AddressDto> addresses2 = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList(); watch.Stop(); Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}"); } Console.ReadKey(); } } }
حال نتایج بدست آمده، در قسمت پایینتر آن نمایان میشود:
البته نتیجهی این آزمایش بسته به سخت افزار سیستم شما ممکن است کمی متفاوت باشد.
در سه آزمایش دیگر به صورت متوالی نتیجهی زیر بدست آمد:
Normal | AutoMapper |
2451 | 2378 |
2120 | 2111 |
2202 | 2124 |
اگر این مقدار جزئی از تفاوت بین دو نوع مختلف آزمایش را مورد نظر نگیریم، میتوان گفت که هر دو روش نتیجهی کاملا یکسانی خواهند داشت. فقط با استفاده از AutoMapper کدهای کمتری نوشته شدهاست!
اما دلیل چیست؟ از آنجایی که ProjectTo از Dto به Model انجام شده و Lambda Expressionی که به سمت Entity Framework فرستاده شدهاست با روش Normal کاملا برابر است و بقیهی عملیات توسط EF انجام میشود، با قاطعیت میتوان گفت که هر دو روش ذکر شده از نظر Performance کاملا یکسان خواهند بود.
نکته: البته به این موضوع باید توجه شود که اگر همین آزمایش را بطور مثال با استفاده از یک Listی از رکوردهای درون Memory ساخته شده توسط خودمان انجام دهیم، آن موقع نتیجهی یکسانی نخواهیم داشت، به دلیل اینکه EFی دیگر وجود نخواهد داشت که مسئولیت بازگشت دادهها را بر عهده بگیرد. از آنجائیکه اکثر کارهایی که توقع داریم AutoMapper برای ما انجام دهد، توسط ORM بازگشت داده میشود، پس میتوان گفت نکتهی فوق تقریبا در دنیای واقعی رخ نخواهد داد و باعث مشکل نخواهد شد.
LineageOS چیست؟
یکی از مهمترین مزایای استفاده از گوشیهای اندرویدی نسبت به iOS ای، آزادی نصب نرم افزار و خصوصا سیستم عاملهای مختلف بر روی آنها است. در حال حاضر، محبوبترین و پر استفادهترین نگارش آزاد اندروید که مستقل از گوگل عمل میکند، LineageOS نامدارد (لینیایج OS) که پیشتر با نامهای CyanogenMod و قبل از آن، Cyanogen (سیانوژن) ارائه میشد. یکی از مهمترین مزایای آن، امکان نصب آخرین نگارش اندروید بر روی گوشیهایی است که دیگر پشتیبانی رسمی نمیشوند و خط تولید آنها خاتمه یافتهاست.
به LineageOS یک Custom ROM هم گفته میشود. ROM مخفف read-only memory است و دقیقا جائیاست که هستهی Android در آن مشغول به کار است. بنابراین منظور از Custom ROM، همان نگارش سفارشی از Android است. به عملیات نصب LineageOS در اصطلاح Flashing هم گفته میشود که به معنای بازنویسی قسمتی از یک نرمافزار، با نرمافزار دیگری است.
پیشنیازهای ضروری نصب LineageOS
- داشتن یک گوشی یا تبلت سازگار با آن (متاسفانه سایت lineageos.org با IP ایرانی باز نمیشود)
- دسترسی به یک کابل USB مخصوص گوشی
- داشتن یک کامپیوتر دسکتاپ و یا لپتاپ
- دسترسی به اینترنت
- زمان! ... (انجام این عملیات برای من در بار اول، حدودا یک روز طول کشید! (صرف نظر از تحقیقات یک هفتهای روش انجام آن) البته نه به علت طولانی بودن زمان نصب آن، بلکه به علت وجود نکات ریزی که در هیچ مستنداتی، به صورت مدون پیدا نخواهید کرد و عدم آشنایی با آنها ممکن است سبب بروز حملهی قلبی، به علت در دست داشتن سخت افزاری شود که هم اکنون کل آنرا فرمت کردهاید و ... راهنماهای ارائه شدهی در اینترنت هم بر روی آن کار نمیکنند! به یک چنین سخت افزاری، brick یا «پاره آجر» هم گفته میشود!)
دریافت Custom ROM سازگار با گوشی یا تبلت
مرحلهی اول نصب LineageOS، دریافت Custom ROM آن است. برای این منظور به آدرس download.lineageos.org مراجعه کرده و ابتدا از منوی سمت چپ صفحه، گوشی خود را پیدا کنید و سپس با انتخاب آن، امکان دریافت ROM مخصوص آنرا خواهید یافت.
نکتهی مهم! متاسفانه در اولین دریافت من از این سایت، به علت ناقص بودن دانلود، فایل دریافتی به همراه CRC Error بود و در زمان نصب فایل zip آن، خطای کلی e1001 ظاهر شد و نه هیچ چیز دیگری. این لحظه واقعا لحظهای است که ممکن است عرق سرد بر روی پیشانی شما ظاهر شود! به صورت اتفاقی با بررسی فایل zip آن بر روی کامپیوتر متوجه شدم که فایل، ناقص دریافت شده. به همین جهت پیش از شروع به نصب، فایل zip را در یک برنامهی باز کنندهی آنها مانند winrar و یا 7-zip باز کرده و بر روی دکمهی test آنها کلیک کنید. اگر خطایی را گزارش ندادند، شروع به ادامهی مراحل نصب کنید.
دریافت فایل Recovery سفارشی
در اینجا نیاز است با دو واژهی جدید bootloader و recovery آشنا شد. زمانیکه گوشی خودتان را روشن میکنید، اولین نرم افزاری که حتی پیش از سیستم عامل اجرا میشود، bootloader نام دارد که کار آن آغاز سایر پروسهها است. بعد از بارگذاری بوتلودر، برنامهی دیگری به نام recovery، کار بارگذاری سیستم عامل را انجام میدهد. بوتلودر و recovery پیشفرض اندروید، اجازهی نصب یک custom ROM را نمیدهند. به همین جهت نیاز است این برنامهی recovery را با یک نمونهی سفارشی بازنویسی کرد که این نمونهی سفارشی در اینجا TWRP نام دارد و نمونهی مخصوص گوشی خود را میتوانید با جستجوی در لیست https://twrp.me/Devices دریافت کنید. ابتدا نوع گوشی و سپس مدل آنرا یافته و سپس در صفحهای که ظاهر میشود، بر روی download link آن کلیک کنید تا لیست فایلهای موجود ظاهر شوند. در ابتدا آخرین نگارش موجود را دریافت کنید.
یک تجربه! متاسفانه آخرین نگارش TWRP دریافت شده، بر روی گوشی من کار نکرد و پس از نصب آن، مدام وارد همان سیستم عامل قبلی، با ارائهی پیام «Recovery is NOT SEANDROID Enforcing» میشد و هیچ تاثیری را نداشت. در این حالت نصب نگارش قدیمیتر 3.3.1، کار کرد. بنابراین بهتر است چندین نگارش آنرا دریافت کنید؛ تا در صورت لزوم بتوانید یکی یکی، آنها را آزمایش کنید.
دریافت Google Apps
LineageOS به همراه برنامههای گوگل، مانند play store و امثال اینها نیست. به همین جهت نیاز است آنها را از آدرس https://opengapps.org دریافت کنید. در اینجا دقت داشته باشید که چه چیزی را انتخاب میکنید! برای نمونه برای گوشی من گزینههای ARM، نگارش 10 و pico انتخاب شدند و سپس کلیک بر روی دکمهی دانلود. گزینهی pico، یکی از کم حجمترین نگارشها است و همینقدر برای شروع به کار، کافی است. نگارش را 10 انتخاب میکنیم چون میخواهیم اندروید 10 را نصب کنیم و انتخاب معماری CPU گوشی هم مهم است. با استفاده از برنامهای مانند device info، به برگهی CPU آن مراجعه کرده و CPU Type گوشی خود را دقیق بررسی کنید. اگر مانند گوشی من، 32bit بود، باید ARM را انتخاب کنید و اگر 64bit بود، گزینهی ARM64 را انتخاب کنید و اگر یک گوشی قدیمی را مانند ASUS دارید، ممکن است CPU آن از نوع intel و x86 باشد.
دریافت برنامهی فعالسازی دسترسی root
اگر میخواهید دسترسی root هم داشته باشید (این گزینه اختیاری است و من آنرا نصب نکردم)، در نگارشهای قبلی LineageOS از برنامهای به نام SU برای انجام اینکار استفاده میشد. این برنامه دیگر نگهداری نمیشود و نباید آنرا به همراه آخرین نگارش LineageOS نصب کرد (خیلی مهم!)؛ وگرنه گوشی شما را حتما به هم خواهد ریخت. برنامهی جایگزین آن Magisk نام دارد که باز هم من آنرا توصیه نمیکنم! چون اگر به انجمنهای LineageOS مراجعه کنید، مشاهده شدهاست که پس از نصب به روز رسانیهای جدید هفتگی LineageOS، ممکن است به علت عدم سازگاری با Magisk، سیستم عامل گوشی شما بالا نیاید و در یک حلقهی بیپایان قرار بگیرید. به همین جهت بهتر است از این گزینه صرفنظر کنید.
آماده سازی گوشی برای اتصال USB و اجرای فرامین بر روی آن
مرحلهی بعد، نصب برنامهی recovery سفارشی است. برای اینکار نیاز است گوشی خود را توسط سیم USB، به یک کامپیوتر متصل کرده و سپس توسط برنامهای خاص که در ادامه معرفی میشود، برنامهی TWRP را بر روی آن نصب کرد. به همین جهت به قسمت «تنظیمات» گوشی اندرویدی خود رفته و گزینهی «دربارهی دستگاه (About)» را پیدا کنید. سپس بر روی شمارهی build آن «Build Number»، هفت بار ضربه بزنید. اینکار سبب میشود تا یک منوی مخفی به نام «Developer Mode» یا «گزینههای توسعه دهندگان/برنامه نویسان»، به لیست منوهای تنظیمات سیستم عامل فعلی اضافه شود. پس از فعال شدن «Developer Mode»، به این گزینه وارد شده و دو گزینهی زیر را در آن فعال کنید:
- USB debugging
- OEM unlocking
اکنون اگر گوشی خود را از طریق سیم USB به کامپیوتر متصل کنید، یک دیالوگ باکس پرسشی، در اندروید جاری ظاهر میشود که درخواست دسترسی به ADB را از شما سؤال میپرسد. گزینهی «Always Allow From This Computer» را انتخاب کرده و با کلیک بر روی OK، این دسترسی را فعال کنید.
دریافت برنامههای انتقال اطلاعات به گوشی اندرویدی
پس از دریافت فایلهای مورد نیاز (TWRP.img, firmware.zip و gapps.zip)، اکنون نوبت به نصب TWRP.img است تا برنامهی recovery پیشفرض گوشی را با یک نمونهی سفارشی که امکان نصب custom ROM را میسر میکند، بازنویسی کنیم. بر روی گوشیهای سامسونگ، برنامهی ODIN یک چنین قابلیتی را به همراه دارد.
البته اگر کمی جستجو کنید، به دستورات زیر هم خواهید رسید که توسط برنامهی Minimal_ADB_Fastboot قابل اجرا هستند:
adb devices adb reboot bootloader fastboot devices fastboot flash recovery TWRP.img fastboot reboot-bootloader
البته برنامهی Minimal_ADB_Fastboot، برنامهی بسیار مفیدی است و در ادامه کاربردهایی از آنرا مطالعه خواهید کرد.
بررسی امنیتی مهم! آیا فایل ROM دریافت شده، بر روی گوشی من نصب میشود؟!
در ادامه پیش از نصب، یکبار گوشی را فرمت میکنیم. در این حال اگر در حین نصب، پیام سازگار نبودن فایل ROM را دریافت کنیم، بسیار دیر است! به همین جهت پس از نصب برنامهی Minimal_ADB_Fastboot، به پوشهی آن وارد شده و خط فرمان را در آنجا آغاز کنید. برای این منظور فقط کافی است بر روی فایل cmd-here.exe کلیک کنید. سپس فرامین زیر را اجرا کنید (با این فرض که گوشی شما از طریق سیم USB به کامپیوتر متصل است و همچنین دسترسی دیباگی را هم که در گوشی عنوان شد، دادهاید):
adb devices adb shell getprop ro.product.device adb shell getprop ro.build.product
سپس فایل custom ROM دریافت شده را باز کرده و به پوشهی «META-INF\com\google\android» آن وارد شوید. در اینجا فایل متنی updater-script را باز کنید. برای مثال در مورد گوشی من، چنین سطری در ابتدای آن درج شده:
assert(getprop("ro.product.device") == "j7elte" || getprop("ro.build.product") == "j7elte" || abort("E3004: This package is for device: j7elte; this device is " + getprop("ro.product.device") + "."););
بنابراین حتما پیش از مطالعه و اجرای ادامهی بحث، مقادیر این ویژگیها را با سطر اول فایل updater-script انطباق دهید تا اگر یکی نبودند، به اشتباه گوشی خود را فرمت نکنید!
البته در جائی دیدم که عدهای برای «خوراندن» rom سفارشی دریافت شده، این سطر بررسی را از فایل یاد شده، پاک کرده و سپس فایل zip جدیدی را تولید و نصب کردهاند. بهتر است اینکار را نکنید و با جستجوی دقیق مطمئن شوید که یک چنین تغییری، برای سیستم شما مشکلی را ایجاد نمیکند!
بازنویسی برنامهی recovery گوشی توسط ODIN
پس از دریافت برنامهی odin، نیاز است گوشی خود را خاموش کنید. فرض بر این است که پیشتر حداقل از contacts خود پشتیبان تهیه کردهاید. چون از این قسمت به بعد، به مراحل بدون بازگشتی قدم خواهیم گذاشت و قرار است گوشی را کاملا فرمت کنیم!
پس از خاموش کردن گوشی، اکنون نیاز است گوشی را در حالت download بالا بیاوریم. برای اینکار سه دکمهی Volume Down + Home + Power با هم بفشارید. بنابراین ابتدا دکمهی «کاهش صدا» را نگه دارید و رها نکنید، سپس دکمهی home را نگه دارید و رها نکنید و در آخر دکمهی power را نگه دارید تا گوشی به حالت ویژهی download وارد شود.
البته در ابتدا یک صفحهی اخطار را نمایش میدهد که در آن درج شده برای ادامه نیاز است دکمهی «افزایش صدا» را بفشارید.
پس از ظاهر شدن تصویر فوق، اینبار دکمهی «افزایش صدا» را بفشارید تا وارد حالت دانلود شوید. در اینجا «حالت دانلود» یعنی گوشی قابلیت دریافت فایلی را پیدا کردهاست.
- در اینجا در قسمت AP، فایل tar مربوط به TWRP را انتخاب کنید.
- سپس در برگهی options، تیک گزینهی Auto reboot را بردارید (بسیار مهم!). اگر این تیک را برندارید، پس از کار نوشتن برنامهی recovery سفارشی، گوشی شما reboot شده و ... وارد برنامهی recovery ... نمیشود! چون سیستم امنیتی توکار اندروید، آنرا با نمونهی اصلی جایگزین میکند!
- اکنون بر روی دکمهی start کلیک کنید تا کار بازنویسی شروع شود.
پس از پایان بازنویسی برنامهی recovery، باید وارد این برنامهی جدید بشویم که روش ورود به آن به صورت زیر است:
پس از پایان بازنویسی، در بعضی از گوشیها در همین حالت که گوشی، حالت download را نمایش میدهد، اگر ترکیب کلیدهای «Volume Up + Power + Home» را بفشارید (اینبار دکمهی «افزایش صدا» است و نه کاهش صدا)، وارد این برنامهی recovery جدید میشوید. اما در مورد گوشی من چنین چیزی رخ نداد. در این حالت تنها روشی که پاسخ داد، «خارج کردن باطری گوشی» بود (در همین حالتی که صفحهی آبی رنگ download نمایش داده میشود، باطری را خارج کنید)؛ چون حتی در حالت خاموش کردن معمولی هم برنامهی recovery سفارشی را پاک و نمونهی اصلی را جایگزین میکرد!
سپس سیستم را به صورت معمولی روشن نکنید. اینبار نیاز است وارد منوی recovery شویم. بنابراین مجددا باطری را قرار داده و اینبار با فشردن ترکیب کلیدهای «Volume Up + Power + Home» به منوی جدید recovery وارد خواهیم شد.
مرحلهی آخر! نصب سیستم عامل جدید و برنامههای گوگل
تا اینجا باید وارد منوی recovery جدید شده باشید. روش خارج کردن باطری را هم فراموش نکنید! (چون اگر سیستم به صورت معمولی ریاستارت شود، یا حتی به صورت معمولی خاموش شود، برنامهی recovery سفارشی را بیاثر کرده و پاک میکند)
- پس از بارگذاری برنامه، پیام «Swipe to Allow Modifications» ظاهر میشود. برای این منظور، فلش آبی رنگ ظاهر شده را به سمت راست بکشید تا بتوانید وارد برنامه شوید.
- اکنون این مراحل را طی کنید:
الف) انتخاب Wipe
در اینجا در ابتدا گزینهی Format Data را انتخاب کنید.
سپس مجددا فلش آبی رنگ پایین صفحه را به سمت راست بکشید تا کار فرمت کردن سیستم شروع شود.
در ادامه در همین قسمت گزینهی Advanced Wipe را انتخاب کرده (همیشه با انتخاب دکمهی back میتوان به منوی اصلی و گزینههای آن رسید) و Dalvik / ART Cache,Data, System, Cache, Internal storage را انتخاب کنید. سپس مجددا فلش آبی رنگ پایین صفحه را به سمت راست بکشید تا کار پاک کردن سیستم شروع شود. در اینجا همه چیز را منهای SD Card، پاک خواهیم کرد. بدون انجام اینکار، وارد یک حلقهی بینهایت خواهید شد و سیستم اصلی پس از نصب، راه اندازی نمیشود (آزمایش کردم!).
ب) انتقال فایلهای Custom ROM و GApps به گوشی
اکنون به کامپیوتر خود و پوشهی محل نصب برنامهی Minimal_ADB_Fastboot وارد شده و خط فرمان را در آنجا آغاز کنید. برای این منظور فقط کافی است بر روی فایل cmd-here.exe کلیک کنید. سپس فرامین زیر را اجرا کنید تا فایلها به گوشی منتقل شوند:
adb devices adb push LINEAGE.zip /sdcard/ adb push GAPPS.zip /sdcard/
1- فایلهای custom ROM و GApps دریافت شده را به درون پوشهی Minimal_ADB_Fastboot کپی کنید. در اینجا منظور از LINEAGE.zip نام کاملی مانند lineage-17.1-20210114-nightly-j7elte-signed.zip است که دریافت کردهاید و همچنین منظور از GAPPS.zip، نام کاملی مانند open_gapps-arm-10.0-pico-20210116.zip است.
2- برای اجرای این دستورات، نیازی به داشتن یک sdcard نیست. نامی که در اینجا ذکر شده، فقط یک نام پوشهی جدید، در گوشی شما است که قرار است در ادامه فایلها را از آن انتخاب کنیم.
یک نکتهی تکمیلی: در حالت منوی recovery و بعد از پاک کردن همه چیز، اگر فولدرهای گوشی در windows explorer مشخص نیستند، باید آنها را mount کرد تا بشود فایلها را به آنها کپی کرد (روش دوم کپی کردن فایلها به گوشی). اگر به منوی ابتدایی TWRP دقت کنید، یک گزینهی mount هم دارد که دقیقا برای همین منظور است. پوشهها را که mount کردید، در windows explorer جهت کپی کردن معمولی ظاهر میشوند.
ج) نصب نهایی سیستم عامل و برنامههای گوگل
پیش از هر کاری به گزینهی Settings در برنامهی TWRP مراجعه کرده و در برگهی General آن، تیک زیر را بر دارید: Prompt to install TWRP app if not installed!
اکنون که فایلهای custom ROM و GApps به گوشی کپی شدند، از منوی اصلی TWRP، اینبار گزینهی Install را انتخاب کنید (همانطور که عنوان شد، در اینجا همیشه دکمهی back، برای بازگشت به صفحهی اصلی کار میکند).
اگر از طریق دستورات adb فایلها را به پوشهی sdcard منتقل کرده باشید، به صورت خودکار اولین فایل انتخاب شده همان فایل ROM است. سپس بر روی دکمهی «Add more zips» کلیک کرده و فایل zip مربوط به GApps را انتخاب کنید. در بالای صفحه «two of max 10 File queued» باید ظاهر شده باشد (مهم) که به معنای تعداد فایلهای موجود در صف نصب است. اکنون فلش آبی رنگ پایین صفحه را به سمت راست بکشید تا کار نصب شروع شود.
پس از پایان نصب این دو برنامه، یکبار بر روی دکمهی Wipe cache/dalvik کلیک کنید (به همراه به سمت راست کشیدن دکمهی فلش آبی پایین صفحه) و سپس بر روی دکمهی Reboot System تا ... وارد اندروید 10 شوید!
یک نکته: در اینجا در حین reboot سؤال میپرسد که آیا نیاز است TWRP را نیز به صورت جداگانهای نصب کند. عنوان کنید، خیر.
چگونه به روز رسانیهای LineageOS را نصب کنیم؟
LineageOS هفتهای یکبار، آخرین به روز رسانیهای اندروید را توزیع میکند. برای نصب آنها پیامی را ظاهر کرده و امکان دانلود را فراهم میکند. پس از دانلود، اگر بر روی دکمهی install کلیک کنید، به صورت خودکار شما را وارد منوی recovery فوق میکند (و نه نصب خودکار). در اینجا تنها کاری را که باید انجام دهید، انتخاب گزینهی install است و سپس انتخاب پوشهی data/lineageos_updates که محل قرار گیری این فایل zip دریافت شدهاست. با انتخاب فایل zip، مراحل نصب آن مانند قبل است. پس از پایان نصب، یکبار بر روی دکمهی پاک کردن کش dalvik کلیک کنید و سپس بر روی reboot. کش dalvik همواره به صورت خودکار توسط اندروید ساخته میشود و پاک کردن آن مشکلی را ایجاد نمیکند.
پس از راه اندازی مجدد سیستم، به منوی Settings>about phone>lineageOS مراجعه کرده و فایل zip قدیمی را حذف کنید (در همان صفحهای که پیام دریافت و نصب را نمایش میداد، اکنون پیام delete ظاهر شدهاست).
پیش نیازها
- شروع یک پروژهی جدید وب با پشتیبانی از Web API
- نصب دو بستهی نیوگت مرتبط با Structure map 3
PM>install-package structuremap PM>install-package structuremap.web
پیاده سازی IHttpControllerActivator توسط Structure map
using System; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using StructureMap; namespace WebApiDISample.Core { public class StructureMapHttpControllerActivator : IHttpControllerActivator { private readonly IContainer _container; public StructureMapHttpControllerActivator(IContainer container) { _container = container; } public IHttpController Create( HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { var nestedContainer = _container.GetNestedContainer(); request.RegisterForDispose(nestedContainer); return (IHttpController)nestedContainer.GetInstance(controllerType); } } }
نکتهی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب میشود تا کلیه کلاسهای IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.
معرفی StructureMapHttpControllerActivator به برنامه
فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http; using System.Web.Http.Dispatcher; using StructureMap; using WebApiDISample.Core; using WebApiDISample.Services; namespace WebApiDISample { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // IoC Config ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>()); var container = ObjectFactory.Container; GlobalConfiguration.Configuration.Services.Replace( typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
تهیه سرویسی برای آزمایش برنامه
namespace WebApiDISample.Services { public interface IEmailsService { void SendEmail(); } } using System; namespace WebApiDISample.Services { /// <summary> /// سرویسی که دارای قسمت دیسپوز نیز هست /// </summary> public class EmailsService : IEmailsService, IDisposable { private bool _disposed; ~EmailsService() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void SendEmail() { //todo: send email! } protected virtual void Dispose(bool disposeManagedResources) { if (_disposed) return; if (!disposeManagedResources) return; //todo: clean up resources here ... _disposed = true; } } }
نکتهی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شدهاست). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.
استفاده از سرویس تعریف شده در یک Web API Controller
using System.Web.Http; using WebApiDISample.Services; namespace WebApiDISample.Controllers { public class ValuesController : ApiController { private readonly IEmailsService _emailsService; public ValuesController(IEmailsService emailsService) { _emailsService = emailsService; } // GET api/values/5 public string Get(int id) { _emailsService.SendEmail(); return "_emailsService.SendEmail(); called!"; } } }
تزریق وهلهی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت میگیرد.
فراخوانی متد Get آنرا نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2> @section scripts { <script type="text/javascript"> $(function () { $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) { alert(data); }); }); </script> }
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهلهی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
WebApiDISample.zip
مقالات آموزش و راه اندازی PWA
اموزش و راه اندازی وب سایت هایی با تکنولوژی PWA
Turning a ASP.NET Core website into a Progressive Web App (PWA)
Building Progressive Web apps with ASP.NET Core
ASP.NET Web API - قسمت اول
Silverlight هم یک نوع پروژه است، مثل Web و Desktop. اگر پروژهی شما بر مبنای Silverlight هست و نیاز دارید تا امکانات اون رو به صورت سرویس ارائه بدید، میتونید از Web API برای عرضهی این امکانات استفاده کنید.
- فشرده سازی قبل از اجرای برنامه (Pre-Compression) یعنی که شما قبل از اینکه برنامه خود را در محیط اصلی نصب و اجرا کنید، فایلهای اسکریپت آن را فشرده کنید. یعنی کاربران فایل اسکریپت فشرده شده را درخواست و دانلود میکنند و عملیات اضافی در سمت سرور انجام نمیشود. به عنوان مثال شما از فایل JQuery.min.js به جای jquery.js استفاده کنید. یعنی استفاده از نسخه فشرده شده اسکریپت ها.
- فشرده سازی زمان اجرا (Run-time Compression) یعنی فشرده سازی اسکریپتهای مورد نیاز کاربر توسط خود برنامه وب (به صورت خودکار و یا توسط یک ماژول اضافی). این عمل باعث میشود که در هر بار درخواست هر کاربر برای یک فایل، برنامه آن را مجدد فشرده سازی کند (و یا از cache استفاده کند). این عمل به معنی استفاده بیشتر از منابع پر ارزش سرور شما میباشد. به عنوان مثال شما بخواهید در هر مرحله درخواست هر کاربر jquery.js را فشرده کنید!
<link href="Styles/bootstrap.min.css" rel="stylesheet" /> //برای نمایش و استفاده از جعبهی رنگ در بوت استراپ <link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" /> <script src="Scripts/jquery-1.8.2.js"></script> <script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script> <script src="Scripts/bootstrap.min.js"></script> <script src="Scripts/bootstrap-colorpalette.js"></script> <script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script> //حاوی متدهایی برای رسم خط با استفاده از HTML5 <script src="Scripts/draw.js" type="text/javascript"></script> //تعریف کلاینتهای متصل به هاب <script src="Scripts/Whiteboard.js"></script>
تعریف کلاس Hub برنامه :
using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using OnlineSignalRWhiteboard.Model; namespace OnlineSignalRWhiteboard.Hubs { [HubName("onWhiteboard")] public class Whiteboard : Hub { public void OnDrawPen(Point prev, Point current, string color, int width) { Clients.All.drawPen(prev, current, color, width); } public void ClearBoard() { Clients.All.clear(); } } }
کدهای کلاینتهای متصل به هاب در فایل Whiteboard.js :
$(function () { $.connection.hub.logging = true; var whiteboard = $.connection.onWhiteboard; $("#clear").click(function () { //پاک کردن صفحه نمایش whiteboard.server.clearBoard(); }); var color = function (colors) { //تغییر رنگ قلم draw.colour = colors; }; $(".size").click(function () { //تغییر سایز قلم draw.lineWidth = $(this).height(); $('#size').css('height', $(this).height()); }); $.connection.hub.start().done(function () { }) .fail(function () { alert("Could not Connect!"); }); draw.onDraw = function (prev, current, color, width) { //ارسال پارامترها به سمت سرور تا سرور بتواند دادهها را به سمت سایر کلاینتها نیز ارسال کند whiteboard.server.onDrawPen(prev, current, color, width); }; whiteboard.client.drawPen = function (prev, current, color, width) { draw.drawPen(prev, current, color, width); }; whiteboard.client.clear = function () { draw.clear(); }; $('#colorpalette3').colorPalette() .on('selectColor', function (e) { $('#color').css('background-color', e.color); color(e.color); }); var canvas = document.getElementById('draw'); //تغییر سایر کانواس متناسب با پنجرهی مرورگر window.addEventListener('resize', resizeCanvas, false); function resizeCanvas() { canvas.width = window.innerWidth - 20; canvas.height = window.innerHeight - 70; } resizeCanvas(); });
کدهای کامل سمت کلاینت :
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <link href="Styles/bootstrap.min.css" rel="stylesheet" /> <link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" /> <script src="Scripts/jquery-1.8.2.js"></script> <script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script> <script src="Scripts/bootstrap.min.js"></script> <script src="Scripts/bootstrap-colorpalette.js"></script> <script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script> <script src="Scripts/draw.js" type="text/javascript"></script> <script src="Scripts/Whiteboard.js"></script> </head> <body> <form id="form1" runat="server"> <div> <div style="position: static;"> <div> <div> <a data-toggle="collapse" data-target=".navbar-inverse-collapse"> <span></span> <span></span> <span></span> </a> <a href="#">تخته وایت برد آنلاین توسط SignalR و HTML5</a> <div> <ul> <li> <div> <a id="selected-color2" data-toggle="dropdown"> <div style="width: 20px; height: 20px; background: black" id="color"> </div> </a> <ul style="width:293px;"> <li style="display:inline-block;"> <div>رنگ پس زمینه</div> <div id="colorpalette3"></div> </li> </ul> </div> </li> <li></li> <li> <div> <a data-toggle="dropdown" style="width: 120px; height: 20px" href="#"> <hr style="width: 120px; height: 1px; background-color: black;margin: 0px; display: table-cell" id="size"> </a> <ul style="width: 120px"> <li><hr style="width: 140px; height: 1px; background-color: black"></li> <li></li> <li><hr style="width: 140px; height: 3px; background-color: black"></li> <li></li> <li><hr style="width: 140px; height: 7px; background-color: black"></li> <li></li> <li><hr style="width: 140px; height: 10px; background-color: black"></li> <li></li> <li><hr style="width: 140px; height: 20px; background-color: black"></li> </ul> </div> </li> <li></li> <li> <div> <a href="#" id="clear"><i></i>جدید</a> </div> </li> </ul> </div><!-- /.nav-collapse --> </div> </div><!-- /navbar-inner --> </div> </div> <div> <div> <canvas id="draw" style="cursor: crosshair;border: 1px solid black;"></canvas> </div> </div> </form> </body> </html>
امکان ساخت قالب برای پروژههای NET Core.
dotnet new somename --framework net5.0