مطالب
معرفی Selector های CSS - قسمت 6
51- :first-child
تگی را انتخاب می‌کند که اولین فرزند والد خود باشد.
<style>
    div.container :first-child {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 1، Text 6و Text 9 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1 9.6  7.0 3.0  4.0 :first-child 2

52- :last-child
تگی را انتخاب می‌کند که آخرین فرزند والد خود باشد.
<style>
    div.container :last-child {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 15، Text 11و Text 9 به رنگ قرمز نمایش می‌یابند. 
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :last-child  3

53- :only-child
تگی را انتخاب می‌کند که تنها فرزند والد خود باشد.
<style>
    div.container :only-child {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 9 به رنگ قرمز نمایش می‌یابد.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :only-child  3

54- :nth-child(n)
تگی را انتخاب می‌کند که nامین فرزند والد خود باشد. به جای n می‌توان از مقادیر odd (فرزندان فرد)، even (فرزندان زوج) و an+b استفاده نمود.
<style>
    div.container :nth-child(2) {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 2و Text 7 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :nth-child(n)  3

55- :nth-last-child(n)
تگی را انتخاب می‌کند که nامین فرزند والد خود از آخر باشد. به جای n می‌توان از مقادیر odd (فرزندان فرد)، even (فرزندان زوج) و an+b استفاده نمود.
<style>
    div.container :nth-last-child(2) {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 14و Text 10 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :nth-last-child(n)  3

56- :first-of-type
تگی را انتخاب می‌کند که اولین تگ در بین هم نوعان خودش و در یک والد باشد. 
<style>
    div.container :first-of-type {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 1، Text 2، Text 3، Text 4، Text 6، Text 7، Text 8، Text 9و Text 10 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :first-of-type  3

57- :last-of-type
تگی را انتخاب می‌کند که آخرین تگ در بین هم نوعان خودش و در یک والد باشد.
<style>
    div.container :last-of-type {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 15، Text 14، Text 12، Text 11، Text 10، Text 9، Text 7، Text 6و Text 3 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :last-of-type  3

58- :only-of-type
تگی را انتخاب می‌کند که تنها تگ در بین هم نوعان خودش و در یک والد باشد.
<style>
    div.container :only-of-type {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 3، Text 6، Text 7، Text 9 و Text 10 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :only-of-type  3

59- :nth-of-type(n)
تگی را انتخاب می‌کند که nامین تگ در بین هم نوعان خودش و در یک والد باشد. به جای n می‌توان از مقادیر odd (فرزندان فرد)، even (فرزندان زوج) و an+b استفاده نمود.
<style>
    div.container :nth-of-type(2) {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 5، Text 9، Text 12 و Text 14 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :nth-of-type(n)  3

60- nth-last-of-type(n)
تگی را انتخاب می‌کند که nامین تگ از آخر در بین هم نوعان خودش و در یک والد باشد. به جای n می‌توان از مقادیر odd (فرزندان فرد)، even (فرزندان زوج) و an+b استفاده نمود.
<style>
    div.container :nth-last-of-type(2) {
        color: red;
    }
</style>
<div class="container">
    <h1>Text 1</h1>
    <span>Text 2</span>
    <p>Text 3</p>
    <div>Text 4</div>
    <div>Text 5</div>
    <div>
        <h1>Text 6</h1>
        <span>Text 7</span>
        <p>Text 8</p>
        <p>
            <span>Text 9</span>
        </p>
        <div>Text 10</div>
        <p>Text 11</p>
    </div>
    <h1>Text 12</h1>
    <div>Text 13</div>
    <span>Text 14</span>
    <div>Text 15</div>
</div>
در مثال فوق Text 1، Text 2، Text 9 و Text 13 به رنگ قرمز نمایش می‌یابند.  
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  9.0 3.5  4.0 :nth-last-of-type(n)  3
مطالب
رمزنگاری فایل‌های PDF با استفاده از کلید عمومی توسط iTextSharp

دو نوع رمزنگاری را می‌توان توسط iTextSharp به PDF تولیدی و یا موجود، اعمال کرد:
الف) رمزنگاری با استفاده از کلمه عبور
ب) رمزنگاری توسط کلید عمومی

الف) رمزنگاری با استفاده از کلمه عبور
در اینجا امکان تنظیم read password و edit password به کمک متد SetEncryption شیء pdfWrite وجود دارد. همچنین می‌توان مشخص کرد که مثلا آیا کاربر می‌تواند فایل PDF را چاپ کند یا خیر (PdfWriter.ALLOW_PRINTING).
ذکر read password اختیاری است؛ اما جهت اعمال permissions حتما نیاز است تا edit password ذکر گردد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;

namespace EncryptPublicKey
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

var readPassword = Encoding.UTF8.GetBytes("123");//it can be null.
var editPassword = Encoding.UTF8.GetBytes("456");
int permissions = PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY;
pdfWriter.SetEncryption(readPassword, editPassword, permissions, PdfWriter.STRENGTH128BITS);

pdfDoc.Open();

pdfDoc.Add(new Phrase("tst 0"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("tst 1"));
}

Process.Start("TestEnc.pdf");
}
}
}


اگر read password ذکر شود، کاربران برای مشاهده محتویات فایل نیاز خواهند داشت تا کلمه‌ی عبور مرتبط را وارد نمایند:


این روش آنچنان امنیتی ندارد. هستند برنامه‌هایی که این نوع فایل‌ها را «آنی» به نمونه‌ی غیر رمزنگاری شده تبدیل می‌کنند (حتی نیازی هم ندارند که از شما کلمه‌ی عبوری را سؤال کنند). بنابراین اگر کاربران شما آنچنان حرفه‌ای نیستند، این روش خوب است؛ در غیراینصورت از آن صرفنظر کنید.


ب) رمزنگاری توسط کلید عمومی
این روش نسبت به حالت الف بسیار پیشرفته‌تر بوده و امنیت قابل توجهی هم دارد و «نیستند» برنامه‌هایی که بتوانند این فایل‌ها را بدون داشتن اطلاعات کافی، به سادگی رمزگشایی کنند.

برای شروع به کار با public key encryption نیاز است یک فایل PFX یا Personal Information Exchange داشته باشیم. یا می‌توان این نوع فایل‌ها را از CA's یا Certificate Authorities خرید، که بسیار هم نیکو یا اینکه می‌توان فعلا برای آزمایش، نمونه‌ی self signed این‌ها را هم تهیه کرد. مثلا با استفاده از این برنامه.


در ادامه نیاز خواهیم داشت تا اطلاعات این فایل PFX را جهت استفاده توسط iTextSharp استخراج کنیم. کلاس‌های زیر اینکار را انجام می‌دهند و نهایتا کلیدهای عمومی و خصوصی ذخیره شده در فایل PFX را بازگشت خواهند داد:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
/// <summary>
/// A Personal Information Exchange File Info
/// </summary>
public class PfxData
{
/// <summary>
/// Represents an X509 certificate
/// </summary>
public X509Certificate[] X509PrivateKeys { set; get; }

/// <summary>
/// Certificate's public key
/// </summary>
public ICipherParameters PublicKey { set; get; }
}
}

using System;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
/// <summary>
/// A Personal Information Exchange File Reader
/// </summary>
public class PfxReader
{
X509Certificate[] _chain;
AsymmetricKeyParameter _asymmetricKeyParameter;

/// <summary>
/// Reads A Personal Information Exchange File.
/// </summary>
/// <param name="pfxPath">Certificate file's path</param>
/// <param name="pfxPassword">Certificate file's password</param>
public PfxData ReadCertificate(string pfxPath, string pfxPassword)
{
using (var stream = new FileStream(pfxPath, FileMode.Open, FileAccess.Read))
{
var pkcs12Store = new Pkcs12Store(stream, pfxPassword.ToCharArray());
var alias = findThePublicKey(pkcs12Store);
_asymmetricKeyParameter = pkcs12Store.GetKey(alias).Key;
constructChain(pkcs12Store, alias);
return new PfxData { X509PrivateKeys = _chain, PublicKey = _asymmetricKeyParameter };
}
}

private void constructChain(Pkcs12Store pkcs12Store, string alias)
{
var certificateChains = pkcs12Store.GetCertificateChain(alias);
_chain = new X509Certificate[certificateChains.Length];

for (int k = 0; k < certificateChains.Length; ++k)
_chain[k] = certificateChains[k].Certificate;
}

private static string findThePublicKey(Pkcs12Store pkcs12Store)
{
string alias = string.Empty;
foreach (string entry in pkcs12Store.Aliases)
{
if (pkcs12Store.IsKeyEntry(entry) && pkcs12Store.GetKey(entry).Key.IsPrivate)
{
alias = entry;
break;
}
}

if (string.IsNullOrEmpty(alias))
throw new NullReferenceException("Provided certificate is invalid.");

return alias;
}
}
}


