مطالب دوره‌ها
استفاده از IL Code Weaving برای تولید ویژگی‌های تکراری مورد نیاز در WCF
با استفاده از IL Code Weaving علاوه بر مدیریت اعمال تکراری پراکنده در سراسر برنامه مانند ثبت وقایع، مدیریت استثناءها، کش کردن داده‌ها و غیره، می‌توان قابلیتی را به کدهای موجود نیز افزود. برای مثال یک برنامه معمول WCF را درنظر بگیرید.
using System.Runtime.Serialization;

namespace AOP03.DataContracts
{
    [DataContract]
    public class User
    {
        [DataMember]
        public int Id { set; get; }

        [DataMember]
        public string Name { set; get; }
    }
}
نیاز است کلاس‌ها و خواص آن توسط ویژگی‌های DataContract و DataMember مزین شوند. در این بین نیز اگر یکی فراموش گردد، کار دیباگ برنامه مشکل خواهد شد و در کل حجم بالایی از کدهای تکراری در اینجا باید در مورد تمام کلاس‌های مورد نیاز انجام شود. در ادامه قصد داریم تولید این ویژگی‌ها را توسط PostSharp انجام دهیم. به عبارتی یک پوشه خاص به نام DataContracts را ایجاد کرده و کلاس‌های خود را به نحوی متداول و بدون اعمال ویژگی خاصی تعریف کنیم. در ادامه پس از کامپایل آن، به صورت خودکار با ویرایش کدهای IL توسط PostSharp، ویژگی‌های لازم را به اسمبلی نهایی اضافه نمائیم.


تهیه DataContractAspect جهت اعمال خودکار ویژگی‌های DataContract و DataMember

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;

namespace AOP03
{
    [Serializable]
    //این ویژگی تنها نیاز است به کلاس‌ها اعمال شود
    [MulticastAttributeUsage(MulticastTargets.Class)]
    public class DataContractAspect : TypeLevelAspect, IAspectProvider
    {
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            var targetType = (Type)targetElement; //همان نوعی است که ویژگی جاری به آن اعمال خواهد شد

            //این سطر معادل است با درخواست تولید ویژگی دیتاکانترکت
            var introduceDataContractAspect = new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes)));

            //این سطر معادل است با درخواست تولید ویژگی دیتاممبر
            var introduceDataMemberAspect = new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes)));

            //در اینجا کار اعمال ویژگی دیتاکانترکت به کلاسی که به عنوان پارامتر متد جاری
            //دریافت شده انجام خواهد شد
            yield return new AspectInstance(targetType, introduceDataContractAspect);

            //مرحله بعد کار اعمال ویژگی دیتاممبر به خواص کلاس است
            foreach (var property in targetType.GetProperties(BindingFlags.Public |
                                                          BindingFlags.DeclaredOnly |
                                                          BindingFlags.Instance))
            {
                if (property.CanWrite)
                    yield return new AspectInstance(property, introduceDataMemberAspect);
            }
        }
    }
}
توضیحات مرتبط با قسمت‌های مختلف این Aspect سفارشی، به صورت کامنت در کدهای فوق ارائه شده‌اند.
برای اعمال آن به سراسر برنامه تنها کافی است به فایل AssemblyInfo.cs پروژه مراجعه و سپس سطر زیر را به آن اضافه کنیم:
 [assembly: DataContractAspect(AttributeTargetTypes = "AOP03.DataContracts.*")]
به این ترتیب در زمان کامپایل پروژه، Aspect تعریف شده به تمام کلاس‌های موجود در فضای نام AOP03.DataContracts اعمال خواهند شد.

در این حالت اگر کلیه ویژگی‌های کلاس User فوق را حذف و برنامه را کامپایل کنیم، با مراجعه به برنامه ILSpy می‌توان صحت اعمال ویژگی‌ها را به کمک PostSharp بررسی کرد:
 

مطالب دوره‌ها
جلوگیری از deadlock در برنامه‌های async
توضیح مطلب جاری نیاز به یک مثال دارد. به همین جهت یک برنامه‌ی WinForms یا WPF را آغاز کنید (تفاوتی نمی‌کند). سپس یک دکمه و یک برچسب را در صفحه قرار دهید. در ادامه کدهای فرم را به نحو ذیل تغییر دهید.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;

namespace Async13
{
    public static class JsonExt
    {
        public static async Task<JObject> GetJsonAsync(this Uri uri)
        {
            using (var client = new HttpClient())
            {
                var jsonString = await client.GetStringAsync(uri);
                return JObject.Parse(jsonString);
            }
        }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnGo_Click(object sender, EventArgs e)
        {
            var url =
                "http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo";
            txtResult.Text = new Uri(url).GetJsonAsync().Result.ToString();
        }
    }
}
این کدها برای کامپایل نیاز به نصب بسته‌ی
 PM> Install-Package Newtonsoft.Json
و همچنین افزودن ارجاعی به اسمبلی استاندارد System.Net.Http نیز دارند.
در اینجا قصد داریم اطلاعات JSON دریافتی را در یک TextBox نمایش دهیم. کاری که انجام شده، فراخوانی متد async ایی است به نام GetJsonAsync و سپس استفاده از خاصیت Result این Task برای صبر کردن تا پایان عملیات.
اگر برنامه را اجرا کنید و بر روی دکمه‌ی دریافت اطلاعات کلیک نمائید، برنامه قفل خواهد کرد. چرا؟
البته تفاوتی هم نمی‌کند که این یک برنامه‌ی دسکتاپ است یا یک برنامه‌ی وب. در هر دو حالت یک deadlock کامل را مشاهده خواهید کرد.


علت بروز deadlock در کدهای async چیست؟

همواره نتیجه‌ی await، در context فراخوان آن بازگشت داده می‌شود. اگر برنامه‌ی دسکتاپ است، این context همان ترد اصلی UI برنامه می‌باشد و اگر برنامه‌ی وب است، این context، زمینه‌ی درخواست در حال پردازش می‌باشد.
خاصیت Result و یا استفاده از متد Wait یک Task، به صورت همزمان عمل می‌کنند و نه غیرهمزمان. متد GetJsonAsync یک Task ناتمام را که فراخوان آن باید جهت پایان‌اش صبر کند، بازگشت می‌دهد. سپس در همینجا کد فراخوان، تردجاری را توسط فراخوانی خاصیت Result قفل می‌کند. متد GetJsonAsync منتظر خواهد ایستاد تا این ترد آزاد شده و بتواند به کارش که بازگردان نتیجه‌ی عملیات به context جاری است، ادامه دهد.
به عبارتی، کدهای async منتظر پایان کار Result هستند تا نتیجه را بازگردانند. در همین لحظه کدهای همزمان برنامه نیز منتظر کدهای async هستند تا خاتمه یابند. نتیجه‌ی کار یک deadlock است.


