نظرات مطالب
Soft Delete در Entity Framework 6
برای مطلب فوق 2 متد به شکل زیر پیاده سازی کردم 

        public void SetEnableActiveFilter(bool isActive)
        {
            this.EnableFilter("IsActive").SetParameter("isActive", isActive);
        }
        public void SetDisableActiveFilter()
        {
            this.DisableFilter("IsActive");
        }
و در Context هم برای تعریف فیلتر یه صورت زیر عمل کردم
            modelBuilder.Conventions.Add(FilterConvention.Create<BaseEntity, bool>("IsActive", (entity, isActive) => entity.IsActive == isActive));

مشکل اینجاست که فعال سازی به صورت کامل انجام می‌شود اما غیر فعال سازی آن انجام نمی‌گیرد و در تمام فرایندهای بعدی نیز وجود دارد.
مشکل از نحوه تعریف هست؟
نظرات مطالب
نحوه استفاده از ViewModel در ASP.NET MVC
سلاام ...
من تاجاییکه میتونستم چیزهایی که درباره viemodel  و automapper توی سایت بود رو خوندم ولی چیزی که میخواستم را پیدا نکردم شاید هم درک درستی ازش نداشتم در کل سوال سوال من از شما دوستان اینه : 
یک viewmodel دارم که از چندین جدول توی db ایجاد شده که در زیر نوشتمش : 
public class ResumeViewModel
    {
        public ResumeViewModel()
        {
            
        }
        public ResumeViewModel(IEnumerable<Resume> resum, IEnumerable<Work_Experience_Job_Seeker> workExperienceJobSeekerOfViewModel, IEnumerable<Job_Expertises> jobExpertisesOfViewModel, IEnumerable<Degrees_Work_Experience_Required> degreesWorkExperienceRequiredOfViewModel, IEnumerable<Specialized_Course> specializedCourseOfViewModel, IEnumerable<Book_Published> bookPublishedOfViewModel, IEnumerable<Basic_Table> basicTable)
        {
            ResumeOfViewModel = resum;
            WorkExperienceJobSeekerOfViewModel = workExperienceJobSeekerOfViewModel;
            JobExpertisesOfViewModel = jobExpertisesOfViewModel;
            DegreesWorkExperienceRequiredOfViewModel = degreesWorkExperienceRequiredOfViewModel;
            SpecializedCourseOfViewModel = specializedCourseOfViewModel;
            BookPublishedOfViewModel = bookPublishedOfViewModel;
            BasicTable = basicTable;
        }
        public IEnumerable<Resume> ResumeOfViewModel { get; set; }
        public IEnumerable<Work_Experience_Job_Seeker> WorkExperienceJobSeekerOfViewModel { get; set; }
        public IEnumerable<Job_Expertises> JobExpertisesOfViewModel { get; set; }
        public IEnumerable<Degrees_Work_Experience_Required> DegreesWorkExperienceRequiredOfViewModel { get; set; }
        public IEnumerable<Specialized_Course> SpecializedCourseOfViewModel { get; set; }
        public IEnumerable<Book_Published> BookPublishedOfViewModel { get; set; }
        public IEnumerable<Basic_Table> BasicTable { get; set; }

        public int NumberForm { get; set; } // EditResumes.chtml & ShowResumes.chtml  ===> baraye select kardan formha :D 

        }
و این viewmodel رو توی متد Edit Resume  استفاده کردم که متد get  بصورت زیر ::
        [HttpGet]
        public ActionResult EditResumes(int id)
        {
            var contex = new Final_My_ProjectEntities2();
            var res1 = contex.Resumes.Where(rec => rec.Resume_ID == id);
            var res2 = contex.Work_Experience_Job_Seeker.Where(rec => rec.Resume_ID == id).ToList();
            var res3 = contex.Job_Expertises.Where(rec => rec.Resume_ID == id).ToList();
            var res4 = contex.Degrees_Work_Experience_Required.Where(rec => rec.Resume_ID == id).ToList();
            var res5 = contex.Specialized_Course.Where(rec => rec.Resume_ID == id).ToList();
            var res6 = contex.Book_Published.Where(rec => rec.Resume_ID == id).ToList();
            var res12 = contex.Basic_Table.ToList();
            var viewModel = new ResumeViewModel(res1, res2, res3, res4, res5, res6,res12);
            var items = new SelectList(
                 new[]
                    {
                        new {Value = "1", Text = "فرم مهارت ها"},
                        new {Value = "2", Text = "فرم کتاب/مقالات منتشر شده"},
                        new {Value = "3", Text = "فرم سابقه کاری"},
                        new {Value = "4", Text = "فرم دوره‌های تخصصی گذرانده"},
                        new {Value = "5", Text = "فرم تخصص‌های شغلی"},
                        new {Value = "6", Text = "فرم مدارک تحصیلی"}
                    },
             "Value", "Text");

            ViewBag.Form = new SelectList(items, "Value", "Text");

            var res7 = contex.Basic_Table.Where(rec => rec.Domain == "MilitaryStatus").ToList();
            ViewBag.MilitaryStatus = new SelectList(res7, "Value", "Meaning",res1);

            var res8 = contex.Basic_Table.Where(rec => rec.Domain == "Sex");
            ViewBag.Sex = new SelectList(res8, "Value", "Meaning", res1);

            var res9 = contex.Basic_Table.Where(rec => rec.Domain == "MartialStatus").ToList();
            ViewBag.MartialStatus = new SelectList(res9, "Value", "Meaning", res1);

            var res10 = contex.Basic_Table.Where(rec => rec.Domain == "Degree").ToList();
            ViewBag.Degree = new SelectList(res10, "Value", "Meaning");

            var res11 = contex.Basic_Table.Where(rec => rec.Domain == "Ability").ToList();
            ViewBag.Ability = new SelectList(res11, "Value", "Meaning");


            return View(viewModel);
        }
و view  این متد بصورت زیر هست البته قسمتی از آن :: 
@model Final_My_Project.ViewModels.ResumeViewModel

@{
    ViewBag.Title = "ویرایش رزومه";
    ViewBag.PartOne = "فرم مهارت ها";
    ViewBag.PartTwo = "فرم کتاب/مقالات منتشر شده";
    ViewBag.Part3 = "فرم سابقه کاری";
    ViewBag.Part4 = "فرم دوره‌های تخصصی گذرانده";
    ViewBag.Part5 = "فرم تخصص‌های شغلی";
    ViewBag.Part6 = "فرم مدارک تحصیلی";

}
<h2 style="font-family:  Arial;">@ViewBag.Title</h2><br/>
   <script type="text/javascript">
       $(function () {
           $('#Gender').change(function () {
               var selectKind = $(this).find('option:selected').text();
               var divMilitary;
               if (selectKind == "زن") {
                   divMilitary = $('#Military');
                   divMilitary.hide();
                   divMilitary.css('display', 'none');
               }
               else if (selectKind == "مرد") {
                   divMilitary = $('#Military');
                   divMilitary.show();
                   divMilitary.css('display', 'block');

               }
           });
       });
</script>

<script type="text/javascript">
    $(function () {
        $('#SelectForm').change(function () {
            var selectFrom = $(this).find('option:selected').text();

            if (selectFrom == "فرم مهارت ها") {
                $('#PartOne').show();
                $('#PartOne').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');


            }
            if (selectFrom == "فرم کتاب/مقالات منتشر شده") {
                $('#PartTwo').show();
                $('#PartTwo').css('display', 'block');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');

            }
            if (selectFrom == "فرم سابقه کاری") {

                $('#Part3').show();
                $('#Part3').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');


            }
            if (selectFrom == "فرم دوره‌های تخصصی گذرانده") {

                $('#Part4').show();
                $('#Part4').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');

            }
            if (selectFrom == "فرم تخصص‌های شغلی") {

                $('#Part5').show();
                $('#Part5').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');

            }

            if (selectFrom == "فرم مدارک تحصیلی") {

                $('#Part6').show();
                $('#Part6').css('display', 'block');
                $('#Part5').show();
                $('#Part5').css('display', 'none');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
            }

        });
    });

</script>

@Html.DropDownListFor(m=>m.NumberForm, (SelectList)ViewBag.Form, new { id = "SelectForm" })


@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

        <div id="PartOne" >
    <h3 style="font-family: Arial; color: #008080; font-weight: bold; ">@ViewBag.PartOne</h3><br/>
    @foreach (var item in Model.ResumeOfViewModel)
    {
        <table dir="rtl">
            <tr>
                <td>
                    @Html.Label("عنوان رزومه")
                </td>
                <td>
                    @Html.TextBoxFor(model =>item.Title_Of_Resume  , new {@class = "text", style = "width:100 px"})
                    @Html.ValidationMessageFor(model => item.Title_Of_Resume)
                </td>
            </tr>
            <tr>
                <td>
        <div id="Gender" >
        @Html.Label("نوع جنسیت")
         @Html.DropDownList("نوع جنسیت", new SelectList(ViewBag.Sex, 
                       "Value", "Text", item.Sex_ID == 0 ? 0 : item.Sex_ID))
        @Html.ValidationMessageFor(model => item.Sex_ID)
        </div>
    </td>

                <td>
                    <div >
                        @Html.Label("وضعیت تاهل")
         @Html.DropDownList("وضعیت تاهل", new SelectList(ViewBag.MartialStatus, 
                       "Value", "Text", item.Martial_Status_ID == 0 ? 0 : item.Martial_Status_ID))
                        @Html.ValidationMessageFor(model => item.Martial_Status_ID)
                    </div>

                </td>
            </tr>

            <tr id="Military" style="display: none;">
                <td>
                    @Html.Label("وضعیت نظام وظیفه")
                </td>
                <td>
         @Html.DropDownList("وضعیت نظام وظیفه", new SelectList(ViewBag.MilitaryStatus,
                           "Value", "Text", item.Military_Status_ID == 0 ? 0 : item.Military_Status_ID), new { id = "Gender" })
                    @Html.ValidationMessageFor(model => item.Military_Status_ID)
                </td>

            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با رایانه")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Of_Computers_ID, (SelectList)ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Computers_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با امور اداری و دفتری")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Administrative_and_Clerical_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Administrative_and_Clerical_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با زبان انگلیسی")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Of_English_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_English_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با زبان عربی")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Of_Arabic_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Arabic_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با ماکروسافت آفیس")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Knowledge_Of_Office_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Office_ID)
                </td>
            </tr>

            <tr>
                <td>  
                    @Html.Label("آشنایی با امور مالی و حسابداری")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Knowledge_Of_Finance_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Finance_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با مدیریت")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Knowledge_Of_Manage_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Knowledge_Of_Manage_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("گواهینامه رانندگی پایه یک")

                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Driving_license_One_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Driving_license_One_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("گواهینامه رانندگی پایه دو")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Driving_license_Two_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Driving_license_Two_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("گواهینامه رانندگی پایه موتورسیکلت")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Certificate_Motor_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Certificate_Motor_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("ماشین شخصی")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Personal_Vehicle_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Personal_Vehicle_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("روابط عمومی")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Public_Relationship_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Public_Relationship_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("دیگر توانایی ها")

                </td>
                <td>
                    @Html.EditorFor(model =>item.Etc_Ability)
                    @Html.ValidationMessageFor(model =>item.Etc_Ability)
                </td>
            </tr>

        </table>
    }
        </div>

    
        <p>
            <input type="submit" value="Save" onclick="return confirm('از ثبت اطلاعات مطمئن هستید؟')" />
        </p>
}

