بازخوردهای پروژه‌ها
خطا در زمان استفاده از پروفایلر
با سلام
پیرو کامنت مورد نظر
این قسمت از برنامه به صورت کامل و درست کار میکند. اما زمانی که پروفایلر را فعال میکنم با این خطا مواجه می‌شوم. جهت ازمایش تنظیمات پروفایلر توی وب کانفیگ کامنت کردم و برنامه به درستی کار کرد. اینم Stack Trace برنامه
[NotSupportedException: Specified method is not supported.]
   EFCache.CachingReader.GetSchemaTable() +28
   System.Data.ProviderBase.SchemaMapping..ctor(DataAdapter adapter, DataSet dataset, DataTable datatable, DataReaderContainer dataReader, Boolean keyInfo, SchemaType schemaType, String sourceTableName, Boolean gettingData, DataColumn parentChapterColumn, Object parentChapterValue) +76
   System.Data.Common.DataAdapter.FillMappingInternal(DataSet dataset, DataTable datatable, String srcTable, DataReaderContainer dataReader, Int32 schemaCount, DataColumn parentChapterColumn, Object parentChapterValue) +94
   System.Data.Common.DataAdapter.FillMapping(DataSet dataset, DataTable datatable, String srcTable, DataReaderContainer dataReader, Int32 schemaCount, DataColumn parentChapterColumn, Object parentChapterValue) +156
   System.Data.Common.DataAdapter.FillFromReader(DataSet dataset, DataTable datatable, String srcTable, DataReaderContainer dataReader, Int32 startRecord, Int32 maxRecords, DataColumn parentChapterColumn, Object parentChapterValue) +79
   System.Data.Common.DataAdapter.Fill(DataTable[] dataTables, IDataReader dataReader, Int32 startRecord, Int32 maxRecords) +309
   System.Data.Common.LoadAdapter.FillFromReader(DataTable[] dataTables, IDataReader dataReader, Int32 startRecord, Int32 maxRecords) +19
   System.Data.DataSet.Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler errorHandler, DataTable[] tables) +157
   DNTProfiler.EntityFramework.Core.DatabaseInterceptor.ReaderExecuted(DbCommand command, DbCommandInterceptionContext`1 interceptionContext) +224
   System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<Reader>b__e(IDbCommandInterceptor i, DbCommand t, DbCommandInterceptionContext`1 c) +13
   System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch(TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed) +679
   System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(DbCommand command, DbCommandInterceptionContext interceptionContext) +402
   System.Data.Entity.Internal.InterceptableDbCommand.ExecuteDbDataReader(CommandBehavior behavior) +166
   System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior) +10
   System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior) +36

[EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details.]
   System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior) +103
   System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan.Execute(ObjectContext context, ObjectParameterCollection parameterValues) +758
   System.Data.Entity.Core.Objects.<>c__DisplayClass7.<GetResults>b__6() +90
   System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction(Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess) +288
   System.Data.Entity.Core.Objects.<>c__DisplayClass7.<GetResults>b__5() +154
   System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute(Func`1 operation) +190
   System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption) +283
   System.Data.Entity.Core.Objects.ObjectQuery`1.Execute(MergeOption mergeOption) +31
   System.Data.Entity.Core.Objects.DataClasses.EntityReference`1.Load(MergeOption mergeOption) +139
   System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.DeferredLoad() +350
   System.Data.Entity.Core.Objects.Internal.LazyLoadBehavior.LoadProperty(TItem propertyValue, String relationshipName, String targetRoleName, Boolean mustBeNull, Object wrapperObject) +85
   System.Data.Entity.Core.Objects.Internal.<>c__DisplayClass7`2.<GetInterceptorDelegate>b__2(TProxy proxy, TItem item) +105
   System.Data.Entity.DynamicProxies.UserRole_BBC6827DA8A54908B2FEB0549B390716893C3DEEF1E93323EDC9D94ABC0097E1.get_Role() +33
   SmartMarket.Services.Members.<>c.<IsInRole>b__9_0(UserRole x) in D:\Bazar\IEM\Dev\Libraries\TikaSoft.SmartMarket.Services\Members\UserExtentions.cs:252
   System.Linq.WhereSelectEnumerableIterator`2.MoveNext() +164
   System.Linq.<DistinctIterator>d__63`1.MoveNext() +220
   System.Linq.WhereEnumerableIterator`1.MoveNext() +77
   System.Linq.Enumerable.FirstOrDefault(IEnumerable`1 source, Func`2 predicate) +94
   SmartMarket.Services.Members.UserExtentions.IsInRole(User user, String roleSystemName, Boolean onlyActiveRoles) in D:\Bazar\IEM\Dev\Libraries\TikaSoft.SmartMarket.Services\Members\UserExtentions.cs:252
   SmartMarket.Services.Members.UserExtentions.IsRegistered(User user, Boolean onlyActiveRoles) in D:\Bazar\IEM\Dev\Libraries\TikaSoft.SmartMarket.Services\Members\UserExtentions.cs:277
   SmartMarket.Services.Authentication.FormsAuthenticationService.GetAuthenticatedUser() in D:\Bazar\IEM\Dev\Libraries\TikaSoft.SmartMarket.Services\Authentication\FormsAuthenticationService.cs:66
   SmartMarket.Web.Framework.WebWorkContext.get_CurrentUser() in D:\Bazar\IEM\Dev\Presentation\TikaSoft.SmartMarket.Web.Framework\WebWorkContext.cs:167
   SmartMarket.Web.Framework.Controllers.SmartController.LogException(Exception exc) in D:\Bazar\IEM\Dev\Presentation\TikaSoft.SmartMarket.Web.Framework\Controllers\SmartController.cs:221
   SmartMarket.Web.Framework.Controllers.SmartController.OnException(ExceptionContext filterContext) in D:\Bazar\IEM\Dev\Presentation\TikaSoft.SmartMarket.Web.Framework\Controllers\SmartController.cs:132
   System.Web.Mvc.Controller.System.Web.Mvc.IExceptionFilter.OnException(ExceptionContext filterContext) +10
   System.Web.Mvc.ControllerActionInvoker.InvokeExceptionFilters(ControllerContext controllerContext, IList`1 filters, Exception exception) +109
   System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState) +721
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +14
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +343
   System.Web.Mvc.Controller.<BeginExecuteCore>b__1c(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState) +25
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +465
   System.Web.Mvc.Controller.<BeginExecute>b__14(AsyncCallback asyncCallback, Object callbackState, Controller controller) +18
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +20
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +374
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +16
   System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__4(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState) +52
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +384
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +48
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +443
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +157



مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت چهارم
تا قسمت سوم توانستیم Xamarin را نصب و پروژه‌ی اولیه آن را بیلد کنیم. سپس کد مشترک بین سه پلتفرم را بر روی Windows اجرا و Edit & continue آن را هم تست کردیم که هم برای UI ای که با Xaml نوشته می‌شود و هم برای منطقی که با CSharp نوشته می‌شود، کار می‌کند.
همانطور که گفتیم، کد UI و Logic برای هر سه پلتفرم مشترک است؛ منتهی به علت امکانات دیباگ فوق العاده و سرعت بیشتر ویندوز، ابتدا آن را بر روی ویندوز تست کردیم و بعد برای تکمیل UI، آن را بر روی Android اجرا می‌کنیم. این بار می‌توانید دو پروژه UWP و iOS را Unload کنید و سپس پروژه Android ای را در صورت Unload بودن Load کنید. (با راست کلیک نمودن روی پروژه). این کار باعث می‌شود Visual Studio بیهوده کند نشود؛ مخصوصا اگر سیستم شما ضعیف است.
ابتدا با موبایل یا تبلت اندرویدی شروع می‌کنیم. اگر چه Xamarin از نسخه‌ی 4.0.3 اندروید به بالا را پشتیبانی می‌کند، ولی توصیه می‌کنم وقتتان را بر روی گوشی‌های اندرویدی کمتر از 4.4 تلف نکنید. دستگاه را می‌توانید، هم به صورت USB و هم به صورت Wifi استفاده کنید. ابتدا باید دستگاه اندرویدی خود را آماده‌ی برای دیباگ کنید. برای این منظور مقاله‌های فارسی و انگلیسی زیادی وجود دارند که می‌توانید از آن‌ها استفاده کنید. من عبارت "اندروید debug" را جستجو کردم و به این مقاله رسیدم. همچنین Android SDK شما باید USB debugging اش نصب شده باشد که البته حجم زیادی ندارد. برای بررسی این مورد ابتدا از وجود فولدر extras\google\usb\_driver درAndroid SDK خود مطمئن شوید. حال سؤال این است که ویژوال استودیو، Android SDK را کجا نصب کرده‌است که خیلی ساده در این لینک توضیح داده شده‌است.
اگر فولدر extras\google\usb\_driver وجود نداشت، باید آن را نصب کنید که خیلی ساده توسط Android SDK Manager امکان پذیر است؛ ولی نه! امکان پذیر نیست!
دلیل: در Xamarin شما همیشه بر روی آخرین SDK‌ها حرکت می‌کنید. این شامل Windows SDK 17134 و Android SDK 27 و iOS SDK 11 است. وقتی از نسخه‌ی فعلی ویژوال استودیو، یعنی 15.8 به نسخه‌ی بعدی ویژوال استودیو که الان Preview است بروید، یعنی 15.9، عملا به این معنا است که به Windows SDK 17763 و Android SDK 28 و iOS SDK 12 می‌روید. این بزرگترین مزیت Xamarin است و این یعنی شما همیشه به صد در صد امکانات هر پلتفرم در زبان CSharp دسترسی دارید و همیشه آخرین SDK هر سیستم عامل در اختیار شماست و اگر دوستی از طریق Swift توانست مثالی از ARKit 2.0 را در iOS 12 پیاده سازی کند، قطعا شما هم می‌توانید. همچنین تیم Xamarin نه تنها این امکانات را بلکه Documentation لازم را نیز در اختیار شما قرار می‌دهد. چون در همین مثال، مستندات Apple به زبان Swift / Objective-C بوده و مستندات Xamarin به زبان CSharp.
حال اگر سری به فولدر Android SDK نصب شده‌ی توسط Visual Studio بزنید، مشاهده می‌کنید که خبری از Android SDK Manager نیست! به صورت رسمی، مدتی است که گوگل در نسخه‌های اخیر Android SDK، دیگر Android SDK Manager را ارائه نمی‌کند و همانطور که گفتم شما الان بر روی آخرین نسخه‌ی Android SDK هستید. هر چند ترفندهایی وجود دارد که این Manager را باز می‌گردانند، ولی لزومی به انجام این کار در Xamarin نیست و شما می‌توانید از Android SDK Manager ای که تیم Xamarin ارائه داده‌است، استفاده کنید. همین مسئله در مورد Android Virtual Device Manager که برای مدیریت Emulator‌ها بود نیز صدق می‌کند.
برای استفاده از این دو، ضمن استفاده از ابزارهای دور زدن تحریم، در ویژوال استودیو، در منوی Tools، به قسمت Android رفته و Android SDK Manager را باز کنید. Android Emulator Manager نیز جایگزین Android Virtual Device Manager ای است که قبلا توسط گوگل ارائه می‌شد. حال بعد از باز کردن Android SDK Manager ارائه شده توسط Xamarin، به برگه‌ی Tools آن بروید و از  قسمت extras مطمئن شوید که Google USB driver تیک خورده باشد.
حال پس از وصل کردن گوشی یا تبلت اندرویدی به سیستم توسط کابل USB و Set as startup project نمودن پروژه‌ی XamApp.Android که در قسمت قبل آن را Clone کرده بودید، می‌توانید پروژه را بر روی گوشی خود اجرا کنید. اگر نام گوشی خود را در کنار دکمه‌ی سبز اجرای پروژه (F5) نمی‌بینید، بستن و باز کردن Visual Studio را امتحان کنید. 

پروژه را که اجرا کنید، اولین بیلد کمی طول می‌کشد (اولین بار دو برنامه بر روی گوشی شما نصب می‌شوند که برای کار دیباگ در Xamarin لازم هستند) و اساسا بیلد یک پروژه‌ی اندرویدی کند است. خوشبختانه به واسطه وجود Xaml edit and continue احتیاجی به Stop - Start کردن پروژه و بیلد کردن برای اعمال تغییرات UI نیست و به محض تغییر Xaml، می‌توانید تاثیر آن را در گوشی خود ببینید. ولی برای هر تغییر CSharp باید Stop - Start و Build کنید که زمان بر است و به همین علت تست بر روی پروژه ویندوزی را برای پیاده سازی منطق برنامه پیشنهاد می‌کنیم. البته در نسخه‌ی 15.9 ویژوال استودیو، سرعت بیلد تا 40% بهبود یافته است.
ممکن است شما گوشی اندرویدی یا تبلت نداشته باشید که بخواهید بر روی آن تست کنید و یا مثلا گوشی شما Android 7 هست و می‌خواهید بر روی Android 8 تست بگیرید. در این جا شما احتیاج به استفاده از Emulator را خواهید داشت.
توجه داشته باشید که Emulator شما ترجیحا نباید ARM باشد و بهتر است یا X86 یا X64 باشد، وگرنه ممکن است خیلی کند شود. همچنین بهتر است Google Play Services داشته باشد. همچنین ترجیحا دنبال گزینه‌ی اجرا کردن Emulator نروید؛ اگر خود ویندوز شما درون یک Virtual Machine در حال اجراست.

ابتدا ضمن جستجو کردن "فعال سازی intel virtualization"، اقدام به فعال سازی این امکان در سیستم خود کنید. این آموزش را مناسب دیدم.
گزینه‌های مطرح: [Google Android Emulator] - [Genymotion] - [Microsoft Hyper-V Android Emulator] که فقط یکی از آنها را لازم دارید.

Google Android Emulator توسط خود Google ارائه می‌شود و دارای Google Play Services نیز هست. بر اساس این آموزش به صفحه Workloads در Visual Studio Installer بروید و از قسمت Xamarin دو مورد "Google Android Emulator API Level 27" و "Intel Hardware Accelerated Execution Manager (HAXM) global install" را نصب کنید. توجه داشته باشید که بدین منظور احتیاج به ابزارهای دور زدن تحریم دارید؛ زیرا نیاز به دسترسی به سرورهای گوگل هست. این Emulator آماده برای دیباگ هست و نیازی به اقدام خاصی نیست.

Genymotion حجم کمتری دارد و برای دانلود احتیاج به ابزارهای دور زدن تحریم را ندارد و اساسا نسبت به بقیه بر روی سیستم‌های ضعیف‌تر، بهتر کار می‌کند. فقط Emulator ای که با آن می‌سازید، به صورت پیش فرض Google Play Services را ندارد که در آخرین نسخه‌های آن گزینه Open  GApps به toolbar اضافه شده که Google Play Services را اضافه می‌کند. (از انجام هر گونه عملیات پیچیده بر اساس آموزش‌هایی که برای نسخه‌های قدیمی‌تر Genymotion هستند، پرهیز کنید). مطابق با ابتدای همین آموزش برای دستگاه‌های اندرویدی، Emulator خود را آماده برای دیباگ کنید.

Microsoft Hyper-V android emulators. مایکروسافت قبلا اقدام به ارائه یک Android Emulator کرده بود که برای نسخه 4 و 5 اندروید بودند و بزرگ‌ترین ضعف آنها عدم پشتیبانی از Google Play Services بود که ادامه داده نشدند. ولی سری جدید ارائه شده توسط مایکروسافت چنین مشکلی را ندارد. اگر CPU شما AMD بوده و روش‌های قبلی برای شما کند هستند یا اساسا کار نمی‌کنند، یا در حال حاضر در حال استفاده از Docker for Windows هستید که از Hyper-V استفاده می‌کنید و قصد استفاده مجدد از منابع موجود را دارید، این نیز گزینه خوبی است که جزئیات آن را می‌توانید در  اینجا  دنبال کنید. این Emulator آماده برای دیباگ هست و نیازی به اقدام خاصی نیست. 

پس از اینکه Emulator خود را ساختید، آن را اجرا کنید. سپس برنامه را از درون ویژوال استدیو اجرا کنید. مطابق نسخه ویندوزی، دوباره یک دکمه دارید و یک Label، عدد بر روی Label، با هر بار کلیک کردن بر روی دکمه، افزایش می‌یابد.
سرعت اجرای این برنامه در Emulator یا گوشی شما برای دیباگ است و در حالت Release، سرعت چندین برابر بهتر خواهد شد و به هیچ وجه تست‌های Performance را بر روی Debug mode انجام ندهید.

