اشتراک‌ها
تغییرات ASP.NET Core در NET 7 Release Candidate 1.

.NET 7 Release Candidate 1 (RC1) is now available and includes many great new improvements to ASP.NET Core.

Here’s a summary of what’s new in this preview release:

  • Dynamic authentication requests in Blazor WebAssembly
  • Handle location changing events
  • Blazor WebAssembly debugging improvements
  • .NET WebAssembly build tools for .NET 6 projects
  • .NET JavaScript interop on WebAssembly
  • Kestrel full certificate chain improvements
  • Faster HTTP/2 uploads
  • HTTP/3 improvements
  • Experimental Kestrel support for WebTransport over HTTP/3
  • Experimental OpenAPI support for gRPC JSON transcoding
  • Rate limiting middleware improvements
  • macOS dev-certs improvements 
تغییرات ASP.NET Core در NET 7 Release Candidate 1.
اشتراک‌ها
آشنایی با FluentValidation

Webinar agenda:

0:00 – Introduction
0:55 – OSS in the .NET Community
3:00 – Welcome, Jeremy and FluentValidation
6:17 – OSS Power-Ups
8:11 – Rider plugin for FluentValidation
9:07 – Introduction to FluentValidation
13:36 – Custom validation messages and placeholders
18:33 – Cross-property validation
21:17 – Nested and compositional validation
25:57 – Validator extensions
31:40 – Unit testing validators
51:25 – ASP.NET Core integration
1:03:35 – Synchronous and asynchronous validation
1:07:33 – Q&A and wrap-up
 

آشنایی با FluentValidation
نظرات مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
مرحله
معادل آن در پروژه‌های قدیمی MVC 5
  ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017  
 یک پروژه‌ی جدید Web API را ایجاد کنید. 
  تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI  
 به این موارد نیازی نخواهید داشت. چون پروژه‌های MVC 5 برخلاف ASP.NET Core، پوشه‌هایی را که دستی به آن اضافه نکنید، به صورت خودکار به پروژه اضافه نمی‌کند. بنابراین فایل‌های Angular-CLI را به Solution Explorer وارد نکنید و بهترین روش کار کردن با آن‌ها از طریق VSCode است. در اینجا برای back-end (کار با Web API) از VS کامل استفاده کنید و برای front-end از VSCode. 
  افزودن یک کنترلر Web API جدید  
 کلیات آن با MVC 5 یکی است. 
  تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular  
 معادل قسمت URL Rewrite آن، از نکته‌ی web.config مطلب «مسیریابی در Angular - قسمت اول - معرفی»، قسمت «تفاوت بین آدرس‌های HTML 5 و Hash-based» استفاده کنید. 
  ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری
 یکی هست. 
  تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot  
 یکی هست. فقط شاید علاقمند باشید مسیر "" (پوشه‌ی ریشه) را بجای wwwroot تنظیم کنید. 
 فراخوانی کنترلر Web API برنامه در برنامه‌ی Angular CLI  
 یکی هست. 
  نصب وابستگی‌های برنامه‌ی Angular CLI  
 یکی هست. 
  روش اول اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI  
 یکی هست. 
  روش دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI  
 در اینجا نیازی به آن نیست ولی در کل مطالعه‌ی نکات آن مفید است.
 فایل‌های bat ارائه شده و یا روش NPM Task Runner در نظرات   یکی هستند. 
مطالب
معماری لایه بندی نرم افزار #4

UI

در نهایت نوبت به طراحی و کدنویسی UI می‌رسد تا بتوانیم محصولات را به کاربر نمایش دهیم. اما قبل از شروع باید موضوعی را یادآوری کنم. اگر به یاد داشته باشید، در کلاس ProductService موجود در لایه‌ی Domain، از طریق یکی از روشهای الگوی Dependency Injection به نام Constructor Injection، فیلدی از نوع IProductRepository را مقداردهی نمودیم. حال زمانی که بخواهیم نمونه ای را از ProductService ایجاد نماییم، باید به عنوان پارامتر ورودی سازنده، شی ایجاد شده از جنس کلاس ProductRepository موجود در لایه Repository را به آن ارسال نماییم. اما از آنجایی که می‌خواهیم تفکیک پذیری لایه‌ها از بین نرود و UI بسته به نیاز خود، نمونه مورد نیاز را ایجاد نموده و به این کلاس ارسال کند، از ابزارهایی برای این منظور استفاده می‌کنیم. یکی از این ابزارها StructureMap می‌باشد که یک Inversion of Control Container یا به اختصار IoC Container نامیده می‌شود. با Inversion of Control در مباحث بعدی بیشتر آشنا خواهید شد. StructureMap ابزاری است که در زمان اجرا، پارامترهای ورودی سازنده‌ی کلاسهایی را که از الگوی Dependency Injection استفاده نموده اند و قطعا پارامتر ورودی آنها از جنس یک Interface می‌باشد را، با ایجاد شی مناسب مقداردهی می‌نماید.

