مطالب
به روز رسانی اطلاعات Master-Detail یا Master-Detail-DetailOfDetail با استفاده از EF Core

یکی از چالش‌هایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail می‌باشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم کرد.


موجودیت‌های فرضی

public abstract class Entity : IHaveTrackingState
{
    public long Id { get; set; }
    [NotMapped] public TrackingState TrackingState { get; set; }
}

public class Master : Entity
{
    public string Title { get; set; }
    public ICollection<Detail> Details { get; set; }
}

public class Detail : Entity
{
    public string Title { get; set; }

    public ICollection<DetailOfDetail> Details { get; set; }
    public Master Master { get; set; }
    public long MasterId { get; set; }
}

public class DetailOfDetail : Entity
{
    public string Title { get; set; }
    public Detail Detail { get; set; }
    public long DetailId { get; set; }
}

DbContext برنامه

public class ProjectDbContext : DbContext
{
    public DbSet<Master> Masters { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder.UseInMemoryDatabase("SharedDatabaseName");
    }
}

واسط IHaveTrackingState
public interface IHaveTrackingState
{
    TrackingState TrackingState { get; set; }
    //ICollection<string> ModifiedProperties { get; set; }
}

public enum TrackingState
{
    Unchanged = 0,
    Added = 1,
    Modified = 2,
    Deleted = 3
}

با استفاده از پراپرتی TrackingState بالا، امکان مشخص کردن صریح State رکورد ارسالی توسط کلاینت مهیا می‌شود. قبلا نیز مطلبی در راستای STE یا همان Self-Tracking Entity تهیه شده است؛ و همچنین نظرات ارسالی این مطلب نیز می‌تواند مفید واقع شود. 


DTO‌های متناظر با موجودیت‌های فرضی

public abstract class Model : IHaveTrackingState
{
    public long Id { get; set; }
    public TrackingState TrackingState { get; set; }
}

public class MasterModel : Model
{
    public string Title { get; set; }
    public ICollection<DetailModel> Details { get; set; }
}

public class DetailModel : Model
{
    public string Title { get; set; }
    public ICollection<DetailOfDetailModel> Details { get; set; }
}

public class DetailOfDetailModel : Model
{
    public string Title { get; set; }
}

تنظیمات نگاشت موجودیت‌ها و DTOها
Mapper.Initialize(expression =>
{
    expression.CreateMap<MasterModel, Master>(MemberList.None).ReverseMap();
    expression.CreateMap<DetailModel, Detail>(MemberList.None).ReverseMap();
    expression.CreateMap<DetailOfDetailModel, DetailOfDetail>(MemberList.None).ReverseMap();
});

البته بهتر است این تنظیمات در درون Profile‌های مرتبط با AutoMapper کپسوله شوند و در زمان مورد نیاز نیز برای انجام نگاشت‌ها، واسط IMapper تزریق شده و استفاده شود.


تهیه داده ارسالی فرضی توسط کلاینت

var masterModel = new MasterModel
    {
        Title = "Master-Title",
        TrackingState = TrackingState.Added,
        Details = new List<DetailModel>
        {
            new DetailModel
            {
                Title = "Detail-Title",
                TrackingState = TrackingState.Added,
                Details = new List<DetailOfDetailModel>
                {
                    new DetailOfDetailModel
                    {
                        Title = "DetailOfDetail-Title",
                        TrackingState = TrackingState.Added,
                    }
                }
            }
        }
    };

ذخیره سازی اطلاعات

در EF Core، متد جدید context.ChangeTracker.TrackGraph برای به روز رسانی وضعیت یک گراف از اشیاء مشابه به اطلاعات ارسالی ذکر شده در بالا، اضافه شده است. این مکانیزم مفهوم کاملا جدیدی در EF Core می‌باشد که امکان کنترل نهایی برروی اشیایی را که قرار است توسط Context ردیابی شوند، مهیا می‌کند. با پیمایش یک گراف، امکان اجرای عملیات مورد نظر شما را برروی تک تک اشیاء، مهیا می‌سازد. 

