مطالب
C# 7.1 - Tuple Name Inference
در مطلب «C# 7 - Tuple return types and deconstruction» با نوع‌های جدید بازگشتی Tuple در C# 7.0 آشنا شدیم. در C# 7.1 تشخیص نام اعضای Tuple تعریف شده بهبود یافته و از این لحاظ شبیه به anonymous types شده‌است. مفهوم «Name Inference» یا «حدس زدن نام‌ها» را با یک مثال بهتر می‌توان توضیح داد.
string name = "User 1";
int age = 20;
var personTuple = (name, age);
Console.WriteLine(personTuple.Item1); // User 1
Console.WriteLine(personTuple.Item2); // 20
در C# 7.0 مفهوم «Name Inference» پیاده سازی نشده‌است. به همین جهت کامپایلر قادر نیست نام اعضای Tuple تعریف شده‌ی فوق را حدس بزند و برای دسترسی به آن‌ها باید تنها از Item1 و Item2 مانند قبل استفاده کرد. البته اگر برای اعضای Tuple نام تعریف کنیم (قسمت «مفهوم Tuple Literals»)، آنگاه می‌توان Item1 و Item2 را با نام‌های این اعضاء جایگزین کرد:
string name = "User 1";
int age = 20;
var personTuple = (name: name, age:age);
Console.WriteLine(personTuple.name); // User 1
Console.WriteLine(personTuple.age); // 20
بنابراین ذکر نام صریح اعضای Tuple در سی‌شارپ 7 الزامی است؛ در غیراینصورت باید با همان نام‌های عمومی Item1 و Item2 جهت دسترسی به این اعضاء، کار کرد.
این وضعیت در C# 7.1 بهبود یافته‌است و اکنون کامپایلر در صورت عدم ذکر صریح نام اعضای Tuple، قادر است این نام‌ها را دقیقا بر اساس نام متغیرها، همانند قابلیتی که در مورد anonymous types وجود دارد، تعیین کند و حدس بزند:
string name = "User 1";
int age = 20;
var personTuple = (name, age);
Console.WriteLine(personTuple.name); // User 1
Console.WriteLine(personTuple.age); // 20
این مثال، شبیه به اولین مثالی است که در مورد C# 7.0 ذکر شد. اما در C# 7.1 نیازی به ذکر Item1 و Item2 جهت دسترسی به اعضای Tuple تعریف شده نیست (هرچند هنوز هم مجاز است) و کامپایلر نام این اعضاء را از نام متغیرهای متناظر با آن‌ها حدس می‌زند.


مثال‌هایی از حدس زدن نام‌های اعضای Tuple در C# 7.1

مثال اول همان حدس زدن نام‌های اعضای Tuple بر اساس نام متغیرهای محلی متناظر با آن‌ها است.

مثال دوم بر اساس نام خواص یک شیء است که توسط یک نوع Tuple بازگشت داده می‌شود:
var p = new Person
{
   Name = "User 1",
   Age = 20
};
var personTuple = (p.Name, p.Age);
Console.WriteLine(personTuple.Name);
Console.WriteLine(personTuple.Age);

در اینجا عملگر .? نیز پشتیبانی می‌شود:
Person p = null;
var personTuple = (p?.Name, p?.Age);
Console.WriteLine(personTuple.Name); // null
Console.WriteLine(personTuple.Age); // null

مثال سوم همان مثال دوم است که در یک عبارت LINQ بکار رفته‌است:
var people = new List<Person>
{
   new Person {Name = "User 1", Age = 42},
   new Person {Name = "User 2", Age = 18},
   new Person {Name = "User 3", Age = 21}
};

var tuples = people
   .Select(person =>
           (
              person.Name,
              person.Age,
              NameAndAge: $"{person.Name} is {person.Age}"
           )
);
var name = tuples.First().Name;
var age = tuples.First().Age; 
var nameAndAge = tuples.First().NameAndAge;
در اینجا نوع خروجی عبارت LINQ نوشته شده، لیستی از Tupleها است. در Tuple خروجی آن، نام دو عضو اول، از نام خواص متناظر با آن‌ها حدس زده می‌شود. نام عنصر سوم به صورت صریح مشخص شده‌است.


نکته 1: حدس زدن نام‌ها در مورد مقادیر خروجی متدها رخ نمی‌دهد.

در مثال ذیل نمی‌توان به personTuple.FirstName بر اساس نام متد ذکر شده دسترسی یافت و تنها می‌توان از Item1 در مورد آن استفاده کرد؛ اما در مورد متغیر محلی age می‌توان:
int age = 42;
var personTuple = (FirstName(), age);
Console.WriteLine(personTuple.Item1);
Console.WriteLine(personTuple.age);


