مطالب
تهیه‌ی گزارش از آخرین ارائه‌های یک پروژه در GitHub
چندی قبل مطلب «اطلاع از بروز رسانی نرم افزار ساخته شده» را در سایت جاری مطالعه کردید. در این روش بسیار متداول، شماره نگارش‌های جدید برنامه در یک فایل XML و مانند آن قرار می‌گیرند و برنامه هربار این فایل را جهت یافتن شماره‌های مندرج در آن اسکن می‌کند. اگر پروژه‌ی شما سورس باز است و در GitHub هاست شده، روش دیگری نیز برای یافتن این اطلاعات وجود دارد. در GitHub می‌توان از طریق آدرسی به شکل https://api.github.com/repos/user_name/project_name/releases به اطلاعات آخرین ارائه‌های یک پروژه (قرار گرفته در برگه‌ی releases آن) با فرمت JSON دسترسی یافت (یک مثال). در ادامه قصد داریم روش استفاده‌ی از آن‌را بررسی کنیم.


ساختار JSON ارائه‌های یک پروژه در GitHub

ساختار کلی اطلاعات ارائه‌های یک پروژه در GitHub چنین شکلی را دارد:
[
  {
    release_info,
    "assets": [
      {
      }
    ]
  }
]
در اینجا آرایه‌ای از اطلاعات ارائه‌ی یک پروژه ارسال می‌شود. هر ارائه نیز دارای دو قسمت است: لینکی به صفحه‌ی اصلی release در GitHub و سپس آرایه‌ای به نام assets که در آن اطلاعات فایل‌های پیوستی مانند نام فایل، آدرس، اندازه و امثال آن قرار گرفته‌اند.



تهیه‌ی کلاس‌های معادل فرمت JSON ارائه‌های برنامه در GitHub

اگر بخواهیم قسمت‌های مهم خروجی JSON فوق را تبدیل به کلاس‌های معادل دات نتی کنیم، به دو کلاس ذیل خواهیم رسید:
using Newtonsoft.Json;
using System;

namespace ApplicationAnnouncements
{
    public class GitHubProjectRelease
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }

        [JsonProperty(PropertyName = "assets_url")]
        public string AssetsUrl { get; set; }

        [JsonProperty(PropertyName = "upload_url")]
        public string UploadUrl { get; set; }

        [JsonProperty(PropertyName = "html_url")]
        public string HtmlUrl { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "tag_name")]
        public string TagName { get; set; }

        [JsonProperty(PropertyName = "target_commitish")]
        public string TargetCommitish { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "body")]
        public string Body { get; set; }

        [JsonProperty(PropertyName = "draft")]
        public bool Draft { get; set; }

        [JsonProperty(PropertyName = "prerelease")]
        public bool PreRelease { get; set; }

        [JsonProperty(PropertyName = "created_at")]
        public DateTime CreatedAt { get; set; }

        [JsonProperty(PropertyName = "published_at")]
        public DateTime PublishedAt { get; set; }

        [JsonProperty(PropertyName = "assets")]
        public Asset[] Assets { get; set; }
    }

    public class Asset
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "label")]
        public string Label { get; set; }

        [JsonProperty(PropertyName = "content_type")]
        public string ContentType { get; set; }

        [JsonProperty(PropertyName = "state")]
        public string State { get; set; }

        [JsonProperty(PropertyName = "size")]
        public int Size { get; set; }

        [JsonProperty(PropertyName = "download_count")]
        public int DownloadCount { get; set; }

        [JsonProperty(PropertyName = "created_at")]
        public DateTime CreatedAt { get; set; }

        [JsonProperty(PropertyName = "updated_at")]
        public DateTime UpdatedAt { get; set; }
    }
}
در اینجا از ویژگی JsonProperty جهت معرفی نام‌های واقعی خواص ارائه شده‌ی توسط GitHub استفاده کرده‌ایم.
پس از تشکیل این کلاس‌ها، مرحله‌ی بعد، دریافت اطلاعات JSON از آدرس API ارائه‌های پروژه در GitHub و سپس نگاشت آن‌ها می‌باشد:
using (var webClient = new WebClient())
{
    webClient.Headers.Add("user-agent", "DNTProfiler");
    var jsonData = webClient.DownloadString(url);
    var gitHubProjectReleases = JsonConvert.DeserializeObject<GitHubProjectRelease[]>(jsonData);

    foreach (var release in gitHubProjectReleases)
    {
       foreach (var asset in release.Assets)
       {
           // …
       }
    }
}
حین کار با WebClient یا هر روش دیگری که برای دریافت اطلاعات از وب استفاده می‌کنید، حتما نیاز است user-agent را ذکر کرد. در غیر اینصورت GitHub درخواست شما را برگشت خواهد زد. پس از دریافت اطلاعات JSON، با استفاده از متد JsonConvert.DeserializeObject کتابخانه‌ی JSON.NET، می‌توان آن‌ها را تبدیل به آرایه‌ای از GitHubProjectRelease کرد.

یک نکته: اگر به صفحه‌ی اصلی ارائه‌های یک پروژه در GitHub دقت کنید، شماره‌ی تعداد بار دریافت یک ارائه مشخص نشده‌است. در این API، عدد DownloadCount، بیانگر تعداد بار دریافت پروژه‌ی شما است.
مطالب
ایده‌ی ثبت خودکار سرویس‌ها، به همراه تنظیمات؛ بدون نوشتن هیچ کدی در ConfigureServices با روش Installer
خودکارسازی، در قسمت‌های مختلف یک پروژه می‌تواند انجام شود. نمونه‌های مختلف این خودکارسازی‌ها که اکثرا توسط رفلکشن انجام می‌شوند شامل نگاشت خودکار Dto به Entity و بالعکس (توسط AutoMapper)، ثبت خودکار تمام Entityها در DbContext بدون نیاز به ثبت تک تک آن‌ها به صورت  public DbSet<Person> People { get; set; }  (که در این روش خودکار، اسم جداول می‌تواند به صورت جمع ثبت شود)، ثبت خودکار EntityTypeConfigurationها، ثبت خودکار کلیه‌ی کلاس‌های Profile برای کانفیگ AutoMapper و رجیستر خودکار DI سرویس‌ها، تا نیازی به نوشتن کدهای تکراری و مشابه   ;()<services.AddTransient<IUserService, UserService نداشته باشیم. 
 برای مشاهده‌ی عملی پیاده‌سازی این نمونه‌ها می‌توانید به پروژه‌ی ASP.NET Core WebAPI مراجعه کنید. در این مقاله می‌خواهیم همین سناریو را برای ثبت سرویس‌هایمان در متد ConfigureServices انجام دهیم، تا نیازی به نوشتن هیچ کدی برای آن‌ها نداشته باشیم. 