روش‌های جلوگیری از deadlock در کدهای async؟

الف) در مورد متد ConfigureAwait در قسمت‌های قبل بحث شد و به عنوان یک best practice مطرح است:
    public static class JsonExt
    {
        public static async Task<JObject> GetJsonAsync(this Uri uri)
        {
            using (var client = new HttpClient())
            {
                var jsonString = await client.GetStringAsync(uri).ConfigureAwait(continueOnCapturedContext: false);
                return JObject.Parse(jsonString);
            }
        }
    }
با استفاده از ConfigureAwait false سبب خواهیم شد تا نتیجه‌ی عملیات به context جاری بازگشت داده نشود و نتیجه بر روی thread pool thread ادامه یابد. با اعمال این تغییر، کدهای متد btnGo_Click بدون مشکل اجرا خواهند شد.

ب) راه حل دوم، عدم استفاده از خواص و متدهای همزمان با متدهای غیر همزمان است:
        private async void btnGo_Click(object sender, EventArgs e)
        {
            var url =
                "http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo";
            var data = await new Uri(url).GetJsonAsync();
            txtResult.Text = data.ToString();
        }
ابتدا امضای متد رویدادگردان را اندکی تغییر داده و واژه‌ی کلیدی async را به آن اضافه می‌کنیم. سپس از await برای صبر کردن تا پایان عملیات متد GetJsonAsync استفاده خواهیم کرد. صبر کردنی که در اینجا انجام شده، یک asynchronous waits است؛ برخلاف روش همزمان استفاده از خاصیت Result یا متد Wait.


خلاصه‌ی بحث
Await را با متدهای همزمان Wait یا خاصیت Result بلاک نکنید. در غیراینصورت در ترد اجرا کننده‌ی دستورات، یک deadlock رخ‌خواهد داد؛ زیرا نتیجه‌ی await باید به context جاری بازگشت داده شود اما این context توسط خواص یا متدهای همزمان فراخوانی شده بعدی، قفل شده‌است.
مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت پنجم - محافظت از مسیرها
در قسمت سوم، کار ورود به سیستم و امکان مشاهده‌ی صفحه‌ی محافظت شده‌ی پس از لاگین را پیاده سازی کردیم. در این قسمت می‌خواهیم امکان دسترسی به مسیر http://localhost:4200/protectedPage را کنترل کنیم. تا اینجا اگر کاربر بدون لاگین کردن نیز این مسیر را درخواست کند، می‌تواند حداقل ساختار ابتدایی آن‌را مشاهده کند که باید مدیریت شود و این مدیریت دسترسی می‌تواند بر اساس وضعیت لاگین کاربر و همچنین نقش‌های او در سیستم باشد:


طراحی بخش‌هایی از این قسمت، از پروژه‌ی «کنترل دسترسی‌ها در Angular با استفاده از Ng2Permission» ایده گرفته شده‌اند.


استخراج اطلاعات کاربر وارد شده‌ی به سیستم از توکن دسترسی او

یکی از روش‌های دسترسی به اطلاعات کاربر در سمت کلاینت، مانند نقش‌های او، تدارک متدی در سمت سرور و بازگشت Claims او به سمت کلاینت است:
public IActionResult Get()
{ 
    var user = this.User.Identity as ClaimsIdentity; 
    var config = new 
    { 
        userName = user.Name, 
        roles = user.Claims.Where(x => x.Type == ClaimTypes.Role).Select(x => x.Value).ToList() 
    }; 
    return Ok(config); 
}
 اما با توجه به اینکه در زمان لاگین، نقش‌های او (و سایر Claims دلخواه) نیز به توکن دسترسی نهایی اضافه می‌شوند، می‌توان این کوئری گرفتن مدام از سرور را تبدیل به روش بسیار سریعتر استخراج آن‌ها از توکنی که هم اکنون در کش مرورگر ذخیره شده‌است، کرد.
همچنین باید دقت داشت چون این توکن دارای امضای دیجیتال است، کوچکترین تغییری در آن در سمت کاربر، سبب برگشت خوردن خودکار درخواست ارسالی به سمت سرور می‌شود (یکی از مراحل اعتبارسنجی کاربر در سمت سرور، اعتبارسنجی توکن دریافتی (قسمت cfg.TokenValidationParameters) و همچنین بررسی خودکار امضای دیجیتال آن است). بنابراین نگرانی از این بابت وجود ندارد.
اگر اطلاعات کاربر در سمت سرور تغییر کنند، با اولین درخواست ارسالی به سمت سرور، رخ‌داد OnTokenValidated وارد عمل شده و درخواست ارسالی را برگشت می‌زند (در مورد پیاده سازی سمت سرور این مورد، در مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» بیشتر بحث شده‌است). در این حالت کاربر مجبور به لاگین مجدد خواهد شد که این مورد سبب به روز رسانی خودکار اطلاعات توکن‌های ذخیره شده‌ی او در مرورگر نیز می‌شود.

