مطالب
دقت نوع داده‌ی decimal در SQL Server و EF Core
از نوع داده‌ا‌ی decimal در SQL Server، بیشتر برای انجام کارهای تجاری و ذخیره‌ی قیمت‌ها و مبالغ استفاده می‌شود؛ جائیکه اعداد و ارقام خیلی سریع بزرگ می‌شوند و گاهی از اوقات ممکن است به همراه اعشار هم باشد. اما ... کار با آن‌ها در SQL Server نیازمند نکات ویژه‌ای است که اگر ندید گرفته شوند، محاسبات نادرستی را سبب خواهند شد!


مفهوم تعریف نوع decimal پیش‌فرض در SQL Server

فرض کنید از EF پیش از EF Core استفاده می‌کنید که به صورت پیش‌فرض، نوع System.Decimal را در مدل‌های شما به همان decimal در SQL Server نگاشت می‌کند. فکر می‌کنید در این حالت خروجی کوئری‌های زیر چه چیزی خواهد بود؟
select '0.4400' as Expected , cast(0.4400 as decimal) as Actual
select '1.3200' as Expected, cast(1.3200 as decimal) as Actual
select '1.7600' as Expected, cast(1.7600 as decimal) as Actual
select '65.0000' as Expected, cast(65.0000 as decimal) as Actual
select '99.50' as Expected, cast(99.50 as decimal) as Actual

این خروجی را در تصویر ذیل مشاهده می‌کنید. در اینجا خصوصا به مورد صفر دقت کنید:


 علت اینجا است که از دیدگاه SQL Server، نوع decimal پیش‌فرض، دقیقا به معنای decimal(18,0) است که به آرگومان اول آن، precision و به آرگومان دوم آن، scale می‌گویند. یعنی حداکثر چه تعداد رقم دسیمال، پیش از ممیز و چه تعداد عدد دسیمال، پس از ممیز قرار است در این نوع داده ذخیره شوند.
بنابراین باتوجه به اینکه در حالت پیش‌فرض، مقدار scale و یا همان تعداد ارقام مجاز پس از ممیز، صفر است، عدد ارائه شده، به نزدیک‌ترین عدد صحیح ممکن، گرد خواهد شد.

به همین جهت برای رفع این مشکل، باید دقیقا مشخص کرد که scale نوع داده‌ای decimal مدنظر چیست. برای مثال می‌توان از decimal(10,4) استفاده کرد که در اینجا، نتایج صحیحی را ارائه می‌دهد:


همچنین به عنوان تمرین، مثال زیر را حل کنید!
 select iif(cast(0.1 + 0.2 as decimal) = 0, 'true', 'false')

بنابراین باید به‌خاطر داشت، اگر از EF 6x (پیش از EF Core) استفاده می‌شود، حتما نیاز است مقادیر precision و scale را دقیقا مشخص کنیم؛ وگرنه حالت پیش‌فرض آن decimal(18,0) است:
modelBuilder.Properties<decimal>().Configure(x => x.HasPrecision(18, 6));


رفتار EF Core با نوع داده‌ای decimal

رفتار EF Core با نوع داده‌ای decimal بهبود یافته و حالت پیش‌فرض آن، بدون هیچگونه تنظیمی، نگاشت به decimal(18,2) است. به علاوه اگر این پیش‌فرض را هم تغییر ندهیم، در حین اعمال Migration، پیام اخطاری را نمایش می‌دهد:
No store type was specified for the decimal property 'Price' on entity type 'Product'.
This will cause values to be silently truncated if they do not fit in the default precision and scale.
Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating'
using 'HasColumnType', specify precision and scale using 'HasPrecision',
or configure a value converter using 'HasConversion'.
اگر می‌خواهید دیگر این اخطار نمایش داده نشود، می‌توان از EF Core 6x به بعد، به صورت زیر و سراسری، تنظیم زیر را اعمال کرد:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<decimal>().HavePrecision(18, 6);
}
و یا روش دیگر تنظیم آن، استفاده از ویژگی جدید [Precision(18, 2)] است که می‌توان آن‌ها را بر روی خواص decimal قرار داد. اگر از نگارش‌های پیش‌از EF Core 6x استفاده می‌کنید، می‌توان از ویژگی [Column(TypeName = "decimal(5, 2)")] نیز استفاده کرد.




دو مطلب مرتبط
- از نوع‌های داده‌ا‌ی float و یا double در مدل‌های EF خود استفاده نکنید.
- همیشه مراقب بزرگ شدن اعداد و مبالغ و جمع نهایی آن‌ها باشید!
مطالب
استفاده از jQuery Ajax جهت تعیین اعتبار یک فرم

فرض کنید تعیین اعتبار یکی از فیلدهای فرم نیاز به انجام محاسباتی در سمت سرور دارد و این‌کار را می‌خواهیم با استفاده از jQuery Ajax‌ انجام دهیم. مشکلی که در اینجا وجود دارد، این است که A در Ajax به معنای asynchronous است. یعنی زمانیکه کاربر دکمه submit را فشرد، دیگر برنامه منتظر این نخواهد شد که پاسخ کامل دریافت شود ، سایر پردازش‌ها صورت گیرد و سپس فرم را به سرور ارسال نماید (شبیه به ایجاد یک ترد جدید در برنامه‌های ویندوزی). مثال زیر را در نظر بگیرید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestCustomValidation.aspx.cs"
Inherits="TestJQueryAjax.TestCustomValodation" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>

