ممنونم؛ می‌توانید پیشنهاد خود را در این قسمت نیز به عنوان یک بک لاگ ثبت کنید.
استفاده از لایه سرویس باعث نقض قانون encapsulation نمی‌شود، این مشکل را Anemic Domain Model ایجاد می‌کند که بهتر است با توجه به Domain مورد نظر با Rich Domain Model جایگزین کرد
نکته دیگر اینکه لایه سرویس مدخل ورودی سیستم می‌باشد؛ DTO میگیرد و DTO بازگشت خواهد داد و نقش آن جداسازی Domain از Presentation، آماده سازی داده به فرمت مورد نیاز Presentation و همچنین محول کردن کارهای رسیده به Business Logic می‌باشد.
مقاله مرتبط با Either هم مفید بود.
‫۶ سال و ۱ ماه قبل، چهارشنبه ۳ مرداد ۱۳۹۷، ساعت ۲۲:۱۰
با تشکر از شما
لزوما با پیاده سازی ارائه شده در مطلب جاری، از شر بررسی Null بودن یا نبودن خلاص نشده ایم (از دید استفاده کننده) چرا که خروجی متد همچنان می‌تواند Nullable باشد (کلاس Option یک نوع ارجاعی می‌باشد). چرا که استفاده کننده از آن لازم است برروی خروجی خود متد که یک وهله از Option می‌باشد بررسی Null بودن یا عدم آن را انجام دهد. برای رهایی از این موضوع استفاده از struct راه حل معقولی می‌باشد؛ یک پیاده سازی از آن به صورت زیر می‌باشد:
    public struct Maybe<T> : IEquatable<Maybe<T>>
        where T : class
    {
        private readonly T _value;

        private Maybe(T value)
        {
            _value = value;
        }

        public bool HasValue => _value != null;
        public T Value => _value ?? throw new InvalidOperationException();
        public static Maybe<T> None => new Maybe<T>();


        public static implicit operator Maybe<T>(T value)
        {
            return new Maybe<T>(value);
        }

        public static bool operator ==(Maybe<T> maybe, T value)
        {
            return maybe.HasValue && maybe.Value.Equals(value);
        }

        public static bool operator !=(Maybe<T> maybe, T value)
        {
            return !(maybe == value);
        }

        public static bool operator ==(Maybe<T> left, Maybe<T> right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Maybe<T> left, Maybe<T> right)
        {
            return !(left == right);
        }

        /// <inheritdoc />
        /// <summary>
        ///     Avoid boxing and Give type safety
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(Maybe<T> other)
        {
            if (!HasValue && !other.HasValue)
                return true;

            if (!HasValue || !other.HasValue)
                return false;

            return _value.Equals(other.Value);
        }

        /// <summary>
        ///     Avoid reflection
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj is T typed)
            {
                obj = new Maybe<T>(typed);
            }

            if (!(obj is Maybe<T> other)) return false;

            return Equals(other);
        }

        /// <summary>
        ///     Good practice when overriding Equals method.
        ///     If x.Equals(y) then we must have x.GetHashCode()==y.GetHashCode()
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return HasValue ? _value.GetHashCode() : 0;
        }

        public override string ToString()
        {
            return HasValue ? _value.ToString() : "NO VALUE";
        }
    }

 این بار می‌توان به امضای متد مذکور اعتماد کرد که قطعا خروجی null ارائه نخواهد داد؛ مگر اینکه به صورت صریح مشخص شود.
نکته: پیاده سازی صحیحی از واسط IEquatable برای Value Typeها در پیاده سازی struct بالا در نظر گرفته شده است.
استفاده از آن
public virtual async Task<Maybe<TModel>> GetByIdAsync(long id)
{
    Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

    var entity = await UnTrackedEntitySet.Where(a => a.Id == id)
        .ProjectTo<TModel>(_mapper.ConfigurationProvider).SingleOrDefaultAsync();

    return entity;
}
ساختار داده Maybe تعریف شده در بالا شبیه است با ساختار داده Nullable با این تفاوت که برای انواع ارجاعی مورد استفاده می‌باشد.
Maybe<T> = Nullable<T>