به منظور استفاده از StructureMap در Visual Studio 2012 باید بر روی پروژه UI خود کلیک راست نموده و گزینه‌ی Manage NuGet Packages را انتخاب نمایید. در پنجره ظاهر شده و از سمت چپ گزینه‌ی Online و سپس در کادر جستجوی سمت راست و بالای پنجره واژه‌ی structuremap را جستجو کنید. توجه داشته باشید که باید به اینترنت متصل باشید تا بتوانید Package مورد نظر را نصب نمایید. پس از پایان عمل جستجو، در کادر میانی structuremap ظاهر می‌شود که می‌توانید با انتخاب آن و فشردن کلید Install آن را بر روی پروژه نصب نمایید.

جهت آشنایی بیشتر با NuGet و نصب آن در سایر نسخه‌های Visual Studio می‌توانید به لینک‌های زیر رجوع کنید:

1. آشنایی با  NuGetقسمت اول

2. آشنایی با  NuGetقسمت دوم

3. Installing NuGet

کلاسی با نام BootStrapper را با کد زیر به پروژه UI خود اضافه کنید:

using StructureMap;
using StructureMap.Configuration.DSL;
using SoCPatterns.Layered.Repository;
using SoCPatterns.Layered.Model;

namespace SoCPatterns.Layered.WebUI
{
    public class BootStrapper
    {
        public static void ConfigureStructureMap()
        {
            ObjectFactory.Initialize(x => x.AddRegistry<ProductRegistry>());
        }
    }
    public class ProductRegistry : Registry
    {
        public ProductRegistry()
        {
            For<IProductRepository>().Use<ProductRepository>();
        }
    }
}

ممکن است یک WinUI ایجاد کرده باشید که در این صورت به جای فضای نام SoCPatterns.Layered.WebUI از SoCPatterns.Layered.WinUI استفاده نمایید.

هدف کلاس BootStrapper این است که تمامی وابستگی‌ها را توسط StructureMap در سیستم Register نماید. زمانی که کدهای کلاینت می‌خواهند به یک کلاس از طریق StructureMap دسترسی داشته باشند، Structuremap وابستگی‌های آن کلاس را تشخیص داده و بصورت خودکار پیاده سازی واقعی (Concrete Implementation) آن کلاس را، براساس همان چیزی که ما برایش تعیین کردیم، به کلاس تزریق می‌نماید. متد ConfigureStructureMap باید در همان لحظه ای که Application آغاز به کار می‌کند فراخوانی و اجرا شود. با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:

در WebUI:

فایل Global.asax را به پروژه اضافه کنید و کد آن را بصورت زیر تغییر دهید:

namespace SoCPatterns.Layered.WebUI
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            BootStrapper.ConfigureStructureMap();
        }
    }
}

در WinUI:

در فایل Program.cs کد زیر را اضافه کنید:

namespace SoCPatterns.Layered.WinUI
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            BootStrapper.ConfigureStructureMap();
            Application.Run(new Form1());
        }
    }
}

سپس برای طراحی رابط کاربری، با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:

در WebUI:

صفحه Default.aspx را باز نموده و کد زیر را به آن اضافه کنید:

<asp:DropDownList AutoPostBack="true" ID="ddlCustomerType" runat="server">
    <asp:ListItem Value="0">Standard</asp:ListItem>
    <asp:ListItem Value="1">Trade</asp:ListItem>
</asp:DropDownList>
<asp:Label ID="lblErrorMessage" runat="server" ></asp:Label>
<asp:Repeater ID="rptProducts" runat="server" >
    <HeaderTemplate>
        <table>
            <tr>
                <td>Name</td>
                <td>RRP</td>
                <td>Selling Price</td>
                <td>Discount</td>
                <td>Savings</td>
            </tr>
            <tr>
                <td colspan="5"><hr /></td>
            </tr>
    </HeaderTemplate>
    <ItemTemplate>
            <tr>
                <td><%# Eval("Name") %></td>
                <td><%# Eval("RRP")%></td>
                <td><%# Eval("SellingPrice") %></td>
                <td><%# Eval("Discount") %></td>
                <td><%# Eval("Savings") %></td>
            </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>

در WinUI:

فایل Form1.Designer.cs را باز نموده و کد آن را بصورت زیر تغییر دهید:

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
    this.cmbCustomerType = new System.Windows.Forms.ComboBox();
    this.dgvProducts = new System.Windows.Forms.DataGridView();
    this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colRrp = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colSellingPrice = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colDiscount = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.colSavings = new System.Windows.Forms.DataGridViewTextBoxColumn();
    ((System.ComponentModel.ISupportInitialize)(this.dgvProducts)).BeginInit();
    this.SuspendLayout();
    //
    // cmbCustomerType
    //
    this.cmbCustomerType.DropDownStyle =                                                                                                                                                                             System.Windows.Forms.ComboBoxStyle.DropDownList;
    this.cmbCustomerType.FormattingEnabled = true;
    this.cmbCustomerType.Items.AddRange(new object[] {
        "Standard",
        "Trade"});
    this.cmbCustomerType.Location = new System.Drawing.Point(12, 90);
    this.cmbCustomerType.Name = "cmbCustomerType";
    this.cmbCustomerType.Size = new System.Drawing.Size(121, 21);
    this.cmbCustomerType.TabIndex = 3;
    //
    // dgvProducts
    //
    this.dgvProducts.ColumnHeadersHeightSizeMode =     System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
    this.dgvProducts.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
    this.colName,
    this.colRrp,
    this.colSellingPrice,
    this.colDiscount,
    this.colSavings});
    this.dgvProducts.Location = new System.Drawing.Point(12, 117);
    this.dgvProducts.Name = "dgvProducts";
    this.dgvProducts.Size = new System.Drawing.Size(561, 206);
    this.dgvProducts.TabIndex = 2;
    //
    // colName
    //
    this.colName.DataPropertyName = "Name";
    this.colName.HeaderText = "Product Name";
    this.colName.Name = "colName";
    this.colName.ReadOnly = true;
    //
    // colRrp
    //
    this.colRrp.DataPropertyName = "Rrp";
    this.colRrp.HeaderText = "RRP";
    this.colRrp.Name = "colRrp";
    this.colRrp.ReadOnly = true;
    //
    // colSellingPrice
    //
    this.colSellingPrice.DataPropertyName = "SellingPrice";
    this.colSellingPrice.HeaderText = "Selling Price";
    this.colSellingPrice.Name = "colSellingPrice";
    this.colSellingPrice.ReadOnly = true;
    //
    // colDiscount
    //
    this.colDiscount.DataPropertyName = "Discount";
    this.colDiscount.HeaderText = "Discount";
    this.colDiscount.Name = "colDiscount";
    //
    // colSavings
    //
    this.colSavings.DataPropertyName = "Savings";
    this.colSavings.HeaderText = "Savings";
    this.colSavings.Name = "colSavings";
    this.colSavings.ReadOnly = true;
    //
    // Form1
    //
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(589, 338);
    this.Controls.Add(this.cmbCustomerType);
    this.Controls.Add(this.dgvProducts);
    this.Name = "Form1";
    this.Text = "Form1";
    ((System.ComponentModel.ISupportInitialize)(this.dgvProducts)).EndInit();
    this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ComboBox cmbCustomerType;
private System.Windows.Forms.DataGridView dgvProducts;
private System.Windows.Forms.DataGridViewTextBoxColumn colName;
private System.Windows.Forms.DataGridViewTextBoxColumn colRrp;
private System.Windows.Forms.DataGridViewTextBoxColumn colSellingPrice;
private System.Windows.Forms.DataGridViewTextBoxColumn colDiscount;
private System.Windows.Forms.DataGridViewTextBoxColumn colSavings;

سپس در Code Behind، با توجه به نوع UI خود یکی از روالهای زیر را انجام دهید:

در WebUI:

وارد کد نویسی صفحه Default.aspx شده و کد آن را بصورت زیر تغییر دهید:

using System;
using System.Collections.Generic;
using SoCPatterns.Layered.Model;
using SoCPatterns.Layered.Presentation;
using SoCPatterns.Layered.Service;
using StructureMap;

namespace SoCPatterns.Layered.WebUI
{
    public partial class Default : System.Web.UI.Page, IProductListView
    {
        private ProductListPresenter _productListPresenter;
        protected void Page_Init(object sender, EventArgs e)
        {
            _productListPresenter = new ProductListPresenter(this,ObjectFactory.GetInstance<Service.ProductService>());
            this.ddlCustomerType.SelectedIndexChanged +=
                delegate { _productListPresenter.Display(); };
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            if(!Page.IsPostBack)
                _productListPresenter.Display();
        }
        public void Display(IList<ProductViewModel> products)
        {
            rptProducts.DataSource = products;
            rptProducts.DataBind();
        }
        public CustomerType CustomerType
        {
            get { return (CustomerType) int.Parse(ddlCustomerType.SelectedValue); }
        }
        public string ErrorMessage
        {
            set
            {
                lblErrorMessage.Text =
                    String.Format("<p><strong>Error:</strong><br/>{0}</p>", value);
            }
        }
    }
}

در WinUI:

وارد کدنویسی Form1 شوید و کد آن را بصورت زیر تغییر دهید:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using SoCPatterns.Layered.Model;
using SoCPatterns.Layered.Presentation;
using SoCPatterns.Layered.Service;
using StructureMap;