اگر از قسمت دوم این سری به‌خاطر داشته باشید، توکن decode شده‌ی برنامه، چنین شکلی را دارد:
{
  "jti": "d1272eb5-1061-45bd-9209-3ccbc6ddcf0a",
  "iss": "http://localhost/",
  "iat": 1513070340,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid",
  "DisplayName": "وحید",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber": "709b64868a1d4d108ee58369f5c3c1f3",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
    "Admin",
    "User"
  ],
  "nbf": 1513070340,
  "exp": 1513070460,
  "aud": "Any"
}
به همین جهت متدی را برای تبدیل این اطلاعات به شیء کاربر، ایجاد خواهیم کرد و در سراسر برنامه از این اطلاعات آماده، برای تعیین دسترسی او به قسمت‌های مختلف برنامه‌ی سمت کلاینت، استفاده می‌کنیم.
برای این منظور اینترفیس src\app\core\models\auth-user.ts را به صورت ذیل ایجاد می‌کنیم:
export interface AuthUser {
  userId: string;
  userName: string;
  displayName: string;
  roles: string[];
}
پس از آن متد getAuthUser را جهت استخراج خواص ویژه‌ی توکن دسترسی decode شده‌ی فوق، به صورت ذیل به سرویس Auth اضافه می‌کنیم:
  getAuthUser(): AuthUser {
    if (!this.isLoggedIn()) {
      return null;
    }

    const decodedToken = this.getDecodedAccessToken();
    let roles = decodedToken["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"];
    if (roles) {
      roles = roles.map(role => role.toLowerCase());
    }
    return Object.freeze({
      userId: decodedToken["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"],
      userName: decodedToken["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],
      displayName: decodedToken["DisplayName"],
      roles: roles
    });
  }
کار با این متد بسیار سریع است و نیازی به رفت و برگشتی به سمت سرور ندارد؛ چون تمام اطلاعات استخراجی توسط آن هم اکنون در کش سریع مرورگر کلاینت موجود هستند. استفاده‌ی از متد Object.freeze هم سبب read-only شدن این خروجی می‌شود.
همچنین در اینجا تمام نقش‌های دریافتی، تبدیل به LowerCase شده‌اند. با اینکار مقایسه‌ی بعدی آن‌ها با نقش‌های درخواستی، حساس به بزرگی و کوچکی حروف نخواهد بود.


تعریف نقش‌های دسترسی به مسیرهای مختلف سمت کلاینت

مرسوم است اطلاعات اضافی مرتبط با هر مسیر را به خاصیت data آن route انتساب می‌دهند. به همین جهت به فایل dashboard-routing.module.ts مراجعه کرده و نقش‌های مجاز به دسترسی به مسیر protectedPage را به خاصیت data آن به صورت ذیل اضافه می‌کنیم:
import { ProtectedPageComponent } from "./protected-page/protected-page.component";
import { AuthGuardPermission } from "../core/models/auth-guard-permission";

const routes: Routes = [
  {
    path: "protectedPage",
    component: ProtectedPageComponent,
    data: {
      permission: {
        permittedRoles: ["Admin"],
        deniedRoles: null
      } as AuthGuardPermission
    }
  }
];
که در اینجا ساختار اینترفیس AuthGuardPermission، در فایل جدید app\core\models\auth-guard-permission.ts به صورت ذیل تعریف شده‌است:
export interface AuthGuardPermission {
  permittedRoles?: string[];
  deniedRoles?: string[];
}
به این ترتیب هر قسمتی از برنامه که نیاز به اطلاعات سطوح دسترسی مسیری را داشت، ابتدا به دنبال route.data["permission"] خواهد گشت (کلیدی به نام permission در خاصیت data یک مسیر) و سپس اطلاعات آن‌را بر اساس ساختار اینترفیس AuthGuardPermission، تحلیل می‌کند.
در اینجا تنها باید یکی از خواص permittedRoles  (نقش‌های مجاز به دسترسی/صدور دسترسی فقط برای این نقش‌های مشخص، منهای مابقی) و یا deniedRoles (نقش‌های غیرمجاز به دسترسی/دسترسی همه‌ی نقش‌های ممکن، منهای این نقش‌های تعیین شده)، مقدار دهی شوند.


افزودن کامپوننت «دسترسی ندارید» به ماژول Authentication

در ادامه می‌خواهیم اگر کاربری به مسیری دسترسی نداشت، به صورت خودکار به صفحه‌ی «دسترسی ندارید» هدایت شود. به همین جهت این کامپوننت را به صورت ذیل به ماژول authentication اضافه می‌کنیم:
>ng g c Authentication/AccessDenied
با این خروجی که سبب درج خودکار آن در قسمت declaration فایل authentication.module نیز می‌شود:
 AccessDenied
  create src/app/Authentication/access-denied/access-denied.component.html (32 bytes)
  create src/app/Authentication/access-denied/access-denied.component.ts (296 bytes)
  create src/app/Authentication/access-denied/access-denied.component.css (0 bytes)
  update src/app/Authentication/authentication.module.ts (550 bytes)
سپس به فایل authentication-routing.module.ts مراجعه کرده و مسیریابی آن‌را نیز اضافه می‌کنیم:
import { LoginComponent } from "./login/login.component";
import { AccessDeniedComponent } from "./access-denied/access-denied.component";

const routes: Routes = [
  { path: "login", component: LoginComponent },
  { path: "accessDenied", component: AccessDeniedComponent }
];
قالب access-denied.component.html را نیز به صورت ذیل تکمیل می‌کنیم:
<h1 class="text-danger">
  <span class="glyphicon glyphicon-ban-circle"></span> Access Denied
</h1>
<p>Sorry! You don't have access to this page.</p>
<button class="btn btn-default" (click)="goBack()">
  <span class="glyphicon glyphicon-arrow-left"></span> Back
</button>

<button *ngIf="!isAuthenticated" class="btn btn-success" [routerLink]="['/login']"
  queryParamsHandling="merge">
  Login
</button>
که دکمه‌ی Back آن به کمک سرویس Location، صورت ذیل پیاده سازی شده‌است:
import { Component, OnInit } from "@angular/core";
import { Location } from "@angular/common";

import { AuthService } from "../../core/services/auth.service";

@Component({
  selector: "app-access-denied",
  templateUrl: "./access-denied.component.html",
  styleUrls: ["./access-denied.component.css"]
})
export class AccessDeniedComponent implements OnInit {

  isAuthenticated = false;

  constructor(
    private location: Location,
    private authService: AuthService
  ) {
  }

  ngOnInit() {
    this.isAuthenticated = this.authService.isLoggedIn();
  }

  goBack() {
    this.location.back(); // <-- go back to previous location on cancel
  }
}


در اینجا اگر کاربر به سیستم وارد نشده باشد، دکمه‌ی لاگین نیز به او نمایش داده می‌شود. همچنین وجود "queryParamsHandling="merge در لینک مراجعه‌ی به صفحه‌ی لاگین، سبب خواهد شد تا query string موجود در صفحه نیز حفظ شود و به صفحه‌ی لاگین انتقال پیدا کند. در صفحه‌ی لاگین نیز جهت پردازش این نوع کوئری استرینگ‌ها، تمهیدات لازم درنظر گرفته شده‌اند.
دکمه‌ی back آن نیز توسط سرویس توکار Location واقع در مسیر angular/common@ پیاده سازی شده‌است.


ایجاد یک محافظ مسیر سمت کلاینت برای بررسی وضعیت کاربر جاری و همچنین نقش‌های او

پس از تعریف متد getAuthUser و استخراج اطلاعات کاربر از توکن دسترسی دریافتی که شامل نقش‌های او نیز می‌شود، اکنون می‌توان متد بررسی این نقش‌ها را نیز به سرویس Auth اضافه کرد:
  isAuthUserInRoles(requiredRoles: string[]): boolean {
    const user = this.getAuthUser();
    if (!user || !user.roles) {
      return false;
    }
    return requiredRoles.some(requiredRole => user.roles.indexOf(requiredRole.toLowerCase()) >= 0);
  }

  isAuthUserInRole(requiredRole: string): boolean {
    return this.isAuthUserInRoles([requiredRole]);
  }
متد some در جاوا اسکریپت شبیه به متد Any در C# LINQ عمل می‌کند. همچنین در مقایسه‌ی صورت گرفته، با توجه به اینکه user.roles را پیشتر به LowerCase تبدیل کرد‌یم، حساسیتی بین نقش Admin و admin و کلا کوچکی و بزرگی نام نقش‌ها وجود ندارد.
اکنون در هر قسمتی از برنامه که نیاز به بررسی امکان دسترسی یک کاربر به نقش یا نقش‌هایی خاص وجود داشته باشد، می‌توان AuthService را به سازنده‌ی ‌آن تزریق و سپس از متد فوق جهت بررسی نهایی، استفاده کرد.

در ادامه یک Route Guard جدید را در مسیر app\core\services\auth.guard.ts ایجاد می‌کنیم. کار آن بررسی خودکار امکان دسترسی به یک مسیر درخواستی است:
import { Injectable } from "@angular/core";
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";

import { AuthService } from "./auth.service";
import { AuthGuardPermission } from "../models/auth-guard-permission";

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    if (!this.authService.isLoggedIn()) {
      this.showAccessDenied(state);
      return false;
    }

    const permissionData = route.data["permission"] as AuthGuardPermission;
    if (!permissionData) {
      return true;
    }

    if (Array.isArray(permissionData.deniedRoles) && Array.isArray(permissionData.permittedRoles)) {
      throw new Error("Don't set both 'deniedRoles' and 'permittedRoles' in route data.");
    }

    if (Array.isArray(permissionData.permittedRoles)) {
      const isInRole = this.authService.isAuthUserInRoles(permissionData.permittedRoles);
      if (isInRole) {
        return true;
      }

      this.showAccessDenied(state);
      return false;
    }

    if (Array.isArray(permissionData.deniedRoles)) {
      const isInRole = this.authService.isAuthUserInRoles(permissionData.deniedRoles);
      if (!isInRole) {
        return true;
      }

      this.showAccessDenied(state);
      return false;
    }
  }

  private showAccessDenied(state: RouterStateSnapshot) {
    this.router.navigate(["/accessDenied"], { queryParams: { returnUrl: state.url } });
  }
}
در اینجا در ابتدا وضعیت لاگین کاربر بررسی می‌گردد. این وضعیت نیز از طریق سرویس Auth که به سازنده‌ی کلاس تزریق شده‌است، تامین می‌شود. اگر کاربر هنوز لاگین نکرده باشد، به صفحه‌ی عدم دسترسی هدایت خواهد شد.
سپس خاصیت permission اطلاعات مسیر استخراج می‌شود. اگر چنین مقداری وجود نداشت، همینجا کار با موفقیت خاتمه پیدا می‌کند.
در آخر وضعیت دسترسی به نقش‌های استخراجی deniedRoles و permittedRoles که از اطلاعات مسیر دریافت شدند، توسط متد isAuthUserInRoles سرویس Auth بررسی می‌شوند.