<div>
    @Html.ActionLink("بازگشت به مدیریت رزومه ها", "ManageOfResumes")
</div>

@section scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
و مشکل اینجاست که بعد از ثبت اطلاعات وقتی به متد post میره مقدارش null  هستش ... درحالیکه فقط در صورت edit  اینجوریه وقتی از همین viewmodel  برای مشاهده رزومه که فقط گزارشگیریه استفاده میکنم نتیجه را میبینم ولی اینجا نه !
متد پست بصورت زیر هستش ... اگر میدونید چطور و چی کار کنم که این درست شه ممنون میشم ... چون دیگه نمیدونم تو متد پست چی بنویسم ... منتظر جوابم ... که چرا null میده و اینکه تو متد پست چطور اینارو ذخیره کنم توی db .../؟
        [HttpPost]
        public ActionResult EditResumes(ResumeViewModel model) // model null mishe ! CHERAA??!
        {
            var contex = new Final_My_ProjectEntities2();
            try
            {
                if (ModelState.IsValid)
                {

                    // Code... che cody?
                    contex.SaveChanges();
                }

            }
            catch (Exception)
            {

                ViewBag.wrong = "لطفا داده‌های ورودی را بررسی نمایید";
            }
            return View(model);

        }

نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: امکان اعتبارسنجی دستی فرم‌ها در Blazor

در این مطلب با روش معرفی EditForm و خاصیت Model آن آشنا شدیم که کار اعتبارسنجی را به صورت خودکار مدیریت می‌کند. اگر خواستیم کنترل بیشتری را بر روی این فرآیند داشته باشیم، می‌توان عملیات اعتبارسنجی را دستی کرد:
@implements IDisposable

<EditForm EditContext="@_editContext" OnValidSubmit="submit">


    <button type="submit" disabled="@_isInvalidForm">Submit</button>
</EditForm>

@code
{
    private User _userModel = new User();
    private EditContext _editContext;
    private bool _isInvalidForm = true;


    protected override void OnInitialized()
    {
        _editContext = new EditContext(_userModel);
        _editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void submit()
    {
        if(_editContext.Validate())
        {
           
        }
    }

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        _isInvalidForm = !_editContext.Validate();
        StateHasChanged();
    }

    public void Dispose()
    {
        _editContext.OnFieldChanged -= HandleFieldChanged;
    }
}
اینبار در اینجا بجای استفاده از خاصیت Model، از خاصیت جدید EditContext استفاده می‌شود (تنها یکی از این دو را می‌توان ذکر کرد). روش مقدار دهی EditContext را در روال آغازین کامپوننت مشاهده می‌کنید که وهله‌ای از مدل را دریافت کرده و تحت بررسی قرار می‌دهد. EditContext یک پارامتر آبشاری است و به صورت خودکار در اختیار تمام کنترل‌ها و کامپوننت‌های محصور شده‌ی توسط EditForm قرار می‌گیرد.
نمونه‌ای از روش کار با آن‌را در متد submit مشاهده می‌کنید که باید به همراه فراخوانی متد Validate آن باشد و یا می‌توان به صورت زیر در مورد یک فیلد عمل کرد:
var isValid = !_editContext.GetValidationMessages(fieldIdentifier).Any();
و یا حتی می‌توان با استفاده از رخ‌داد OnFieldChanged، برای مثال بررسی کرد که آیا کل فرم معتبر هست یا خیر؟ و اگر خیر، برای مثال دکمه‌ی submit را غیرفعال کرد. در این حالت همواره بهتر است که پاکسازی رویداد OnFieldChanged را در پایان کار انجام داد، تا برنامه دچار نشتی حافظه نشود.
مطالب
اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC
اگر مطلب «استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب» را مطالعه کرده باشید، قسمتی از آن، به فرم‌ها و همچنین جلب توجه کاربران به فیلدها، برای نمایش خطاهای اعتبارسنجی اختصاص داشت. در مطلب جاری قصد داریم تا این موارد را به یک فرم ASP.NET MVC که به صورت پیش فرض از jQuery Validator برای اعتبارسنجی استفاده می‌کند، اعمال کنیم تا حالت نمایشی پیش فرض این فرم‌ها و همچنین خطاهای اعتبارسنجی آن، با Twitter Bootstrap همخوانی پیدا کند.

مدل برنامه

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Mvc4TwitterBootStrapTest.Models
{
    public class User
    {
        [DisplayName("نام")]
        [Required(ErrorMessage="لطفا نام را تکمیل کنید")]
        public string Name { set; get; }

        [DisplayName("نام خانوادگی")]
        [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")]
        public string LastName { set; get; }
    }
}
در اینجا یک مدل ساده را به همراه دو خاصیت و اعتبارسنجی‌های ساده مرتبط با آن‌ها، مشاهده می‌کنید.

کنترلر برنامه

using System.Web.Mvc;
using Mvc4TwitterBootStrapTest.Models;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(User user)
        {
            if (this.ModelState.IsValid)
            {
                if (user.Name != "Vahid")
                {
                    this.ModelState.AddModelError("", "لطفا مشکلات را برطرف کنید!");
                    this.ModelState.AddModelError("Name", "نام فقط باید وحید باشد!");
                    return View(user);
                }
                // todo: save ...
                return RedirectToAction("Index");
            }
            return View(user);
        }
    }
}
کنترلر برنامه نیز نکته مهمی نداشته و بیشتر برای نمایش خطاهای اعتبارسنجی سفارشی این مثال طراحی شده است.

طراحی View سازگار با Twitter bootstrap

@model Mvc4TwitterBootStrapTest.Models.User
@{
    ViewBag.Title = "تعریف کاربر";
}
@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @class = "form-horizontal" }))
{
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset>
        <legend>تعریف کاربر</legend>
        <div class="control-group">
            @Html.LabelFor(x => x.Name, new { @class = "control-label" })
            <div class="controls">
                @Html.TextBoxFor(x => x.Name)
                @Html.ValidationMessageFor(x => x.Name, null, new { @class = "help-inline" })
            </div>
        </div>
        <div class="control-group">
            @Html.LabelFor(x => x.LastName, new { @class = "control-label" })
            <div class="controls">
                @Html.TextBoxFor(x => x.LastName)
                @Html.ValidationMessageFor(x => x.LastName, null, new { @class = "help-inline" })
            </div>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">
                ارسال</button>
        </div>
    </fieldset>
}
در اینجا View متناظر با اکشن متد Index را ملاحظه می‌کنید که نکات ذیل به آن اعمال شده است:
1) کلاس form-horizontal به فرم جاری اضافه شده است تا در ادامه بتوانیم برچسب‌ها را در کنار تکست باکس‌ها به صورت افقی نمایش دهیم.
2) به ValidationSummary کلاس‌های alert alert-error alert-block انتساب داده شده‌اند تا نمایش خطای کلی یک فرم، متناسب با Twitter bootstrap شود.
3) هر خاصیت، با یک div دارای کلاس control-group محصور شده است.
4) هر برچسب دارای کلاس control-label است.
5) به هر ValidationMessageFor کلاس help-inline انتساب داده شده است.
6) کنترل‌های ورودی برنامه در divایی با کلاس controls محصور شده‌اند.
7) قسمت دکمه فرم، در div ایی با کلاس form-actions قرار گرفته تا یک زمینه خاکستری در اینجا ظاهر شود.
8) دکمه فرم، با کلاس btn خاص bootstrap تزئین شده.

در این حالت به شکل فوق خواهیم رسید. همانطور که ملاحظه می‌کنید در صورتیکه بر روی دکمه ارسال کلیک شود، همان رنگ‌های متداول jQuery Validator ظاهر می‌شوند و کل ردیف همانند روش‌های متداول Twitter bootstrap دارای رنگ قرمز انتساب یافته توسط کلاس error نخواهد شد.

برای رفع این مشکل باید اندکی اسکریپت نویسی کرد:
@section javaScript
{
    <script type="text/javascript">
        $.validator.setDefaults({
            highlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                } else {
                    $(element).addClass(errorClass).removeClass(validClass);
                    $(element).closest('.control-group').removeClass('success').addClass('error');
                }
                $(element).trigger('highlated');
            },
            unhighlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                } else {
                    $(element).removeClass(errorClass).addClass(validClass);
                    $(element).closest('.control-group').removeClass('error').addClass('success');
                }
                $(element).trigger('unhighlated');
            }
        });

        $(function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        });
    </script>
}
کاری که در اینجا انجام شده، تغییر پیش فرض‌های jQuery Validator جهت سازگار سازی آن با کلاس error مرتبط با bootstrap است. همچنین در حالت postback و نمایش خطاهای سفارشی، قسمت بررسی field-validation-error انجام شده و در صورت یافتن موردی، سطر مرتبط با آن، با کلاس error مزین می‌شود.

