DECLARE @Test TABLE ( RowID INT IDENTITY, FName VARCHAR(20), Salary SMALLINT ); INSERT INTO @Test (FName, Salary) VALUES ('George', 800), ('Sam', 950), ('Diane', 1100), ('Nicholas', 1250), ('Samuel', 1250), ('Patricia', 1300), ('Brian', 3000), ('Thomas', 1600), ('Fran', 2450), ('Debbie', 2850), ('Mark', 2975), ('James', 3000), ('Cynthia', 3000), ('Christopher', 5000); SELECT RowID,FName,Salary, SumByRows = SUM(Salary) OVER (ORDER BY Salary ROWS UNBOUNDED PRECEDING), SumByRange = SUM(Salary) OVER (ORDER BY Salary RANGE UNBOUNDED PRECEDING) FROM @Test ORDER BY RowID;
من از دو فیلد از 2 دیتابیس رو با دو dropdown نمایش دادم به صورت زیر:
public ActionResult Create() { Models.category cat = new Models.category(); List<SelectListItem> list = cat.GetList().Select(p => new SelectListItem { Text = p.category1, Value = p.ID.ToString() }).ToList(); list[0].Selected = true; ViewData["Category1"] = list; Models.subcategory subcat = new Models.subcategory(); List<SelectListItem> list1 = subcat.GetList().Select(p => new SelectListItem { Text = p.subcategory1, Value = p.ID.ToString() }).ToList(); list1[0].Selected = true; ViewData["Category2"] = list1; return View(); } -------------------------razor view----------------------------------------------- <div> @{ List<SelectListItem> lstCategories = (List<SelectListItem>)ViewData["category1"]; } @Html.DropDownList("dbcat", lstCategories, new { id="dbcat" }) </div> <div> @{ List<SelectListItem> lstsubCategories = (List<SelectListItem>)ViewData["Category2"]; } @Html.DropDownList("dbsubcat", lstsubCategories, new { id="dbsubcat"})
public ActionResult SelectCategory(int id) { studentDataContext db= new studentDataContext(); var subcat = db.subcategories.Where(m => m.ID == id).Select(c => new { c.ID, c.subcategory1 }); return Json(subcat, JsonRequestBehavior.AllowGet); }
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script type="text/javascript"> $('#dbcat').change(function () { jQuery.getJSON('@Url.Action("SelectCategory")', { id: $(this).attr('value') }, function (data) { $('#dbsubcat').empty(); jQuery.each(data, function (i) { var option = $('<option></option>') .attr("value", data[i].Id).text(data[i].Title); $("#dbcat").append(option); }); }); }); </script>
قسمت کد جی کوئری من اجرا نمیشه.حتی alert داخل
$('#dbcat').change(function ()
با تشکر
تنظیم مسیریابی ماژولها
در اینجا نیازی به تنظیم base path نیست و این تنظیم تنها یکبار به ازای کل برنامه انجام میشود. همانطور که در قسمت قبل نیز عنوان شد، ماژول مسیریابی Angular و یا همان RouterModule، به همراه سرویسی برای دسترسی به امکانات آن، تنظیمات مسیریابی و یک سری دایرکتیو مانند routerLink، جهت تعامل با آن است. از آنجائیکه سرویس ماژول مسیریابی در فایل src\app\app-routing.module.ts تعریف و تنظیم شدهاست، باید اطمینان حاصل کرد که این سرویس تنها یکبار در طول عمر برنامه وهله سازی میشود و از آنجائیکه هر ماژول تنظیمات مجزای مسیریابی خود را خواهد داشت، دیگر نمیتوان از متد RouterModule.forRoot سراسری استفاده کرد و در اینجا باید از متد forChild این ماژول، جهت تعریف تنظیمات مسیریابیهای ماژولهای مختلف کمک گرفت. متد forChild نیز شبیه به همان آرایهی تنظیمات مسیریابی متد forRoot را دریافت میکند.
یک مثال: در ادامهی مثالی که در قسمت قبل به کمک Angular CLI ایجاد کردیم، ماژول جدید محصولات را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
>ng g m product --routing
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class ProductRoutingModule { }
سپس ProductRoutingModule به قسمت imports ماژول محصولات به صورت خودکار اضافه شدهاست:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ProductRoutingModule } from './product-routing.module'; @NgModule({ imports: [ CommonModule, ProductRoutingModule ], declarations: [] }) export class ProductModule { }
در ادامه کامپوننت جدید لیست محصولات را به این ماژول اضافه میکنیم:
>ng g c product/ProductList
installing component create src\app\product\product-list\product-list.component.css create src\app\product\product-list\product-list.component.html create src\app\product\product-list\product-list.component.spec.ts create src\app\product\product-list\product-list.component.ts update src\app\product\product.module.ts
import { ProductListComponent } from './product-list/product-list.component'; @NgModule({ imports: [ ], declarations: [ProductListComponent] }) export class ProductModule { }
اکنون که این ماژول جدید را به همراه یک کامپوننت نمونه در آن تعریف کردیم، برای افزودن مسیریابی به آن، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آرایهی Routes آنرا تکمیل میکنیم:
import { ProductListComponent } from './product-list/product-list.component'; const routes: Routes = [ { path: 'products', component: ProductListComponent } ];
در ادامه میخواهیم لینکی را به این مسیریابی جدید اضافه کنیم. در قسمت قبل منویی را به برنامه اضافه کردیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink جدیدی را به آن اضافه میکنیم:
<nav class="navbar navbar-default"> <div class="container-fluid"> <a class="navbar-brand">{{title}}</a> <ul class="nav navbar-nav"> <li> <a [routerLink]="['/home']">Home</a> </li> <li> <a [routerLink]="['/products']">Product List</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
در اینجا نیز نحوهی تعریف لینکها مانند قبل است و آرایهی تنظیمات پارامترهای لینک باید به مقدار خاصیت path تعریف شده اشاره کند.
اکنون دستور ng serve -o را صادر کنید تا برنامه در حافظه ساخته شده و در مرورگر نمایش داده شود. در ادامه اگر بر روی لینک لیست محصولات کلیک کنید، صفحهی ذیل را مشاهده خواهید کرد:
به این معنا که برنامه اطلاعی از این مسیریابی جدید نداشته و صفحهی یافت نشدن مسیریابی را که در قسمت قبل تنظیم کردیم، نمایش دادهاست. برای رفع این مشکل باید به فایل src\app\app.module.ts مراجعه کرده و این ماژول جدید را به آن معرفی کنیم:
import { ProductModule } from './product/product.module'; @NgModule({ declarations: [ ], imports: [ BrowserModule, FormsModule, HttpModule, ProductModule, AppRoutingModule ],
نکته 1: علت اینکه ProductModule را پیش از AppRoutingModule تعریف کردیم این است که AppRoutingModule دارای تعریف مسیریابی ** یا catch all است که در قسمت قبل آنرا جهت مدیریت مسیرهای یافت نشده به برنامه افزودیم. اگر ابتدا AppRoutingModule تعریف میشد و سپس ProductModule، هیچگاه فرصت به پردازش مسیریابیهای ماژول محصولات نمیرسید؛ چون مسیر ** پیشتر برنده شده بود.
نکته 2: میتوان در قسمت import متد RouterModule.forRoot را نیز مستقیما قرار داد (بجای AppRoutingModule). اگر این کار صورت گیرد، ابتدا مسیریابیهای موجود در ماژولها پردازش میشوند و در آخر مسیرهای موجود در RouterModule.forRoot صرفنظر از محل قرارگیری آن در این لیست بررسی خواهد شد (حتی اگر در ابتدای لیست قرار گیرد). هرچند جهت مدیریت بهتر برنامه، این متد به AppRoutingModule منتقل شدهاست. بنابراین اکنون «نکتهی 1» برقرار است.
انتخاب استراتژی مناسب نامگذاری مسیرها
هنگام کار کردن با تعدادی ویژگی مرتبط به هم قرار گرفتهی داخل یک ماژول، بهتر است روش نامگذاری مناسبی را برای تنظیمات مسیریابی آن درنظر گرفت تا مسیرهای تعیین شده علاوه بر زیبایی، وضوح بیشتری را نیز پیدا کنند. به علاوه این نامگذاری مناسب، گروه بندی مسیریابیها و lazy loading آنها را نیز سادهتر میکند.
استراتژی ابتدایی که به ذهن میرسد، نامگذاری هر مسیر بر اساس عملکرد آنها است مانند products برای نمایش لیست محصولات، product/:id برای نمایش جزئیات محصولی خاص که در اینجا id پارامتر مسیریابی است و productEdit/:id برای ویرایش جزئیات یک محصول مشخص. همانطور که مشاهده میکنید، هرچند این مسیرها متعلق به یک ماژول هستند، اما مسیرهای تعیین شدهی برای آنها اینگونه به نظر نمیرسد. بنابراین بهتر است تمام ویژگیهای قرار گرفتهی درون یک ماژول را با مسیر ریشهی یکسانی شروع کنیم. به این ترتیب نمایش لیست محصولات همان products باقی خواهد ماند اما برای نمایش جزئیات محصولی خاص از مسیر products/:id استفاده میکنیم (همان اسم جمع ریشهی مسیر؛ بجای اسم مفرد). اینبار مسیر ویرایش جزئیات یک محصول به صورت products/:id/edit تنظیم خواهد شد:
products products/:id products/:id/edit
فعالسازی یک مسیر با کدنویسی
تا اینجا نحوهی فعالسازی یک مسیر را با استفاده از دایرکتیو routerLink بررسی کردیم. اما گاهی از اوقات نیاز است تا بتوان با کدنویسی نیز کاربران را به مسیری خاص هدایت کرد. برای مثال پس از عملیات logout میخواهیم مجددا صفحهی اول سایت نمایش داده شود. برای اینکار از سرویس Router مسیریاب Angular کمک گرفته میشود. ابتدا آنرا در سازندهی یک کامپوننت تزریق کرده و سپس میتوان به قابلیتهای آن مانند استفادهی از متد navigate آن، در کدهای برنامه دسترسی یافت.
باید درنظر داشت که دایرکتیو routerLink نیز در پشت صحنه از همین متد navigate سرویس Router استفاده میکند. بنابراین تمام پارامترهای آن در متد navigate نیز قابل استفاده هستند. برای مثال زمانیکه تعداد پارامترهای routerLink یک مورد است، میتوان آرایهی آنرا به یک رشته خلاصه کرد. یک چنین قابلیتی با متد navigate نیز میسر است.
متد navigate تنها قسمتهایی از URL جاری را تغییر میدهد. اگر نیاز باشد تا کل آدرس تعویض شود، میتوان از متد دیگر سرویس Router به نام navigateByUrl استفاده کرد. این متد تمام URL segments موجود را با مسیر جدیدی جایگزین میکند. به علاوه برخلاف متد navigate، تنها یک رشته را به عنوان پارامتر میپذیرد.
در ادامه مثال جاری میخواهیم پیاده سازی ابتدایی login و logout را به برنامه اضافه کنیم. به همین منظور ابتدا ماژول جدید user را به همراه تنظیمات ابتدایی مسیریابی آن اضافه میکنیم:
>ng g m user --routing
همانند ماژول قبلی، نیاز است UserModule را به قسمت imports فایل src\app\app.module.ts نیز معرفی کنیم:
import { UserModule } from './user/user.module'; @NgModule({ declarations: [ ], imports: [ BrowserModule, FormsModule, HttpModule, ProductModule, UserModule, AppRoutingModule ],
سپس کامپوننت جدید لاگین را به ماژول user برنامه اضافه میکنیم:
>ng g c user/login
در ادامه به فایل src\app\user\user-routing.module.ts مراجعه کرده و مسیریابی جدیدی را به کامپوننت لاگین تعریف میکنیم:
import { LoginComponent } from './login/login.component'; const routes: Routes = [ { path: 'login', component: LoginComponent} ];
مرحلهی بعد، فعالسازی این مسیریابی است، با تعریف لینکی به آن. به همین جهت به فایل 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']">Home</a> </li> <li> <a [routerLink]="['/products']">Product List</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> <li> <a [routerLink]="['/login']">Log In</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
تکمیل کامپوننت login و افزودن لینک logout
در ادامه میخواهیم یک فرم لاگین مقدماتی را پس از کلیک بر روی لینک لاگین نمایش دهیم و هدایت به صفحهی لیست محصولات را پس از لاگین و مخفی کردن لینک لاگین و نمایش لینک خروج را در این حالت پیاده سازی کنیم. برای این منظور ابتدا اینترفیس خالی کاربر را ایجاد میکنیم:
>ng g i user/user
export interface IUser { id: number; userName: string; isAdmin: boolean; }
پس از آن یک سرویس ابتدایی اعتبارسنجی کاربران را نیز اضافه خواهیم کرد:
>ng g s user/auth -m user/user.module
installing service create src\app\user\auth.service.spec.ts create src\app\user\auth.service.ts update src\app\user\user.module.ts
پس از ایجاد قالب ابتدایی فایل auth.service.ts آنرا به نحو ذیل تکمیل کنید:
import { IUser } from './user'; import { Injectable } from '@angular/core'; @Injectable() export class AuthService { currentUser: IUser; constructor() { } isLoggedIn(): boolean { return !this.currentUser; } login(userName: string, password: string): boolean { if (!userName || !password) { return false; } if (userName === 'admin') { this.currentUser = { id: 1, userName: userName, isAdmin: true }; return true; } this.currentUser = { id: 2, userName: userName, isAdmin: false }; return true; } logout(): void { this.currentUser = null; } }
سپس کامپوننت لاگین واقع در فایل src\app\user\login\login.component.ts را به نحو ذیل تکمیل کنید:
import { Router } from '@angular/router'; import { AuthService } from './../auth.service'; import { Component, OnInit } from '@angular/core'; import { NgForm } from '@angular/forms'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { errorMessage: string; pageTitle = 'Log In'; constructor(private authService: AuthService, private router: Router) { } ngOnInit() { } login(loginForm: NgForm) { if (loginForm && loginForm.valid) { let userName = loginForm.form.value.userName; let password = loginForm.form.value.password; if (this.authService.login(userName, password)) { this.router.navigate(['/products']); } } else { this.errorMessage = 'Please enter a user name and password.'; }; } }
از AuthService برای اعتبارسنجی کاربر و لاگین او به سیستم استفاده میکنیم و از سرویس مسیریاب Angular جهت فراخوانی متد navigate آن به صفحهی مشاهدهی محصولات، پس از لاگین کاربر استفاده شدهاست.
اکنون میخواهیم قالب این کامپوننت را نیز تکمیل کنیم. پیش از آن به فایل src\app\user\user.module.ts مراجعه کرده و در قسمت imports آن FormsModule را نیز اضافه کنید:
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ CommonModule, FormsModule, UserRoutingModule ],
سپس فایل src\app\user\login\login.component.html را به نحو ذیل تغییر دهید:
<div class="panel panel-default"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <form class="form-horizontal" novalidate (ngSubmit)="login(loginForm)" #loginForm="ngForm" autocomplete="off"> <fieldset> <div class="form-group" [ngClass]="{'has-error': (userNameVar.touched || userNameVar.dirty) && !userNameVar.valid }"> <label class="col-md-2 control-label" for="userNameId">User Name</label> <div class="col-md-8"> <input class="form-control" id="userNameId" type="text" placeholder="User Name (required)" required (ngModel)="userName" name="userName" #userNameVar="ngModel" /> <span class="help-block" *ngIf="(userNameVar.touched || userNameVar.dirty) && userNameVar.errors"> <span *ngIf="userNameVar.errors.required"> User name is required. </span> </span> </div> </div> <div class="form-group" [ngClass]="{'has-error': (passwordVar.touched || passwordVar.dirty) && !passwordVar.valid }"> <label class="col-md-2 control-label" for="passwordId">Password</label> <div class="col-md-8"> <input class="form-control" id="passwordId" type="password" placeholder="Password (required)" required (ngModel)="password" name="password" #passwordVar="ngModel" /> <span class="help-block" *ngIf="(passwordVar.touched || passwordVar.dirty) && passwordVar.errors"> <span *ngIf="passwordVar.errors.required"> Password is required. </span> </span> </div> </div> <div class="form-group"> <div class="col-md-4 col-md-offset-2"> <span> <button class="btn btn-primary" type="submit" style="width:80px;margin-right:10px" [disabled]="!loginForm.valid"> Log In </button> </span> <span> <a class="btn btn-default" [routerLink]="['/welcome']"> Cancel </a> </span> </div> </div> </fieldset> </form> <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div> </div> </div>
اکنون میخواهیم پس از ورود او، نام او را نمایش داده و همچنین دکمهی logout را بجای login در منوی بالای سایت نمایش دهیم. به همین جهت در قالب کامپوننت App که منوی برنامه در آن تنظیم شدهاست، نیاز است بتوانیم به سرویس Auth سفارشی دسترسی یافته و خروجی متد isLoggedIn آنرا بررسی کنیم. به همین منظور به فایل src\app\app.component.ts مراجعه کرده و آنرا به صورت ذیل تکمیل کنید:
import { Router } from '@angular/router'; import { AuthService } from './user/auth.service'; import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { pageTitle: string = 'Routing Lab'; constructor(private authService: AuthService, private router: Router) { } logOut(): void { this.authService.logout(); this.router.navigateByUrl('/welcome'); } }
پس از این تغییرات، اکنون میتوان قالب 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']">Home</a> </li> <li> <a [routerLink]="['/products']">Product List</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> <li *ngIf="authService.isLoggedIn()"> <a>Welcome {{ authService.currentUser.userName }}</a> </li> <li *ngIf="!authService.isLoggedIn()"> <a [routerLink]="['/login']">Log In</a> </li> <li *ngIf="authService.isLoggedIn()"> <a (click)="logOut()">Log Out</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
اکنون اگر برنامه را توسط دستور ng serve -o اجرا کنید، صفحهی لاگین و منوی بالای صفحه چنین شکلی را خواهد داشت:
پس از لاگین، لینک لاگین از منو حذف شده و سپس نام کاربری و لینک به logout نمایان میشوند.
اینبار اگر بر روی logout کلیک کنید، نام کاربری و لینک logout از صفحه حذف و مجددا لینک لاگین نمایش داده میشود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
معرفی ELMAH
عموما کاربران نمیتوانند گزارش خطای خوبی را ارائه بدهند و البته انتظاری هم از آنان نیست. تنها گزارشی که از یک کاربر دریافت میکنید این است: "برنامه کار نمیکنه!" و همین!
روشهای متعددی برای لاگ کردن خطاهای یک برنامه ASP.Net موجود است؛ چه خودتان آنها را توسعه دهید و یا از ASP.NET health monitoring استفاده کنید.
روش دیگری که این روزها در وبلاگهای متعددی در مورد آن مطلب منتشر میشود، استفاده از ELMAH است. (البته ELMAH به تازگی منتشر نشده ولی تا کیفیت محصولی به عموم ثابت شود مدتی زمان میبرد)
ELMAH یک ماژول رایگان و سورس باز لاگ کردن خطاهای مدیریت نشده برنامههای ASP.Net است. برای استفاده از این ماژول نیازی نیست تا تغییری در برنامه خود ایجاد کنید یا حتی آنرا کامپایل مجدد نمائید. یک فایل dll دارد به همراه کمی تغییر در web.config برنامه جهت معرفی آن و این تمام کاری است که برای برپایی آن لازم است صورت گیرد. این ماژول تمامی خطاهای مدیریت نشدهی برنامه شما را لاگ کرده (در حافظه سرور، در یک فایل xml ، در یک دیتابیس اس کیوال سرور یا اوراکل ، در یک دیتابیس اکسس و یا در یک دیتابیس اس کیوال لایت) و برای مرور آنها یک صفحهی وب سفارشی یا فیدی مخصوص را نیز در اختیار شما قرار میدهد. همچنین این قابلیت را هم دارد که به محض بروز خطایی یک ایمیل را نیز به شما ارسال نماید.
با توجه به اینکه این ماژول در Google code قرار گرفته احتمالا دسترسی به آن مشکل خواهد بود. سورس و فایلهای کامپایل شده آنرا از آدرسهای زیر نیز میتوان دریافت نمود:
نحوه استفاده از ELMAH :
برای استفاده از ELMAH دو کار را باید انجام دهید:
الف) کپی کردن فایل Elmah.dll در پوشه bin برنامه
ب) تنظیم وب کانفیگ برنامه
بهترین مرجع برای آشنایی با نحوه بکار گیری این ماژول، مراجعه به فایل web.config موجود در پوشه samples آن است. بر اساس این فایل نمونه:
- ابتدا باید configSections آن را به وب کانفیگ خود اضافه کنید.
- سپس تگ elmah باید اضافه شود. در این تگ موارد زیر مشخص میشوند:
الف) آیا خطاها توسط آدرس elmah.axd توسط کاربران راه دور قابل مشاهده شود یا خیر.
ب) خطاها کجا ذخیره شوند؟ موارد زیر پشتیبانی میشوند:
دیتابیسهای اس کیوال سرور ، اوراکل ، حافظه سرور، فایلهای xml ، دیتابیس SQLite ، دیتابیس اکسس و یا دیتابیسی از نوع VistaDB
ج) آیا خطاها ایمیل هم بشوند؟ اگر بلی، تگ مربوطه را تنظیم کنید.
د) آیا خطاها به اکانت twitter شما نیز ارسال شوند؟
- در ادامه تگ مربوط به معرفی این httpModules باید تنظیم شود.
- سپس httpHandlers ایی به نام elmah.axd که جهت مرور خطاها میتوان از آن استفاده نمود معرفی میگردد.
- از IIS7 استفاده میکنید؟ قسمت system.webServer را نیز باید اضافه نمائید.
- و در آخر نحوهی دسترسی به elmah.axd مشخص میشود. اگر اجازه دسترسی از راه دور را داده باشید، به این طریق میشود دسترسی را فقط به کاربران مجاز و تعیین اعتبار شده، اعطاء کرد و یا به نقشی مشخص مانند ادمین و غیره.
برای نمونه، اگر بخواهید از دیتابیس SQLite جهت ذخیره سازی خطاهای حاصل شده استفاده نمائید و نیز از ارسال ایمیل صرفنظر کنید، وب کانفیگ برنامه شما باید به شکل زیر تغییر یابد:
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>
<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.SQLiteErrorLog, Elmah" connectionStringName="cn1" />
</elmah>
<appSettings/>
<connectionStrings>
<add name="cn1" connectionString="data source=~/ErrorsLog/Errors.db" />
</connectionStrings>
<system.web>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<compilation debug="true"/>
<authentication mode="Windows" />
</system.web>
</configuration>
سادهترین تنظیم این ماژول استفاده از حالت xml است که به ازای هر خطا یک فایل xml را تولید کرده و نیاز به اسمبلی دیگری بجز ماژول مربوطه نخواهد داشت.
تذکر:
از لحاظ امنیتی مثال فوق توصیه نمیشود زیرا allowRemoteAccess آن 1 است و قسمت authorization ذکر نشده است. این مثال فقط جهت راه اندازی و آزمایش اولیه ارائه گردیده است. (همچنین بهتر است این نام پیش فرض را به نامی دیگر مثلا myloggermdl.axd تغییر داده و در قسمت httpHandlers تنظیم نمائید. سپس این نام را به تگ location نیز اضافه کنید)
اکنون برای مشاهده خروجی این ماژول به انتهای آدرس سایت خود، elmah.axd را اضافه کرده و سپس enter کنید:
همانطور که در تصویر مشخص است، تمامی خطاهای لاگ شده گزارش داده شدهاند. همچنین دو نوع فید به همراه امکان دریافت خطاها به صورت CSV نیز موجود است. با کلیک بر روی لینک details ، صفحهی بسیار ارزندهای ارائه میشود که تقریبا نحوهی وقوع ماجرا را بازسازی میکند.
نکتهی مهمی که در صفحهی جزئیات ارائه میشود (علاوه بر stack trace و مشخصات کاربر)، مقادیر تمامی فیلدهای یک صفحه هنگام بروز خطا است (قسمت Raw/Source data in XML or in JSON در این صفحه) :
به این صورت دیگر نیازی نیست از کاربر بپرسید چه چیزی را وارد کرده بودید که خطا حاصل شد. دقیقا مقادیر فیلدهای همان صفحهی زمان بروز خطا نیز برای شما لاگ میگردد.
نکته:
ماژول SQLite ایی که به همراه مجموعه ELMAH ارائه میشود 32 بیتی کامپایل شده (64 بیتی آن نیز موجود است که باید از آن در صورت لزوم استفاده شود). بنابراین برای اینکه در یک سرور 64 بیتی به مشکل برنخورید و خطای BadImageFormat را دریافت نکنید نیاز است تا به این نکته دقت داشت.
برای مطالعه بیشتر:
Error Logging Modules And Handlers
Sending ELMAH Errors Via GMail
Exception-Driven Development
Using HTTP Modules and Handlers to Create Pluggable ASP.NET Components
برای این کار روش استاندارد و مستقیمی وجود ندارد؛ اما با استفاده از بعضی از روشها مانند ارسال عکس مخفی، برخی تگهایی که یک آدرس را فراخوانی میکنند (مانند bgsound)، کلیک روی لینک و یا ترکیبی از چند روش میتوان تا حدود زیادی به اهداف فوق رسید.
روشی که بررسی خواهیم کرد، مبتنی بر ارسال عکس بوده و برای شروع، به یک عکس بسیار کوچک (1X1) با حجم کم و غیرقابل دید (Transparent) نیاز داریم که میتوانید یکی از آنها را از این آدرس انتخاب کنید. در این روش به همراه ایمیل، این عکس را ارسال خواهیم کرد (در تگ img). عکس باید بگونهای باشد که ظاهر ایمیل را تحت تأثیر قرار ندهد و آدرس عکس باید طوری تنظیم شود که به برنامه ما یک درخواست را ارسال کند و برنامه با توجه به درخواست ارسال شده، عملیات لازم را انجام دهد.
مثالی از عکسی که باید ارسال شود:
<img src='http://www.example.com/1x1.gif' style='width:1px;height:1px;'>
private bool SendEmail(string mail_to, string mail_subject, string mail_body) { bool result = false; try { SmtpClient client = new SmtpClient("smtp.gmail.com"); client.UseDefaultCredentials = false; client.EnableSsl = true; client.Port = 587; client.Credentials = new NetworkCredential("from@mail.com", "your_gmail_password");
MailMessage mailMessage = new MailMessage(); mailMessage.From = new MailAddress("from@mail.com"); mailMessage.To.Add(mail_to); mailMessage.Body = mail_body; mailMessage.Subject = mail_subject; client.Send(mailMessage); result = true; } catch (Exception ex) { result = false; } return result; }
public IActionResult SendEmailWithTransparentImage() { var email_body = "your_email_body"; var imageUrl = Url.Action("ImageRequestFromEmail", "Home", new {user_id=12345}, protocol: Url.ActionContext.HttpContext.Request.Scheme); var imageTag = $"<img src='{imageUrl}' style='width:1px;height:1px;'>"; email_body += imageTag; var result = SendEmail("to@email.com", "test image", email_body); return View(result); }
[Route("1x1.gif")] public IActionResult ImageResponse() { //درخواست عکس ارسال شده و اینجا باید عملیات دلخواه را انجام دهیم var emailOpenDate = DateTime.Now; //سپس عکس را در جواب درخواست ارسال میکنیم byte[] imagegBytes = Convert.FromBase64String("R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAI="); return File(imagegBytes, "image/gif"); }
<img src='http://www.example.com/1x1.gif?user_id=12345' style='width:1px;height:1px;'>
var imageUrl = Url.Action("ImageResponse, "Home", new {user_id=12345},protocol: Url.ActionContext.HttpContext.Request.Scheme);
public IActionResult ImageResponse(int user_id)
Line 118: { Line 119: user = new ApplicationUser { UserName = name, Email = name }; Line 120: var result = this.Create(user, password); Line 121: result = this.SetLockoutEnabled(user.Id, false); Line 122: }
Line 119: user = new ApplicationUser { UserName = name, Email = name }; Line 120: var result = this.Create(user, password); Line 121: result = this.SetLockoutEnabled(user.Id, false); Line 122: } Line 123:
در فریمورک NET. ابزارهای مختلفی برای کار با دادههای XML در نظر گرفته شدهاست که بعد از نسخه 3.5 آن، انتخاب اول LINQ to XML می باشد. در این مطلب قصد داریم API ای را برای خواندن اطلاعات فایلهای XML با استفاده از LINQ to XML و انقیاد پویا در سیشارپ (Dynamic Binding) تهیه کنیم.
راه حل اول: استفاده از ExpandoObject
public static class ExpandoXml { public static dynamic AsExpando(this XDocument document) { return CreateExpando(document.Root); } private static dynamic CreateExpando(XElement element) { var result = new ExpandoObject() as IDictionary<string, object>; if (element.Elements().Any(e => e.HasElements)) { var list = new List<ExpandoObject>(); result.Add(element.Name.ToString(), list); foreach (var childElement in element.Elements()) { list.Add(CreateExpando(childElement)); } } else { foreach (var leafElement in element.Elements()) { result.Add(leafElement.Name.ToString(), leafElement.Value); } } return result; } }
در تکه کد بالا از طریق متد CreateExpando به صورت بازگشتی ابتدا بررسی میشود که آیا عنصر جاری دارای عناصری میباشد و همچنین آیا آنها دارای فرزند میباشند یا خیر؛ در صورت برقراری شرط، نتیجهی اجرای متد CreateExpando بر روی تک تک عناصر فرزند را درون لیستی از ExpandoObject قرار داده و سپس آن لیست نیز به عنوان Value عنصر جاری در نظر گرفته میشود. در صورت عدم برقراری شرط مذکور، مقادیر مربوط به عناصر فرزند را در قالب یک ExpandoObject به عنوان خروجی بازگشت خواهد داد.
راه حل دوم: استفاده از DynamicObject
public class DynamicXml : DynamicObject, IEnumerable { private readonly dynamic _xml; public DynamicXml(string fileName) { _xml = XDocument.Load(fileName); } public DynamicXml(dynamic xml) { _xml = xml; } public IEnumerator GetEnumerator() { foreach (var item in _xml.Elements()) { yield return new DynamicXml(item); } } public override bool TryGetMember(GetMemberBinder binder, out object result) { var xml = _xml.Element(binder.Name); if (xml != null) { result = new DynamicXml(xml); return true; } var attribute = _xml.Attribute(binder.Name); if (attribute != null) { result = new DynamicXml(attribute); return true; } result = null; return false; } public static implicit operator string(DynamicXml xml) { return xml._xml.Value; } }
کلاس DynamicXml از طریق سازنده اول، نام فایل را دریافت کرده و از طریق LINQ to XML با استفاده از متد Load کلاس XDocument، فایل مورد نظر بارگذاری شده و درون فیلدی به نام xml_ از نوع dynamic نگه داشته میشود. کار بعدی، بازنویسی متد TryGetMember میباشد. در بدنه بازنویسی شده این متد ابتدا بررسی میشود که آیا با نام خصوصت درخواست شده عنصری در داده XML وجود دارد یا خیر؛ در صورت موجود بودن، پارامتر result با یک وهله جدید از DynamicXml مقدار دهی میشود که عنصر یافت شده از طریق سازنده دوم، به عنوان داده xml برای مقدار دهی فیلد xml_ به عنوان آرگومان ارسال میشود. در صورت عدم وجود عنصر مذکور، بدنبال خصوصیتی با آن نام بوده و در صورت یافت شدن، باز به عنوان یک وهله DynamicXml برای مقدار دهی result استفاده میشود.
در ادامه برای نسبت دادن یک وهله از DynamicXml به یک متغیر string و دستیابی به مقدار یک عنصر که از طریق خصوصیت، درخواست میشود نیاز است تا اپراتور ضمنی string را نیز برای کلاس بالا نظر بگیریم. همچنین برای ایجاد امکان پیمایش برروی عناصر فرزند از طریق foreach، لازم است واسط IEnumerable را نیز پیاده سازی کرده باشیم.
طریقه استفاده
class Program { static void Main(string[] args) { var doc1 = XDocument.Load("Employees.xml"); foreach (var element in doc1.Element("Employees").Elements("Employee")) { Console.WriteLine(element.Element("FirstName").Value); } dynamic doc2 = XDocument.Load("Employees.xml").AsExpando(); foreach (var employee in doc2.Employees) { Console.WriteLine(employee.FirstName); } dynamic doc3 = new DynamicXml("Employees.xml"); foreach (var employee in doc3.Employees) { Console.WriteLine(employee.FirstName); Console.WriteLine(employee.Id); } } }
<?xml version="1.0" encoding="utf-8" ?> <Employees> <Employee Id="1"> <FirstName> Employee1 </FirstName> </Employee> <Employee Id="2"> <FirstName> Employee2 </FirstName> </Employee> <Employee Id="3"> <FirstName> Employee3 </FirstName> </Employee> <Employee Id="4"> <FirstName> Employee4 </FirstName> </Employee> </Employees>