در متد showAccessDenied کار ارسال آدرس درخواستی (state.url) به صورت یک کوئری استرینگ (queryParams) با کلید returnUrl به صفحه‌ی accessDenied صورت می‌گیرد. در این صفحه نیز دکمه‌ی لاگین به همراه "queryParamsHandling="merge است. یعنی کامپوننت لاگین برنامه، کوئری استرینگ returnUrl را دریافت می‌کند:
 this.returnUrl = this.route.snapshot.queryParams["returnUrl"];
 و پس از لاگین موفق، در صورت وجود این کوئری استرینگ، هدایت خودکار کاربر، به مسیر returnUrl پیشین صورت خواهد گرفت:
if (this.returnUrl) {
   this.router.navigate([this.returnUrl]);
} else {
   this.router.navigate(["/protectedPage"]);
}

محل معرفی این AuthGuard جدید که در حقیقت یک سرویس است، در ماژول Core، در قسمت providers آن، به صورت ذیل می‌باشد:
import { AuthGuard } from "./services/auth.guard";

@NgModule({
  providers: [
    AuthGuard
  ]
})
export class CoreModule {}
در آخر برای اعمال این Guard جدید، به فایل dashboard-routing.module.ts مراجعه کرده و خاصیت canActivate را مقدار دهی می‌کنیم:
import { ProtectedPageComponent } from "./protected-page/protected-page.component";
import { AuthGuardPermission } from "../core/models/auth-guard-permission";
import { AuthGuard } from "../core/services/auth.guard";

const routes: Routes = [
  {
    path: "protectedPage",
    component: ProtectedPageComponent,
    data: {
      permission: {
        permittedRoles: ["Admin"],
        deniedRoles: null
      } as AuthGuardPermission
    },
    canActivate: [AuthGuard]
  }
];
به این ترتیب با درخواست این مسیر، پیش از فعالسازی و نمایش آن، توسط AuthGuard معرفی شده‌ی به آن، کار بررسی وضعیت کاربر و نقش‌های او که از خاصیت permission خاصیت data دریافت می‌شوند، صورت گرفته و اگر عملیات تعیین اعتبار اطلاعات با موفقیت به پایان رسید، آنگاه کاربر مجوز دسترسی به این قسمت از برنامه را خواهد یافت.

اگر قصد آزمایش آن‌را داشتید، فقط کافی است بجای نقش Admin، مثلا Admin1 را در permittedRoles مقدار دهی کنید، تا صفحه‌ی access denied را در صورت درخواست مسیر protectedPage، بتوان مشاهده کرد.




کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
مطالب
Minimal API's در دات نت 6 - قسمت سوم - ایجاد endpoints مقدماتی
در دو قسمت قبل، ساختار ابتدایی برنامه‌ی Minimal API's بلاگ دهی را ایجاد کردیم. در این قسمت می‌خواهیم بررسی کنیم، معادل‌های کنترلرهای MVC و اکشن متدهای آن‌ها در سیستم جدید Minimal API، به چه صورتی ایجاد می‌شوند.


ایجاد اولین endpoint از نوع Get مبتنی بر Minimal API

برای افزودن اولین endpoint برنامه، به فایل Program.cs برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
// ...

app.UseHttpsRedirection();

app.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
{
    var authors = await ctx.Authors.ToListAsync();
    return authors;
});

app.Run();
app.MapGet، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpGet هستند، ارائه می‌دهد. در همینجا می‌توان آدرس دقیق این endpoint را به عنوان پارامتر اول، مشخص کرد که پس از فراخوانی آن در مرورگر، یک Delegate که هندلر نام دارد (پارامتر دوم این متد)، اجرا می‌شود تا Response ای را ارائه دهد.
همانطور که مشاهده می‌کنید می‌توان در اینجا، این Delegate را از نوع Lambda expressions تعریف کرد و با ذکر MinimalBlogDbContext به صورت یک پارامتر آن، کار تزریق وابستگی‌های خودکار آن نیز صورت می‌گیرد. شبیه به حالتی که می‌توان یک سرویس را به عنوان پارامتر یک اکشن متد، با ذکر ویژگی [FromServices] در کنترلرهای MVC معرفی کرد؛ البته در اینجا بدون نیاز به ذکر این ویژگی (هرچند هنوز هم قابل ذکر است). مزیت آن این است که هر endpoint، تنها سرویس‌های مورد نیاز خودش را دریافت می‌کند و نه یک لیست قابل توجه از تمام سرویس‌هایی که قرار است در قسمت‌های مختلف یک کنترلر استفاده شوند.
پس از آن می‌توان با Context ای که در اختیار داریم، عملیات مدنظر را پیاده سازی کرده و یک خروجی را ارائه دهیم. در اینجا دیگر نیازی به تعریف IActionResult‌ها و امثال آن نیست و همه چیز ساده شده‌است.