using (var context = new ProjectDbContext())
{
    Console.WriteLine("################ Create Master and Details and DetailsOfDetail ##################");
    Print(masterModel);

    var masterEntity = Mapper.Map<Master>(masterModel);

    context.ChangeTracker.TrackGraph(
        masterEntity,
        n =>
        {
            var entity = (IHaveTrackingState) n.Entry.Entity;
            n.Entry.State = entity.TrackingState.ToEntityState();
        });

    context.SaveChanges();
}

در تکه کد بالا، پس از انجام عملیات نگاشت، توسط متد TrackGraph به صورت صریح، وضعیت موجودیت‌ها مشخص شده است؛ این کار با تغییر State ارسالی توسط کلاینت به State قابل فهم توسط EF انجام شده‌است. برای این منظور دو متد الحاقی زیر را می‌توان در نظر گرفت:

public static class TrackingStateExtensions
{
    public static EntityState ToEntityState(this TrackingState trackingState)
    {
        switch (trackingState)
        {
            case TrackingState.Added:
                return EntityState.Added;

            case TrackingState.Modified:
                return EntityState.Modified;

            case TrackingState.Deleted:
                return EntityState.Deleted;

            case TrackingState.Unchanged:
                return EntityState.Unchanged;

            default:
                return EntityState.Unchanged;
        }
    }

    public static TrackingState ToTrackingState(this EntityState state)
    {
        switch (state)
        {
            case EntityState.Added:
                return TrackingState.Added;

            case EntityState.Modified:
                return TrackingState.Modified;

            case EntityState.Deleted:
                return TrackingState.Deleted;

            case EntityState.Unchanged:
                return TrackingState.Unchanged;

            default:
                return TrackingState.Unchanged;
        }
    }
}

شبیه سازی عملیات ویرایش
//GetForEditAsync
var masterModel = context.Masters
    .ProjectTo<MasterModel>()
    .AsNoTracking().Single(a => a.Id == 1);

//Client
var detail1 = masterModel.Details.First();
detail1.Title = "Details-EditedTitle";
detail1.TrackingState = TrackingState.Modified;

foreach (var detail in detail1.Details)
{
    detail.TrackingState = TrackingState.Deleted;
    //detail.Title = "DetailOfDetails-EditedTitle";
}

متدی تحت عنوان GetForEditAsync که یک MasterModel را بازگشت می‌دهد، در نظر بگیرید؛ کلاینت از طریق API، این Object Graph را دریافت می‌کند و تغییرات خود را اعمال کرده و همانطور که مشخص می‌باشد به دلیل اینکه تنظیمات نگاشت بین Detail و DetailModel در ابتدای بحث نیز انجام شده است، این بار دیگر نیاز به استفاده از متد Include نمی‌باشد و این عملیات توسط متد ProjectTo خودکار می‌باشد. در نهایت داده ارسالی توسط کلاینت را دریافت کرده و به شکل زیر عملیات به روز رسانی انجام می‌شود:

using (var context = new ProjectDbContext())
{
    Console.WriteLine(
        "################ Unchanged Master and Modified Details and Deleted DetailsOfDetail ##################");
    Print(masterModel);

    var masterEntity = Mapper.Map<Master>(masterModel);

    context.ChangeTracker.TrackGraph(
        masterEntity,
        n =>
        {
            var entity = (IHaveTrackingState) n.Entry.Entity;
            n.Entry.State = entity.TrackingState.ToEntityState();
        });

    context.SaveChanges();
}

با خروجی زیر:

برای بحث اعتبارسنجی هم می‌توان به شکل زیر عمل کرد:

public class MasterValidator : AbstractValidator<MasterModel>
{
    public MasterValidator()
    {
        RuleFor(a => a.Title).NotEmpty();
        RuleForEach(a => a.Details).SetValidator(new DetailValidator());
    }
}

public class DetailValidator : AbstractValidator<DetailModel>
{
    public DetailValidator()
    {
        RuleFor(a => a.Title).NotEmpty();
        RuleForEach(a => a.Details).SetValidator(new DetailOfDetailValidator());
    }
}

public class DetailOfDetailValidator : AbstractValidator<DetailOfDetailModel>
{
    public DetailOfDetailValidator()
    {
        RuleFor(a => a.Title).NotEmpty();
    }
}

با استفاده از متد RuleForEach و SetValidator موجود در کتابخانه FluentValidation، امکان مشخص کردن اعتبارسنج برای Detail موجود در شیء Master را خواهیم داشت.