اینبار در حالت اعتبار سنجی، به شکل ذیل خواهیم رسید:

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

و در حالت خطاهای سفارشی سمت سرور، پس از postback، شکل زیر نمایش داده می‌شود:


مطالب
بررسی تفاوت Task و ValueTask

زمانیکه تصمیم میگیریم کدهای زده شده را بهینه کنیم، اکثرا دنبال راه حل‌های جدید نمیگردیم. این مورد کاملا غریزی است؛ چرا که به‌دنبال کم‌ترین انرژی و بیشترین بازدهی هستیم؛ این طبیعت انسان است. صرفا کدهای قبلی را بازبینی میکنیم و سعی میکنیم  نحوه‌ی نوشتن منطق‌های موجود را بهینه کنیم. در همین راستا درک عملکرد Task و ValueTask ‌ها شاید قدمی مهم در مورد بهینه کردن کد‌ها باشد؛ چرا استفاده درست و بجای این دو مورد می‌تواند تاثیر زیادی بر روی سرعت و استفاده از مصرف حافظه داشته باشد؟ در این مقاله سعی میکنیم تا درک درستی از این دو داشته باشیم.


Task<T>  چیست؟

Task یک کلاس در فضای نام System.Threading.Tasks است؛ به‌طوریکه کمک میکند تا یک قسمت از برنامه به صورت مستقل از Thread اصلی اجرا شود. به‌بیان دیگر می‌تواند یک Thread Pool را ایجاد و با توجه به روند کار، از یک مرحله‌ی اجرایی به مرحله‌ای دیگر منتقل می‌کند. همچنین هر Task می‌تواند یک مقدار برگشتی نیز داشته باشد.

 این درحالی‌است که می‌تواند صرفا یک فرآیند را اجرا کند، بدون اینکه خروجی داشته باشد. به‌عبارتی دیگر اگر فرآیندی داشته باشیم که در نهایت یک شناسه را برمیگرداند، از Task<int> و اگر فرآیندی داشته باشیم که صرفا فرآیند همگام سازی داده‌های قدیمی به جدید را انجام میدهد، می‌تواند از نوع Task باشد.

همانطور که اشاره شد، Task یک کلاس است که شامل متد‌ها و فیلد‌های مختلفی می‌باشد. با استفاده از این اعضا می‌توان نحوه‌ی اجرای کدها و وضعیت‌های مختلف اجرای آن را مدیریت کرد، تا در نهایت اجرای آن کامل شود.

به دلیل اینکه Task یک class است و class ‌ها از نوع ReferenceType می‌باشند، روی حافظه‌ی Heap ذخیره می‌شوند و به‌ازای هر بار فراخوانی متدی که خروجی Task دارد، شیء Task را روی Heap ذخیره میکند. این شیء وضعیت اجرای قسمتی از کد ما را که میتواند sync یا async باشد، در خود ذخیره میکند تا در نهایت اجرای آن کامل شود.


نحوه استفاده از Task<T>

برای درک بهتر، یک تکه کد را با بهره بردن از Task ایجاد میکنیم :

public static class DummyWeatherProvider
{
    public static async Task<Weather> Get(string city)
    {
        await Task.Delay(10);
        var weather = new Weather 
        { 
            City = city, 
            Date = DateTime.Now, 
            AvgTempratureF = new Random().Next(5, 70) 
        };
        
        return weather;
    }
}
همان طور که مشخص است، کلاس موجود یک متد به نام Get دارد تا اطلاعات آب و هوای  شهر مورد نظر را به صورت یک Task  برگرداند. حال کد زیر را جهت بررسی تغییر وضعیت‌های اجرایی این Task ایجاد می‌کنیم :
static async Task CheckTaskStatus()
{
   var task = DummyWeatherProvider.Get("Stockholm");
    LogTaskStatus(task.Status);
    await task;
    LogTaskStatus(task.Status);
}

static void LogTaskStatus(TaskStatus status)
{
    Console.WriteLine($"Task Status: {Enum.GetName(typeof(TaskStatus), status)}");
}
TaskStatus یک enumeration است، به‌طوری‌که بیانگر وضعیت‌های مختلف یک Task در حال اجرا می‌باشد. برای مثال: WaitingForActivation, Running, RanToCompletion. در کد بالا ابتدا متد را فراخوانی می‌کنیم. سپس منتظر می‌مانیم تا متد اجرا شده، تکمیل شود. در اولین لاگ وضعیت، به WaitingForActivation و در دومین لاگ به RanToCompletion تبدیل میشود. حال‌که با Task ها و نحوه‌ی اجرای فرآیند آن آشنا شدیم، در قسمت بعدی به بررسی ValueTask ها می‌پردازیم. 

ValueTask<T>  چیست؟

همانند Task ، ValueTask هم برای مدیریت وضعیت فرآیند استفاده میشود؛ با این تفاوت که ValueTask ‌ها از نوع struct هستند. به‌طوریکه نحوه‌ی ذخیره سازی آن‌ها در حافظه به نسبت class ‌ها کاملا متفاوت است. از نقطه نظر سرعت، تشخیص دادن اینکه کدامیک باید استفاده شود، باید با توجه به سناریو، بررسی و انتخاب شود؛ چرا که از نظر تخصیص حافظه متفاوت عمل می‌کنند. برای درک بهتر عملکرد ValueTask ‌ها کد زیر را بررسی میکنیم :

public class WeatherService
{
    private readonly ConcurrentDictionary<string, Weather> _cache;
    public WeatherService()
    {
        _cache = new();
    }

    public async Task<Weather> GetWeatherTask(string city)
    {
        if (!_cache.ContainsKey(city))
        {
            var weather = await DummyWeatherProvider.Get(city);
            _cache.TryAdd(city, weather);
        }
        return _cache[city];
    }

    public async ValueTask<Weather> GetWeatherValueTask(string city)
    {
        if (!_cache.ContainsKey(city))
        {
            var weather = await DummyWeatherProvider.Get(city);
            _cache.TryAdd(city, weather);
        }
        return _cache[city];   
  }

کلاس WeatherService شامل یک فیلد private از نوع collection و دو متد است. ما از _cache  جهت نگهداری اطلاعاتی که قبلا دریافت شده، استفاده می‌کنیم و به نوعی in-memory cache را پیاده سازی میکنیم. پیاده سازی منطق هر دو متد  GetWeatherTask و GetWeatherValueTask  کاملا شبیه به هم است؛ به‌طوری‌که اول بررسی میکنیم اطلاعات آب و هوای شهر مورد نظر در _cache وجود دارد یا خیر؟ اگر وجود داشت، اطلاعات به صورت مستقیم برگشت داده می‌شود؛ در غیر این صورت DummyWeatherProvider.Get()  فراخوانی خواهد شد. 

در قدم بعدی اطلاعات به‌دست آمده را در _cache ذخیره می‌کنیم. سپس مقدار ذخیره شده را برگشت میدهیم. در واقع تنها تفاوت دو متد ذکر شده، نوع خروجی آن می‌باشد؛ یکی از Taskو دیگری از ValueTask استفاده می‌کند.

برای مقایسه‌ی مصرف حافظه‌ی این دو روی هر دو متد، Benchmark میگیریم. برای پیاده سازی نیار به کد‌های زیر داریم : 

[MemoryDiagnoser]
public class TaskAndValueTaskBenchmark
{
    private readonly WeatherService _weatherService;
    public TaskAndValueTaskBenchmark()
    {
        _weatherService = new();
    }
    
    [Benchmark]
    [Arguments("Denver")]
    public async Task<Weather> TaskBenchmark(string city)
    {
        return await _weatherService.GetWeatherTask(city);
    }

    [Benchmark]
    [Arguments("London")]
    public async ValueTask<Weather> ValueTaskBenchmark(string city)
    {
        return await _weatherService.GetWeatherValueTask(city);
    }
}

نتیجه به دست آمده به شرح زیر است :

Allocated

Gen0

Method

144 B

0.0229

TaskBenchmark

------

----

ValueTaskBenchmark

  با توجه به نتیجه به‌دست آمده، متدی که خروجی ValueTask دارد، حافظه‌ای را تخصیص نداده‌است؛ این دقیقا مزیت مهم ValueTask نسبت به Task  می‌باشد.

مزیت  ValueTask<T>

به‌دلیل اینکه از نوع struct هستند، بر روی حافظه، در قسمت Stack ذخیره می‌شوند و به صورت خودکار بعد از اینکه نیازی به آنها نباشد، از حافظه حذف می‌شوند . به همین دلیل به شکل قابل توجهی، فشار را از روی GC کاهش می‌دهد .