‫۶ سال و ۱ ماه قبل، سه‌شنبه ۲ مرداد ۱۳۹۷، ساعت ۲۱:۱۱
public class Result
{
    public bool Success { get; private set; }
    public string Error { get; private set; }
    public bool Failure { /* … */ }
    protected Result(bool success, string error) { /* … */ }
    public static Result Fail(string message) { /* … */ }
    public static Result<T> Ok<T>(T value) {  /* … */ }
}

public class Result<T> : Result
{
    public T Value { get; set; }
    protected internal Result(T value, bool success, string error)
        : base(success, error)
    {
        /* … */
    }
}

‫۶ سال و ۲ ماه قبل، دوشنبه ۱ مرداد ۱۳۹۷، ساعت ۱۶:۲۲
یک راه حل جایگزین برای ارسال پیغام‌های خطاهای حاصل از اجرای قواعد تجاری خاص در لایه‌های داخلی

در مطلب طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها راه حلی برای خودکار سازی فرآیند اعتبارسنجی‌های خصوصیات مرتبط با DTO ها، ارائه شد. در برخی از سناریوها در میان منطق تجاری نیاز است یکسری قواعد هم بررسی شوند و داده مورد نیاز این بررسی در دل پیاده سازی متد مورد نظر وجود دارد و نیازی نیست تا برای بررسی این نوع قواعد، این داده را در Validator مرتبط با DTO نیز واکشی کنیم. در این مواقع یک راه حل همان صدور استثناء می‌باشد البته با یک trade off مناسب؛ راه حل بعدی آن همانطور که در مطلب جاری هم به آن اشاره شد، بحث بازگشت OperationResult می‌باشد. یک راه حل دیگر آن نیز به شکل زیر می‌باشد:
واسط IValidationDictionary برای کپسوله کردن امکانات ModelState
public interface IValidationDictionary
{
    void AddError(string key, string message);
    bool IsValid { get; }
}
کلاس ModelStateWrapper 
public class ModelStateWrapper : IValidationDictionary
{
    private ModelStateDictionary _modelState;

    public ModelStateWrapper(ModelStateDictionary modelState)
    {
        _modelState = modelState;
    }

    public void AddError(string key, string errorMessage)
    {
        _modelState.AddModelError(key, errorMessage);
    }

    public bool IsValid
    {
        get { return _modelState.IsValid; }
    }
}
واسط IApplicationService
public interface IApplicationService
{
    void Initialize(IValidationDictionary validationDictionary);
}
واسط مرتبط با سرویس موجودیت User
public interface IUserService : IApplicationService
{
    Task CreateAsync(UserCreateModel model);
}

پیاده ساز واسط IUserService
public class UserService : IUserService
{
    private readonly IUnitOfWork _uow;
    private IValidationDictionary _validationDictionary;

    public UserService(IUnitOfWork uow)
    {
        _uow = uow ?? throw new ArgumentNullException(nameof(uow));
    }

    public void Initialize(IValidationDictionary validationDictionary)
    {
        _validationDictionary = validationDictionary ?? throw new ArgumentNullException(nameof(validationDictionary));
    }

    public Task CreateAsync(UserCreateModel model)
    {
        //todo: logic for create new user

        if (condition)
            _validationDictionary.AddError(string.Empty, "پیغامی که قرار است برای کاربرنهایی قابل مشاهده باشد");

        if (other condition)
            _validationDictionary.AddError(string.Empty, "پیغامی که قرار است برای کاربرنهایی قابل مشاهده باشد");
    }
}

تزریق واسط سرویس کاربران و استفاده از آن
public class UsersController : Controller
{
    private readonly IUserService _service;