namespace SoCPatterns.Layered.WinUI
{
    public partial class Form1 : Form, IProductListView
    {
        private ProductListPresenter _productListPresenter;
        public Form1()
        {
            InitializeComponent();
            _productListPresenter =
                new ProductListPresenter(this, ObjectFactory.GetInstance<Service.ProductService>());
            this.cmbCustomerType.SelectedIndexChanged +=
                delegate { _productListPresenter.Display(); };
            dgvProducts.AutoGenerateColumns = false;
            cmbCustomerType.SelectedIndex = 0;
        }
        public void Display(IList<ProductViewModel> products)
        {
            dgvProducts.DataSource = products;
        }
        public CustomerType CustomerType
        {
            get { return (CustomerType)cmbCustomerType.SelectedIndex; }
        }
        public string ErrorMessage
        {
            set
            {
                MessageBox.Show(
                    String.Format("Error:{0}{1}", Environment.NewLine, value));
            }
        }
    }
}

با توجه به کد فوق، نمونه ای را از کلاس ProductListPresenter، در لحظه‌ی نمونه سازی اولیه‌ی کلاس UI، ایجاد نمودیم. با استفاده از متد ObjectFactory.GetInstance مربوط به StructureMap، نمونه ای از کلاس ProductService ایجاد شده است و به سازنده‌ی کلاس ProductListPresenter ارسال گردیده است. در مورد Structuremap در مباحث بعدی با جزئیات بیشتری صحبت خواهم کرد. پیاده سازی معماری لایه بندی در اینجا به پایان رسید.

اما اصلا نگران نباشید، شما فقط پرواز کوتاه و مختصری را بر فراز کدهای معماری لایه بندی داشته اید که این فقط یک دید کلی را به شما در مورد این معماری داده است. این معماری هنوز جای زیادی برای کار دارد، اما در حال حاضر شما یک Applicaion با پیوند ضعیف (Loosely Coupled) بین لایه‌ها دارید که دارای قابلیت تست پذیری قوی، نگهداری و پشتیبانی آسان و تفکیک پذیری قدرتمند بین اجزای آن می‌باشد. شکل زیر تعامل بین لایه‌ها و وظایف هر یک از آنها را نمایش می‌دهد.

مطالب
کنترلرهای تو در تو در angularJs و نحوه ارث بری متدها و property ها در آن - بخش اول
در این مقاله قصد داریم تا به بررسی و پیاده سازی کنترلر‌های تو در تو در AngularJs بپردازیم. به این صورت که میتوانیم در یک صفحه یک کنترلر اصلی را در نظر بگیریم و کنترلر‌های دیگر را در این کنترلر قرار دهیم. نحوه‌ی ارث بری متغیرها، انقیاد داده‌ها و مقادیر تعریف شده در $scope، از جمله مواردی است که به آن خواهیم پرداخت. تمامی مواردی که ذکر خواهد شد در قالب یک پروژه قرار داده شده است.
حال به بررسی جزئی کنترلر‌های تودرتو یا همان Nested Controllers می‌پردازیم. در این پروژه قصد داریم تا با تعریف سه متغییر firstName و lastName و middleName نحوه ارث بری پارامترها را در این کنترلر‌های تو در تو بررسی نماییم. ساختار یک صفحه با کنترلر‌های تو در تو به صورت زیر میباشد. همانطور که مشاهده میکنید کنترلر دوم و سوم به صورت nested درون کنترلر اول تعریف شده اند.
<body ng-app ="app">
      <div ng-controller="firstControllerScope">
            <div ng-controller="secondControllerScope">
                   <div ng-controller="thirdControllerScope">
                   </div>
             </div>
    </div>
</body>
در کدهای پیاده سازی شده این کنترلر به صورت زیر میباشد. همانطور که مشاهده میکنید در کنترلر اول تنها پارامتر firstName و در کنترلر دوم و سوم نیز به ترتیب lastName و middleName اضافه شده اند. همچنین دو تابع utility به نام getFullName در کنترلر دوم و سوم تعریف شده است.
var firstControllerScope = function ($scope) {
    // Initialize the model variables
    $scope.firstName = "John";
};

var secondControllerScope = function ($scope) {
    // Initialize the model variables
    $scope.lastName = "Doe";

    // Define utility functions
    $scope.getFullName = function () {
        return $scope.firstName + " " + $scope.lastName;
    };
};

