uploader.bind('FileUploaded', function(up, file, info) { var obj = JSON.parse(info.response); $('form#quoteRequest').append('<input type="hidden" name="file_name" value="' + obj.result.cleanFileName + '" />'); //note obj.result.cleanFileName instead obj.cleanFileName });
<html> <body> <div> Prevent default: <input type="checkbox" id="keydownStop" value="1" /> keydown <input type="checkbox" id="keypressStop" value="1" /> keypress <input type="checkbox" id="keyupStop" value="1" /> keyup </div> Ignore: <input type="checkbox" id="keydownIgnore" value="1" /> keydown <input type="checkbox" id="keypressIgnore" value="1" /> keypress <input type="checkbox" id="keyupIgnore" value="1" /> keyup <div> Focus on the input below and press any key. </div> <div> <input type="text" style=" width: 600px" id="keyInput" /> </div> Log: <div> <textarea id="keyLogger" rows="18" onfocus="this.blur()" style="width: 600px; border: 1px solid black"></textarea> </div> <input type="button" value="Clear" onclick="clearLog()" /> <script type="text/javascript"> document.getElementById('keyInput').onkeydown = keyHandler; document.getElementById('keyInput').onkeyup = keyHandler; document.getElementById('keyInput').onkeypress = keyHandler; document.getElementById('keyInput').focus(); function keyHandler(e) { e = e || window.event; if (document.getElementById(e.type + 'Ignore').checked) return; var evt = e.type; while (evt.length < 10) evt += ' ' + log(evt + ' keyCode=' + e.keyCode + ' which=' + e.which + ' charCode=' + e.charCode + ' char=' + String.fromCharCode(e.keyCode || e.charCode) + (e.shiftKey ? ' +shift' : '') + (e.ctrlKey ? ' +ctrl' : '') + (e.altKey ? ' +alt' : '') + (e.metaKey ? ' +meta' : '')); if (document.getElementById(e.type + 'Stop').checked) { e.preventDefault ? e.preventDefault() : (e.returnValue = false); } } function clearLog() { document.getElementById('keyLogger').value = ''; document.getElementById('keyInput').focus(); } function log(text) { var area = document.getElementById('keyLogger'); area.value += text + '\n'; area.scrollTop = area.scrollHeight; } </script> </body> </html>
<!doctype html>
document.onkeydown = function (e) { e = e || event; console.log(e); }
function getCharacter(event) { if (event.which == null) return String.fromCharCode(event.keyCode); // IE else if (event.which != 0 && event.charCode != 0) return String.fromCharCode(event.which); // All others return null; // special key }
<html> <body> <input id="inputText" type="text" /> <script> document.onkeydown = keyDown; document.onkeypress = keyPress; document.onkeyup = keyUp; function keyDown(e) { var input = document.getElementById('inputText'); input.value = 'keyDown started ...'; input.disabled = true; var j = 0; for (var i = 0; i < 999999999; i++) { j = i - j; } console.log(j); //alert('keyDown'); input.value = 'keyDown finished.'; input.disabled = false; } function keyPress(e) { alert('keyPress'); //console.log('keyPress'); } function keyUp(e) { alert('keyUp'); //console.log('keyUp'); } </script> </body> </html>
<html> <head> <script type="text/javascript"> function process() { var above = 0, below = 0; for (var i = 0; i < 200000; i++) { if (Math.random() * 2 > 1) { above++; } else { below++; } } } function test() { var result1 = document.getElementById('log'); var start = new Date().getTime(); console.log('start'); for (var i = 0; i < 200; i++) { result1.value = 'time=' + (new Date().getTime() - start) + ' [i=' + i + ']'; process(); } result1.value = 'time=' + (new Date().getTime() - start) + ' [done]'; console.log('end'); } window.onload = test; </script> </head> <body> <input id='log' /> </body> </html>
<input onkeydown="return false" /> <input onkeypress="return false" />
document.getElementById('myInputText').onkeypress = function (e) { var char = getCharacter(e || window.event); if (!char) return; // special key this.value += char.toUpperCase(); return false; // برای اینکه کاراکتر اضافی نمایش داده نشود }
e ? e : window.event;
document.getElementById('numberInputText').onkeypress = function (e) { e = e || window.event; var chr = getCharacter(e); if (!isNumeric(chr) && chr !== null) return false; } function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } function isNumber(val) { return val !== "NaN" && (+val) + '' === val + '' }
0==false // true 0===false // false, because they are of a different type 1=="1" // true, auto type coercion 1==="1" // false, because they are of a different type
e.ctrlKey && e.keyCode == 'A'.charCodeAt(0)
<html> <body> <div id="dotnettips" style="width: 35px; height: 35px; background-image: url(https://www.dntips.ir/favicon.ico); position: absolute; left: 10px; top: 10px;" tabindex="0"> </div> <script> document.getElementById('dotnettips').onkeydown = function (e) { e = e || event; switch (e.keyCode) { case 37: // left this.style.left = parseInt(this.style.left) - this.offsetWidth + 'px'; return false; case 38: // up this.style.top = parseInt(this.style.top) - this.offsetHeight + 'px'; return false; case 39: // right this.style.left = parseInt(this.style.left) + this.offsetWidth + 'px'; return false; case 40: // down this.style.top = parseInt(this.style.top) + this.offsetHeight + 'px'; return false; } } </script> </body> </html>
<html> <body> <input id="inputText" type="text" /> <div id="divCapsLock" style="color: Red; display: none;">Caps Lock is ON!</div> <script> var capsLock = null; document.onkeypress = keyPress; document.onkeydown = keyDown; function keyDown(e) { e = e || event; capsLock = (e.keyCode == 20 && capsLock !== null) ? !capsLock : capsLock; document.getElementById('divCapsLock').style.display = capsLock ? 'block' : 'none'; } function keyPress(e) { if (capsLock != null) return; e = e || window.event; var charCode = e.charCode || e.keyCode; capsLock = (charCode >= 97 && charCode <= 122 && e.shiftKey) || (charCode >= 65 && charCode <= 90 && !e.shiftKey); document.getElementById('divCapsLock').style.display = capsLock ? 'block' : 'none'; } </script> </body> </html>
<html> <body> <div id='divInput'> <input id="inputText" type="text" /> </div> <script> document.getElementById('inputText').onkeydown = inputKeyDown; document.getElementById('divInput').onkeydown = divKeyDown; document.onkeydown = documentKeyDown; window.onkeydown = windowKeyDown; function divKeyDown(e) { console.log('divKeyDown'); } function inputKeyDown(e) { console.log('inputKeyDown'); } function documentKeyDown(e) { console.log('documentKeyDown'); } function windowKeyDown(e) { console.log('windowKeyDown'); } </script> </body> </html>
<html> <body> <input id="inputText" type="text" /> <div id="divCapsLock" style="color: Red; display: none;">Caps Lock is ON!</div> <script> var capsLock = null; var hasFocus = false; document.onkeyup = keyUp; document.onkeypress = keyPress; document.getElementById('inputText').onfocus = focus; document.getElementById('inputText').onblur = focus; function warnCapsLock() { document.getElementById('divCapsLock').style.display = (capsLock != null && capsLock && hasFocus) ? 'block' : 'none'; } function focus() { hasFocus = !hasFocus; warnCapsLock(); } function keyUp(e) { e = e || event; capsLock = (e.keyCode == 20 && capsLock !== null) ? !capsLock : capsLock; warnCapsLock(); } function keyPress(e) { if (capsLock != null) return; e = e || window.event; var charCode = e.charCode || e.keyCode; capsLock = (charCode >= 97 && charCode <= 122 && e.shiftKey) || (charCode >= 65 && charCode <= 90 && !e.shiftKey); warnCapsLock(); } </script> </body> </html>
window.onblur = function () { capsLock = null; }
var Client = {}; Client.Keyboard = {}; Client.Keyboard.EnableKeyDown = true; Client.Keyboard.EnableKeyUp = false; Client.Keyboard.CurrentKeyEvent = null; window.onkeydown = function (event) { if (!Client.Keyboard.EnableKeyDown) return true; return KeyboardEvents(event); }; window.onkeyup = function (event) { if (!Client.Keyboard.EnableKeyUp) return true; return KeyboardEvents(event); }; function Rise(event) { var e = Client.Keyboard.CurrentKeyEvent; if (event) { var data = { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey }; if (!event(data)) return false; return event(data); } return true; } function KeyboardEvents(e) { e = e || window.event; Client.Keyboard.CurrentKeyEvent = e; switch (e.keyCode) { case 8: return Rise(Client.Keyboard.Backspace); case 9: return Rise(Client.Keyboard.Tab); case 13: return Rise(Client.Keyboard.Enter); case 16: return Rise(Client.Keyboard.Shift); case 17: return Rise(Client.Keyboard.Ctrl); case 18: return Rise(Client.Keyboard.Alt); case 19: return Rise(Client.Keyboard.Pause); case 20: return Rise(Client.Keyboard.CapsLock); case 27: return Rise(Client.Keyboard.Esc); case 33: return Rise(Client.Keyboard.PageUp); case 34: return Rise(Client.Keyboard.PageDown); case 35: return Rise(Client.Keyboard.End); case 36: return Rise(Client.Keyboard.Home); case 37: return Rise(Client.Keyboard.Left); case 38: return Rise(Client.Keyboard.Up); case 39: return Rise(Client.Keyboard.Right); case 40: return Rise(Client.Keyboard.Down); case 44: return Rise(Client.Keyboard.PrtScr); case 45: return Rise(Client.Keyboard.Insert); case 46: return Rise(Client.Keyboard.Delete); ////////////////////////////////////////////////////////////////////////////////////////////////// case 48: return Rise(Client.Keyboard.Num0); case 49: return Rise(Client.Keyboard.Num1); case 50: return Rise(Client.Keyboard.Num2); case 51: return Rise(Client.Keyboard.Num3); case 52: return Rise(Client.Keyboard.Num4); case 53: return Rise(Client.Keyboard.Num5); case 54: return Rise(Client.Keyboard.Num6); case 55: return Rise(Client.Keyboard.Num7); case 56: return Rise(Client.Keyboard.Num8); case 57: return Rise(Client.Keyboard.Num9); ////////////////////////////////////////////////////////////////////////////////////////////////// case 65: return Rise(Client.Keyboard.A); case 66: return Rise(Client.Keyboard.B); case 67: return Rise(Client.Keyboard.C); case 68: return Rise(Client.Keyboard.D); case 69: return Rise(Client.Keyboard.E); case 70: return Rise(Client.Keyboard.F); case 71: return Rise(Client.Keyboard.G); case 72: return Rise(Client.Keyboard.H); case 73: return Rise(Client.Keyboard.I); case 74: return Rise(Client.Keyboard.J); case 75: return Rise(Client.Keyboard.K); case 76: return Rise(Client.Keyboard.L); case 77: return Rise(Client.Keyboard.M); case 78: return Rise(Client.Keyboard.N); case 79: return Rise(Client.Keyboard.O); case 80: return Rise(Client.Keyboard.P); case 81: return Rise(Client.Keyboard.Q); case 82: return Rise(Client.Keyboard.R); case 83: return Rise(Client.Keyboard.S); case 84: return Rise(Client.Keyboard.T); case 85: return Rise(Client.Keyboard.U); case 86: return Rise(Client.Keyboard.V); case 87: return Rise(Client.Keyboard.W); case 88: return Rise(Client.Keyboard.X); case 89: return Rise(Client.Keyboard.Y); case 90: return Rise(Client.Keyboard.Z); //////////////////////////////////////////////////////////////////////////// case 91: //case 219: // opera return Rise(Client.Keyboard.LeftWindow); case 92: return Rise(Client.Keyboard.RightWindow); case 93: return Rise(Client.Keyboard.ContextMenu); //////////////////////////////////////////////////////////////////////////// case 96: return Rise(Client.Keyboard.NumPad0); case 97: return Rise(Client.Keyboard.NumPad1); case 98: return Rise(Client.Keyboard.NumPad2); case 99: return Rise(Client.Keyboard.NumPad3); case 100: return Rise(Client.Keyboard.NumPad4); case 101: return Rise(Client.Keyboard.NumPad5); case 102: return Rise(Client.Keyboard.NumPad6); case 103: return Rise(Client.Keyboard.NumPad7); case 104: return Rise(Client.Keyboard.NumPad8); case 105: return Rise(Client.Keyboard.NumPad9); //////////////////////////////////////////////////////////////////////////// case 106: return Rise(Client.Keyboard.Multiply); case 107: return Rise(Client.Keyboard.Add); case 109: return Rise(Client.Keyboard.Subtract); case 110: return Rise(Client.Keyboard.DecimalPoint); case 111: return Rise(Client.Keyboard.Divide); //////////////////////////////////////////////////////////////////////////// case 112: return Rise(Client.Keyboard.F1); case 113: return Rise(Client.Keyboard.F2); case 114: return Rise(Client.Keyboard.F3); case 115: return Rise(Client.Keyboard.F4); case 116: return Rise(Client.Keyboard.F5); case 117: return Rise(Client.Keyboard.F6); case 118: return Rise(Client.Keyboard.F7); case 119: return Rise(Client.Keyboard.F8); case 120: return Rise(Client.Keyboard.F9); case 121: return Rise(Client.Keyboard.F10); case 122: return Rise(Client.Keyboard.F11); case 123: return Rise(Client.Keyboard.F12); //////////////////////////////////////////////////////////////////////////// case 144: return Rise(Client.Keyboard.NumLock); case 145: return Rise(Client.Keyboard.ScrollLock); case 186: case 59: // opera & firefox return Rise(Client.Keyboard.SemiColon); case 187: case 61: // opera //case 107: //firefox return Rise(Client.Keyboard.Equal); case 188: return Rise(Client.Keyboard.Camma); case 189: return Rise(Client.Keyboard.Dash); case 190: return Rise(Client.Keyboard.Period); case 191: return Rise(Client.Keyboard.Slash); case 192: return Rise(Client.Keyboard.GraveAccent); case 219: return Rise(Client.Keyboard.OpenBracket); case 220: return Rise(Client.Keyboard.BackSlash); case 221: return Rise(Client.Keyboard.CloseBracket); case 222: return Rise(Client.Keyboard.SingleQuote); } };
// ctrl + s Client.Keyboard.S = function (e) { if (e.ctrl) { // انجام عملیات موردنظر } return true; }
قبل از استفاده از وب ورکر، بهتر هست مرورگر را بررسی کنیم که آیا از این قابلیت پشتیبانی میکند یا خیر؟ روش بررسی کردن این قابلیت، شیوههای مختلفی دارد که به تعدادی از آنها اشاره میکنیم:
typeof(Worker) !== "undefined"
<script src="/js/modernizr-1.5.min.js"></script> Modernizr.webworkers
نحوه پشتیبانی وب ورکرها در مروگرهای مختلف به شرح زیر است:
برای ایجاد یک وب ورکر ابتدا لازم است تا کدهای پردازشی را داخل یک فایل js جداگانه بنویسیم. در این مثال ما قصد داریم که شمارندهای را بنویسیم:
var i=0; function timedCount() { i=i+1; postMessage(i); setTimeout("timedCount()", 500); } timedCount();
سپس در فایل HTML به شکل زیر وب ورکر را مورد استفاده قرار میدهیم. در سازنده Worker، ما آدرس فایل js را وارد میکنیم و برای توقف آن نیز از متد terminate استفاده میکنیم:
<!DOCTYPE html> <html> <head> <script> var worker; function Start() { worker=new Worker("webwroker-even-numbers.js"); worker.onmessage=(event)=> { document.getElementById("output").value=event.data; } } function Stop() { worker.terminate(); } </script> <meta charset="utf-8"> <title></title> </head> <body> <input type="text" id="output"/> <button onclick="Start();">Start Worker</button> <button onclick="Stop();">Stop Worker</button> </body> </html>
worker.onerror = function (event) { console.log(event.message, event); };
همچنین برای ورکر هم میتوانید پیامی را ارسال کنید، برای همین کد زیر را به کد ورکر اضافه میکنیم:
self.onmessage=(event)=>{ i=event.data; };
worker.onmessage=(event)=> { document.getElementById("output").value=event.data; if(event.data==8) worker.postMessage(100); }
روشهای ارسال پیام
به این نوع ارسال پیام، Structure Cloning گویند و با استفاده از این شیوه، امکان ارسال نوعهای مختلفی امکان پذیر شده است؛ مثل فایلها، Blobها، آرایهها و کلاسها و ... ولی باید دقت داشته باشید که این ارسال پیامها به صورت کپی بوده و آدرسی ارجاع داده نمیشود و باید مدنظر داشته باشید که ارسال یک فایل، به فرض پنجاه مگابایتی، به خوبی قابل تشخیص است. طبق نظر گوگل، از حجم 32 مگابایت به بعد، این گفته به خوبی مشهود بوده و زمانبر میشود. به همین علت فناوری با نام Transferable Objects ایجاد شده است که "کپی صفر" Zero-Copy را پیاده سازی میکند و باعث بهبود عملگر کپی میشود:
worker.postMessage(arrayBuffer, [arrayBuffer]);
var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]); if (ab.byteLength) { alert('Transferables are not supported in your browser!'); } else { // Transferables are supported. }
worker.postMessage({data: ab1, moreData: ab2}, [ab1, ab2]);
نمودار زیر مقایسهای بین Structure Cloning و Transferable Objects است که توسط گوگل منتشر شده است:
RTT=Round Trip Time
نمودار
بالا برای یک فایل 32 مگابایتی است که زمان رفت به ورکر و پاسخ (برگشت از ورکر)
را اندازه گرفتهاند. در ستونهای اول، این موضوع برای فایرفاکس به روش
Structure Cloning به مدت 302 میلی ثانیه زمان برد که همین موضوع برای
Transferables حدود 6.6 میلی ثانیه زمان برد.
آقای اریک بایدلمن در بخش مهندسی کروم گوگل میگوید: همین سرعت به ما در انتقال تکسچرها و مشها در WebGL کمک میکند.
استفاده از اسکریپت خارجی
در صورتیکه قصد دارید از یک اسکرپیت خارجی، در ورکر استفاده کنید، تابع importScripts برای اینکار ایجاد شده است:
importScripts('script1.js'); importScripts('script2.js');
importScripts('script1.js', 'script2.js');
Inline Worker
اگر بخواهید در همان صفحه اصلی یک ورکر را ایجاد کنید و فایل جاوا اسکریپتی خارجی نداشته باشید، میتوانید از inline worker استفاده کنید. در این روش باید یک نوع blob را ایجاد کنید:
var blob = new Blob([ "onmessage = function(e) { postMessage('msg from worker'); }"]); // یک آدرس همانند آدرس ارجاع به فایل درست میکند var blobURL = window.URL.createObjectURL(blob); var worker = new Worker(blobURL); worker.onmessage = function(e) { // e.data... }; worker.postMessage(); // ورکر آغاز میشود
blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1
window.URL.revokeObjectURL(blobURL);
chrome://blob-internals
برای بهبود این وضعیت میتوان مرحلهی دومی را نیز به این فرآیند لاگین افزود؛ پس از اینکه مشخص شد کاربر وارد شدهی به سایت، دارای اکانتی در IDP ما است، کدی را به آدرس ایمیل او ارسال میکنیم. اگر این ایمیل واقعا متعلق به این شخص است، بنابراین قادر به دسترسی به آن، خواندن و ورود آن به برنامهی ما نیز میباشد. این اعتبارسنجی دو مرحلهای را میتوان به عملیات لاگین متداول از طریق ورود نام کاربری و کلمهی عبور در IDP ما نیز اضافه کرد.
تنظیم میانافزار Cookie Authentication
مرحلهی اول ایجاد گردش کاری اعتبارسنجی دو مرحلهای، فعالسازی میانافزار Cookie Authentication در برنامهی IDP است. برای این منظور به کلاس Startup آن مراجعه کرده و AddCookie را اضافه میکنیم:
namespace DNT.IDP { public class Startup { public const string TwoFactorAuthenticationScheme = "idsrv.2FA"; public void ConfigureServices(IServiceCollection services) { // ... services.AddAuthentication() .AddCookie(authenticationScheme: TwoFactorAuthenticationScheme) .AddGoogle(authenticationScheme: "Google", configureOptions: options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = Configuration["Authentication:Google:ClientId"]; options.ClientSecret = Configuration["Authentication:Google:ClientSecret"]; }); }
اصلاح اکشن متد Login برای هدایت کاربر به صفحهی ورود اطلاعات کد موقتی
تا این مرحله، در اکشن متد Login کنترلر Account، اگر کاربر، اطلاعات هویتی خود را صحیح وارد کند، به سیستم وارد خواهد شد. برای لغو این عملکرد پیشفرض، کدهای HttpContext.SignInAsync آنرا حذف کرده و با Redirect به اکشن متد نمایش صفحهی ورود کد موقتی ارسال شدهی به آدرس ایمیل کاربر، جایگزین میکنیم.
namespace DNT.IDP.Controllers.Account { [SecurityHeaders] [AllowAnonymous] public class AccountController : Controller { [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginInputModel model, string button) { // ... if (ModelState.IsValid) { if (await _usersService.AreUserCredentialsValidAsync(model.Username, model.Password)) { var user = await _usersService.GetUserByUsernameAsync(model.Username); var id = new ClaimsIdentity(); id.AddClaim(new Claim(JwtClaimTypes.Subject, user.SubjectId)); await HttpContext.SignInAsync(scheme: Startup.TwoFactorAuthenticationScheme, principal: new ClaimsPrincipal(id)); await _twoFactorAuthenticationService.SendTemporaryCodeAsync(user.SubjectId); var redirectToAdditionalFactorUrl = Url.Action("AdditionalAuthenticationFactor", new { returnUrl = model.ReturnUrl, rememberLogin = model.RememberLogin }); // request for a local page if (Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(redirectToAdditionalFactorUrl); } if (string.IsNullOrEmpty(model.ReturnUrl)) { return Redirect("~/"); } // user might have clicked on a malicious link - should be logged throw new Exception("invalid return URL"); } await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); } // something went wrong, show form with error var vm = await BuildLoginViewModelAsync(model); return View(vm); }
- سپس بر اساس Id این کاربر، یک ClaimsIdentity تشکیل میشود.
- در ادامه با فراخوانی متد SignInAsync بر روی این ClaimsIdentity، یک کوکی رمزنگاری شده را با scheme تعیین شده که با authenticationScheme تنظیم شدهی در کلاس آغازین برنامه تطابق دارد، ایجاد میکنیم.
await HttpContext.SignInAsync(scheme: Startup.TwoFactorAuthenticationScheme, principal: new ClaimsPrincipal(id));
public interface ITwoFactorAuthenticationService { Task SendTemporaryCodeAsync(string subjectId); Task<bool> IsValidTemporaryCodeAsync(string subjectId, string code); }
- متد IsValidTemporaryCodeAsync، کد دریافت شدهی از کاربر را با نمونهی موجود در بانک اطلاعاتی مقایسه و اعتبار آنرا اعلام میکند.
ایجاد اکشن متد AdditionalAuthenticationFactor و View مرتبط با آن
پس از ارسال کد موقتی به کاربر، کاربر را به صورت خودکار به اکشن متد جدید AdditionalAuthenticationFactor هدایت میکنیم تا این کد موقتی را که به صورت ایمیل (و یا در اینجا با مشاهدهی لاگ برنامه)، دریافت کردهاست، وارد کند. همچنین returnUrl را نیز به این اکشن متد جدید ارسال میکنیم تا بدانیم پس از ورود موفق کد موقتی توسط کاربر، او را باید در ادامهی این گردش کاری به کجا هدایت کنیم. بنابراین قسمت بعدی کار، ایجاد این اکشن متد و تکمیل View آن است.
ViewModel ای که بیانگر ساختار View مرتبط است، چنین تعریفی را دارد:
using System.ComponentModel.DataAnnotations; namespace DNT.IDP.Controllers.Account { public class AdditionalAuthenticationFactorViewModel { [Required] public string Code { get; set; } public string ReturnUrl { get; set; } public bool RememberLogin { get; set; } } }
سپس اکشن متد AdditionalAuthenticationFactor در حالت Get، این View را نمایش میدهد و در حالت Post، اطلاعات آنرا از کاربر دریافت خواهد کرد:
namespace DNT.IDP.Controllers.Account { public class AccountController : Controller { [HttpGet] public IActionResult AdditionalAuthenticationFactor(string returnUrl, bool rememberLogin) { // create VM var vm = new AdditionalAuthenticationFactorViewModel { RememberLogin = rememberLogin, ReturnUrl = returnUrl }; return View(vm); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> AdditionalAuthenticationFactor( AdditionalAuthenticationFactorViewModel model) { if (!ModelState.IsValid) { return View(model); } // read identity from the temporary cookie var info = await HttpContext.AuthenticateAsync(Startup.TwoFactorAuthenticationScheme); var tempUser = info?.Principal; if (tempUser == null) { throw new Exception("2FA error"); } // ... check code for user if (!await _twoFactorAuthenticationService.IsValidTemporaryCodeAsync(tempUser.GetSubjectId(), model.Code)) { ModelState.AddModelError("code", "2FA code is invalid."); return View(model); } // login the user AuthenticationProperties props = null; if (AccountOptions.AllowRememberLogin && model.RememberLogin) { props = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; } // issue authentication cookie for user var user = await _usersService.GetUserBySubjectIdAsync(tempUser.GetSubjectId()); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); await HttpContext.SignInAsync(user.SubjectId, user.Username, props); // delete temporary cookie used for 2FA await HttpContext.SignOutAsync(Startup.TwoFactorAuthenticationScheme); if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } return Redirect("~/"); }
- فراخوانی HttpContext.SignInAsync با اسکیمای مشخص شده، یک کوکی رمزنگاری شده را در اکشن متد Login ایجاد میکند. اکنون در اینجا با استفاده از متد HttpContext.AuthenticateAsync و ذکر همان اسکیما، میتوانیم به محتوای این کوکی رمزنگاری شده دسترسی داشته باشیم و از طریق آن، Id کاربر را استخراج کنیم.
- اکنون که این Id را داریم و همچنین Code موقتی نیز از طرف کاربر ارسال شدهاست، آنرا به متد IsValidTemporaryCodeAsync که پیشتر در مورد آن توضیح دادیم، ارسال کرده و اعتبارسنجی میکنیم.
- در آخر این کوکی رمزنگاری شده را با فراخوانی متد HttpContext.SignOutAsync، حذف و سپس یک کوکی جدید را بر اساس اطلاعات هویت کاربر، توسط متد HttpContext.SignInAsync ایجاد و ثبت میکنیم تا کاربر بتواند بدون مشکل وارد سیستم شود.
View متناظر با آن نیز در فایل src\IDP\DNT.IDP\Views\Account\AdditionalAuthenticationFactor.cshtml، به صورت زیر تعریف شدهاست تا کد موقتی را به همراه آدرس بازگشت پس از ورود آن، به سمت سرور ارسال کند:
@model AdditionalAuthenticationFactorViewModel <div> <div class="page-header"> <h1>2-Factor Authentication</h1> </div> @Html.Partial("_ValidationSummary") <div class="row"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Input your 2FA code</h3> </div> <div class="panel-body"> <form asp-route="Login"> <input type="hidden" asp-for="ReturnUrl" /> <input type="hidden" asp-for="RememberLogin" /> <fieldset> <div class="form-group"> <label asp-for="Code"></label> <input class="form-control" placeholder="Code" asp-for="Code" autofocus> </div> <div class="form-group"> <button class="btn btn-primary">Submit code</button> </div> </fieldset> </form> </div> </div> </div> </div>
آزمایش برنامه جهت بررسی اعتبارسنجی دو مرحلهای
پس از طی این مراحل، اعتبارسنجی دو مرحلهای در برنامه فعال شدهاست. اکنون برای آزمایش آن، برنامهها را اجرا میکنیم. پس از لاگین، صفحهی زیر نمایش داده میشود:
همچنین کد موقتی این مرحله را نیز در لاگهای برنامه مشاهده میکنید:
پس از ورود آن، کار اعتبارسنجی نهایی آن انجام شده و سپس بلافاصله به برنامهی MVC Client هدایت میشویم.
اضافه کردن اعتبارسنجی دو مرحلهای به قسمت ورود از طریق تامین کنندههای هویت خارجی
دقیقا همین مراحل را نیز به اکشن متد Callback کنترلر ExternalController اضافه میکنیم. در این اکشن متد، تا قسمت کدهای مشخص شدن user آن که از اکانت خارجی وارد شدهاست، با قبل یکی است. پس از آن تمام کدهای لاگین شخص به برنامه را از اینجا حذف و به اکشن متد جدید AdditionalAuthenticationFactor در همین کنترلر منتقل میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.
معرفی کامپوننت EasyQuery
از کامپوننت EasyQuery برای ارائه یک رابط کاربر پسند (UserFriendly) جهت جستجو و فیلترینگ پیشرفته بر روی اطلاعات توسط کاربران استفاده میشود. این کامپوننت به کاربران اجازه میدهد تا اطلاعاتی را که نیاز دارند به راحتی و بدون نیاز به کمک شما یا تیم پشتیبانی، فراهم نمایند.
نسخه آزمایشی گزارش گیری ساخته شده با EasyQuery تحت وب
نسخههای آزمایشی دیگر بر روی ASP.NET MVC و Silverlight و ASP.NET AJAX و Win Forms
دانلود کدهای نسخه آزمایشی تحت وب
private void DecrementValue() { //Value--; <-- Don't do this // Do this instead: ValueChanged.InvokeAsync(Value - 1); }
اطلاعات بیشتر و همچنین
پیشنیازها
- مطالعهی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامههای آن، پیش از مطالعهی این مطلب ضروری است.
- همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کردهاید و با نحوهی برپایی یک برنامهی MVC آن و ارائهی فایلهای استاتیک توسط یک پروژهی ASP.NET Core آشنایی دارید.
ایجاد یک پروژهی جدید ASP.NET Core در VS 2017
در ابتدا یک پروژهی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
- ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینهی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
- در صفحهی بعدی آن هم گزینهی «empty template» را انتخاب نمائید.
تنظیمات یک برنامهی ASP.NET Core خالی برای اجرای یک برنامهی Angular CLI
برای اجرای یک برنامهی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامهی ASP.NET Core کلیک راست کرده و گزینهی Edit آنرا انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:
الف) درخواست عدم کامپایل فایلهای TypeScript
<PropertyGroup> <TargetFramework>netcoreapp1.1</TargetFramework> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> </PropertyGroup>
ب) مشخص کردن پوشههایی که باید الحاق و یا حذف شوند
<ItemGroup> <Folder Include="Controllers\" /> <Folder Include="wwwroot\" /> </ItemGroup> <ItemGroup> <Compile Remove="node_modules\**" /> <Content Remove="node_modules\**" /> <EmbeddedResource Remove="node_modules\**" /> <None Remove="node_modules\**" /> </ItemGroup> <ItemGroup> <Compile Remove="src\**" /> <Content Remove="src\**" /> <EmbeddedResource Remove="src\**" /> </ItemGroup>
سپس دو پوشهی node_modules و src واقع در ریشهی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کردهایم. پوشهی node_modules وابستگیهای Angular را به همراه دارد و src همان پوشهی برنامهی Angular ما خواهد بود.
ج) افزودن وابستگیهای سمت سرور مورد نیاز
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> </ItemGroup> <ItemGroup> <!-- extends watching group to include *.js files --> <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" /> </ItemGroup>
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شدهاند که در ادامهی بحث به آن اشاره خواهد شد.
افزودن یک کنترلر Web API جدید
با توجه به اینکه دیگر در اینجا قرار نیست با فایلهای cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائهی Web API و همچنین فایلهای Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفتهاست.
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; namespace ASPNETCoreIntegrationWithAngularCLI.Controllers { [Route("api/[controller]")] public class ValuesController : Controller { // GET: api/values [HttpGet] [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] public IEnumerable<string> Get() { return new string[] { "Hello", "DNT" }; } } }
تنظیمات فایل آغازین یک برنامهی ASP.NET Core جهت ارائهی برنامههای Angular
در ادامه به فایل Startup.cs برنامهی خالی جاری، مراجعه کرده و آنرا به نحو ذیل تغییر دهید:
using System; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace ASPNETCoreIntegrationWithAngularCLI { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) { context.Request.Path = "/index.html"; await next(); } }); app.UseMvcWithDefaultRoute(); app.UseDefaultFiles(); app.UseStaticFiles(); } } }
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامههای Angular را مشاهده میکنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال میشود و چون چنین صفحهای در سمت سرور وجود ندارد، خطای 404 بازگشت داده میشود و کار به پردازش برنامهی Angular نخواهد رسید. اینجا است که تنظیم میانافزار فوق، کار مدیریت خروجیهای 404 را بر عهده گرفته و کاربر را به فایل index.html برنامهی تک صفحهای وب، هدایت میکند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کنندهی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحهی index.html معاف خواهد شد.
ایجاد ساختار اولیهی برنامهی Angular CLI در داخل پروژهی جاری
اکنون از طریق خط فرمان به پوشهی ریشهی برنامهی ASP.NET Core، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
>ng new ClientApp --routing --skip-install --skip-git --skip-commit
پس از تولید ساختار برنامهی Angular CLI، به پوشهی آن وارد شده و تمام فایلهای آن را Cut کنید. سپس به پوشهی ریشهی برنامهی ASP.NET Core جاری، وارد شده و این فایلها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژههای Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایلهای تنظیمات آنرا میشناسد و قابلیت پردازش آنها را دارد.
پس از این cut/paste، پوشهی خالی ClientApp را نیز حذف کنید.
تنظیم محل خروجی نهایی Angular CLI به پوشهی wwwroot
برای اینکه سیستم Build پروژهی Angular CLI جاری، خروجی خود را در پوشهی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آنرا به wwwroot تنظیم کنیم:
"apps": [ { "root": "src", "outDir": "wwwroot",
فراخوانی کنترلر Web API برنامه در برنامهی Angular CLI
در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core'; import { Http } from '@angular/http'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { constructor(private _httpService: Http) { } apiValues: string[] = []; ngOnInit() { this._httpService.get('/api/values').subscribe(values => { this.apiValues = values.json() as string[]; }); } }
سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1> <ul *ngFor="let value of apiValues"> <li>{{value}}</li> </ul> <router-outlet></router-outlet>
نصب وابستگیهای برنامهی Angular CLI
در ابتدای ایجاد پوشهی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمامتر، ایجاد شود. اکنون برای نصب وابستگیهای آن یا میتوان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینهی restore packages ظاهر میشود. و یا میتوان به روش متداول این نوع پروژهها، از طریق خط فرمان به پوشهی ریشهی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارشهای این ابزار که بر روی سیستم نصب شدهاست، استفاده میکنید.
روش اول اجرای برنامههای مبتنی بر ASP.NET Core و Angular CLI
تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشدهاست.
بنابراین روش اول اجرای این نوع برنامهها، شامل مراحل ذیل است:
الف) ساخت پروژهی Angular CLI در حالت watch
> ng build --watch
ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشهی wwwroot برنامه انجام شدهاست، میتوان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:
یک نکته: میتوان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build"> <Exec Command="ng build" /> </Target>
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده میشود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.
روش دوم اجرای برنامههای مبتنی بر ASP.NET Core و Angular CLI
روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامههای NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آنها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگیهای برنامه اضافه کردیم.
الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشهی پروژه، صادر کنید:
> dotnet watch run
>dotnet watch run [90mwatch : [39mStarted Hosting environment: Production Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down.
ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شدهاست، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو سادهتری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامهی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده میشود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامهی Angular ما، یک چنین درخواستهایی را به سمت سرور، جهت دریافت اطلاعات ارسال میکند: localhost:4200/api
برای رفع این مشکل میتوان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{ "/api": { "target": "http://localhost:5000", "secure": false } }
>ng serve --proxy-config proxy.config.json -o
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.
نکته 1: بدیهی است میتوان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژهی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمیشود که آنرا نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمهی refresh پس از هر بار پایان کار ng build).
نکته 2: میتوان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی میخواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه میخواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شدهاست. عدد این پورت را میتوان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شدهی در این مطلب، میتوان از آنها استفاده کرد:
- فایل restore.bat کار بازیابی و نصب وابستگیهای پروژهی دات نتی و همچنین Angular CLI را انجام میدهد.
- دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائهی نهایی.
- دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کنندهی قسمتهای «الف» و «ب» روش دوم هستند.
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script> <link href="~/content/css/bootstrap-switch.min.css" rel=stylesheet"></link> <script src="~/Scripts/bootstrap-switch.min.js"></script>
نکته مهم: فایل css شامل دو نسخه هست که یکی از آن برای Bootstrap2 و دیگری برای نسخه 3 آن است که نسبت به آن نسخه، استایل مناسب را انتخاب کنید.
var model=BootstrapSwitchModel(); //وضعیت فعال بودن و غیرفعال بودن سوئیچ model.Checked=true; //اندازه آن model.Size=BootstrapSize..normal; //یک انیمیشن ساده موقع سوئیچ کردن دارد model.Animate=true; //به چک باکس عادی تبدیل میشود model.Disabled=true; //غیرفعال شده و به صورت فقط خواندنی قابل دسترس است model.Readonly=true; //رنگ قعال بودن model.OnColor=BootstrapColor.Success; //رنگ غیرفعال بودن model.OffColor=BootstrapColor.Danger; //متن نمایشی در هنگام فعال بودن model.OnText="On"; //متن نمایشی در حالت عدم انتخاب model.OffText="Off"; //بین دو حالت روشن و خاموش نمایش داده میشود model.label="Public Display"; //تعیین میزان اندازه برچسب بالا model.LabelWidth=100; //سوئیچ به صورت آینه ای معکوس میشود model.Inverse=false; //کلاسی جهت تغییر استایل سوئیچ model.BaseClass="myclass"; //تعیین کلاس برای تگ اصلی پدر model.WrapperClass="wclass"; //فقط یکی از چند سوئیچ میتواند فعال باشد model.RadioAllOff=false; //یک سوئیچ در حالت عادی فقط یکی از //وضعیتها را نمایش میده ولی در این حالت //سوئیچ در ابتدا بین این دو وضعیت گیر کرده است model.Indeterminate=true; //اندازه سمت چپ و راست سوئیچ model.HandleWidth=25;
@{ var model=BootstrapSwitchModel(); ....} @HTML.BootstrapSwitch("id",model);
بررسی Select For XML
پیشنیاز بحث
در ادامه، از بانک اطلاعاتی معروف northwind برای تهیه کوئریها استفاده خواهیم کرد. بنابراین فرض بر این است که این بانک اطلاعاتی را پیشتر به وهلهی جاری SQL Server خود افزودهاید.
بررسی FOR XML RAW
از نگارش 2005 به بعد، Select for XML علاوه بر خروجی متنی XML، توانایی تولید خروجی از نوع XML را نیز یافته است. در ادامه 4 حالت مختلف خروجی آنرا بررسی خواهیم کرد.
SELECT Customers.CustomerID, Orders.OrderID FROM Customers, Orders WHERE Customers.CustomerID = Orders.CustomerID ORDER BY Customers.CustomerID FOR XML RAW
<row CustomerID="ALFKI" OrderID="10643" /> <row CustomerID="ALFKI" OrderID="10692" />
برای تغییر حالت خروجی آن میتوان از حالت ELEMENTS استفاده کرد:
SELECT Customers.CustomerID, Orders.OrderID FROM Customers, Orders WHERE Customers.CustomerID = Orders.CustomerID ORDER BY Customers.CustomerID FOR XML RAW, ELEMENTS
<row> <CustomerID>ALFKI</CustomerID> <OrderID>10643</OrderID> </row>
حالت پیشرفتهتر FOR XML RAW را در ادامه ملاحظه میکنید:
SELECT Customers.CustomerID, Orders.OrderID FROM Customers, Orders WHERE Customers.CustomerID = Orders.CustomerID ORDER BY Customers.CustomerID FOR XML RAW('Customer'), ELEMENTS XSINIL, ROOT('Customers'), XMLSCHEMA('http://MyCustomers')
<Customers> <Customer xmlns="http://MyCustomers"> <CustomerID>ALFKI</CustomerID> <OrderID>10643</OrderID> </Customer>
ذکر XMLSCHEMA، سبب میشود تا SQL Server به صورت خودکار XML Schema را بر اساس اطلاعات ستونهای رابطهای مورد استفاده تولید کند.
این نکات را برای FOR XML AUTO نیز میتوان بکار برد.
بررسی FOR XML AUTO
حالت دوم بکارگیری Select for XML به همراه عبارت Auto است:
SELECT Customers.CustomerID, Orders.OrderID FROM Customers, Orders WHERE Customers.CustomerID = Orders.CustomerID ORDER BY Customers.CustomerID FOR XML AUTO, ELEMENTS
<Customers> <CustomerID>ALFKI</CustomerID> <Orders> <OrderID>10643</OrderID> </Orders> <Orders> <OrderID>10692</OrderID> </Orders> </Customers>
بررسی For XML Explicit
اگر بخواهیم خروجی را تبدیل به ترکیبی از المانها و ویژگیها کنیم، میتوان از For XML Explicit استفاده کرد:
SELECT 1 AS Tag, NULL AS Parent, Customers.CustomerID AS [Customers!1!CustomerID], NULL AS [Order!2!OrderId] FROM Customers UNION ALL SELECT 2, 1, Customers.CustomerID, Orders.OrderID FROM Customers, Orders WHERE Customers.CustomerID = Orders.CustomerID ORDER BY [Customers!1!CustomerID] FOR XML EXPLICIT
<Customers CustomerID="ALFKI"> <Order OrderId="10643" /> <Order OrderId="10692" /> <Order OrderId="10702" /> <Order OrderId="10835" /> <Order OrderId="10952" /> <Order OrderId="11011" /> </Customers>
به علاوه دو ستون اضافی Tag و Parent نیز باید ذکر شوند. از این دو برای مشخص سازی سلسه مراتب استفاده میشوند.
!1! سبب تولید یک ویژگی در سطح اول میشود و !2! سبب تولید ویژگی دیگری در سطح دوم.
بررسی FOR XML PATH
همانطور که مشاهده میکنید، نوشتن FOR XML EXPLICIT نسبتا طولانی و پیچیدهاست. برای ساده سازی آن از نگارش 2005 به بعد، روش For XML Path معرفی شدهاست:
WITH XMLNAMESPACES('http://somens' AS au) SELECT CustomerID AS [@au:CustomerID], CompanyName AS [Company/Name], ContactName AS [Contact/Name] FROM Customers FOR XML PATH('Customer')
<Customer xmlns:au="http://somens" au:CustomerID="ALFKI"> <Company> <Name>Alfreds Futterkiste</Name> </Company> <Contact> <Name>Maria Anders</Name> </Contact> </Customer>
یک نکته: اگر کوئری FOR XML PATH را اجرا کنید، نام ستون خروجی به صورت خودکار به XML_F5..6B تنظیم میشود. علت اینجا است که در حالت پیش فرض، نوع خروجی این افزونه، استریم است و نه XML. برای تبدیل آن به نوع XML باید یک Type را اضافه کرد:
FOR XML PATH('Customer'), Type