    public UsersController(IUserService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
        _service.Initialize(new ModelStateWrapper(ModelState));
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromForm]UserCreateModel model)
    {
        if (!ModelState.IsValid) return View(model);

        await _service.CreateAsync(model);

        //todo: Display ModelState's Errors
    }
}

 نکته اضافی تکه کد بالا، فراخوانی متد Initialize مرتبط با ApplicationService مورد نظر می‌باشد برای ارسال پیاده سازی از IValidationDictionary به آن.
این بار بعد از اجرای متد CreateAsync اگر خطایی اعتبارسنجی حاصل از اجرای قواعد تجاری موجود باشد، می‌توان از طریق ModelState به آن رسید.
‫۶ سال و ۲ ماه قبل، پنجشنبه ۲۸ تیر ۱۳۹۷، ساعت ۱۴:۴۵
Application Service‌ها هر آنچه برای انجام یک Use-case نیاز است را در دل خود دارند به عنوان مثال:
        [HttpPost, AjaxOnly, ValidateAntiForgeryToken, ValidateModelState]
        public virtual async Task<ActionResult> Create([Bind(Prefix = "Model")]MeetingCreateModel model)
        {
            await _service.CreateAsync(model).ConfigureAwait(false);

            return InformationNotification("Messages.Save.Success");
        }
صحبت شما کاملا درست است ولی زمانی که به عنوان مثال بخشی از منطق تجاری اصلی در بالاترین لایه هم قرار می‌گرفت.
فرض کنید، برای ثبت یک جلسه نیاز است این اعتبارسنجی انجام شود که مدعوین انتخاب شده از حیث جایگاه خود در ساختار سازمانی امکان انتخاب شدن به عنوان مدعو توسط کاربر جاری را داشتند یا خیر؛ مکان این اعتبارسنجی قطعا لایه سرویس می‌باشد. چه بسا در این اعتبارسنجی کوئری‌های مختلفی اجرا شود که خروجی Null هم داشته باشند و مشکلی هم ندارد و من هم روی این خروجی‌ها بررسی شرطی انجام می‌دهم ولی اینها همه در دل متد CreateAsync قرار دارند یا برای آنها Validator مناسب در نظر گرفته شده است. صحبت این است که این متد اگر خود یک استثنا صادر نکند، باید یک OperationResult بازگشت دهد و نهایتا این خطاها به ModelState اضافه شوند برای نمایش به کاربر نهایی. من استثنا صادر می‌کنم و آن را گرفته و هم می‌توانم به خطاهای ModelState اضافه کنم یا به عنوان پاسخ یک درخواست Ajax ای استفاده کنم.
Action‌های لایه Presentation مورد نظر من هیچ کاری انجام نمی‌دهند؛ برای مثال اگر قرار است یکسری برچسب به یک مطلب اضافه شود این قضیه که کدام یک قبلا بودن یا جدید هستند و از این قبیل کارها در دل متد مثلا Create مرتبط با مطلب یا ویرایش آنها صورت می‌گیرد و در واقع در پیاده سازی آنها همان بررسی Null بودن یا این چنین بررسی‌ها انجام خواهد گرفت قطعا.
صحبت نهایی اینکه Application Service‌های مورد نظر من هر آنچه نیاز است را برای انجام یک use-case انجام خواهند داد و واقعا به عنوان مدخل ورودی سیستم عمل می‌کنند. حال مدخل ورودی سیستم مورد نظر شما شاید همان بالاترین لایه یا خارجی‌ترین آن باشد که در آن صورت قضیه متفاوت می‌باشد.
‫۶ سال و ۲ ماه قبل، چهارشنبه ۲۷ تیر ۱۳۹۷، ساعت ۱۸:۳۷
- به عنوان مثال در بسیاری از متدهای لایه سرویس نیاز به چنین بررسی می‌باشد و ابتدای متدها این قضیه ابتدا چک می‌شود. این متد به عنوان یک متد Domain Service صرفا در لایه سرویس استفاده می‌شود. 
        [Transactional]
        public async Task CreateAsync(OrganizationalUnitCreateModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            if (model.ParentId.HasValue)
                await _manager.CheckIsDeactiveAsync(model.ParentId.Value).ConfigureAwait(false);
            //...

        }