var thirdControllerScope = function ($scope) {
    // Initialize the model variables
    $scope.middleName = "Al";
    $scope.lastName = "Smith";

    // Define utility functions
    $scope.getFullName = function () {
        return $scope.firstName + " " + $scope.middleName + " " + $scope.lastName;
    };
};
فایل html حاوی این کنترلر‌ها در زیر نمایش داده شده است. همانطور که مشاهده میکنید تمامی کنترلر دوم یعنی secondControllerScope و کنترلر سوم درون firstControllerScope تعریف شده اند. همچنین دو تابع utility با نام‌های getFullName نیز در زیر مقدار fullName را باز میگردانند. 
    <div ng-controller="firstControllerScope">
        <h3>First controller</h3>
        <strong>First name:</strong> {{firstName}}<br />
        <br />
        <label>first name: <input type="text" ng-model="firstName" /></label><br />
        <br />
        

        <div ng-controller="secondControllerScope">
            <h3>Second controller (inside First)</h3>
            <strong>First name (from First):</strong> {{firstName}}<br />
            <strong>Last name (new variable):</strong> {{lastName}}<br />
            <strong>Full name:</strong> {{getFullName()}}<br />
            <br />
            <label>first name: <input type="text" ng-model="firstName" /></label><br />
            <label>last name: <input type="text" ng-model="lastName" /></label><br />
            <br />
            

            <div ng-controller="thirdControllerScope">
                <h3>Third controller (inside Second and First)</h3>
                <strong>First name (from First):</strong> {{firstName}}<br />
                <strong>Middle name (new variable):</strong> {{middleName}}<br />
                <strong>Last name (from Second):</strong> {{$parent.lastName}}<br />
                <strong>Last name (redefined in Third):</strong> {{lastName}}<br />
                <strong>Full name (redefined in Third):</strong> {{getFullName()}}<br />
                <br />
                <label>first name: <input type="text" ng-model="firstName" /></label><br />
                <label>middle name: <input type="text" ng-model="middleName" /></label><br />
                <label>last name: <input type="text" ng-model="lastName" /></label>
            </div>
        </div>
    </div>
با توجه به مقادیر تعریف شده درون هر یک از کنترلر‌ها و با اجرای پروژه نمونه مشاهده میکنیم که اگر یک پارامتر مدل (مانند middleName) را درون کنترلر فرزند به وسیله input field‌ها تغییر دهیم، این تغییر به والد منتقل نمیشود. یعنی اگر به عنوان مثال ما lastName را در کنترلر سوم تغییر دهیم دیگر lastName که در کنترلر سوم است تغییر نخواهد کرد. حال اگر همان lastName در کنترلر دوم که در یک فیلد input قرار داده شده تغییر کند، مشاهده میکنیم که در کنترلر سوم نیز مقدار lastName تغییر خواهد کرد.
در بخش بعدی نوع دیگری از ارث بری پارامتر‌ها و تابع‌ها در کنترلر‌های AngularJs را شرح خواهیم داد.
نظرات مطالب
راه اندازی StimulSoft Report در ASP.NET MVC
سلام؛ من از این روش برای پیاده سازی خودم و تهیه گزارش استفاده کردم، اما وقتی View من فراخونی میشه هیچ یکی از متدهای GetReportSnapshot و یا ViewerEvent  و یا PrintReport فراخوانی نمی‌شن و در عوض خود متد فراخواننده View گزارش فراخوانی میشه. به عبارت بهتر وقتی من متد گزارش رو به آدرس
 http://localhost:14425/tableproduct/printtable?reporttype=21
فراخوانی میکنم این آدرس یک بار فراخوانی شده و سپس View فراخوانی می‌شود و View درخواستی به شکل زیر را به سرور می‌دهد :
/tableproduct/PrintTable?reporttype=21&mvcviewer_resource=scripts&mvcviewer_id=MVCViewer1&mvcviewer_theme=Office2013&mvcviewer_loc=
fi9Db250ZW50L1N0aW11bHNvZnQvTG9jYWxpemF0aW9uL2ZhLnhtbA%3d%3d&mvcviewer_version=2015.2 
به نظر شما دلیل این مشکل چی میتونه باشه؟ نتیجه بررسی‌های خودم نشون میده احتمالا مشکل از CustomRoute من هستش. پروژه ای که من دارم گزارش رو توی اون ایجاد میکنم از ساختار Routing پروژه Iris  استفاده میکنه.
اشتراک‌ها
کتابخانه‌ی Formo برای مدیریت فایل‌های Config
The settings

<appSettings>
    <add key="RetryAttempts" value="5" />
    <add key="ApplicationBuildDate" value="11/4/1999 6:23 AM" />
</appSettings>

The code