ایجاد اولین endpoint از نوع Post مبتنی بر Minimal API

app.MapPost، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpPost هستند، ارائه می‌دهد:
//...

app.UseHttpsRedirection();

//...

app.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;
});

app.Run();

internal record AuthorDto(string FirstName, string LastName, DateTime DateOfBirth, string? Bio);
در ابتدا یک Dto را که حاوی اطلاعات نویسنده‌ی جدیدی است، معادل خواص مدل Author دومین برنامه، تعریف می‌کنیم. سپس می‌توان این Dto را نیز به صورت یک پارامتر جدید به Lambda Expression متد app.MapPost معرفی کرد تا کار نگاشت اطلاعات دریافتی به آن، به صورت خودکار انجام شود (حالت پیش‌فرض آن [FromBody] است که نیازی به ذکر آن نیست).
سعی شده‌است تا این مثال در ساده‌ترین شکل ممکن خودش ارائه شود. در ادامه کار نگاشت خواص Dto را به مدل دومین برنامه، توسط AutoMapper انجام خواهیم داد.
مابقی نکات متد app.MapPost نیز مانند متد app.MapGet است؛ برای مثال در اینجا نیز تعریف مسیر endpoint، توسط اولین پارامتر این متد صورت می‌گیرد و نحوه‌ی تزریق سرویس DbContext برنامه نیز یکی است.


آزمایش برنامه‌ی Minimal API's

برنامه‌ی Minimal API's تهیه شده، به همراه یک Swagger از پیش تنظیم شده نیز هست. به همین جهت برای کار با این API الزاما نیازی به استفاده‌ی از مثلا برنامه‌ی Postman یا راه حل‌های مشابه نیست. بنابراین فقط کافی است تا برنامه‌ی API را اجرا کرده و در رابط کاربری ظاهر شده در آدرس https://localhost:7085/swagger/index.html، بر روی دکمه‌ی Try it out هر کدام از endpointها کلیک کنیم. برای مثال اگر چنین کاری را در قسمت Post انجام دهیم، به تصویر زیر می‌رسیم:



در اینجا پس از ویرایش اطلاعات شیء JSON ای که برای ما تدارک دیده‌است، فقط کافی است بر روی دکمه‌ی execute ذیل آن کلیک کنیم تا اطلاعات این Dto را به app.MapPost متناظر فوق ارسال کند و برای نمونه خروجی بازگشتی از سرور را نیز در همینجا نمایش می‌دهد که در آن، Id رکورد نیز پس از ثبت در بانک اطلاعاتی، مشخص است:



شروع به Refactoring و خلوت کردن فایل Program.cs

اگر بخواهیم به همین نحو تمام endpoints و dtoها را داخل فایل Program.cs اضافه کنیم، پس از مدتی به یک فایل بسیار حجیم و غیرقابل نگهداری خواهیم رسید. بنابراین در مرحله‌ی اول، تنظیمات سرویس‌ها و میان افزارها را به خارج از آن منتقل می‌کنیم. برای این منظور پوشه‌ی جدید Extensions را به همراه دو کلاس زیر ایجاد می‌کنیم:
using Microsoft.EntityFrameworkCore;
using MinimalBlog.Dal;

namespace MinimalBlog.Api.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var connectionString = builder.Configuration.GetConnectionString("Default");
        builder.Services.AddDbContext<MinimalBlogDbContext>(opt => opt.UseSqlServer(connectionString));

        return services;
    }
}
کار این متد الحاقی، خارج کردن تنظیمات سرویس‌های برنامه از کلاس Program است.

همچنین نیاز به متد الحاقی دیگری برای خارج کردن تنظیمات میان‌افزارها داریم:
namespace MinimalBlog.Api.Extensions;

public static class WebApplicationExtensions
{
    public static WebApplication ConfigureApplication(this WebApplication app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        return app;
    }
}
پس از این تغییرات، اکنون ابتدای کلاس Program برنامه‌ی Api به صورت زیر تغییر می‌کند و خلاصه می‌شود:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationServices(builder);

var app = builder.Build();
app.ConfigureApplication();

در قسمت بعد، endpoints را از این کلاس آغازین برنامه خارج می‌کنیم.
نظرات مطالب
ASP.NET MVC #16
سلام؛ چطور میشه خطاهای اتفاق افتاده که در Elmah نمایش داده می‌شوند را در فایل xml ذخیره کرد ؟
من مثال را دانلود کردم ولی نفهمیدم چطور باید اینکار را انجام بدهم...
ممنون // 
نظرات مطالب
آشنایی با FileTable در SQL Server 2012 بخش 2
می خواستم بدونم چطور میشه یک فایل رو که در Filetable ذخیره شده، به اصطلاح Fetch کنم و توسط برنامه مربوطه (مثلا adobe acrobat یا word یا ...) نشونش بدم؟
مطالب
نحوه راه اندازی مجدد یک دیتابیس اس کیوال سرور پس از پر شدن هارد دیسک

امروز یکی از برنامه‌ها (برنامه ASP.Net) با مشکل زیر مواجه شده بود:

پیغام خطا:
اتصال با سرور اس کیوال قطع شده است. لطفا با مسئول مربوطه هماهنگ نمائید.
SQLErr:4060

این خطا به معنای عدم امکان باز کردن دیتابیس است.

در طی این مدت با موارد زیادی از این دست (مشکلات مختلف عدم امکان برقراری ارتباط با اس کیوال سرور) برخورد داشتم که خلاصه تمام آن‌ها تابع زیر شده است:
public void CheckSQLServerStat(Exception ex)
{
try
{
SqlException ar = (SqlException) ex;
switch (ar.Number)
{
case 2:
case 11:
case 17:
case 40:
case 4060:
case 1326:
case 17142:
case 18456:
HttpContext.Current.Response.Write("<br/>" + "اتصال با سرور اس کیوال قطع شده است. لطفا با مسئول مربوطه هماهنگ نمائید." + "<br/> SQLErr:" + ar.Number + "<br/>");
break;
}
}catch{}
}

هنگام رخ‌دادن یک خطا (در توابعی که با اس کیوال سرور کار می‌کنند)، exception حاصل را به این تابع پاس کرده و خطای حاصل را به صورت مختصر و مفید به کاربر نشان می‌دهم. کاربر متوجه می‌شود که یک مشکل اساسی در سیستم رخ داده است. برنامه نویس هم با جستجو در مورد شماره خطا می‌تواند مشکل یابی کند. تابع Response.Write هم بالاتر از هر المان دیگری در صفحه، این خطا را به شکل واضحی نمایش می‌دهد. (کلا ریخت صفحه را به هم می‌ریزد که از لحاظ روانی لازم است! چون عملا در این حالت سیستم از کار افتاده است)