حال نوبت به پابلیش پروژه می‌رسد. در این قسمت باید توجه کنید که حجم Apk شما برای پروژه‌ی XamApp مثال ما به 7 مگ می‌رسد که برای یک فرم ساده خیلی زیاد به نظر می‌رسد. ولی اگر شما بجای یک فرم ساده، صد فرم پیچیده نیز داشته باشید، باز هم این حجم به 8 مگ نخواهد رسید. حجم Apk خیلی متاثر از کدهای شما نیست، بلکه شامل موارد زیر است:
1- NET. که خود شامل CLR  & BCL است. (BCL (Base Class Library  مثل کلاس‌های string - Stream - List - File و (CLR (Common language runtime که شامل موارد لازم برای اجرای کدها است. این پیاده سازی بر اساس NET Standard 2.0. بوده که عملا اجازه استفاده از تعداد خیلی زیادی از کتابخانه‌های موجود را می‌دهد، حتی Entity framework core! البته هر کتابخانه حجم DLL‌های خودش را اضافه می‌کند.
2- Android Support libraries که به شما اجازه می‌دهد از تعداد زیادی (و البته نه همه) امکانات نسخه‌های جدید اندروید در پروژه‌تان استفاده کنید که بر روی نسخ قدیمی‌تر Android نیز کار کنند. همچنین با یکپارچگی با Google Play Services عملا خیلی از کارها ساده‌تر و با Performance بهتری انجام می‌شود، مانند گرفتن موقعیت کاربر جاری.
3-  Xamarin essentials . اگر چه در CSharp شما به صد در صد امکانات هر سیستم عامل دسترسی دارید و می‌توانید مثلا مقدار درصد شارژ باطری را بخوانید، ولی اینکار مستلزم نوشتن سه کد CSharp ای برای Android - iOS - Windows است که طبیعتا کار را سخت می‌کند. اما Xamarin Essentials به شما اجازه می‌دهد با یک کد CSharp واحد برای هر سه پلتفرم، با باطری، کلیپ‌بورد، قطب نما و خیلی موارد دیگر کار کنید.
4- Xamarin.Forms. اگر Button و Label ای که در مثال برنامه داشتیم، با یکبار نوشتن بر روی هر سه پلتفرم دارند کار می‌کنند، در حالی که هر پلتفرم، Button مخصوص به خود را دارد؛ این را Xamarin Forms مدیریت می‌کند. علاوه بر این، Binding نیز به عهده‌ی Xamarin Forms است.
5- Prism Autofac Bit Framework: درک آن‌ها نیاز به دنبال کردن آموزش‌های این دوره را دارد؛ ولی به صورت کلی معماری پروژه شما بسیار کارآمد و حرفه‌ای خواهد شد و به کدی با قابلیت نگهداری بالا خواهید رسید. 
6-  Rg Plugins Popup  و  Xamanimation  نیز دو کتابخانه‌ی UI بسیار کاربردی و جالب هستند که در طول این آموزش از آنها استفاده خواهد شد.
حجم 7 مگ برای این تعداد کتابخانه و امکان، خیلی زیاد نیست و شما عملا تعداد زیادی از پروژه‌های خود را می‌توانید با همین حجم ببندید و اگر مثلا به پروژه‌ی Humanizer خیلی علاقه داشته باشید (که در این صورت حق هم دارید!) می‌توانید با اضافه شدن چند کیلوبایت (!) به پروژه آن را داشته باشید. اکثر کتابخانه‌های NET. ای سبک هستند. همچنین موقع قرار گرفتن در پروژه، فشرده سازی نیز می‌شوند و قسمت‌های استفاده نشده‌ی آن‌ها نیز توسط Linker حذف می‌شوند.
علاوه بر این، اجرای برنامه بر روی گوشی‌های ضعیف و قدیمی کمی طول می‌کشد. این مربوط به اجرای برنامه است؛ نه باز شدن فرم مثال ما که دارای Button و Label بود و اگر مثال ما دو فرم داشته باشد (که در آموزش‌های بعدی به آن می‌رسیم) می‌بینید که چرخش بین فرم‌ها بسیار سریع است.

مواردی مهم در زمینه‌ی بهبود عملکرد پروژه‌های Xamarin در Android
در ابتدا باید بدانید Apk شما شامل دو قسمت است؛ یکی کدهای CSharp ای شما که DotNet ای بوده و در کنار کدهای کتابخانه‌هایی چون Json.NET بر روی DotNet اجرا می‌شوند. دیگری کتابخانه‌ای است که مثلا با Java نوشته شده و بعد برای استفاده در CSharp بر روی آن یک Wrapper یا پوشاننده توسعه داده شده‌است. عموما توسعه دهندگان چنین پروژه‌هایی، ابتدا پروژه را به Java می‌نویسند و بعد برای JavaScript - CSharp و ... Wrapper ارائه می‌دهند.
برای بهبود اینها ابزارهایی چون AOT-NDK-LLVM-ProGurad-Linker و ... وجود دارند که سعی می‌کنم به صورت ساده آنها را توضیح دهم.

وظیفه ProGurad این است که از قسمتی از پروژه‌ی شما که بخاطر کتابخانه‌های Java ای، عملا DotNet ای نیست، کدهای اضافه و استفاده نشده را حذف کند.
ممکن است استفاده از ProGurad باعث شود کلاسی که داینامیک استفاده شده است، به اشتباه حذف شود. پروژه XamApp دارای یک ProGuard configuration file است که جلوی چنین اشتباهاتی را حتی الامکان می‌گیرد.
همچنین ProGurad که در داخل Android SDK قرار دارد، به Space در طول مسیر حساس است (!) و با توجه به اینکه مسیر پیش فرض Android SDK نصب شده‌ی توسط ویژوال استودیو دارای Space است (C:\Program Files (x86)\Android\android-sdk)  شما در همان ابتدا دچار مشکل می‌شوید! برای حل این مشکل ابدا فولدر Android SDK را جا به جا نکنید؛ بلکه از امکانی در ویندوز به نام Junction folder یا فولدر جانشین استفاده کنید. بدین منظور دستور زیر را وارد کنید:
mklink /j C:\android-sdk "C:\Program Files (x86)\Android\android-sdk"
این مورد باعث می‌شود که مسیر C:\android-sdk نیز به همان مسیر پیش فرض اشاره کند و این دو مسیر در واقع یکی هستند. امیدوارم این امکان را با قابلیت Shortcut سازی در ویندوز اشتباه نگیرید! حال از منوی Tools > Options > Xamarin > Android Settings مسیر Android SDK را به C:\android-sdk تغییر دهید که فاقد Space است و ویژوال استودیو را ببندید و باز کنید.

NDK که در ادامه SDK برای Android قرار می‌گیرد، Native Development Kit است و باعث می‌شود هم DLL‌های DotNet ای و هم Jar‌های Java ای به فایل‌های so تبدیل شوند. so همان DLL ویندوز است، البته برای Linux و همانطور که احتمالا می‌دانید، پایه Android بر روی Linux است. طبیعتا کامپایل شدن کدها به so، بر روی بهبود سرعت برنامه تاثیر گذار است.

Linker نیز مشابه با ProGuard کمک می‌کند، ولی اینبار حجم DLL‌های DotNet ای مانند Json.NET را کم می‌کند. بالاخره شما از صد در صد کلاس‌های یک DLL استفاده نمی‌کنید و موارد اضافی نیز باید حذف شوند. البته این وسط، امکان حذف اشتباه کلاس‌هایی که به صورت داینامیک فراخوانی شده باشند وجود دارد که LinkerConfig موجود در پروژه XamApp حتی الامکان جلوی این مشکل را می‌گیرد.

Release mode  مثل هر پروژه CSharp ای دیگری، بهتر است پروژه در حالت Release mode پابلیش شود. در پروژه XamApp در حالت Release mode، موارد بالا یعنی Linker-NDK-ProGuard نیز درخواست می‌شوند.

جزئیات این موارد در مستندات Xamarin وجود دارد و در پایان این دوره یک Project Builder سورس باز نیز به شما ارائه می‌شود که ساختار اولیه پروژه‌ها را بر اساس نیازهای شما و با بهترین تنظیمات ممکن می‌سازد.

در پروژه XamApp علاوه بر موارد فوق، دو مورد دیگر نیز آماده به استفاده هستند، ولی غیر فعال شده اند؛ AOT و LLVM. اگر به تازگی برنامه نویس شده‌اید، موارد زیر ممکن است خیلی برایتان پیچیده باشند، از آن‌ها عبور کنید و به عنوان "نحوه انجام دادن پابلیش" بروید.

کدهای‌های DotNet ای به سه شکل می‌توانند اجرا شوند:
JIT - AOT - Interpreter
یک برنامه DotNet ای برای اجرا می‌تواند از ترکیب اینها استفاده کند. حالت Interpreter که خیلی جدید معرفی شده و الآن موضوع بحث نیست؛ می‌ماند JIT و AOT
کد CSharp در هنگام کامپایل به IL تبدیل و سپس در زمان اجرا توسط Just in time compiler به زبان ماشین تبدیل می‌شود. اگر قبلا پروژه‌ی ASP.NET یا ASP.NET Core نوشته باشید، چنین رفتاری را در پشت صحنه خواهد داشت. خود JIT که در هر بار اجرای برنامه انجام می‌شود، عملا زمان بر هست. ولی کد زبان ماشین حاصل از آن خیلی Optimize شده برای دقیقا همان ماشین هست؛ با در نظر گرفتن خیلی فاکتورها. در پروژه‌های سمت سرور مثل ASP.NET که پروژه وقتی یک بار اجرا می‌شود، مثلا روی IIS، ممکن است صدها هزار دستور را اجرا کند، در طول چندین روز یا ماه، این عمل JIT خیلی مفید هست. البته همان سربار اولیه‌ی JIT هم توسط چیزی به عنوان Tiered JIT می‌تواند کمتر شود.
اما در پروژه‌ی موبایل که برنامه ممکن است بعد از باز شدن، مثلا ده دقیقه باز باشد و بعد بسته شود، انجام شدن JIT با هر بار باز شدن برنامه خیلی مفید به فایده نیست. بنا به برخی مسائل که واقعا سطح این آموزش را خیلی پیچیده می‌کند، نتیجه کار JIT قابلیت Cache شدن آن چنانی ندارد و عملا باید هر بار اجرا شود.
در پروژه‌های موبایل، گزینه دیگری بر روی میز هست به نام Ahead of time یا AOT که کار تبدیل IL به زبان ماشین را در زمان کامپایل و پابلیش پروژه انجام دهد. طبیعتا این باعث می‌شود سرعت برنامه موبایل در عمل خیلی بالاتر رود، چون سربار JIT در هر بار اجرای برنامه حذف می‌شود. همچنین روال AOT می‌تواند از LLVM یا Low level virtual machine استفاده کند که منجر به تبدیل شدن کد زبان ماشینی می‌شود که بر روی LLVM کار می‌کند. LLVM خودش یک Runtime با سرعت خیلی بالاست که بر روی تمامی سیستم عامل‌ها کار می‌کند.
بر روی Android - iOS - Windows می‌شود از AOT استفاده کرد. در iOS و ویندوز، استفاده‌ی از AOT منجر به افزایش سایز برنامه نمی‌شود، چون قبلا برنامه یک سری کد IL بوده که زمان اجرا توسط JIT به کد ماشین تبدیل می‌شده و الان بجای آن IL، یک سری کد زبان ماشین مبتنی بر LLVM هست. اما بر روی Android، پیاده سازی AOT ناقص هست و البته که با فعال کردن‌اش، سرعت برنامه بسیار بیشتر می‌شود، ولی کماکان نیاز به JIT و IL هم برای برخی از سناریوها هست. این مورد یعنی اینکه فعال سازی AOT+LLVM بر روی اندروید تا مادامی که AOT در Android به صورت آزمایشی هست، باعث افزایش حجم Apk ما از 7 به 13 مگ می‌شود. البته این مورد در نسخه‌های بعدی رفع خواهد شد و رفتار Android مشابه با iOS-Windows خواهد بود؛ یعنی حجم نسبتا کم و سرعت خیلی بالا.
برای فعال سازی AOT+LLVM در csproj پروژه اندرویدی، دو مقدار AotAssemblies و EnableLLVM را از false به true تغییر دهید:
 <AotAssemblies>true</AotAssemblies> 
 <EnableLLVM>true</EnableLLVM>
با این تنظیمات، بیلد شما طولانی‌تر و در عوض سرعت اجرای برنامه بیشتر خواهد شد.

نحوه انجام دادن پابلیش 
برای انجام دادن پابلیش، بر روی پروژه XamApp.Android در هنگامیکه بر روی Release mode هستید، راست کلیک کنید و Archive را بزنید. سپس فایل Archive شده را انتخاب و Distribute را بزنید که به شما Apk مناسب برای انتشار توسط خودتان یا Google Play می‌دهد.
نکات مهم:
1- فایل Apk حاصل از Archive را بدون Distribute کردن، در اختیار کسی قرار ندهید. فقط پیام Corrupt و خراب بودن فایل، حاصل کارتان خواهد بود!
2- اولین باری که Distribute می‌کنید، Wizard مربوطه کمک می‌کند تا یک فایل Certificate را برای Apk اتان بسازید. آن فایل را گم نکنید! در پابلیش‌های بعدی دیگر نباید Certificate جدیدی بسازید؛ بلکه فایل قبلی را باید به آن معرفی کنید و فقط رمز آن Certificate را دوباره بزنید.
3- به برنامه آیکون بدهید. برای آن Splash Screen خوبی بگذارید. در هر بار پابلیش، ورژن برنامه را افزایش دهید. اینها همگی توضیحات اش بر روی بستر وب موجود است. سؤالی بود، همینجا هم می‌توانید بپرسید.
فایل‌های Apk این مثال را می‌توانید از اینجا دانلود کنید.

در قسمت بعدی آموزش، دیباگ و پابلیش گرفتن پروژه بر روی iOS را خواهیم داشت که البته مقداری از مطالب اش با مطالب این آموزش مشترک هست. بعد دست به کد شده و آموزش CSharp و Xaml را خواهیم داشت تا پروژه‌ای با کیفیت، کارآمد و عالی از هر جهت بنویسید.
همچنین تعدادی از نکات مربوط به Performance که مربوط به ظاهر برنامه و نحوه چیدمان صفحات و کنترل‌ها هستند و بر روی Performance هر سه پلتفرم تاثیر گذار هستند (و نه فقط Android‌) نیز در ادامه بحث خواهند شد.
اشتراک‌ها
آیا سرمایه گذاری بر روی Xamarin منطقی است؟

So when Microsoft acquired Xamarin in 2016 and started integrating the Xamarin Visual Studio plugins more with the standard VS features, I knew I had to try and switch over to take advantage of the powerful IDE and language. Some of the immediate benefits I gained from the switch are:

  • Simple asynchronous programming
  • Access to powerful plugins like ReSharper
  • Freedom to work in Windows or OSX
  • Access to powerful debugging tools for the Android on Windows (debugging iOS on Mac side is good but can be buggy).
  • Access to built in NuGet package management for third party libraries 
آیا سرمایه گذاری بر روی Xamarin منطقی است؟
مطالب
زیر نویس فارسی ویدیوهای ساخت برنامه‌های مترو توسط سی شارپ و XAML - قسمت چهارم

زیرنویس‌های فارسی قسمت چهارم «Building Windows 8 Metro Apps in C# and XAML» را از اینجا و یا اینجا می‌تونید دریافت کنید.

لیست سرفصل‌های قسمت چهارم به شرح زیر است:

List Controls  00:31:14 
This module shows the Metro controls available to XAML applications for working with collections of items.
This includes the new GridView and ListView controls,
which are optimized for handling collections in a touch-based user interface.

Introduction
Items Controls
Demo: ListBox vs ListView
Demo: GridView
Demo: FlipView
Common ItemsControl
Semantic Zoom
Demo: JumpViewer
Summary

این قسمت به بررسی یک سری کنترل لیستی جدید ویندوز 8 اختصاص دارد شامل ListView بازنویسی شده و همچنین GridView به همراه دو کنترل FlipView و JumpViewer که تمام این‌ها جهت کار با صفحات لمسی بهینه سازی شده‌اند.

نظرات مطالب
Build Events
با سلام 
در پاسخ به مشکل شما چند نکته باید اشاره بشه. 

نکته اول: ماکروی TargetFileName فقط اسم فایل خروجی پروژه رو برمی‌گردونه، درصورتیکه برای کارکردن دستور فوق مسیر کامل فایل نیازه. چون برنامه editbin.exe درون مسیر خروجی پروژه شما اجرا نمی‌شه. شما می‌تونین از ماکروی TargetPath استفاده کنید که مسیر کامل فایل خروجی پروژه رو برمی‌گردونه.

نکته دوم: کد خطای 9009 مربوط به پیدا نکردن فایل هست. البته فایلی که در اینجا پیدا نشده خروجی پروژه شما نیست بلکه خود ابزار editbin هستش. مسیر درستش در سیستم 32 بیتی برای ویژوال استودیو 2010 اینه:
C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\editbin.exe
اما چون این مسیرها معمولا حاوی فاصله هستند نیاز به استفاده از دابل کوتیشن در ابتدا و انتها وجود داره. بنابراین دستور کامل باید به صورت زیر باشه:
"C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\editbin.exe" /STACK:1000000 "$(TargetPath)"
اما با اجرای دستور فوق باز هم خطایی صادر میشه که کمی خطرناکتر از قبلیه. و اما دلیلش:

نکته سوم: متن زیر از msdn گرفته شده:
You can start this tool only from the Visual Studio command prompt. You cannot start it from a system command prompt or from Windows Explorer.
البته منظور دقیق‌تر این جمله اینه که ابزار editbin نیاز به یکسری تنظیمات و متغیرهای ازپیش تعیین شده داره که در Visual Studio command prompt انجام شده. اما نگران نباشید برای تنظیم این تنظیمات و تبدیل خط فرمان Build Events در ویژوال استودیو به یک Visual Studio command prompt کافیه که خط زیر رو در ابتدای مجموعه دستورات build events خودتون قرار بدین:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
این بچ فایل حاوی دستوراتی نسبتا مفصل برای تنظیم تنظیمات موردنیاز است. درواقع با اجرای این بچ فایل هر خط فرمانی تقریبا تبدیل به Visual Studio command prompt خواهد شد. با توجه به ماکروی (DevEnvDir)$ مسیر کامل این فایل در سیستم 32 بیتی و برای ویژوال استودیوی 2010 به صورت زیر است:
C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools\vsvars32.bat
بنابراین برای کار کردن دستور موردنظر شما کافیه که این دو دستور به صورت زیر در Post Build Event اضافه بشه:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
"C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\editbin.exe" /STACK:1000000 "$(TargetPath)"

نکته چهارم: با توجه به اشاره‌ای که در نکته قبلی شد ("با اجرای این فایل هر خط فرمانی تقریبا تبدیل به Visual Studio command prompt خواهد شد.") بنابراین دستور فوق را میتوان به صورت زیر خلاصه کرد:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
"editbin.exe" /STACK:1000000 "$(TargetPath)"

موفق باشید.
مطالب
راهنمای گام به گام انتقال پروژه از MVC 5 به MVC 6
با اینکه چند ماهی از انتشار نسخه‌های مختلف ASP.NET 5 و MVC 6 می‌گذرد و مطالب زیادی هم در همین سایت در مورد ویژگی‌ها و امکانات جدید آن قرار داده شده، اما شاید افرادی هم باشند که مانند من از تغییرات زیادی که در ساختار پروژه‌ها در MVC 6 به وجود آمده این ترس را داشته باشند که مهاجرت به آن، کار سخت و زمانبری است و ترجیح می‌دهند که پروژه‌های قدیمی خود را به همان حالت حفظ کنند و همین امر باعث شده که از لذت امکانات جدید و فوق العاده‌ی ASP.NET 5 محروم بمانند. خب باید بگویم که من این کار را برای پروژه‌ی خودم انجام دادم و پیچیدگی زیادی ندارد. در ادامه سعی می‌کنم، گام به گام مراحلی را که طی کرده‌ام خدمتتان عرض کنم و تجربیاتی را که در این بین کسب نموده‌ام، در اختیارتان قرار دهم.

شما برای این کار باید چند مرحله را انجام دهید:

مرحله‌ی اول: یک پروژه‌ی جدید بسازید

در ویژوال 2015 یک پروژه‌ی جدید را از نوع ASP.NET Web Application ایجاد نمایید. نام دلخواهی را قرار داده و در قسمت بعد ASP.NET 5 Empty template را انتخاب نمایید. پروژه را اجرا کنید تا از صحت و درستی آن مطمئن شوید.
نکته: برای اینکار می‌توانید پروژه را از نوع ASP.NET 5 Web Application نیز انتخاب نمایید و مراحل کوتاه‌تری را طی نمایید. اما راه اندازی دستی قسمت‌های مختلف پروژه برای یکبار، به درک بهتر ساختار آن کمک زیادی می‌کند. از طرفی کار کردن بر روی یک پروژه‌ی تمیز و خالی، برای انتقال داده‌های مورد نیاز از یک پروژه‌ی دیگر، از بروز خطاهای پیش بینی نشده و تداخل‌های احتمالی نیز جلوگیری می‌کند.

مرحله‌ی دوم: اعمال تنظیمات جهت استفاده‌ی از MVC

همان طور که مشاهده می‌کنید در این پروژه دیگر خبری از web.config نیست. اما نگران نباشید، امکان اعمال تنظیمات، باز هم وجود دارد و فقط به فایل‌های json منتقل شده‌اند که project.json هم یکی از آن‌هاست. برای استفاده‌ی از Microsoft.AspNet.Mvc فقط کافی است فایل project.json را باز کنید و در قسمت "dependencies" پکیج "Microsoft.AspNet.Mvc" را به آن اضافه نمایید تا به صورت خودکار دانلود و به پروژه اضافه گردد.

"dependencies": {
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta5",
    "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5",
    "Microsoft.AspNet.Mvc": "6.0.0-beta5"
  },
همان طور که می‌بینید خبری از فایل Global.asax و همچنین فایل‌های موجود در پوشه‌ی App_Start هم نیست. خب در عوض Routing به فایل startup.cs منتقل شده است. این فایل را باز کنید و دو تغییر زیر را در آن انجام دهید:
public void ConfigureServices(IServiceCollection services)
{
      services.AddMvc();
}

public void Configure(IApplicationBuilder app)
{
     app.UseMvc(routes =>
     {
            routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
       });
}
تنها کاری که در این مرحله می‌ماند، ایجاد یک view و controller تستی در پروژه است تا کار را تا همین‌جا تست کنید. سپس دو پوشه‌ی Controllers و Views را اضافه کنید. یک کنترلر جدید را به نام HomeController به پوشه‌ی Controller و سپس متناسب با آن یک پوشه‌ی جدید را به نام Home در پوشه‌ی Views اضافه نمایید. در آخر هم یک View به نام Index در مسیر Views/Home اضافه نمایید و داخل آن را به صورت زیر قرار دهید:
<h1>My First MVC6 Website!</h1>
دقت کنید ساختار پروژه تا به اینجای کار به شکل زیر خواهد شد:


برنامه را اجرا کنید تا خروجی مورد انتظار را مشاهده نمایید.


مرحله‌ی سوم: انتقال فایل‌های پروژه‌ی قبلی به پروژه‌ی جدید

خب زمان انتقال فایل‌ها فرا رسیده است. برای این منظور باید تمام فایل‌های client-side، شامل استایل‌ها و فایل‌های js و نیز فایل‌های موجود در پوشه‌های model و view و controller، به پروژه‌ی جدید منتقل شوند. بهتر است تمام فایل‌ها را با هم انتقال ندهید. با یک فایل سبک شروع کنید. در صورت موفقیت آمیز بودن بقیه را هم منتقل کنید. به عنوان مثال می‌توانید از کنترلر Home شروع کنید که معمولا اکشن‌های Contact و About را دارا هستند. تمام محتویات این کنترلر را انتقال دهید و به این نکته نیز توجه داشته باشید که خروجی تمامی این اکشن‌ها در MVC 5 از نوع ActionResult است؛ ولی در MVC6 به IActionResult تغییر می‌کند. اما چندان هم مهم نیست؛ چرا که با ActionResult هم مشکلی نخواهد داشت.
View‌های مربوط به این controller را نیز به پوشه‌ی Views/Home منتقل نمایید و پروژه را بار دیگر اجرا نمایید. حال باید نمایی از محتوای این فایل‌ها بدون اعمال استایل‌ها را مشاهده نمایید.
همان طور که می‌دانیم MVC 5 برای استایل صفحات خود از bootstrap استفاده می‌کند و فایل‌های مورد نیاز آن در پوشه‌های Content و Script -که در root سایت موجود هستند- قرار دارند و نیز ارجاع به این فایل‌ها در Layout.cshtml_ قرار دارد. اما در MVC 6 قضیه کمی متفاوت و البته بهتر شده است. در MVC 6 تمام فایل‌های client-side شامل css و js در پوشه‌ی wwwroot قرار دارند و ما می‌تواینم فایل‌های bootstrap و غیره را از پروژه‌ی خود، به این مکان کپی نماییم. ولی روش بهتر، استفاده از ابزارهای bower و gulp برای این کار است. همان طور که میدانید bower یک package manager برای نصب، به روزرسانی و مدیریت کتابخانه‌های سمت کلاینت و gulp نیز یک task runner برای انجام کارهای مختلف از قبیل script minification و ... در سمت کلاینت است. gulp و bower به طور تو کار در ویژوال 2015 پشتیبانی می‌شوند و حتی اگر پروژه‌ی خود را از نوع ASP.NET 5 Web Application انتخاب کرده باشید، به صورت پیش فرض از آنها استفاده می‌کند.
در اینجا برای استفاده، ابتدا یک فایل از نوع Bower JSON Configuration را به root پروژه اضافه کرده و آن را bower.json بنامید و در خاصیت "dependencies" آن bootstrap, jquery,  jquery-validation, jquery-validation-unobtrusive را اضافه نمایید.

نکته: من در این قسمت، در restore کردن پکیج‌ها با استفاده از bower، با خطای زیر مواجه شدم:

visual 2015 ECMDERR Failed to execute "git ls-remote --tags --heads git://github.com/jquery/jquery.git", exit code of #-532462766

من از نسخه‌ی Visual Studio 2015 Update 1 CTP استفاده می‌کنم، ولی ظاهرا این مشکل در نسخه‌های دیگر هم وجود دارد و فایل bower.cmd ویژوال به درستی کار نمی‌کند. من برای حل این مشکل، ابتدا git را نصب کردم و در تنظیمات bower، مسیر پیش فرض ویژال رو به مسیر نصب git تغییر دادم. یعنی در پروژه بر روی پوشه‌ی Bower کلیک راست و configure external tools را انتخاب کردم و تیک $(DevEnvDir)\Extensions\Microsoft\Web Tools\External\git را برداشته و در عوض مسیر پیش فرض خودم یعنی C:\Program Files\Git\bin را اضافه کردم. 

اگر راه درست و اصولی‌تری برای حل این مشکل وجود دارد ممنون می‌شوم دوستان راهنمایی بفرمایند.

خب بعد از اضافه کردن خاصیت dependencies و پکیج‌های مورد نیاز، خاصیت exportsOverride را نیز مانند نمونه به فایل bower.json اضافه نمایید.

{
  "name": "ASP.NET",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.6",
    "jquery": "2.1.4",
    "jquery-validation": "1.14.0",
    "jquery-validation-unobtrusive": "3.2.4"
  },
  "exportsOverride": {
    "bootstrap": {
      "js": "dist/js/*.*",
      "css": "dist/css/*.*",
      "fonts": "dist/fonts/*.*"
    },

    "jquery": {
      "": "jquery.{js,min.js,min.map}"
    },
    "jquery-validation": {
      "": "jquery.validate.js"
    },
    "jquery-validation-unobtrusive": {
      "": "jquery.validate.unobtrusive.{js,min.js}"
    }
  }
}
Bower به صورت خودکار پکیج‌ها و وابستگی‌های آن‌ها را دانلود می‌کند، ولی توجه نمایید که هنوز فایل‌ها در پوشه‌ی wwwroot قرار نگرفته‌اند و قابل استفاده نیستند. پس باید از gulp کمک بگیریم تا این فایل‌ها را پردازش و در wwwroot قرار دهد. برای نصب gulp در پروژه، یک فایل NPM configuration به پروژه اضافه می‌کنیم و آن را package.json می‌نامیم. خاصیت "devDependencies" آن را همانند زیر تکمیل می‌کنیم:
"devDependencies": {
    "gulp": "3.9.0",
    "rimraf": "2.4.4",
    "gulp-concat": "2.6.0"
  }
بعد از ذخیره‌ی تغییرات باید پوشه‌ی NPM را در مسیر Dependencies مشاهده نمایید که شامل gulp و بقیه پکیج‌ها باشد.

حال که از نصب بودن gulp در پروژه مطمئن هستید فایل تنظیمات آن یعنی یک فایل Gulp Configuration را به root پروژه اضافه نمایید و آن را Gulpfile.js بنامید. برای استفاده از bower باید تنظیمات gulp را به صورت زیر انجام دهید:
var gulp = require('gulp');
var rimraf = require('rimraf');

var paths = {
    bower: "./bower_components/",
    lib: "./wwwroot/lib/"
};

gulp.task('clean', function (callback) {
    rimraf(paths.lib, callback);
});

gulp.task('default', ['clean'], function () {
    var bower = {
        "bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}",
        "jquery": "jquery/jquery*.{js,map}",
        "jquery-validation": "jquery-validation/jquery.validate.js",
        "jquery-validation-unobtrusive":
                "jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"
    };

    for (var destinationDir in bower) {
        gulp.src(paths.bower + bower[destinationDir])
                .pipe(gulp.dest(paths.lib + destinationDir));
    }
});
تنظیمات مورد نیاز برای استفاده‌ی از bower و gulp تمام شد. حال باید از این ابزارها برای مدیریت فایل‌های client-side خود استفاده نماییم. روی Gulpfile.js کلیک راست کرده و Task Runner Explorer را انتخاب نمایید. از پنجره‌ی سمت چپ روی default دوبار کلیک نمایید تا خروجی زیر نمایان شود.


اگر پردازش فوق با موفقیت و بدون خطا انجام شود، می‌توانید پکیج‌های ایجاد شده را در مسیر wwwroot/lib، مشاهده نمایید.


مرحله‌ی چهارم: ویرایش برخی از view ها

حال که پکیج‌های مورد نیاز پروژه، در پوشه‌ی wwwroot قرار گرفتند، باید view هایی که ارجاعی را به این فایل‌ها دارند، نیز ویرایش نماییم. یکی از این فایل‌ها Layout.cshtml_ است که در مسیر Views/Shared قرار دارد. این فایل را باز کرده و به جای متد ()Styles .Render از عنصر <link> برای لود کردن استایل‌های بوت استرپ و غیره استفاده نمایید.

<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
برای فایل‌های js نیز متد () Scripts.Render را با عنصر <script> جایگزین نمایید.
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>


مرحله‌ی پنجم(اختیاری): جایگزین کردن متدهای Html helper با ساختار Tag Helper

یکی از ویژگی‌های جالب و مفید MVC 6 ساختار TagHalper ‌ها هستند که در واقع جایگزینی برای متدهای HtmlHelper و عملکردی مشابه به آنها دارند. البته استفاده از این ویژگی اجباری نیست ولی اگر تعداد ویوهای شما زیاد نیست و خواهان استفاده‌ی از این قابلیت در پروژه‌ی خود هستید، تنها کاری که باید انجام دهید، پیدا کردن HtmlHelper‌ها و جایگزینی آنها به صورت زیر می‌باشد: 

@Html.TextBoxFor(model => model.Name, new { style = "width: 100px" })
جایگزین شود با
<input asp-for="Name" style="width: 100px" />


نتیجه گیری
همان طور ملاحظه نمودید انتقال پروژه از ASP.NET MVC 5 به ASP.NET MVC 6 شامل انجام چند مرحله است و دشواری خاصی ندارد. عمده‌ی این تغییرات و پیچیدگی‌ها هم مربوط به انتقال فایل‌های client-side و نحوه‌ی کار با ابزارهای مدیریت پکیج می‌شود و البته تنظیماتی که در این بین باید انجام شوند. البته قسمت‌های دیگری مانند تنظیمات bundling و connection string نیز با MVC 5 تفاوت هایی دارد و کار با آن‌ها نیز بسیار ساده می‌باشد.

اشتراک‌ها
سخت افزار Nick Craver از برنامه نویسان ارشد Stack Overflow

Stack Overflow Developer Desktop Build - 2017

One of the things we're big on at Stack Exchange is hardware - we love it. More importantly, we love not waiting on it. With that in mind, we upgrade our developer machines every 2 years. In case it helps anyone else, I'm posting our current parts list here. This isn't set in stone, we review and update it to the latest tech every time we build a machine. We also customize the build for each developer if needed - for example those needing extra space or specific display connections, etc. I'll try and keep this page updated as we make changes

 

سخت افزار Nick Craver از برنامه نویسان ارشد  Stack Overflow
مطالب
امکان استفاده از کتابخانه‌های native در Blazor WASM 6x
کتابخانه‌‌های بسیاری هستند که به زبان‌های C ، C++ ، Rust و امثال آن تهیه شده‌اند. دات نت 6، قابلیت جدید استفاده‌ی از این نوع کتابخانه‌ها را بدون نیاز به تبدیل کدهای آن‌ها به #C، به برنامه‌های سمت کلاینت Blazor Web Assembly اضافه کرده که در این مطلب، نمونه‌ای از آن‌را با استفاده از بانک اطلاعاتی SQLite در برنامه‌های Blazor WASM 6x، بررسی خواهیم کرد. یعنی یک برنامه‌ی SPA سمت کلاینت که بدون نیاز به سرور و Web API، تنها با استفاده از EF-Core و بانک اطلاعاتی بومی SQLite می‌تواند اطلاعات مورد نیاز خود را ثبت و یا بازیابی کند (همه چیز داخل مرورگر رخ می‌دهد).


ایجاد یک پروژه‌ی Blazor WASM جدید

یک پوشه‌ی جدید دلخواه را به نام BlazorWasmSQLite ایجاد کرده و با اجرای دستور dotnet new blazorwasm، یک پروژه‌ی Blazor Web Assembly خالی جدید را در آن آغاز می‌کنیم. همانطور که از دستور نیز مشخص است، این پروژه از نوع hosted که به همراه Web API هم هست، نمی‌باشد.


افزودن Context و مدل EF-Core به برنامه

مدل برنامه به صورت زیر در پوشه‌ی Models آن قرار می‌گیرد:
namespace BlazorWasmSQLite.Models;

public class Car
{
  public int Id { get; set; }

  public string Brand { get; set; }

  public  int Price { get; set; }
}
و Context ای که آن‌را در معرض دید قرار می‌دهد، به صورت زیر تعریف خواهد شد:
using Microsoft.EntityFrameworkCore;
using BlazorWasmSQLite.Models;

namespace BlazorWasmSQLite.Data;

public class ClientSideDbContext : DbContext
{
  public DbSet<Car> Cars { get; set; } = default!;

  public ClientSideDbContext(DbContextOptions<ClientSideDbContext> options) :
    base(options)
  {
  }
}
همچنین چون می‌خواهیم از بانک اطلاعاتی SQLite استفاده کنیم، وابستگی زیر را به فایل csproj برنامه اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  <ItemGroup>
    <!-- EF Core and Sqlite -->
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
  </ItemGroup>
</Project>
سپس این Context را به نحو زیر به فایل Program.cs معرفی می‌کنیم:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using BlazorWasmSQLite;
using Microsoft.EntityFrameworkCore;
using BlazorWasmSQLite.Data;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

// Sets up EF Core with Sqlite
builder.Services.AddDbContextFactory<ClientSideDbContext>(options =>
      options
        .UseSqlite($"Filename=DemoData.db")
        .EnableSensitiveDataLogging());

await builder.Build().RunAsync();
در مورد علت استفاده‌ی از AddDbContextFactory و نکات مرتبط با آن، به مطلب «نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server» مراجعه نمائید.


ثبت تعدادی رکورد در بانک اطلاعاتی

در ادامه سعی می‌کنیم در فایل Index.razor، تعدادی رکورد را به بانک اطلاعاتی اضافه کنیم:
@page "/"

@using Microsoft.Data.Sqlite
@using Microsoft.EntityFrameworkCore
@using BlazorWasmSQLite.Data
@using BlazorWasmSQLite.Models


<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

@code {
  [Inject]
  private IDbContextFactory<ClientSideDbContext> _dbContextFactory { get; set; } = default!;

  protected override async Task OnInitializedAsync()
  {
    await using var db = await _dbContextFactory.CreateDbContextAsync();
    await db.Database.EnsureCreatedAsync();

    // create seed data
    if (!db.Cars.Any())
    {
      var cars = new[]
      {
        new Car { Brand = "Audi", Price = 21000 },
        new Car { Brand = "Volvo", Price = 11000 },
        new Car { Brand = "Range Rover", Price = 135000 },
        new Car { Brand = "Ford", Price = 8995 }
      };

      await db.Cars.AddRangeAsync(cars);
      await db.SaveChangesAsync();
    }

    await base.OnInitializedAsync();
  }
}
در این مثال سعی شده‌است ساده‌ترین حالت ممکن کار با EF-Core در پیش گرفته شود؛ چون هدف اصلی آن، دسترسی به SQLite است.


اولین سعی در اجرای برنامه

در ادامه سعی می‌کنیم تا برنامه را اجرا کنیم. با خطای زیر متوقف خواهیم شد:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component:
The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception. System.TypeInitializationException:
The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception. ---> System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. ---> System.DllNotFoundException: e_sqlite3 at
SQLitePCL.SQLite3Provider_e_sqlite3.SQLitePCL.ISQLite3Provider.sqlite3_libversion_number()
عنوان می‌کند که فایل‌های بانک اطلاعاتی SQLite به همراه EF-Core را نمی‌تواند پیدا کند. یا به عبارتی هر DLL بومی را نمی‌توان داخل مرورگر اجرا کرد.


رفع مشکل کار با SQLite با کامپایل ویژه‌ی آن

برای دسترسی به کدهای native در Blazor WASM و مرورگر، باید آن‌ها را توسط کامپایلر emcc به صورت زیر کامپایل کرد:
$ git clone https://github.com/cloudmeter/sqlite
$ cd sqlite
$ emcc sqlite3.c -shared -o e_sqlite3.o
در اینجا هر نوع فایل portable native code با فرمت‌های o. یا object files، .a و یا archive files و یا .bc یا bitcode و یا .wasm یا Standalone WebAssembly modules توسط Blazor wasm قابل استفاده هستند که در مثال فوق نمونه‌ی object files آن‌ها توسط کامپایلر تولید می‌شود.
مرحله‌ی بعد، معرفی این object file تولید شده به برنامه است. برای اینکار ابتدا باید dotnet workload install wasm-tools را نصب کرد (مهم). سپس به فایل csproj برنامه مراجعه کرده و فایل e_sqlite3.o را به آن معرفی می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  <ItemGroup>
    <!-- EF Core and Sqlite -->
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
    <NativeFileReference Include="Data\e_sqlite3.o" />
  </ItemGroup>
</Project>
در اینجا فرض شده‌است که فایل o. حاصل، در پوشه‌ی data قرار دارد. این نوع فایل‌ها توسط NativeFileReferenceها به برنامه معرفی می‌شوند.


سعی در اجرای مجدد برنامه

پس از نصب wasm-tools و ذکر NativeFileReference فوق، اکنون اگر برنامه را اجرا کنیم، برنامه بدون مشکل اجرا خواهد شد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorWasmSQLite.zip
مطالب
فعال سازی قسمت آپلود تصویر و فایل Kendo UI Editor
یکی دیگر از ویجت‌های Kendo UI یک HTML Editor کامل است به همراه امکانات ارسال فایل، تصویر و ... پشتیبانی از راست به چپ. در ادامه قصد داریم نحوه‌ی مدیریت نمایش لیست فایل‌ها، افزودن و حذف آن‌ها را از طریق این ادیتور بررسی کنیم.


تنظیمات ابتدایی Kendo UI Editor

در ذیل کدهای سمت کاربر فعال سازی مقدماتی Kendo UI را مشاهده می‌کنید. در قسمت tools آن، لیست امکانات و نوار ابزار مهیای آن درج شده‌اند.
دو مورد insertImage و insertFile آن نیاز به تنظیمات سمت کاربر و سرور بیشتری دارند.
<!--نحوه‌ی راست به چپ سازی -->
<div class="k-rtl">
    <textarea id="editor" rows="10" cols="30" style="height: 440px"></textarea>
</div>
 
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#editor").kendoEditor({
                tools: [
                    "bold", "italic", "underline", "strikethrough", "justifyLeft",
                    "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList",
                    "insertOrderedList", "indent", "outdent", "createLink", "unlink",
                    "insertImage", "insertFile",
                    "subscript", "superscript", "createTable", "addRowAbove", "addRowBelow",
                    "addColumnLeft", "addColumnRight", "deleteRow", "deleteColumn", "viewHtml",
                    "formatting", "cleanFormatting", "fontName", "fontSize", "foreColor",
                    "backColor", "print"
                ],
                imageBrowser: {
                    messages: {
                        dropFilesHere: "فایل‌های خود را به اینجا کشیده و رها کنید"
                    },
                    transport: {
                        read: {
                            url: "@Url.Action("GetFilesList", "KendoEditorImages")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            cache: false
                        },
                        destroy: {
                            url: "@Url.Action("DestroyFile", "KendoEditorImages")",
                            type: "POST"
                        },
                        create: {
                            url: "@Url.Action("CreateFolder", "KendoEditorImages")",
                            type: "POST"
                        },
                        thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")",
                        uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")",
                        imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}"
                    }
                },
                fileBrowser: {
                    messages: {
                        dropFilesHere: "فایل‌های خود را به اینجا کشیده و رها کنید"
                    },
                    transport: {
                        read: {
                            url: "@Url.Action("GetFilesList", "KendoEditorFiles")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            cache: false
                        },
                        destroy: {
                            url: "@Url.Action("DestroyFile", "KendoEditorFiles")",
                            type: "POST"
                        },
                        create: {
                            url: "@Url.Action("CreateFolder", "KendoEditorFiles")",
                            type: "POST"
                        },
                        uploadUrl: "@Url.Action("UploadFile", "KendoEditorFiles")",
                        fileUrl: "@Url.Action("GetFile", "KendoEditorFiles")?path={0}"
                    }
                }
            });
        });
    </script>
}
در اینجا نحوه‌ی تنظیم مسیرهای مختلف ارسال فایل و تصویر Kendo UI Editor را ملاحظه می‌کنید.
منهای قسمت thumbnailUrl، عملکرد قسمت‌های مختلف افزودن فایل و تصویر این ادیتور یکسان هستند. به همین جهت می‌توان برای مثال کنترلی مانند KendoEditorFilesController را ایجاد و سپس در کنترلر KendoEditorImagesController از آن ارث بری کرد و متد دریافت و نمایش بند انگشتی تصاویر را افزود. به این ترتیب دیگر نیازی به تکرار کدهای مشترک بین این دو قسمت نخواهد بود.