<script type="text/javascript">
function validate() {
var number1 = $("#<%=txtNumber1.ClientID %>").val();
var number2 = $("#<%=txtNumber2.ClientID %>").val();
var result = false;
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/ValidateIt',
data: '{"number1":' + number1 + ',"number2":' + number2 + '}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
if (msg.d) {
result = true;
alert('بسیار خوب');
}
else {
result = false;
alert('دوباره سعی کنید');
}
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
result = false;
alert("خطایی رخ داده است");
}
});
//debugger;
return result;
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
number 1 :
<asp:TextBox runat="server" ID="txtNumber1" />
<br />
number 2 :
<asp:TextBox runat="server" ID="txtNumber2" />
<br />
<asp:Button ID="btnSubmit" Text="Submit" UseSubmitBehavior="false" runat="server"
OnClientClick="if(!validate()){ return false;}" OnClick="btnSubmitClick" />
</div>
</form>
</body>
</html>

این مثال یک نوع اعتبار سنجی سفارشی را در حین submit با استفاده از وب سرویس زیر انجام می‌دهد (حاصلضرب دو عدد دریافتی را بررسی می‌کند که باید مساوی 10 باشند. البته هدف از این مثال ساده، آشنایی با نحوه‌ی انجام این نوع عملیات است که می‌تواند شامل کار با دیتابیس و غیره هم باشد. و گرنه بدیهی است این بررسی را با دو سطر کد جاوا اسکریپتی نیز می‌شد انجام داد):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Script.Services;

namespace TestJQueryAjax
{
/// <summary>
/// Summary description for AjaxSrv
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public bool ValidateIt(int number1, int number2)
{
return number1 * number2 == 10;
}
}
}
در این مثال هنگام submit ، عملیات اعتبار سنجی با توجه به وضعیت asynchronous عملیات Ajax ، تمام و کمال رخ نداده ، یا اعتبار سنجی انجام نمی‌شود و یا پیغام خطایی را دریافت خواهیم کرد.

راه حل چیست؟
راه حل‌های فضایی بسیاری را در وب در این مورد می‌توان پیدا کرد؛ اما راه حل استاندارد آن در این حالت ویژه، استفاده از Ajax در حالت غیرهمزمان است. یعنی این فریم ورک Ajax را وادار کنیم که تا پایان عملیات مورد نظر، منتظر بماند و سپس فرم را ارسال کند. برای این منظور تنها کافی است یک سطر زیر را پیش از فراخوانی تابع Ajax ، اضافه و فراخوانی نمائیم:

$.ajaxSetup({async: false}) ;
نکته:
UseSubmitBehavior دکمه ما را به شکل زیر رندر می‌کند (دکمه به یک button معمولی (بجای حالت submit) تبدیل شده و سپس یک doPostBack را اضافه خواهد کرد):

<input id="btnSubmit" type="button" onclick="if(!validate()){ return false;};__doPostBack('btnSubmit','')" value="Submit" name="btnSubmit"/>


نظرات مطالب
ASP.NET MVC #12
سلام آقای نصیری
با تشکر از توضیحاتتون
من کد زیر رو که خودتون واسه شمسی کردن تاریخ گذاشته بودین، به نام datetime.cshtml و صورت PartialView داخل پوشه DisplayTemplates قرار دادم. اما همچنان DateTime‌ها رو به فرمت میلادی نمایش میده. میشه بگین اشکال کارم کجاست ؟
@using System.Globalization
@model Nullable<DateTime>
           
@helper ShamsiDateTime(DateTime info, string separator = "/", bool includeHourMinute = true)
{
    int ym = info.Year;
    int mm = info.Month;
    int dm = info.Day;
    var sss = new PersianCalendar();
    int ys = sss.GetYear(new DateTime(ym, mm, dm, new GregorianCalendar()));
    int ms = sss.GetMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));
    int ds = sss.GetDayOfMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));    
    if (includeHourMinute)
    {
        @(ys + separator + ms.ToString("00") + separator + ds.ToString("00") + " " + info.Hour + ":" + info.Minute)
    }
    else
    {
        @(ys + separator + ms.ToString("00") + separator + ds.ToString("00"))
    }
}

@if (@Model.HasValue)
{
  @ShamsiDateTime(@Model.Value , separator: "/", includeHourMinute: false)
}

مطالب
نمایش گرادیان در iTextSharp

کتابخانه iTextSharp نمایش گرادیانی از رنگ‌ها را هم پشتیبانی می‌کند و بدیهی است این نمایش برداری است. روش استفاده از آن هم بسیار ساده است؛ مثلا:

PdfShading shading = PdfShading.SimpleAxial(pdfWriter, x0, y0, x1, y1, BaseColor.YELLOW, BaseColor.RED);
PdfShadingPattern pattern = new PdfShadingPattern(shading);
ShadingColor color = new ShadingColor(pattern);