 علاوه بر این، در سناریویی که اکثر کدها به صورت sync اجرا می‌شوند، در این مواقع استفاده از ValueTask، بهتر از Task می‌باشد .

این سری متد GetWeatherValueTask   را جهت تشخص اینکه  اغلب کدها به صورت sync یا async اجرا می‌شوند، بررسی می‌کنیم. در متد ذکر شده اگر اطلاعات شهر مورد نظر وجود داشته باشد، کار به صورت sync اجرا می‌شود و اگر شهر وجود نداشته باشد، کار به صورت async اجرا می‌شود. با بررسی دقیق‌تر متوجه می‌شویم اکثر مواقع در این متد کار به صورت sync  اجرا می‌شود؛ چرا که بعد ازدریافت اطلاعات، مجدد آن را دریافت نمیکند، بلکه از حافظه میخواند (همان _cache ) .


محدودیت‌های استفاده از    ValueTask<T>  

1. در اینجا تنها یکبار امکان استفاده از await وجود دارد. وقتی یکبار valueTask را await می‌کنیم، بهتر است کار دیگری بر روی آن انجام ندهیم؛ چراکه ممکن است از حافظه پاک شده باشد.

2. اگر در سناریویی لازم دارید چندین بار await را بر روی valueTask اجرا کنید، لازم است ابتدا آن را به Task تبدیل کنیم. برای این کار متد AsTask را فراخوانی میکنیم (بهتر است صرفا یکبار متد AsTask را فراخوانی کنیم).

3. نمیتوانیم به یک ValueTask به صورت هم زمان در حالت Multi threads دسترسی داشته باشیم.

4. به صورت پیش فرض خروجی عملیات async، نوع Task می‌باشد؛ مگر اینکه اغلب مراحل کار به صورت sync اجرا شود، مانند مثالی که بالاتر اشاره شد.


منابع :

مطالب دوره‌ها
اصول طراحی یک سیستم افزونه پذیر به کمک StructureMap
قصد داریم سیستمی را طراحی کنیم که افزونه‌های خود را در زمان اجرا از مسیری خاص خوانده و سپس وهله‌های آن‌ها‌را جهت استفاده در دسترس قرار دهد. برنامه‌ای که در اینجا مورد بررسی قرار می‌گیرد، یک برنامه‌ی WinForms ساده است؛ به نام WinFormsWithPluginSupport. اما اصول کلی مطرح شده، در تمام فناوری‌های دیگر دات نتی نیز کاربرد دارد و یکسان است.


تهیه قرارداد

یک پروژه‌ی Class library به نام PluginsBase را به Solution جاری اضافه کنید. به آن اینترفیس قرار داد پلاگین‌های برنامه خود را اضافه نمائید. برای مثال:
namespace PluginsBase
{
    public interface IPlugin
    {
        string Name { get; }
        void Run();
    }
}
هر پلاگین دارای یک نام یا توضیح خاص خود خواهد بود به همراه متدی برای اجرای منطق مرتبط با آن.


تهیه سه پلاگین جدید

به Solution جاری سه پروژه‌ی مجزای Class library با نام‌های plugin1 تا 3 را اضافه کنید. در ادامه به هر پلاگین، ارجاعی را به اسمبلی PluginsBase، برای دریافت قرارداد پیاده سازی منطق پلاگین، اضافه نمائید. هدف این است که اینترفیس IPlugin، در این اسمبلی‌ها قابل دسترسی شود.
هر پلاگین هم دارای برای مثال کدهایی مانند کد ذیل خواهد بود که در آن صرفا نام آن‌ها به 2 و 3 تنظیم می‌شود.
using PluginsBase;

namespace Plugin1
{
    public class Plugin1Main : IPlugin
    {
        public string Name
        {
            get { return "Test 1"; }
        }

        public void Run()
        {
            // todo: ...
        }
    }
}


کپی خودکار پلاگین‌ها به پوشه‌ی مخصوص آن‌ها

به پروژه‌ی WinFormsWithPluginSupport مراجعه کنید. در پوشه‌ی bin\debug آن یک پوشه‌ی جدید به نام Plugins ایجاد نمائید. بدیهی است هربار که پلاگین‌های برنامه تغییر کنند نیاز است اسمبلی‌های نهایی آن‌ها را به این پوشه کپی نمائیم. اما راه بهتری نیز وجود دارد. به خواص هر کدام از پروژه‌های پلاگین مراجعه کرده و برگه‌ی Build events را باز کنید.


در اینجا قسمت post-build event را به نحو ذیل تغییر دهید:
 Copy "$(ProjectDir)$(OutDir)$(TargetName).*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"
این کار را برای هر سه پلاگین تکرار کنید.
به این ترتیب هربار که پلاگین جاری کامپایل شود، پس از آن به صورت خودکار به پوشه‌ی plugins تعیین شده، کپی می‌شود و دیگر نیازی به کپی دستی نخواهد بود.
تنظیم فوق، تنها اسمبلی اصلی پروژه را به پوشه‌ی bin\debug\plugins کپی می‌کند. اگر می‌خواهید تمام فایل‌ها کپی شوند، از تنظیم ذیل استفاده کنید:
 Copy "$(ProjectDir)$(OutDir)*.*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"


اضافه کردن وابستگی‌های اصلی پروژه‌ی WinForms

در ادامه بسته‌ی نیوگت StructureMap را به پروژه‌ی WinForms از طریق دستور ذیل اضافه کنید:
 PM> install-package structuremap
همچنین این پروژه تنها نیاز دارد ارجاع مستقیمی را به اسمبلی PluginsBase ابتدای مطلب داشته باشد. از آن، جهت تنظیمات اولیه یافتن افزونه‌ها استفاده می‌کنیم.


تعریف محل ثبت پلاگین‌ها

روش‌های متفاوتی برای کار با StructureMap وجود دارد. یکی از آن‌ها تعریف کلاسی است مشتق شده از کلاس Registry آن به نحو ذیل:
using System.IO;
using System.Windows.Forms;
using PluginsBase;
using StructureMap.Configuration.DSL;
using StructureMap.Graph;

namespace WinFormsWithPluginSupport.Core
{
    public class PluginsRegistry : Registry
    {
        public PluginsRegistry()
        {
            this.Scan(scanner =>
            {
                scanner.AssembliesFromPath(
                    path: Path.Combine(Application.StartupPath, "plugins"),
                    // یک اسمبلی نباید دوبار بارگذاری شود
                    assemblyFilter: assembly =>
                    {
                        return !assembly.FullName.Equals(typeof(IPlugin).Assembly.FullName);
                    });
                scanner.AddAllTypesOf<IPlugin>().NameBy(item => item.FullName);
            });
        }
    }
}
در اینجا مشخص کرده‌ایم که اسمبلی‌های پوشه plugins را که یک سطح پایین‌تر از پوشه‌ی اجرایی برنامه قرار می‌گیرند، خوانده و در این بین آن‌هایی را که پیاده سازی از اینترفیس IPlugin دارند، در دسترس قرار دهد.

یک نکته‌ی مهم
در قسمت assemblyFilter تعیین کرده‌ایم که اسمبلی تکراری PluginBase بارگذاری نشود. چون این اسمبلی هم اکنون به برنامه‌ی WinForms ارجاع دارد. رعایت این نکته جهت رفع تداخلات آتی بسیار مهم است. همچنین این فایل در پوشه‌ی Plugins نیز نباید حضور داشته باشد وگرنه شاهد بارگذاری افزونه‌ها نخواهید بود.


سپس نیاز به وهله سازی Container آن و معرفی این کلاس PluginsRegistry می‌باشد:
using System;
using System.Threading;
using StructureMap;

namespace WinFormsWithPluginSupport
{
    public static class IocConfig
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);

        public static IContainer Container
        {
            get { return _containerBuilder.Value; }
        }

        private static Container defaultContainer()
        {
            return new Container(x =>
            {
                x.AddRegistry<PluginsRegistry>();
            });
        }
    }
}


تنظیمات ابتدایی WinForms برای دسترسی به امکانات StructureMap

به فرم اصلی برنامه مراجعه کرده و به سازنده‌ی آن IContainer را اضافه کنید. از این اینترفیس جهت دسترسی به پلاگین‌های برنامه استفاده خواهیم کرد.
using System.Windows.Forms;
using StructureMap;

namespace WinFormsWithPluginSupport
{
    public partial class FrmMain : Form
    {
        private readonly IContainer _container;

        public FrmMain(IContainer container)
        {
            _container = container;
            InitializeComponent();
        }
    }
}
اکنون برنامه دیگر کامپایل نخواهد شد؛ چون در فایل Program.cs وهله سازی مستقیمی از FrmMain وجود دارد. این وهله سازی را اکنون به StructureMap محول می‌کنیم تا مشکل برطرف شود:
using System;
using System.Windows.Forms;

namespace WinFormsWithPluginSupport
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(IocConfig.Container.GetInstance<FrmMain>());
        }
    }
}
زمانیکه از متد IocConfig.Container.GetInstance استفاده می‌شود، تا هر تعداد سطحی که تعریف شده، سازنده‌های کلاس‌های مرتبط وهله سازی می‌شوند. در اینجا نیاز است سازنده‌ی کلاس FrmMain وهله سازی شود. چون IContainer اینترفیس اصلی خود StructureMap است، آن‌را شناخته و به صورت خودکار وهله سازی می‌کند. اگر اینترفیس دیگری را ذکر کنید، نیاز است مطابق معمول آن‌را در کلاس IocConfig و متد defaultContainer آن معرفی نمائید.


بارگذاری و اجرای افزونه‌ها

دو دکمه‌ی Run و ReLoad را به فرم اصلی برنامه با کدهای ذیل اضافه کنید:
using System.Linq;
using System.Windows.Forms;
using PluginsBase;
using StructureMap;
using WinFormsWithPluginSupport.Core;

namespace WinFormsWithPluginSupport
{
    public partial class FrmMain : Form
    {
        private readonly IContainer _container;

        public FrmMain(IContainer container)
        {
            _container = container;
            InitializeComponent();
        }

        private void BtnRun_Click(object sender, System.EventArgs e)
        {
            var plugins = _container.GetAllInstances<IPlugin>().ToList();
            foreach (var plugin in plugins)
            {
                plugin.Run();
            }
        }