dynamic config = new Configuration();
var retryAttempts1 = config.RetryAttempts;                 // returns 5 as a string
var retryAttempts2 = config.RetryAttempts(10);             // returns 5 if found in config, else 10
var retryAttempts3 = config.RetryAttempts(userInput, 10);  // returns 5 if it exists in config, else userInput i
کتابخانه‌ی Formo برای مدیریت فایل‌های Config
مطالب
استفاده از پیاده سازی Katana مربوط به استاندارد Owin در ASP.NET 4.x
قطعا ASP.NET MVC 5.x به عنوان یک فریم ورک بالغ و با امکانات فراوان شناخته میشود که در این مساله هیچ بحثی نیست. اما آیا در همه‌ی پروژه‌ها حتما باید از این فریم ورک استفاده شود؟ امروزه اکثر وب اپلیکیشن‌ها از فریم ورک‌های SPA استفاده میکنند و بنده به وفور در پروژه‌های مختلف شاهد این بوده‌ام که ASP.NET MVCی که در کنار آن استفاده میشود، عملا چیزی بیشتر از یک کنترلر Home و یک متد Index و حداکثر یک Layout، نیستند و معمولا در کنار آن از Web Api استفاده میکنند که حداقل در ASP.NET MVC 5.x چیزی کاملا مجزای از آن به حساب می‌آید. با این حال آیا واقعا از امکانات MVC 5.x استفاده شده است؟! یا فقط اینگونه پروژه‌ها محدودیت‌های MVC را به دوش میکشند؟
در ASP.NET 4.x به صورت معمول ارسال درخواست‌ها بدین صورت است که از سمت کلاینت به IIS و بعد از آن بر روی ISAPI نگاشت میشوند (یا Static File برای فایل‌های استاتیک). پس عملا وابستگی شدیدی به IIS ایجاد شده‌است و اینکه مشکلات این وابستگی چیست در این مقاله نمیگنجد. اگر قرار باشد همین امروز پروژه‌ای شروع شود قطعا ASP.NET 4.x گزینه‌ی معقولی به نظر میرسد؛ اما در پروژه‌های حجیم بیزینسی که باید ماه‌ها و شاید چندین سال بر روی نرم افزار آن کار شود، آیا آن موقع نیز ASP.NET مانند حال گزینه‌ی معقولی است یا بطور مثال ASP.NET Core با امکانات منحصر به فردش جایگزین خواهد شد؟ در نگاه اول وقتی دو پروژه‌ی ASP.NET 4.x و ASP.NET Core را در کنار هم میگذاریم، شاید اختلافات زیاد باشند و ارتقاء نرم افزار به ASP.NET Core سخت و یا حتی غیر ممکن به نظر برسد. اما آیا واقعا هیچ راهی وجود ندارد که هم اکنون نرم افزار خود را با ASP.NET 4.x که کاملا بالغ هم شده شروع کرده و بعد‌ها به ASP.NET Core به روز رسانی شود؟
این‌ها سوال‌هایی است که قطعا قبل از شروع یک پروژه‌ی بزرگ نرم افزاری باید از خود بپرسیم. شاید با نگاه عمیق‌تری بر روی این سوال‌ها بتوان پاسخی مناسب را برای آن‌ها داد. یکی از این راه‌حل‌ها استفاده از استاندارد Owin و پیاده سازی آن به نام Katana است.

برای شروع پروژه ابتدا یک پروژه‌ی ASP.NET 4.x از نوع empty را بسازید.
برای راحت شدن کار، ابتدا packages.config را باز کرده و کد‌های زیر را جایگزین آن نمایید:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.AspNet.OData" version="6.0.0" targetFramework="net462" />
  <package id="Microsoft.AspNet.SignalR.Core" version="2.2.1" targetFramework="net462" />
  <package id="Microsoft.AspNet.SignalR.Owin" version="1.2.2" targetFramework="net462" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net462" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net462" />
  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net462" />
  <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.2" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyInjection" version="1.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.0.0" targetFramework="net462" />
  <package id="Microsoft.Net.Compilers" version="1.3.2" targetFramework="net462" developmentDependency="true" />
  <package id="Microsoft.OData.Core" version="7.0.0" targetFramework="net462" />
  <package id="Microsoft.OData.Edm" version="7.0.0" targetFramework="net462" />
  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net462" />
  <package id="Microsoft.Spatial" version="7.0.0" targetFramework="net462" />
  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net462" />
  <package id="Owin" version="1.0" targetFramework="net462" />
  <package id="System.Collections" version="4.0.11" targetFramework="net462" />
  <package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net462" />
  <package id="System.ComponentModel" version="4.0.1" targetFramework="net462" />
  <package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="net462" />
  <package id="System.Globalization" version="4.0.11" targetFramework="net462" />
  <package id="System.Linq" version="4.1.0" targetFramework="net462" />
  <package id="System.Linq.Expressions" version="4.1.0" targetFramework="net462" />
  <package id="System.Reflection" version="4.1.0" targetFramework="net462" />
  <package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net462" />
  <package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net462" />
  <package id="System.Threading" version="4.0.11" targetFramework="net462" />
  <package id="System.Threading.Tasks" version="4.0.11" targetFramework="net462" />