اکنون رمزنگاری فایل PDF تولیدی توسط کلید عمومی، به سادگی چند سطر کد زیر خواهد بود:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
pdfWriter.SetEncryption(
certs: certs.X509PrivateKeys,
permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
encryptionType: PdfWriter.ENCRYPTION_AES_128);

pdfDoc.Open();

pdfDoc.Add(new Phrase("tst 0"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("tst 1"));
}

Process.Start("Test.pdf");
}
}
}

پیش از فراخوانی متد Open باید تنظیمات رمزنگاری مشخص شوند. در اینجا ابتدا فایل PFX خوانده شده و کلیدهای عمومی و خصوصی آن استخراج می‌شوند. سپس به متد SetEncryption جهت استفاده نهایی ارسال خواهند شد.

نحوه استفاده از این نوع فایل‌های رمزنگاری شده:
اگر سعی در گشودن این فایل رمزنگاری شده نمائیم با خطای زیر مواجه خواهیم شد:


کاربران برای اینکه بتوانند این فایل‌های PDF را بار کنند نیاز است تا فایل PFX شما را در سیستم خود نصب کنند. ویندوز فایل‌های PFX را می‌شناسد و نصب آن‌ها با دوبار کلیک بر روی فایل و چندبار کلیک بر روی دکمه‌ی Next و وارد کردن کلمه عبور آن، به پایان می‌رسد.