همچنین با توجه به این که برای عملیات Create و Edit از یک مدل (DTO) استفاده خواهیم کرد، شاید لازم باشد اعتبارسنجی خاصی را فقط در زمان ویرایش لازم داشته باشیم، که در این صورت می‌توان از امکانات RuleSet استفاده کنید. در مطلب «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» با استفاده ValidateWithRuleAttribute امکان مشخص کردن RuleSet مورد نظر برای اعتبارسنجی ورودی متد سرویس نیز در نظر گرفته شده است.


منابع تکمیلی

کتابخانه کمکی

کدهای کامل مطلب جاری را  از اینجا می‌توانید دریافت کنید.
مطالب
سرعت ایجاد اشیاء CLR

به نظر شما چه تعداد شیء CLR را می‌توان در یک ثانیه ایجاد کرد؟
برنامه کنسول زیر دو نسخه معمولی و نسخه پردازش موازی یک آزمایش ساده را برای اندازه گیری این مطلب ارائه می‌دهد:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ObjectInitSpeedTest
{
class Program
{
//Note: don't forget to build it in Release mode.
static void Main()
{
normalSpeedTest();
parallelSpeedTest();

Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Press a key ...");
Console.ReadKey();
}

private static void parallelSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("parallelSpeedTest");

long totalObjectsCreated = 0;
long totalElapsedTime = 0;

var tasks = new List<Task>();
var processorCount = Environment.ProcessorCount;

Console.WriteLine("Running on {0} cores", processorCount);

for (var t = 0; t < processorCount; t++)
{
tasks.Add(Task.Factory.StartNew(
() =>
{
const int reps = 1000000000;
var sp = Stopwatch.StartNew();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Interlocked.Add(ref totalObjectsCreated, reps);
Interlocked.Add(ref totalElapsedTime, sp.ElapsedMilliseconds);
}
));
}

// let's complete all the tasks
Task.WaitAll(tasks.ToArray());

Console.WriteLine("Created {0:N} objects in 1 sec\n", (totalObjectsCreated / (totalElapsedTime / processorCount)) * 1000);
}

private static void normalSpeedTest()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("normalSpeedTest");

const int reps = 1000000000;
var sp = Stopwatch.StartNew();
sp.Start();
for (var j = 0; j < reps; ++j)
{
new object();
}
sp.Stop();

Console.WriteLine("Created {0:N} objects in 1 sec\n", (reps / sp.ElapsedMilliseconds) * 1000);
}
}
}

هنگام اجرای برنامه فراموش نکنید که حالت Build را بر روی Release قرار دهید.



مطالب
SQL Instance
ممکن است کاربر بر روی سیستم خود نسخه‌های مختلفی از SQL Server را نصب کرده باشد. برای مثال SQL Express, SQL 2005, SQL 2008. و یا نسخه ای خاص (مثلا 2012) را چند بار روی سیستم خود نصب کرده باشد. SQL برای تفکیک این نسخه‌ها و نصب‌ها از مفهومی با عنوان Instance استفاده می‌کند. یعنی به هر نسخه نصب شده نامی یکتا می‌دهد تا بتوان به تفکیک به آنها دسترسی داشت.
برای اتصال به این نسخه‌ها باید در بخش آدرس سرور، از ترکیب نام سیستم و نام Instance به این شکل استفاده کرد:  SystemName\Instance
بعضی مواقع لازم است که لیست Instance‌های نصب شده روی سیستم کاربر را به دست آوریم. ADO.NET کلاسی به همین منظور تعبیه کرده که شبکه را جستجو کرده و SQL Instance‌های مختلف را که قابل دسترسی هستند را برای شما لیست می‌کند. استفاده از این کلاس بسیار ساده است:
using System.Data.Sql;

class Program
{
  static void Main()
  {
    // Retrieve the enumerator instance and then the data.
    SqlDataSourceEnumerator instance =
      SqlDataSourceEnumerator.Instance;
    System.Data.DataTable table = instance.GetDataSources();

    // Display the contents of the table.
    DisplayData(table);

    Console.WriteLine("Press any key to continue.");
    Console.ReadKey();
  }

  private static void DisplayData(System.Data.DataTable table)
  {
    foreach (System.Data.DataRow row in table.Rows)
    {
      foreach (System.Data.DataColumn col in table.Columns)
      {
        Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
      }
      Console.WriteLine("============================");
    }
  }
}