نکته 2: اگر نام اعضای Tuple یکی باشند، عملیات حدس زدن نام‌ها رخ نمی‌دهد.

var p1 = new Person
{
   Name = "User 1",
   Age = 20
};

var p2 = new Person
{
   Name = "User 2",
   Age = 22
};

var personTuple = (p1.Name, p2.Name);
Console.WriteLine(personTuple.Item1); // User 1
Console.WriteLine(personTuple.Item2); // User 2
در این مثال چون Tuple تشکیل شده دارای نام‌های یکسان Name است، امکان حدس زدن نام‌ها میسر نیست و در اینجا نیز باید از طریق Item1 و ... به اعضای Tuple دسترسی یافت (و یا می‌توان به هر عضو Tuple یک نام منحصربفرد را انتساب داد).
مطالب
پیاده سازی برنامه‌های چند مستاجری در ASP.NET Core

سناریویی را در نظر بگیرید که یک برنامه وب نوشته شده، قرار است به چندین مستاجر (مشتری یا tenant) خدماتی را ارائه کند. در این حالت اطلاعات هر مشتری به صورت کاملا جدا شده از دیگر مشتریان در سیستم قرار دارد و فقط به همان قسمت‌ها دسترسی دارد.

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

 در معماری Multi-Tenancy، چندین کاربر می‌توانند از یک نمونه (Single Instance) از اپلیکیشن نرم‌افزاری استفاده کنند. یعنی این نمونه روی سرور اجرا می‌شود و به چندین کاربر سرویس می‌دهد. هر کاربر را یک Tenant می‌نامیم. می‌توان به Tenantها امکان تغییر و شخصی‌سازی بخشی از اپلیکیشن را داد؛ مثلا امکان تغییر رنگ رابط کاربری و یا قوانین کسب‌وکار، اما آنها نمی‌توانند کدهای اپلیکیشن را شخصی‌سازی کنند.

بدون داشتن دانش کافی، پیاده سازی معماری multi tenant می‌تواند تبدیل یه یک چالش بزرگ شود. مخصوصا در نسخه‌ی قبلی ASP.NET که یکپارچه نبودن فریم ورک‌های مختلف می‌توانست باعث ایجاد چندین پیاده سازی مختلف در برنامه شود. موضوع وقتی پیچیده‌تر می‌شد که شما قصد داشتید در یک برنامه چندین فریم ورک مختلف مثل SignalR, MVC, Web API را مورد استفاده قرار دهید.

خوشبختانه اوضاع با وجود OWIN بهتر شده و ما در این مطلب قصد استفاده از یک تولکیت را به نام SaasKit، برای پیاده سازی این معماری در ASP.NET Core داریم. هدف از این toolkit، ساده‌تر کردن هر چه بیشتر ساخت برنامه‌های SaaS (Software as a Service) هست. با استفاده از OWIN ما قادریم که بدون در نظر گرفتن فریم ورک مورد استفاده، رفتار مورد نظر خودمان را مستقیما در یک چرخه درخواست HTTP پیاده سازی کنیم و البته به لطف طراحی خاص ASP.NET Core 1.0 و استفاده از میان افزار‌هایی مشابه OWIN در برنامه، کار ما با SaasKit باز هم راحت‌تر خواهد بود.

شروع کار 

یک پروژه ASP.NET Core جدید را ایجاد کنید و سپس ارجاعی را به فضای نام SaasKit.Multitenancy  (موجود در Nuget) بدهید. 
PM> Install-Package SaasKit.Multitenancy
بعد از اینکار ما باید به SaasKit اطلاع دهیم که چطور مستاجر‌های ما را شناسایی کند.

شناسایی مستاجر (tenant) 

اولین جنبه در معماری multi-tenant، شناسایی مستاجر بر اساس اطلاعات درخواست جاری می‌باشد که می‌تواند از hostname ، کاربر جاری یا یک HTTP header باشد.
ابتدا به تعریف کلاس مستاجر می‌پردازیم: 
    public class AppTenant
    {
        public string Name { get; set; }
        public string[] Hostnames { get; set; }
    }