در پاسخ به ادامه سوال یک باید عرض کنم بله وقتی کاربری یافت نشد چه کاری می‌توانیم انجام دهیم؟ وظیفه متد مورد نظر بازگشت دادن یک کاربر بود نه مقدار Null و در اینجا صرفا یک Exception سفارشی صادر شده تا در بالاترین لایه مدیریت شود و پیغامی مناسب به کاربر نشان داده شود یا ...
توجه شما را جلب می‌کنم به مطلب :نکات کار با استثناءها در دات نت
به این چنین کدهایی معمولاً The null cancer گفته می‌شود (سرطان نال!) زیرا اجازه داده‌ایم متد، خروجی null را بازگشت دهد. 
- خیلی موافق نیستم با صحبت شما؛ من از استثنا‌ها برای ارسال پیغامی از داخلی‌ترین لایه به خارجی‌ترین لایه استفاده می‌کنم بجای بازگشت خروجی مثل OperationResult به لایه‌های بالاتر به صورت زنجیره ای. رکورد کاربری در دیتبایس موجود نیست را شما پیش بینی می‌کنید؟ من در متن مطلب هم اشاره کردم به عنوان ابزاری برای هدف خاصی استفاده می‌کنم از استثنا ها.
- بله درست است. ولی این الگوهایی که نام بردید در یک پروژه بزرگ تعداد خط کد را خیلی بالا خواهد برد. 
این روش برای مدیریت این چنین کارها به مراتب خیلی ساده بوده و بحث fail-fast را دنبال می‌کند.
‫۶ سال و ۲ ماه قبل، سه‌شنبه ۲۶ تیر ۱۳۹۷، ساعت ۱۶:۱۷
با تشکر؛ 
آیا الزامی برای این نوع کپسوله سازی وجود دارد؟ در واقع با داشتن انتزاعی برروی کتابخانه Microsoft.Extensions.Caching.Abstraction به این شکل، تنها با یک واسط ICacheService چه چیز اضافی را بدست خواهیم آورد؟ حال اگر تعویض پذیر بودن مد نظر می‌باشد، در صورت نیاز به دو نسخه توزیع شده و درون حافظه ای به صورت یکجا، داشتن یک واسط جوابگو نخواهد بود مگر با استفاده از الگوی Abstract Factory  یا امکانات پیشرفته IoC Container‌های ثالث برای تعیین این مورد که برای یک واسط یکتا، کدام نوع کش را وهله سازی کند. 
آیا داشتن دو واسط متمایز به عنوان مثال ICacheService و IDistributedCacheService هم کار اضافی نمی‌باشد؟ 
با توجه به اینکه در توضیحات مخزن گیت این مجموعه اشاره شده است به کپسوله سازی امکانات IMemoryCache، که در این صورت  برای استفاده از کتابخانه Microsoft.Extensions.Logging هم نیاز به انتزاعی جدید خواهد بود و همچنین سایر کتابخانه ها. یا اگر نکته مربوط به متد GetOrAdd و داشتن یکسری متد جدید مدنظر بود؛ می‌توان با یکسری متد الحاقی هم به این اهداف رسید.
‫۶ سال و ۲ ماه قبل، شنبه ۲۳ تیر ۱۳۹۷، ساعت ۰۱:۵۲
یک مثال از ExpandoObject
public static class ExpandoXml
{
    public static dynamic AsExpando(this XDocument document)
    {
        return CreateExpando(document.Root);
    }