ثبت سرویس‌های مختلف، به همراه تنظیمات آن‌ها (مانند Authentication، Swagger، DbContext، ApiVersioning و ...) در استارتاپ می‌تواند به چندین صورت انجام شود.
روش اول اینکه به صورت دستی تمام کدهای مربوط به رجیستر کردن سرویس‌ها و تنظیمات آن‌ها، در متد ConfigureServices نوشته شود که خیلی جالب نیست و موجب شلوغ شدن سریع این متد می‌شود. نمونه‌ی این شیوه را برای ثبت سرویس مربوط به DbContext می‌بینیم:
public void ConfigureServices(IServiceCollection services) {
      // DbContext Service
      services.AddDbContext<AppDbContext>(options =>
            {
                options
             .UseSqlServer(appSettings.ConnectionStrings.MyConnectionString, sqlServerOptionsBuilder =>
                {     sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(1).TotalSeconds); //Default is 30 seconds
                    sqlServerOptionsBuilder.EnableRetryOnFailure();
                    sqlServerOptionsBuilder.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
                })
                    //Tips
                    .ConfigureWarnings(warning => warning.Throw(RelationalEventId
                        .QueryPossibleExceptionWithAggregateOperatorWarning));

                // Activate EF Second Level Cache
                options.AddInterceptors(new SecondLevelCacheInterceptor());
            });

      // register other services ....

}


راه دوم روش استفاده از متدهای الحاقی است؛ طوریکه برای هر سرویس، یک متد الحاقی را تعریف کنیم و از آن، در این متد استفاده کنیم که حجم کدها را تا حد زیادی کم می‌کند. برای مثال ثبت سرویس بالا را می‌توانیم در کلاس دیگری با نام DbContextServiceCollectionExtensions.cs ثبت کنیم:
public static class DbContextServiceCollectionExtensions
    {
        public static void AddDbContext(this IServiceCollection services)
        {
              services.AddDbContext<AppDbContext>(options =>
            {
                options
                    .UseSqlServer(appSettings.ConnectionStrings.MyConnectionString, sqlServerOptionsBuilder =>
                {
                    sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(1).TotalSeconds); //Default is 30 seconds
                    sqlServerOptionsBuilder.EnableRetryOnFailure();
                    sqlServerOptionsBuilder.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
                })
                    //Tips
                    .ConfigureWarnings(warning => warning.Throw(RelationalEventId
                        .QueryPossibleExceptionWithAggregateOperatorWarning));

                // Activate EF Second Level Cache
                options.AddInterceptors(new SecondLevelCacheInterceptor());
            });
        }
    }
و سپس در متد ConfigureServices می‌توان آن را به صورت زیر استفاده کرد:
public void ConfigureServices(IServiceCollection services) {
// Add DbContext
 services.AddDbContext();

//.... Register other services
}

ولی اگه پروژه‌ی ما متوسط به بالا باشد، کم‌کم تعداد سرویس‌های ما زیاد می‌شود (برای مثال چند نمونه از سرویس‌های رایج مورد استفاده، شامل سرویس‌های لاگ خطاها مثل Elmah و سرویس HttpClientFactory و AutoRegisterDi (توضیح در ادامه مقاله) و AutoMapper و Cache و EFSecondLevelCache و Hangfire و ....) می‌بینیم که تعداد این سرویس‌ها هم زیاد است و حتی به صورت اکستنشن هم به مرور زمان باعث شلوغ شدن استارتاپ می‌شوند. ضمن اینکه یک کار تکراری است که باید هر بار انجام شود.

راه سوم ثبت سرویس، استفاده از یک اینترفیس به نام IServiceInstaller و استفاده از آن در کلاس‌های مختلف مربوط به ثبت سرویس و بعد خواندن خودکار این تنظیمات با یک خط کد ساده‌ی رفلکشن است که در ادامه می‌بینیم: 
اینترفیس IServiceInstaller را تعریف می‌کنیم: 
public interface IServiceInstaller
    {
        void InstallServices(IServiceCollection services, AppSettings appSettings, Assembly startupProjectAssembly);
    }
توضیح: پارامتر appSettings کلاسی شامل کلیه‌ی مقادیر فایل appsettings.json است. شما می‌توانید بجای آن از IConfiguration استفاده کنید و مقدار آن را در Startup پاس دهید. پارامتر آخر برای حالتی است که این فایل‌ها را در لایه‌ی دیگری به غیر از لایه‌ی اصلی Api (مثل لایه‌ی WebFamewrk) پیاده سازی می‌کنید.
سپس کلاس‌های ثبت سرویس‌هایمان را با ارث بری از این اینترفیس می‌سازیم. برای نمونه رجیستر DbContext را با ایجاد کلاسی به نام DbContextInstaller انجام می‌دهیم:
public class DbContextInstaller : IServiceInstaller
    {
        public void InstallServices(IServiceCollection services, AppSettings appSettings, Assembly startupProjectAssembly)
        {
            services.AddDbContext<AppDbContext>(options =>
            {
                options
               .UseSqlServer(appSettings.ConnectionStrings.MyConnectionString, sqlServerOptionsBuilder =>
                {
                    sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(1).TotalSeconds); //Default is 30 seconds
                    sqlServerOptionsBuilder.EnableRetryOnFailure();
                    sqlServerOptionsBuilder.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName);
                })
                    //Tips
                    .ConfigureWarnings(warning => warning.Throw(RelationalEventId
                        .QueryPossibleExceptionWithAggregateOperatorWarning));

                // Activate EF Second Level Cache
                options.AddInterceptors(new SecondLevelCacheInterceptor());
            });
        }
    }

حالا برای ثبت این کلاس و کلاس‌های مشابه Installer، می‌آییم یک متد الحاقی را برای متد ConfigureServices می‌نویسیم که در آن از رفلکشن استفاده می‌کنیم: 
public static class ServiceInstallerExtensions
    {
        public static void InstallServicesInAssemblies(this IServiceCollection services, AppSettings appSettings)
        {
            var startupProjectAssembly = Assembly.GetCallingAssembly();
            var assemblies = new[] { startupProjectAssembly, Assembly.GetExecutingAssembly() };
            var installers = assemblies.SelectMany(a => a.GetExportedTypes())
                .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic && typeof(IServiceInstaller).IsAssignableFrom(c))
                .Select(Activator.CreateInstance).Cast<IServiceInstaller>().ToList();
            installers.ForEach(i => i.InstallServices(services, appSettings, startupProjectAssembly));
        }
    }