سپس از طریق پیاده سازی اینترفیس ITenantResolver  و نوشتن یک tenant resolver به SaasKit اطلاع می‌دهیم که چطور مستاجر جاری را بر اساس اطلاعات درخواست جاری شناسایی کند و در صورتیکه موفق به شناسایی شود، یک وهله از نوع <TenantContext<TTenant را بازگشت خواهد داد. 
    public class AppTenantResolver : ITenantResolver<AppTenant>
    {
        IEnumerable<AppTenant> tenants = new List<AppTenant>(new[]
        {
            new AppTenant {
                Name = "Tenant 1",
                Hostnames = new[] { "localhost:6000", "localhost:6001" }
            },
            new AppTenant {
                Name = "Tenant 2",
                Hostnames = new[] { "localhost:6002" }
            }
        });
        public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
        {
            TenantContext<AppTenant> tenantContext = null;
            var tenant = tenants.FirstOrDefault(t =>
                t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
            if (tenant != null)
            {
                tenantContext = new TenantContext<AppTenant>(tenant);
            }
            return tenantContext;
        }
    }
در نظر داشته باشید که اینجا ما اطلاعات مستاجر را از روی hostname استخراج کردیم؛ اما از آنجا که شما به شیء HttpContext دسترسی کاملی دارید، می‌توانید از هر چیزی که مایل باشید استفاده کنید؛ مثل URL، اطلاعات کاربر، هدر‌های HTTP و غیره. در اینجا فعلا مشخصات مستاجر‌های خودمان را در کد نوشتیم. اما شما می‌توانید در برنامه خودتان این اطلاعات را از فایل تنظیمات برنامه و یا یک بانک اطلاعاتی دریافت کنید.
 

سیم کشی کردن 

بعد از پیاده سازی این اینترفیس نوبت به سیم کشی‌های SaasKit میرسد. من در اینجا سعی کردم که مثل الگوی برنامه‌های ASP.NET Core عمل کنم. ابتدا نیاز داریم که وابستگی‌های SaasKit را ثبت کنیم. فایل startups.cs  را باز کنید و کدهای زیر را در متد ConfigureServices اضافه نمایید: 
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMultitenancy<AppTenant, AppTenantResolver>();
    }
سپس باید میان افزار SaasKit را ثبت کنیم. کدهای زیر را به متد Configure اضافه کنید:
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // after .UseStaticFiles()
        app.UseMultitenancy<AppTenant>();
        // before .UseMvc()
    }


دریافت مستاجر جاری 

حالا هر جا که نیاز به وهله‌ای از شیء مستاجر جاری داشتید، می‌توانید به روش زیر عمل کنید: 
    public class HomeController : Controller
    {
        private AppTenant tenant;
        public HomeController(AppTenant tenant)
        {
            this.tenant = tenant;
        }
    }
به عنوان مثال قصد داریم نام مستاجر را در عنوان سایت نمایش دهیم. برای اینکار ما از قابلیت جدید MVC Core یعنی تزریق سرویس‌ها به View استفاده خواهیم کرد.  در فایل Layout.cshtml_ تکه کد زیر را به بالای صفحه اضافه کنید:
    @inject AppTenant Tenant;
این کد، AppTenant را برای ما در تمامی View‌ها از طریق شی Tenant قابل دسترس می‌کند. حالا می‌توانیم در View خود از جزییات مستاجر به شکل زیر استفاده کنیم: 
    <a asp-controller="Home" asp-action="Index">@Tenant.Name</a>


اجرای نمونه مثال 

فایل project.json را باز کنید و مقدار web را به شکل زیر مقدار دهی کنید: (در اینجا برای سایت خود 3 آدرس را نگاشت کردیم) 
    "commands": {
      "web": "Microsoft.AspNet.Server.Kestrel --server.urls=http://localhost:6000;http://localhost:6001;http://localhost:6002",
    },
سپس کنسول را در محل ریشه پروژه باز نموده و دستور زیر را اجرا کنید: 
    dotnet run
حال اگر در مرورگر خود آدرس http://localhost:6000 را وارد کنیم، مستاجر 1 را خواهیم دید:


و اگر آدرس http://localhost:6002 را وارد کنیم، مستاجر 2 را مشاهده می‌کنیم:


قابل پیکربندی کردن مستاجر ها 

از آنجائیکه نوشتن مشخصات مستاجر‌ها در کد زیاد جالب نیست، برای همین تصمیم داریم که این مشخصات را با استفاده از قابلیت‌های ASP.NET Core از فایل appsettings.json دریافت کنیم. تنظیمات مستاجر‌ها را مطابق اطلاعات زیر به این فایل اضافه کنید:

    "Multitenancy": {
      "Tenants": [
        {
          "Name": "Tenant 1",
          "Hostnames": [
            "localhost:6000",
            "localhost:6001"
          ]
        },
        {
          "Name": "Tenant 2",
          "Hostnames": [
            "localhost:6002"
          ]
        }
      ]
    }
