$scope.$on('$includeContentLoaded', function () { //دستورات بعد از اتمام بارگیری محتوا });
import someModule from './dir/someModule.js';
import('./dir/someModule.js') .then(someModule => someModule.foo());
یا یک مثال عملیاتی؛ فرض کنید با کلیک بر روی دکمهای میخواهیم یک Dialog را باز کنیم که منطق و قوائد مخصوص به خود را دارد و به صورت یک ماژول جداگانه نوشته شدهاست. کد زیر را مشاهده کنید:
button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ }) });
این قابلیت هم وجود دارد که دو ماژول را که در یک فایل نوشته شدهاند نیز به صورت جداگانه استفاده کنید.
import('./myModule.js') .then(({export1, export2}) => { export1.run(); export2.fire(); });
شما حتی میتوانید چند ماژول را باهم بارگذاری کنید و بعد از پایان بارگذاری همه ماژولها، یک عمل خاصی را انجام دهید. کد زیر را مشاهده کنید:
Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]) .then(([module1, module2, module3]) => { // code });
حتی شما میتوانید با قابلیت async و await نیز کدهای تمیزتر و با قابلیت خوانایی بالاتری را بنویسید. مثال زیر را مشاهده کنید:
async function main() { const myModule = await import('./myModule.js'); myModule.getInfo(); const {export1, export2} = await import('./myModule.js'); export1.run(); export2.fire() } main();
البته آقای Jake Archibald کد جالبی را برای این قابلیت پیشنهاد دادهاست که ترکیبی از import استاتیک ES6 میباشد:
function importModule (url) { return new Promise((resolve, reject) => { const script = document.createElement("script"); const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2); script.type = "module"; script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`; script.onload = () => { resolve(window[tempGlobal]); delete window[tempGlobal]; script.remove(); }; script.onerror = () => { reject(new Error("Failed to load module script with URL " + url)); delete window[tempGlobal]; script.remove(); }; document.documentElement.appendChild(script); }); }
تهیه سرویس اطلاعات پویای برنامه
سرویس Web API ارائه شدهی توسط ASP.NET Core در این برنامه، لیست کاربران را به همراه یادداشتهای آنها به سمت کلاینت باز میگرداند و ساختار موجودیتهای آنها به صورت زیر است:
موجودیت کاربر که یک رابطهی one-to-many را با UserNotes دارد:
using System; using System.Collections.Generic; namespace MaterialAspNetCoreBackend.DomainClasses { public class User { public User() { UserNotes = new HashSet<UserNote>(); } public int Id { set; get; } public DateTimeOffset BirthDate { set; get; } public string Name { set; get; } public string Avatar { set; get; } public string Bio { set; get; } public ICollection<UserNote> UserNotes { set; get; } } }
using System; namespace MaterialAspNetCoreBackend.DomainClasses { public class UserNote { public int Id { set; get; } public DateTimeOffset Date { set; get; } public string Title { set; get; } public User User { set; get; } public int UserId { set; get; } } }
public interface IUsersService { Task<List<User>> GetAllUsersIncludeNotesAsync(); Task<User> GetUserIncludeNotesAsync(int id); }
namespace MaterialAspNetCoreBackend.WebApp.Controllers { [Route("api/[controller]")] public class UsersController : Controller { private readonly IUsersService _usersService; public UsersController(IUsersService usersService) { _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); } [HttpGet] public async Task<IActionResult> Get() { return Ok(await _usersService.GetAllUsersIncludeNotesAsync()); } [HttpGet("{id:int}")] public async Task<IActionResult> Get(int id) { return Ok(await _usersService.GetUserIncludeNotesAsync(id)); } } }
در این حالت اگر برنامه را اجرا کنیم، در مسیر زیر
https://localhost:5001/api/users
و آدرس https://localhost:5001/api/users/1 صرفا مشخصات اولین کاربر را بازگشت میدهد.
تنظیم محل تولید خروجی Angular CLI
ساختار پوشه بندی پروژهی جاری به صورت زیر است:
همانطور که ملاحظه میکنید، کلاینت Angular در یک پوشهاست و برنامهی سمت سرور ASP.NET Core در پوشهای دیگر. برای اینکه خروجی نهایی Angular CLI را به پوشهی wwwroot پروژهی وب کپی کنیم، فایل angular.json کلاینت Angular را به صورت زیر ویرایش میکنیم:
"build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "../MaterialAspNetCoreBackend/MaterialAspNetCoreBackend.WebApp/wwwroot",
ng build --no-delete-output-path --watch
dotnet watch run
بنابراین دو صفحهی کنسول مجزا را باز کنید. در اولی ng build (را با پارامترهای یاد شده در پوشهی MaterialAngularClient) و در دومی dotnet watch run را در پوشهی MaterialAspNetCoreBackend.WebApp اجرا نمائید.
هر دو دستور در حالت watch اجرا میشوند. مزیت مهم آن این است که اگر تغییر کوچکی را در هر کدام از پروژهها ایجاد کردید، صرفا همان قسمت کامپایل میشود و در نهایت سرعت کامپایل نهایی برنامه به شدت افزایش خواهد یافت.
تعریف معادلهای کلاسهای موجودیتهای سمت سرور، در برنامهی Angular
در ادامه پیش از تکمیل سرویس دریافت اطلاعات از سرور، نیاز است معادلهای کلاسهای موجودیتهای سمت سرور خود را به صورت اینترفیسهایی تایپاسکریپتی تعریف کنیم:
ng g i contact-manager/models/user ng g i contact-manager/models/user-note
محتویات فایل contact-manager\models\user-note.ts :
export interface UserNote { id: number; title: string; date: Date; userId: number; }
import { UserNote } from "./user-note"; export interface User { id: number; birthDate: Date; name: string; avatar: string; bio: string; userNotes: UserNote[]; }
ایجاد سرویس Angular دریافت اطلاعات از سرور
ساختار ابتدایی سرویس دریافت اطلاعات از سرور را توسط دستور زیر ایجاد میکنیم:
ng g s contact-manager/services/user --no-spec
import { Injectable } from "@angular/core"; @Injectable({ providedIn: "root" }) export class UserService { constructor() { } }
کدهای تکمیل شدهی UserService را در ذیل مشاهده میکنید:
import { HttpClient, HttpErrorResponse } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { Observable, throwError } from "rxjs"; import { catchError, map } from "rxjs/operators"; import { User } from "../models/user"; @Injectable({ providedIn: "root" }) export class UserService { constructor(private http: HttpClient) { } getAllUsersIncludeNotes(): Observable<User[]> { return this.http .get<User[]>("/api/users").pipe( map(response => response || []), catchError((error: HttpErrorResponse) => throwError(error)) ); } getUserIncludeNotes(id: number): Observable<User> { return this.http .get<User>(`/api/users/${id}`).pipe( map(response => response || {} as User), catchError((error: HttpErrorResponse) => throwError(error)) ); } }
- متد getAllUsersIncludeNotes، لیست تمام کاربران را به همراه یادداشتهای آنها از سرور واکشی میکند.
- متد getUserIncludeNotes صرفا اطلاعات یک کاربر را به همراه یادداشتهای او از سرور دریافت میکند.
بارگذاری و معرفی فایل svg نمایش avatars کاربران به Angular Material
بستهی Angular Material و کامپوننت mat-icon آن به همراه یک MatIconRegistry نیز هست که قصد داریم از آن برای نمایش avatars کاربران استفاده کنیم.
در قسمت اول، نحوهی «افزودن آیکنهای متریال به برنامه» را بررسی کردیم که در آنجا آیکنهای مرتبط، از فایلهای قلم، دریافت و نمایش داده میشوند. این کامپوننت، علاوه بر قلم آیکنها، از فایلهای svg حاوی آیکنها نیز پشتیبانی میکند که یک نمونه از این فایلها در مسیر wwwroot\assets\avatars.svg فایل پیوستی انتهای مطلب کپی شدهاست (چون برنامهی وب ASP.NET Core، هاست برنامه است، این فایل را در آنجا کپی کردیم).
ساختار این فایل svg نیز به صورت زیر است:
<?xml version="1.0" encoding="utf-8"?> <svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <svg viewBox="0 0 128 128" height="100%" width="100%" pointer-events="none" display="block" id="user1" >
ابتدا به فایل contact-manager-app.component.ts مراجعه و سپس این کامپوننت آغازین ماژول مدیریت تماسها را با صورت زیر تکمیل میکنیم:
import { Component } from "@angular/core"; import { MatIconRegistry } from "@angular/material"; import { DomSanitizer } from "@angular/platform-browser"; @Component() export class ContactManagerAppComponent { constructor(iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) { iconRegistry.addSvgIconSet(sanitizer.bypassSecurityTrustResourceUrl("assets/avatars.svg")); } }
در اینجا در صورتیکه فایل svg شما دارای یک تک آیکن است، روش ثبت آن به صورت زیر است:
iconRegistry.addSvgIcon( "unicorn", this.domSanitizer.bypassSecurityTrustResourceUrl("assets/unicorn_icon.svg") );
<mat-icon svgIcon="unicorn"></mat-icon>
یک نکته: پوشهی node_modules\material-design-icons به همراه تعداد قابل ملاحظهای فایل svg نیز هست.
نمایش لیست کاربران در sidenav
در ادامه به فایل sidenav\sidenav.component.ts مراجعه کرده و سرویس فوق را به آن جهت دریافت لیست کاربران، تزریق میکنیم:
import { User } from "../../models/user"; import { UserService } from "../../services/user.service"; @Component() export class SidenavComponent implements OnInit { users: User[] = []; constructor(private userService: UserService) { } ngOnInit() { this.userService.getAllUsersIncludeNotes() .subscribe(data => this.users = data); } }
اکنون میخواهیم از این اطلاعات جهت نمایش پویای آنها در sidenav استفاده کنیم. در قسمت قبل، جای آنها را در منوی سمت چپ صفحه به صورت زیر با اطلاعات ایستا مشخص کردیم:
<mat-list> <mat-list-item>Item 1</mat-list-item> <mat-list-item>Item 2</mat-list-item> <mat-list-item>Item 3</mat-list-item> </mat-list>
<mat-nav-list> <mat-list-item *ngFor="let user of users"> <a matLine href="#"> <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.name }} </a> </mat-list-item> </mat-nav-list>
که در اینجا علاوه بر لیست کاربران که از سرویس Users دریافت شده، آیکن avatar آنها که از فایل assets/avatars.svg بارگذاری شده نیز قابل مشاهده است.
اتصال کاربران به صفحهی نمایش جزئیات آنها
در mat-nav-list فوق، فعلا هر کاربر به آدرس # لینک شدهاست. در ادامه میخواهیم با کمک سیستم مسیریابی، با کلیک بر روی نام هر کاربر، در سمت راست صفحه جزئیات او نیز نمایش داده شود:
<mat-nav-list> <mat-list-item *ngFor="let user of users"> <a matLine [routerLink]="['/contactmanager', user.id]"> <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.name }} </a> </mat-list-item> </mat-nav-list>
const routes: Routes = [ { path: "", component: ContactManagerAppComponent, children: [ { path: ":id", component: MainContentComponent }, { path: "", component: MainContentComponent } ] }, { path: "**", redirectTo: "" } ];
این مشکل دو علت دارد:
الف) چون ContactManagerModule را به صورت lazy load تعریف کردهایم، دیگر نباید در لیست imports فایل AppModule ظاهر شود. بنابراین فایل app.module.ts را گشوده و سپس تعریف ContactManagerModule را هم از قسمت imports بالای صفحه و هم از قسمت imports ماژول حذف کنید؛ چون نیازی به آن نیست.
ب) برای مدیریت خواندن id کاربر، فایل main-content\main-content.component.ts را گشوده و به صورت زیر تکمیل میکنیم:
import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { User } from "../../models/user"; import { UserService } from "../../services/user.service"; @Component({ selector: "app-main-content", templateUrl: "./main-content.component.html", styleUrls: ["./main-content.component.css"] }) export class MainContentComponent implements OnInit { user: User; constructor(private route: ActivatedRoute, private userService: UserService) { } ngOnInit() { this.route.params.subscribe(params => { this.user = null; const id = params["id"]; if (!id) { return; } this.userService.getUserIncludeNotes(id) .subscribe(data => this.user = data); }); } }
اکنون میتوان از اطلاعات این user دریافتی، در قالب این کامپوننت و یا همان فایل main-content.component.html استفاده کرد:
<div *ngIf="!user"> <mat-spinner></mat-spinner> </div> <div *ngIf="user"> <mat-card> <mat-card-header> <mat-icon mat-card-avatar svgIcon="{{user.avatar}}"></mat-icon> <mat-card-title> <h2>{{ user.name }}</h2> </mat-card-title> <mat-card-subtitle> Birthday {{ user.birthDate | date:'d LLLL' }} </mat-card-subtitle> </mat-card-header> <mat-card-content> <mat-tab-group> <mat-tab label="Bio"> <p> {{user.bio}} </p> </mat-tab> <!-- <mat-tab label="Notes"></mat-tab> --> </mat-tab-group> </mat-card-content> </mat-card> </div>
همچنین mat-card را هم بر اساس مثال مستندات آن، ابتدا کپی و سپس سفارشی سازی کردهایم (اگر دقت کنید، هر کامپوننت آن سه برگهی overview، سپس API و در آخر Example را به همراه دارد). این روشی است که همواره میتوان با کامپوننتهای این مجموعه انجام داد. ابتدا مثالی را در مستندات آن پیدا میکنیم که مناسب کار ما باشد. سپس سورس آنرا از همانجا کپی و در برنامه قرار میدهیم و در آخر آنرا بر اساس اطلاعات خود سفارشی سازی میکنیم.
نمایش جزئیات اولین کاربر در حین بارگذاری اولیهی برنامه
تا اینجای کار اگر برنامه را از ابتدا بارگذاری کنیم، mat-spinner قسمت نمایش جزئیات تماسها ظاهر میشود و همانطور باقی میماند، با اینکه هنوز موردی انتخاب نشدهاست. برای رفع آن به کامپوننت sidnav مراجعه کرده و در لحظهی بارگذاری اطلاعات، اولین مورد را به صورت دستی نمایش میدهیم:
import { Router } from "@angular/router"; @Component() export class SidenavComponent implements OnInit, OnDestroy { users: User[] = []; constructor(private userService: UserService, private router: Router) { } ngOnInit() { this.userService.getAllUsersIncludeNotes() .subscribe(data => { this.users = data; if (data && data.length > 0 && !this.router.navigated) { this.router.navigate(["/contactmanager", data[0].id]); } }); } }
البته روش دیگر مدیریت این حالت، حذف کدهای فوق و تبدیل کدهای کامپوننت main-content به صورت زیر است:
let id = params['id']; if (!id) id = 1;
بستن خودکار sidenav در حالت نمایش موبایل
اگر اندازهی صفحهی نمایشی را کوچکتر کنیم، قسمت sidenav در حالت over نمایان خواهد شد. در این حالت اگر آیتمهای آنرا انتخاب کنیم، هرچند آنها نمایش داده میشوند، اما زیر این sidenav مخفی باقی خواهند ماند:
بنابراین در جهت بهبود کاربری این قسمت بهتر است با کلیک کاربر بر روی sidenav و گزینههای آن، این قسمت بسته شده و ناحیهی زیر آن نمایش داده شود.
در کدهای قالب sidenav، یک template reference variable برای آن به نام sidenav درنظر گرفته شدهاست:
<mat-sidenav #sidenav
import { MatSidenav } from "@angular/material"; @Component() export class SidenavComponent implements OnInit, OnDestroy { @ViewChild(MatSidenav) sidenav: MatSidenav;
ngOnInit() { this.router.events.subscribe(() => { if (this.isScreenSmall) { this.sidenav.close(); } }); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-03.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
1- باز کردن فایل hosts و نوشتن کدهای زیر (البته شما میتوانید هر اسم دلخواهی وارد نمایید)
127.0.0.1 localhost 127.0.0.1 mysite.com 127.0.0.1 master.mysite.com 127.0.0.1 store1.mysite.com 127.0.0.1 store2.mysite.com 127.0.0.1 store3.mysite.com 127.0.0.1 store4.mysite.com 127.0.0.1 store5.mysite.com 127.0.0.1 store6.mysite.com 127.0.0.1 store7.mysite.com 127.0.0.1 store8.mysite.com 127.0.0.1 store9.mysite.com
<bindings> <!--<binding protocol="http" bindingInformation="*:80:localhost" />--> <binding protocol="http" bindingInformation="*:80:mysite.com" /> <binding protocol="http" bindingInformation="*:80:master.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store1.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store2.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store3.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store4.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store5.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store6.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store7.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store8.mysite.com" /> <binding protocol="http" bindingInformation="*:80:store9.mysite.com" /> </bindings>
برای این کار ابتدا cmd را با دسترسی مدیریتی اجرا کنید و با استفاده از دستور زیر میتوان تک تک دامنهها را رجیستر کرد:
netsh http add urlacl url=http://mysite.com:80/ user=everyone netsh http add urlacl url=http://master.mysite.com:80/ user=everyone . . . netsh http add urlacl url=http://store9.mysite.com:80/ user=everyone
پ.ن: در صورتی که نمیخواهید از پورت 80 استفاده کنید میتوانید به جای 80 پورت دلخواه خود را وارد نمایید. در این صورت از طریق آدرس mysite.com:mportnumber در دسترس خواهد بود
الف) ابتدا افزونه Razor Generator را از اینجا دریافت و نصب کنید.
ب) سپس به پروژه MVC خود بسته NuGet زیر را اضافه نمائید:
PM> Install-Package RazorGenerator.Mvc
با اضافه کردن این بسته NuGet تغییرات زیر به پروژه جاری اعمال خواهند شد:
- ارجاعی به اسمبلی RazorGenerator.Mvc.dll به پروژه اضافه خواهد شد.
- در پوشه App_Start، فایلی به نام RazorGeneratorMvcStart.cs اضافه گردیده و کار تنظیم موتور View مخصوص کار با Viewهای کامپایل شده را انجام میدهد.
ج) پس از نصب بسته NuGet یاد شده در همان خط فرمان پاورشل نوگت دستور زیر را صادر کنید:
PM> Enable-RazorGenerator
پس از انجام اینکار، دو کار صورت خواهد گرفت:
- برای تمام Viewهای برنامه، فایل cs متناظری تولید میشود که ذیل فایلهای View قابل مشاهده است.
- گزینه Custom tool این Viewها نیز به RazorGenerator تنظیم میشود.
بدیهی است اگر از دستور Enable-RazorGenerator استفاده نکنید، نیاز خواهید داشت تا تنظیم گزینه Custom tool به RazorGenerator کلیه Viewها را دستی انجام داده و اگر فایل cs متناظر با View تولید نشد روی فایل view کلیک راست کرده و گزینه run custom tool را انتخاب کنید. اما دستور Enable-RazorGenerator کار را بسیار ساده میکند.
مزایا:
- در عمل موتور ASP.NET همین کارها را در زمان اولین بار اجرای Viewها(ی کامپایل نشده) در پشت صحنه انجام میدهد. بنابراین با این روش زمان آغاز برنامه سریعتر میشود.
- دیگر نیازی به توزیع فایلهای cshtml نخواهید داشت.
- خطایابی Viewها نیز سادهتر میشود. از این جهت که کامپایل آنها به زمان اجرا موکول نخواهد شد.
یک سری قابلیتهای دیگر نیز به همراه این پروژه است مانند انتقال Viewها به یک اسمبلی دیگر و یا استفاده از MSBuild برای انجام عملیات که میتوانید آنها را در Wiki پروژه Razor Generator مطالعه کنید. انتقال Viewها به یک اسمبلی دیگر هرچند در این روش کاملا ممکن شده و کار میکند اما صفحه dialog افزودن یک view جدید مهیا در کلیک راست بر روی اکشن متدهای یک کنترلر را غیرفعال میکند که در عمل آنچنان جالب نیست.
یک نکته مهم:
اگر در آینده بسته NuGet و افزونه یاد شده را به روز کردید نیاز است دستور زیر را اجرا کنید:
PM> Redo-RazorGenerator
فایلهای Helper قرار گرفته در پوشه App_Code
اگر HTML Helperهای خود را توسط فایلهای Razor قرار گرفته در پوشه App_Code تولید میکنید، پس از اجرای دستور Enable-RazorGenerator، برای این موارد نیز فایلهای cs متناظری تولید میشود. با این تفاوت که Build Action آنها بر روی Compile قرار ندارند که این مورد را باید دستی تنظیم کنید. همچنین حین استفاده از این توابع کمکی نیاز است فضای نام مرتبط را نیز در ابتدای فایل View خود ذکر کنید مثلا:
@using MvcViewGenTest2.app_code
حذف فایل RazorGeneratorMvcStart.cs
اگر علاقمند به استفاده از فایل پیش فرض RazorGeneratorMvcStart.cs نیستید و میخواهید موتورهای View اضافی را حذف کنید، ابتدا فایل RazorGeneratorMvcStart.cs را حذف کرده و سپس در فایل global.asax.cs تغییر زیر را اعمال نمائید:
using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; using System.Web.WebPages; using RazorGenerator.Mvc; namespace MvcViewGenTest2 { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); // Adding PrecompiledMvcEngine var engine = new PrecompiledMvcEngine(typeof(MvcApplication).Assembly); ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(engine); VirtualPathFactoryManager.RegisterVirtualPathFactory(engine); } } }
استفاده از محیط گرافیکی IIS جهت لیست کردن، اضافه و حذف ماژولها
به بخش modules درIIS بروید. در پنل سمت راست همه امکانات جهت افزودن و ویرایش و حذف وجود دارند:
روش معرفی ماژول در خط فرمان با استفاده از دستور Appcmd
Appcmd.exe install module /name:MODULE_NAME /image:PATH_TO_DLL
قسمت name که نام ماژول است و قسمت image هم مسیر قرار گرفتن فایل dll هست.
برای نمونه:
%windir%\system32\inetsrv\appcmd.exe install module /name:DefaultDocumentModule /image:%windir%\system32\inetsrv\defdoc.dll
در صورتیکه ماژولی که قبلا افزوده شده باشد را بخواهید اضافه کنید، خطای زیر را دریافت خواهید کرد:
ERROR ( message:Failed to add duplicate collection element "DefaultDocumentModule". )
جهت حذف ماژول دستور زیر را صادر کنید:
Appcmd.exe uninstall module MODULE_NAME
نمونه:
%windir%\system32\inetsrv\appcmd.exe uninstall module DefaultDocumentModule
گرفتن کوئری یا لیستی از ماژولهای فعال برای یک اپلیکیشن یا عمومی:
Appcmd.exe list modules [/app.name:APPLICATION_NAME]
سوپپچ aap.name اختیاری است ولی اگر نام یک اپلیکیشن را به آن بدهید، فقط ماژولهایی را که روی این اپلیکیشن اجرا میشوند، لیست میکند.
نمونه:
%windir%\system32\inetsrv\appcmd.exe list modules /app.name:"Default Web Site"
کد زیر هم نمونه ای برای لیست کردن تمامی ماژولهای عمومی که بر روی تمامی اپلیکیشنها اجرا میشوند:
%windir%\system32\inetsrv\appcmd.exe list modules
خط زیر یک ماژول را برای همه اپلیکیشنها یا اپلیکیشن خاصی فعال میکند که بستگی دارد سوییچ type چگونه مقداردهی شده باشد:
Appcmd.exe add module /name:MODULE_NAME /type:MGD_TYPE
برای مثال خط زیر باعث میشود ماژول Forms Authentication فقط برای وب اپلیکیشن default web site فعال شود:
%windir%\system32\inetsrv\appcmd.exe add module /name:FormsAuthentication /type:System.Web.Security.FormsAuthenticationModule /app.name:"Default Web Site"
یا در پایین آن را به صورت عمومی یا global فعال میکند:
%windir%\system32\inetsrv\appcmd.exe add module /name:FormsAuthentication /type:System.Web.Security.FormsAuthenticationModule
برای غیرفعال کردن یک ماژول از دستور زیر استفاده میشود:
Appcmd.exe delete module MODULE_NAME [/app.name:APPLICATION_NAME]
اگر غیر فعال کردن یک ماژول در یک اپلیکیشن خاص مدنظر شما باشد دستور زیر نمونه آن است:
%windir%\system32\inetsrv\appcmd.exe delete module FormsAuthentication /app.name:"Default Web Site"
اگر قصد دارید آنرا بر روی تمامی اپلیکیشنها غیرفعال کنید، دستور زیر نمونه آن است:
%windir%\system32\inetsrv\appcmd.exe delete module FormsAuthentication
حفظ کردن یا به خاطر سپردن دستورات بالا ممکن است کار سخت و دشواری باشد، به همین جهت از help کمک بگیرید:
Appcmd.exe module /?
یا به شکل اختصاصیتر برای یک دستور
Appcmd.exe install module /?Appcmd add module /?
پشتیبانی از Full Text Search در SQL Server
Full Text Search یا به اختصار FTS یکی از قابلیتهای SQL Server جهت جستجوی پیشرفته در متون میباشد. این قابلیت تا کنون در EF 6.1.1 ایجاد نشده است.
در ادامه پیاده سازی از FTS در EF را مشاهده مینمایید.
جهت ایجاد قابلیت FTS از متد Contains در Linq استفاده شده است.
ابتدا متدهای الحاقی جهت اعمال دستورات FREETEXT و CONTAINS اضافه میشود.سپس دستورات تولیدی EF را قبل از اجرا بر روی بانک اطلاعاتی توسط امکان Command Interception به دستورات FTS تغییر میدهیم.
همانطور که میدانید دستور Contains در Linq توسط EF به دستور LIKE تبدیل میشود. به جهت اینکه ممکن است بخواهیم از دستور LIKE نیز استفاده کنیم یک پیشوند به مقادیری که میخواهیم به دستورات FTS تبدیل شوند اضافه مینماییم.
جهت استفاده از Fts در EF کلاسهای زیر را ایجاد نمایید.
کلاس FullTextPrefixes :
/// <summary> /// /// </summary> public static class FullTextPrefixes { /// <summary> /// /// </summary> public const string ContainsPrefix = "-CONTAINS-"; /// <summary> /// /// </summary> public const string FreetextPrefix = "-FREETEXT-"; /// <summary> /// /// </summary> /// <param name="searchTerm"></param> /// <returns></returns> public static string Contains(string searchTerm) { return string.Format("({0}{1})", ContainsPrefix, searchTerm); } /// <summary> /// /// </summary> /// <param name="searchTerm"></param> /// <returns></returns> public static string Freetext(string searchTerm) { return string.Format("({0}{1})", FreetextPrefix, searchTerm); } }
کلاس جاری جهت علامت گذاری دستورات FTS میباشد و توسط متدهای الحاقی و کلاس FtsInterceptor استفاده میگردد.
کلاس FullTextSearchExtensions :
public static class FullTextSearchExtensions { /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> public static IQueryable<TEntity> FreeTextSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class { return FreeTextSearchImp(source, expression, FullTextPrefixes.Freetext(searchTerm)); } /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> public static IQueryable<TEntity> ContainsSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class { return FreeTextSearchImp(source, expression, FullTextPrefixes.Contains(searchTerm)); } /// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="source"></param> /// <param name="expression"></param> /// <param name="searchTerm"></param> /// <returns></returns> private static IQueryable<TEntity> FreeTextSearchImp<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) { if (String.IsNullOrEmpty(searchTerm)) { return source; } // The below represents the following lamda: // source.Where(x => x.[property].Contains(searchTerm)) //Create expression to represent x.[property].Contains(searchTerm) //var searchTermExpression = Expression.Constant(searchTerm); var searchTermExpression = Expression.Property(Expression.Constant(new { Value = searchTerm }), "Value"); var checkContainsExpression = Expression.Call(expression.Body, typeof(string).GetMethod("Contains"), searchTermExpression); //Join not null and contains expressions var methodCallExpression = Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Lambda<Func<TEntity, bool>>(checkContainsExpression, expression.Parameters)); return source.Provider.CreateQuery<TEntity>(methodCallExpression); }
در این کلاس متدهای الحاقی جهت اعمال قابلیت Fts ایجاد شده است.
متد FreeTextSearch جهت استفاده از دستور FREETEXT استفاده میشود.در این متد پیشوند -FREETEXT- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد ContainsSearch جهت استفاده از دستور CONTAINS استفاده میشود.در این متد پیشوند -CONTAINS- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد FreeTextSearchImp جهت ایجاد دستور Contains در Linq میباشد.
کلاس FtsInterceptor :
public class FtsInterceptor : IDbCommandInterceptor { /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { RewriteFullTextQuery(command); } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { RewriteFullTextQuery(command); } /// <summary> /// /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { } /// <summary> /// /// </summary> /// <param name="cmd"></param> public static void RewriteFullTextQuery(DbCommand cmd) { var text = cmd.CommandText; for (var i = 0; i < cmd.Parameters.Count; i++) { var parameter = cmd.Parameters[i]; if ( !parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength)) continue; if (parameter.Value == DBNull.Value) continue; var value = (string)parameter.Value; if (value.IndexOf(FullTextPrefixes.ContainsPrefix, StringComparison.Ordinal) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefixes.ContainsPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"CONTAINS([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } else if (value.IndexOf(FullTextPrefixes.FreetextPrefix, StringComparison.Ordinal) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefixes.FreetextPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"FREETEXT([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } } } }
در این کلاس دستوراتی را که توسط متدهای الحاقی جهت امکان Fts علامت گذاری شده بودند را یافته و دستور LIKE را با دستورات CONTAINSو FREETEXT جایگزین میکنیم.
در ادامه برنامه ای جهت استفاده از این امکان به همراه دستورات تولیدی آنرا مشاهده مینمایید.
public class Note { /// <summary> /// /// </summary> public int Id { get; set; } /// <summary> /// /// </summary> public string NoteText { get; set; } } public class FtsSampleContext : DbContext { /// <summary> /// /// </summary> public DbSet<Note> Notes { get; set; } /// <summary> /// /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new NoteMap()); } } /// <summary> /// /// </summary> class Program { /// <summary> /// /// </summary> /// <param name="args"></param> static void Main(string[] args) { DbInterception.Add(new FtsInterceptor()); const string searchTerm = "john"; using (var db = new FtsSampleContext()) { var result1 = db.Notes.FreeTextSearch(a => a.NoteText, searchTerm).ToList(); //SQL Server Profiler result ===>>> //exec sp_executesql N'SELECT // [Extent1].[Id] AS [Id], // [Extent1].[NoteText] AS [NoteText] // FROM [dbo].[Notes] AS [Extent1] // WHERE FREETEXT([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 //char(4096)',@p__linq__0='(john)' var result2 = db.Notes.ContainsSearch(a => a.NoteText, searchTerm).ToList(); //SQL Server Profiler result ===>>> //exec sp_executesql N'SELECT // [Extent1].[Id] AS [Id], // [Extent1].[NoteText] AS [NoteText] // FROM [dbo].[Notes] AS [Extent1] // WHERE CONTAINS([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 //char(4096)',@p__linq__0='(john)' } Console.ReadKey(); } }
ابتدا کلاس FtsInterceptor را به EF معرفی مینماییم. سپس از دو متد الحاقی مذکور استفاده مینماییم. خروجی هر دو متد توسط SQL Server Profiler در زیر هر متد مشاهده مینمایید.
تمامی کدها و مثال مربوطه در آدرس https://effts.codeplex.com قرار گرفته است.
منبع:
Full Text Search in Entity Framework 6