        private void BtnReload_Click(object sender, System.EventArgs e)
        {
            _container.EjectAllInstancesOf<IPlugin>();
            _container.Configure(x =>
                x.AddRegistry<PluginsRegistry>()
            );
        }
    }
}
در اینجا توسط متد container.GetAllInstances می‌توان به تمامی وهله‌های پلاگین‌های بارگذاری شده، دسترسی یافت و سپس آن‌ها را اجرا کرد.
همچنین در متد ReLoad نحوه‌ی بارگذاری مجدد این پلاگین‌ها را در صورت نیاز مشاهده می‌کنید.
اگر برنامه را اجرا کردید و پلاگینی بارگذاری نشد، به دنبال اسمبلی‌های تکراری بگردید. برای مثال PluginsBase نباید هم در پوشه‌ی اصلی اجرایی برنامه حضور داشته باشد و هم در پوشه‌ی پلاگین‌ها.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
WinFormsWithPluginSupport.zip
 
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت نهم
در قسمت‌های قبل یاد گرفتید که چطور View و View Model را در متد RegisterTypes در App.xaml.cs به یکدیگر وصل کرده و به آن‌ها یک اسم دهید و با Navigation Service آن‌ها را در حالت‌های مختلف مثل Navigation Page و Master Detail و Popup و ... باز کنید و بین صفحات جابجا شوید.
در View‌ها دو مورد را به صورت ابتدایی توضیح دادیم، Binding و Command. در این قسمت وارد جزئیات می‌شویم.

Command قطعه‌ای کد در View Model است (عموما یک متد CSharp ای) که در View می‌تواند مورد استفاده قرار گیرد. برای این که کلیک بر روی یک دکمه، متد مربوطه در View Model را اجرا کند، آن را درون یک BitDelegateCommand قرار دادیم و به Command دکمه Bind کردیم.
می‌دانیم که Button، بجز کلیک، دارای Event‌های دیگری نیز هست. علاوه بر این، ممکن است بجای کلیک دکمه، بخواهیم زمانیکه روی یک عکس عمل Swipe انجام می‌شود، کاری در سمت View Model انجام شود.
برای حل این مشکل از Event To Command و Gesture Recognizer استفاده می‌کنیم. مورد اول همانطور که از نام‌اش پیداست، وظیفه آن اتصال بر قرار کردن بین یک Event و یک Command است و دومی نیز برای مسائلی چون Swipe - Tap - Pan - Pinch و ... بکار می‌رود.
در کد پایین که می‌تواند در HelloWorldView.xaml در پروژه XamApp نیز تست شود، یکبار Command دکمه را Bind کرده‌ایم و یکبار Clicked Event آن را، که البته نتیجه یکی است! به جای نام Clicked می‌توان نام هر یک از Event‌‌ها را نوشت. همچنین این کار بجز Button، بر روی باقی کنترل‌ها نیز کار می‌کند.
        <Button Command="{Binding IncreaseStepsCountCommand}" Text="Button with command binding" />
        <Button Text="Button with event to command">
            <Button.Behaviors>
                <prismBehaviors:EventToCommandBehavior Command="{Binding IncreaseStepsCountCommand}" EventName="Clicked" />
            </Button.Behaviors>
        </Button>
همانطور که می‌بینید، EventToCommandBehavior که توسط Prism ارائه شده‌است، به Behavior‌های Button اضافه شده‌است. هر Behavior، رفتاری خاص را به کنترل‌های شما اضافه می‌کند و امکان نوشتن Custom behavior نیز وجود دارد که از موضوع این بحث خارج است.

prismBehaviors در بالای فایل Xaml به شکل زیر تعریف شده است:
xmlns:prismBehaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
این عمل معادل using نوشتن در بالای فایل‌های CSharp است و می‌گوید که EventToCommandBehavior در کدام namespace و dll است. وقتی این کار را می‌کنید، می‌توانید در Xaml به تمامی کلاس‌های آن namespace دسترسی داشته باشید.

در همان مثال HelloWorldView.xaml، یک Label بود که تعداد زده شدن دکمه را نشان می‌داد. اگر بخواهیم وقتی روی آن Label به سمت چپ Swipe می‌کنیم نیز Command مربوطه اجرا شود، خواهیم داشت:
        <Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}">
            <Label.GestureRecognizers>
                <SwipeGestureRecognizer Command="{Binding IncreaseStepsCountCommand}" Direction="Left" />
            </Label.GestureRecognizers>
        </Label>
در صورتیکه قصد تست بر روی Windows-UWP را دارید و صفحه نمایش لمسی ندارید، مطابق با آموزش قسمت Windows همین سری آموزشی، بر روی Simulator خروجی بگیرید.

در مورد Command، مطالبی چون Command Parameter و Event Args Parameter Path و Event Args Converter باقی می‌مانند که در قسمت‌های بعدی به آن‌ها نیز خواهیم پرداخت. ولی به صورت کلی در نظر داشته باشید که جای منطق برنامه، در سمت View Model است و اگر در سناریویی نمی‌دانستید که چگونه در واکنش به تغییری در View، در سمت View Model کاری کنید، می‌توانید همینجا سوال خود را بپرسید.

Binding نیز همان طور که تا الان متوجه شده‌اید، باعث اتصال دو چیز به یک دیگر می‌شود. وقتی می‌نویسیم:
<Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}" />
داریم Text را که یک Property در Label در View است، به StepsCount که یک Property در View Model است، وصل می‌کنیم.
هر Binding یک Source دارد. به صورت پیش فرض، چون داریم از Prism استفاده می‌کنیم، تمامی Binding‌ها در View به صورت پیش فرض به View Model اشاره می‌کنند؛ مگر اینکه چیز دیگری بگوییم. برای مثال، اگر بخواهیم یک Label داشته باشیم که متن یک Entry را نمایش می‌دهد، هیچ‌یک از این دو در View Model نیستند. پس در این Binding خاص، باید سورس را نیز مشخص کنیم:
        <Entry x:Name="MyEntry" />
        <Label Text="{Binding Text, Source={x:Reference MyEntry}}" />
این باعث می‌شود که Text، که یک Property در Label است، به Text، که یک Property در کنترلی با نام MyEntry است، وصل شود.

x:DataType
Binding‌ها به صورت پیش فرض، در زمان اجرا و با کمک Reflection کار می‌کنند؛ این مهم دو مسئله را ایجاد می‌کند:
1- اگر در View، نام Property مد نظر در View Model را به اشتباه بنویسید، تا زمانیکه برنامه را اجرا نکنید و وارد آن فرم نشوید، متوجه مشکل دار بودن ماجرا نمی‌شوید و وقتی در View Model نام یک Property را عوض می‌کنید، در سمت View در هنگام Build خطایی نمی‌گیرید.
2- Reflection چیز خوبی است و برخی سناریوها بدون آن خیلی سخت پیاده سازی می‌شوند؛ ولی Reflection سربار کارآیی دارد و بهتر است در صورت امکان از آن پرهیز شود.
برای حل این مشکل در Xamarin Forms امکانی به نام x:DataType معرفی شده‌است که در View می‌گویید که Source چه کلاسی است، که قاعدتا View Model است. برای این کار داریم:
<ContentPage
...
    xmlns:vm="clr-namespace:XamApp.ViewModels"
    x:DataType="vm:HelloWorldViewModel">
با این روش، هر دو مشکل فوق حل می‌شوند و هم خطای زمان Build برای Binding‌های اشتباه خواهیم داشت و هم بدون Reflection از Binding‌ها استفاده می‌کنیم که بهبود سرعت برنامه را در بر دارد.

INotifyPropertyChanged
در مثال XamApp، وقتی روی دکمه کلیک می‌کنیم، مقدار StepsCount را که یک Property در View Model است، یکی یکی افزایش می‌دهیم. این Property در View به Label بایند شده‌است. وقتی مقداری در View Model که سورس Binding ما محسوب می‌شود رخ می‌دهد، View چگونه خبر دار می‌شود؟
هر کلاسی که بخواهد در Binding به عنوان Source استفاده شود (منجمله View Model)، می‌تواند یک interface را پیاده سازی کند؛ به نام INotifyPropertyChanged. همچنین تعریف یک Property نیز باید به این شکل باشد:
        private int _StepsCount;
        public int StepsCount
        {
            get => _StepsCount;
            set => SetProperty(ref _StepsCount, value);
        }
طبیعتا این کد که در سناریوهایی می‌تواند حتی پیچیده‌تر شود، جالب نیست و برای همین ما از  PropertyChanged.Fody استفاده کرده‌ایم که در زمان Build، خودش همه مسائل را مدیریت می‌کند و کد فوق به سادگی می‌تواند به صورت زیر نوشته شود:
public int StepsCount { get; set; }
به صفحه GitHub این کتابخانه مراجعه کنید و با سایر امکانات فوق العاده‌ی آن آشنا شوید!
در Binding، موارد دیگری چون Value Converters و Relative Source نیز وجود دارند که در ادامه با آنها آشنا می‌شویم. اگر در Binding نیز مشکلی دارید، می‌توانید همینجا نیز بپرسید.
مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت چهارم - اعتبارسنجی Async سمت کلاینت و یا همان Remote Client Side Validation
در قسمت قبل با نحوه‌ی پیاده سازی اعتبارسنجی‌های سفارشی سمت کلاینت مخصوص کتابخانه‌ی Fluent Validation آشنا شدیم. در این قسمت، یک حالت خاص همان نوع اعتبارسنجی‌های سمت کلاینت را که remote validation نام دارد، بررسی می‌کنیم. در این حالت خاص، نیازی به کدنویسی جاوااسکریپتی خاصی نیست. چون زیرساخت آن به همراه unobtrusive jQuery Ajax خود ASP.NET Core ارائه می‌شود. در اینجا فقط نیاز است تا متادیتای خاص آن‌را تولید کنیم. به عبارتی اینبار هدف ما تنها تولید یک چنین تگ HTML ای است:
<input dir="ltr" class="form-control input-validation-error" 
type="email" 
data-val="true" 
data-val-email="'آدرس ایمیل' is not a valid email address." 
data-val-remote="این آدرس ایمیل هم اکنون مورد استفاده‌است" 
data-val-remote-url="/Home/ValidateUniqueEmail" 
data-val-required="'آدرس ایمیل' must not be empty." 
id="Email" 
name="Email" 
>
که به همراه ویژگی‌های data-val ، data-val-remote و data-val-remote-url است. همینقدر که این سه ویژگی وجود داشته باشند، مابقی منطق اعتبارسنجی سمت کلاینت آن توسط unobtrusive jQuery Ajax (ارسال خودکار Ajax ای مقدار ایمیل، به سمت سرور و دریافت پاسخ) و unobtrusive java script validation مدیریت خواهند شد.