در نهایت متد ConfigureServices ما به صورت زیر خواهد بود (بعد از اضافه کردن تمام سرویس‌ها!):
public void ConfigureServices(IServiceCollection services)
        {
            //* HttpContextAccessor
            // services.AddHttpContextAccessor();

            //* Controllers
            services.AddControllers(options => { options.Filters.Add(new AuthorizeFilter()); })
                .AddNewtonsoftJson();

            //* Installers
            services.InstallServicesInAssemblies(_appSettings);
        }
کار تمام شد. حالا تمام سرویس‌های ما با ایجاد کلاس مرتبط و implement شدن از اینترفیس IServiceInstaller، به طور خودکار در استارتاپ و متد ConfigureServies ثبت خواهند شد.

فقط یک نکته آخر اینکه برای رجیستر خودکار DI سرویس‌ها (و ننوشتن کدهایی مانند   ()<services.AddTransient<IUserService, UserService برای رجیستر هر سرویس) می‌توانیم از Autofac استفاده کنیم (در پروژه‌ی بالا آمده است) و یا از پکیج AutoRegisterDi استفاده کنیم (متعلق به Jon P Smith) که از خود Container داخلی Core استفاده می‌کند و از Autofac سبکتر است. کلاسی می‌سازیم به نام RegisterServicesUsingAutoRegisterDiInstaller: 
public class RegisterServicesUsingAutoRegisterDiInstaller : IServiceInstaller
    {
        public void InstallServices(IServiceCollection services, AppSettings appSettings, Assembly startupProjectAssembly)
        {
            var dataAssembly = typeof(SomeRepository).Assembly;
            var serviceAssembly = typeof(SomeService).Assembly;
            var webFrameworkAssembly = Assembly.GetExecutingAssembly();
            var startupAssembly = startupProjectAssembly;
            var assembliesToScan = new[] { dataAssembly, serviceAssembly, webFrameworkAssembly, startupAssembly };

            #region Generic Type Dependencies
            services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
            #endregion

            #region Scoped Dependency Interface
            services.RegisterAssemblyPublicNonGenericClasses(assembliesToScan)
                .Where(c => c.GetInterfaces().Contains(typeof(IScopedDependency)))
                .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
            #endregion

            #region Singleton Dependency Interface
            services.RegisterAssemblyPublicNonGenericClasses(assembliesToScan)
                .Where(c => c.GetInterfaces().Contains(typeof(ISingletonDependency)))
                .AsPublicImplementedInterfaces(ServiceLifetime.Singleton);
            #endregion

            #region Transient Dependency Interface
            services.RegisterAssemblyPublicNonGenericClasses(assembliesToScan)
                .Where(c => c.GetInterfaces().Contains(typeof(ITransientDependency)))
                .AsPublicImplementedInterfaces(); // Default is Transient
            #endregion

            #region Register DIs By Name
            services.RegisterAssemblyPublicNonGenericClasses(dataAssembly)
                .Where(c => c.Name.EndsWith("Repository")
                            && !c.GetInterfaces().Contains(typeof(ITransientDependency))
                            && !c.GetInterfaces().Contains(typeof(IScopedDependency))
                            && !c.GetInterfaces().Contains(typeof(ISingletonDependency)))
                .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);

            services.RegisterAssemblyPublicNonGenericClasses(serviceAssembly)
                .Where(c => c.Name.EndsWith("Service")
                            && !c.GetInterfaces().Contains(typeof(ITransientDependency))
                            && !c.GetInterfaces().Contains(typeof(IScopedDependency))
                            && !c.GetInterfaces().Contains(typeof(ISingletonDependency)))
                .AsPublicImplementedInterfaces();
            #endregion
        }
    }
 (رجیستر در اینجا با اولویت اینترفیس‌های ITransiantDependency، IScopedDependency، ISingletonDependency و سپس اتمام نام سرویس با کلمه‌های Repository و Service انجام می‌شود که شما می‌توانید با منطق و نیاز خودتان آن‌ها را تغییر دهید)
مطالب
T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
تعریف ActionLink زیر را درنظر بگیرید:
@Html.ActionLink("text", "Index", "Home")

پارامترهای دوم و سوم آن که به نام‌های یک اکشن متد و کنترلر آن اشاره می‌کنند، توسط رشته‌ها تعریف شده‌اند. مشکلاتی هم که با رشته‌ها در حالت کلی وجود دارند به شرح زیر است:
الف) می‌توان نام کنترلر یا نام متد را در برنامه تغییر داد. به این ترتیب تمام ActionLink هایی که در برنامه به این کنترلر اشاره می‌کردند از کار می‌افتند (تکرار رشته‌ها به علاوه refactoring friendly نبودن آن‌ها).
ب) برای نوشتن رشته‌ها intellisense کارآیی ندارد.
ج) امکان بروز اشتباهات تایپی در این بین بسیار زیاد است.

راه حل متداولی که برای حل این نوع مشکلات وجود دارد، تعریف یک کلاس عمومی و معرفی رشته‌ها به صورت فیلدهایی ثابت در آن‌ها می‌باشند و سپس استفاده از این فیلدها بجای استفاده مستقیم از رشته‌ها.
و ... چقدر خوب می‌شد اگر ابزاری وجود می‌داشت که کلاس‌های کنترلرهای ما را آنالیز می‌کرد و خودش این ثوابت رشته‌ای را از آن‌ها استخراج و کلاس‌های عمومی یاد شده را تشکیل می‌داد!
خوشبختانه نیازی به اختراع مجدد چرخ نیست و اینکار توسط پروژه‌ی سورس بازی به نام T4MVC انجام شده است. برای دریافت آن به سایت زیر مراجعه نمائید:
این پروژه توسط David Ebbo از اعضای تیم ASP.NET MVC تهیه شده است.

پس از دریافت پروژه، تنها به دو فایل زیر از آن نیاز داریم:
T4MVC.tt و T4MVC.tt.settings.t4

دو فایل فوق را به درون پوشه‌ای از پروژه جاری MVC خود کپی کنید (مثلا یک پوشه T4MVC را ایجاد و این دو فایل را به آن اضافه کنید). بلافاصله این فایل‌های t4 وارد عمل شده و کلاس‌های کنترلرها، Viewها، تصاویر و غیره را آنالیز و ... ثوابت رشته‌ای معادل آن‌ها را تولید می‌کنند.
اکنون برای استفاده از این کلاس‌های تولید شده می‌توان به صورت زیر عمل کرد:

اصلاح ActionLink‌ها و حذف رشته‌های موجود در آن‌ها

اینبار بجای اینکه بنویسیم:
@Html.ActionLink("text", "Index", "Home")
می‌توان نوشت:
@Html.ActionLink("text", result: MVC.Home.Index())
و یا:
@Html.ActionLink("text", MVC.Home.ActionNames.Index, MVC.Home.Name)