متد PdfShading.SimpleAxial بر اساس شیء PdfWriter که توسط آن به Canvas صفحه دسترسی پیدا می‌کند، در مختصاتی مشخص، یک طیف رنگی را ایجاد می‌کند. بر این اساس می‌توان به یک ShadingColor هم رسید که از آن مثلا به عنوان BackgroundColor یک PdfPCell قابل استفاده است.
تا اینجا ساده به نظر می‌رسد اما واقعیت این است که مختصات ذکر شده، مهم است و آن‌را در مورد مثلا سلول‌های یک جدول تنها در زمان تهیه نهایی یک جدول می‌توان به دست آورد. البته اگر شیء ساده‌ای را روی صفحه رسم کنیم، این مورد بر اساس مختصات ابتدایی شیء واضح به نظر می‌رسد.
برای حل این مشکل در مورد جداول و سلول‌های آن خاصیتی به نام CellEvent وجود دارد که از نوع IPdfPCellEvent است. به عبارتی با ارسال یک وهله از کلاسی که اینترفیس IPdfPCellEvent را پیاده سازی می‌کند، می‌توان در زمان نمایش نهایی یک سلول به مختصات دقیق آن دسترسی پیدا کرد.
یک مثال کامل را در مورد پیاده سازی IPdfPCellEvent و استفاده از آن جهت نمایش گرادیان در Header و footer یک جدول، در ادامه مشاهده خواهید نمود:

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

namespace iTextSharpGradientTest
{
public class GradientCellEvent : IPdfPCellEvent
{
public void CellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases)
{
var cb = canvases[PdfPTable.BACKGROUNDCANVAS];
cb.SaveState();

var shading = PdfShading.SimpleAxial(
cb.PdfWriter,
position.Left, position.Top, position.Left, position.Bottom,
BaseColor.YELLOW, BaseColor.ORANGE);
var shadingPattern = new PdfShadingPattern(shading);

cb.SetShadingFill(shadingPattern);
cb.Rectangle(position.Left, position.Bottom, position.Width, position.Height);
cb.Fill();

cb.RestoreState();
}
}

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));
pdfDoc.Open();

var table1 = new PdfPTable(1);
table1.HeaderRows = 2;
table1.FooterRows = 1;

//header row
var headerCell = new PdfPCell(new Phrase("header"))
{
HorizontalAlignment = Element.ALIGN_CENTER,
Border = 0
};
headerCell.CellEvent = new GradientCellEvent();
table1.AddCell(headerCell);

//footer row
var footerCell = new PdfPCell(new Phrase("footer"))
{
HorizontalAlignment = Element.ALIGN_CENTER,
Border = 0
};
footerCell.CellEvent = new GradientCellEvent();
table1.AddCell(footerCell);

//adding some rows
for (int i = 0; i < 70; i++)
{
var rowCell = new PdfPCell(new Phrase("Row " + i)) { BorderColor = BaseColor.LIGHT_GRAY };
table1.AddCell(rowCell);
}

pdfDoc.Add(table1);
}

//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}

با خروجی:


نکته مهم این مثال نحوه مقدار دهی CellEvent است. به این ترتیب در زمان نمایش نهایی یک سلول می‌توان در متد CellLayout، به خواص فقط خواندنی آن سلول دسترسی یافت. مثلا position، مختصات نهایی مستطیل مرتبط با سلول جاری را بر می‌گرداند؛ یا از طریق canvases می‌توان برای آخرین بار فرصت یافت تا در یک سلول نقاشی کرد.


مطالب
یکی کردن اسمبلی‌های ارجاعی یک برنامه WPF با فایل خروجی آن
ممکن است برای شما هم پیش آمده باشد که بخواهید پس از پابلیش برنامه‌ای که نوشته‌اید، تمامی فایل‌های اسمبلی استفاده شده در برنامه را نیز با فایل خروجی آن ادغام کنید و به اصلاح تنها یک فایل، برای اجرا داشته باشید. مایکروسافت ابزاری را به نام ILMerge، برای اینکار معرفی کرده است که به وسیله آن، امکان ادغام اسمبلی‌ها با فایل اصلی برنامه وجود دارد؛ بجز اسمبلی‌های مربوط به WPF، به خاطر داشتن فایل‌های XAML.
برای حل این مسئله می‌توان از دو راه استفاده کرد:
  • اضافه کردن اسمبلی‌ها به صورت دستی به پروژه و تنظیم Build Action آن‌ها به Embedded Resource
  • تنظیم فایل csproj پروژه برای Embed کردن خودکار رفرنس‌های پروژه در زمان Build


روش اول

بعد از این که ارجاع اسمبلی مورد نظر را به پروژه اضافه کردید، نیاز است مقدار Copy Local آن‌ها را نیز در پنجره Properties به False تغییر دهید و سپس با استفاده از گزینه Add -> Existing Item فایل اسمبلی مورد نظر را به پروژه اضافه کرده و مقدار Build Action را در پنجره Properties به Embedded Resource تغییر دهید.
نکته: در صورتی که فایل اسمبلی به صورت unmanaged / native داشتید و امکان افزودن ارجاعی به آن وجود نداشت، تنها کافیست آن را به صورت Embedded Resource اضافه کنید.
تا به اینجا کار ادغام اسمبلی‌ها با فایل خروجی برنامه با موفقیت انجام شد و به علت یکسان بودن کد مربوط به بارگذاری اسمبلی‌ها، بعد از روش دوم، توضیح داده خواهد شد.