افزودن آدرس ایمیل به مدل کاربران

به همان مدل قسمت قبل، قصد داریم خاصیت آدرس ایمیل را هم اضافه کنیم:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class UserModel
    {
        [Display(Name = "نام کاربری")]
        public string Username { get; set; }

        [Display(Name = "سن")]
        public int Age { get; set; }

        [Display(Name = "سابقه کار")]
        public int Experience { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
    }
}


ایجاد سرویسی برای بررسی منحصربفرد بودن آدرس ایمیل

در ادامه قصد داریم سرویسی را ایجاد کنیم که برای مثال با بانک اطلاعاتی ارتباط برقرار کرده (در اینجا جهت سهولت ارائه، از یک آرایه استفاده شده‌است) و مشخص می‌کند که آیا ایمیل دریافتی پیشتر استفاده شده‌است یا خیر:
using System.Linq;

namespace FluentValidationSample.Services
{
    public interface IUsersService
    {
        bool IsUniqueEmail(string emailAddress);
    }

    public class UsersService : IUsersService
    {
        public bool IsUniqueEmail(string emailAddress)
        {
            string[] registedEmails = {
                "email@site.com",
                "test@gmail.com"
            };
            return !registedEmails.Contains(emailAddress);
        }
    }
}
این سرویس را هم با طول عمر Scoped به برنامه معرفی می‌کنیم؛ چون طول عمر سرویس‌هایی که با بانک اطلاعاتی و DbContext کار می‌کنند، به همین نحو تعیین می‌شوند:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();


ایجاد اعتبارسنج سمت سرور بررسی منحصربفرد بودن آدرس ایمیل

در ادامه یک PropertyValidator جدید را ایجاد می‌کنیم تا بتوان توسط آن مقدار ایمیل دریافتی را در سمت سرور، تعیین اعتبار کرد:
using FluentValidation.Validators;
using FluentValidationSample.Services;

namespace FluentValidationSample.ModelsValidations
{
    public class UniqueEmailValidator : PropertyValidator
    {
        private readonly IUsersService _usersService;

        public UniqueEmailValidator(IUsersService usersService)
                : base("این آدرس ایمیل هم اکنون مورد استفاده‌است")
        {
            _usersService = usersService;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            return context.PropertyValue != null &&
                    _usersService.IsUniqueEmail((string)context.PropertyValue);
        }
    }
}
همانطور که مشاهده می‌کنید، در اینجا تزریق سرویس سفارشی IUsersService به سازنده‌ی کلاس اعتبارسنج، مجاز است و بدون مشکل کار می‌کند.
پس از آن جهت سهولت استفاده‌ی از آن، یک متد الحاقی جدید را نیز به نام UniqueEmail به نحو زیر تعریف می‌کنیم:
using FluentValidation;
using FluentValidationSample.Services;

namespace FluentValidationSample.ModelsValidations
{
    public static class CustomValidatorExtensions
    {
        public static IRuleBuilderOptions<T, string> UniqueEmail<T>(
            this IRuleBuilder<T, string> ruleBuilder, IUsersService usersService)
        {
            return ruleBuilder.SetValidator(new UniqueEmailValidator(usersService));
        }
    }
}

یک نکته: اگر دقت کرده باشید، فضای نام این اعتبارسنج در این قسمت FluentValidationSample.ModelsValidations شده‌است:


علت اینجا است که اعتبارسنج تعریف شده نیاز دارد هم از مدل‌ها استفاده کند و هم از سرویس کاربران. سرویس کاربران هم از مدل‌ها استفاده می‌کند. به همین جهت اگر تعاریف اعتبارسنجی را داخل پروژه‌ی مدل‌ها قرار دهیم، یک وابستگی حلقوی رخ خواهد داد (وابستگی مدل‌ها به سرویس‌ها و برعکس). بنابراین بهتر است اعتبارسنج‌ها را به یک پروژه‌ی مجزا منتقل کنیم تا از بروز این cyclic dependency جلوگیری شود.


اعمال اعتبارسنجی منحصربفرد بودن ایمیل دریافتی به اعتبارسنج UserModel

پس از تهیه‌ی متد الحاقی UniqueEmail، آن‌را به RuleFor مخصوص خاصیت ایمیل اضافه می‌کنیم. در اینجا نیز تزریق وابستگی سرویس سفارشی IUsersService به سازنده‌ی کلاس اعتبارسنج مجاز است:
using FluentValidation;
using FluentValidationSample.Models;
using FluentValidationSample.Services;

namespace FluentValidationSample.ModelsValidations
{
    public class UserModelValidator : AbstractValidator<UserModel>
    {
        public UserModelValidator(IUsersService usersService)
        {
            RuleFor(x => x.Username).NotNull();
            RuleFor(x => x.Age).NotNull();
            RuleFor(x => x.Experience).LowerThan(nameof(UserModel.Age)).NotNull();
            RuleFor(x => x.Email).EmailAddress().NotNull().UniqueEmail(usersService);
        }
    }
}


ایجاد متادیتای مورد نیاز جهت unobtrusive java script validation در سمت سرور

در ادامه نیاز است ویژگی‌های data-val خاص unobtrusive java script validation را توسط FluentValidation ایجاد کنیم:
using FluentValidation;
using FluentValidation.AspNetCore;
using FluentValidation.Internal;
using FluentValidation.Resources;
using FluentValidation.Validators;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace FluentValidationSample.ModelsValidations
{
    public class RemoteClientValidator : ClientValidatorBase
    {
        public string RemoteUrl { set; get; }

        public RemoteClientValidator(PropertyRule rule, IPropertyValidator validator) :
            base(rule, validator)
        {
        }

        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-remote", GetErrorMessage(context));
            MergeAttribute(context.Attributes, "data-val-remote-url", RemoteUrl);
        }

        private string GetErrorMessage(ClientModelValidationContext context)
        {
            var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName());
            string messageTemplate;
            try
            {
                messageTemplate = Validator.Options.ErrorMessageSource.GetString(null);
            }
            catch (FluentValidationMessageFormatException)
            {
                messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>();
            }
            return formatter.BuildMessage(messageTemplate);
        }
    }
}
در این کدها، تنها قسمت مهم آن، متد AddValidation است که کار تعریف و افزودن متادیتاهای unobtrusive java script validation را انجام می‌دهد و برای مثال سبب رندر تگ HTML ای زیر می‌شود:
<input dir="ltr" class="form-control input-validation-error" 
type="email" 
data-val="true" 
data-val-email="'آدرس ایمیل' is not a valid email address." 
data-val-remote="این آدرس ایمیل هم اکنون مورد استفاده‌است" 
data-val-remote-url="/Home/ValidateUniqueEmail" 
data-val-required="'آدرس ایمیل' must not be empty." 
id="Email" 
name="Email" 
>
که به همراه ویژگی‌های data-val، data-val-remote و data-val-remote-url است تا unobtrusive jQuery Ajax validation را فعال کند.


افزودن اعتبارسنج‌های تعریف شده به تنظیمات برنامه

پس از تعریف UniqueEmailValidator و RemoteClientValidator، روش افزودن آن‌ها به تنظیمات FluentValidation به صورت زیر است:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();

            services.AddControllersWithViews().AddFluentValidation(
                fv =>
                {
                    fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
                    fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
                    fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;

                    fv.ConfigureClientsideValidation(clientSideValidation =>
                    {
                        // ...

                        clientSideValidation.Add(
                            validatorType: typeof(UniqueEmailValidator),
                            factory: (context, rule, validator) =>
                                        new RemoteClientValidator(rule, validator)
                                        {
                                            RemoteUrl = "/Home/ValidateUniqueEmail"
                                        });
                    });
                }
            );
        }
در اینجا یک RemoteUrl را هم مشاهده می‌کنید که به صورت زیر باید تعریف شود:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IUsersService _usersService;

        public HomeController(IUsersService usersService)
        {
            _usersService = usersService;
        }

         // ...

        public IActionResult ValidateUniqueEmail(string email)
        {
            return Ok(_usersService.IsUniqueEmail(email));
        }
    }
}
زمانیکه اعتبارسنجی سمت کلاینت رخ می‌دهد، آدرس ایمیل، به اکشن متد فوق ارسال شده و یک true و یا false را دریافت می‌کند که بیانگر موفقیت آمیز بودن و یا شکست اعتبارسنجی از راه دور است.


تعریف کدهای جاوا اسکریپتی مورد نیاز

پیش از هرکاری، اسکریپت‌های فایل layout برنامه باید چنین تعریفی را داشته باشند:
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
در اینجا مداخل jquery، سپس jquery.validate و بعد از آن jquery.validate.unobtrusive را مشاهده می‌کنید. در ادامه نیازی به تکمیل فایل js/site.js جهت افزودن کدهای remote client validation نیست و این کدها جزئی از کتابخانه‌ی jquery.validate.unobtrusive هستند.


آزمایش برنامه

View این قسمت نیز همانند قسمت قبل است که فقط یک آدرس ایمیل به آن اضافه شده‌است:


برای آزمایش آن اگر برای مثال یکی از آدرس‌های ایمیل از پیش تعریف شده‌ی در متد IsUniqueEmail سرویس کاربران را وارد کنیم، با خطای اعتبارسنجی سمت کلاینت فوق روبرو خواهیم شد.