سپس کلاسی را که بیانگر تنظیمات چند مستاجری باشد، می‌نویسیم: 
    public class MultitenancyOptions
    {
        public Collection<AppTenant> Tenants { get; set; }
    }
حالا نیاز داریم که به برنامه اعلام کنیم تا تنظیمات مورد نیاز خود را از فایل appsettings.json بخواند. کد زیر را به ConfigureServices اضافه کنید: 
    services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
سپس کدهای resolver خود را جهت دریافت اطلاعات از MultitenancyOptions مطابق زیر تغییر می‌دهیم: 
    public class AppTenantResolver : ITenantResolver<AppTenant>
    {
        private readonly IEnumerable<AppTenant> tenants;
        public AppTenantResolver(IOptions<MultitenancyOptions> options)
        {
            this.tenants = options.Value.Tenants;
        }
        public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
        {
            TenantContext<AppTenant> tenantContext = null;
            var tenant = tenants.FirstOrDefault(t => 
                t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
            if (tenant != null)
            {
                tenantContext = new TenantContext<AppTenant>(tenant);
            }
            return Task.FromResult(tenantContext);
        }
    }
برنامه را یکبار re-build کرده و اجرا کنید . 


در آخر 

اولین قدم در پیاده سازی یک معماری multi-tenant، تصمیم گیری درباره این موضوع است که شما چطور مستاجر خود را شناسایی کنید. به محض این شناسایی شما می‌توانید عملیات‌های بعدی خود را مثل تفکیک بخشی از برنامه، فیلتر کردن داده‌ای، نمایش یک view خاص برای هر مستاجر و یا بازنویسی قسمت‌های مختلف برنامه بر اساس هر مستاجر، انجام دهید.

_ سورس مثال بالا در گیت هاب قابل دریافت می‌باشد.

_ منبع: اینجا  

اشتراک‌ها
اعتبارسنجی Domain Model‎

BC scope validation implementation 

public interface ICustomerUniquenessChecker
{
    bool IsUnique(Customer customer);
}
public Customer(string email, string name, ICustomerUniquenessChecker customerUniquenessChecker)
{
    this.Email = email;
    this.Name = name;

    var isUnique = customerUniquenessChecker.IsUnique(this);
    if (!isUnique)
    {
        throw new BusinessRuleValidationException("Customer with this email already exists.");
    }

    this.AddDomainEvent(new CustomerRegisteredEvent(this));
}


اعتبارسنجی Domain Model‎
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API
یک نکته تکمیلی
حین استفاده از ControllerBase میتوانیم از یک تعداد helper برای خروجی اکشن‌ها استفاده کنیم؛ به عنوان مثال میتوانیم بعد از ساخت یک resource جدید از CreatedAtAction استفاده کنیم که به صورت خودکار خروجی 201 را برمیگرداند و همچنین هدر Location را نیز برایمان تنظیم خواهد کرد؛ 
[ApiController]
[Route("api/users")]
public class UserController : ControllerBase
{
    [HttpGet("{id:int}")]
    public ActionResult GetUserById(int id)
    {
        var user = new { Id = 1, Name = "Sirwan Afifi" };
        return Ok(user);
    }

    [HttpPost]
    public ActionResult CreateUser(CreateUserDto user)
    {
        return CreatedAtAction(nameof(GetUserById), new { user.Id }, user);
    }
}

اطلاعات بیشتر (+)

نظرات مطالب
کوئری نویسی در EF Core - قسمت هشتم - کوئری‌های بازگشتی
با سلام
همینکار و انجام دادم 
var memberHierarchyCte =
                    _dbContext.CreateLinqToDbContext().GetCte<MemberHierarchyCTE>(memberHierarchy =>
                    {
                        return
                            (
                                from navbar in _dbContext.Navbars
                                select new MemberHierarchyCTE
                                {
                                    ChildId = navbar.Id,
                                    ParentId = navbar.ParentId,
                                    Title = navbar.Title
                                }
                            )
                            .Concat
                            (
                                from nav in _dbContext.Navbars
                                from hierarchy in memberHierarchy
                                            .InnerJoin(hierarchy => nav.Id == hierarchy.ParentId)
                                select new MemberHierarchyCTE
                                {
                                    ChildId = hierarchy.ChildId,
                                    ParentId = nav.ParentId,
                                    Title = nav.Title
                                }
                            );
                    });
البته خطایی ندارم اما خروجی Result View به شکل زیره :


نظرات مطالب
روشی برای محاسبه‌ی تعداد کاربران آنلاین در ASP.NET Core
متد invoke به صورت زیر اصلاح شد:
public async Task Invoke(HttpContext context)
        {
            if (!_memoryCache.TryGetValue("OnlineUsers", out Dictionary<string,DateTime> onlineUsers))
            {
                onlineUsers = new Dictionary<string, DateTime>();
                _memoryCache.Set("OnlineUsers", onlineUsers , new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
            }
            if (context.User.Identity.IsAuthenticated)
            {
                var name = context.User.Identity.Name;
                if (name != null)
                {
                    if (onlineUsers.ContainsKey(name))
                        onlineUsers[name] = DateTime.Now;
                    else
                        onlineUsers.Add(name, DateTime.Now);
                }

            }


            await _next(context);
        }
در مورد بحث thread-safe  باید MemoryCashe را بازنویسی کرد و متد GetOrAdd را فراخوانی کرد 
نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت پنجم - پیاده سازی ورود و خروج از سیستم
- همان مسیری را که عنوان کرده (https://localhost:6001/.well-known/openid-configuration) در مرورگر باز کنید؛ باید قابل دسترسی باشد.
- اگر قابل دسترسی بود، بیشتر مشکل HTTPS هست. برای این منظور، حتما نیاز هست با قسمت «تنظیم مجوز امضای توکن‌های IDP» آشنا باشید و آن‌را پیاده سازی کرده باشید (مهم و اجباری!)
- غیرفعال کردن بررسی مجوز در Xamarin این نکات را هم به همراه دارد:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;

var options = new OidcClientOptions(); 
options.BackchannelHandler = new HttpClientHandler
{ 
  ServerCertificateCustomValidationCallback = (message, certificate, chain, sslPolicyErrors) => true
};
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API
یک نکته‌ی تکمیلی: 
 در صورتی که قصد داشته باشیم نتایج حاصل از خطاهای اعتبارسنجی را به صورت سفارشی شده  مثلا  ValidationProblemDetails   به کلاینت بازگشت دهیم می‌توان به صورت زیر عمل کرد:
services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var problemDetails = new ValidationProblemDetails(context.ModelState)
            {
                Type = "https://contoso.com/probs/modelvalidation",
                Title = "One or more model validation errors occurred.",
                Status = StatusCodes.Status400BadRequest,
                Detail = "See the errors property for details.",
                Instance = context.HttpContext.Request.Path
            };

            return new BadRequestObjectResult(problemDetails)
            {
                ContentTypes = { "application/problem+json" }
            };
        };
    });