سؤال: آیا می‌توان فایل‌های PDF موجود را هم به همین روش رمزنگاری کرد؟
بله. iTextSharp علاوه بر PdfWriter دارای PdfReader نیز می‌باشد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
class Program
{
static void Main(string[] args)
{
PdfReader reader = new PdfReader("TestDec.pdf");
using (var stamper = new PdfStamper(reader, new FileStream("TestEnc.pdf", FileMode.Create)))
{
var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
stamper.SetEncryption(
certs: certs.X509PrivateKeys,
permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
encryptionType: PdfWriter.ENCRYPTION_AES_128);
stamper.Close();
}

Process.Start("TestEnc.pdf");
}
}
}


سؤال: آیا می‌توان نصب کلید عمومی را خودکار کرد؟
سورس برنامه SelfCert که معرفی شد، در دسترس است. این برنامه قابلیت انجام نصب خودکار مجوزها را دارد.

نظرات مطالب
یک دست سازی ی و ک در برنامه‌های Entity framework 6

بر روی SqlServer درست کارمی کند ولی بر روی کانکشن SQL CE پیغام زیر را می‌دهد:

System.NotSupportedException was unhandled by user code
  HResult=-2146233067
  Message=DesignTimeVisible
  Source=EntityFramework.SqlServerCompact
  StackTrace:
       at System.Data.Entity.SqlServerCompact.SqlCeMultiCommand.set_CommandText(String value)
       at EfExt.YeKe.ApplyCorrectYeKe(DbCommand command) in e:\test\EfExt\YeKe.cs:line 33
       at EfExt.YeKeInterceptor.ReaderExecuting(DbCommand command, DbCommandInterceptionContext`1 interceptionContext) in e:\MyFilesAndPrograms\WebIO\EfExt\YeKeInterceptor.cs:line 15
       at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<Reader>b__d(IDbCommandInterceptor i, DbCommand t, DbCommandInterceptionContext`1 c)
       at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
       at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(DbCommand command, DbCommandInterceptionContext interceptionContext)
       at System.Data.Entity.Internal.InterceptableDbCommand.ExecuteDbDataReader(CommandBehavior behavior)
       at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
       at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
  InnerException:
نظرات مطالب
ASP.NET MVC #13
با سلام و تشکر فراوان
اعتبار سنجی سمت کلاینت در برنامه من مشکل دارد همه موارذ کاملا مانند مثال است در فایرباگ breakpoint گذاشتم وارد این خط نمی‌شود
return Date.parse(value) < new Date();
و بعد از این خط به بیرون می‌رود
jQuery.validator.addMethod("mydatevalidator",
 function (value, element, param) {
ایا مشکل از اینجاست یا جای دیگر در ضمن اگر خط بالا هم در document.readyنباشد هم این خط هم فراخوانی نمی‌شود
نظرات مطالب
کاهش تعداد بار تعریف using ها در C# 10.0 و NET 6.0.
یک نکته‌ی تکمیلی: ترفندی برای معرفی Stubs توسط ویژگی global using statements در unit tests

برای نمونه متد زیر را درنظر بگیرید:
public static string CurrentInvariantMonthName()
    {
        var month = DateTimeOffset.UtcNow.Month;
        return CultureInfo
            .InvariantCulture
            .DateTimeFormat
            .GetMonthName(month);
    }
در کل نوشتن آزمون واحد برای متدهایی که با زمان و خواصی مانند UtcNow و یا Now، کار می‌کنند، مشکل است. در آزمون‌های واحد نیاز داریم تا یک خروجی مشخص را با مقداری از پیش معلوم، مقایسه کنیم تا اطمینان حاصل شود که عملیات صورت گرفته، صحیح است. اما UtcNow هر بار متغیر است.
برای حل این مشکل، با استفاده از ویژگی global using statements و compiler directives، می‌توان دو مفهوم متفاوت را برای زمان ارائه داد:
#if MOCK_TIME
global using DateTimeOffset = StubDateTimeOffset; 
#else
global using DateTimeOffset = System.DateTimeOffset;
#endif

public static class StubDateTimeOffset
{
    private static System.DateTimeOffset? value;
    
    public static System.DateTimeOffset UtcNow 
        => value ?? System.DateTimeOffset.UtcNow ;
    
    public static void Set(System.DateTimeOffset dateTimeOffset) {
        value = dateTimeOffset;
    }

    public static void Reset() => value = null;
}
در اینجا یک فایل خالی cs. ایجاد شده و قطعه کد فوق در آن درج می‌شود. در این حالت اگر توسط تنظیمات کامپایلر در فایل csproj برنامه، MOCK_TIME فعال شود، مفهوم DateTimeOffset، دیگر همان System.DateTimeOffset استاندارد نخواهد بود؛ بلکه از یک کلاس بدلی به نام StubDateTimeOffset تامین می‌شود. برای فعالسازی MOCK_TIME هم می‌توان به صورت زیر عمل کرد که این تعریف، در فایل csproj پروژه‌ی آزمایشی قرار می‌گیرد:
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
   <DefineConstants>MOCK_TIME</DefineConstants>
</PropertyGroup>
پس از این تغییر، اینبار کلاس StubDateTimeOffset، تامین کننده‌ی DateTimeOffset در آزمون‌های واحد خواهد بود و در این آزمون‌ها می‌توان با مقدار دهی DateTimeOffset به صورت زیر، هربار آزمایشات را بر اساس یک UtcNow «مشخص» انجام داد:
// set time
StubDateTimeOffset.Set(new(new(2022, 7, 1)));
مطالب
پردازش توالی توالی‌ها در Reactive extensions
به صورت پیش فرض، Rx هر بار تنها یک مقدار را بررسی می‌کند. اما گاهی از اوقات نیاز است تا در هربار، بیشتر از یک مقدار دریافت و پردازش شوند. برای این منظور Rx متدهای الحاقی ویژه‌ای را به نام‌های Buffer ،Scan و Window تدارک دیده‌است تا بتواند از یک توالی، چندین توالی را تولید کند (توالی توالی‌ها = Sequence of sequences).


متد Scan

فرض کنید قصد دارید تعدادی عدد را با هم جمع بزنید. برای اینکار عموما عدد اول با عدد دوم جمع زده شده و سپس حاصل آن با عدد سوم جمع زده خواهد شد و به همین ترتیب تا آخر توالی. کار متد Scan نیز دقیقا به همین نحو است. هربار که قرار است توالی پردازش شود، حاصل عملیات مرحله‌ی قبل را در اختیار مصرف کننده قرار می‌دهد.
در مثال ذیل، قصد داریم حاصل جمع اعداد موجود در آرایه‌ای را بدست بیاوریم:
var sequence = new[] { 12, 3, -4, 7 }.ToObservable();
var runningSum = sequence.Scan((accumulator, value) =>
{
    Console.WriteLine("accumulator {0}", accumulator);
    Console.WriteLine("value {0}", value);
    return accumulator + value;
});
runningSum.Subscribe(result => Console.WriteLine("result {0}\n", result));
با این خروجی
result 12

accumulator 12
value 3
result 15

accumulator 15
value -4
result 11

accumulator 11
value 7
result 18
در اولین بار اجرای متد Subscribe، کار مقدار دهی accumulator با اولین عنصر آرایه صورت می‌گیرد.
در دفعات بعدی، مقدار این accumulator با عدد جاری جمع زده شده و حاصل این عملیات در تکرار آتی، مجددا توسط accumulator قابل دسترسی خواهد بود.

یک نکته: اگر علاقمند باشیم که مقدار اولیه‌ی accumulator، اولین عنصر توالی نباشد، می‌توان آن‌را توسط پارامتر seed متد Scan مقدار دهی کرد:
 var runningSum = sequence.Scan(seed: 10, accumulator: (accumulator, value) =>


متد Buffer

متد بافر، کار تقسیم یک توالی را به توالی‌های کوچکتر، بر اساس زمان، یا تعداد عنصر مشخص شده، انجام می‌دهد. برای مثال در برنامه‌های دسکتاپ شاید نیازی نباشد تا به ازای هر عنصر توالی، یکبار رابط کاربری را به روز کرد. عموما بهتر است تا تعداد مشخصی از عناصر یکجا پردازش شده و نتیجه‌ی این پردازش به تدریج نمایش داده شود.
var sequence = Enumerable.Range(1, 200)
                 .ToObservable()
                 .Buffer(count: 10);

sequence.Subscribe(onNext: numbers =>
{
   Console.WriteLine(numbers.Sum());
});
در اینجا نحوه‌ی استفاده از متد بافر را به همراه مشخص کردن تعداد اعضای بافر ملاحظه می‌کنید. هربار که onNext متد Subscribe فراخوانی شود، 10 عنصر از توالی را در اختیار خواهیم داشت (بجای یک عنصر حالت متداول بافر نشده).
به این ترتیب می‌توان فشار حجم اطلاعات ورودی با فرکانس بالا را کنترل کرد و در نتیجه از منابع موجود بهتر استفاده نمود. برای مثال اگر می‌خواهید عملیات bulk insert را انجام دهید، می‌توان بر اساس یک batch size مشخص، گروه گروه اطلاعات را به بانک اطلاعاتی اضافه کرد تا فشار کار کاهش یابد.

همینکار را بر اساس زمان نیز می‌توان انجام داد:
 var sequence = Enumerable.Range(1, 200)
                   .ToObservable()
                   .Buffer(timeSpan: TimeSpan.FromSeconds(2));
در مثال فوق هر 2 ثانیه یکبار، مجموعه‌ای از عناصر به متد onNext ارسال خواهند شد.


متد Window

متد Window نیز دقیقا همان پارامترهای متد بافر را قبول می‌کند. با این تفاوت که هربار، یک توالی obsevable را به متد onNext ارسال می‌کند.
نوع numbers پارامتر onNext، در حین بکارگیری متد بافر در مثال‌های فوق، IList of int است. اما اگر متدهای Buffer را تبدیل به متد Window کنیم، اینبار نوع numbers، معادل IObservable of int خواهد شد.
 var sequence = Enumerable.Range(1, 200)
                           .ToObservable()
                           .Window(timeSpan: TimeSpan.FromSeconds(2));

sequence.Subscribe(onNext: numbers =>
{
       numbers.Subscribe(onNext: number => Console.WriteLine(number));
});


چه زمانی باید از Buffer استفاده کرد و چه زمانی از Window؟

در متد بافر، به ازای هر توالی که به پارامتر onNext ارسال می‌شود، یکبار وهله‌ی جدیدی از توالی مدنظر در حافظه ایجاد و ارسال خواهد شد. در متد Window صرفا اشاره‌گرهایی به این توالی را در اختیار داریم؛ بنابراین مصرف حافظه‌ی کمتری را شاهد خواهیم بود. متد Window بسیار مناسب است برای اعمال aggregation. مثلا اگر نیاز است جمع، میانگین، حداقل و حداکثر عناصر دریافتی محاسبه شوند، بهتر است از متد Window استفاده شود که نهایتا قابلیت استفاده از متدهای الحاقی Sum و Max و Min را به همراه دارد. با این تفاوت که حاصل این‌ها نیز یک IObservable است که باید Subscribe آن‌را برای دریافت نتیجه فراخوانی کرد:
 var sequence = Enumerable.Range(1, 200)
                          .ToObservable()
                          .Window(10);

sequence.Subscribe(onNext: numbers =>
{
     numbers.Sum().Subscribe(onNext: number => Console.WriteLine(number));
});
در این حالت متد Window، برخلاف متد Buffer، توالی numbers را هربار کش نمی‌کند و به این ترتیب می‌توان به مصرف حافظه‌ی کمتری رسید.


کاربردهای دنیای واقعی

در اینجا دو مثال از بکارگیری متد Buffer را جهت پردازش مجموعه‌های عظیمی از اطلاعات و نمایش همزمان آن‌ها در رابط کاربری ملاحظه می‌کنید.
مثال اول: فرض کنید قصد دارید تمام فایل‌های درایو C خود را توسط یک TreeView نمایش دهید. در این حالت نباید رابط کاربری برنامه در حالت هنگ به نظر برسد. همچنین به علت زیاد بودن تعداد فایل‌ها و نمایش همزمان آن‌ها در UI، نباید CPU Usage برنامه تا حدی باشد که در کار سایر برنامه‌ها اخلال ایجاد کند. در این مثال‌ها با استفاده از Rx و متد بافر آن، هربار مثلا 1000 آیتم را بافر کرده و سپس یکجا در TreeView نمایش می‌دهند. به این ترتیب دو شرط یاد شده محقق می‌شوند.
The Rx Framework By Example

مثال دوم: خواندن تعداد زیادی رکورد از بانک اطلاعاتی به همراه نمایش همزمان آن‌ها در UI بدون اخلالی در کار سیستم و همچنین هنگ کردن برنامه.
Using Reactive Extensions for Streaming Data from Database
نظرات مطالب
EF Code First #12
با سلام، من کد شما را به صورت زیر تغییر دادم
ابتدا یک اینترفیس به صورت زیر ایجاد کردم
namespace Service.Interfaces
{
    public interface IGenericService<T> 
    {
        void AddOrUpdate(T entity);
        void Delete(T entity);
        T Find(Func<T, bool> predicate);
        T GetLast(Func<T, bool> predicate);
        IList<T> GetAll();
        IList<T> GetAll(Func<T, bool> predicate);
        IList<T> GetAll(Expression<Func<T, object>> orderby);
        IList<T> GetAll(Func<T, bool> predicate, Expression<Func<T, object>> orderby);

        Task<List<T>> GetAllAsync();
        Task<List<T>> GetAllAsync(Func<T, bool> predicate);
        Task<List<T>> GetAllAsync(Expression<Func<T, object>> orderby);
        Task<List<T>> GetAllAsync(Func<T, bool> predicate, Expression<Func<T, object>> orderby);

        int Count();
        int Count(Func<T, bool> predicate);
    }
}

و تمام اینترفیس‌های دیگر از این به صورت زیر به ارث برده شده اند
public interface IBookGroupService:IGenericService<BookGroup>
    {
    }

و در قسمت Servise یک کلاس ایجاد کردم که اینترفیس IGenericService  را پیاده سازی می‌کند که کدهای آن به صورت زیر است

public class EFGenericService<TEntity> : IGenericService<TEntity>
         where TEntity : class
    {
        protected IUnitOfWork _uow;
        protected IDbSet<TEntity> _tEntities;

        public EFGenericService(IUnitOfWork uow)
        {
            _uow = uow;
            _tEntities = _uow.Set<TEntity>();
        }


        public void AddOrUpdate(TEntity entity)
        {
             _tEntities.AddOrUpdate(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            _tEntities.Remove(entity);
        }

        public virtual TEntity Find(Func<TEntity, bool> predicate)
        {
            return _tEntities.Where(predicate).FirstOrDefault();
        }

        public virtual TEntity GetLast(Func<TEntity, bool> predicate)
        {
            return _tEntities.Where(predicate).Last();
        }


        public virtual IList<TEntity> GetAll()
        {
            return _tEntities.ToList();
        }

        public virtual IList<TEntity> GetAll(Func<TEntity, bool> predicate)
        {
            return _tEntities.Where(predicate).ToList();
        }

        public virtual IList<TEntity> GetAll(Expression<Func<TEntity, object>> @orderby)
        {
            return _tEntities.OrderBy(@orderby).ToList();
        }

        public virtual IList<TEntity> GetAll(Func<TEntity, bool> predicate, Expression<Func<TEntity, object>> @orderby)
        {
            return _tEntities.OrderBy(@orderby).Where(predicate).ToList();
        }

        public async Task<List<TEntity>> GetAllAsync()
        {
           return await Task.Run(() => _tEntities.ToList());
        }

        public async Task<List<TEntity>> GetAllAsync(Func<TEntity, bool> predicate)
        {
          return await Task.Run(() => _tEntities.Where(predicate).ToList());
        }

        public async Task<List<TEntity>> GetAllAsync(Expression<Func<TEntity, object>> @orderby)
        {
           return await Task.Run(() => _tEntities.OrderBy(@orderby).ToList());
        }

        public async Task<List<TEntity>> GetAllAsync(Func<TEntity, bool> predicate, Expression<Func<TEntity, object>> @orderby)
        {
           return await Task.Run(()=> _tEntities.OrderBy(@orderby).Where(predicate).ToList());
        }

        public virtual int Count()
        {
            return _tEntities.Count();
        }

        public virtual int Count(Func<TEntity, bool> predicate)
        {
            return _tEntities.Count(predicate);
        }
    }


و بقیه کلاس‌ها از کلاس بالا به ارث می‌برند.

public class EFBorrowService:EFGenericService<Borrow>,IBorrowService
    {
        public EFBorrowService(IUnitOfWork uow) : base(uow)
        {
        }
    }


حال سوال من اینه که این پیاده سازی از لحاظ پیاده سازی مشکلی ندارد؟ و می‌توانم در پروژه هام از این روش استفاده کنم یا خیر؟

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

  1. تامین اطلاعات از طریق کامپوننت پدر و انتساب اطلاعات به کامپوننت‌های فرزند جهت نمایش
  2. تامین اطلاعات به صورت توکار و توسط خود کامپوننت فرزند 

به جهت کپسوله سازی هرچه بیشتر و همچنین کاهش کدهای تکراری، مورد دوم پیشنهاد می‌شود. در این حالت کامپوننت پدر بایستی شناسه مشتری مورد نظر را به کامپوننت فرزند ارسال کند تا کامپوننت فرزند بر اساس این شناسه، اطلاعات مشتری را واکشی و نمایش دهد. در این مثال انتقال اطلاعات از کامپوننت پدر به فرزند جهت ارسال شناسه مشتری ضروری است. همچنین در مواردی نیز ممکن است کامپوننت فرزند اطلاعات و رخدادهایی را به کامپوننت پدر ارسال کند. مثلا کامپوننت «نمایش اطلاعات شخص» قرار است بعد از واکشی اطلاعات، «حقیقی» یا «حقوقی» بودن شخص را به کامپوننت پدر جهت نمایش عنوان مناسب اطلاع دهد. 

در کل، جهت نقل و انتقال اطلاعات مشترک و تعامل بین کامپوننت‌ها، به چند روش زیر می‌توان اقدام کرد:
  1. انتقال اطلاعات از کامپوننت پدر به فرزند از طریق متادیتا Input@
  2. به‌جریان انداختن رخداد از کامپوننت فرزند و گرفتن آن از طریق کامپوننت پدر
  3. تعامل کامپوننت پدر و فرزند از طریق template reference variable
  4. فراخوانی کامپوننت فرزند از کامپوننت پدر به کمک ViewChild@
  5. ارتباط کامپوننت پدر و فرزند از طریق سرویس 
مثال بالا یک مورد بسیار ساده در جهت تفهیم اجبار نقل و انتقال اطلاعات بین کامپوننت‌ها بود. مطمئنا در برخی موارد پیچیده‌تر، حتی با به اشتراک گذاری اطلاعات بین کامپوننت‌ها لازم است پیاده سازی یک رخداد کامپوننت فرزند را به پدر آن واگذار کنیم. در ادامه تمامی این روش‌ها را مورد برسی قرار خواهیم داد. 

نکته: تمامی مثالهای ذکر شده در ادامه تنها در جهت توضیح مفهوم بوده و در برخی موارد گزینه مناسبی جهت پیاده سازی در برنامه‌های واقعی نیستند. 

انتقال اطلاعات از کامپوننت پدر به فرزند از طریق متادیتای Input@
فرض کنید کامپوننت CustomerInfoComponent را داریم که هم نمایش اطلاعات و هم دریافت اطلاعات مشتری را برعهده دارد (در حالت نمایشی به عنوان مثال قرار است تگ input را به صورت readOnly نمایش دهد). کامپوننت پدر تصمیم می‌گیرد که این کامپوننت به چه صورتی ظاهر شود. برای اینکار لازم است متغیری را در کامپوننت CustomerInfoComponent تعریف کنیم که از طریق کامپوننت پدر (استفاده کننده) قابل تنظیم و مقدار دهی باشد. برای این کار کافی است قبل از تعریف متغیر، از متادیتای Input@ به شکل زیر استفاده کنیم:
import { Component, OnInit, Input} from '@angular/core';

@Component({
    selector: 'app-customer-info',
    templateUrl: './customer-info.component.html',
    styleUrls: ['./customer-info.component.css']
})
export class CustomerInfoComponent implements OnInit {

    @Input() FormIsReadOnly: boolean;

    constructor() { }

    ngOnInit() {
    }
}
به همین راحتی. حالا هنگام استفاده از کامپوننت و ارسال مقدار به کامپوننت به شکل زیر عمل می‌کنیم:
<app-customer-info FormIsReadOnly="{{true}}"></app-customer-info>


در صورت استفاده به شکل زیر:

<app-customer-info FormIsReadOnly="true"></app-customer-info>
مقدار منتسب به متغیر FormIsReadOnly به صورت رشته خواهد بود (یعنی مقدار "true"). همچنین برای انتساب یک متغیر به FormIsReadOnly باید به شکل زیر عمل کرد (با این فرض که booleanVariable به صورت یک متغیر از نوع boolean در کامپوننت پدر تعریف شده است): 
<app-customer-info [FormIsReadOnly]="booleanVariable"></app-customer-info>
در این صورت با تغییر متغیر booleanVariable در کامپوننت پدر، کامپوننت فرزند متوجه تغییر خواهد شد ولی برعکس این مورد صادق نیست. یعنی هر تغییری در متغیر FormIsReadOnly در کامپوننت فرزند، باعث تغییر مقدار booleanVariable نخواهد شد. این بدان معنی است که انقیاد انجام گرفته به صورت یک طرفه و از سمت منبع داده (data source) به کامپوننت فرزند است. 
ادامه دارد/ 
نظرات مطالب
C# 6 - Null-conditional operators
یک نکته‌ی تکمیلی: بهبود Null Coalescing Assignment در C# 8.0
ساده سازی انتساب مقداری به یک متغیر، اگر نال باشد:
- روش انجام اینکار تا پیش از C# 8.0
if (variable == null)
{
   variable = expression; // C# 1..7
}
- در C# 8.0
variable ??= expression; // C# 8
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
در قسمت قبل موفق شدیم بر اساس OpenAPI specification endpoint تنظیم شده، رابط کاربری خودکاری را توسط ابزار Swagger-UI تولید کنیم. در ادامه می‌خواهیم این مستندات تولید شده را غنی‌تر کرده و کیفیت آن‌را بهبود دهیم.


استفاده از XML Comments برای بهبود کیفیت مستندات API

نوشتن توضیحات XML ای برای متدها و پارامترها در پروژه‌های دات‌نتی، روشی استاندارد و شناخته شده‌است. برای نمونه در AuthorsController، می‌خواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
/// <summary>
/// Get an author by his/her id
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <returns>An ActionResult of type Author</returns>
[HttpGet("{authorId}")]
public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
در این حالت اگر برنامه را اجرا کنیم، این توضیحات XMLای هیچ تاثیری را بر روی OpenAPI specification تولیدی و در نهایت Swagger-UI تولید شده‌ی بر اساس آن، نخواهد داشت. برای رفع این مشکل، باید به فایل OpenAPISwaggerDoc.Web.csproj مراجعه نمود و تولید فایل XML متناظر با این توضیحات را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
پس از تنظیم خاصیت GenerateDocumentationFile به true، با هر بار Build برنامه، فایل xml ای مطابق نام اسمبلی برنامه، در پوشه‌ی bin آن تشکیل خواهد شد؛ مانند فایل bin\Debug\netcoreapp2.2\OpenAPISwaggerDoc.Web.xml در این مثال.
اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
                    // ... 
                   );

                var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
                setupAction.IncludeXmlComments(xmlCommentsFullPath);
            });
        }