کدهای کامل این سری را تا این قسمت از اینجا می‌توانید دریافت کنید: FluentValidationSample-part04.zip
مطالب دوره‌ها
استفاده از Delegates بجای اینترفیس‌ها در تزریق وابستگی‌ها
عموما در معکوس سازی مسئولیت‌ها و واگذاری آن‌ها به لایه‌های دیگر، از اینترفیس‌ها استفاده می‌شود. روش دیگری را که در اینجا می‌توان بکار گرفته استفاده از Delegates است بجای اینترفیس‌ها. از این جهت که یک Delegate در عمل می‌تواند به صورت یک Anonymous Interface عمل کند.
در بسیاری از مواردی که اینترفیس شما تنها از یک متد تشکیل می‌شود، می‌توان عملکرد آن‌را با یک Delagate جهت ساده سازی فرآیند تزریق وابستگی‌ها تعویض کرد.

یک مثال از تعویض اینترفیس‌های تک متدی با Delegates

public interface IAuthentication
{
    bool IsUserAuthenticated(string userName, string password);
}

public class AuthenticationService : IAuthentication
{
    public bool IsUserAuthenticated(string userName, string password)
    {
        return userName == "Vahid" && password == "123";
    }
}

public class LoginController
{
    private readonly IAuthentication _authentication;
    public LoginController(IAuthentication authentication)
    {
        _authentication = authentication;
    }

    // ...
}

مثال بسیار متداول فوق را درنظر بگیرید. در لایه سرویس برنامه، اینترفیس و کلاس پیاده سازی کننده منطق اعتبارسنجی کاربران را تدارک دیده‌ایم. نهایتا جایی در سطحی بالاتر از این توانمندی قرار است استفاده شود. مثلا در کلاس LoginController.
نکته مهم اینترفیس IAuthentication، تک متدی بودن آن است. به همین جهت تعریف کلاس LoginController را به شکل زیر نیز می‌توان بازنویسی کرد:
public class LoginController
{
    private readonly Func<string, string, bool> _authenticationStrategy;
    public LoginController(Func<string, string, bool> authenticationStrategy)
    {
        _authenticationStrategy = authenticationStrategy;
    }

    // ...
}
در این حالت نیز کلاس LoginController استفاده کننده از Delegate تعریف شده، از نحوه و استراتژی اعتبارسنجی بی‌خبر است و پیاده سازی آن به کلاسی دیگر که قرار است از LoginController استفاده کند، واگذار می‌شود.
مطالب
نکاتی توصیه ای برای برنامه نویسی اندروید : قسمت اول
اگر جدیدا قصد برنامه نویسی اندروید را کرده‌اید، یا هنوز روش‌های متدوالی را برای کار با این زبان انتخاب نکرده‌اید؛ به نظرم این مقاله می‌تواند کمک خوبی برای شما باشد. مسائلی که بیان میکنم در واقع از تجربیات شخصی و راه حل هایی است که برای خودم تعیین کرده‌ام و تعدادی از آن‌ها را در طول مدتی که در این زمینه فعالیت کرده‌ام، از جاهای مختلف دیده و در یک جا گردآوری کرده‌ام. برای نامگذاری اشیاء و متغیرها و دیگر موارد، من از این قاعده پیروی میکنم که به نظرم بسیار ایده آل می‌باشد. الگوی معماری هم که جدیدا مورد استفاده قرار داده‌ام، الگوی MVP است که نمونه‌ای از آن، در گیت هاب قرار گرفته است. البته این مثال ساده تر نیز وجود دارد. تشریح کامل این معماری را به همراه آزمون واحد آن، می‌توانید در این مقاله سه قسمتی ببینید.

در اینجا، یک سری نکات را در طول برنامه نویسی، متذکر می‌شوم تا مدیریت کدهای شما را در اندروید راحت‌تر کند.

یک نکته‌ی دیگر را که باید متذکر شوم این است که همه اصطلاحاتی که در این مقاله استفاده می‌شوند بر اساس اندروید استادیو و مستندات رسمی گوگل است است؛ به عنوان نمونه عبارت‌های ماژول و پروژه آن چیزی هستند که ما در اندروید استادیو به آن‌ها اشاره می‌کنیم، نه آنچه که کاربران Eclipse به آن اشاره می‌کنند.

یک. برای هر تکه کد و یا متدی که می‌نویسید مستندات کافی قرار دهید و اگر این متد نیاز به مجوز خاصی دارد مانند نمونه زیر، آن را حتما ذکر کنید:
/**
     *
     * <p>
     *   check network is available or not <br/>
     *   internet connection is not matter,for check internet connection refer to IsInternetConnected() Method in this class
     * </p>
       * <p>
     *   Required Permission : <b>android.permission.ACCESS_NETWORK_STATE</b>
     * </p>
     * @param context
     * @return returns true if a network is available
     */
    public boolean isNetworkAvailable(Context context) {

        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
همچنین اگر، مورد خاص دیگری مثل بالا بود، حتما آن را ذکر کنید. می‌توانید از تگ گذاری در کامنت ها نیز استفاده کنید. از ویژگی‌های کامنت todo در اندروید استادیو این است که میتوانید در حین کار با سیستم گیت نیز از آن بهره ببرید  و قبل از کامیت کردن کد، کدهای todo به شما یادآوری شوند و هر پیکربندی را که لازم دارید، روی آن انجام دهید.



دو. از یک کلاس واحد جهت استفاده از اطلاعات عمومی و یا ثابت‌ها استفاده نمایید. این اطلاعات می‌توانند شامل: مسیرها، آدرس‌های وب سرویس، شماره اختصاصی هر نوتیفیکیشن و .... باشند. برای اینکار میتوان هر کدام از اطلاعات را داخل یک کلاس قرار داد و همه این کلاس‌ها را به صورت استاتیک تعریف کنید تا بدین شکل در دسترس قرار بگیرند (از الگوی singleton هم می‌توان استفاده کرد).
public class ProjectSettings
{
       public static NotificationsId=new NotificationsId();
       public static UrlAddresss=new UrlAddresss();
       public static SdPath=new SdPath();
       ......
}
نحوه صدا زدن هم به همین شکل می‌شود:
ProjectSettings.NotificationsId.UpdateNotificationId
بدین شکل هم به طور ساده و مفهومی صدا زده می‌شود و هم اینکه در همه جای برنامه این ثابت‌ها و مقادیر قابل استفاده هستند. به عنوان مثال به شماره هر نوتیفیکیشن از همه جا دسترسی دارید و هم اینکه شماره‌ای تکراری اشتباها انتخاب نمی‌شود.

سه. حداکثر استفاده از اینترفیس را به خصوص برای UI انجام بدهید:
به عنوان نمونه، بسیاری نمایش یک toast را به شکل زیر انجام می‌دهند:
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
یا اینکه برای یک دیالوگ مستقیما و در جا همانجا به کدنویسی مشغول می‌شوند. این روش‌ها هیچ مشکلی ندارند ولی در آینده نگهداری کد را مشکل می‌کنند. مثلا تصور کنید شما بسیاری از جاهای برنامه، Toast زدید و حالا قصد دارید در نسخه بعدی برنامه، toast‌های دلخواه و یا custom ایی را ایجاد کنید. در این صورت مجبورید کل برنامه را رصد کرده و هر جا toast هست آن را تغییر دهید. در اینجا هم اصول DRY را نادیده گرفته‌اید و هم زحمت شما زیاد شده‌است و حتی ممکن است یک یا چندتایی از قلم بیفتند. برای دیالوگ‌ها هم بدین صورت خواهد بود و خیلی از مسائل دیگر. به همین جهت استفاده از اینترفیس‌ها توصیه می‌شود و فردا نیز اگر باز یک کلاس دیگر را نوشتید، خیلی راحت آن را با کلاس فعلی تعویض می‌کنید.
public interface IMessageUI
{
    void ShowToast(Context context,String message);
}

public class MessageUI impelement IMessageUI
{
      public void ShowToast(Context context,string message)
       {
              Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
       }
}

چهار
. اگر برای اولین بار است وارد اندروید می‌شوید، خوب چرخه‌های یک شیء، چون اکتیویتی یا فراگمنت را یاد بگیرید تا در آینده با مشکلات خاصی روبرو نشوید.
به عنوان مثال درست است که اولین رویداد فراخوانی در onCreate رخ میدهد ولی همیشه محل مناسبی برای دریافت دیتاها در زمان اولیه نیست. به عنوان مثال تصور کنید که لیستی در اکتیویتی A دارید و به اکتیویتی B می‌روید و یک آیتم به اطلاعات اضافه می‌شود و موقعی که به اکتیویتی A بر می‌گردید، زیاد تعجب نکنید که لیست دقیقا به همان شکل قبلی است و خبری از آیتم جدید نیست.
  چون اکتیویتی در حالت stop بوده و بعد از آن به حالت Resume رفته و تا موقعی که این اکتیویتی از حافظه خارج نشود یا گوشی چرخش نداشته باشد، واکشی دیتاها صورت نخواهد گرفت. پس بهترین مکان در این حالت، رویداد OnStart است که در هر دو وضعیت صدا زده می‌شود؛ یا اینکه در OnRestatr روی آداپتور تغییرات جدید را اعمال کنید تا نیازی به واکشی مجدد داده‌ها نباشد. 



به طور خلاصه نحوه اجرای رویدادها بدین شکل است که ابتدای رویداد OnCreate اجرا می‌شود که هنوز هیچ UI ئی در آن پیاده سازی نشده‌است و شما در اینجا موظفید Layout خود را معرفی کنید. رویداد OnStart بعد از آن موقعی که UI آماده شده است، اجرا می‌گردد. سپس رویداد OnResume اجرا می‌شود.

 تا بدینجا اکتیویتی مشکلی ندارد و میتواند به عملیات پاسخ دهد ولی اگر قسمتی از اکتیویتی در زیر لایه‌ای از UI پنهان شود، به عنوان مثال دیالوگی باز شود که قسمتی از اکتیویتی را بپوشاند و یا منویی همانند تلگرام قسمتی از صفحه را بپوشاند، اکتیویتی اصطلاحا در حالت Pause قرار گرفته و بدین ترتیب رویداد OnPause اجرا می‌گردد. اگر همین دیالوگ بسته شود و مجددا اکتیویتی به طور کامل نمایان گردد مجددا رویداد OnResume اجرا می‌گردد.

از رویداد Onresume میتوانید برای کارهایی که بین زمان آغاز اکتیویتی و برگشت اکتیویتی مشترکند استفاده کرد. اگر به هر نحوی اکتیویتی به طور کامل پنهان شود٬، به این معناست که شما به اکتیویتی دیگری رفته‌اید رویداد OnStop اجرا شده‌است و در صورت بازگشت، رویداد OnRestart اجرا خواهد شد. ولی اگر مدت طولانی از رویداد OnStop بگذرد احتمال اینکه سیستم مدیریت منابع اندروید، اکتیویتی شما را از حافظه خارج کند زیاد است و رویداد OnDestroy صورت خواهد گرفت. در این حالت دفعه بعد، مجددا همه عملیات از ابتدا آغاز می‌گردند.

پنج. سرویس را با تردهای UI ترکیب نکنید. بعضا دیده می‌شود که کاربران AsyncTask را داخل سرویس استفاده می‌کنند ولی این را بدانید که سرویس یک ترد پردازشی جداگانه است و تضمینی برای ارتباط با UI به شما نمی‌دهند. هر چند گوگل جدیدا تمهیداتی را برای آن اندیشیده است که به شما اجازه اینکار را نمی‌دهد. ولی اگر باز هم اندروید استادیو به شما خورده‌ای نگرفت، خودتان این قانون را اجرا کنید. قرار نیست یک AsyncTask با سرویس ترکیب شود.

شش. اگر برنامه شما قرار است در چندین حالت مختلفی که اتفاق می‌افتد، یک کار خاصی را انجام دهد، برای برنامه‌تان یک Receiver بنویسید و در آن کدهای تکراری را نوشته و در محل‌های مختلف وقوع آن رویدادها، رسیور را صدا بزنید. برای نمونه برنامه تلگرام یک سرویس پیام رسان پشت صحنه دارد که در دو رویداد قرار است اجرا شوند. یکی موقعی که گوشی بوت خود را تکمیل کرده است و در حال آغاز فرایندهای سیستم عامل است و دیگر زمانی است که برنامه اجرا می‌شود. در اینجا تلگرام از یک رسیور سیستمی برای آگاهی از بوت شدن و یک رسیور داخل برنامه جهت آگاهی از اجرای برنامه استفاده میکند و هر دو به یک کلاس از جنس BroadcastReceiver متصلند:
      <receiver android:name=".AppStartReceiver" android:enabled="true">
            <intent-filter>
                <action android:name="org.telegram.start" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

public class AppStartReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        AndroidUtilities.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                ApplicationLoader.startPushService();
            }
        });
    }
}
برای نام رسیورهای داخلی هم میتوانید مورد شماره 2 را اجرا کنید.
برنامه تلگرام حتی برای حالت‌های پخش هم رسیورها استفاده کرده است که در همین رسیور وضعیت تغییر پلیر مشخص می‌شود:
    <receiver android:name=".MusicPlayerReceiver" >
            <intent-filter>
                <action android:name="org.telegram.android.musicplayer.close" />
                <action android:name="org.telegram.android.musicplayer.pause" />
                <action android:name="org.telegram.android.musicplayer.next" />
                <action android:name="org.telegram.android.musicplayer.play" />
                <action android:name="org.telegram.android.musicplayer.previous" />
                <action android:name="android.intent.action.MEDIA_BUTTON" />
                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
            </intent-filter>
        </receiver>