برای دسترسی به امکانات آن با نام کلاس MVC شروع می‌کنیم و سپس برای مثال به نام کنترلر Home رسیده و توسط ActionNames آن به تمام اکشن متدهای موجود در آن می‌توان دسترسی داشت.
البته این پروژه بسیار فراتر از تولید فیلدهای strongly typed معادل رشته‌ها است. همانطور که ملاحظه می‌کنید، یک سری overload را هم به متدهای پیش فرض ASP.NET MVC اضافه کرده است و حتی بجای معرفی رشته معادل اکشن متد Index، خود این اکشن متد را می‌توان معرفی کرد (آرگومانی از نوع ActionResult را هم اضافه کرده است). نمونه دیگر آن به نحو زیر می‌تواند باشد:

@Url.Action(result: MVC.Article.Delete())
که بجای مورد متداول ذیل قابل استفاده است:
@Url.Action("Delete", "Article")


امکان معرفی بهتر نام Partial Viewها

برای مثال اگر پیشتر یک Partial View را به این شکل تعریف می‌کردید:
@{ Html.RenderPartial("_ViewPage1"); }
اکنون امکان معرفی آن به نحو زیر فراهم شده است:
@{ Html.RenderPartial(MVC.Home.Views._ViewPage1); }
همچنین تمام این کلاس‌ها در کنترلرها نیز قابل دسترسی هستند:
return PartialView(Views._ViewPage1);
اینجا دیگر نیازی به ذکر کلاس MVC نبوده و می‌توان کار را با کلاس جدید Views شروع کرد. یا اگر نیازی به ذکر نام اکشن متدی در کنترلر جاری بود می‌توان از ActionNames مستقیما استفاده کرد.
و یا بجای:
return RedirectToAction(actionName: "Index", controllerName: "Menu");
می‌توان نوشت:
return RedirectToAction(actionName: MVC.Menu.ActionNames.Index, controllerName: MVC.Menu.Name);
و یا حتی به صورت خلاصه‌تر به نحو زیر:
return RedirectToAction(result: MVC.Menu.Index());
این overload جدیدی است که با T4MVC به پروژه اضافه می‌شود و یک Action Result را می‌تواند بپذیرد. به این ترتیب به صورت خودکار نام کنترلر و متد Index آن تنها در یک پارامتر لحاظ می‌شوند. اگر نیاز به ارسال پارامتری هم به متد Index وجود داشت، همینجا می‌توان اینکار را انجام داد (بجای استفاده از anonymously typed objects متداول).

و یا بجای مسیر دهی به شکل زیر:
return PartialView("~/Views/CommentsArchive/_LatestCommentsInfo.cshtml", data);
می‌توان نوشت:
return PartialView(MVC.CommentsArchive.Views._LatestCommentsInfo, data);
همچنین برای مسیر دهی viewهای قرار گرفته در پوشه shared می‌توان از MVC.Shared.Views شروع کرد.


امکان معرفی بهتر عناصر استاتیک سایت

این مورد نیز بسیار جالب توجه است. توسط کلاس Links آن می‌توان به محتویات استاتیک (تصاویر، فایل‌های css و غیره) پوشه‌های Content و Scripts هم دسترسی یافت و حتی این موارد را نیز refactor کرد:

<img src="@Links.Content.Images.arrow_right_png" alt="arrow" />
<script src="@Links.Scripts.jquery_1_5_1_min_js" type="text/javascript"></script>


امکان تعریف بهتر پارامترها و مقادیر route

بجای اینکه  routeValues را همانند سابق با anonymously typed objects مقدار دهی کنیم:
Html.ActionLink(linkText: "عنوان",
                       actionName: "Index",
                       controllerName: "Comments",
                       routeValues: new
                       {
                            userName = @Model.FriendlyName
                       },
                       htmlAttributes: null))
اینبار می‌توان نوشت:
Html.ActionLink(linkText: "عنوان",
                       result: MVC.Comments.Index(userName: @Model.FriendlyName)
                       htmlAttributes: null))
در overload جدیدی که ملاحظه می‌کنید، هم دسترسی به متد Index یک کنترلر از نوع strongly typed است و هم امکان تعریف پارامترهای آن به نحو منطقی‌تری فراهم شده است (متد Comments.Index واقعا وجود خارجی داشته و پارامتری از نوع userName را می‌پذیرد).


چند نکته جانبی
-این ابزار بر اساس Reflection کار می‌کند (البته فقط در حین تشکیل خودکار کلاس‌های مورد نیاز؛ وگرنه ثوابتی را که ایجاد می‌کند کامپایل شده و در زمان اجرا سرباری را به برنامه اضافه نمی‌کنند). بنابراین اگر کلاسی به پروژه اضافه شده است، کامپایل کردن آن‌را فراموش نکنید.
-اگر تغییری در فایل‌های View، در تعداد و نام آن‌ها صورت گرفت، روی فایل T4MVC.tt کلیک راست کرده و گزینه‌ی اجرای آن‌را انتخاب کنید. پس از این‌کار، مجددا کامپایل پروژه را فراموش نکنید.
-در فایل T4MVC.tt.settings.t4 یک سری تنظیمات پیش فرض قرار دارند. برای مثال اگر علاقمندید که به این فایل‌های تولید شده خودکار، فضای نام سفارشی خاصی را اضافه کنید می‌شود آرایه ReferencedNamespaces آن‌را مقدار دهی کرد.
- overloadهای جدید ActionResult دار آن نسبت به نمونه‌های استاندارد موجود، بسیار منطقی‌تر به نظر می‌رسند.
- توضیحات کامل امکانات T4MVC را در مستندات رسمی آن می‌توانید مطالعه کنید.


و ... اگر یک مدت با آن کار کنید خواهید گفت: «من قبلا چطور با ASP.NET MVC کار می‌کردم؟!»

مطالب
آموزش TypeScript #4
در پست‌های قبل با کلیات و primitive types در زبان TypeScript آشنا شدیم:

در این پست به مفاهیم شی گرایی در این زبان می‌پردازیم.

ماژول ها:
تعریف یک ماژول: برای تعریف یک ماژول باید از کلمه کلیدی module استفاده کنید. یک ماژول معادل یک ظرف است برای نگهداری کلاس‌ها و اینترفیس‌ها و سایر ماژول ها. کلاس‌ها و اینترفیس‌ها در TypeScript می‌توانند به صورت internal یا public باشند(به صورت پیش فرض internal است؛ یعنی فقط در همان ماژول قابل استفاده و فراخوانی است). هر چیزی که در داخل یک ماژول تعریف می‌شود محدوده آن در داخل آن ماژول خواهد بود. اگر قصد توسعه یک پروژه در مقیاس بزرگ را دارید می‌توانید همانند دات نت که در آن امکان تعریف فضای نام‌های تودرتو امکان پذیر است در TypeScript نیز، ماژول‌های تودرتو تعریف کنید.  برای مثال:
module MyModule1 {
    module  MyModule2 {
     }
}
اما به صورت معمول سعی می‌شود هر ماژول در یک فایل جداگانه تعریف شود. استفاده از چند ماژول در یک فایل به مرور، درک پروژه را سخت خواهد کرد و در هنگام توسعه امکان برخورد با مشکل وجود خواهد داشت. برای مثال اگر یک فایل به نام MyModule.ts داشته باشیم که یک ماژول به این نام را شامل شود بعد از کامپایل یک فایل به نام  MyModule.js ایجاد خواهد شد. 