البته با توجه به اینکه شبکه را جستجو می‌کند در نرم افزار شما وقفه خواهد انداخت. خوب اگه بخواهیم Instance‌های نصب شده روی سیستم کاربر را پیدا کنیم چی؟ ساده‌ترین و سریعترین راه استفاده از رجیستری سیستم است. نام Instance‌ها در رجیستری ویندوز در آدرس زیر قابل دسترسی است:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names

برای استفاده از این کلید در c# می‌توان از کد زیر کمک بگیرید:
            var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names");

            foreach (string sk in key.GetSubKeyNames())
            {
                var rkey = key.OpenSubKey(sk);
                foreach (string s in rkey.GetValueNames())
                {
                    MessageBox.Show("Sql instance name:" + s);
                }
            }
فقط دو نکته قابل توجه است. برنامه باید در Any CPU کامپایل شود تا در سیستم‌های 64 بیتی بتوانید به محل درست رجیستری دسترسی پیدا کنید. چون نرم افزارهای 32 بیت در ویندوز 64 بیت در سیستم wow64 اجرا می‌شود که دسترسی به رجیستری آن در آدرس wow64 هر قسمت رجیستری است. بنابراین کد فوق در حالت Any CPU و غیر فعال بودن Prefer 32-bit قسمت Build در Properties برنامه به درستی اجرا می‌شود.
نکته: Default Instance در SQL مقدار MSSQLSERVER  می‌باشد.
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
- این پروژه باید یکبار کامل کامپایل شود و همچنین پوشه‌ی bin آن موجود باشد.
- خطای Could not load type صرفا به معنای عدم کامپایل شدن برنامه و یا نبود فایل‌های dll مرتبط در پوشه‌ی bin است. مراجعه کنید به منوی Build و گزینه‌ی Rebuild solution را انتخاب کنید.
نظرات مطالب
ASP.NET MVC #7
- Model ذکر شده در حلقه با M بزرگ است و نه کوچک.
- ذکر model@ با m کوچک برای تعریف Model در ابتدای View ضروری است.
- پس از تعریف کلاس‌های مدل برنامه، کامپایل را فراموش نکنید (خیلی از قسمت‌ها بر اساس Reflection کار می‌کنند و پس از کامپایل قابل دسترسی می‌شوند).
مطالب
Ef 6 و Ngen : شروعی سریعتر برای برنامه های مبتنی بر Entity Framework
تولید کد Native زمانی اتفاق می‌افتد که کامپایلر JIT، کد اسمبلی‌های MSIL را به کدهای Native در ماشین محلی کامپایل می‌کند و این عمل بلافاصله قبل از اجرای متد برای اولین بار اتفاق می‌افتد. این کد به صورت موقتی بوده و در حافظه‌ای که برای پردازش در نظر گرفته شده ذخیره می‌شود و در پایان هر پردازش توسط سیستم عامل ویرایش می‌شود. کد Native به ازای هر بار شروع یک پردازش تولید می‌شود. ابزار Native Image Generator یا همان Ngen اقدام به تولید کد Native با استفاده از کامپایلر JIT نموده و آن را در هارد دیسک ذخیره می‌نماید. زمانیکه برنامه نیازمند یک اسمبلی CLR است، به جای بارگذاری خود اسمبلی، ایمیج کد Native آن بارگذاری می‌شود. به این نکته نیز توجه داشته باشید که CLR اطلاعاتی در مورد اینکه کدام اسمبلی، ایمیج کد Native است و این ایمیج در کجا و در چه زمانی تهیه شده است، دارد. کد Native باعث بهبود استفاده از حافظه می‌شود، زمانیکه یک اسمبلی بین پروسس‌ها به اشتراک گذاشته شده‌است. تا قبل از EF6 کتابخانه‌های هسته‌ای EF در زمان اجرا جزئی از دات نت فریمورک بودند و تولید کد Native آنها به صورت اتوماتیک انجام می‌شد. اما از نسخه 6، تمامی این کتابخانه‌ها در داخل پکیج Nuget آن ترکیب شده‌اند . پس برای تولید کد Native مربوط به فایل EntityFramework.dll نیازمند ابزار Ngen هستیم.
1- ابتدا یک برنامه‌ی ساده کنسول ویندوز ساخته و از Package Manager Console دستور Install-package entityframework را اجرا نموده تا پکیج Ef به برنامه اضافه گردد.
using System;
using System.Data.Entity;

