علت اینجا است که فعلا پوشهی router، فاقد پوشهی bundles است. تمام اجزای نگارش 2 دارای پوشههای bundles هستند (مانند node_modules\@angular\common\bundles)، منهای پوشهی router. همچنین در فایل systemjs.config.js یک چنین تعریفی هست:
function packUmd(pkgName) { packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' }; };
var setPackageConfig = /*System.packageWithIndex ? packIndex : packUmd;*/packIndex; // **OR** method 2 ngPackageNames.forEach(setPackageConfig); packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' };
[BreezeController] public class AccountController : ApiController { ... }
module Interfaces { export interface IAuthService { user: Models.IUserToken getUserInfo(accessToken); login(data); logOut(); register(data); changePassword(data); accessToken(accessToken, data); } }
سرویس AuthService پیاده سازی اینترفیس IAuthService را برعهده دارد. در سازنده آن، وابستگیهای آن مقداردهی شدهاست و همچنین تنظیمات manager را انجام دادهایم.
"grant_type=password & username=myusername & password=mypassword";
var ajaxAdapter = breeze.config.getAdapterInstance("ajax"); breeze.ajaxpost(ajaxAdapter);
.withParameters({ $method: 'POST', $encoding: 'JSON', $data: newData }
module AdApps { var securityUrls = { site: '/', login: '/token', logout: 'logout', register: 'register', userInfo: 'getUserInfo', changePassword: 'changePassword', } export class AuthService implements Interfaces.IAuthService { private manager: breeze.EntityManager; constructor( private _breeze: typeof breeze, private $http: ng.IHttpProvider, private toaster: ngtoaster.IToasterService, private $location: ng.ILocationService) { var dataService = new _breeze.DataService({ serviceName: "/breeze/Account", hasServerMetadata: false }); var metadataStore = new _breeze.MetadataStore({ namingConvention: _breeze.NamingConvention.camelCase }); this.manager = new _breeze.EntityManager({ dataService: dataService, metadataStore: metadataStore, saveOptions: new _breeze.SaveOptions({ allowConcurrentSaves: true, tag: [{}] }) }); } user: Models.IUserToken; accessToken(accessToken, data): string { if (accessToken === 'clear') { localStorage.removeItem('accessToken'); delete this.$http.defaults.headers.common.Authorization; } else { window.localStorage.setItem("accessToken", accessToken); this.$http.defaults.headers.common.Authorization = 'Bearer ' + accessToken; } return accessToken; } getUserInfo(): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.userInfo); return this.manager.executeQuery(query).then(data => { return data.results[0]; }); } login(data: any): ng.IPromise<any> { var newData = "grant_type=password&username=" + data.userName + "&password=" + data.password; var query = this._breeze.EntityQuery.from(securityUrls.login) .withParameters({ $method: 'POST', $encoding: 'JSON', $data: newData }); return this.manager.executeQuery(query).then(data => { var self = this; var result = data.results[0] as any; self.accessToken(result.access_token, data.results[0]); self.user = <Models.IUserToken>{}; self.user = <Models.IUserToken>result; return result; }); } logOut(): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.logout) .withParameters({ $method: 'POST', $encoding: 'JSON', }); return this.manager.executeQuery(query).then(data => { this.user = null; this.accessToken('clear', null); this.$location.path("/"); }); } register(data: Object): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.register) .withParameters({ $method: 'POST', $encoding: 'JSON', $data: data }); return this.manager.executeQuery(query).then(data => { }); } changePassword(data: Object): ng.IPromise<any> { var query = this._breeze.EntityQuery.from(securityUrls.changePassword) .withParameters({ $method: 'POST', $encoding: 'JSON', $data: data }); return this.manager.executeQuery(query).then(data => { }); } } }
سرویس HttpInterceptor : رهگیری و پیگیری کردن نتیجه درخواستهای http را بر عهده دارد.
module AdApps { export class HttpInterceptor { private static _toaster: ngtoaster.IToasterService; private static _$q: ng.IQService; constructor( private $q: ng.IQService, private toaster: ngtoaster.IToasterService, private $location: ng.ILocationService) { HttpInterceptor._toaster = toaster; HttpInterceptor._$q = $q; } request(config): string { config.headers = config.headers || {}; var authData = window.localStorage.getItem("accessToken"); if (authData) { config.headers.Authorization = "Bearer " + authData; } return config; }; response(response): ng.IPromise<any> { if (response.data && response.data.message && response.status === 200) { HttpInterceptor._toaster.success(response.data.message) } return HttpInterceptor._$q.resolve(response); }; responseError(response): ng.IPromise<any> { var self = this; var data = response.data; var title = "خطا"; var messages = []; if (data) { if (data.error) { title = data.error; } if (data.message) { messages.push(data.message); } if (data.Message) { messages.push(data.Message); } if (data.ModelState) { angular.forEach(data.ModelState, function (errors, key) { if (key.substr(0, 1) != "$") { messages.push(errors); } }); } if (data.exceptionMessage) { messages.push(data.exceptionMessage); } if (data.ExceptionMessage) { messages.push(data.ExceptionMessage); } if (data.error_description) { messages.push(data.error_description); } if (messages.length > 0) { HttpInterceptor._toaster.error(title, messages.join("<br/>")); } if (response.status === "401") { self.$location.path("/ورود"); } } return HttpInterceptor._$q.reject(response); } } }
معرفی کردن مسیرهای ورود، ثبت نام و تغییر رمز عبور به انگولار
module AdApps { class SecurityCtrl { constructor(private $scope: Interfaces.IAuthScope, private authService: AuthService) { $scope.authService = authService; if (window.localStorage.getItem("accessToken") != null) { authService.getUserInfo().then(function (data) { $scope.authService.user = data; }); } $scope.logOut = function () { return authService.logOut().then(function () { }); } } } define(["angularAmd", "angular", "factory/AuthService", "factory/httpInterceptor"], (angularAmd, ng) => { angularAmd = angularAmd.__proto__; var app = ng.module("AngularTypeScript", ['ngRoute', 'breeze.angular', 'toaster']); var viewPath = "app/views/"; var controllerPath = "app/controller/"; app.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $httpProvider.interceptors.push("HttpInterceptor"); $routeProvider .when("/", angularAmd.route({ templateUrl: viewPath + "home.html", controllerUrl: controllerPath + "home.js" })) .when("/login", angularAmd.route({ templateUrl: viewPath + "login.html", controllerUrl: controllerPath + "login.js" })) .when("/register", angularAmd.route({ templateUrl: viewPath + "register.html", controllerUrl: controllerPath + "register.js" })) .when("/changePassword", angularAmd.route({ templateUrl: viewPath + "change-password.html", controllerUrl: controllerPath + "changePassword.js" })) .otherwise({ redirectTo: '/' }); } ]); app.service('AuthService', ['breeze', '$http', 'toaster', '$location', AuthService]); app.service("HttpInterceptor", ["$q", "toaster", "$location", HttpInterceptor]); app.controller('SecurityCtrl', ['$scope', 'AuthService', SecurityCtrl]); return angularAmd.bootstrap(app); }); }
ایجاد کنترلر .login.ts و ارسال سرویسهای لازم به کلاس LoginCtrl
در صورت صحیح بودن نام کاربری و رمز عبور به صفحه اصلی هدایت خواهد شد.
module AdApps { define(['app'], function (app) { app.controller('LoginCtrl', ["$scope", "AuthService", "$location", LoginCtrl]); }); export class LoginCtrl { constructor($scope: Interfaces.ILoginScope, authService: AuthService, $location: ng.ILocationService) { $scope.submit = function () { authService.login(angular.copy($scope.form)) .then(function (data) { this.$location.path("/"); }) }; } } }
ایجاد login.html
<div ng-controller="LoginCtrl"> <div> <i></i> <span>ورود</span> <div> <div> </div> </div> </div> <div> <div> <div> <form name="Form" id="form1"> <fieldset> <div> <div> <input name="username" ng-model="form.userName" placeholder="نام کاربری" required> <span> <i></i> </span> </div> </div> <div> <div> <input name="password" type="password" ng-model="form.password" placeholder="{{'Password'}}" validator="required"> <span> <i></i> </span> </div> </div> </fieldset> <div> <button type="submit" ng-click="submit()">ورود</button> </div> </form> </div> </div> </div> </div>
requirejs.config({ paths: { "app": "app", "angularAmd": "/Scripts/angularAmd", "angular": "/Scripts/angular", "breezeAjaxpost": "/Scripts/breeze/breeze.ajaxpost", "breeze": "/Scripts/breeze/breeze.debug", "breezeAngular": "/Scripts/breeze/breeze.angular", "bootstrap": "/Scripts/bootstrap", "angularRoute": "/Scripts/angular-route", "jquery": "/Scripts/jquery-2.2.2", "entityManagerService": "factory/entityManagerService", "toaster": "/Scripts/toaster", }, waitSeconds: 0, shim: { "angular": { exports: "angular" }, "angularRoute": { deps: ["angular"] }, "bootstrap": { deps: ["jquery"] }, "breeze": { deps: ["jquery"] }, "breezeAngular": { deps: ["angular", "breeze"] }, "toaster": { deps: ["angular"] }, "app": { deps: ["bootstrap", "angularRoute", "toaster", "breezeAngular", "breezeAjaxpost"] } } }); require(["app"]);
- ViewUsers
- CreateUser
- EditUser
- DeleteUser
همانطور که مشاهده میکنید، المنتهایی در صفحه وجود دارند که کاربر X نباید آنها را مشاهده کند. از جمله دکمه حذف کاربر و دکمه ایجاد کاربر. برای مخفی کردن آنها چه راهحلی را میتوان ارائه داد؟ شاید بخواهید برای اینکار از ngIf* استفاده کنید. برای اینکار کافی است دست بکار شوید تا مشکلاتی را که در این روش به آنها بر میخورید، متوجه شوید. از جمله این مشکلات میتوان به پیچیدگی بسیار زیاد و وجود کدهای تکراری در هر کامپوننت اشاره کرد (در بهترین حالت هر کامپوننت باید سرویس حاوی دسترسیها را در خود تزریق کرده و با استفاده از توابعی، وجود یا عدم وجود دسترسی را بررسی کند).
راهحل بهتر، استفاده از یک Directive سفارشی است. همچنین ماژول Ng2Permission علاوه بر فراهم کردن Directive جهت مدیریت المنتهای روی صفحه، امکاناتی را جهت نگهداری و تعریف دسترسیهای جدید و همچنین محافظت از Routeها فراهم کرده است. این ماژول الهام گرفته از ماژول angular-permission میباشد.
کافی است با استفاده از دستور زیر این ماژول را نصب کنید:
npm install angular2-permission --save
بعد از نصب، ماژول Ng2Permission را در قسمت imports در ماژول اصلی برنامه، اضافه کنید.
import { Ng2Permission } from 'angular2-permission'; @NgModule({ imports: [ Ng2Permission ] })
مدیریت دسترسیها
توضیحات | امضاء |
تعریف دسترسیهای جدید | define(permissions: Array<string>): void |
افزودن دسترسی جدید | add(permission: string ) : void |
حذف دسترسی مشخص شده | remove(permission: string ) : void |
برسی اینکه دسترسی قبلا تعریف شده است؟ | hasDefined( permission : string ) : boolean |
برسی اینکه حداقل یکی از دسترسیهای ورودی قبلا تعریف شده است؟ | hasOneDefined(permissions: Array < string > ) : boolean |
حذف تمامی دسترسیها | clearStore( ) : void |
دریافت تمامی دسترسیهای تعریف شده | get store ( ) : Array < string> |
Emitter جهت تغییر در دسترسیها | get permissionStoreChangeEmitter ( ) : EventEmitter< an y> |
برای مثال جهت تعریف دسترسیهای جدید، کافی است سرویس PermissionService را تزریق کرده و با استفاده از متدهای define اقدام به اینکار کنید:
import { PermissionService } from 'angular2-permission'; @Component({ […] }) export class LoginComponent implements OnInit { constructor(private _permissionService: PermissionService) { this._permissionService.define(['ViewUsers', 'CreateUser', 'EditUser', 'DeleteUser']); } }
آنچه که واضح است، این است که لیست دسترسیها میتوانند از سمت سرور تامین شوند.
محافظت از مسیرهای تعریف شده
import { PermissionGuard, IPermissionGuardModel } from 'angular2-permission'; […] const routes: Routes = [ { path: 'login', component: LoginComponent, children: [] }, { path: 'users', component: UserListComponent, canActivate: [PermissionGuard], data: { Permission: { Only: ['ViewUsers'], RedirectTo: '403' } as IPermissionGuardModel }, children: [] }, { path: 'users/create', component: UserCreateComponent, canActivate: [PermissionGuard], data: { Permission: { Only: ['CreateUser'], RedirectTo: '403' } as IPermissionGuardModel }, children: [] }, { path: '403', component: AccessDeniedComponent, children: [] } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
توضیحات | خصوصیت |
فقط دسترسیهای تعریف شده در این قسمت، امکان پیمایش به این مسیر را خواهند داشت. | Only |
تمامی دسترسیهای تعریف شده، به جز دسترسیهای تعریف شده در این قسمت، امکان پیمایش به این مسیر را خواهند داشت. | Except |
در صورتیکه تقاضای غیر مجازی جهت پیمایش به این مسیر صادر شد، کاربر را به این مسیر هدایت میکند. | RedirectTo |
{ path: 'users/create', component: UserCreateComponent, canActivate: [PermissionGuard], data: { Permission: { Only: ['Admin', 'CreateUser'], RedirectTo: '403' } as IPermissionGuardModel }, children: [] }
محافظت از المنتهای صفحه
توضیحات | Input type | Directive |
فقط دسترسیهای تعریف شده در این دایرکتیو امکان مشاهده (به صورت پیش فرض) المنت را دارند. | Arrayy<string> | hasPermission |
تمامی دسترسیهای موجود، به جز دسترسیهای تعریف شده در این دایرکتیو امکان مشاهده (به صورت پیش فرض) المنت را دارند. | Array<string> | exceptPermission |
استراتژی برخورد با المنت، هنگامیکه کاربر دسترسی به المنت دارد. | string | Function | onAuthorizedPermission |
استراتژی برخورد با المنت، هنگامیکه کاربر دسترسی به المنت را ندارد. | string | Function | onUnauthorizedPermission |
<button type="button" [hasPermission]="['DeleteUser']"> <span aria-hidden="true"></span> Delete </button>
در صورتیکه بیش از یک دسترسی مد نظر باشد، با کاما از هم جدا خواهند شد.
<button type="button" [hasPermission]="[ 'Admin', 'DeleteUser']"> <span aria-hidden="true"></span> Delete </button>
<button type="button" [exceptPermission]="['GeustUser']"> <span aria-hidden="true"></span> Delete </button>
<button type="button" [hasPermission]="['GeustUser']" onAuthorizedPermission="enable" onUnauthorizedPermission="disable"> <span aria-hidden="true"></span> Delete </button>
رفتار | مقدار |
حذف خصوصیت disabled از المنت | enable |
افزودن خصوصیت disabled به المنت | disable |
تنظیم استایل display به inherit | show |
تنظیم استایل display به none | hide |
@Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css'] }) export class UserListComponent { constructor() { } OnAuthorizedPermission(element: ElementRef) { element.nativeElement.style.visibility ="inherit"; } OnUnauthorizedPermission(element: ElementRef) { element.nativeElement.style.visibility = "hidden"; } }
<button [hasPermission]="['CreateUser']" [onAuthorizedPermission]="OnAuthorizedPermission" [onUnauthorizedPermission]="OnUnauthorizedPermission"> <span aria-hidden="true"></span> Add New User </button>
EF Code First #2
اگر برنامه ما به شکلی باشد که ماژول پذیر باشد و در DLL جداگانه همانند این مثال یک ماژول با نام Blog داشته باشیم برای اضافه کردن به Context باید چگونه عمل نماییم؟
SELECT top 1 * FROM (EXEC MyProc) AS TEMP
context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").AsEnumerable().FirstOrDefault();
استفاده از Web API در ASP.NET Web Forms
- Web API یک بحث سمت سرور است. به آن به زبان ساده به چشم یک وب سرویس مدرن نگاه کنید. برای نمونه بجای وبمتدهای استاتیک صفحات aspx یا فایلهای ashx یا asmx و حتی سرویسهای WCF از نوع REST و امثال آن، بهتر است از Web API استفاده کنید.
- برای نمونه پایه مباحثی مانند Forms Authentication در اینجا هم کاربرد دارد (البته این یک نمونه است).
- برای کار با Web API الزاما نیازی به ASP.NET ندارید (نه وب فرمها و نه MVC)؛ به هیچکدام از نگارشهای آن. سمت کاربر آن AngularJS و سمت سرور آن Web API باشد. کار میکند. (اهمیت این مساله در اینجا است که الان میشود یک فریم ورک جدید توسعهی برنامههای وب را کاملا مستقل از وب فرمها و MVC طراحی کرد)
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
String.format = function () { var s = arguments[0]; for (var arg in arguments) { var i = parseInt(arg); s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
console.log(String.format("{0} is nice!", "donettips.info"));
donettips.info is nice!
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
donettips.info is very nice! {0} is {1} nice!
String.format = function () { var original = arguments[0], replaced; for (var i = 0; i < arguments.length - 1; i++) { replaced = ''; while (replaced != original) { original = replaced || original; replaced = original.replace("{" + i + "}", arguments[i + 1]); } } return replaced; };
donettips.info is very nice! donettips.info is very nice!
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]); } return s; };
String.format = function () { var s = arguments[0], i = arguments.length - 1; while (i--) { s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]); } return s; };
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
zero:0 {2}:1 two:2
zero:0 two:1 two:2
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
zero:0 one:1 one:2
zero:0 one:1 {1}:2
String.format = function () { var args = arguments; return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; }); };
console.log(String.format("{0} is {1} nice!", "donettips.info"));
donettips.info is undefined nice!
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/{(\d+)}/g, function (match, number) { var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}", "zero", "{2}", "two"));
zero:0 {2}:1 two:2, {zero} {{{2}}} {{{two}}} two
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
zero:0 {2}:1 two:2, {0} {{2}} {{2}} two
String.prototype.format = function () { ... }
String.prototype.format = function () { var s = this.toString(), args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } return typeof args[number] != 'undefined' ? args[number] : match; }); };
console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}".format("zero", "{2}", "two"));
String.format = function () { var s = arguments[0], args = arguments[1]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
String.prototype.format = function () { var s = this.toString(), args = arguments[0]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" })); console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
String.format = function String$format(format, args) { /// <summary locid="M:J#String.format" /> /// <param name="format" type="String"></param> /// <param name="args" parameterArray="true" mayBeNull="true"></param> /// <returns type="String"></returns> // var e = Function._validateParams(arguments, [ // { name: "format", type: String }, // { name: "args", mayBeNull: true, parameterArray: true } // ]); // if (e) throw e; return String._toFormattedString(false, arguments); }; String._toFormattedString = function String$_toFormattedString(useLocale, args) { var result = ''; var format = args[0]; for (var i = 0; ; ) { var open = format.indexOf('{', i); var close = format.indexOf('}', i); if ((open < 0) && (close < 0)) { result += format.slice(i); break; } if ((close > 0) && ((close < open) || (open < 0))) { if (format.charAt(close + 1) !== '}') { throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); } result += format.slice(i, close + 1); i = close + 2; continue; } result += format.slice(i, open); i = open + 1; if (format.charAt(i) === '{') { result += '{'; i++; continue; } if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); var brace = format.substring(i, close); var colonIndex = brace.indexOf(':'); var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1; if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid); var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1); var arg = args[argNumber]; if (typeof (arg) === "undefined" || arg === null) { arg = ''; } if (arg.toFormattedString) { result += arg.toFormattedString(argFormat); } else if (useLocale && arg.localeFormat) { result += arg.localeFormat(argFormat); } else if (arg.format) { result += arg.format(argFormat); } else result += arg.toString(); i = close + 1; } return result; }
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001)); // result: 100.00, ¤100.00, 10,000.01 %, 100.0001 console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45))); // result: 02/01/2015, 10:45
var template = jQuery.validator.format("{0} is not a valid value"); console.log(template("abc")); // result: 'abc is not a valid value'
String.format([full format string], [arguments...]); // or: [date|number].format([partial format string]);
// Object path String.format("Welcome back, {username}!", { id: 3, username: "JohnDoe" }); // Result: "Welcome back, JohnDoe!" // Date/time formatting String.format("The time is now {0:t}.", new Date(2009, 5, 1, 13, 22)); // Result: "The time is now 01:22 PM." // Date/time formatting (without using a full format string) var d = new Date(); d.format("hh:mm:ss tt"); // Result: "02:28:06 PM" // Custom number format string String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111); // Result: "Please call me at +46 (0) 111-11 11." // Another custom number format string String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346); // Result: "The last year result was -$5,543.35." // Alignment String.format("|{0,10:PI=0.00}|", Math.PI); // Result: "| PI=3.14|" // Rounding String.format("1/3 ~ {0:0.00}", 1/3); // Result: "1/3 ~ 0.33" // Boolean values String.format("{0:true;;false}", 0); // Result: "false" // Explicitly specified localization // (note that you have to include the .js file for used cultures) msf.setCulture("en-US"); String.format("{0:#,0.0}", 3641.667); // Result: "3,641.7" msf.setCulture("sv-SE"); String.format("{0:#,0.0}", 3641.667); // Result: "3 641,7"
//inline arguments String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value'); //returns: 'some string with first value and second value injected argument {number}' //single array String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]); //returns: 'some string with first value and second value injected using array {number}' //single object String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'}); //returns: 'some string with first value and second value injected using {propertyName}'