کلاس ها:
برای تعریف یک کلاس می‌توانیم همانند دات نت از کلمه کلیدی class استفاده کنیم. بعد از تعریف کلاس می‌توانیم متغیر‌ها و توابع مورد نظر را در این کلاس قرار داده و تعریف کنیم.  
module Utilities {
   export class Logger {
      log(message: string): void{
       if(typeofwindow.console !== 'undefined') {
           window.console.log(message);
        }
      }
   }    
}
نکته مهم و جالب قسمت بالا کلمه export است. export معادل public در دات نت است و کلاس  logger را قابل دسترس در خارج ماژول Utilities خواهد کرد. اگر از export در هنگام تعریف کلاس استفاده نکنیم این کلاس فقط در سایر کلاس‌های تعریف شده در داخل همان ماژول قابل دسترس است.
تابع log  که در کلاس بالا تعریف کردیم به صورت پیش فرض public یا عمومی است و نیاز به استفاده export نیست.
برای استفاده از کلاس بالا باید این کلمه کلیدی new استفاده کنیم.  
window.onload = function() {
  varlogger = new Utilities.Logger();
  logger.log('Logger is loaded'); 
};
برای تعریف سازنده برای کلاس بالا باید از کلمه کلیدی constructor استفاده نماییم:
export class Logger{
constructor(private num: number) { 
}
با کمی دقت متوجه تعریف متغیر num به صورت private خواهید شد که برخلاف انتظار ما در زبان‌های دات نتی است. بر خلاف دات نت در زبان TypeScript، دسترسی به متغیر تعریف شده در سازنده با کمک اشاره گر this  در هر جای کلاس ممکن می‌باشد. در نتیجه نیازی به تعریف متغیر جدید و  پاس دادن مقادیر این متغیر‌ها به این فیلدها نمی‌باشد.
اگر به تابع log دقت کنید خواهید دید که یک پارامتر ورودی به نام message دارد که نوع آن string است. در ضمن Typescript از پارامتر‌های اختیاری( پارامتر با مقدار پیش فرض) نیز پشتیبانی می‌کند. مثال:

pad(num: number, len: number= 2, char: string= '0')
استفاده از پارامترهای Rest
منظور از پارامترهای Rest یعنی در هنگام فراخوانی توابع محدودیتی برای تعداد پارامتر‌ها نیست که معادل params در دات نت است. برای تعریف این گونه پارامترهاکافیست به جای params از ... استفاده نماییم.
function addManyNumbers(...numbers: number[]) {
  var sum = 0;
  for(var i = 0; i < numbers.length; i++) {
    sum += numbers[i];
 }
  returnsum;
}
var result = addManyNumbers(1,2,3,5,6,7,8,9);
تعریف توابع خصوصی
در TypeScript امکان توابع خصوصی با کلمه کلیدی private امکان پذیر است. همانند دات نت با استفاده از کلمه کلیدی private می‌توانیم کلاسی تعریف کنیم که فقط برای همان کلاس قابل دسترس باشد(به صورت پیش فرض توابع به صورت عمومی هستند).
module Utilities {
    Export class Logger {  
     log(message: string): void{
                 if(typeofwindow.console !== 'undefined') {   
                    window.console.log(this.getTimeStamp() + ' -'+ message);
                    window.console.log(this.getTimeStamp() + ' -'+ message); 
                }
        }
  private getTimeStamp(): string{
      var now = newDate();
      return now.getHours() + ':'+
      now.getMinutes() + ':'+
      now.getSeconds() + ':'+
      now.getMilliseconds();
  }
 }
}
از آن جا که تابع getTimeStamp به صورت خصوصی تعریف شده است در نتیجه امکان استفاده از آن در خارج کلاس وجود ندارد. اگر سعی بر استفاده این تابع داشته باشیم توسط کامپایلر با یک warning مواجه خواهیم شد.

یک نکته مهم این است که کلمه private فقط برای توابع و متغیر‌ها قابل استفاده است.

تعریف توابع static:

در TypeScript امکان تعریف توابع static وجود دارد. همانند دات نت باید از کلمه کلیدی static استفاده کنیم.

classFormatter {
static pad(num: number, len: number, char: string): string{
      var output = num.toString();
         while(output.length < len) {
         output = char + output;
      }
   returnoutput;
   }
  }
}
و استفاده از این تابع بدون وهله سازی از کلاس :
Formatter.pad(now.getSeconds(), 2, '0') +
Function Overload
همان گونه که در دات نت امکان overload کردن توابع میسر است در TypeScript هم این امکان وجود دارد.
static pad(num: number, len?: number, char?: string);
static pad(num: string, len?: number, char?: string);
static pad(num: any, len: number= 2, char: string= '0') {
 var output = num.toString();
 while(output.length < len) {
 output = char + output;
 }
 returnoutput;
}

ادامه دارد...
مطالب
مفاهیم برنامه نویسی ـ آشنایی با سازنده‌ها
در مطلب پیشین برای نگهداری حالت شیء یا همان ویژگی‌های آن Property‌‌‌ها را در کلاس معرفی کردیم و پس از ایجاد شیء مقدار مناسبی را به پروپرتی‌ها اختصاص دادیم.
اگرچه ایجاد شیء و مقداردهی به ویژگی‌های آن ما را به هدفمان می‌رساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازنده‌ها (Constructors) حل می‌شود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا می‌شود. وظیفه سازنده مقداردهی به ویژگی‌های عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامه‌نویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی می‌کنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی می‌کنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی می‌کنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
public class Triangle
{
    private int _height;
    private int _baseLength;

    public int Height
    {
        get { return _height; }

        set
        {
            if (value < 1 || value > 100)
            {
                // تولید خطا
            }

            _height = value;
        }
    }

    public int BaseLength
    {
        get { return _baseLength; }

        set
        {
            if (value < 1 || value > 100)
            {
                // تولید خطا
            }

            _baseLength = value;
        }
    }

