In Visual Studio 2015 Succinctly, author Alessandro Del Sole explains how to take advantage of the highly anticipated features in Microsoft Visual Studio 2015. Topics include sharing code between different types of projects, new options for debugging and diagnostics, and improving productivity with other services in the Visual Studio ecosystem, such as NuGet and Azure.
اشتراکها
نظرات مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
یک نکتهی تکمیلی: روش به روز رسانی قالب create-react-app
این قالب، هر از چندگاهی به روز رسانی میشود؛ بنابراین تنها کافی است در خط فرمان، دستورات زیر را صادر کنید تا تمام بستههای global نصب شده را یکجا و یکدست به روز رسانی کنید:
npm i -g npm npm cache verify npm update -g
npm install npm-check-updates -g ncu -u npm install
اشتراکها
CSS Nesting چیست؟
در قسمتهای قبل ( ^ ، ^ و ^ ) GraphQL را در ASP.Net Core راه اندازی کردیم و در قسمت ( فراخوانی GraphQL API در یک کلاینت ASP.NET Core ) از GraphQL API فراهم شده در یک کلاینت ASP Net Core استفاده کردیم. اکنون میخواهیم چگونگی استفاده از GraphQL را در انگیولار، یاد بگیریم.
Apollo Angular، به شما اجازه میدهد دادهها را از یک سرور GraphQL دریافت و از آن برای ساختن UI های واکنشی و پیچیده در انگیولار استفاده کنید. وقتی که از Apollo Client استفاده میکنیم، نیازی نیست هیچ چیز خاصی را در مورد سینتکس query ها یادبگیریم؛ به دلیل اینکه همه چیز همان استاندارد GraphQL میباشد. هر چیزی را که شما در GraphQL query IDE تایپ میکنید، میتوانید آنها را در کدهای Apollo Client نیز قرار دهید.
Installation with Angular Schematics
بعد از ایجاد یک پروژه انگیولار با دستور زیر
ng new apollo-angular-project
ng add apollo-angular
const uri = 'https://localhost:5001/graphql';
اکنون همه چیز تمام شدهاست. شما میتوانید اولین query خود را اجرا کنید.
Installation without Angular Schematics
اگر میخواهید Apollo را بدون کمک گرفتن از Angular Schematics نصب کنید، در ابتدا کتابخانههای زیر را نصب نمائید:
npm install --save apollo-angular \ apollo-angular-link-http \ apollo-link \ apollo-client \ apollo-cache-inmemory \ graphql-tag \ graphql
{ "compilerOptions": { // ... "lib": [ "es2017", "dom", "esnext.asynciterable" ] } }
در ادامه، فایل app.module.ts را باز کرده و آن را مطابق زیر ویرایش کنید:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { HttpClientModule } from "@angular/common/http"; import { ApolloModule, APOLLO_OPTIONS } from "apollo-angular"; import { HttpLinkModule, HttpLink } from "apollo-angular-link-http"; import { InMemoryCache } from "apollo-cache-inmemory"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, ApolloModule, HttpLinkModule ], providers: [ { provide: APOLLO_OPTIONS, useFactory: (httpLink: HttpLink) => { return { cache: new InMemoryCache(), link: httpLink.create({ uri: "https://localhost:5001/graphql" }) } }, deps: [ HttpLink ] }], bootstrap: [ AppComponent ] }) export class AppModule { }
- با استفاده از سرویس apollo-angular-link-http و HttpLink، کلاینت را به یک سرور GraphQL متصل میکنیم.
- apollo-cache-inmemory و InMemoryCache محلی برای ذخیره سازی دادهها میباشد.
- APOLLO_OPTIONS فراهم کننده تنظیمات Apollo Client است.
- HttpLink نیاز به HttpClient دارد. به همین خاطر است که ما از HttpClientModule استفاده کردهایم.
Links and Cache
Apollo Client، یک لایه واسط شبکه قابل تعویض را دارد که به شما اجازه میدهد تا تنظیم کنید که چگونه query ها در HTTP ارسال شوند؛ یا کل بخش Network را با چیزی کاملا سفارشی سازی شده جایگزین کنید؛ مثل یک websocket transport.
apollo-angular-link-http : از Http برای ارسال query ها استفاده میکند.
apollo-cache-inmemory : پیاده سازی کش پیش فرض برای Apollo Client 2.0 میباشد.
نکته
Apollo یک سرویس export شده انگیولار از apollo-angular، برای به اشتراک گذاشتن دادههای GraphQL با UI شما میباشد.
شروع کار
به همان روش که فایلهای Model را برای کلاینت ASP.NET Core ایجاد کردیم، در اینجا هم ایجاد میکنیم. کار را با ایجاد کردن یک پوشه جدید به نام types، شروع میکنیم و چند type را در آن تعریف خواهیم کرد ( OwnerInputType ،AccountType و OwnerType ):
export type OwnerInputType = { name: string; address: string; }
export type AccountType = { 'id': string; 'description': string; 'ownerId' : string; 'type': string; }
import { AccountType } from './accountType'; export type OwnerType = { 'id': string; 'name': string; 'address': string; 'accounts': AccountType[]; }
سپس یک سرویس را به نام graphql ایجاد میکنیم:
ng g s graphql
و آن را همانند زیر ویرایش میکنیم:
import { Injectable } from '@angular/core'; import { Apollo } from 'apollo-angular'; import gql from 'graphql-tag'; @Injectable({ providedIn: 'root' }) export class GraphqlService { constructor(private apollo: Apollo) { } }
اکنون همه چیز آماده است تا تعدادی query و mutation را اجرا کنیم ( providedIn ).
بازیابی تمامی Owner ها
سرویس graphql را باز میکنیم و آن را همانند زیر ویرایش میکنیم ( اضافه کردن متد getOwners ):
public getOwners = () => { return this.apollo.query({ query: gql`query getOwners{ owners{ id, name, address, accounts{ id, description, type } } }` }); }
سپس فایل app.component.ts را باز کرده و همانند زیر ویرایش میکنیم:
export class AppComponent implements OnInit { public owners: OwnerType[]; public loading = true; constructor(private graphQLService: GraphqlService) { } ngOnInit() { this.graphQLService.getOwners().subscribe(result => { this.owners = result.data["owners"] as OwnerType[]; this.loading = result.loading; }); } }
و هم چنین app.component.html:
<div> <div *ngIf="!this.loading"> <table> <thead> <tr> <th> # </th> <th> نام و نام خانوادگی </th> <th> آدرس </th> </tr> </thead> <tbody> <ng-container *ngFor="let item of this.owners;let idx=index" [ngTemplateOutlet]="innertable" [ngTemplateOutletContext]="{item:item, index:idx}"></ng-container> </tbody> </table> </div> <div *ngIf="this.loading"> <p> در حال بارگذاری لیست ... </p> </div> </div> <ng-template #innertable let-item="item" let-idx="index"> <tr> <td>{{idx+1}}</td> <td>{{item.name}}</td> <td>{{item.address}}</td> </tr> <tr *ngIf="this.item.accounts && this.item.accounts.length > 0"> <td colspan="4"> <div> <p>Accounts</p> </div> <div> <table> <thead> <tr> <th>#</th> <th>نوع</th> <th>توضیحات</th> </tr> </thead> <tbody> <tr *ngFor="let innerItem of this.item.accounts;let innerIndex=index"> <td> {{innerIndex+1}} </td> <td> {{innerItem.type}} </td> <td> {{innerItem.description}} </td> </tr> </tbody> </table> </div> </td> </tr> </ng-template>
dotnet restore dotnet run
سپس پروژه را اجرا کنید:
ng serve
خروجی به صورت زیر میباشد (لیست تمامی Owner ها به همراه Accountهای مربوط به هر Owner):
در متد getOwner، بجای apollo.query میتوان از apollo.watchQuery استفاده کرد که در نمونه زیر، در ابتدا، GraphQL query را در تابع gql ( از graphql-tag ) برای خصوصیت query در متد apollo.watchQuery پاس میدهیم.
public getOwners = () => { return this.apollo.watchQuery<any>({ query: gql`query getOwners{ owners{ id, name, address, accounts{ id, description, type } } }` }) }
export class AppComponent implements OnInit { loading: boolean; public owners: OwnerType[]; private querySubscription: Subscription; constructor(private graphQLService: GraphqlService) { } ngOnInit() { this.querySubscription = this.graphQLService.getOwners() .valueChanges .subscribe(result => { this.loading = result.loading; this.owners = result.data["owners"] as OwnerType[]; }); } ngOnDestroy() { this.querySubscription.unsubscribe(); } }
مابقی query ها و mutation ها از سرویس graphql
بازیابی یک Owner مشخص
public getOwner = (id) => { return this.apollo.query({ query: gql`query getOwner($ownerID: ID!){ owner(ownerId: $ownerID){ id, name, address, accounts{ id, description, type } } }`, variables: { ownerID: id } }) }
ایجاد یک Owner جدید
public createOwner = (ownerToCreate: OwnerInputType) => { return this.apollo.mutate({ mutation: gql`mutation($owner: ownerInput!){ createOwner(owner: $owner){ id, name, address } }`, variables: { owner: ownerToCreate } }) }
ویرایش یک Owner
public updateOwner = (ownerToUpdate: OwnerInputType, id: string) => { return this.apollo.mutate({ mutation: gql`mutation($owner: ownerInput!, $ownerId: ID!){ updateOwner(owner: $owner, ownerId: $ownerId){ id, name, address } }`, variables: { owner: ownerToUpdate, ownerId: id } }) }
و در نهایت حذف یک Owner
public deleteOwner = (id: string) => { return this.apollo.mutate({ mutation: gql`mutation($ownerId: ID!){ deleteOwner(ownerId: $ownerId) }`, variables: { ownerId: id } }) }
کدهای کامل این قسمت را از ایجا دریافت کنید : GraphQL_Angular.zip
کدهای کامل قسمت ( GraphQL Mutations در ASP.NET Core ( عملیات POST, PUT, DELETE ) ) را از اینجا دریافت کنید : ASPCoreGraphQL_3.rar
Variable Naming Best Practices in JavaScript
Like any other programming language, JavaScript relies heavily on well-structured and understandable code. One of the fundamental building blocks of clean JavaScript code is effective variable naming.
By adhering to certain best practices, you can significantly enhance the readability and maintainability of your JavaScript projects. Let’s dive into 12 sets of JavaScript variable naming guidelines.
احتمالا به اهمیت بحث SEO و آدرسهای تمیز آن آشنا هستید. ASP.NET MVC از همان ابتدا از قابلیت URL Routing پشتیبانی میکرد و ASP.NET Web Forms هم از نسخه NET 4.0. این قابلیت را در خود اضافه نمود. با این حال نوشتن Url Routing برای وب سایتها کمی مشکل و در ASP.NET کمتر استفاده میشود. علاوه بر آن تغییر آدرسهای قدیمی با Url Routing بسیار وقت گیر میباشد. به همین خاطر تیم توسعه ASP.NET کتابخانه ای با نام Asp.NET FriendlyUrls که فعلا نسخه قبل از نهایی آن منتشر شده است را در اختیار توسعه دهندگان وب سایتها قرار دادهاند که با حداقل کد نویسی و بدون اضافه نمودن مسیریابی آدرسها، تمام آدرسهای قدیم و جدید را مطابق استاندارد SEO تغییر میدهد.
در آخر، فضای نام کتابخانه، Microsoft.AspNet.FriendlyUrls میباشد.
دریافت و نصب Asp.NET FriendlyUrls از Nuget
در Visual Studio 2012.2 در صورتی که یک پروژه جدید از نوع ASP.NET Web Forms ایجاد نمایید بصورت پیشفرض این کتابخانه نصب شده است. اما میتوانید با دستور زیر نیز آن را از Nuget دریافت نمایید:
Install-Package Microsoft.AspNet.FriendlyUrls -Pre
پس از نصب، در کلاس Global میبایست کتابخانه را فعال نمایید:
public static class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.EnableFriendlyUrls(); } }
و همچنین در Application_Start
void Application_Start(object sender, EventArgs e) { RouteConfig.RegisterRoutes(RouteTable.Routes); }
به همین راحتی؛ حال دو صفحه First.aspx و Second.aspx را به پروژه اضافه نمائید و وب سایت را اجرا کنید. بدون نوشتن پسوند aspx به صفحات دسترسی خواهید داشت:
از درون برنامه هم به راحتی میتوانید صفحات را به همان صورت قبل بدون پسوند صدا بزنید:
Response.Redirect("~\\Second");
دسترسی به پارامترها نیز به صورت قبل امکان پذیر میباشد:
Response.Redirect("~\\First?Id=1001"); string Id = Request.QueryString["Id"] as string;
برای آدرسهای پیشرفتهتر، 3 متود الحاقی اضافه گردیده است. مثلا اگر آدرس زیر را داشته باشیم
http://localhost:57353/First/Products/NewProduct
- ()Request.GetFriendlyUrlFileVirtualPath
مسیر مجازی فایل را بر میگرداند - ()Request.GetFriendlyUrlSegments
بخشهای آدرس را بر میگرداند - ()Request.GetFriendlyUrlFileExtension
پسوند فایل آدرس را بر میگرداند
کلاس FriendlyUrl دو متود استاتیک و یک خاصیت دارد
- FriendlyUrl.Segments
شبیه ()Request.GetFriendlyUrlSegments مجموعه بخشهای آدرس صفحه جاری را بر میگرداند - FriendlyUrl.Resolve
آدرس صفحه جاری را بر میگرداند - FriendlyUrl.Href
امکان ساخت یک آدرس را با استفاده از مسیر مجازی، بخشها و پارامترها، فراهم مینماید
در آخر، فضای نام کتابخانه، Microsoft.AspNet.FriendlyUrls میباشد.
مطالب
Roslyn #5
بررسی Semantic Models
همانطور که از قسمت قبل بهخاطر دارید، برای دسترسی به اطلاعات semantics، نیاز به یک context مناسب که همان Compilation API است، میباشد. این context دارای اطلاعاتی مانند دسترسی به تمام نوعهای تعریف شدهی توسط کاربر و متادیتاهای ارجاعی، مانند کلاسهای پایهی دات نت فریمورک است. بنابراین پس از ایجاد وهلهای از Compilation API، کار با فراخوانی متد GetSemanticModel آن ادامه مییابد. در ادامه با مثالهایی، کاربرد این متد را بررسی خواهیم کرد.
ساختار جدید Optional
خروجیهای تعدادی از متدهای Roslyn با ساختار جدیدی به نام Optional ارائه میشوند:
این ساختار که بسیار شبیه است به ساختار قدیمی <Nullable<T، منحصر به Value types نیست و Reference types را نیز شامل میشود و بیانگر این است که آیا یک Reference type، واقعا مقدار دهی شدهاست یا خیر؟
دریافت مقادیر ثابت Literals
فرض کنید میخواهیم مقدار ثابت ; int x = 42 را دریافت کنیم. برای اینکار ابتدا باید syntax tree آن تشکیل شود و سپس نیاز به یک سری حلقه و if و else و همچنین بررسی نال بودن بسیاری از موارد است تا به نود مقدار ثابت 42 برسیم. سپس متد GetConstantValue مربوط به GetSemanticModel را بر روی آن فراخوانی میکنیم تا به مقدار واقعی آن که ممکن است در اثر محاسبات جاری تغییر کرده باشد، برسیم.
اما روش بهتر و توصیه شده، استفاده از CSharpSyntaxWalker است که در انتهای قسمت سوم معرفی شد:
اگر به کدهای ادامهی بحث دقت کنید، قصد داریم مقادیر ثابت آرگومانهای Console.WriteLine را استخراج کنیم. به همین جهت در این SyntaxWalker، نوع Console و متد WriteLine آن مورد بررسی قرار گرفتهاند. اگر این نود دارای یک تک آرگومان بود، آین آرگومان استخراج شده و به لیست آرگومانهای خروجی این کلاس اضافه میشود.
در ادامه نحوهی استفادهی از این SyntaxWalker را ملاحظه میکنید. در اینجا ابتدا سورس کدی حاوی یک سری Console.WriteLine که دارای تک آرگومانهای ثابتی هستند، تبدیل به syntax tree میشود. سپس از روی آن CSharpCompilation تولید میگردد تا بتوان به اطلاعات semantics دسترسی یافت:
در ادامه با استفاده از CSharpCompilation و متد GetSemanticModel آن به SemanticModel جاری دسترسی خواهیم یافت. اکنون SyntaxWalker را وارد به حرکت بر روی ریشهی syntax tree سورس کد آنالیز شده میکنیم. به این ترتیب لیست آرگومانهای متدهای Console.WriteLine بدست میآیند. سپس با فراخوانی متد model.GetConstantValue بر روی هر آرگومان دریافتی، مقادیر آنها با فرمت <Optional<T استخراج میشوند.
خروجی نمایش داده شدهی توسط برنامه به صورت ذیل است:
درک مفهوم Symbols
اینترفیس ISymbol در Roslyn، ریشهی تمام Symbolهای مختلف مدل سازی شدهی در آن است که تعدادی از آنها را در تصویر ذیل مشاهده میکنید:
API کار با Symbols بسیار شبیه به API کار با Reflection است با این تفاوت که در زمان آنالیز کدها رخ میدهد و نه در زمان اجرای برنامه. همچنین در Symbols API امکان دسترسی به اطلاعاتی مانند locals, labels و امثال آن نیز وجود دارد که با استفاده از Reflection زمان اجرای برنامه قابل دسترسی نیستند. برای مثال فضاهای نام در Reflection صرفا به صورت رشتهای، با دات جدا شده از نوعهای آنالیز شدهی توسط آن است؛ اما در اینجا مطابق تصویر فوق، یک اینترفیس مجزای خاص خود را دارد. جهت سهولت کار کردن با Symbols، الگوی Visitor با معرفی کلاس پایهی SymbolVisitor نیز پیش بینی شدهاست.
یکی از کاربردهای مهم Symbols API دریافت اطلاعات Symbols نقطهای خاص از کدها میباشد. برای مثال در محل اشارهگر ادیتور، چه Symbols ایی تعریف شدهاند و از آنها در مباحث ساخت افزونههای آنالیز کدها زیاد استفاده میشود. نمونهای از آنرا در قطعه کد فوق ملاحظه میکنید. در اینجا با استفاده از متد GetEnclosingSymbol، سعی در یافتن Symbols قرار گرفتهی در ناحیهی insideBar# کدهای فوق داریم؛ با خروجی ذیل که نام demo.exe آن از نام CSharpCompilation آن گرفته شدهاست:
همچنین در ادامهی کد، توسط متد IsAccessible قصد داریم بررسی کنیم آیا Symbol قرار گرفته در محل کرسر، دسترسی به خاصیت protected کلاس Qux را دارد یا خیر؟ که پاسخ آن خیر است.
آشنایی با Binding symbols
یکی از مراحل کامپایل کد، binding نام دارد و در این مرحله است که اطلاعات Symbolic هر نود از Syntax tree دریافت میشود. برای مثال در اینجا مشخص میشود که این x، آیا یک متغیر محلی است، یا یک فیلد و یا یک خاصیت؟
مثال ذیل بسیار شبیه است به مثال getConstantValue ابتدای بحث، با این تفاوت که در حلقهی آخر کار از متد GetSymbolInfo استفاده شدهاست:
با این خروجی:
در مثال فوق، با استفاده از Syntax Walker طراحی شده در ابتدای بحث که کار استخراج آرگومانهای متدهای Console.WriteLine را انجام میدهد، قصد داریم بررسی کنیم، هر آرگومان به چه Symbol ایی بایند شدهاست و نوعش چیست؟ برای مثال Console.WriteLine اول که از پارامتر x استفاده میکند، نوع x مورد استفادهاش چیست؟ آیا فیلد است، متغیر محلی است یا یک پارامتر؟ این اطلاعات را با استفاده از متد model.GetSymbolInfo میتوان استخراج کرد.
همانطور که از قسمت قبل بهخاطر دارید، برای دسترسی به اطلاعات semantics، نیاز به یک context مناسب که همان Compilation API است، میباشد. این context دارای اطلاعاتی مانند دسترسی به تمام نوعهای تعریف شدهی توسط کاربر و متادیتاهای ارجاعی، مانند کلاسهای پایهی دات نت فریمورک است. بنابراین پس از ایجاد وهلهای از Compilation API، کار با فراخوانی متد GetSemanticModel آن ادامه مییابد. در ادامه با مثالهایی، کاربرد این متد را بررسی خواهیم کرد.
ساختار جدید Optional
خروجیهای تعدادی از متدهای Roslyn با ساختار جدیدی به نام Optional ارائه میشوند:
public struct Optional<T> { public bool HasValue { get; } public T Value { get; } }
دریافت مقادیر ثابت Literals
فرض کنید میخواهیم مقدار ثابت ; int x = 42 را دریافت کنیم. برای اینکار ابتدا باید syntax tree آن تشکیل شود و سپس نیاز به یک سری حلقه و if و else و همچنین بررسی نال بودن بسیاری از موارد است تا به نود مقدار ثابت 42 برسیم. سپس متد GetConstantValue مربوط به GetSemanticModel را بر روی آن فراخوانی میکنیم تا به مقدار واقعی آن که ممکن است در اثر محاسبات جاری تغییر کرده باشد، برسیم.
اما روش بهتر و توصیه شده، استفاده از CSharpSyntaxWalker است که در انتهای قسمت سوم معرفی شد:
class ConsoleWriteLineWalker : CSharpSyntaxWalker { public ConsoleWriteLineWalker() { Arguments = new List<ExpressionSyntax>(); } public List<ExpressionSyntax> Arguments { get; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var member = node.Expression as MemberAccessExpressionSyntax; var type = member?.Expression as IdentifierNameSyntax; if (type != null && type.Identifier.Text == "Console" && member.Name.Identifier.Text == "WriteLine") { if (node.ArgumentList.Arguments.Count == 1) { var arg = node.ArgumentList.Arguments.Single().Expression; Arguments.Add(arg); return; } } base.VisitInvocationExpression(node); } }
در ادامه نحوهی استفادهی از این SyntaxWalker را ملاحظه میکنید. در اینجا ابتدا سورس کدی حاوی یک سری Console.WriteLine که دارای تک آرگومانهای ثابتی هستند، تبدیل به syntax tree میشود. سپس از روی آن CSharpCompilation تولید میگردد تا بتوان به اطلاعات semantics دسترسی یافت:
static void getConstantValue() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { Console.WriteLine(3.14); Console.WriteLine(""qux""); Console.WriteLine('c'); Console.WriteLine(null); Console.WriteLine(x * 2 + 1); } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Analyze the constant argument (if any). foreach (var arg in walker.Arguments) { var val = model.GetConstantValue(arg); if (val.HasValue) { Console.WriteLine(arg + " has constant value " + (val.Value ?? "null") + " of type " + (val.Value?.GetType() ?? typeof(object))); } else { Console.WriteLine(arg + " has no constant value"); } } }
خروجی نمایش داده شدهی توسط برنامه به صورت ذیل است:
3.14 has constant value 3.14 of type System.Double "qux" has constant value qux of type System.String 'c' has constant value c of type System.Char null has constant value null of type System.Object x * 2 + 1 has no constant value
درک مفهوم Symbols
اینترفیس ISymbol در Roslyn، ریشهی تمام Symbolهای مختلف مدل سازی شدهی در آن است که تعدادی از آنها را در تصویر ذیل مشاهده میکنید:
API کار با Symbols بسیار شبیه به API کار با Reflection است با این تفاوت که در زمان آنالیز کدها رخ میدهد و نه در زمان اجرای برنامه. همچنین در Symbols API امکان دسترسی به اطلاعاتی مانند locals, labels و امثال آن نیز وجود دارد که با استفاده از Reflection زمان اجرای برنامه قابل دسترسی نیستند. برای مثال فضاهای نام در Reflection صرفا به صورت رشتهای، با دات جدا شده از نوعهای آنالیز شدهی توسط آن است؛ اما در اینجا مطابق تصویر فوق، یک اینترفیس مجزای خاص خود را دارد. جهت سهولت کار کردن با Symbols، الگوی Visitor با معرفی کلاس پایهی SymbolVisitor نیز پیش بینی شدهاست.
static void workingWithSymbols() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { // #insideBar } } class Qux { protected int Baz { get; set; } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse enclosing symbol hierarchy. var cursor = code.IndexOf("#insideBar"); var barSymbol = model.GetEnclosingSymbol(cursor); for (var symbol = barSymbol; symbol != null; symbol = symbol.ContainingSymbol) { Console.WriteLine(symbol); } // Analyze accessibility of Baz inside Bar. var bazProp = ((CompilationUnitSyntax)root) .Members.OfType<ClassDeclarationSyntax>() .Single(m => m.Identifier.Text == "Qux") .Members.OfType<PropertyDeclarationSyntax>() .Single(); var bazSymbol = model.GetDeclaredSymbol(bazProp); var canAccess = model.IsAccessible(cursor, bazSymbol); }
Foo.Bar(int) Foo <global namespace> Demo.exe Demo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
همچنین در ادامهی کد، توسط متد IsAccessible قصد داریم بررسی کنیم آیا Symbol قرار گرفته در محل کرسر، دسترسی به خاصیت protected کلاس Qux را دارد یا خیر؟ که پاسخ آن خیر است.
آشنایی با Binding symbols
یکی از مراحل کامپایل کد، binding نام دارد و در این مرحله است که اطلاعات Symbolic هر نود از Syntax tree دریافت میشود. برای مثال در اینجا مشخص میشود که این x، آیا یک متغیر محلی است، یا یک فیلد و یا یک خاصیت؟
مثال ذیل بسیار شبیه است به مثال getConstantValue ابتدای بحث، با این تفاوت که در حلقهی آخر کار از متد GetSymbolInfo استفاده شدهاست:
static void bindingSymbols() { // Get the syntax tree. var code = @" using System; class Foo { private int y; void Bar(int x) { Console.WriteLine(x); Console.WriteLine(y); int z = 42; Console.WriteLine(z); Console.WriteLine(a); } }"; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Bind the arguments. foreach (var arg in walker.Arguments) { var symbol = model.GetSymbolInfo(arg); if (symbol.Symbol != null) { Console.WriteLine(arg + " is bound to " + symbol.Symbol + " of type " + symbol.Symbol.Kind); } else { Console.WriteLine(arg + " could not be bound"); } } }
x is bound to int of type Parameter y is bound to Foo.y of type Field z is bound to z of type Local a could not be bound
در قسمت قبلی با Volume آشنا شدیم و نحوهی اجرا کردن یک Source Code را درون Container یاد گرفتیم. در این قسمت میخواهیم یک Image شخصی ساخته، آنرا اجرا و درون Docker hub ارسال نماییم.
درون package.json هم این قسمت را تغییر میدهیم:
حال فایل Dockerfile را ساخته و دستورالعملهای زیر را درون آن مینویسیم:
توضیحات دستورات فوق
حال با استفاده از port 8080 میتوانید اپلیکیشن را اجرا نمایید.
حال روی localhost قابلیت اجرا خواهد داشت؛ اما میخواهیم این app را بر روی container اجرا کنیم. بنابراین Dockerfile را اینگونه مینویسیم:
همه چیز خیلی شبیه به داکرفایل قبلی است، با این تفاوت که از ایمیج microsoft/dotnet استفاده کردهایم (از imageهای سبکتری برای محیط production استفاده میکنیم! ضمن اینکه image فوق از Debian استفاده میکند. image جدیدی روی توزیع Alpine ایجاد شده است که حجم خیلی پایینی داشته و برای مطالعه بیشتر به اینجا رجوع کنید).
اکنون app شما باید روی پورت خارجی 8080 قابل اجرا باشد.
Dockerfile چیست؟
Dockerfile عملا چیزی بیشتر از یک دستور العمل از نوع متنی برای build و ساخت یک docker image از آن نمیباشد. ضمن اینکه مراحل build شدن، cache شده و buildهای بعدی با سرعت خیلی بیشتری اجرا خواهند شد. بعد از نوشتن چند dockerfile متوجه خواهیم شد که مراحلش بسیار ساده است.
ساخت اولین Dockerfile
قبل از ساخت dockerfile، مثل جلسهی قبل یک پروژهی سادهی nodejsی را با فایل index.js میسازیم:
const express = require('express') const app = express() const PORT = 3000; app.get('/', (req, res) => { res.send('Hello World') }) app.listen(PORT, () => { console.log(`listening on port ${PORT}!`) })
"scripts": { "start": "node index" },
FROM node ENV NODE_ENV=production COPY . /var/www WORKDIR /var/www RUN npm i EXPOSE 3000 ENTRYPOINT npm start
1) FROM node یک imageی است که برنامهی شما از آن استفاده میکند.
2) از environment variable استفاده کرده و نوع آن را روی production میگذاریم.
3) COPY کردن تمام فایلهای دایرکتوری جاری پروژه درون فایل سیستم container به آدرس فوق.
4) عوض کردن work directory روی آدرسی که پروژه کپی شده است.
5) اجرا کردن دستور npm i برای نصب شدن Dependenciesهای پروژه.
6) EXPOSE کردن یک port برای ایجاد دسترسی.
7) نقطهی شروع برنامه و دستور npm start که درون package.json قبل تعریف نموده بودیم.
بعد از ساخت Dockerfile فوق نوبت به build گرفتن از آن میرسد که با استفاده از دستور زیر میباشد:
docker build -f Dockerfile -t alikhll/testnode1 .
نکته: اگر image node را روی سیستم خود نداشته باشید ابتدا بصورت خودکار آن را pull مینماید.
1) پرچم f- که برای شناساندن فایل Dockefile میباشد، بدلیل این است که نام این فایل قابل تغییر میباشد.2) پرچم t- برای نام image ساخته شده میباشد. همچنین . نیز به دایرکتوری جاری اشاره میکند.
بعد از ساخته شدن image با استفاده از دستور docker images میتوانید آن را مشاهده نمایید.
برای اجرای image نیز از دستور زیر استفاده میکنیم:
docker run -d -p 8080:3000 alikhll/testnode1
از آنجایی که اکثر خوانندگان این مجموعه برنامه نویسان دات نت هستند یک Dockerfile دات نتی نیز برای تسلط بیشتر مینویسیم.
ابتدا دستورات زیر را درون ترمینال خود وارد کرده و یک پروژهی وب از نوع Net Core. را میسازیم:
dotnet new web dotnet restore dotnet run
FROM microsoft/dotnet ENV ASPNETCORE_URLS http://*:3000 COPY . /var/www WORKDIR /var/www RUN dotnet restore EXPOSE 3000 ENTRYPOINT dotnet run
نکتهی مهم ASPNETCORE_URLS میباشد چون میخواهیم بتوانیم از خارج از محیط container با استفاده از IP، به آن دسترسی داشته باشیم (محیط local نیست).
دستورات زیر را برای build و اجرا وارد مینماییم:
docker build -f Dockerfile -t alikhll/testasp1 .
docker run -d -p 8080:3000 alikhll/testasp1
نکته: من container قبلی nodejsی را stop کرده بودم وگرنه این پورت قابل استفادهی مجدد نبود!
پابلیش کردن روی Docker Hub
انتشار دادن روی Docker hub ّبسیار ساده است. میتوانید یک اکانت به صورت رایگان ساخته و imageهای خود را بر روی آن انتشار دهید.
نکته: پروژههای تستی خود را میتوانید آنجا انتشار داده که البته قابلیت private بودن را ندارند. در صورتیکه برای یک پروژهی واقعی که imageهای عمومی نیستند و فقط درون سازمان باید به آن دسترسی داشته باشند، میتوانید اکانت enterprise تهیه کرده و imageهای خود را درون آن مدیریت نمایید. همچنین از سرویسهای ابری Azure, Amazon نیز برای انتشار دادن imageهای خصوصی میتوان استفاده نمود.
دستور زیر برای انتشار دادن imageی که ساختیم روی docker hub میباشد. ابتدا login کرده user/password را وارد کرده سپس push مینماییم:
docker login
docker push alikhll/testnode1
نکته: به جای alikhll باید username شخصی خود را وارد نمایید.
اکنون به راحتی با استفاده از دستور زیر روی یک ماشین دیگر که داکر روی آن نصب شده است، میتوانید image را pull کرده و اجرا نمایید:
docker pull alikhll/testnode1