نمایش لیست پوشه‌ها و تصویر در ابتدای باز شدن صفحه‌ی درج تصویر

با کلیک بر روی دکمه‌ی نمایش لیست تصاویر، صفحه دیالوگی مانند شکل زیر ظاهر خواهد شد:


تنظیمات خواندن این فایل‌ها، از قسمت read مربوط به imageBrowser دریافت می‌شود که cache آن نیز به false تنظیم شده‌است تا در این بین مرورگر اطلاعات را کش نکند. این مورد در حین حذف فایل‌ها و پوشه‌ها مهم است. زیرا اگر cache:false تنظیم نشده باشد، حذف یک فایل یا پوشه در سمت کاربر تاثیری نخواهد داشت.
imageBrowser: {
    transport: {
        read: {
            url: "@Url.Action("GetFilesList", "KendoEditorImages")",
            dataType: "json",
            contentType: 'application/json; charset=utf-8',
            type: 'GET',
            cache: false
        }
    }
},
در ادامه نیاز است اکشن متد GetFilesList را به نحو ذیل در سمت سرور تهیه کرد:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpGet]
        public ActionResult GetFilesList(string path)
        {
            path = GetSafeDirPath(path);
            var imagesList = new DirectoryInfo(path)
                                .GetFiles()
                                .Select(fileInfo => new KendoFile
                                {
                                    Name = fileInfo.Name,
                                    Size = fileInfo.Length,
                                    Type = KendoFileType
                                }).ToList();
 
            var foldersList = new DirectoryInfo(path)
                                .GetDirectories()
                                .Select(directoryInfo => new KendoFile
                                {
                                    Name = directoryInfo.Name,
                                    Type = KendoDirType
                                }).ToList();
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(imagesList.Union(foldersList), new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
 
 
        protected string GetSafeDirPath(string path)
        {
            // path = مسیر زیر پوشه‌ی وارد شده
            if (string.IsNullOrWhiteSpace(path))
            {
                return Server.MapPath(FilesFolder);
            }
 
            //تمیز سازی امنیتی
            path = Path.GetDirectoryName(path);
            path = Path.Combine(Server.MapPath(FilesFolder), path);
            return path;
        } 
    }
}
در اینجا کدهای کلاس پایه KendoEditorFilesController را مشاهده می‌کنید. به این جهت فیلد FilesFolder آن protected تعریف شده‌است تا در کلاسی که از آن ارث بری می‌کند نیز قابل دسترسی باشد. سپس لیست فایل‌ها و پوشه‌های path دریافتی با فرمت لیستی از KendoFile تهیه شده و با فرمت JSON بازگشت داده می‌شوند. ساختار KendoFile را در ذیل مشاهده می‌کنید:
namespace KendoUI13.Models
{
    public class KendoFile
    {
        public string Name { set; get; }
        public string Type { set; get; }
        public long Size { set; get; }
    }
}
- در اینجا Type می‌تواند از نوع فایل با مقدار f و یا از نوع پوشه با مقدار d باشد.
- علت استفاده از CamelCasePropertyNamesContractResolver در حین بازگشت JSON نهایی، تبدیل خواص دات نتی، به نام‌های سازگار با JavaScript است. برای مثال به صورت خودکار Name را تبدیل به name می‌کند.
- پارامتر path در ابتدای کار خالی است. اما کاربر می‌تواند در بین پوشه‌های باز شده‌ی توسط مرورگر تصاویر Kendo UI حرکت کند. به همین جهت مقدار آن باید هربار بررسی شده و بر این اساس لیست فایل‌ها و پوشه‌های جاری بازگشت داده شوند.