نظرات مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت اول - تزریق وابستگی‌ها در برنامه‌های کنسول
یک نکته‌ی تکمیلی: الگویی thread-safe برای ساخت Service Provider در برنامه‌های کنسول
namespace Test
{
    public static class ConfigureServices
    {
        private static readonly Lazy<IServiceProvider> _serviceProviderBuilder =
            new Lazy<IServiceProvider>(getServiceProvider, LazyThreadSafetyMode.ExecutionAndPublication);

        /// <summary>
        /// A lazy loaded thread-safe singleton
        /// </summary>
        public static IServiceProvider Instance { get; } = _serviceProviderBuilder.Value;

        private static IServiceProvider getServiceProvider()
        {
            var services = new ServiceCollection();
            
// TODO: add other services here ... services.AddSingleton ....

            return services.BuildServiceProvider();
        }
    }
}
نظرات مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
دلیل آن مرتبط است به روشی که از آن استفاده کردید. این قابلیت برای اینکه کار کند، نیاز به بافر کردن اطلاعات دارد، در حالیکه شما در حال دانلود یک فایل از یک سایت دیگر هستید. ترکیب این‌ها با هم، برای ارائه‌ی resume کار نمی‌کنند. زمانیکه قرار است قابلیت resume وجود داشته باشد، یعنی مثلا کاربر درخواست دریافت اطلاعات را از بایت 1000 تا 1500، می‌دهد. File Stream Result چطور باید این درخواست را برای httpClient.GetStreamAsync که چنین قابلیتی را ندارد، ترجمه کند؟ اگر می‌خواهید آن‌را برای حالت resume آزمایش کنید، از استریمی از نوع System.IO.File.OpenRead و یا new FileStream استفاده کنید:
public IActionResult FileStreamActionResult()
{
  var fileStream = System.IO.File.OpenRead(@"D:\path\Controllers\HomeController.cs");
  return new FileStreamResult(fileStream, "text/plain") { FileDownloadName = "HomeController.cs" };
}