    private static dynamic CreateExpando(XElement element)
    {
        var result = new ExpandoObject() as IDictionary<string, object>;

        if (element.Elements().Any(e => e.HasElements))
        {
            var list = new List<ExpandoObject>();
            result.Add(element.Name.ToString(), list);
            foreach (var childElement in element.Elements())
            {
                list.Add(CreateExpando(childElement));
            }
        }
        else
        {
            foreach (var leafElement in element.Elements())
            {
                result.Add(leafElement.Name.ToString(), leafElement.Value);
            }
        }

        return result;
    }
}

طریقه استفاده
class Program
{
    static void Main(string[] args)
    {
        var doc1 = XDocument.Load("Employees.xml");
        foreach (var element in doc1.Element("Employees").Elements("Employee"))
        {
            Console.WriteLine(element.Element("FirstName").Value);
        }

        var doc2 = XDocument.Load("Employees.xml").AsExpando();
        foreach (var employee in doc2.Employees)
        {
            Console.WriteLine(employee.FirstName);
        }
    }
}
همانطور که در مقاله ذکر شد، برای حالت پیشرفته‌تر آن می‌توان با استفاده از DynamicObject یک DynamicXml را تهیه کرد.
‫۶ سال و ۴ ماه قبل، شنبه ۲۹ اردیبهشت ۱۳۹۷، ساعت ۱۳:۲۷
نکته تکمیلی
در پروژه خود از الگوی Container Per Request استفاده می‌کنید؟ برای نزدیکتر کردن شرایط تست به شرایط محیط عملیاتی می‌توان به شکل زیر عمل کرد:
کلاسی برای ایجاد و تخریب Nested Container 
    public static class TestDependencyScope
    {
        private static IContainer _currentNestedContainer;

        public static void Begin()
        {
            if (_currentNestedContainer != null)
                throw new Exception("Cannot begin test dependency scope. Another dependency scope is still in effect.");

            _currentNestedContainer = IoC.Container.GetNestedContainer();
        }

        public static IContainer CurrentNestedContainer
        {
            get
            {
                if (_currentNestedContainer == null)
                    throw new Exception($"Cannot access the {nameof(CurrentNestedContainer)}. There is no dependency scope in effect.");

                return _currentNestedContainer;
            }
        }

        public static void End()
        {
            if (_currentNestedContainer == null)
                throw new Exception("Cannot end test dependency scope. There is no dependency scope in effect.");

            _currentNestedContainer.Dispose();
            _currentNestedContainer = null;
        }
    }

سپس به مانند  AutoRollbackAttrbiute مذکور در مطلب جاری، ContainerPerTestCaseAttribute را برای مدیریت این قضیه در نظر می‌گیریم:
 public class ContainerPerTestCaseAttribute : Attribute, ITestAction
    {   
        public void BeforeTest(ITest test)
        {
            TestDependencyScope.Begin();
        }

        public void AfterTest(ITest test)
        {
            TestDependencyScope.End();
        }

        public ActionTargets Targets => ActionTargets.Test;
    }
و حال نحوه استفاده از آن:
    [PopulateHttpContext]
    [ContainerPerTestCase]
    [Transactional]
    [TestFixture]
    public class IntegratedTestBase
    {
        [SetUp]
        public void EachTestSetUp()
        {
            BeforeEachTest();
        }
        [TearDown]
        public void EachTestTearDown()
        {
            AfterEachTest();
        }
        protected virtual void BeforeEachTest()
        {
        }
        protected virtual void AfterEachTest()
        {
        }

        protected void UsingUnitOfWork(Action<IUnitOfWork> action)
        {
            IoC.Container.Using((IUnitOfWork uow)=>
            {
                uow.DisableAllFilters();
                action(uow);
            });
        }

        protected T UsingUnitOfWork<T>(Func<IUnitOfWork, T> func)
        {
            var uow = IoC.Resolve<IUnitOfWork>();

            uow.DisableAllFilters();

            using (uow)
            {
                var result = func(uow);

                uow.SaveChanges();

                return result;
            }
        }
        protected async Task<T> UsingUnitOfWorkAsync<T>(Func<IUnitOfWork, Task<T>> func)
        {
            var uow = IoC.Resolve<IUnitOfWork>();

            uow.DisableAllFilters();

            using (uow)
            {
                var result = await func(uow).ConfigureAwait(false);

                await uow.SaveChangesAsync().ConfigureAwait(false);

                return result;
            }

        }
    }