روش دوم

در این روش باید فایل csproj و یا vbproj برنامه را در یک ادیتور باز کرده ( یا با استفاده از گزینه Unload Project و انتخاب گزینه Edit projectName.csproj ) و در قسمت انتهای فایل، قبل از تگ Project، این کد را اضافه می‌کنیم:
<Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
  <ItemGroup>
    <AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" />
    <EmbeddedResource Include="@(AssembliesToEmbed)" Condition="'%(AssembliesToEmbed.Extension)' == '.dll'">
      <LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
  <Message Importance="high" Text="Embedding: @(AssembliesToEmbed->'%(DestinationSubDirectory)%(Filename)%(Extension)', ', ')" />
</Target>
<Target Name="DeleteAllReferenceCopyLocalPaths" AfterTargets="Build">
  <Delete Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" />
</Target>
بعد از اضافه کردن این کد به فایل پروژه و بارگذاری مجدد پروژه، با اجرای برنامه یا Build کردن آن، در پوشه bin (پوشه خروجی برنامه) مشاهده می‌کنید که فایل‌های اسمبلی ارجاعی برنامه در این پوشه وجود ندارند و حجم فایل خروجی افزایش یافته است.

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


نحوه بارگذاری اسمبلی‌های Embed شده

در پروژه‌های WPF، در OnStartup event کلاس App و در پروژه‌های WinForm در متد Main کلاس Program، قطعه کد زیر را وارد می‌کنیم:

private void App_OnStartup( object sender, StartupEventArgs e )
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    var assembly = Assembly.GetExecutingAssembly();
    foreach (var name in assembly.GetManifestResourceNames())
    {
        if ( name.ToLower()
                 .EndsWith( ".resources" ) ||
             !name.ToLower()
                  .EndsWith( ".dll" ) )
            continue;
        EmbeddedAssembly.Load( name,
                               name );
    }
}

static Assembly OnResolveAssembly( object sender, ResolveEventArgs args )
{
    var fields = args.Name.Split( ',' );
    var name = fields[0];
    var culture = fields[2];
    if ( name.EndsWith( ".resources" ) &&
         !culture.EndsWith( "neutral" ) )
        return null;

    return EmbeddedAssembly.Get( args.Name );
}

با استفاده از رویداد AssemblyResolve می توان اسمبلی Embed شده را در زمانیکه نیاز به آن است، بارگذاری کرد. کد مربوط به کلاس EmbeddedAssembly نیز به این صورت می‌باشد:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;

public static class EmbeddedAssembly
{
    static Dictionary< string, Assembly > _dic;

    public static void Load( string embeddedResource,
                                string fileName )
    {
        if ( _dic == null )
            _dic = new Dictionary< string, Assembly >();

        byte[] ba;
        Assembly asm;
        var curAsm = Assembly.GetExecutingAssembly();

        using ( var stm = curAsm.GetManifestResourceStream( embeddedResource ) )
        {
            if ( stm == null )
                return;

            ba = new byte[(int)stm.Length];
            stm.Read( ba,
                      0,
                      (int)stm.Length );
            try
            {
                asm = Assembly.Load( ba );

                _dic.Add( asm.GetName().Name,
                            asm );
                return;
            }
            catch
            {
            }
        }

        bool fileOk;
        string tempFile;

        using ( var sha1 = new SHA1CryptoServiceProvider() )
        {
            var fileHash = BitConverter.ToString( sha1.ComputeHash( ba ) )
                                        .Replace( "-",
                                                    string.Empty );

            tempFile = Path.GetTempPath() + fileName;

            if ( File.Exists( tempFile ) )
            {
                var bb = File.ReadAllBytes( tempFile );
                var fileHash2 = BitConverter.ToString( sha1.ComputeHash( bb ) )
                                            .Replace( "-",
                                                        string.Empty );

                fileOk = fileHash == fileHash2;
            }
            else
            {
                fileOk = false;
            }
        }

        if ( !fileOk )
        {
            File.WriteAllBytes( tempFile,
                                ba );
        }

        asm = Assembly.LoadFile( tempFile );

        _dic.Add( asm.GetName().Name,
                    asm );
    }

    public static Assembly Get( string assemblyFullName )
    {
        if ( _dic == null ||
                _dic.Count == 0 )
            return null;

        var name = new AssemblyName( assemblyFullName ).Name;
        return _dic.ContainsKey( name )
            ? _dic[name]
            : null;
    }
}

با استفاده از متد Load کلاس بالا، کل اسمبلی‌هایی که بارگذاری شده‌اند در یک دیکشنری استاتیک نگهداری می‌شوند. ابتدا اسمبلی‌ها را با استفاده از []byte بارگذاری می‌کنیم و در صورتیکه بارگذاری اسمبلی با خطایی مواجه شود، بارگذاری را با استفاده از فایل temp انجام می‌دهیم (که معمولا برای فایل‌های unmanaged این مورد اتفاق می‌افتد).

با استفاده از متد Get که در زمان نیاز به یک اسمبلی توسط AssemblyResolve فراخوانی می‌شود، اسمبلی مربوطه از دیکشنری پیدا شده و برگشت داده می‌شود.