مدیریت حذف تصاویر و پوشه‌ها

همانطور که در شکل فوق نیز مشخص است، با انتخاب یک پوشه یا فایل، دکمه‌ای با آیکن ضربدر جهت فراهم آوردن امکان حذف، ظاهر می‌شود. این دکمه متصل است به قسمت destroy تنظیمات ادیتور:
imageBrowser: {
    transport: {
        destroy: {
            url: "@Url.Action("DestroyFile", "KendoEditorImages")",
            type: "POST"
        }
    }
},
این تنظیمات سمت کاربر را باید به نحو ذیل در سمت سرور مدیریت کرد:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpPost]
        public ActionResult DestroyFile(string name, string path)
        {
            //تمیز سازی امنیتی
            name = Path.GetFileName(name);
            path = GetSafeDirPath(path);
 
            var pathToDelete = Path.Combine(path, name);
 
            var attr = System.IO.File.GetAttributes(pathToDelete);
            if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
            {
                Directory.Delete(pathToDelete, recursive: true);
            }
            else
            {
                System.IO.File.Delete(pathToDelete);
            }
 
            return Json(new object[0]);
        } 
    }
}
- استفاده از Path.GetFileName جهت دریافت نام فایل‌ها در اینجا بسیار مهم است. زیرا اگر این تمیز سازی امنیتی صورت نگیرد، ممکن است با کمی تغییر در آن، فایل web.config برنامه، دریافت یا حذف شود.
- پارامتر name دریافتی مساوی است با نام فایل انتخاب شده و path مشخص می‌کند که در کدام پوشه قرار داریم.
- چون در اینجا امکان حذف یک پوشه یا فایل وجود دارد، حتما نیاز است بررسی کنیم، مسیر دریافتی پوشه‌است یا فایل و سپس بر این اساس جهت حذف آن‌ها اقدام صورت گیرد.