    public double Area
    {
        get { return _height * _baseLength * 0.5; }
    }
}
چون در بخشی از یک پروژه نیاز پیدا کردیم با یک سری مثلث کار کنیم، کلاس بالا را طراحی کرده ایم. به نکات زیر توجه نمایید.
 • در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگی‌ها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز می‌توان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت می‌کنیم.
 • در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.

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

سازنده پیش فرض

سازنده‌ها مانند متدهای دیگر می‌توانند پارامتر دریافت کنند. سازنده ای که هیچ پارامتری دریافت نمی‌کند سازنده پیش فرض (Default constructor) نامیده می‌شود. سازنده پیش فرض زمانی اجرا می‌شود که با استفاده از عملگر new شیء ای ایجاد می‌کنید اما هیچ آرگومانی را برای این عملگر در نظر نگرفته اید.
اگر برای کلاسی که طراحی می‌کنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتی‌ها را با توجه به نوع آن‌ها تنظیم می‌نماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتی‌ها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای می‌سازد که وضعیت صحیحی ندارد و نمی‌تواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.

جایگزینی سازنده پیش فرض ساخته شده توسط کامپایلر

افزودن یک سازنده صریح به کلاس بسیار شبیه به تعریف یک متد در کلاس است. با این تفاوت که:
  • سازنده هم نام کلاس است.
  • برای سازنده نوع خروجی در نظر گرفته نمی‌شود.

در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف می‌کنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
public Triangle()
{
    _height = _baseLength = 1;
}
در این سازنده مقدار ۱ را برای متغیر خصوصی پشت (backing field یا backing store) هر یک از دو ویژگی قاعده و ارتفاع تنظیم نموده ایم.

اجرای سازنده

همانطور که گفته شد سازنده اضافه شده به کلاس جایگزین سازنده پیش فرض کامپایلر شده و در هنگام ایجاد یک شیء جدید از کلاس مثلث توسط عملگر new اجرا می‌شود. برای بررسی اجرا شدن سازنده به سادگی می‌توان کدی مشابه مثال زیر را نوشت.
Triangle triangle = new Triangle();
 
Console.WriteLine(triangle.Height);
Console.WriteLine(triangle.BaseLength);
Console.WriteLine(triangle.Area);
کد بالا مقدار ۱ را برای قاعده و ارتفاع و مقدار ۰.۵ را برای مساحت چاپ می‌نماید. بنابراین مشخص است که سازنده اجرا شده و مقادیر مناسب را برای شیء تنظیم نموده به طوری که شیء از بدو تولید در وضعیت مناسبی است.

سازنده‌های پارامتر دار

در مثال قبل یک سازنده بدون پارامتر را به کلاس اضافه کردیم. این سازنده تنها مقادیر پیش فرض مناسبی را تنظیم می‌کند. بدیهی است پس از ایجاد شیء در صورت نیاز می‌توان مقادیر مورد نظر دیگر را برای قاعده و ارتفاع تنظیم نمود. اما برای اینکه سازنده بهتر بتواند فرآیند وهله سازی را کنترل نماید می‌توان پارامترهایی را به آن افزود. افزودن پارامتر به سازنده مانند افزودن پارامتر به متدهای دیگر صورت می‌گیرد. در مثال زیر سازنده دیگری تعریف می‌کنیم که دارای دو پارامتر است. یکی قاعده و دیگری ارتفاع. به این ترتیب در حین فرآیند وهله سازی می‌توان مقادیر مورد نظر را منتسب نمود.
public Triangle(int height, int baseLength)
{
    Height = height;
    BaseLength = baseLength;
}
با توجه به اینکه مقادیر ارسالی به این سازنده توسط کد مشتری در نظر گرفته می‌شود و ممکن است در محدوده مجاز نباشد، به جای انتساب مستقیم این مقادیر به فیلد خصوصی پشت ویژگی قاعده و ارتفاع یعنی baseLength_ و height_ آنها را به پروپرتی‌ها منتسب کردیم تا قانون اعتبارسنجی موجود در اکسسور set پروپرتی‌ها از انتساب مقادیر غیر مجاز جلوگیری کند.
سازنده اخیر را می‌توان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومان‌های مورد نظر مورد استفاده قرار داد.
Triangle triangle = new Triangle(5, 8);
 
Console.WriteLine(triangle.Height);
Console.WriteLine(triangle.BaseLength);
Console.WriteLine(triangle.Area);
مقادیر چاپ شده برابر ۵ برای ارتفاع، ۸ برای قاعده و ۲۰ برای مساحت خواهد بود که نشان از اجرای صحیح سازنده دارد.
در مطالب بالا چندین بار از سازنده‌ها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه می‌کنیم. این دو نکته را به خاطر داشته باشید:
  • یک کلاس می‌تواند دارای چندین سازنده باشد که بر اساس آرگومان‌های فراهم شده هنگام وهله سازی، سازنده مورد نظر انتخاب و اجرا می‌شود.
  • الزامی به تعریف یک سازنده پیش فرض (به معنای بدون پارامتر) نیست. یعنی یک کلاس می‌تواند هیچ سازنده بی پارامتری نداشته باشد.

در بخش‌های بعدی مطالب بیشتری در مورد سازنده‌ها و سایر اعضای کلاس خواهید آموخت.

 
مطالب
تعیین اعتبار کردن یک عبارت SQL - قسمت دوم

مطلبی را روز قبل نوشتم در مورد تعیین اعتبار یک کوئری. این مورد از آنجایی حائز اهمیت می‌شود که برای مثال تغییری در ساختار یکی از جداول حاصل شود. اکنون می‌خواهیم بررسی کنیم آیا سیستم از کار افتاده یا نه!؟
شما می‌توانید نام یک فیلد را تغییر دهید (حتی اگر این فیلد در یک رویه ذخیره شده استفاده شده باشد) و هیچ خطایی هم نخواهید گرفت و این منشاء دردسرهای زیادی خواهد بود.
در حالت استفاده از SET NOEXEC ON ، کوئری مورد نظر فقط کامپایل می‌شود و همچنین از لحاظ نحوی بررسی خواهد شد، اما این کافی نیست.
مثال زیر را در نظر بگیرید:

Create PROCEDURE Test1
AS
SELECT * FROM tblPIDs1
جدول tblPIDs1 در دیتابیس مورد نظر وجود ندارد.
این کوئری قابل اجرا است. دکمه‌ی F5 را فشار دهید، بلافاصله رویه ذخیره شده‌ی Test1 برای شما ایجاد خواهد شد.
سپس کوئری زیر را اجرا کنید:

USE testdb
SET NOEXEC ON;
exec test1 ;
SET NOEXEC OFF;
بدون مشکل و بروز خطایی، پیغام زیر را نشان می‌دهد:
Command(s) completed successfully