</packages>
ما پکیج‌های OData و SignlarR , WebApi, Owin را اضافه نموده‌ایم و دستور زیر را برای اضافه شدن ارجاعات اجرا می‌کنیم:
PM>Update-Package -reinstall -Project YourProjectName
اکنون عملا تمام پکیج‌های لازم را برای شروع به کار، در اختیار داریم (اگر از dotNetFrameWork نسخه‌ی پایین‌تری بطور مثال 4.6.1 استفاده میکنید، بعد از اجرای دستور فوق، targetFramework شما به 461 اصلاح خواهد شد).
حال میخواهیم کمی کار اختیاری را نیز بر روی وب کانفیگ انجام دهیم که به performance نرم افزار شما بهبود قابل ملاحظه‌ای را می‌افزاید. کد‌های زیر را در وب کانفیگ جایگزین نمایید:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="owin:AppStartup" value="OwinKatanaTest.OwinAppStartup, OwinKatanaTest" />
    <!-- Owin App Startup Class -->
    <add key="webpages:Enabled" value="false" />
    <!-- Disable asp.net web pages. Note that based on our current configuration, asp.net web forms, mvc and web pages won't work. This configuration is for owin stuffs only, for example asp.net web api & odata, signalr, etc. -->
  </appSettings>
  <system.web>
    <compilation debug="true" defaultLanguage="c#" enablePrefetchOptimization="true" optimizeCompilations="true" targetFramework="4.6.2">
      <assemblies>
        <remove assembly="*" />
        <!-- To improve app startup performance, our app will continue its work without this compilations, these are required for asp.net web forms, mvc and web pages. -->
        <add assembly="OwinKatanaTest" />
      </assemblies>
    </compilation>
    <httpRuntime targetFramework="4.6.2" />
    <httpModules>
      <!-- No need to these modules and handlers, owin handler itself will do everything for us -->
      <clear />
    </httpModules>
    <httpHandlers>
      <clear />
    </httpHandlers>
    <sessionState mode="Off" />
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
    </compilers>
  </system.codedom>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.AspNet.SignalR.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.2.1.0" newVersion="2.2.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.Extensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="false">
      <!-- We're not going to remove all modules, some modules such as static & dynamic compression modules are really cool (-: -->
      <remove name="RewriteModule" />
      <remove name="OutputCache" />
      <remove name="Session" />
      <remove name="WindowsAuthentication" />
      <remove name="FormsAuthentication" />
      <remove name="DefaultAuthentication" />
      <remove name="RoleManager" />
      <remove name="FileAuthorization" />
      <remove name="UrlAuthorization" />
      <remove name="AnonymousIdentification" />
      <remove name="Profile" />
      <remove name="UrlMappingsModule" />
      <remove name="ServiceModel-4.0" />
      <remove name="UrlRoutingModule-4.0" />
      <remove name="ScriptModule-4.0" />
      <remove name="Isapi" />
      <remove name="IsapiFilter" />
      <remove name="DigestAuthentication" />
      <remove name="WindowsAuthentication" />
      <remove name="ServerSideInclude" />
      <remove name="DirectoryListing" />
      <remove name="DefaultDocument" />
      <remove name="CustomError" />
      <remove name="Cgi" />
    </modules>
    <defaultDocument>
      <!-- Default docs will be configured using owin static files middleware -->
      <files>
        <clear />
      </files>
    </defaultDocument>
    <handlers>
      <!-- Only use this handler for all requests -->
      <clear />
      <add name="Owin" verb="*" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb" />
    </handlers>
    <httpProtocol>
      <customHeaders>
        <clear />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>
1) AppSettings برای کانفیگ Owin startup خواهد بود (در ادامه‌ی مقاله آن را مینویسیم).
2) در تگ compilation اسمبلی‌های اضافی را حذف مینماییم (برای بهبود performance از آنجایی که به asp.net web form یا mvc احتیاجی نداریم).
3) حذف http module و http handler در system.web (مربوط به iis 6).
4) در تگ system.codedom کامپایلر مربوط به vb را حذف مینماییم.
5) در تگ system.webserver ماژول‌ها و هندلر‌های اضافی را پاک مینماییم.
6) تگ defaultdocument، به دلیل اینکه از static file مربوط به owin استفاده میکنیم.
7) custom headers‌ها را نیز پاک میکنیم.

بعد از build کردن پروژه، در صورت خطا داشتن از References‌ها، System.Reflection و System.Runtime.Extensions را حذف کنید.
یک ریشه جدید را به نام Model ساخته و مدل‌های آزمایشی Product و Category را که هر دو فقط حاوی دو پراپرتی Id, Name میباشند، به آن اضافه کنید.

در root پروژه یک کلاس به نام OwinAppStartup را با محتوای زیر بسازید
using Microsoft.AspNet.SignalR;
using Microsoft.OData;
using Microsoft.OData.Edm;
using Owin;
using OwinKatanaTest.Model;
using OwinKatanaTest.ODataControllers;
using System.Collections.Generic;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using System.Web.OData.Routing.Conventions;