مدیریت ایجاد یک پوشه‌ی جدید

تنظیمات قسمت create مرورگر تصاویر، مرتبط است به زمانیکه کاربر با کلیک بر روی دکمه‌ی +، درخواست ایجاد یک پوشه‌ی جدید را کرده‌است:
imageBrowser: {
    transport: {
        create: {
            url: "@Url.Action("CreateFolder", "KendoEditorImages")",
            type: "POST"
        }
    }
},
کدهای اکشن متد متناظر با این عمل را در ذیل مشاهده می‌کنید:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpPost]
        public ActionResult CreateFolder(string name, string path)
        {
            //تمیز سازی امنیتی
            name = Path.GetFileName(name);
            path = GetSafeDirPath(path);
            var dirToCreate = Path.Combine(path, name);
 
            Directory.CreateDirectory(dirToCreate);
 
            return KendoFile(new KendoFile
            {
                Name = name,
                Type = KendoDirType
            });
        }
 
        protected ActionResult KendoFile(KendoFile file)
        {
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(file,
                    new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
- در اینجا نیز name مساوی نام پوشه‌ی درخواستی است و path به مسیر تو در توی پوشه‌ی جاری اشاره می‌کند.
- پس از ایجاد پوشه، باید نام آن‌را با فرمت KendoFile به صورت JSON بازگشت داد. همچنین در اینجا Type را نیز باید به d (پوشه) تنظیم کرد.


مدیریت قسمت ارسال فایل و تصویر

زمانیکه کاربر بر روی دکمه‌ی upload file یا بارگذاری تصاویر در اینجا کلیک می‌کند، اطلاعات فایل آپلودی به مسیر uploadUrl ارسال می‌گردد.
imageBrowser: {
    transport: {
        thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")",
        uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")",
        imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}"
    }
},
دو تنظیم دیگر thumbnailUrl و imageUrl، برای نمایش بند انگشتی و نمایش کامل تصویر کاربرد دارند.
در ادامه کدهای مدیریت سمت سرور قسمت آپلود این ادیتور را مشاهده می‌کنید:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";

 
        [HttpPost]
        public ActionResult UploadFile(HttpPostedFileBase file, string path)
        {
            //تمیز سازی امنیتی
            var name = Path.GetFileName(file.FileName);
            path = GetSafeDirPath(path);
            var pathToSave = Path.Combine(path, name);
 
            file.SaveAs(pathToSave);
 
            return KendoFile(new KendoFile
            {
                Name = name,
                Size = file.ContentLength,
                Type = KendoFileType
            });
        } 
    }
}
- در اینجا path مشخص می‌کند که در کدام پوشه‌ی تو در تو قرار داریم و file نیز حاوی محتوای ارسالی به سرور است.
- پس از ذخیره سازی اطلاعات فایل، نیاز است اطلاعات فایل نهایی را با فرمت KendoFile به صورت JSON بازگشت دهیم.


ارث بری از KendoEditorFilesController جهت تکمیل قسمت مدیریت تصاویر

تا اینجا کدهایی را که ملاحظه کردید، برای هر دو قسمت ارسال تصویر و فایل کاربرد دارند. قسمت ارسال تصاویر برای تکمیل نیاز به متد دریافت تصاویر به صورت بند انگشتی نیز دارد که به صورت ذیل قابل تعریف است و چون از کلاس پایه KendoEditorFilesController ارث بری کرده‌است، این کنترلر به صورت خودکار حاوی اکشن متدهای کلاس پایه نیز خواهد بود.
using System.Web.Mvc;
 
namespace KendoUI13.Controllers
{
    public class KendoEditorImagesController : KendoEditorFilesController
    {
        public KendoEditorImagesController()
        {
            // بازنویسی مسیر پوشه‌ی فایل‌ها
            FilesFolder = "~/images";
        }
 
        [HttpGet]
        [OutputCache(Duration = 3600, VaryByParam = "path")]
        public ActionResult GetThumbnail(string path)
        {
            //todo: create thumb/ resize image
 
            path = GetSafeFileAndDirPath(path);
            return File(path, "image/png");
        }
    }
}

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.