نکته ها

  • در صورتیکه بخواهید فایلی را از Embed کردن خودکار (روش دوم) استثناء کنید، باید از Condition استفاده کنید:
  <Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
    <ItemGroup>
      <AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" />
      <EmbeddedResource Include="@(AssembliesToEmbed)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(AssembliesToEmbed.Filename)', '^((?!Microsoft).)*$')) And '%(AssembliesToEmbed.Extension)' == '.dll'">
        <LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
    <Message Importance="high" Text="Embedding: @(AssembliesToEmbed->'%(DestinationSubDirectory)%(Filename)%(Extension)', ', ')" />
  </Target>
  <Target Name="DeleteAllReferenceCopyLocalPaths" AfterTargets="Build">
    <Delete Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Filename)', '^((?!Microsoft).)*$')) Or '%(Extension)' == '.xml'" />
  </Target>

برای نمونه در اینجا با استفاده از Regex، تمامی فایل‌هایی که شروع نام آنها با Microsoft است، استثناء شده‌اند. فقط توجه داشته باشید در صورتیکه شرطی را برای Embed کردن تعریف می‌کنید، حتما در هر دو قسمت، شرط را وارد کنید.
  • در صورتیکه بعد از اجرای برنامه و یا اجرای به صورت دیباگ با خطای Stackoverflow مواجه شدید که به خاطر ارجاعات زیاد Resource‌های برنامه پیش می‌آید، کد زیر را به فایل AssemblyInfo، در پوشه Properties اضافه کنید:
[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.MainAssembly)]


  • در صورتیکه پروژه شما از نوع Office Add-Ins باشد، باید در کد مربوط به AssemblyResolve را در فایل ThisAddIn.Designer.cs (در صورت عدم تغییر نام) به متد Initialize اضافه کنید و دستور بارگذاری را در متد ThisAddIn_Startup اضافه کنید. نکته خیلی مهم:  در فایل csproj حتما در قسمت Condition باید اسمبلی‌هایی را که با نام Microsoft شروع می‌شوند، از Embed شدن استثناء کنید و در قسمت DeleteAllReferenceCopyLocalPaths مقدار "AfterTargets="VisualStudioForApplicationsBuild را قرار دهید (تا امکان Build پروژه برای شما باشد) و همچنین پسوند vsto را نیز نباید حذف کنید.

مطالب
ایجاد شناسه ی منحصر به فرد برای هر سیستم
چند روز پیش یک افزونه در nuget نظرم رو به خودش جلب کرد . بعد از دانلود و نصب اون و مقداری کار کردن باهاش جای خودش رو تو دلم باز کرد ولی متاسفانه این افزونه تا 21 روز رایگان بود. توی نت برای پیدا کردن سریال و یا کرکش زیاد گشتم ولی هیچ چیز یافت نشد . شاید به خاطر اینکه از زمان تولیدش زیاد نمیگذره ... در هر حال گذشتن از خیرش برام سخت بود بنابر این به یاد قدیم تصمیم گرفتم خودم دست به کار بشم و release کنمش ... 
بعد از deobfuscate  کردنش سیستم امنیتیش نکات خیلی جالبی داشت که یکیش ایجاد شناسه‌ی منحصر به فرد برای هر سیستم بود . البته شاید باورش سخت باشه ولی برای بخش امنیتش 23 تا کلاس داشت که هر کدوم کلی تابع و ... داشتن که همه هم به هم مرتبط بود . تا به حال ندیده بودم توی هیچ افزونه ای اینقدر روی امنیتش کار بشه و خب همینم باعث شد 5-4 ساعت وقتمو بگیره ...

کد زیر رو از یکی از کلاس هاش استخراج کردم که توسط اون میتونید یک شناسه منحصر به فرد بر اساس مشخصات پردازنده - برد اصلی و ... تولید کنید . فقط در هنگام استفاده توجه داشته باشید به ارجاع‌های برنامه و اینکه باید System.Management رو به reference‌های برنامه اضافه بکنید حتما ...

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Win32;
using System.Management;

namespace ConsoleApplication1
{

    class Program
    {
        private static string GetSystemCode_int1(byte[] byte_0)
        {
            if (byte_0 != null)
            {
                return Convert.ToBase64String((new MD5CryptoServiceProvider()).ComputeHash(byte_0));
            }
            else
            {
                return string.Empty;
            }
        }

        private static string GetSystemCode_int0(string string_2)
        {
            if (!string.IsNullOrEmpty(string_2))
            {
                return GetSystemCode_int1(Encoding.UTF8.GetBytes(string_2));
            }
            else
            {
                return string.Empty;
            }
        }