ایرادی هم وارد نیست چون فقط عملیات parsing و compile صورت گرفته و نه اجرای واقعی رویه ذخیره شده. اینجا از لحاظ دستوری مشکلی وجود ندارد.

در این نوع موارد می‌توان از SET FMTONLY ON استفاده کرد. این مورد اجرای غیر واقعی یک کوئری را سبب می‌شود (تاثیری روی دیتابیس موجود نخواهد داشت، برای مثال اگر در رویه ذخیره شما عبارت insert وجود داشت، دیتایی insert نخواهد شد) و تنها متادیتای حاصل را بازگشت می‌دهد. مثلا نام ستون‌های یک کوئری را و همچنین در این حین اگر خطایی رخ داده باشد، آن‌را نیز ارائه خواهد داد.

USE testdb
SET FMTONLY ON;
exec test1 ;
SET FMTONLY OFF;
با اجرای کوئری فوق خطای زیر ظاهر می‌شود:
Msg 208, Level 16, State 1, Procedure test1, Line 3
Invalid object name 'tblPIDs1'.
برای اتوماسیون این توانایی می‌توان از کوئری زیر استفاده کرد:

USE testdb;

SET NOCOUNT ON;

DECLARE @name NVARCHAR(MAX),
@sql NVARCHAR(MAX),
@type CHAR(2), -- object type
@type_desc NVARCHAR(60), -- object type description
@params NVARCHAR(MAX) -- parameters

DECLARE @tblInvalid TABLE (
-- invalid objects
[type_desc] NVARCHAR(60),
[name] NVARCHAR(MAX),
[error_number] INT,
[error_message] NVARCHAR(MAX),
[type] CHAR(2)
);

DECLARE testSPs CURSOR FAST_FORWARD
FOR
SELECT [name] = OBJECT_NAME(SM.[object_id]),
[type] = SO.[type],
SO.[type_desc],
[params] = (
SELECT (
SELECT CONVERT(
XML,
(
SELECT STUFF(
(
SELECT ', ' + [name] +
'=NULL' AS
[text()]
FROM sys.parameters
WHERE [object_id] = SM.[object_id]
FOR XML PATH('')
),
1,
1,
''
)
)
)
FOR XML RAW,
TYPE
).value('/row[1]', 'varchar(max)')
)
FROM sys.sql_modules SM
JOIN sys.objects SO
ON SO.[object_id] = SM.[object_id]
WHERE SO.[is_ms_shipped] = 0
AND SO.[type] = 'P'


OPEN testSPs
FETCH NEXT FROM testSPs INTO @name, @type, @type_desc, @params

WHILE (@@FETCH_STATUS = 0)
BEGIN
BEGIN TRY
SET @sql = 'SET FMTONLY ON; exec ' + @name + ' ' + @params +
'; SET FMTONLY OFF;'
--PRINT @sql;
EXEC (@sql) ;
END TRY
BEGIN CATCH
PRINT @type_desc + ', ' + @name + ', Error: ' + CAST(ERROR_NUMBER() AS VARCHAR)
+ ', ' + ERROR_MESSAGE();
INSERT INTO @tblInvalid
SELECT @type_desc,
@name,
ERROR_NUMBER(),
ERROR_MESSAGE(),
@type

;
END CATCH


FETCH NEXT FROM testSPs INTO @name, @type, @type_desc, @params
END
CLOSE testSPs
DEALLOCATE testSPs


SELECT [type_desc],
[name],
[error_number],
[error_message]
FROM @tblInvalid
ORDER BY
CHARINDEX([type], ' U V PK UQ F TR FN TF P SQ '),
[name];

توضیحات:
این کوئری، در دیتابیس جاری که در قسمت use dbname مشخص می‌شود، تمامی رویه‌های ذخیره شده را به صورت خودکار پیدا می‌کند. سپس لیست آرگومان‌های آن‌ها را نیز یافته و عبارت exec مربوطه را تشکیل می‌دهد. سپس با استفاده از SET FMTONLY ON سعی در شبیه سازی اجرای تک تک رویه‌های ذخیره شده می‌کند. اگر خطایی در این بین رخ داد، آن‌ها را در یک جدول موقتی ذخیره کرده و در آخر نتیجه را نمایش می‌دهد.

ارزش این کوئری زمانی مشخص می‌شود که تعداد زیادی رویه ذخیره شده داشته باشید اما نمی‌دانید کدامیک از آن‌ها بر اساس آخرین تغییرات صورت گرفته، هنوز معتبر هستند یا نه. آیا به قول معروف، سیستم اومد پایین یا خیر!؟

نکته:
قسمتی که از XML استفاده شده جهت concatenating نتیجه حاصل از کوئری، مورد استفاده قرار گرفته و این روزها بحث رایجی است که در بسیاری از سایت‌ها در مورد آن می‌توان مطالب مفیدی را یافت. راه دیگر انجام آن استفاده از COALESCE می‌باشد.


مآخذ:
Check Validity of SQL Server Stored Procedures
Which of your Stored Procedures are no longer Valid
SET FMTONLY ON

نظرات مطالب
فقط به خاطر یک نیم فاصله!
سلام،

راه حلی که من برای دور زدن این اشکال MKLC پیدا کرده بودم این بود که یک برنامه کوچک که خط فرمان را در جایی ذخیره می‌کرد نوشتم و آن را جایگزین فایل‌های cl.exe و link.exe که به همراه MKLC می‌آیند کردم.

MKLC به این روش کار می‌کند که از تعریف صفحه کلید یک فایل کد C ایجاد می‌کند و آن را توسط نسخه‌ای از Visual C که به همراه خود برنامه هست کامپایل می‌کند.

من به این روش فایل C تولید شده را ویرایش می‌کردم و ترکیب Shift+Space را در آن اصلاح می‌کردم و با خط فرمانی که به کمک برنامه ذکر شده به دست می‌آوردم صفحه کلید را کامپایل می‌کردم.

ولی راه حل شما هم خیلی جالبه و فکر می‌کنم که برای نسخه‌های بعدی من هم از آن استفاده کنم.
نظرات مطالب
توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن
اتفاقا چند وقت پیش با RazorGenerator کار کردم اونموقع مطلب شما رو در این باره نخونده بودم با کلی مکافات تونستم ازش خروجی بگیرم که یه سوال برام پیش اومد و ازش منصرف شدم. با توجه به اینکه Razorgenarator یه برای هر view یه فایل cs میسازه و توی اون با استفاده از writer تمام متن داخل view رو به خروجی میفرسته استفاده از یه همچین روشی درسته و بار اضافه روی ایجاد view‌ها نمیزاره ؟
از روش‌های دیگه استفاده کریدن؟ مثل SparkViewFactory 
فکر میکنید کدوم بهتره؟
مطالب
WF:Windows Workflow #2
ایجاد یک گردش ساده