به management studio اس کیوال سرور که مراجعه کردم، علامت خاصی کنار نام دیتابیس نبود فقط برخلاف سایر دیتابیس‌ها که آیکون + مربوط به باز شدن tree آن وجود دارد، این یک مورد آن‌را نداشت. بر روی نام دیتابیس کلیک راست کردم و انتخاب خواص، خطای زیر نمایش داده شد:
------------------------------
An exception occurred while executing a Transact-SQL statement or batch. (Microsoft.SqlServer.ConnectionInfo)
------------------------------
Database 'dbName' cannot be opened due to inaccessible files or insufficient memory or disk space. See the SQL Server errorlog for details. (Microsoft SQL Server, Error: 945)
------------------------------

بله! دیتابیس قابل باز شدن نبود چون درایو مربوطه پر شده بود. بعد از خالی کردن درایو و باز کردن فضای لازم، باز هم دیتابیس به حالت اجرایی و قابل استفاده برنگشت. یک راه این است که کل سرویس مربوط به اس کیوال سرور را استاپ و استارت کرد. که البته این مورد سبب قطع ارتباط کل مجموعه می‌شود. یا راه دیگر اجرای چند سطر زیر است که دیتابیس را مجددا راه اندازی خواهد کرد (بدون نیاز به ری استارت سرویس)

use master;
alter database dbName set OFFLINE;
alter database dbName set online;

به این صورت دیتابیس مربوطه به حالت عادی بازگشت و قابل استفاده شد.

مطالب دوره‌ها
چرا XML و چرا پشتیبانی توکار از آن در SQL Server
مقدمه

فیلدهای XML از سال 2005 به امکانات توکار SQL Server اضافه شده‌اند و بسیاری از مزایای دنیای NoSQL را درون SQL Server رابطه‌ای مهیا می‌سازند. برای مثال با تعریف یک فیلد به صورت XML، می‌توان از هر ردیف به ردیفی دیگر، اطلاعات متفاوتی را ذخیره کرد؛ به این ترتیب امکان کار با یک فیلد که می‌تواند اطلاعات یک شیء را قبول کند و در حقیقت امکان تعریف اسکیمای پویا و متغیر را در کنار امکانات یک بانک اطلاعاتی رابطه‌ای که از اسکیمای ثابت پشتیبانی می‌کند، میسر می‌شود.
همچنین SQL Server در این حالت قابلیتی را ارائه می‌دهد که در بسیاری از بانک‌های اطلاعاتی NoSQL میسر نیست. در اینجا در صورت نیاز و لزوم می‌توان اسکیمای کاملا مشخصی را به یک فیلد XML نیز انتساب داد؛ هر چند این مورد اختیاری است و می‌توان یک un typed XML را نیز بکار برد. به علاوه امکانات کوئری گرفتن توکار از این اطلاعات را به کمک XPath ترکیب شده با T-SQL، نیز فراموش نکنید.
بنابراین اگر یکی از اهداف اصلی گرایش شما به سمت دنیای NoSQL، استفاده از امکان تعریف اطلاعاتی با اسکیمای متغیر و پویا است، فیلدهای نوع XML اس کیوال سرور را مدنظر داشته باشید.
یک مثال عملی: فناوری Azure Dev Fabric's Table Storage (نسخه Developer ویندوز Azure که روی ویندوزهای معمولی اجرا می‌شود؛ یک شبیه ساز خانگی) به کمک SQL Server و فیلدهای XML آن طراحی شده است.



چرا XML و چرا پشتیبانی توکار از آن در SQL Server

یک سند XML معمولا بیشتر از یک قطعه داده را در خود نگهداری می‌کند و نوع داده‌ی پیچیده محسوب می‌شود؛ برخلاف داده‌هایی مانند int یا varchar که نوع‌هایی ساده بوده و تنها یک قطعه از اطلاعات خاصی را در خود نگهداری می‌کنند. بنابراین شاید این سؤال مطرح شود که چرا از این نوع داده پیچیده در SQL Server پشتیبانی شده‌است؟
- از سال‌های نسبتا دور، از XML برای انتقال داده‌ها بین سیستم‌ها و سکوهای کاری مختلف استفاده شده‌است.
- استفاده‌ی گسترده‌ای در برنامه‌های تجاری دارد.
- بسیاری از فناوری‌های موجود از آن پشتیبانی می‌کنند.
برای مثال اگر با فناوری‌های مایکروسافتی کار کرده باشید، به طور قطع حداقل در یک یا چند قسمت از آن‌ها، مستقیما از XML استفاده شده‌است.
بنابراین با توجه به اهمیت و گستردگی استفاده از آن، بهتر است پشتیبانی توکاری نیز از آن داخل موتور یک بانک اطلاعاتی، پیاده سازی شده باشد. این مساله سهولت تهیه پشتیبان‌های خودکار، بازیابی آن‌ها و امنیت یکپارچه با SQL Server را به همراه خواهد داشت؛ به همراه تمام زیرساخت‌های مهیای در SQL Server.



روش‌های مختلف ذخیره سازی XML در بانک‌های اطلاعاتی رابطه‌ای

الف) ذخیره سازی متنی
این روش نیاز به نگارش خاصی از SQL Server یا بانک اطلاعاتی الزاما خاصی نداشته و با تمام بانک‌های اطلاعاتی رابطه‌ای سازگار است؛ مثلا از فیلدهای varchar برای ذخیره سازی آن استفاده شود. مشکلی که این روش به همراه خواهد داشت، از دست دادن ارزش یک سند XML و برخورد متنی با آن است. زیرا در این حالت برای تعیین اعتبار آن یا کوئری گرفتن از آن‌ها نیاز است اطلاعات را از بانک اطلاعاتی خارج کرده و در لایه‌ای دیگر از برنامه، کار جستجو پردازش آن‌ها را انجام داد.

ب) تجزیه XML به چندین جدول رابطه‌ای
برای مثال یک سند XML را درنظر بگیرید که دارای اطلاعات شخص و خرید‌های او است. می‌توان این سند را به چندین فیلد در چندین جدول مختلف رابطه‌ای تجزیه کرد و سپس با روش‌های متداول کار با بانک‌های اطلاعاتی رابطه‌ای از آن‌ها استفاده نمود.