در متد IncludeXmlComments، بجای ذکر صریح نام و مسیر فایل OpenAPISwaggerDoc.Web.xml، بر اساس نام اسمبلی جاری، نام فایل XML مستندات تعیین و مقدار دهی شده‌است.
پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:




افزودن توضیحات به Response

تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:


علت را نیز در تصویر فوق مشاهده می‌کنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدل‌های مرتبط دریافت می‌کند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
using System;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author with Id, FirstName and LastName fields
    /// </summary>
    public class Author
    {
        /// <summary>
        /// The id of the author
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// The first name of the author
        /// </summary>
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        public string LastName { get; set; }
    }
}
مشکل! در این حالت اگر برنامه را اجرا کنیم، خروجی این توضیحات را در قسمت schemas مشاهده نخواهیم کرد. علت اینجا است که چون اسمبلی OpenAPISwaggerDoc.Models با اسمبلی OpenAPISwaggerDoc.Web یکی نیست و آن‌را از پروژه‌ی اصلی خارج کرده‌ایم، به همین جهت نیاز است ابتدا به فایل OpenAPISwaggerDoc.Models.csproj مراجعه و GenerateDocumentationFile آن‌را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
</Project>
سپس باید فایل xml مستندات آن‌را به صورت مجزایی به تنظیمات ابتدایی برنامه معرفی نمود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
  // ...
                   );
                var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList();
                xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile));
            });
        }