namespace UsingNgen
{

    public class NgenDbContex : DbContext
    { }

    class Program
    {
        static void Main()
        {
            var nGenCtx = new NgenDbContex();
            Console.WriteLine("Press a key to exit...");
            Console.ReadKey();
        }
    }


}
حال کد ساده بالا را به برنامه اضافه می‌کنیم و برنامه را Build میکنیم.
2- برای ثبت جزئیات اجرای برنامه از ابزار Windows Performance Recorder که جزئی از ویندوز می‌باشد، استفاده می‌کنیم. کافیست عبارت WPR را در نوار جستجوی ویندوز تایپ کنید تا این ابزار در دسترس قرار گیرد. 



برای ضبط جزئیات، روی دکمه‌ی Start کلیک کنید و به محل ذخیره‌ی فایل اجرایی حاصل از Build ویژوال استودیو رفته و آن را اجرا کنید. بعد از اتمام اجرا، جزئیات را ذخیره نمایید.

بعد از ذخیره فایل، در پنجره بالا دکمه‌ای به نام Open in WPA ظاهر می‌شود. WPA مخفف Windows Performance Analyzer می‌باشد. آن را کلیک کنید تا محیط آنالایزر باز شود.

حال در سمت چپ این پنجره انواع آنالایزرها را مشاهده می‌کنید. روی آنالایزر Computation کلیک کنید و از زیرمجموعه‌ی آن، CPU Usage را انتخاب کنید. آمار مربوط به برنامه خودمان را در تصویر بالا مشاهده می‌کنید. کل برنامه 164 میلی ثانیه زمان برده و فایل Clr.dll حدود 47 میلی ثانیه و یک فایل clrjit.dll نیز برای تولید کد JIT وجود دارد. حال برای تسریع در عمل شروع، از تکنیک Ngen به صورت زیر استفاده می‌کنیم.

3- دوباره به نوار جستجوی ویندوز رفته و ابزار Developer Command Prompt for VsXXXX را با امتیاز دسترسی از نوع Admin اجرا کنید. XXXX نسخه‌ی ویژوال استودیو می‌باشد.

حال به محل ذخیره فایل اجرایی برنامه رفته و دستور Ngen Install EntityFramework.dll را تایپ کنید تا یک ایمیج کد Native از entityframework.dll ساخته شود. دوباره ابزار Windows Performance Recorder را لود کرده و روی دکمه Start کلیک کنید و فایل اجرایی برنامه را اجرا نمایید. پس از اتمام عملیات ثبت جزئیات، آن را در Windows Performance Analyzer باز نمایید.

همانطور که مشاهده می‌کنید کل برنامه ما 89 میلی ثانیه زمان برده و Clr.dll 29 ثانیه و به جای clrjit.dll فایل EntityFramework به صورت native تولید شده است.

نظرات مطالب
ASP.NET MVC #1
اما بازم متوجه نشدم که این تاخیر در ورود هر کاربر رخ میده یا اولین کامپایل برنامه ؟
یعنی با از بین رفتن سشن کاربر و ورود دوباره این تاخیر در بارگذاری را داریم ؟
مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
یک برنامه‌ی Angular، از گروهی از کامپوننت‌ها تشکیل می‌شود؛ برای مثال یک کامپوننت App وجود دارد که آن نیز از تعدادی کامپوننت مختلف تشکیل می‌شود. ماژول‌ها کار سازماندهی و بسته بندی این کامپوننت‌ها را انجام می‌دهند و با بزرگتر شدن برنامه می‌توان قسمت‌های مختلف را در ماژول‌های متفاوتی قرار داد. مزایای این روش به شرح زیر هستند:
- بهبود کپسوله سازی قسمت‌های مختلف برنامه با بسته بندی آن‌ها در ماژول‌های متفاوت
- فراهم آوردن امکان lazy loading و بهبود کارآیی برنامه


انواع ماژول‌های توصیه شده‌ی در برنامه‌های Angular

