جایگاه کتابخانههای ثالث(third party lib) همانند ngx bootstrap (برای استفاده از امکانات بوت استرپ بدون استفاده از جی کوئری)در این ساختار به چه شکل خواهد بود. آیا باید همگی در root یا همان app.module نصب و فعالسازی گردد برای استفاده در بقیه جاهای برنامه یا بهتره که به ماژول خاصی منتقل گردند.
در معماری vertical slices با features سر و کار داریم؛ برای مثال برنامهی ما دو ویژگی نویسندهها و بلاگها را خواهد داشت و هر ویژگی، کاملا متکی به خود است. برای نمونه هر ویژگی میتواند به همراه یک ماژول باشد که به صورت مستقل، تمام سرویسها، endpoints و میانافزارهای مورد نیاز خودش را ثبت میکند. در این معماری، تمام قسمتهای مورد نیاز جهت کارکرد یک ویژگی، در کنار هم قرار میگیرند تا یافتن آنها و درک ارتباطات بین آنها سادهتر شود.
تعریف ساختار ماژولهای ویژگیهای معماری vertical slices
برای تعریف ساختار ماژولی که کار ثبت تمام نیازمندیهای یک ویژگی را انجام میدهد، مانند ثبت سرویسها، endpoints و میانافزارها، ابتدا پوشهای به نام Contracts را به پروژهی Api اضافه میکنیم؛ با این اینترفیس:
ثبت خودکار ماژولهای برنامه در ابتدای اجرای آن
پس از تعریف این قرارداد، اکنون میخواهیم هر ماژولی که در برنامه، اینترفیس فوق را پیاده سازی میکند، در ابتدای اجرای برنامه به صورت خودکار، یافت شده و اطلاعات آن به سیستم اضافه شود. برای این منظور متدهای الحاقی زیر را تعریف میکنیم:
این کلاس ساختار سادهای دارد؛ ابتدا در متد AddAllModules، اسمبلی جاری جهت یافتن کلاسهای پیاده سازی کنندهی اینترفیس IModule، اسکن میشود؛ با استفاده از کتابخانهی Scrutor.
سپس کلاسهای ثبت شده که هم اکنون جزئی از سیستم تزریق وابستگیهای برنامه هستند، یافت شده و متد RegisterEndpoints آنها فراخوانی میشوند تا دیگر نیازی نباشد به ازای هر ماژول، یکبار ثبت دستی این موارد در کلاس Program انجام شود.
بنابراین در ادامه به کلاس Program مراجعه کرد و متد عمومی کلاس فوق را در آن به صورت app.RegisterEndpoints فراخوانی میکنیم:
این چند سطر، کل محتوای فایل Program.cs برنامه را تشکیل میدهند.
ایجاد اولین Feature برنامه؛ ویژگی نویسندگان
برای تعریف اولین ویژگی برنامه که مختص به نویسندگان است، پوشههای جدید Features\Authors را در برنامهی Api ایجاد میکنیم.
- اولین کاری را که در ادامه انجام خواهیم داد، انتقال فایل AuthorDto.cs که در قسمت قبل ایجاد کردیم، به درون این پوشهی جدید است.
- سپس ماژول نویسندگان را به صورت زیر به آن اضافه میکنیم:
در اینجا ماژول نویسندگان را که با پیاده سازی قرارداد IModule تشکیل شدهاست، مشاهده میکنید. در متد RegisterEndpoints آن، دو endpoints تعریف شدهی در کلاس Program برنامه را در قسمت قبل، Cut کرده و به اینجا منتقل کردهایم. بنابراین اکنون کلاس Program، دیگر به همراه تعریف مستقیم هیچ endpoint ای نیست و خلوت شدهاست. هدف از Features هم دقیقا همین است تا هر ویژگی برنامه، متکی به خود بوده و مستقل باشد؛ به همراه تمام تعاریف مورد نیاز جهت کار با آن در یک محل مشخص (مانند انتقال فایل Dto مربوط به آن، به درون همین پوشه). مزیت این روش، درک سادهتر اجزای مرتبط و یافتن سریعتر ارتباطات قسمتهای یک ویژگی خاص است. در آینده اگر مشکلی رخ داد و باگی بروز پیدا کرد، دقیقا میدانیم که محدودهای که باید مورد بررسی قرار گیرد، کجاست و این محدوده، کوچک و متکی به خود است و در بین چندین پروژهی مختلف، پراکنده نشدهاست.
کار نمونه سازی و اجرای متدهای این ماژولها نیز توسط متدهای الحاقی کلاس ModuleExtensions، در ابتدای اجرای برنامه به صورت خودکار انجام میشود و نیازی به شلوغ کردن کلاس Program برای ثبت دستی آنها نیست.
افزودن AutoMapper و MediatR به پروژهی Api
در ادامه برای ساده سازی کار نگاشتهای Dtoهای برنامه به مدلهای دومین آن، از AutoMapper استفاده خواهیم کرد؛ همچنین از MediatR نیز برای پیاده سازی الگوی CQRS که در قسمت بعدی پیگیری خواهد شد. بنابراین در ابتدا بستههای نیوگت این دو را به پروژهی Api اضافه میکنیم:
سپس به کلاس ServiceCollectionExtensions مراجعه کرده و تعاریف ثبت سرویسهای این دو را نیز اضافه میکنیم:
اکنون میتوان اولین Profile مربوط به AutoMapper را که کار نگاشت AuthorDto به Author و برعکس را انجام میدهد، به صورت زیر تهیه کنیم:
این فایل نیز درون پوشهی Features\Authors قرار میگیرد.
تعریف ساختار ماژولهای ویژگیهای معماری vertical slices
برای تعریف ساختار ماژولی که کار ثبت تمام نیازمندیهای یک ویژگی را انجام میدهد، مانند ثبت سرویسها، endpoints و میانافزارها، ابتدا پوشهای به نام Contracts را به پروژهی Api اضافه میکنیم؛ با این اینترفیس:
namespace MinimalBlog.Api.Contracts; public interface IModule { IEndpointRouteBuilder RegisterEndpoints(IEndpointRouteBuilder endpoints); }
ثبت خودکار ماژولهای برنامه در ابتدای اجرای آن
پس از تعریف این قرارداد، اکنون میخواهیم هر ماژولی که در برنامه، اینترفیس فوق را پیاده سازی میکند، در ابتدای اجرای برنامه به صورت خودکار، یافت شده و اطلاعات آن به سیستم اضافه شود. برای این منظور متدهای الحاقی زیر را تعریف میکنیم:
public static class ServiceCollectionExtensions { public static IServiceCollection AddApplicationServices(this IServiceCollection services, WebApplicationBuilder builder) { // ... builder.Services.AddAllModules(typeof(Program)); return services; } private static void AddAllModules(this IServiceCollection services, params Type[] types) { // Using the `Scrutor` to add all of the application's modules at once. services.Scan(scan => scan.FromAssembliesOf(types) .AddClasses(classes => classes.AssignableTo<IModule>()) .AsImplementedInterfaces() .WithSingletonLifetime()); } }
سپس کلاسهای ثبت شده که هم اکنون جزئی از سیستم تزریق وابستگیهای برنامه هستند، یافت شده و متد RegisterEndpoints آنها فراخوانی میشوند تا دیگر نیازی نباشد به ازای هر ماژول، یکبار ثبت دستی این موارد در کلاس Program انجام شود.
using MinimalBlog.Api.Contracts; namespace MinimalBlog.Api.Extensions; public static class ModuleExtensions { public static WebApplication RegisterEndpoints(this WebApplication app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } var modules = app.Services.GetServices<IModule>(); foreach (var module in modules) { module.RegisterEndpoints(app); } return app; } }
using MinimalBlog.Api.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddApplicationServices(builder); var app = builder.Build(); app.ConfigureApplication(); app.RegisterEndpoints(); app.Run();
ایجاد اولین Feature برنامه؛ ویژگی نویسندگان
برای تعریف اولین ویژگی برنامه که مختص به نویسندگان است، پوشههای جدید Features\Authors را در برنامهی Api ایجاد میکنیم.
- اولین کاری را که در ادامه انجام خواهیم داد، انتقال فایل AuthorDto.cs که در قسمت قبل ایجاد کردیم، به درون این پوشهی جدید است.
- سپس ماژول نویسندگان را به صورت زیر به آن اضافه میکنیم:
namespace MinimalBlog.Api.Features.Authors; public class AuthorModule : IModule { public IEndpointRouteBuilder RegisterEndpoints(IEndpointRouteBuilder endpoints) { endpoints.MapGet("/api/authors", async (MinimalBlogDbContext ctx) => { var authors = await ctx.Authors.ToListAsync(); return authors; }); endpoints.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) => { var author = new Author(); author.FirstName = authorDto.FirstName; author.LastName = authorDto.LastName; author.Bio = authorDto.Bio; author.DateOfBirth = authorDto.DateOfBirth; ctx.Authors.Add(author); await ctx.SaveChangesAsync(); return author; }); return endpoints; } }
کار نمونه سازی و اجرای متدهای این ماژولها نیز توسط متدهای الحاقی کلاس ModuleExtensions، در ابتدای اجرای برنامه به صورت خودکار انجام میشود و نیازی به شلوغ کردن کلاس Program برای ثبت دستی آنها نیست.
افزودن AutoMapper و MediatR به پروژهی Api
در ادامه برای ساده سازی کار نگاشتهای Dtoهای برنامه به مدلهای دومین آن، از AutoMapper استفاده خواهیم کرد؛ همچنین از MediatR نیز برای پیاده سازی الگوی CQRS که در قسمت بعدی پیگیری خواهد شد. بنابراین در ابتدا بستههای نیوگت این دو را به پروژهی Api اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" /> <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" /> </ItemGroup> </Project>
public static class ServiceCollectionExtensions { public static IServiceCollection AddApplicationServices(this IServiceCollection services, WebApplicationBuilder builder) { // ... builder.Services.AddMediatR(typeof(Program)); builder.Services.AddAutoMapper(typeof(Program)); return services; } }
using AutoMapper; using MinimalBlog.Domain.Model; namespace MinimalBlog.Api.Features.Authors; public class AuthorProfile : Profile { public AuthorProfile() { CreateMap<AuthorDto, Author>().ReverseMap(); } }
نگارش AspNetCore.SignalR 1.0.0-alpha1-final چند روزی هست که منتشر شدهاست. در این مطلب قصد داریم یک برنامهی وب ASP.NET Core 2.0 را به همراه یک Hub ایجاد کرده و سپس این Hub را در یک کلاینت Angular (2+) مورد استفاده قرار دهیم.
پیشنیازها
برای دنبال کردن این مثال فرض بر این است که NET Core 2.0 SDK. و همچنین Angular CLI را نیز پیشتر نصب کردهاید. مابقی بحث توسط خط فرمان و ابزارهای dotnet cli و angular cli ادامه داده خواهند شد و الزامی به نصب هیچگونه IDE نیست و این مثال تنها توسط VSCode پیگیری شدهاست.
تدارک ساختار ابتدایی مثال جاری
ساخت برنامهی وب، توسط dotnet cli
ابتدا یک پوشهی جدید را به نام SignalRCore2Sample ایجاد میکنیم. سپس داخل این پوشه، پوشهی دیگری را به نام SignalRCore2WebApp ایجاد خواهیم کرد (تصویر فوق). از طریق خط فرمان به این پوشه وارد شده (در ویندوز، در نوار آدرس، دستور cmd.exe را تایپ و enter کنید) و سپس فرمان ذیل را صادر میکنیم:
این دستور، یک برنامهی جدید ASP.NET Core 2.0 را تولید خواهد کرد.
ساخت برنامهی کلاینت، توسط angular cli
سپس از طریق خط فرمان به پوشهی SignalRCore2Sample بازگشته و دستور ذیل را صادر میکنیم:
این دستور، یک برنامهی Angular را در پوشهی SignalRCore2Client تولید میکند (تصویر فوق).
اکنون که در پوشهی ریشهی SignalRCore2Sample قرار داریم، اگر در خط فرمان، دستور . code را صادر کنیم، VSCode هر دو پوشهی وب و client را با هم در اختیار ما قرار میدهد:
تکمیل پیشنیازهای برنامهی وب
پس از ایجاد ساختار اولیهی برنامههای وب ASP.NET Core و کلاینت Angular، اکنون نیاز است وابستگی جدید AspNetCore.SignalR را به آن معرفی کنیم. به همین جهت به فایل SignalRCore2WebApp.csproj مراجعه کرده و تغییرات ذیل را به آن اعمال میکنیم:
در اینجا ابتدا بستهی Microsoft.AspNetCore.SignalR اضافه شدهاست. همچنین Microsoft.DotNet.Watcher.Tools را نیز اضافه کردهایم تا بتوان از مزیت build تدریجی پروژه، به ازای هر تغییر صورت گرفته، استفاده کنیم.
پس از این تغییرات، دستور ذیل را در خط فرمان صادر میکنیم تا وابستگیهای پروژه نصب شوند:
البته اگر افزونهی #C مخصوص VSCode را نصب کرده باشید، تغییرات فایل csproj را دنبال کرده و پیام restore را نیز ظاهر میکند؛ تا همین دستور فوق را به صورت خودکار اجرا کند.
یک نکته: نگارش فعلی افزونهی #C مخصوص VSCode، با تغییر فایل csproj و restore وابستگیهای آن نیاز دارد یکبار آنرا بسته و سپس مجددا اجرا کنید، تا اطلاعات intellisense خود را به روز رسانی کند. بنابراین اگر VSCode بلافاصله کلاسهای مرتبط با بستههای جدید را تشخیص نمیدهد، علت صرفا این موضوع است.
پس از بازیابی وابستگیها، به ریشهی پروژهی برنامهی وب وارد شده و دستور ذیل را صادر کنید:
این دستور، پروژه را build کرده و سپس بر روی پورت 5000 ارائه میدهد. همچنین به ازای هر تغییری در فایلهای کدهای برنامه، به صورت خودکار برنامه را build کرده و مجددا ارائه میدهد.
تکمیل برنامهی وب جهت ارسال پیامهایی به کلاینتهای متصل به آن
پس از افزودن وابستگیهای مورد نیاز، بازیابی و build برنامه، اکنون نوبت به تعریف یک Hub است، تا از طریق آن بتوان پیامهایی را به کلاینتهای متصل ارسال کرد. به همین جهت یک پوشهی جدید را به نام Hubs به پروژهی وب افزوده و سپس کلاس جدید MessageHub را به صورت ذیل به آن اضافه میکنیم:
این کلاس از کلاس پایه Hub مشتق میشود. سپس در متد Send آن میتوان پیامهایی را به کلاینتهای متصل به برنامه ارسال کرد.
پس از تعریف این Hub، نیاز است به کلاس Startup مراجعه کرده و دو تغییر ذیل را اعمال کنیم:
الف) ثبت و معرفی سرویس SignalR
ابتدا باید SignalR را فعالسازی کرد. به همین جهت نیاز است سرویسهای آنرا به صورت یکجا توسط متد الحاقی AddSignalR در متد ConfigureServices به نحو ذیل معرفی کرد:
ب) ثبت مسیریابی دسترسی به Hub
پس از تعریف Hub، مرحلهی بعدی، مشخص سازی نحوهی دسترسی به آن است. به همین جهت در متد Configure، به نحو ذیل Hub را معرفی کرده و سپس یک path را برای آن مشخص میکنیم:
یعنی اکنون این Hub در آدرس ذیل قابل دسترسی است:
این آدرسی است که در کلاینت Angular، از آن برای اتصال به هاب، استفاده خواهیم کرد.
انتشار پیامهایی به تمام کاربران متصل به برنامه
آدرس فوق به تنهایی کار خاصی را انجام نمیدهد. از آن جهت اتصال کلاینتهای برنامه استفاده میشود و این کلاینتها پیامهای رسیدهی از طرف برنامه را از این آدرس دریافت خواهند کرد. بنابراین مرحلهی بعد، ارسال تعدادی پیام به سمت کلاینتها است. برای این منظور به HomeController برنامهی وب مراجعه کرده و آنرا به نحو ذیل تغییر میدهیم:
برای دسترسی به Hubهای تعریف شده میتوان از سیستم تزریق وابستگیها استفاده کرد. برای این منظور تنها کافی است Hub مدنظر را به عنوان آرگومان جنریک IHubContext تعریف کرد. سپس از طریق آن میتوان به این context، در قسمتهای مختلف برنامه دسترسی یافت و برای مثال پیامهایی را به کاربران ارائه داد.
در این مثال ابتدا View ذیل نمایش داده میشود:
کار آن فرستادن یک پیام به متد Index است. سپس این متد، به کمک context تزریق شدهی Hub پیامها، این پیام را به تمام کلاینتهای متصل ارسال میکند.
تکمیل برنامهی کلاینت Angular جهت نمایش پیامهای رسیدهی از طرف سرور
تا اینجا ساختار ابتدایی برنامهی Angular را توسط Angular CLI ایجاد کردیم. اکنون نیاز است وابستگی سمت کلاینت SignalR Core را نصب کنیم. به همین جهت از طریق خط فرمان به پوشهی SignalRCore2Client وارد شده و دستور ذیل را صادر کنید:
پرچم save آن سبب خواهد شد تا این وابستگی علاوه بر نصب، در فایل package.json نیز درج شود.
کلاینت رسمی signalr، هم جاوا اسکریپتی است و هم تایپاسکریپتی. به همین جهت به سادگی توسط یک برنامهی تایپ اسکریپتی Angular قابل استفاده است. کلاسهای آنرا در مسیر node_modules\@aspnet\signalr-client\dist\src میتوانید مشاهده کنید.
در ابتدا، فایل app.component.ts را به نحو ذیل تغییر میدهیم:
در اینجا در ابتدا، کلاس HubConnection از ماژول aspnet/signalr-client@ دریافت شدهاست. سپس بر این اساس در ngOnInit، یک وهله از آن که به مسیر Hub تعریف شدهی برنامه اشاره میکند، ایجاد خواهد شد. هر زمانیکه پیامی از سمت سرور دریافت گردید، این پیام را به لیست messages، که یک آرایه است اضافه میکنیم. در آخر برای راه اندازی این اتصال، متد start آنرا فراخوانی خواهیم کرد. در اینجا میتوان یک متد سمت سرور را فراخوانی کرد و یا برقراری اتصال را در کنسول developers مرورگر نمایش داد.
آرایهی messages را به نحو ذیل توسط یک حلقه در قالب این کامپوننت نمایش خواهیم داد:
پس از آن به ریشهی پروژهی کلاینت مراجعه کرده و دستور ذیل را صادر میکنیم تا برنامهی Angular ساخته شده و در مرورگر پیش فرض سیستم نمایش داده شود:
در این حالت برنامه در آدرس http://localhost:4200/ قابل دسترسی خواهد بود.
همانطور که مشاهده میکنید، پیام خطای ذیل را صادر کردهاست:
علت اینجا است که برنامهی Angular بر روی پورت 4200 کار میکند و برنامهی وب ما بر روی پورت 5000 تنظیم شدهاست. به همین جهت نیاز است CORS را در برنامهی وب تنظیم کرد تا امکان یک چنین دسترسی صادر شود.
برای این منظور به فایل آغازین برنامهی وب مراجعه کرده و سرویسهای AddCors را به مجموعهی سرویسهای برنامه اضافه میکنیم:
پس از آن در متد Configure، این سیاست دسترسی باید مورد استفاده قرار گیرد؛ و گرنه این تنظیمات کار نخواهد کرد. محل قرارگیری آن نیز باید پیش از سایر تنظیمات باشد:
اکنون اگر مجددا برنامهی Angular را Refresh کنیم، در console توسعه دهندگان مرورگر، مشاهده خواهیم کرد که اتصال برقرار شدهاست:
در آخر برای آزمایش برنامه، به آدرس http://localhost:5000 یا همان برنامهی وب، مراجعه کرده و پیامی را ارسال کنید. بلافاصله مشاهده خواهید کرد که این پیام توسط کلاینت Angular دریافت شده و نمایش داده میشود:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SignalRCore2Sample.zip
برای اجرا آن، ابتدا به پوشهی SignalRCore2WebApp مراجعه کرده و دو فایل bat آنرا به ترتیب اجرا کنید. اولی وابستگیهای برنامه را بازیابی میکند و دومی برنامه را بر روی پورت 5000 ارائه میدهد.
سپس به پوشهی SignalRCore2Client مراجعه کرده و در آنجا نیز دو فایل bat ابتدایی آنرا به ترتیب اجرا کنید. اولی وابستگیهای برنامهی Angular را بازیابی میکند و دومی برنامهی Angular را بر روی پورت 4200 اجرا خواهد کرد.
پیشنیازها
برای دنبال کردن این مثال فرض بر این است که NET Core 2.0 SDK. و همچنین Angular CLI را نیز پیشتر نصب کردهاید. مابقی بحث توسط خط فرمان و ابزارهای dotnet cli و angular cli ادامه داده خواهند شد و الزامی به نصب هیچگونه IDE نیست و این مثال تنها توسط VSCode پیگیری شدهاست.
تدارک ساختار ابتدایی مثال جاری
ساخت برنامهی وب، توسط dotnet cli
ابتدا یک پوشهی جدید را به نام SignalRCore2Sample ایجاد میکنیم. سپس داخل این پوشه، پوشهی دیگری را به نام SignalRCore2WebApp ایجاد خواهیم کرد (تصویر فوق). از طریق خط فرمان به این پوشه وارد شده (در ویندوز، در نوار آدرس، دستور cmd.exe را تایپ و enter کنید) و سپس فرمان ذیل را صادر میکنیم:
dotnet new mvc
ساخت برنامهی کلاینت، توسط angular cli
سپس از طریق خط فرمان به پوشهی SignalRCore2Sample بازگشته و دستور ذیل را صادر میکنیم:
ng new SignalRCore2Client
اکنون که در پوشهی ریشهی SignalRCore2Sample قرار داریم، اگر در خط فرمان، دستور . code را صادر کنیم، VSCode هر دو پوشهی وب و client را با هم در اختیار ما قرار میدهد:
تکمیل پیشنیازهای برنامهی وب
پس از ایجاد ساختار اولیهی برنامههای وب ASP.NET Core و کلاینت Angular، اکنون نیاز است وابستگی جدید AspNetCore.SignalR را به آن معرفی کنیم. به همین جهت به فایل SignalRCore2WebApp.csproj مراجعه کرده و تغییرات ذیل را به آن اعمال میکنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha1-final" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" /> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" /> </ItemGroup> </Project>
پس از این تغییرات، دستور ذیل را در خط فرمان صادر میکنیم تا وابستگیهای پروژه نصب شوند:
dotnet restore
یک نکته: نگارش فعلی افزونهی #C مخصوص VSCode، با تغییر فایل csproj و restore وابستگیهای آن نیاز دارد یکبار آنرا بسته و سپس مجددا اجرا کنید، تا اطلاعات intellisense خود را به روز رسانی کند. بنابراین اگر VSCode بلافاصله کلاسهای مرتبط با بستههای جدید را تشخیص نمیدهد، علت صرفا این موضوع است.
پس از بازیابی وابستگیها، به ریشهی پروژهی برنامهی وب وارد شده و دستور ذیل را صادر کنید:
dotnet watch run
تکمیل برنامهی وب جهت ارسال پیامهایی به کلاینتهای متصل به آن
پس از افزودن وابستگیهای مورد نیاز، بازیابی و build برنامه، اکنون نوبت به تعریف یک Hub است، تا از طریق آن بتوان پیامهایی را به کلاینتهای متصل ارسال کرد. به همین جهت یک پوشهی جدید را به نام Hubs به پروژهی وب افزوده و سپس کلاس جدید MessageHub را به صورت ذیل به آن اضافه میکنیم:
using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; namespace SignalRCore2WebApp.Hubs { public class MessageHub : Hub { public Task Send(string message) { return Clients.All.InvokeAsync("Send", message); } } }
پس از تعریف این Hub، نیاز است به کلاس Startup مراجعه کرده و دو تغییر ذیل را اعمال کنیم:
الف) ثبت و معرفی سرویس SignalR
ابتدا باید SignalR را فعالسازی کرد. به همین جهت نیاز است سرویسهای آنرا به صورت یکجا توسط متد الحاقی AddSignalR در متد ConfigureServices به نحو ذیل معرفی کرد:
public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); services.AddMvc(); }
ب) ثبت مسیریابی دسترسی به Hub
پس از تعریف Hub، مرحلهی بعدی، مشخص سازی نحوهی دسترسی به آن است. به همین جهت در متد Configure، به نحو ذیل Hub را معرفی کرده و سپس یک path را برای آن مشخص میکنیم:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseSignalR(routes => { routes.MapHub<MessageHub>(path: "message"); });
http://localhost:5000/message
انتشار پیامهایی به تمام کاربران متصل به برنامه
آدرس فوق به تنهایی کار خاصی را انجام نمیدهد. از آن جهت اتصال کلاینتهای برنامه استفاده میشود و این کلاینتها پیامهای رسیدهی از طرف برنامه را از این آدرس دریافت خواهند کرد. بنابراین مرحلهی بعد، ارسال تعدادی پیام به سمت کلاینتها است. برای این منظور به HomeController برنامهی وب مراجعه کرده و آنرا به نحو ذیل تغییر میدهیم:
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using SignalRCore2WebApp.Hubs; namespace SignalRCore2WebApp.Controllers { public class HomeController : Controller { private readonly IHubContext<MessageHub> _messageHubContext; public HomeController(IHubContext<MessageHub> messageHubContext) { _messageHubContext = messageHubContext; } public IActionResult Index() { return View(); // show the view } [HttpPost] public async Task<IActionResult> Index(string message) { await _messageHubContext.Clients.All.InvokeAsync("Send", message); return View(); } } }
در این مثال ابتدا View ذیل نمایش داده میشود:
@{ ViewData["Title"] = "Home Page"; } <form method="post" asp-action="Index" asp-controller="Home" role="form"> <div class="form-group"> <label label-for="message">Message: </label> <input id="message" name="message" class="form-control"/> </div> <button class="btn btn-primary" type="submit">Send</button> </form>
تکمیل برنامهی کلاینت Angular جهت نمایش پیامهای رسیدهی از طرف سرور
تا اینجا ساختار ابتدایی برنامهی Angular را توسط Angular CLI ایجاد کردیم. اکنون نیاز است وابستگی سمت کلاینت SignalR Core را نصب کنیم. به همین جهت از طریق خط فرمان به پوشهی SignalRCore2Client وارد شده و دستور ذیل را صادر کنید:
npm install @aspnet/signalr-client --save
کلاینت رسمی signalr، هم جاوا اسکریپتی است و هم تایپاسکریپتی. به همین جهت به سادگی توسط یک برنامهی تایپ اسکریپتی Angular قابل استفاده است. کلاسهای آنرا در مسیر node_modules\@aspnet\signalr-client\dist\src میتوانید مشاهده کنید.
در ابتدا، فایل app.component.ts را به نحو ذیل تغییر میدهیم:
import { Component, OnInit } from "@angular/core"; import { HubConnection } from "@aspnet/signalr-client"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent implements OnInit { hubPath = "http://localhost:5000/message"; messages: string[] = []; ngOnInit(): void { const connection = new HubConnection(this.hubPath); connection.on("send", data => { this.messages.push(data); }); connection.start().then(() => { // connection.invoke("send", "Hello"); console.log("connected."); }); } }
آرایهی messages را به نحو ذیل توسط یک حلقه در قالب این کامپوننت نمایش خواهیم داد:
<div> <h1> The messages from the server: </h1> <ul> <li *ngFor="let message of messages"> {{message}} </li> </ul> </div>
ng serve -o
همانطور که مشاهده میکنید، پیام خطای ذیل را صادر کردهاست:
Failed to load http://localhost:5000/message: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.
برای این منظور به فایل آغازین برنامهی وب مراجعه کرده و سرویسهای AddCors را به مجموعهی سرویسهای برنامه اضافه میکنیم:
public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); }); services.AddMvc(); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseCors(policyName: "CorsPolicy");
در آخر برای آزمایش برنامه، به آدرس http://localhost:5000 یا همان برنامهی وب، مراجعه کرده و پیامی را ارسال کنید. بلافاصله مشاهده خواهید کرد که این پیام توسط کلاینت Angular دریافت شده و نمایش داده میشود:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SignalRCore2Sample.zip
برای اجرا آن، ابتدا به پوشهی SignalRCore2WebApp مراجعه کرده و دو فایل bat آنرا به ترتیب اجرا کنید. اولی وابستگیهای برنامه را بازیابی میکند و دومی برنامه را بر روی پورت 5000 ارائه میدهد.
سپس به پوشهی SignalRCore2Client مراجعه کرده و در آنجا نیز دو فایل bat ابتدایی آنرا به ترتیب اجرا کنید. اولی وابستگیهای برنامهی Angular را بازیابی میکند و دومی برنامهی Angular را بر روی پورت 4200 اجرا خواهد کرد.
لطفا قسمت دوم را در اینجا مطالعه بفرمایید
همچنین یک Named Constructor یا سازندهی با نام را به کلاس PirateName بصورت زیر اضافه کنید. توضیحات
جهت ذخیره سازی آخرین تغییرات کلاس PirateName در فضای ذخیره سازی Local، از یک کلید استفاده میکنیم که مقدار آن محتوای PirateName میباشد. در واقع فضای ذخیره سازی Local دادهها را به صورت جفت کلید-مقدار یا Key-Value Pairs نگهداری مینماید. جهت تعریف کلید، یک متغیر رشته ای را بصورت top-level و به شکل زیر تعریف کنید.
زمانیکه تغییری در badge name صورت گرفت، این تغییرات را در فضای ذخیره سازی Local، توسط ویژگی window.localStorage ذخیره مینماییم. تغییرات زیر را در تابع setBadgeName اعمال نمایید
تابع getBadgeNameFromStorage را بصورت top-level تعریف نمایید. این تابع دادههای ذخیره شده را از Local Storage بازیابی نموده و یک شی از نوع کلاس PirateName ایجاد مینماید.
در پایان نیز تابع setBadgeName را به منظور مقدار دهی اولیه به badge name، در تابع main، فراخوانی مینماییم.
حال به مانند گامهای قبل برنامه را اجرا و بررسی نمایید.
این فایل شامل یک شی Json با دو لیست رشته ای میباشد.
این دو المنت پس از اینکه تمامی نامها از فایل Json با موفقیت خوانده شدند فعال میگردند. توضیحات توضیحات
تغییرات زیر را در تابع main ایجاد کنید.
کد زیر را نیز به منظور خواندن نامها از فایل Json اضافه کنید. در این کد اجرای موفقیت آمیز درخواست و عدم اجرای درخواست، هر دو به شکلی مناسب مدیریت شده اند. توضیحات
خدمت دوستان عزیز مطلبی را عرض کنم که البته باید در ابتدای این سری مقالات متذکر میشدم. این سری مقالات Dart مرجع کاملی برای یادگیری Dart نمیباشد. فقط یک Quick Start یا Get Started محسوب میشود برای آشنایی مقدماتی با ساختار Dart. از عنوان مقاله هم این موضوع قابل درک و تشخیص میباشد. همچنین فرض شده است که دوستان آشنایی مقدماتی با جاوااسکریپت و مباحث شی گرایی را نیز دارند. البته اگر مشغله کاری به بنده این اجازه را بدهد، مطالب جامعتری را در این زمینه آماده و منتشر میکنم.
گام پنجم: ذخیره سازی اطلاعات در فضای محلی یا Local
در این گام، تغییرات badge را در فضای ذخیره سازی سیستم Local نگهداری مینماییم؛ بطوری که اگر دوباره برنامه را راه اندازی نمودید، badge با دادههای ذخیره شده در سیستم Local مقداردهی اولیه میگردد.
کتابخانه dart:convert را به منظور استفاده از کلاس مبدل JSON به فایل piratebadge.dart اضافه نمایید.
import 'dart:html'; import 'dart:math' show Random; import 'dart:convert' show JSON;
class PirateName { ... PirateName.fromJSON(String jsonString) { Map storedName = JSON.decode(jsonString); _firstName = storedName['f']; _appellation = storedName['a']; } }
- جهت کسب اطلاعات بیشتر در مورد Json به این لینک مراجعه نمایید
- کلاس JSON جهت کار با داده هایی به فرمت Json استفاده میشود که امکاناتی را جهت دسترسی سریعتر و راحتتر به این دادهها فراهم میکند.
- سازندهی PirateName.fromJSON، از یک رشته حاوی دادهی Json، یک نمونه از کلاس PirateName ایجاد میکند.
- سازندهی PirateName.fromJSON، یک Named Constructor میباشد. این نوع سازندهها دارای نامی متفاوت از نام سازندههای معمول هستند و بصورت خودکار نمونه ای از کلاس مورد نظر را ایجاد نموده و به عنوان خروجی بر میگردانند.
- تابع JSON.decode یک رشتهی حاوی دادهی Json را تفسیر نموده و اشیاء Dart را از آن ایجاد میکند.
یک Getter به کلاس PirateName اضافه کنید که مقادیر ویژگیهای آن را به یک رشته Json تبدیل میکند
class PirateName { ... String get jsonString => JSON.encode({"f": _firstName, "a": _appellation}); }
final String TREASURE_KEY = 'pirateName'; void main() { ... }
void setBadgeName(PirateName newName) { if (newName == null) { return; } querySelector('#badgeName').text = newName.pirateName; window.localStorage[TREASURE_KEY] = newName.jsonString; }
void setBadgeName(PirateName newName) { ... } PirateName getBadgeNameFromStorage() { String storedName = window.localStorage[TREASURE_KEY]; if (storedName != null) { return new PirateName.fromJSON(storedName); } else { return null; } }
void main() { ... setBadgeName(getBadgeNameFromStorage()); }
گام ششم: خواندن نامها از فایلهای ذخیره شده به فرمت Json
در این گام کلاس PirateName را به گونهای تغییر میدهیم که نامها را از فایل Json بخواند. این عمل موجب میشود تا به راحتی اسامی مورد نظر را به فایل اضافه نمایید تا توسط کلاس خوانده شوند، بدون آنکه نیاز باشد کد کلاس را مجددا دستکاری کنید.
به منوی File > New File... مراجعه نموده و فایل piratenames.json را با محتوای زیر ایجاد نمایید. این فایل را در پوشه 1-blankbadge و در کنار فایلهای HTML و Dart ایجاد کنید.
{ "names": [ "Anne", "Bette", "Cate", "Dawn", "Elise", "Faye", "Ginger", "Harriot", "Izzy", "Jane", "Kaye", "Liz", "Maria", "Nell", "Olive", "Pat", "Queenie", "Rae", "Sal", "Tam", "Uma", "Violet", "Wilma", "Xana", "Yvonne", "Zelda", "Abe", "Billy", "Caleb", "Davie", "Eb", "Frank", "Gabe", "House", "Icarus", "Jack", "Kurt", "Larry", "Mike", "Nolan", "Oliver", "Pat", "Quib", "Roy", "Sal", "Tom", "Ube", "Val", "Walt", "Xavier", "Yvan", "Zeb"], "appellations": [ "Awesome", "Captain", "Even", "Fighter", "Great", "Hearty", "Jackal", "King", "Lord", "Mighty", "Noble", "Old", "Powerful", "Quick", "Red", "Stalwart", "Tank", "Ultimate", "Vicious", "Wily", "aXe", "Young", "Brave", "Eager", "Kind", "Sandy", "Xeric", "Yellow", "Zesty"]}
به فایل piratebadge.html مراجعه نمایید و فیلد input و المنت button را غیر فعال نمایید.
... <div> <input type="text" id="inputName" maxlength="15" disabled> </div> <div> <button id="generateButton" disabled>Aye! Gimme a name!</button> </div> ...
کتابخانه dart:async را در ابتدای فایل دارت import نمایید
import 'dart:html'; import 'dart:math' show Random; import 'dart:convert' show JSON; import 'dart:async' show Future;
- کتابخانه dart:async برنامه نویسی غیر همزمان یا asynchronous را فراهم میکند
- کلاس Future روشی را ارئه میکند که در آن مقادیر مورد نیاز در آینده ای نزدیک و به صورت غیر همزمان واکشی خواهند شد.
در مرحله بعد لیستهای names و appellations را با کد زیر بصورت یک لیست خالی جایگزین نمایید.
class PirateName { ... static List<String> names = []; static List<String> appellations = []; ... }
- مطمئن شوید که کلمه کلیدی final را از تعاریف فوق حذف نموده اید
- [] معادل new List() میباشد
- کلاس List یک نوع Generic میباشد که میتواند شامل هر نوع شی ای باشد. اگر میخواهید که لیست شما فقط شامل داده هایی از نوع String باشد، آن را بصورت List<String> تعریف نمایید.
دو تابع static را بصورت زیر به کلاس PirateName اضافه نمایید
class PirateName { ... static Future readyThePirates() { var path = 'piratenames.json'; return HttpRequest.getString(path) .then(_parsePirateNamesFromJSON); } static _parsePirateNamesFromJSON(String jsonString) { Map pirateNames = JSON.decode(jsonString); names = pirateNames['names']; appellations = pirateNames['appellations']; } }
توضیحات
- کلاس HttpRequest یک Utility میباشد که دادهها را از یک آدرس یا URL خاص واکشی مینماید.- تابع getString یک درخواست را به صورت GET ارسال مینماید و رشته ای را بر میگرداند
- در کد فوق از کلاس Future استفاده شده است که موجب میشود درخواست GET بصورت غیر همزمان ارسال گردد.
- زمانیکه Future با موفقیت خاتمه یافت، تابع then فراخوانی میشود. پارامتر ورودی این تابع، یک تابع میباشد که پس از خاتمه درخواست GET اجرا خواهد شد. به این نوع توابع که پس از انجام یک عملیات خاص بصورت خودکار اجرا میشوند توابع CallBack میگویند.
- زمانیکه Future با موفقیت خاتمه یافت، اسامی از فایل Json خوانده خواهند شد
- تابع readyThePirates دارای نوع خروجی Future میباشد بطوری که برنامه اصلی در زمانی که فایلها در حال خوانده شدن هستند، به کار خود ادامه میدهد و متوقف نخواهد شد
یک متغیر top-level از نوع SpanElement در کلاس PirateName ایجاد کنید.
SpanElement badgeNameElement; void main() { ... }
void main() { InputElement inputField = querySelector('#inputName'); inputField.onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); badgeNameElement = querySelector('#badgeName'); ... }
void main() { ... PirateName.readyThePirates() .then((_) { //on success inputField.disabled = false; //enable genButton.disabled = false; //enable setBadgeName(getBadgeNameFromStorage()); }) .catchError((arrr) { print('Error initializing pirate names: $arrr'); badgeNameElement.text = 'Arrr! No names.'; }); }
- تابع readyThePirates فراخوانی شده است که یک Future بر میگرداند.
- زمانی که Future با موفقیت خاتمه یافت تابع CallBack موجود در تابع then فراخوانی میشود.
- (_) به عنوان پارامتر ورودی تابع then ارسال شده است، به این معنا که از پارامتر ورودی صرف نظر شود.
- تابع then المنتهای صفحه را فعال میکند و دادههای ذخیره شده را بازیابی مینماید
- اگر Future با خطا مواجه شود، توسط تابع catchError که یک تابع CallBack میباشد، پیغام خطایی را نمایش میدهیم.
برنامه را به مانند گامهای قبل اجرا نموده و نتیجه را مشاهده نمایید
پیشنیازها
- آشنایی با Angular CLI
- آشنایی با مسیریابیها در Angular
همچنین اگر پیشتر Angular CLI را نصب کردهاید، قسمت «به روز رسانی Angular CLI» ذکر شدهی در مطلب «Angular CLI - قسمت اول - نصب و راه اندازی» را نیز اعمال کنید. در این سری از angular/cli: 1.1.2@ استفاده شدهاست.
فناوریهای مختلف کار با فرمها در Angular
Angular (که خلاصه شدهی نام تمام نگارشهای پس از Angular 2 است)، به همراه دو فناوری توکار کار با فرمها است:
الف) فرمهای مبتنی بر قالبها یا Template driven forms
در اینجا عمدهی کار تعاریف فرمها، در قالبهای HTML ایی کامپوننتها به همراه data binding انجام میشود. کار با آن سادهتر است و به همراه حداقل کدنویسی در قسمت کامپوننتهای برنامه است؛ چون two-way data binding بسیاری از مسایل را به صورت خودکار مدیریت میکند. همچنین این روش برای کسانیکه با Angular 1.x کار کرده باشند، آشناتر است.
ب) فرمهای واکنشی یا Reactive forms
در اینجا نیز همانند حالت الف کار تعریف ابتدایی فرم در قالبهای HTML ایی کامپوننتها انجام میشود. اما در اینجا نیاز است مدل فرم را توسط کدهای TypeScript کامپوننت نیز ایجاد کرد و با قالب HTML ایی هماهنگ نمود (به همین جهت به آن model driven forms هم میگویند). مزیت این روش نسبت به حالت الف، سادگی Unit testing و همچنین امکان تعریف اعتبارسنجیهای پیچیدهاست. به علاوه در این حالت میتوان فرمهای پویایی را نیز طراحی کرد.
ما در این سری حالت Template driven forms را بررسی خواهیم کرد.
ایجاد ساختار اولیهی مثال این سری
در ادامه، یک پروژهی جدید مبتنی بر Angular CLI را به نام angular-template-driven-forms-lab به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
به علاوه، قصد استفادهی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشهی پروژه وارد شده و سپس دستور ذیل را صادر کنید، تا بوت استرپ نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوهنامهی بوت استرپ را تعریف کنیم:
به این ترتیب، به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.
در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوشآمدگویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه میکنیم:
که سبب ایجاد کامپوننتهای src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد؛ به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
در اینجا زمانیکه کاربر ریشهی سایت را درخواست میکند، به کامپوننت welcome هدایت خواهد شد.
همچنین مدیریت مسیریابی آدرسهای ناموجود در سایت نیز با تعریف ** صورت گرفتهاست.
زمانیکه یک کامپوننت فعالسازی میشود، قالب آن در router-outlet نمایش داده خواهد شد. برای این منظور فایل src\app\app.component.html را گشوده و به نحو ذیل تغییر دهید:
تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظهای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد:
افزودن ماژول فرمها به برنامه
پس از ایجاد ساختار اولیه برنامه، اولین کاری را که در جهت استفادهی از فرمهای مبتنی بر قالبها باید انجام داد، افزودن ماژول فرمها به ماژول اصلی برنامه است. برای این منظور فایل src\app\app.module.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
در اینجا ابتدا FormsModule، از ماژول Angular مرتبط با آن دریافت شده و سپس به لیست imports ماژول اصلی برنامه اضافه گردیدهاست.
ایجاد ماژول و کامپوننت فرم ثبت نام کارمندان
در ادامه میخواهیم فرم ثبت نام یک کارمند را تکمیل کنیم. بنابراین ماژول جدید کارمندان را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
با این خروجی
اگر به سطر آخر آن دقت کنید، فایل app.module.ts را نیز به صورت خودکار به روز رسانی کردهاست:
در اینجا EmployeeRoutingModule را به انتهای لیست imports افزودهاست که نیاز به اندکی تغییر دارد و باید EmployeeRoutingModule را پیش از AppRoutingModule تعریف کرد. علت این است که AppRoutingModule، دارای تعریف مسیریابی ** یا catch all است که آنرا جهت مدیریت مسیرهای یافت نشده به برنامه افزودهایم. بنابراین اگر ابتدا AppRoutingModule تعریف شود و سپس EmployeeRoutingModule، هیچگاه فرصت به پردازش مسیریابیهای ماژول کارمندان نمیرسد؛ چون مسیر ** پیشتر برنده شدهاست.
همچنین برای اینکه کامپوننتهای این ماژول نیز در حین مسیریابی در دسترس باشند، نیاز است بجای EmployeeRoutingModule، خود EmployeeModule را ذکر کرد که حاوی تعاریف مسیریابی (ذکر EmployeeRoutingModule در قسمت imports آن) نیز میباشد. بنابراین فایل app.module.ts چنین تعاریفی را پیدا میکند:
در ادامه کامپوننت جدید ثبت یک کارمند را به این ماژول اضافه میکنیم:
با این خروجی
اگر به سطر آخر آن دقت کنید، کار به روز رسانی فایل ماژول کارمندان، جهت درج این کامپوننت جدید، در قسمت declarations فایل employee.module.ts نیز به صورت خودکار انجام شدهاست:
در ادامه میخواهیم قالب این کامپوننت را در منوی اصلی سایت قابل دسترسی کنیم. به همین جهت به فایل src\app\employee\employee-routing.module.ts مراجعه کرده و مسیریابی این کامپوننت را تعریف میکنیم:
ابتدا کامپوننت ثبت کارمندان import شده و سپس آرایهی Routes مسیری را به این کامپوننت تعریف کردهاست.
سپس میخواهیم لینکی را به این مسیریابی جدید اضافه کنیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink آنرا اضافه میکنیم:
تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظهای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد (البته میتوان پنجرهی کنسول ng serve را باز نگه داشت تا کار watch را به صورت خودکار انجام دهد؛ این روش سریعتر و به همراه build تدریجی است):
در قسمت بعد، ایجاد اولین فرم مبتنی بر قالبها را پیگیری میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
- آشنایی با Angular CLI
- آشنایی با مسیریابیها در Angular
همچنین اگر پیشتر Angular CLI را نصب کردهاید، قسمت «به روز رسانی Angular CLI» ذکر شدهی در مطلب «Angular CLI - قسمت اول - نصب و راه اندازی» را نیز اعمال کنید. در این سری از angular/cli: 1.1.2@ استفاده شدهاست.
فناوریهای مختلف کار با فرمها در Angular
Angular (که خلاصه شدهی نام تمام نگارشهای پس از Angular 2 است)، به همراه دو فناوری توکار کار با فرمها است:
الف) فرمهای مبتنی بر قالبها یا Template driven forms
در اینجا عمدهی کار تعاریف فرمها، در قالبهای HTML ایی کامپوننتها به همراه data binding انجام میشود. کار با آن سادهتر است و به همراه حداقل کدنویسی در قسمت کامپوننتهای برنامه است؛ چون two-way data binding بسیاری از مسایل را به صورت خودکار مدیریت میکند. همچنین این روش برای کسانیکه با Angular 1.x کار کرده باشند، آشناتر است.
ب) فرمهای واکنشی یا Reactive forms
در اینجا نیز همانند حالت الف کار تعریف ابتدایی فرم در قالبهای HTML ایی کامپوننتها انجام میشود. اما در اینجا نیاز است مدل فرم را توسط کدهای TypeScript کامپوننت نیز ایجاد کرد و با قالب HTML ایی هماهنگ نمود (به همین جهت به آن model driven forms هم میگویند). مزیت این روش نسبت به حالت الف، سادگی Unit testing و همچنین امکان تعریف اعتبارسنجیهای پیچیدهاست. به علاوه در این حالت میتوان فرمهای پویایی را نیز طراحی کرد.
ما در این سری حالت Template driven forms را بررسی خواهیم کرد.
ایجاد ساختار اولیهی مثال این سری
در ادامه، یک پروژهی جدید مبتنی بر Angular CLI را به نام angular-template-driven-forms-lab به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
> ng new angular-template-driven-forms-lab --routing
> npm install bootstrap --save
"apps": [ { "styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ],
در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوشآمدگویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه میکنیم:
>ng g c welcome >ng g c PageNotFound
@NgModule({ declarations: [ AppComponent, WelcomeComponent, PageNotFoundComponent ],
سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; import { WelcomeComponent } from './welcome/welcome.component'; import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: 'welcome', component: WelcomeComponent }, { path: '', redirectTo: 'welcome', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
همچنین مدیریت مسیریابی آدرسهای ناموجود در سایت نیز با تعریف ** صورت گرفتهاست.
زمانیکه یک کامپوننت فعالسازی میشود، قالب آن در router-outlet نمایش داده خواهد شد. برای این منظور فایل src\app\app.component.html را گشوده و به نحو ذیل تغییر دهید:
<nav class="navbar navbar-default"> <div class="container-fluid"> <a class="navbar-brand">{{title}}</a> <ul class="nav navbar-nav"> <li> <a [routerLink]="['/']">Home</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
افزودن ماژول فرمها به برنامه
پس از ایجاد ساختار اولیه برنامه، اولین کاری را که در جهت استفادهی از فرمهای مبتنی بر قالبها باید انجام داد، افزودن ماژول فرمها به ماژول اصلی برنامه است. برای این منظور فایل src\app\app.module.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule, AppRoutingModule ]
ایجاد ماژول و کامپوننت فرم ثبت نام کارمندان
در ادامه میخواهیم فرم ثبت نام یک کارمند را تکمیل کنیم. بنابراین ماژول جدید کارمندان را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
>ng g m Employee -m app.module --routing
installing module create src\app\employee\employee-routing.module.ts create src\app\employee\employee.module.ts update src\app\app.module.ts
import { EmployeeRoutingModule } from './employee/employee-routing.module'; @NgModule({ imports: [ BrowserModule, FormsModule, AppRoutingModule, EmployeeRoutingModule ]
همچنین برای اینکه کامپوننتهای این ماژول نیز در حین مسیریابی در دسترس باشند، نیاز است بجای EmployeeRoutingModule، خود EmployeeModule را ذکر کرد که حاوی تعاریف مسیریابی (ذکر EmployeeRoutingModule در قسمت imports آن) نیز میباشد. بنابراین فایل app.module.ts چنین تعاریفی را پیدا میکند:
import { EmployeeModule } from './employee/employee.module'; @NgModule({ imports: [ BrowserModule, FormsModule, EmployeeModule, AppRoutingModule ]
در ادامه کامپوننت جدید ثبت یک کارمند را به این ماژول اضافه میکنیم:
>ng g c employee/employee-register
installing component create src\app\employee\employee-register\employee-register.component.css create src\app\employee\employee-register\employee-register.component.html create src\app\employee\employee-register\employee-register.component.spec.ts create src\app\employee\employee-register\employee-register.component.ts update src\app\employee\employee.module.ts
import { EmployeeRegisterComponent } from './employee-register/employee-register.component'; @NgModule({ declarations: [EmployeeRegisterComponent]
در ادامه میخواهیم قالب این کامپوننت را در منوی اصلی سایت قابل دسترسی کنیم. به همین جهت به فایل src\app\employee\employee-routing.module.ts مراجعه کرده و مسیریابی این کامپوننت را تعریف میکنیم:
import { EmployeeRegisterComponent } from './employee-register/employee-register.component'; const routes: Routes = [ { path: 'register', component: EmployeeRegisterComponent } ];
سپس میخواهیم لینکی را به این مسیریابی جدید اضافه کنیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink آنرا اضافه میکنیم:
<ul class="nav navbar-nav"> <li> <a [routerLink]="['/']">Home</a> </li> <li> <a [routerLink]="['/register']">Register</a> </li> </ul>
در قسمت بعد، ایجاد اولین فرم مبتنی بر قالبها را پیگیری میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
AngularJS #1
پیش از اینکه آموزش AngularJs را شروع کنیم بهتر است با مفهوم برنامههای تک صفحه ای وب و یا Single Page Web Applications آشنا شویم؛ چرا که AngularJS برای توسعه هر چه سادهتر و قویتر این گونه برنامهها متولد شده است.
Single Page Application
برای درک چگونگی کارکرد این برنامه ها، مثالی را میزنیم که هر روزه با آن سرو کار دارید، یکی از نمونههای کامل و قدرتمند برنامههای Single Page Application و یا به اختصار SPA، سرویس پست الکترونیکی Google و یا همان Gmail است.
اجازه بدهید تا ویژگیهای SPA را با بررسی Gmail انجام دهم، تا به درک روشنی از آن برسید:
Reload نشدن صفحات
در Gmail هیچ گاه صفحه Reload و یا اصطلاحا بارگیری مجدد نمیشود. برای مثال، وقتی شما لیست ایمیلهای خود را مشاهده میکنید و سپس بر روی یکی از آنها کلیک میکنید، بدون اینکه به صفحه ای دیگر هدایت شوید؛ایمیل مورد نظر را میبینید. در حقیقت تمامی اطلاعات در همان صفحه نمایش داده میشوند و بر عکس وب سایتهای معمول است که از صفحه ای به صفحهی دیگر هدایت میشوید ، در یک صفحه تمام کارهای مورد نیاز خود را انجام میدهید و احتیاجی به بارگیری مجدد صفحات نیست. با توجه به این صحبتها برای توسعه دهندههای وب آشکار است که تکنیک AJAX، نقشی اساسی در این گونه برنامهها دارد، چون کلیهی عملیات ارتباط با سرور در پشت زمینه انجام میشوند.
تغییر URL در نوار آدرس مرورگر
وقتی شما بر روی یک ایمیل کلیک میکنید و آن ایمیل را بدون Reload شدن مجدد صفحه مشاهده میکنید، آدرس صفحه در مرورگر نیز تغییر میکند. خب مزیت این ویژگی چیست؟ مزیت این ویژگی در این است که هر ایمیل شما دارای یک آدرس منحصر به فرد است و به شما امکان Bookmark کردن آن لینک، باز کردن آن در یک Tab جدید و یا حتی ارسال آن به دوستان خود را دارید. حتی اگر این مطلب را جدا از Gmail در نظر بگیریم، به موتورهای جست و جو کمک میکند، تا هر صفحه را جداگانه Index کنند؛ جدا از اینکه وبسایت ما SPA است. همچنین این کار یک مزیت مهم دیگر نیز دارد؛ و آن کار کردن کلیدهای back و forward مرورگر، برای بازگشت به صفحات پیمایش شده قبلی است.
شاید قبل از بیان این ویژگی با خود گفته باشید که پیاده سازی Reload نشدن صفحات با AJAX آن چنان کار پیچیده ای نیست. بله درست است، اما آیا شما قبل از این راه حلی برای تغییر URL اندیشیده بودید؟ مطمئنا شما هم صفحات وب زیادی را دیده اید که همهی صفحات آن دارای یک URL در نوار آدرس مرورگر هستند و هیچگاه تغییر نمیکنند و با باز کردن یک لینک در یک Tab جدید، باز همان صفحهی تکراری را مشاهده میکنند! و یا بدتر از همه که دکمهی back مرورگر غیر عادی عمل میکند. بله، اینها تنها تعدادی از صدها مشکلات رایج سیستمهای نوشته شده ای است که سعی کردند همهی کارها در یک صفحه انجام شود.
Cache شدن اطلاعات دریافتی
شاید خیلیها ویژگیهای فوق را برای یک SPA کافی بدانند، اما تعدادی هم مانند نگارنده وجود یک کمبود را حس میکنند و آن کش شدن اطلاعات دریافتی در مرورگر است. Gmail این امکان را به خوبی پیاده سازی کرده است. لیست ایمیلهای دریافتی در بار اول از سرور دریافت میشود، سپس شما بر روی یک ایمیل کلیک و آن را مشاهده میکنید. حال به لیست ایمیلهای دریافتی بازگردید، آیا رفت و برگشتی به سرور انجام میشود؟ مسلما خیر. حتی اگر دوباره بر روی آن ایمیل مشاهده شده ، کلیک کنید، بدون رفت و برگشتی به سرور آن ایمیل را مشاهده میکنید.
کش شدن اطلاعات سبب میشود که بار سرور خیلی کاهش یابد و رفت و آمدهای بیهوده صورت نگیرد. کش شدن دادهها یک مزیت دیگر نیز دارد و آن تبدیل برنامههای معمول وب stateless به برنامههای شبه دسکتاپ state full است.
تکنیک AJAX در پیاده سازی امکانات فوق نقشی اساسی را بازی میکند. کمی به عقب برمیگردیم یعنی زمانی که AJAX برای اولین بار مطرح شد و هدف اصلی به وجود آمدن آن پیاده سازی برنامههای وب به شکل دسکتاپ بود و این کار از طریق انجام تمامی ارتباطات سرور با XMLHttpRequest امکان پذیر میشد. شاید آن زمان با توجه به محدودیت تکنولوژیها موجود این کار به صورت تمام و کمال امکان پذیر نبود، اما امروزه به بهترین شکل ممکن قابل پیاده سازی است.
شاید اکنون این سوال پیش بیاید که چرا باید وبسایت خود را به شکل SPA طراحی کنیم؟
برای پاسخ دادن به این سوال باید گفت که سیستمهای وب امروزی به دو دستهی زیر تقسیم میشوند:
- Web Documents و یا همان وب سایتهای معمول
- Web Applications و یا همان Single Page Web Applications
اگر هدف شما طراحی یک وب سایت معمول است که هدف آن، نمایش یک سری اطلاعات است و به قولی دارای محتواست، مطمئنا پیاده سازی این سیستم به صورت SPA کاری بیهوده به نظر میآید؛ ولی اگر هدفتان نوشتن سیستم هایی مثل Gmail، Google Maps، Azure، Facebook و ... است، پیاده سازی آنها به صورت وب سایتهای معمولی، غیر معقول به نظر میآید. حتی بخشهای مدیریتی یک وبسایت هم میتواند به خوبی توسط SPA پیاده سازی شود، چرا که واقعا برای مدیریت اطلاعات یک وب سایت احتیاجی نیست، که از این صفحه به آن صفحه جا به جا شد.
معرفی کتابخانهی AngularJS
AngularJS فریم ورکی متن باز و نوشته شده به زبان جاوا اسکریپت است. هدف از به وجود آمدن این فریم ورک، توسعه هر چه سادهتر SPAها با الگوی طراحی MVC و تست پذیری هر چه آسانتر آنها است. این فریم ورک توسط یکی از محققان Google در سال 2009 به وجود آمد. بعدها این فریم ورک تحت مجوز MIT به صورت متن باز در آمد و اکنون گوگل آن را حمایت میکند و توسط هزاران توسعه دهنده در سرتاسر دنیا، توسعه داده میشود.
قبل از اینکه به بررسی ویژگیهای Angular بپردازم، بهتر است ابتدا مطلبی دربارهی به کارگیری Angular از Brad Green که کارمند گوگل است، بیان کنم.
در سال 2009 تیمی در گوگل مشغول انجام پروژه ای به نام Google Feedback بودند. آنها سعی داشتند تا در طی چند ماه، به سرعت کدهای خوب و تست پذیر بنویسند. پس از 6 ماه کدنویسی، نتیجهی کار 17000 خط کد شد. در آن موقع یکی از اعضای تیم به نام Misko Hevery، ادعا کرد که میتواند کل این پروژه را در دو هفته به کمک کتابخانهی متن بازی که در اوقات فراغت توسعه داده است، بازنویسی کند. Misko نتوانست در دو هفته این کار را انجام دهد. اما پس از سه هفته همهی اعضای تیم را شگفت زده کرد. نتیجهی کار تنها 1500 خط بود! همین باعث شد که ما بفهمیم که، Misko بر روی چیزی کاری میکند که ارزش دنبال کردن دارد.
پس از آن قضیه Misko و Brad بر روی Angular کار کردند و اکنون هم Angular توسط تیمی در گوگل و هزاران توسعه دهندهی متن باز حرفه ای در سرتاسر جهان، درحال توسعه است.
فکر کنم همین داستان ذکر شده، قدرت فوق العاده زیاد این فریم ورک را برای همگان آشکار سازد.
ویژگیهای AngularJS:
- قالبهای سمت کاربر (Client Side Templates): انگولار دارای یک template engine قدرتمند برای تعریف قالب است.
- پیروی از الگوی طراحی MVC: انگولار، الگوی طراحی MVC را برای توسعه پیشنهاد میدهد و امکانات زیادی برای توسعه هر چه راحتتر با این الگو فراهم کرده است.
- Data Binding: امکان تعریف انقیاد داده دوطرفه (Two-Way Data Binding) در این فریم ورک به راحتی هرچه تمام، امکان پذیر است.
- Dependency Injection: این فریم ورک برای دریافت وابستگیهای تعریف شده، دارای یک سیستم تزریق وابستگی توکار است.
- تعریف Serviceهای سفارشی: در این فریم ورک امکان تعریف سرویسهای دلخواه به صورت ماژول وجود دارد. این ماژولهای مجزا را به کمک سیستم تزریق وابستگی توکار Angular، به راحتی در هر جای برنامه میتوان تزریق کرد.
- تعریف Directiveهای سفارشی: یکی از جذابترین و قدرتمندترین امکانات این فریم ورک، تعریف Directiveهای سفارشی است. Directive ها، امکان توسعه HTML را فراهم کرده اند. توسعهی HTML اکنون در قالب Web Componentsها فراهم شده است، اما هنوز هم خیلی از مرورگرهای جدید نیز از آن پشتیبانی نمیکنند.
- فرمت کردن اطلاعات با استفاده از فیلترهای سفارشی: با استفاده از فیلترها میتوانید چگونگی الحاق شدن اطلاعات را برای نمایش به کاربر تایین کنید ؛ انگولار همراه با فیلترهای گوناگون مختلفی عرضه میشود که میتوان برایه مثال به فیلتر currency ، date ،uppercase کردن رشتهها و .... اشاره کرد همچنین شما محدود به فیلترهای تعریف شده در انگولار نیستید و آزادید که فیلترهای سفارشی خودتان را نیز تعریف کنید.
- سیستم Routing: دارا بودن سیستم Routing قدرتمند، توسعه SPAها را بسیار ساده کرده است.
- سیستم اعتبار سنجی: Angular دارای سیستم اعتبار سنجی توکار قدرتمند برای بررسی دادههای ورودی است.
- سرویس تو کار برای ارتباط با سرور: Angular دارای سرویس پیش فرض ارتباط با سرور به صورت AJAX است.
- تست پذیری: Angular دارای بستری آماده برای تست کردن برنامههای نوشته شده است و از Unit Tests و Integrated End-to-End Test هم پشتیبانی میکند.
- جامعهی متن باز بسیار قوی
اینها فقط یک مرور کلی بر تواناییهای این فریم ورک بود و در ادامه هر کدام از این ویژگی را به صورت دقیق بررسی خواهیم کرد.
در مقالهی بعدی، به چگونگی نصب AngularJS خواهیم پرداخت. سپس، اولین کد خود را با استفاده از آن خواهیم نوشت و مطالب Client Side Templates و MVC را دقیقتر بررسی خواهیم کرد.
با تشکر فراوان از مقاله بسیار عالی
سوالی که اینجا مطرحه راه کار برای استفاده از این ماژول در دو وب سایت که بر روی یک هاست بارگذاری شدند چیه ؟
در واقع وقتی یکی از وب سایتها فعال است برای وب سایت دوم این ماژول کار نمیکنه و پیام System.Security.Cryptography.CryptographicException: Object already exists دریافت میکنم.
لطف میکنید راهنمایی کنید به چه طریقی مشکل حل میشه .
سوالی که اینجا مطرحه راه کار برای استفاده از این ماژول در دو وب سایت که بر روی یک هاست بارگذاری شدند چیه ؟
در واقع وقتی یکی از وب سایتها فعال است برای وب سایت دوم این ماژول کار نمیکنه و پیام System.Security.Cryptography.CryptographicException: Object already exists دریافت میکنم.
لطف میکنید راهنمایی کنید به چه طریقی مشکل حل میشه .
در بیشتر مواردی که یک تکنولوژی جدید را برای
یادگیری انتخاب میکنیم در اولین فرصت سراغ منابع آنلاین از قبیل کتابها و یا
ویدئوهای موجود بر روی نت میرویم و در این بین ممکن است با محدودیت هایی از قبیل
کیفیت بد اتصال به اینترنت و یا حجم مربوط به فایلهای موجود مواجه شویم. خوب چاره
و نکته در اینجاست که با انتخاب یک کتاب مفید در این زمینه میتوان تا حدود زیادی
این محدویتها را برطرف کرد. در ادامه
برای شروع کار با NHibernate که روز به روز در حال توسعه است، میتوان کتاب
زیر را شروع بسیار خوبی برای کار دانست:
NHibernate 3 Beginners Guide, Aug 2011
در این کتاب بصورتی بسیار جامع از ابتداییترین مسئله تا فنیترین مسائلی که در هر پروژهی عملی هر توسعه دهنده ای با هر سطحی امکان مواجه شدن با این مشکلات را دارد به تفصیل بررسی شده. این کتاب شامل 12 فصل بوده که مطالب آن به شرح زیر ارائه شده است:
1- فصل اول – نگاه اولیه:
- NHibernate چیست
- موارد تازه در آخرین نسخه NHibernate
- چرا ما استفاده کنیم و چه کسانی دیگری استفاده میکنند
- زمانیکه به مشکل برخوردیم از کجا کمک بگیریم یا حتی نسخه ای تجاری تهیه کنیم
- آماده سازی سیستم برای توسعه برنامهها با استفاده از NHibernate
- ایجاد یک مدل ساده از مشکل موجود
- ایجاد بانک و برپایی یک نگاشت (Map) بین مدل و بانک
- نوشتن و خواندن داده از و به بانک
- بحث در مورد بدست آوردن نتیجه معادل بدون استفاده از NHibernate و یا ORM دیگر
- مدل چیست؟
- عوامل اصلی موجود در ایجاد یک مدل چیست؟
- چطور میتوان مدل ساخت؟
- یادگیری جدول چیست؟
- یادگیری چطور جدولها به هم مرتبط شود؟
- بحث در مورد استراتژیهای تحمیلی ای که کدام داده میتواند ذخیره شود
- نمایش امکانات موجود برای بهبود کارایی دسترسی به داده
- ایجاد بانک داده برای سیستم سفارش (Ordering System)
- بدست آوردن یک درک درست درباره نگاشت و پیش نیازهای آن
- بحث در مورد ریزه کاریهای چهار تکنیک پر استفاده معمول نگاشت
- توصیف و توسعه قراردادها برای کاهش تقلا در کدنویسی
- ایجاد خودکار اسکریپت برای ایجاد شمای بانک دیتا از روی نگاشت مان
- توصیف و نگاشت مدل دامنه سیستم سفارش مان
- بحث در مورد اشیاء وهله و تراکنش
- معرفی شیء session factory
- پیاده سازی برنامه ای که دیتا ذخیره و بازخوانی میکند
- تجزیه و تحلیل متدهای گوناگون برای مدیریت وهلهها در پر استفادهترین انواع برنامه
- پیاده سازی یک بستر پایه برای ساخت آزمایش ساده کد دسترسی به بانک داده
- ایجاد آزمایشها برای تایید کد دسترسی به بانک داده مان
- تجزیه و تحلیل ارتباط بین NHibernate و بانک داده
- پیکربندی NHibernate برای واقع نگاری دادههای مورد توجه
- بحث در مورد پیکربندی قبل از شروع
- آشنا شدن با لیست مولفههای NHibernate که میتوان پیکربندی کرد
- یادگیری چهار روش متفاوت پیکربندی که چگونه میتوان در برنامه هایمان استفاده کرد
- یادگیری چگونگی استفاه از (LINQ (Language Integrated Query در NHibernate برای دریافت داده
- پرس و جو با استفاده از criteria query API
- استفاده از گویش object-oriented اصلی SQL بنام Hibernate Query Language HQL
- بحث در مورد موجودیت هایی با خواصی که توان lazy load دارند
- مقابله با بارگذاری حریصانه با lazy loading بطوریکه بصورت دسته ای از پرس و جو بنظر آید
11- فصل یازدهم – اشتباهات متداول – چیزهایی برای جلوگیری
در پستهای قبلی با مفهوم ng-app آشنا شدید. دایرکتیو ng-app برای استفاده از راه انداز خودکار فریم ورک Angular (معروف به auto-bootstrap) استفاده میشود. در حالت پیش فرض، به ازای هر سند Html فقط میتوان یک ماژول در Angular تعریف کرد. در سند مربوطه اولین المانی که دارای دایرکتیو ng-app باشد به عنوان عنصر ریشه در نظر گرفته میشود و تمام عناصر تعریف شده در محدوده این دایرکتیو قایل استفاده برای ماژول مورد نظر خواهد بود. سایر عناصر حتی اگر ng-app یکسان داشته باشند نادیده گرفته میشوند.
ابتدا یک مثال زیر را به روش auto-bootstrap بررسی میکنیم:
ابتدا یک مثال زیر را به روش auto-bootstrap بررسی میکنیم:
<div ng-app="myApp"> <div ng-controller="myController as ctrl"> <span>ng-app #1</span> {{ctrl.firstName}} {{ctrl.lastName}} </div> </div> <div ng-app="myApp"> <div ng-controller="myController as ctrl"> <span>ng-app #2</span> {{ctrl.firstName}} {{ctrl.lastName}} </div> </div> @section scripts { <script type="text/javascript" src="~/scripts/Modules/module8.js"></script> }
در کنترلر مورد نظر نیز تعاریف به صورت زیر خواهد بود:
var app = angular.module('myApp', []); app.controller('myController', function () { this.firstName = "Masoud"; this.lastName = "Pakdel"; });
در مثال بالا دو تگ div وجود دارد که به صورت مشترک با استفاده از دایرکتیو ng-app به یک ماژول اشاره میکنند. طبق گفتهها بالا در روش auto-bootstrap اولین عنصری که دارای دایرکتیو ng-app باشد به عنوان محدوده ماژول مورد استفاده قرار خواهد گرفت در نتیجه سایر المانها (در اینجا منظور تگ div دوم است)نادیده گرفته خواهند شد. پس خروجی به صورت زیر میشود:
اما اگر قصد داشته باشیم که در یک سند html دو نقطه شروع تعریف کنیم در حالی که هر کدام از یک منبع داده استفاده نمایند باید bootstrap برنامه را به صورت دستی تعیین کرد. برای این کار کافیست از دستور angular.bootstrap به صورت زیر استفاده نماییم:
پیاده سازی مثال بالا
پیاده سازی مثال بالا
<div id="myAppContainer1"> <div ng-controller="myController as ctrl"> <span>ng-app #1</span> {{ctrl.firstName}} {{ctrl.lastName}} </div> </div> <div id="myAppContainer2"> <div ng-controller="myController as ctrl"> <span>ng-app #2</span> {{ctrl.firstName}} {{ctrl.lastName}} </div> </div> @section scripts { <script type="text/javascript" src="~/scripts/Modules/module8.js"></script> }
اولین تغییر مورد نظر این است که، دایرکتیو ng-app حذف شد و به جای آن id برای تگ div تعیین کردیم. در فایل کنترلر مورد نظر نیز تغییر زیر را اعمال میکنیم:
var app = angular.module('myApp', []); app.controller('myController', function () { this.firstName = "Masoud"; this.lastName = "Pakdel"; }); angular.bootstrap(document.getElementById("myAppContainer1"), ["myApp"]); angular.bootstrap(document.getElementById("myAppContainer2"), ["myApp"]);
با استفاده از دستور angular.bootstrap میتوان بر اساس id تعیین شده تگ مورد نظر در سند را به دست آورد و ماژول مورد نظر را به آن نسبت داد.
خروجی مثال بالا:
خروجی مثال بالا:
برخلاف حالت قبل هر دو نقطه شروع به یک منبع داده اشاره میکنند و محدودیت حالت قبل برطرف میشود.
نظرات مطالب
EF Code First #12
- من هر نوع طراحی رو تائید نمیکنم. چرا یک برنامه باید چندین DbContext
داشته باشد؟ نیازی نداره. چرا باید چندین ماژول کنترلر داشته باشه؟
- سؤال شما خارج از موضوع بحث است (در اینجا بحثی در مورد طراحی «افزونه پذیر» مطرح نشده). برای طراحی افزونه پذیر میتونید به مباحث زیر مراجعه کنید:
ابتدا فقط و فقط یک DbContext مرکزی را در کل برنامه تعریف کنید. بعد تنظیمات نگاشتها را به صورت پویا یافته و به آن اضافه کنید. سپس موجودیتهای مهیا را به صورت پویا یافته و به Context مرکزی اضافه نمائید.
+ در EF نمیتونید در عمل چندین DbContext داشته باشید مرتبط با یک دیتابیس. Change tracking در EF بر مبنای یک DbContext کار میکند. اگر قرار باشد چندین وهله از DbContextهای مختلف مثلا در طی یک درخواست وجود داشته باشند، یعنی چندین اتصال باز شده به دیتابیس و چندین تراکنش مجزا در حال انجام است (کل بحث جاری از ابتدا). به علاوه قابلیت کار کردن با چندین موجودیت را به صورت همزمان در طی یک تراکنش از دست میدهید.
- برای اینکه در حین کار با Structure Map خطای Circular dependency را مشاهده نکنید، نیاز است یک کتابخانه یا حتی یک کلاس واسط طراحی کنید تا مشترکات در آن قرار گیرند.
- سؤال شما خارج از موضوع بحث است (در اینجا بحثی در مورد طراحی «افزونه پذیر» مطرح نشده). برای طراحی افزونه پذیر میتونید به مباحث زیر مراجعه کنید:
ابتدا فقط و فقط یک DbContext مرکزی را در کل برنامه تعریف کنید. بعد تنظیمات نگاشتها را به صورت پویا یافته و به آن اضافه کنید. سپس موجودیتهای مهیا را به صورت پویا یافته و به Context مرکزی اضافه نمائید.
+ در EF نمیتونید در عمل چندین DbContext داشته باشید مرتبط با یک دیتابیس. Change tracking در EF بر مبنای یک DbContext کار میکند. اگر قرار باشد چندین وهله از DbContextهای مختلف مثلا در طی یک درخواست وجود داشته باشند، یعنی چندین اتصال باز شده به دیتابیس و چندین تراکنش مجزا در حال انجام است (کل بحث جاری از ابتدا). به علاوه قابلیت کار کردن با چندین موجودیت را به صورت همزمان در طی یک تراکنش از دست میدهید.
- برای اینکه در حین کار با Structure Map خطای Circular dependency را مشاهده نکنید، نیاز است یک کتابخانه یا حتی یک کلاس واسط طراحی کنید تا مشترکات در آن قرار گیرند.