        public static string GetSystemCode()
        {
            string key = null;
            if (key == null)
            {
                string empty = string.Empty;
                try
                {
                    ManagementClass managementClass = new ManagementClass("win32_processor");
                    ManagementObjectCollection instances = managementClass.GetInstances();
                    foreach (ManagementBaseObject instance in instances)
                    {
                        try
                        {
                            empty = string.Concat(empty, instance.Properties["processorID"].Value.ToString());
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
                catch
                {
                    try
                    {
                        ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
                        foreach (ManagementBaseObject managementBaseObject in managementObjectSearcher.Get())
                        {
                            empty = string.Concat(empty, managementBaseObject["SerialNumber"].ToString().Trim());
                        }
                    }
                    catch
                    {
                    }
                }
                try
                {
                    ManagementObject managementObject = new ManagementObject("win32_logicaldisk.deviceid=\"C:\"");
                    managementObject.Get();
                    empty = string.Concat(empty, managementObject["VolumeSerialNumber"].ToString());
                }
                catch
                {
                }
                if (string.IsNullOrWhiteSpace(empty))
                {
                    empty = Environment.MachineName;
                }
                key = GetSystemCode_int0(empty);
            }
            return key;
        }

        static void Main(string[] args)
        {
            Console.WriteLine(GetSystemCode());
            Console.ReadKey();
        }
    }
}
مطالب
استفاده از OfType یا Cast در Linq
تقریبا تمام توسعه دهندگان دات نت با تکنولوژی Linq  و Lambda Expression‌ها آشنایی دارند. همان طور که می‌دانیم Extension Method‌های موجود در فضای نام System.Linq فقط بر روی مجموعه ای از داده‌ها که اینترفیس IEnumerable<t> که در فضای نام System.Collections.Generic قرار دارد را پیاده سازی کرده باشند قابل اجرا هستند. مجموعه داده‌های جنریک فقط قابلیت نگهداری از یک نوع داده که به عنوان پارامتر T برای این مجموعه تعریف می‌شود را داراست.
نکته: البته در مجموعه هایی نظیر Dictionary یا سایر Collection‌ها امکان تعریف چند نوع داده به عنوان پارامتر وجود دارد. نکته مهم این است که داده‌های استفاده شده در این مجموعه ها، حتما باید از نوع پارامتر تعریف شده باشند.
اگر در یک مجموعه داده قصد داشته باشیم که داده هایی با نوع مختلف را ذخیره کنیم و در جای مناسب آن‌ها را بازیابی کرده و در برنامه استفاده نماییم چه باید کرد. به عنوان یک پیشنهاد می‌توان از مجموعه‌های موجود در فضای نام System.Collection بهره بگیریم. اما همان طور که واضح است این مجموعه از  داده‌ها به صورت جنریک نمی‌باشند و امکان استفاده از Query‌های Linq در آن‌ها به صورت معمول امکان پذیر نیست. برای حل این مشکل در دات نت دو متد تعبیه شده است که وظیفه آن تبدیل این مجموعه از داده‌ها به مجموعه ای است که بتوان بر روی آن‌ها Query‌های از جنس Linq یا Lambda Expression را اجرا کرد.
  • Cast
  • OfType
#مثال 1
فرض کنید یک مجموعه مثل زیر داریم:
 ArrayList myList = new ArrayList();

  myList.Add( "Value1" );
  myList.Add( "Value2" );
  myList.Add( "Value3" );

  var myCollection = myList.Cast<string>();
در مثال بالا یک Collection از نوع ArrayList ایجاد کردیم که در فضای نام System.Collection قرار دارد. شما در این مجموعه می‌توانید از هر نوع داده ای که مد نظرتان است استفاده کنید. با استفاده از اپراتور Cast توانستیم این مجموعه را به نوع مورد نظر خودمان تبدیل کنیم و در نهایت به یک مجموعه از IEnumerable<T> برسیم. حال امکان استفاده از تمام متد‌های Linq امکان پذیر است.
#مثال دوم:
   ArrayList myList = new ArrayList();

    myList.Add( "Value1" );
    myList.Add( 10 );
    myList.Add( 10.2 );

   var myCollection = myList.Cast<string>();
در مثال بالا در خط آخر با یک runtime Error مواجه خواهیم شد. دلیلش هم این است که ما از در ArrayList خود داده‌های غیر از string نظیر int یا double داریم. درنتیجه هنگام تبدیل داده‌های int یا double به string یک Exception رخ خواهد داد. در این گونه موارد که در لیست مورد نظر داده‌های غیر هم نوع وجود دارد باید متد OfType را جایگزین کنیم.
ArrayList myList = new ArrayList();

myList.Add( "Value1" );
myList.Add( 10 );
myList.Add( 10.2 );
           
 var doubleNumber = myList.OfType<double>().Single();
 var integerNumber = myList.OfType<int>().Single();
 var stringValue = myList.OfType<string>().Single();
تفاوت بین متد Cast و OfType در این است که متد Cast سعی دارد تمام داده‌های موجود در مجموعه را به نوع مورد نظر تبدیل کند ولی متد OfType فقط داده‌های از نوع مشخص شده را برگشت خواهد داد. حتی اگر هیچ آیتمی از نوع مورد نظر در این مجموعه نباشد یک مجموعه بدون هیچ داده ای برگشت داده می‌شود.
مطالب دوره‌ها
توابع(Function)
برنامه نویسی تابع گرا در یک جمله یعنی نوشتن توابع در پروژه و فراخوانی آن‌ها به همراه مقدار دهی به آرگومان‌های متناظر و دریافت خروجی در صورت نیاز. در #F پارامتر‌های یک تابع با پرانتز یا کاما از هم تمیز داده نمی‌شوند بلکه باید فقط از یک فضای خالی بین آن‌ها استفاده کنید.(البته می‌تونید برای خوانایی بهتر از پرانتز استفاده کنید)
let add x y = x + y
let result = add 4 5
printfn "(add 4 5) = %i" result
همان طور که می‌بینید تابعی به نام add داریم که دارای 2 پارامتر ورودی است به نام‌های x , y که فقط توسط یک فضای خالی از هم جدا شدند. حال به مثال دیگر توجه کنید.
let add x y = x + y
let result1 = add 4 5
let result2 = add 6 7
let finalResult = add result1 result2
در مثال بالا همان تابع add 2 بار فراخوانی شده است که یک بار مقدار خروجی تابع در یک شناسه به نام result1 و یک بار مقدار خروجی با مقادیر متفاوت در شناسه به نام result2 قرار گرفت. شناسه finalResult حاصل فراخوانی تابع add با مقادیر result1 , result2 است. می‌تونیم کد بالا رو به روش مناسب‌تری باز نویسی کنیم.
let add x y = x + y
let result =add (add 4 5) (add 6 7)
در اینجا برای خوانایی بهتر کد از پرانتز برای جداسازی مقدار پارامتر‌ها استفاده کردم.

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

let cylinderVolume radius length : float =
   
   let pi = 3.14159
   length * pi * radius * radius
در مثال بالا خروجی تابع مقدار ( length * pi * radius * radius ) است و نو ع آن float می‌باشد.
یک مثال دیگر:
let sign num =
    if num > 0 then "positive"
    elif num < 0 then "negative"
    else "zero"
خروجی تابع بالا از نوع string است و مقدار آن با توجه به ورودی تابع positive یا negative یا zero خواهد بود.

تعریف پارامترهای تابع با ذکر نوع به صورت صریح
اگر هنگام تعریف توابع مایل باشید که نوع پارامترها را به صورت صریح تعیین کنید از روش زیر استفاده می‌کنیم.
let replace(str: string) =
    str.Replace("A", "a")
تعریف تابع به همراه دو پارامتر و ذکر نوع فقط برای یکی از پارامتر‌ها :
let addu1 (x : uint32) y =
    x + y

Pipe-Forward Operator
در #F روشی دیگری برای تعریف توابع وجود دارد که به pipe-Forward معروف است. فقط کافیست از اپراتور (<|) به صورت زیر استفاده کنید.
let (|>) x f = f x
کد بالا به این معنی است که تابعی  یک پارامتر ورودی به نام x دارد و این پارامتر رو به تابع مورد نظر(هر تابعی که شما هنگام استفاده تعیین کنید) تحویل می‌دهد و خروجی را بر می‌گرداند. برای مثال
let result = 0.5 |> System.Math.Cos
یا
let add x y = x + y
let result = add 6 7 |> add 4 |> add 5
در مثال بالا ابتدا حاصل جمع 7 و 6 محاسبه می‌شود و نتیجه با 4 جمع می‌شود و دوباره نتیجه با 5 جمع می‌شود تا حاصل نهایی در result قرار گیرد. به نظر اکثر برنامه نویسان #F این روش نسبت به روش‌های قبلی خواناتر است. این روش همچنین مزایای دیگری نیز دارد که در مبحث Partial Function‌ها بحث خواهیم کرد.

Partial Fucntion Or Application
partial function به این معنی است که در هنگام فراخوانی یک تابع نیاز نیست که به تمام آرگومان‌های مورد نیاز مقدار اختصاص دهیم. برای نمونه در مثال بالا تابع add نیاز به 2 آرگومان ورودی داشت در حالی که فقط یک مقدار به آن پاس داده شد.
let result = add 6 7 |> add 4
دلیل برخورد #F با این مسئله این است که #F توابع رو به شکل مقدار در نظر می‌گیرد و اگر تمام مقادیر مورد نیاز یک تابع در هنگام فراخوانی تحویل داده نشود، از مقدار برگشت داده شده فراخوانی تابع قبلی استفاده خواهد کرد. البته این مورد همیشه خوشایند نیست. اما می‌تونیم با استفاده از پرانتز ر هنگام تعریف توابع مشخص کنیم که دقیقا نیاز به چند تا مقدار ورودی برای توابع داریم.
let sub (a, b) = a - b
let subFour = sub 4
کد بالا کامپایل نخواهد شد و خطای زیر رو مشاهده خواهید کرد.
prog.fs(15,19): error: FS0001: This expression has type
int
but is here used with type
'a * 'b
توابع بازگشتی
در مورد ماهیت توابع بازگشتی نیاز به توضیح نیست فقط در مورد نوع پیاده سازی اون در #F توضیح خواهم داد. برای تعریف توابع به صورت بازگشتی کافیست از کلمه rec بعد از let استفاده کنیم(زمانی که قصد فراخوانی تابع رو در خود تابع داشته باشیم). مثال پایین به خوبی مسئله را روشن خواهد کرد.(پیاده سازی تابع فیبو ناچی)
let rec fib x =
match x with
| 1 -> 1
| 2 -> 1
| x -> fib (x - 1) + fib (x - 2)
printfn "(fib 2) = %i" (fib 2) printfn "(fib 6) = %i" (fib 6) printfn "(fib 11) = %i" (fib 11) 
*درباره الگوی Matching در فصل بعد به صورت کامل توضیح خواهم داد.
خروجی برای مثال بالا به صورت خواهد شد.
(fib 2) = 1
(fib 6) = 8
(fib 11) = 89
توابع بازگشتی دو طرفه
گاهی اوقات توابع به صورت دوطرفه بازگشتی می‌شوند. یعنی فراخوانی توابع به صورت چرخشی انجام می‌شود. (فراخوانی یک تابع در تابع دیگر و بالعکس). به مثال زیر دقت کنید.
let rec Even x =
   if x = 0 then true 
   else Odd (x - 1)
and Odd x =
   if x = 1 then true 
   else Even (x - 1)
کاملا واضح است در تابع Even فراخوانی تابع Odd انجام می‌شود و در تابع Odd فراخوانی تابع Even. به این حالت mutual recursive می‌گویند.
ترکیب توابع
let firstFunction x = x + 1
let secondFunction x = x * 2
let newFunction = firstFunction >> secondFunction
let result = newFunction 100
در مثال بالا دو تابع به نام‌های firstFunction  و secondFunction داریم. بااستفاده از (<<) دو تابع را با هم ترکیب می‌کنیم. خروجی بدین صورت محاسبه می‌شود که ابتدا تابع firstFucntion مقدار x را محاسبه می‌کند و حاصل به تابع secondFucntion پاس داده می‌شود. در نهایت یک تابع جدید به نام newFunction خواهیم داشت که مقدار نهایی محاسبه خواهد شد. خروجی مثال بالا 202 است.

توابع تودرتو
در #F امکان تعریف توابع تودرتو وجود دارد. بعنی می‌تونیم یک تابع را در یک تابع دیگر تعریف کنیم. فقط نکته مهم در امر استفاده از توابع به این شکل این است که توابع تودرتو فقط در همون تابعی که تعریف می‌شوند قایل استفاده هستند و محدوده این توابع در خود همون تابع است.
let sumOfDivisors n =
    let rec loop current max acc =
//شروع تابع داخلی
 if current > max then acc else if n % current = 0 then loop (current + 1) max (acc + current) else loop (current + 1) max acc
//ادامه بدنه تابع اصلی
 let start = 2 let max = n / 2 (* largest factor, apart from n, cannot be > n / 2 *) let minSum = 1 + n (* 1 and n are already factors of n *) loop start max minSum printfn "%d" (sumOfDivisors 10)
در مثال بالا یک تابع تعریف کرده ایم به نام sumOfDivisors. در داخل این تابع یک تابع دیگر به نام loop داریم که از نوع بازگشتی است(به دلیل وجود rec بعد از let). بدنه تابع داخلی به صورت زیر است:
  if current > max then
            acc
        else
            if n % current = 0 then
                loop (current + 1) max (acc + current)
            else
                loop (current + 1) max acc
خروجی مثال بالا برای ورودی 10 عدد 18 می‌باشد. مجموع مقصوم علیه‌های عدد 10 (1 + 2 + 5 + 10 ).

آیا می‌توان توابع را Overload کرد؟
در #F امکان overloading برای یک تابع وجود ندارد. ولی متدها را می‌توان overload  کرد.(متد‌ها در فصل شی گرایی توضیح داده می‌شود).

do keyword
زمانی که قصد اجرای یک کد را بدون تعریف یک تابع داشته باشیم باید از do  استفاده کنیم. همچنین از do در انجام برخی عملیات پیش فرض در کلاس‌ها زیاد استفاده می‌کنیم.(در فصل شی گرایی با این مورد آشنا خواهید شد).
open System
open System.Windows.Forms

let form1 = new Form()
form1.Text <- "XYZ"

[<STAThread>]
do
   Application.Run(form1)

نظرات مطالب
روش های مختلف پردازش یک رشته و تبدیل آن به نوع داده تاریخ
یک نکته
اگر مثال زیر را برای تبدیل تاریخ شمسی 1393/02/31 یا 1393/04/31، به کمک متد DateTime.ParseExact اجرا کنید، با استثنایی متوقف خواهید شد:
var persianCulture = new CultureInfo("fa-IR");
var persianDateTime = DateTime.ParseExact("31/02/1393", "dd/MM/yyyy", persianCulture);
علت اینجا است که persianCulture.Calendar به GregorianCalendar تنظیم شده‌است (از ابتدا و به اشتباه) و قابل تغییر نیست (مگر با Reflection). راه حل جایگزین، چیزی شبیه به این خواهد بود:
   using System;
   using System.Globalization;
   using System.Linq;

    public static DateTime PersianDateToGregorianDate(string pDate)
    {
        var dateParts = pDate.Split(new[] { '/' }).Select(d => int.Parse(d)).ToArray();
        var hour = 0;
        var min = 0;
        var seconds = 0;
        return new DateTime(dateParts[0], dateParts[1], dateParts[2],
                            hour, min, seconds, new PersianCalendar());
    }
اشتراک‌ها
ReSharper 2022.1 منتشر شد
  • We have two new refactorings for global usings, Extract Global Using and Inline Global Using, with Find Usages support for this feature.
  • For nullable reference types, we’ve split the “should never be null” warning into two categories: one for those who look at NRT annotation and one for those who have a runtime check for null. We’ve also added a setting to enable runtime enforced not null warnings only, and supported [MemberNotNull] and [MemberNotNullWhen] annotations.
  • We’ve implemented generic attributes support for C#11. 
ReSharper 2022.1 منتشر شد