منهای App Module پیش‌فرض یک برنامه‌های Angular، ایجاد سه نوع ماژول دیگر نیز در جهت سازماندهی اینگونه برنامه‌ها توصیه می‌شوند:
- Core Module
هدف از آن فراهم آوردن سرویس‌های Singleton اشتراکی بین کامپوننت‌ها و ماژول‌های مختلف برنامه است. علت این‌جا است که سیستم تزریق وابستگی‌های Angular، به ازای هر ماژولی که Lazy loaded باشد، سرویس تزریقی در آن‌ها را مجددا وهله سازی می‌کند. به همین جهت نیاز است تک ماژول اختصاصی را برای مدیریت سرویس‌هایی که نیازی است تنها یکبار در طول عمر برنامه وهله سازی شوند، تدارک ببینیم و Core Module مکان مناسبی برای این‌کار است.
همچنین Code Module باید شامل کامپوننت‌هایی در سطح برنامه باشد. دراینجا منظور از «در سطح برنامه»، کامپوننت‌هایی که قرار است در بین تمام ماژول‌ها به اشتراک گذاشته شوند، نیست. منظور تنها کامپوننت‌هایی هستند که در App Component اصلی برنامه قرار است استفاده شوند؛ مانند منوی راهبری بالای سایت.

- Shared Module
هدف از آن مدیریت و بسته بندی کامپوننت‌ها، دایرکتیوها و Pipes اشتراکی بین تمام اجزای برنامه است. برای مثال کامپوننت «لطفا منتظر بمانید ...» اگر قرار است در تمام قسمت‌های برنامه استفاده شود، نیاز است در Shared Module تعریف شود. از این جهت که در یک برنامه‌ی Angular نمی‌توان یک کامپوننت را بین دو ماژول مختلف به اشتراک گذاشت. به همین جهت نیاز است یک مکان مرکزی برای تعریف این کامپوننت‌های اشتراکی ایجاد شود و سپس این تک ماژول را در قسمت‌های مختلف برنامه، بدون مشکل مورد استفاده قرار داد.

- Feature Module
این ماژول‌ها به ازای هر ویژگی برنامه ایجاد شده و کامپوننت‌ها، سرویس‌ها، دایرکتیوها و Pipes اختصاصی آن ویژگی را بسته بندی می‌کنند.


ایجاد Core Module

فرض کنید می‌خواهید اطلاعات کاربر جاری لاگین شده را در طول عمر برنامه نگهداری کنید و از آن در تمام قسمت‌های برنامه استفاده نمائید. یک چنین سرویسی نیاز است دارای طول عمر Singleton باشد و تنها یکبار وهله سازی شود تا اطلاعات کاربر جاری از دست نرود. به همین جهت بهترین مکان تعریف این سرویس، در Core Module است.
برای این منظور در ساختار برنامه‌ی خود، پوشه‌ی جدید src\app\core را ایجاد می‌کنیم. سپس فایل core.module.ts را به صورت ذیل در آن تعریف خواهیم کرد:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

import { UserRepositoryService } from './user-repository.service';
import { NavBarComponent } from './nav-bar.component';
import { AccountMenuComponent } from './account-menu.component';

@NgModule({
  imports: [ CommonModule, RouterModule ],
  exports: [ NavBarComponent, AccountMenuComponent ],
  declarations: [ NavBarComponent, AccountMenuComponent ],
  providers: [ UserRepositoryService ]
})
export class CoreModule { };
 - CoreModule در ابتدا تنها CommonModule و RouterModule را در صورت نیاز import می‌کند.
 - سپس سرویس‌های اشتراکی و Singleton برنامه در قسمت providers آن قرار می‌گیرند.
 - در اینجا همچنین دو کامپوننت منو که توسط app.component.ts مورد استفاده قرار می‌گیرند نیز import شده‌اند.
 - فایل‌های account-menu.component.ts، nav-bar.component.ts و user-repository.service.ts نیز به درون پوشه‌ی src\app\core منتقل خواهند شد (به همراه تمام فایل‌های html و css متناظر با آن‌ها).
 - اگر دقت کنید، قسمت exports این ماژول نیز مقدار دهی شده‌است. چون این کامپوننت‌ها قرار است خارج از این ماژول و در AppModule استفاده شوند، نیاز است آن‌ها را به صورت خروجی نیز معرفی کنیم.