با توجه به اینکه تمام فایل‌های xml تولید شده در آخر به پوشه‌ی bin\Debug\netcoreapp2.2 کپی می‌شوند، فقط کافی است حلقه‌ای را تشکیل داده و تمام آن‌ها را یکی یکی توسط متد IncludeXmlComments به تنظیمات AddSwaggerGen اضافه کرد.

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



بهبود مستندات به کمک Data Annotations

اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
جائیکه موجودیت Author را در پروژه‌ی OpenAPISwaggerDoc.Entities تعریف کرده‌ایم، نام و نام خانوادگی اجباری بوده و دارای حداکثر طول 150 حرف، هستند. قصد داریم همین ویژگی‌ها را به DTO دریافتی این متد، یعنی AuthorForUpdate نیز اعمال کنیم:
using System.ComponentModel.DataAnnotations;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author for update with FirstName and LastName fields
    /// </summary>
    public class AuthorForUpdate
    {
        /// <summary>
        /// The first name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string LastName { get; set; }
    }
}
پس از افزودن ویژگی‌های Required و MaxLength به این DTO، خروجی Sawgger-UI به صورت زیر بهبود پیدا می‌کند:



بهبود مستندات متد HttpPatch با ارائه‌ی یک مثال

دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
یکی HttpPut است
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
و دیگری HttpPatch:
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
            Guid authorId,
            JsonPatchDocument<AuthorForUpdate> patchDocument)