و برای دسترسی به Nested Container جاری می‌توان به شکل زیر عمل کرد:
namespace ProjectName.ServiceLayer.IntegrationTests
{
    public static class Testing
    {
        private static IContainer Container => TestDependencyScope.CurrentNestedContainer;

        public static T Resolve<T>()
        {
            return Container.GetInstance<T>();
        }

        public static object Resolve(Type type)
        {
            return Container.GetInstance(type);
        }

        public static void Inject<T>(T instance) where T : class
        {
            Container.Inject(instance);
        }
    }
}

//in test classes
using static ProjectName.ServiceLayer.IntegrationTests.Testing;
namespace ProjectName.ServiceLayer.IntegrationTests
{
    public class RoleServiceTests : IntegratedTestBase
    {
        private IRoleService _service;
        protected override void BeforeEachTest()
        {
            _service = Resolve<IRoleService>();
        }
     }
}

‫۶ سال و ۵ ماه قبل، سه‌شنبه ۲۸ فروردین ۱۳۹۷، ساعت ۰۵:۳۹
نکته تکمیلی
به صورت پیش فرض امکان مطلع شدن از خطاهای اعتبارسنجی درtabهای دیگر به غیر از tab جاری، ممکن نمی‌باشد؛ برای حل این مسئله می‌توان به شکل زیر عمل کرد:
 if ($(".tab-content").find("div.tab-pane.active:has(div.has-error)").length === 0) {
            $(".tab-content").find("div.tab-pane:hidden:has(div.has-error)").each(function (index, tab) {
                var id = $(tab).attr("id");
                $('a[href="#' + id + '"]').tab('show');
            });
        }
در تکه کد بالا ابتدا بررسی می‌شود در tab جاری هیچ خطای اعتبارسنجی وجود نداشته باشد؛ سپس به صورت خودکار tab ای که دارای خطای اعتبارسنجی می‌باشد، فعال خواهد شد.
اگر از unobtrusive validation استفاده نمی‌کنید، می‌توانید به شکل زیر عمل کنید:
$.validator.setDefaults({
    //برای رفع مشکل عدم اعتبارسنجی المان‌های مخفی مانند تب‌های بسته
    ignore: [],
    invalidHandler: function (event, validator) {
        if ($(".tab-content").find("div.tab-pane.active:has(div.has-error)").length === 0) {
            $(".tab-content").find("div.tab-pane:hidden:has(div.has-error)").each(function (index, tab) {
                var id = $(tab).attr("id");
                $('a[href="#' + id + '"]').tab('show');
            });
        }
    }
});
invalidHandler زمانی که فرم دارای خطاهای اعتبارسنجی می‌باشد و سعی داریم آن را به سمت سرور ارسال کنیم (این اتفاق رخ نمی‌دهد)، فراخوانی می‌شود.
در غیر این صورت لازم است، مشترک رویداد «invalid-form.validate» شوید؛ به شکل زیر:
$("form").unbind("invalid-form.validate"); // unbind default MS handler
$("form").bind("invalid-form.validate", function(event , validator) {
    if ($(".tab-content").find("div.tab-pane.active:has(div.has-error)").length === 0) {
        $(".tab-content").find("div.tab-pane:hidden:has(div.has-error)").each(function (index, tab) {
            var id = $(tab).attr("id");
            $('a[href="#' + id + '"]').tab('show');
        });
    }
});