اکنون جهت استفاده‌ی از این قابلیت‌ها، تنها کافی است تعریف CoreModule را به AppModule در فایل app.module.ts اضافه کنیم:
import { CoreModule } from "./core/core.module";

@NgModule({
  imports:      [
//...
    CoreModule,
//...
    RouterModule.forRoot(appRoutes)
  ],
//...
})
export class AppModule { }


ایجاد Shared Modules

در Shared Module اجزایی را قرار خواهیم داد که قرار است در بیش از یک ماژول مورد استفاده قرار گیرند. به همین جهت در ساختار برنامه‌ی خود، پوشه‌ی جدید src\app\shared را ایجاد می‌کنیم. سپس در آن، ماژول جدید shared.module.ts را ایجاد خواهیم کرد:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { LoadingSpinnerComponent } from './loading-spinner.component';

@NgModule({
  imports: [ CommonModule ],
  declarations: [ LoadingSpinnerComponent ],
  exports: [ LoadingSpinnerComponent, CommonModule ],
  providers: [ ]
})
export class SharedModule { };
ساختار این ماژول نیز شبیه به Core Module است. ابتدای CommonModule به آن import شده‌است. سپس کامپوننت‌هایی که قرار است در بین سایر ماژول‌های سایت به اشتراک گذاشته شوند (برای مثال یک کامپوننت Loading Spinner فرضی)، در هر دو قسمت declarations و exports این ماژول اشتراکی قرار می‌گیرند. همچنین فایل loading-spinner.component.ts و تمام اجزای وابسته‌ی به آن نیز به پوشه‌ی src\app\shared منتقل می‌شوند.
از این جهت که اجزای خروجی این ماژول قرار است در Feature Moduleها استفاده شوند، CommonModule مورد استفاده‌ی در آن‌ها نیز در قسمت exports ذکر شده‌است.

اکنون جهت استفاده‌ی از این قابلیت‌ها، تنها کافی است تعریف SharedModule را به AppModule در فایل app.module.ts اضافه کنیم:
import { CoreModule } from "./core/core.module";
import { SharedModule } from "./shared/shared.module";

@NgModule({
  imports:      [
//...
    CoreModule,
    SharedModule,
//...
    RouterModule.forRoot(appRoutes)
  ],
//...
})
export class AppModule { }


ایجاد Feature Modules

این مورد نکته‌ی ویژه‌ای را به همراه ندارد و همانند ایجاد سایر ماژول‌های برنامه‌است. برای مثال ویژگی مدیریت کاربران، به همراه تمام اجزای آن درون ماژول کاربران قرار می‌گیرد و به همین ترتیب برای سایر ویژگی‌های دیگر برنامه. ایجاد و مدیریت اینگونه ماژول‌ها توسط Angular CLI بسیار ساده‌است:
> ng g m users -m app.module --routing
> ng g c users/users-list
دستور اول ایجاد ماژول جدید users، پوشه‌ی مرتبط با آن و همچنین به روز رسانی فایل app.module را به صورت خودکار انجام می‌دهد.
دستور دوم نیز کامپوننتی را به این ماژول اضافه می‌کند؛ به همراه به روز رسانی تعاریف این ماژول.

فقط در اینجا SharedModule ایی را که پیشتر اضافه کردیم، به قسمت imports آن اضافه می‌کنیم:
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { SharedModule } from '../shared/shared.module';
import { UsersListComponent } from './users-list.component';

@NgModule({
  imports: [ RouterModule, SharedModule ],
  declarations: [ UsersListComponent ],
  exports: [  ],
  providers: [ ]
})
export class UsersModule { };
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
من با این صورت فایل رو آپدیت می‌کنم
        public void UpdateSettings(SiteSettings siteSettings)
        {
            var path = System.IO.Path.Combine(_hostingEnvironment.WebRootPath, "appsettings.json");
            System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(path, false);
            streamWriter.Write(Newtonsoft.Json.JsonConvert.SerializeObject(siteSettings));
            streamWriter.Close();
        }
ولی نه در فایل تغییری ایجاد می‌شه و نه در مقادیر :
        private readonly IOptionsSnapshot<SiteSettings> _siteOptions;
برنامه خطای دسترسی هم نمی‌دهد!