این مورد آرام آرام در حال تبدیل شدن به یک استاندارد است؛ چون امکان Partial updates را فراهم می‌کند. به همین جهت نسبت به HttpPut، کارآیی بهتری را ارائه می‌دهد. اما چون پارامتر دریافتی آن از نوع ویژه‌ی JsonPatchDocument است و مثال پیش‌فرض مستندات آن، آنچنان مفهوم نیست:


بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
/// <summary>
/// Partially update an author
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <param name="patchDocument">The set of operations to apply to the author</param>
/// <returns>An ActionResult of type Author</returns>
/// <remarks>
/// Sample request (this request updates the author's first name) \
/// PATCH /authors/id \
/// [ \
///     { \
///       "op": "replace", \
///       "path": "/firstname", \
///       "value": "new first name" \
///       } \
/// ] \
/// </remarks>
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
    Guid authorId,
    JsonPatchDocument<AuthorForUpdate> patchDocument)
در اینجا در حین کامنت نویسی، می‌توان از المان remarks، برای نوشتن توضیحات اضافی مانند ارائه‌ی یک مثال، استفاده کرد که در آن op و path معادل‌های بهتری را نسبت به مستندات پیش‌فرض آن پیدا کرده‌‌اند. در اینجا برای ذکر خطوط جدید باید از \ استفاده کرد؛ وگرنه خروجی نهایی، در یک سطر نمایش داده می‌شود:



روش کنترل warningهای کامنت‌های تکمیل نشده

با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر می‌کند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمت‌های برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنت‌ها در فایل csproj برنامه است:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningsAsErrors>NU1605;</WarningsAsErrors>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>
توضیحات:
- اگر می‌خواهید خودتان را مجبور به کامنت نویسی کنید، می‌توانید نبود کامنت‌ها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
- اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم می‌توانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شده‌ی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
- اگر به warning نبود کامنت‌ها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع می‌شود. یعنی  CS1591 مربوط به کامنت‌های نوشته نشده‌است. می‌توان برای صرفنظر کردن از آن، شماره‌ی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-03.zip

در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.