اینگونه تلگرام میتواند از همه جا سرویس را کنترل کند. مثلا موقعی که دانلود یک موزیک تمام شده، سریعا پخش آن موزیک دانلود شده را آغاز کند.

هفت. اگر از یک ORM برای لایه داده‌ها استفاده می‌کنید (قبلا در سایت جاری در مورد ORM‌های اندروید صحبت کرده‌ایم و  ORM‌های خوش دستی که خودم از آن‌ها استفاده میکنم ActiveAndroid و CPORM هستند که هم کار کردن با آنها راحت است و هم اینکه امکانات خوبی را عرضه می‌کنند) در این نوع ORM‌ها شما نباید انتظار چیزی مانند EF را داشته باشید و در بعضی موارد باید کمی خودتان کمک کنید. به عنوان مثال در Active Android برای ایجاد یک inner join باید به شکل زیر بنویسید:
     From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on("poems.id=bank_poems_groups.poem")
               .where("BankGroup=?", String.valueOf(groupId));
        return query.execute();
همانطور که می‌بینید بخش‌هایی از آن مثل جوین‌ها و شرط‌ها را باید خودتان تکمیل کنید. از آنجا که ممکن است در آینده نام فیلد تغییر کند یا اینکه در حین انبوهی از کدها، عبارت رشته‌ای را اشتباه وارد کنید، بهتر است به این فرم کار کنید:
@Table(name="poems")
public class Poem extends Model {

    public static String tableName="poems";
    public static  String codeColumn="code";
    public static  String titleColumn="title";
    public static  String bookColumn="book";
    ......

    @Column(name="code",index = true)
    public int Code;

    @Column(name="title")
    public String Title;

    @Column(name="book")
    public Book Book;
.....}
در مدل بالا، نام فیلدها و جداول به صورت استاتیک تعریف شده‌اند. حالا می‌توانیم از این اسامی به راحتی در لایه سرویس استفاده کنیم:
    From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on(Poem.TableName+"."+ Poem.IdColumn+"="+ BankPoemsGroups.TableName+"."+ BankPoemsGroups.PoemColumn)
               .where(Poem.BankGroupColumn+"=?", String.valueOf(groupId));
        return query.execute();
حالا کمی بهتر شد. هم برای تغییر آینده بهتر شد و هم اینکه احتمال خطای تایپی کاهش یافت. ولی باز هم ایجاد کوئری هنوز سخت است و نوشتن مرتب یک رابطه جوین و شرطی و چسباندن مداوم رشته‌ها کار خسته کننده‌ای است و احتمال خطای سهوی و انسانی هم در آن بالاست. برای رفع این مشکل بهتر است یک کلاس جدید برای ساخت این کوئری‌ها داشته باشیم که یک نمونه از آن را در این پایین می‌بینید:
public class QueryConcater {
  public String GetInnerJoinQuery(String table1,String field1,String table2,String field2)
    {
        String query=table1 +"." +field1+"="+table2+"."+field2;
        return query;
    }
......
}
در ادامه برای مرتب سازی و شرط و ... هم می‌نویسیم:
   return new Select()
                .from(Color.class)
                .innerJoin(ProductItem.class)
                .on(queryConcater.GetInnerJoinQuery(ProductItem.TableName,
                        ProductItem.ColorColumn, Color.TableName))
                .where(queryConcater.WhereConditionQuery
                        (ProductItem.TableName, ProductItem.ProductColumn), productId)
                .execute();
در دستورات بالا از این کلاس دو متد برای کوئری جوین و یکی هم برای ساخت شرط ایجاد شده است و مقادیر به صورت پارامتر داده شده‌اند. این الگو کمک میکند که اگر هم این تکه کد اشتباه باشد، با تغییر یکجا بقیه کدها هم تغییر میکنند و اگر در آینده هم ORM تغییر یافت، نحوه کوئری نویسی‌ها در این کلاس تغییر کنند، نه اینکه در طول لایه سرویس پراکنده باشند.

هشت. سعی کنید همیشه از یک سیستم گزارش خطا در اپلیکیشن خود استفاده کنید. در حال حاضر معروفترین سیستم گزارش خطا Acra است که می‌توانید backend آن را هم از اینجا تهیه کنید و اگر هم نخواستید، سایت Tracepot امکانات خوبی را به رایگان برای شما فراهم می‌کند. از این پس با سیستم آکرا شما به یک سیستم گزارش خطا متصلید که خطاهای برنامه شما در گوشی کاربر به شما گزارش داده خواهد شد. این گزارش‌ها شامل:
  • وضعیت گوشی در حین باز شدن برنامه و در حین خطا چگونه بوده است.
  • مشخصات گوشی
  • این خطا به چه تعداد رخ داده است و برای چه تعداد کاربر
  • گزارش گیری بر اساس اولین تاریخ رخداد خطا و آخرین تاریخ، نسخه سیستم عامل اندروید، ورژن برنامه شما و...
و امکانات دیگر.

نه. آکرا همانند Elmah نمی‌تواند خطاهای catch شده را دریافت کند. برای حل این مشکل عبارت زیر را در catch‌ها بنویسید:
ACRA.getErrorReporter().handleException(caughtException);

ده. بر خلاف سیستم دات نت که شما اجباری به استفاده از Try Catch‌ها ندارید. در جاوا اینگونه نیست و هر متدی که Throw روی آن انجام شده باشد مستلزم  استفاده از catch است. به همین دلیل در شماره نه گفتیم که چگونه باید این مشکل را حل کنیم. ولی در بسیاری از اوقات پیش می‌آید که ما داریم از ماژول‌های متفاوتی استفاده میکنیم که جدا از ماژول اصلی برنامه هستند و این مورد باعث می‌شود که بعضی افراد یا Acra را در همه ماژول‌ها صدا بزنند یا اینکه بی خیال آن شوند. ولی کار راحت‌تر این است که شما هم همانند برنامه نویسان جاوا متد خود را به Throw مزین کنید تا در هنگام استفاده از آن در برنامه اصلی نیاز به catch شدن باشد. در واقع شما نباید catch‌ها را داخل یک کتابخانه جدا و مستقل قرار دهید و روش صحیح هم همین است حالا چه استفاده از آکرا نیاز باشد و چه نباشد.

نمونه اشتباه:
 public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) {
        try {
        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
نمونه صحیح:
    public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) throws IOException {

        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
    }
  ادامه دارد...