در این دوره آموزشی قصد آموزش WF4  را داریم. برای ایجاد یک پروژه از نوع WF4  نیاز به VS2010 یا VS2012 است.
زمانیکه ویژوال استودیو را باز می‌کنید و بر روی گزینه ایجاد پروژه جدید کلیک می‌نمائید، در قسمت Workflow، چندین نوع پروژه وجود دارد که هر کدام از آن‌ها را به نوبت بررسی خواهیم کرد.
ابتدا یک پروژه از نوع Workflow Console Application  را ایجاد کنید:

پس ایجاد پروژه، اگر دقت داشته باشید، فایل ایجاد شده با پسوند .XAML  می‌باشد و یک کلاس Program.cs  هم در پروژه ایجاد گردیده که حاوی کلاس Main است و کار آن دقیقا مثل برنامه‌های کنسول معمولی می‌باشد.
اگر به قسمت پایین ویژوال استودیو دقت کنید، سه گزینه به نام‌های Variables ,Arguments ,Imports قابل مشاهده هستند:
  1. Variables : همین طور که از نام این گزینه بر می‌آید، برای تعریف متغیر‌ها در طول یک فرآیند می‌باشد.
  2. Arguments : این گزینه هم مانند قسمت قبل عمل کرده، ولی تفاوت‌هایی باهم دارند. به عنوان مثال در گزینه ۱ می‌توان برای متغیر، محدوده ایجاد کرد. ولی در گزینه 2 هیچگونه محدودیتی وجود ندارد و در تمام  Workflowها می‌توان از آن استفاده کرد و دیگر اینکه در گزینه ۲ می‌توان به متغیر گفت که از نوع ورودی In یا از نوع خروجی Out ویا هر دو  In/Out و یا اینکه از نوع Property باشد.
  3. Imports : این قسمت فضا‌های نامی که در برنامه استفاده می شوند، نشان داده می‌شوند .

برای ایجاد یک Flow ابتدا باید از  Toolbox، قسمت Control Flow، کنترل  Sequence را به داخل صفحه کشید و  پس از آن می‌توانیم از سایر کنترل‌ها استفاده نمائیم. پس از این کار، از قسمت  Primitives کنترل  WriteLine را به درون Sequence انتقال می‌دهیم .


این کنترل برای چاپ مقادیر در خروجی می‌باشد. مانند کدی که در زیر نوشته شده است عمل کرده است و این کار را از طریق خاصیتی به نام Text انجام می‌دهد.
 Console.WriteLine("Hello Word");
نکته: توجه کنید برای تغییر نام اجزایی که به درون صفحه کشیده می‌شوند، می‌توان برروی نام آن دوبار کلیک کرده و نام آن را تغییر دهید و یا با انتخاب آن، در پنجره Properties نام آن را عوض کنیم. برای این کار کافی است مقدار گزینه DisplayName  را با مقدار مورد نظر عوض نمائیم. این گزینه در تمام اجزای Workflow  موجود می‌باشد.
حال باید برنامه را اجرا کنیم تا خروجی را که چاپ رشته  "Hello Word"  است، مشاهده نمائیم. ولی قبل از آن باید تغییراتی در فایل Program.cs داده شود که به شرح زیر می‌باشد:
WorkflowInvoker.Invoke( new Workflow1());

 Console.WriteLine("Press ENTER to exit"); 
 Console.ReadLine();
از کلاس  WorkflowInvoker برای اجرای Flow مورد نظر استفاده شده و این‌کار از طریق متد Invoke انجام داده می‌شود.
مطالب
ساده سازی تعریف فضاهای نام در C# 10.0
در ادامه‌ی طراحی مبتنی بر مینی‌مالیسم C# 10.0، پس از پیش‌فرض شدن «top level programs» و همچنین «کاهش تعداد بار تعاریف usingها»، تغییر سوم صورت گرفته‌ی در قالب‌های پروژه‌های مبتنی بر دات نت 6، ساده سازی تعاریف فضاهای نام است. برای مثال یک کنترلر، به این صورت تعریف شده‌است:
namespace mvc.Controllers;

public class HomeController : Controller
{
}
که به آن «File-Scoped Namespaces» هم گفته می‌شود.


بررسی مفهوم «File-Scoped Namespaces»

یکی از اهداف مهم C# 10.0، کاهش نویز موجود در فایل‌های cs. است. اگر قرار است صدها بار در فایل‌های مختلف برنامه، using System نوشته شود، چرا یکبار آن‌را به صورت عمومی تعریف نکنیم و یا اگر در 99 درصد موارد، توسعه دهنده‌ها به ازای یک فایل، تنها یک فضای نام را تعریف می‌کنند، چرا باید یک فضای اضافی خالی، برای تعریف آن اختصاص داده شود و تمام فایل‌ها به همراه یک «tab فاصله‌ی» اضافی مختص به این فضای نام باشند؟
تعریف فعلی فضاهای نام در #C به صورت زیر است:
namespace MyNamespace
{
    public class MyClass
    {
        public void MyMethod()
        {
            //...Method implementation
        }
    }
}
در این حالت هر شیءای که داخل {} این فضای نام قرار گیرد، متعلق به آن است.
در C# 10.0، می‌توان این تعریف را ساده کرد؛ از آنجائیکه به ندرت چند فضای نام در یک تک فایل تعریف می‌شوند، می‌توان تعریف فضای نام را در یک سطر، در ابتدای فایل ذکر کرد، تا به صورت خودکار به کل فایل و اشیاء موجود در آن اعمال شود:
namespace MyNamespace;
public class MyClass
{
    public void MyMethod()
    {
        //...Method implementation
    }
}
در این حالت، روش استفاده‌ی از یک چنین اشیایی هیچ تغییری نخواهد کرد؛ فقط یک tab space و فاصله از کنار صفحه، صرفه‌جویی می‌شود!


محدویت‌های «File-Scoped Namespaces»

- بدیهی است در این حالت دیگر نمی‌توان چندین فضای نام را همانند قبل در یک فایل cs. تعریف کرد:
namespace Name1
{
    public class Class1
    {
    }
}

namespace Name1.Name2
{
  public class Class2
  {
  }
}
 و البته این موردی است که جزو best practices توسعه‌ی برنامه‌های #C به هیچ عنوان توصیه نمی‌شود.
- همچنین امکان ترکیب روش قبلی تعریف فضاهای نام، با روش جدید، در یک فایل وجود ندارد.
- به علاوه امکان تعریف فضاهای نام تو در تو که با روش قدیمی وجود دارد:
namespace Name1
{
    public class Class1
    {
    }

    namespace Name1.Name2
    {
        public class Class2
        {
        }
    }
}
در این حالت جدید پشتیبانی نمی‌شود.