ج) ذخیره سازی آن‌ها توسط فیلدهای خاص XML
در این حالت با استفاده از فیلدهای ویژه XML می‌توان از فناوری‌های مرتبط با XML تمام و کمال استفاده کرد. برای مثال تهیه کوئری‌های پیچیده داخل همان بانک اطلاعاتی بدون نیاز به تجزیه سند به چندین جدول و یا خارج کردن آن‌ها از بانک اطلاعاتی و جستجوی بر روی آن‌ها در لایه‌ای دیگر از برنامه.


موارد کاربرد XML در SQL Server

کاربردهای مناسب

- اطلاعات، سلسله مراتبی و تو در تو هستند. XQuery و XPath در این موارد بسیار خوب عمل می‌کند.
- ساختار قسمتی از اطلاعات ثابت است و قسمتی از آن خیر. برای نمونه، یک برنامه‌ی فرم ساز را درنظر بگیرید که هر فرم آن هر چند دارای یک سری خواص ثابت مانند نام، گروه و امثال آن است، اما هر کدام دارای فیلدهای تشکیل دهنده متفاوتی نیز می‌باشد. به این ترتیب با استفاده از یک فیلد XML، دیگری نیازی به نگران بودن در مورد نحوه مدیریت اسکیمای متغیر مورد نیاز، نخواهد بود.
نمونه‌ی دیگر آن ذخیره سازی خواص متغیر اشیاء است. هر شیء دارای یک سری خواص ثابت است اما خواص توصیف کننده‌ی آن‌ها از هر رکورد به رکوردی دیگر متفاوت است.

کاربردهای نامناسب

- کل اطلاعات را داخل فیلد XML قرار دادن. هدف از فیلدهای XML قرار دادن یک دیتابیس داخل یک سلول نیست.
- ساختار تعریف شده کاملا مشخص بوده و به این زودی‌ها هم قرار نیست تغییر کند. در این حالت استفاده از قابلیت‌های رابطه‌ای متداول SQL Server مناسب‌تر است.
- قرار دادن اطلاعات باینری بسیار حجیم در سلول‌های XML ایی.
 


تاریخچه‌ی پشتیبانی از XML در نگارش‌های مختلف SQL Server

الف) SQL Server 2000
در SQL Server 2000 روش (ب) توضیح داده شده در قسمت قبل، پشتیبانی می‌شود. در آن برای تجزیه یک سند XML به معادل رابطه‌ای آن، از تابعی به نام OpenXML استفاده می‌شود و برای تبدیل این اطلاعات به XML از روش Select … for XML می‌توان کمک گرفت. همچنین تاحدودی مباحث XPath Queries نیز در آن گنجانده شد‌ه‌است.

ب) SQL Server 2005
در نگارش 2005 آن، برای اولین بار نوع داده‌ای ویژه XML معرفی گشت به همراه امکان تعریف اسکیمای XML و اعتبارسنجی آن و پشتیبانی از XQuery برای جستجوی سریع بر روی داده‌های XML داخل همان بانک اطلاعاتی، بدون نیاز به استخراج اطلاعات XML و پردازش مجزای آن‌ها در لایه‌ای دیگر از برنامه.

ج) SQL Server 2008 به بعد
در اینجا فاز نگهداری این نوع داده خاص شروع شده و بیشتر شامل یک سری بهبودهای کوچک در کارآیی و نحوه‌ی استفاده از آن‌ها می‌شود.



استفاده از XML با کمک SQLCLR

از SQL Server 2005 به بعد، امکان استفاده از کلیه‌ی امکانات موجود در فضای نام System.Xml دات نت، در SQL Server نیز به کمک SQL CLR مهیا شده‌است. همچنین از SQL Server 2008 به بعد، امکانات فضای نام System.Xml.Linq و مباحث LINQ to XML نیز توسط SQL CLR پشتیبانی می‌شوند.
البته این امکانات در SQL Server 2005 نیز قابل استفاده هستند، اما اسمبلی شما unsafe تلقی می‌شود. پس از آزمایشات و بررسی کافی، فضای نام مرتبط با LINQ to XML و امکانات آن، به عنوان اسمبلی‌هایی امن و قابل استفاده در SQL Server 2008 به بعد، معرفی شده‌اند.




مزایای وجود فیلد ویژه XML در SQL Server

پس از اینکه فیلدهای XML به صورت یک نوع داده بومی بانک اطلاعاتی SQL Server معرفی شدند، مزایای ذیل بلافاصله در اختیار برنامه نویس‌ها قرار گرفت:
- امکان تعریف آن‌ها به صورت یک ستون جدولی خاصی
- استفاده از آن‌ها به عنوان یک پارامتر رویه‌های ذخیره شده
- امکان تعریف خروجی توابع scalar سفارشی تعریف شده به صورت XML
- امکان تعریف متغیرهای T-SQL از نوع XML

برای مثال در اینجا نحوه‌ی تعریف یک جدول جدید دارای فیلدی از نوع XML را مشاهده می‌کنید:
 CREATE TABLE xml_tab
(
  id INT,
  xml_col  XML
)
- پشتیبانی از فناوری‌های XML ایی مانند اعتبارسنجی اسکیما و نوشتن کوئری‌های پیشرفته با XQuery و XPath.
- امکان تعریف ایندکس‌های XML ایی اضافه شده‌است.



چه نوع XML ایی را می‌توان در فیلدهای XML ذخیره کرد؟

فیلدهای XML امکان ذخیره سازی داده‌های XML خوش فرم را مطابق استاندارد یک XML، دارند. حداکثر اندازه قابل ذخیره سازی در یک فیلد XML دو گیگابایت است.
البته امکانات مهیای در SQL Server در بسیاری از موارد فراتر از استاندارد یک XML هستند. به این معنا که در فیلدهای XML می‌توان Documents و یا Fragments را ذخیره سازی کرد. یک سند XML یا Document حاوی تنها یک ریشه اصلی است؛ اما یک Fragment می‌تواند بیش از یک ریشه اصلی را در خود ذخیره کند. یک مثال:
 DECLARE @xml_tab TABLE (xml_col XML)
-- document
INSERT @xml_tab VALUES ('<person/>')
-- fragment
INSERT @xml_tab VALUES ('<person/><person/>')
SELECT * FROM @xml_tab
مدل داده‌ای XML در SQL Server بر مبنای استانداردهای  XQuery و XPath طراحی شده‌است و این استانداردها Fragments را به عنوان یک قطعه داده XML معتبر، قابل پردازش می‌دانند؛ علاوه بر آن مقادیر null و خالی را نیز معتبر می‌دانند. برای مثال عبارات ذیل معتبر هستند:
 DECLARE @xml_tab TABLE (xml_col XML)