namespace OwinKatanaTest
{
    public class OwinAppStartup
    {
        public void Configuration(IAppBuilder owinApp)
        {
            owinApp.Map("/odata", innerOwinAppForOData =>
            {
                HttpConfiguration webApiODataConfig = new HttpConfiguration();
                webApiODataConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;

                webApiODataConfig.Formatters.Clear();

                IEnumerable<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault();

                ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(webApiODataConfig);

                modelBuilder.Namespace = modelBuilder.ContainerName = "Test";
                var categoriesSetConfig = modelBuilder.EntitySet<Category>("Categories");
                var getBestCategoryFunctionConfig = categoriesSetConfig.EntityType.Collection.Function(nameof(CategoriesController.GetBestCategory));
                getBestCategoryFunctionConfig.ReturnsFromEntitySet<Category>("Categories");

                IEdmModel edmModel = modelBuilder.GetEdmModel();

                webApiODataConfig.MapODataServiceRoute("default", "", builder =>
                {
                    builder.AddService(ServiceLifetime.Singleton, sp => conventions);
                    builder.AddService(ServiceLifetime.Singleton, sp => edmModel);
                });

                innerOwinAppForOData.UseWebApi(webApiODataConfig);

            });

            owinApp.Map("/api", innerOwinAppForWebApi =>
            {
                HttpConfiguration webApiConfig = new HttpConfiguration();
                webApiConfig.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;

                webApiConfig.MapHttpAttributeRoutes();

                webApiConfig.Routes.MapHttpRoute(name: "default", routeTemplate: "{controller}/{action}", defaults: new { action = RouteParameter.Optional });

                innerOwinAppForWebApi.UseWebApi(webApiConfig);
            });

            owinApp.Map("/signalr", innerOwinAppForSignalR =>
            {
                innerOwinAppForSignalR.RunSignalR(new HubConfiguration
                {
                    EnableDetailedErrors = true
                });
            });

            owinApp.UseStaticFiles();

            owinApp.Run(async context =>
            {
                await context.Response.WriteAsync("owin katana");
            });
        }
    }
}
در وب کانفیگ، کار مربوط به استارتاپ را انجام دادیم و دیگر نیازی به قید کردن آن نیست. نگاشت اول، کانفیگ OData، دومی برای web api و همچنین سومی کانفیگ SignalR میباشد.
سپس یک پوشه‌ی جدید را به نام ODataControllers حاوی کلاسی با نام CategoriesController بدین گونه بسازید:
using OwinKatanaTest.Model;
using System.Web.Http;
using System.Web.OData;

namespace OwinKatanaTest.ODataControllers
{
    public class CategoriesController : ODataController
    {
        [HttpGet]
        public Category GetBestCategory()
        {
            return new Category { Id = 1, Name = "Test" };
        }
    }
}
و همچنین یک پوشه دیگر را به نام ApiControllers  به نام ProductsController با محتوای زیر:
using OwinKatanaTest.Model;
using System.Collections.Generic;
using System.Web.Http;

namespace OwinKatanaTest.ApiControllers
{
    public class ProductsController : ApiController
    {
        [HttpGet]
        [Route("products/{categoryId}")]
        public List<Product> GetProductsByCategoryId(int categoryId)
        {
            return new List<Product>
            {
                new Product { Id = 1 , Name = "Test" }
            };
        }
    }
}
حالا میتوانیم یه پروژه‌ی یونیت تست نوشته و کلیات مراحل فوق را تست نماییم. unit test را به پروژه اضافه کنید و reference پروژه‌ی اصلی خود را بدان اضافه کنید.
مانند پروژه‌ی قبلی، package.config را اضافه کرده و همه‌ی پکیج‌های قبلی به علاوه پکیج زیر را اضافه کنید:
<package id="Microsoft.Owin.Testing" version="3.0.1" targetFramework="net462" />
update-package فراموش نشود

در ادامه تست خود را اینگونه مینویسیم
using Microsoft.Owin.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OwinKatanaTest;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Test
{
    [TestClass]
    public class Test
    {
        [TestMethod]
        public async Task TestWebApi()
        {
            using (TestServer server = TestServer.Create<OwinAppStartup>())
            {
                HttpResponseMessage apiResponse = await server.HttpClient.GetAsync("/api/products/1");
                apiResponse.EnsureSuccessStatusCode();
                Assert.AreEqual(HttpStatusCode.OK, apiResponse.StatusCode);
            }
        }

        [TestMethod]
        public async Task TestOData()
        {
            using (TestServer server = TestServer.Create<OwinAppStartup>())
            {
                HttpResponseMessage odataResponse = await server.HttpClient.GetAsync("odata/Categories/Test.GetBestCategory");
                odataResponse.EnsureSuccessStatusCode();
                Assert.AreEqual(HttpStatusCode.OK, odataResponse.StatusCode);
            }
        }
    }
}
الان باید solution شما چیزی شبیه به این باشد:

بعد از اجرای تست‌ها، باید تیک سبز کنارشان ایجاد شود.
بعد از خواندن این مقاله شاید متوجه شده باشید که چقدر pipeline این پروژه شبیه به پروژه‌های ASP.NET Coreی است؛ یا بهتر است تصحیح کنم به عکس. بلکه ASP.NET Core هست که خیلی شبیه به این میباشد!
عملا سرویس‌های شما کاملا مجزا شده‌اند و میتوانید به راحتی یک فریم ورک SPA را به پروژه‌ی خود اضافه کرده و برای Authentication هم Single Sign On Identity Server بسیار مناسب میباشد. بدون اینکه حتی برنامه نویسان بیزینسی پروژه‌ی شما متوجه بشوند، با تغییراتی در کانفیگ این پروژه، می‌توان آن را بروزرسانی نمود.
لینک دانلود پروژه OwinKatanaTest.zip