-- text only
INSERT @xml_tab VALUES ('data data data .....')
-- empty string
INSERT @xml_tab VALUES ('')
-- null value
INSERT @xml_tab VALUES (null)
SELECT * FROM @xml_tab
همچنین امکان ذخیره سازی یک متن خالی بدون فرمت نیز در اینجا مجاز است. بنابراین به کمک T-SQL می‌توان برای مثال نوع داده varchar و varchar max را به XML تبدیل کرد و برعکس. امکان تبدیل Text و NText (منسوخ شده) نیز به XML وجود دارد ولی در این حالت خاص، عکس آن، پشتیبانی نمی‌شود.
به علاوه باید دقت داشت که در SQL Server نوع داده‌ای XML برای ذخیره سازی داده‌ها بکار گرفته می‌شود. به این معنا که در اینجا پیشوندهای فضاهای نام XML بی‌معنا هستند.
 DECLARE @xml_tab TABLE (xml_col XML)
INSERT @xml_tab VALUES ('<doc/>')
INSERT @xml_tab VALUES ('<doc xmlns="http://www.doctors.com"/>')
-- این سه سطر در عمل یکی هستند
INSERT @xml_tab VALUES ('<doc xmlns="http://www.documents.com"/>')
INSERT @xml_tab VALUES ('<dd:doc xmlns:dd="http://www.documents.com"/>')
INSERT @xml_tab VALUES ('<rr:doc xmlns:rr="http://www.documents.com"/>')
SELECT * FROM @xml_tab
در این مثال، سه insert آخر در عمل یکی درنظر گرفته می‌شوند.



Encoding ذخیره سازی داده‌های XML

SQL Server امکان ذخیره سازی اطلاعات متنی را به فرمت UFT8، اسکی و غیره، دارد. اما جهت پردازش فیلدهای XML و ذخیره سازی آن‌ها از Collation پیش فرض بانک اطلاعاتی کمک خواهد گرفت. البته ذخیره سازی نهایی آن همیشه با فرمت UCS2 است (یونیکد دو بایتی).
 DECLARE @xml_tab TABLE  (id INT, xml_col XML)

INSERT INTO @xml_tab
VALUES
  (
5,
N'<?xml version="1.0" encoding="utf-8"?>
<doc1>
  <row name="vahid"></row>
</doc1>
')
برای نمونه به مثال فوق دقت کنید. اگر آن‌را اجرا کنید، برنامه با خطای ذیل متوقف خواهد شد:
 XML parsing: line 1, character 38, unable to switch the encoding
علت اینجا است که با قرار دادن N در ابتدای رشته XML ایی در حال ذخیره سازی، آن‌را به صورت یونیکد دوبایتی معرفی کرده‌ایم اما encoding سند در حال ذخیره سازی utf-8 تعریف شده‌است و این‌دو با هم سازگاری ندارند.
برای حل این مشکل باید N ابتدای رشته را حذف کرد. روش دوم، معرفی و استفاده از utf-16 است بجای utf-8 در ویژگی encoding.
همچنین در این حالت اگر encoding را utf-16 معرفی کنیم و ابتدای رشته در حال ذخیره سازی N قرار نگیرد، باز با خطای unable to switch the encoding مواجه خواهیم شد.



نحوه‌ی ذخیره سازی اطلاعات XML ایی در SQL Server

SQL Server فرمت اطلاعات XML وارد شده را حفظ نمی‌کند. برای مثال اگر قطعه کد زیر را اجرا کنید
 DECLARE @xml_tab TABLE  (id INT, xml_col XML)

INSERT INTO @xml_tab
VALUES
  (
5,
'<?xml version="1.0" encoding="utf-8"?><doc1><row name="vahid"></row></doc1>'
  )
   
SELECT * FROM @xml_tab
خروجی Select انجام شده به صورت زیر است:
 <doc1>
  <row name="vahid" />
</doc1>
اطلاعات و داده نهایی، بدون تغییری از آن قابل استخرج است. اما اصطلاحا lexical integrity آن حفظ نشده و نمی‌شود. بنابراین در اینجا ذکر سطر xml version ضروری نیست و یا برای مثال اگر ویژگی‌ها را توسط " و یا ' مقدار دهی کنید، همیشه توسط "  ذخیره خواهد شد.



ذخیره سازی داده‌هایی حاوی کاراکترهای غیرمجاز XML

اطلاعات دنیای واقعی همیشه به همراه اطلاعات تک کلمه‌ای ساده نیست. ممکن است نیاز شود انواع و اقسام حروف و تگ‌ها نیز در این بین به عنوان داده ذخیره شوند. روش حل استاندارد آن بدون نیاز به دستکاری اطلاعات ورودی، استفاده از CDATA است:
 DECLARE @xml_tab TABLE  (id INT, xml_col XML)

INSERT INTO @xml_tab
VALUES
  (
5,
'<person><![CDATA[ 3 > 2 ]]></person>'
  )
   
SELECT * FROM @xml_tab
در این حالت خروجی select اطلاعات ذخیره شده به صورت زیر خواهد بود:
 <person> 3 &gt; 2 </person>
به صورت خودکار قسمت CDATA پردازش شده و اصطلاحا حروف غیرمجاز XML ایی به صورت خودکار escape شده‌اند.



محدودیت‌های فیلدهای XML

- امکان مقایسه مستقیم را ندارند؛ بجز مقایسه با نال. البته می‌توان XML را تبدیل به مثلا varchar کرد و سپس این داده رشته‌ای را مقایسه نمود. برای مقایسه با null توابع isnull و coalesce نیز قابل بکارگیری هستند.
- order by و group by بر روی این فیلدها پشتیبانی نمی‌شود.
- به عنوان ستون کلید قابل تعریف نیست.
- به صورت منحصربفرد و unique نیز قابل علامتگذاری و تعریف نیست.
- فیلدهای XML نمی‌توانند دارای collate باشند.
نظرات مطالب
ASP.NET MVC #14
با سلام من از همین روش استفاده کردم و روی لوکال هیچ مشکلی ندارد و به خوبی کار میکند اما وقتی پروژه را روی هاست آپلود می‌کنم مثلا اگه در Area به این صورت تعریف کرده باشم
@using (Html.BeginForm("GotoIndex1", "Home", new { area="Admin"}))
{


<input type="submit" value="Send" />
}
در زمان اجرا بر روی هاست به این شکل می‌شود
<form method="post" action="">
<input type="submit" value="Send">
</form>
انگار که اکشن را مقدار دهی نکردم .
فایل AdminAreaRegistration.cs  به صورت زیر می‌باشد:
 public override void RegisterArea(AreaRegistrationContext context)
  {
context.MapRoute(
"Admin_default11",
"Admin/{controller}/{action}/{id}/{*Extparam}",
new { action = "Index", controller = "Home", id = UrlParameter.Optional }
  );
  }
و فایل RouteConfig به صورت زیر است
routes.MapRoute(
  name: "Default",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  , namespaces: new[] { "ForTest.Controllers" }
);
باز هم تاکید می‌کنم که روی لوکال بدون هیچ مشکلی اجرا می‌شود. اما روی سرور که میرود انگار action فرم مقدار دهی نشده.