استفادهای که من از متد seed میکنم در عمل، تعریف قیودی مانند unique است با sql نویسی (داخل try/catch البته).
EF Code First #12
استفادهای که من از متد seed میکنم در عمل، تعریف قیودی مانند unique است با sql نویسی (داخل try/catch البته).
public interface IEnumerator<out T> : IEnumerator, IDisposable { // // Summary: // Gets the element in the collection at the current position of the enumerator. // // Returns: // The element in the collection at the current position of the enumerator. T Current { get; } }
Error CS1579 foreach statement cannot operate on variables of type ‘IEnumerator’ because ‘IEnumerator’ does not contain a public instance or extension definition for ‘GetEnumerator’
static class Extensions { public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator; }
class Program { void Main() { var enumerator = Enumerable.Range(0, 10).GetEnumerator(); foreach (var item in enumerator) { Console.WriteLine(item); } } }
static class Extensions { public static IAsyncEnumerator<T> GetAsyncEnumerator<T>(this IAsyncEnumerator<T> enumerator) => enumerator; } class Program { static async Task Main() { var enumerator = GetAsyncEnumerator(); await foreach (var item in enumerator) { Console.WriteLine(item); } } static async IAsyncEnumerator<int> GetAsyncEnumerator() { yield return 0; await Task.Delay(1); yield return 1; } }
class Program { static void Main() { foreach (var item in (1, 2, 3)) { Console.WriteLine(item); } } }
foreach statement cannot operate on variables of type '(int, int, int)' because '(int, int, int)' does not contain a public instance or extension definition for 'GetEnumerator' [CS9Features]csharp(CS1579)
static class Extensions { public static IEnumerator<object> GetEnumerator<T1, T2, T3>(this ValueTuple<T1, T2, T3> tuple) { yield return tuple.Item1; yield return tuple.Item2; } }
<!-- Required for routing --> <script src="~/node_modules/angular2/bundles/router.dev.js"></script>
<!DOCTYPE html> <html> <head> <base href="/">
//same as before ... import { ROUTER_PROVIDERS } from 'angular2/router'; //same as before ... @Component({ //same as before … providers: [ ProductService, HTTP_PROVIDERS, ROUTER_PROVIDERS ] }) //same as before ...
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> </div>
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; }
{ path: '/products', name: 'Products', component: ProductListComponent },
//same as before … import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router'; //same as before … @Component({ //same as before … }) @RouteConfig([ { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true }, { path: '/products', name: 'Products', component: ProductListComponent } ]) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/home/welcome.component.html' }) export class WelcomeComponent { public pageTitle: string = "Welcome"; }
<div class="panel panel-primary"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <h3 class="text-center">Default page</h3> </div> </div>
import { WelcomeComponent } from './home/welcome.component';
@Component({ //same as before … template: ` <div> <nav class='navbar navbar-default'> <div class='container-fluid'> <a class='navbar-brand'>{{pageTitle}}</a> <ul class='nav navbar-nav'> <li><a [routerLink]="['Welcome']">Home</a></li> <li><a [routerLink]="['Products']">Product List</a></li> </ul> </div> </nav> <div class='container'> <router-outlet></router-outlet> </div> </div> `, //same as before … })
import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
directives: [ROUTER_DIRECTIVES],
//same as before … import { ProductDetailComponent } from './products/product-detail.component'; @Component({ //same as before … }) @RouteConfig([ //same as before … { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent } ]) //same as before …
<td>{{ product.productName }}</td>
<td> <a [routerLink]="['ProductDetail', {id: product.productId}]"> {{product.productName}} </a> </td>
import { Component, OnInit } from 'angular2/core'; import { ROUTER_DIRECTIVES } from 'angular2/router'; //same as before … @Component({ //same as before … directives: [StarComponent, ROUTER_DIRECTIVES] })
import { Component } from 'angular2/core'; import { RouteParams } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } }
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> <div class='panel-footer'> <a class='btn btn-default' (click)='onBack()' style='width:80px'> <i class='glyphicon glyphicon-chevron-left'></i> Back </a> </div> </div>
سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
import { Component } from 'angular2/core'; import { RouteParams, Router } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams, private _router: Router) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } onBack(): void { this._router.navigate(['Products']); } }
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName ); // Route override to work with Angularjs and HTML5 routing routes.MapRoute( name: "NotFound", url: "{*catchall}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
<CascadingAuthenticationState> <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <Authorizing> <p>Please wait, we are authorizing the user.</p> </Authorizing> <NotAuthorized> <p>Not Authorized</p> </NotAuthorized> </AuthorizeRouteView> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState>
{ "iss": "https://localhost:5001/", "iat": 1616396383, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "vahid@dntips.ir", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "vahid@dntips.ir", "Id": "582855fb-e95b-45ab-b349-5e9f7de40c0c", "DisplayName": "vahid@dntips.ir", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin", "nbf": 1616396383, "exp": 1616397583, "aud": "Any" }
using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text.Json; namespace BlazorWasm.Client.Utils { /// <summary> /// From the Steve Sanderson’s Mission Control project: /// https://github.com/SteveSandersonMS/presentation-2019-06-NDCOslo/blob/master/demos/MissionControl/MissionControl.Client/Util/ServiceExtensions.cs /// </summary> public static class JwtParser { public static IEnumerable<Claim> ParseClaimsFromJwt(string jwt) { var claims = new List<Claim>(); var payload = jwt.Split('.')[1]; var jsonBytes = ParseBase64WithoutPadding(payload); var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes); claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()))); return claims; } private static byte[] ParseBase64WithoutPadding(string base64) { switch (base64.Length % 4) { case 2: base64 += "=="; break; case 3: base64 += "="; break; } return Convert.FromBase64String(base64); } } }
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.4" /> </ItemGroup> </Project>
namespace BlazorWasm.Client.Services { public class AuthStateProvider : AuthenticationStateProvider { private readonly HttpClient _httpClient; private readonly ILocalStorageService _localStorage; public AuthStateProvider(HttpClient httpClient, ILocalStorageService localStorage) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _localStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage)); } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var token = await _localStorage.GetItemAsync<string>(ConstantKeys.LocalToken); if (token == null) { return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); return new AuthenticationState( new ClaimsPrincipal( new ClaimsIdentity(JwtParser.ParseClaimsFromJwt(token), "jwtAuthType") ) ); } } }
namespace BlazorServer.Common { public static class ConstantKeys { // ... public const string LocalToken = "JWT Token"; } }
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>(); // ... } } }
@using Microsoft.AspNetCore.Components.Authorization
namespace BlazorWasm.Client.Services { public interface IClientAuthenticationService { Task<AuthenticationResponseDTO> LoginAsync(AuthenticationDTO userFromAuthentication); Task LogoutAsync(); Task<RegisterationResponseDTO> RegisterUserAsync(UserRequestDTO userForRegisteration); } }
namespace BlazorWasm.Client.Services { public class ClientAuthenticationService : IClientAuthenticationService { private readonly HttpClient _client; private readonly ILocalStorageService _localStorage; public ClientAuthenticationService(HttpClient client, ILocalStorageService localStorage) { _client = client; _localStorage = localStorage; } public async Task<AuthenticationResponseDTO> LoginAsync(AuthenticationDTO userFromAuthentication) { var response = await _client.PostAsJsonAsync("api/account/signin", userFromAuthentication); var responseContent = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize<AuthenticationResponseDTO>(responseContent); if (response.IsSuccessStatusCode) { await _localStorage.SetItemAsync(ConstantKeys.LocalToken, result.Token); await _localStorage.SetItemAsync(ConstantKeys.LocalUserDetails, result.UserDTO); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token); return new AuthenticationResponseDTO { IsAuthSuccessful = true }; } else { return result; } } public async Task LogoutAsync() { await _localStorage.RemoveItemAsync(ConstantKeys.LocalToken); await _localStorage.RemoveItemAsync(ConstantKeys.LocalUserDetails); _client.DefaultRequestHeaders.Authorization = null; } public async Task<RegisterationResponseDTO> RegisterUserAsync(UserRequestDTO userForRegisteration) { var response = await _client.PostAsJsonAsync("api/account/signup", userForRegisteration); var responseContent = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize<RegisterationResponseDTO>(responseContent); if (response.IsSuccessStatusCode) { return new RegisterationResponseDTO { IsRegisterationSuccessful = true }; } else { return result; } } } }
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddScoped<IClientAuthenticationService, ClientAuthenticationService>(); // ... } } }
Blazor WebAssembly apps don't currently have a concept of DI scopes. Scoped-registered services behave like Singleton services.
[Route("{culture}/[controller]")] public class ValuesController : Controller { [Route("ShowMeTheCulture")] public string GetCulture() { return $"CurrentCulture:{CultureInfo.CurrentCulture.Name}, CurrentUICulture:{CultureInfo.CurrentUICulture.Name}"; } }
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre
using System.ComponentModel.DataAnnotations.Schema; using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim> { // سایر خواص اضافی در اینجا [ForeignKey("AddressId")] public virtual Address Address { get; set; } public int? AddressId { get; set; } } } using System.Collections.Generic; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class Address { public int Id { get; set; } public string City { get; set; } public string State { get; set; } public virtual ICollection<ApplicationUser> ApplicationUsers { set; get; } } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomRole : IdentityRole<int, CustomUserRole> { public CustomRole() { } public CustomRole(string name) { Name = name; } } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserClaim : IdentityUserClaim<int> { } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserLogin : IdentityUserLogin<int> { } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserRole : IdentityUserRole<int> { } }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
using System.Collections.Generic; using System.Data.Entity; namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context { public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveAllChanges(); void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class; IList<T> GetRows<T>(string sql, params object[] parameters) where T : class; IEnumerable<TEntity> AddThisRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; void ForceDatabaseInitialize(); } }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; } public DbSet<Address> Addresses { set; get; }
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
using System.Data.Entity.Migrations; namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context { public class Configuration : DbMigrationsConfiguration<ApplicationDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } }
using System; using System.Security.Claims; using System.Threading.Tasks; using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationUserManager : UserManager<ApplicationUser, int>, IApplicationUserManager { private readonly IDataProtectionProvider _dataProtectionProvider; private readonly IIdentityMessageService _emailService; private readonly IApplicationRoleManager _roleManager; private readonly IIdentityMessageService _smsService; private readonly IUserStore<ApplicationUser, int> _store; public ApplicationUserManager(IUserStore<ApplicationUser, int> store, IApplicationRoleManager roleManager, IDataProtectionProvider dataProtectionProvider, IIdentityMessageService smsService, IIdentityMessageService emailService) : base(store) { _store = store; _roleManager = roleManager; _dataProtectionProvider = dataProtectionProvider; _smsService = smsService; _emailService = emailService; createApplicationUserManager(); } public void SeedDatabase() { } private void createApplicationUserManager() { // Configure validation logic for usernames this.UserValidator = new UserValidator<ApplicationUser, int>(this) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords this.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; // Configure user lockout defaults this.UserLockoutEnabledByDefault = true; this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); this.MaxFailedAccessAttemptsBeforeLockout = 5; // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user // You can write your own provider and plug in here. this.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser, int> { MessageFormat = "Your security code is: {0}" }); this.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser, int> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" }); this.EmailService = _emailService; this.SmsService = _smsService; if (_dataProtectionProvider != null) { var dataProtector = _dataProtectionProvider.Create("ASP.NET Identity"); this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, int>(dataProtector); } } } }
using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationSignInManager : SignInManager<ApplicationUser, int>, IApplicationSignInManager { private readonly ApplicationUserManager _userManager; private readonly IAuthenticationManager _authenticationManager; public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { _userManager = userManager; _authenticationManager = authenticationManager; } } }
using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationRoleManager : RoleManager<CustomRole, int>, IApplicationRoleManager { private readonly IRoleStore<CustomRole, int> _roleStore; public ApplicationRoleManager(IRoleStore<CustomRole, int> roleStore) : base(roleStore) { _roleStore = roleStore; } public CustomRole FindRoleByName(string roleName) { return this.FindByName(roleName); // RoleManagerExtensions } public IdentityResult CreateRole(CustomRole role) { return this.Create(role); // RoleManagerExtensions } } }
using System; using System.Data.Entity; using System.Threading; using System.Web; using AspNetIdentityDependencyInjectionSample.DataLayer.Context; using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using StructureMap; using StructureMap.Web; namespace AspNetIdentityDependencyInjectionSample.IocConfig { public static class SmObjectFactory { private static readonly Lazy<Container> _containerBuilder = new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication); public static IContainer Container { get { return _containerBuilder.Value; } } private static Container defaultContainer() { return new Container(ioc => { ioc.For<IUnitOfWork>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationDbContext>(); ioc.For<ApplicationDbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>(); ioc.For<DbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>(); ioc.For<IUserStore<ApplicationUser, int>>() .HybridHttpOrThreadLocalScoped() .Use<UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>>(); ioc.For<IRoleStore<CustomRole, int>>() .HybridHttpOrThreadLocalScoped() .Use<RoleStore<CustomRole, int, CustomUserRole>>(); ioc.For<IAuthenticationManager>() .Use(() => HttpContext.Current.GetOwinContext().Authentication); ioc.For<IApplicationSignInManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationSignInManager>(); ioc.For<IApplicationUserManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationUserManager>(); ioc.For<IApplicationRoleManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationRoleManager>(); ioc.For<IIdentityMessageService>().Use<SmsService>(); ioc.For<IIdentityMessageService>().Use<EmailService>(); ioc.For<ICustomRoleStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomRoleStore>(); ioc.For<ICustomUserStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>(); //config.For<IDataProtectionProvider>().Use(()=> app.GetDataProtectionProvider()); // In Startup class ioc.For<ICategoryService>().Use<EfCategoryService>(); ioc.For<IProductService>().Use<EfProductService>(); }); } } }
using System; using AspNetIdentityDependencyInjectionSample.IocConfig; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; using Owin; using StructureMap.Web; namespace AspNetIdentityDependencyInjectionSample { public class Startup { public void Configuration(IAppBuilder app) { configureAuth(app); } private static void configureAuth(IAppBuilder app) { SmObjectFactory.Container.Configure(config => { config.For<IDataProtectionProvider>() .HybridHttpOrThreadLocalScoped() .Use(()=> app.GetDataProtectionProvider()); }); SmObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase(); // Configure the db context, user manager and role manager to use a single instance per request app.CreatePerOwinContext(() => SmObjectFactory.Container.GetInstance<IApplicationUserManager>()); // Enable the application to use a cookie to store information for the signed in user // and to use a cookie to temporarily store information about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = SmObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity() } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process. app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); // Enables the application to remember the second login verification factor such as phone or email. // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from. // This is similar to the RememberMe option when you log in. app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); } } }
using System; using System.Data.Entity; using System.Web; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using AspNetIdentityDependencyInjectionSample.DataLayer.Context; using AspNetIdentityDependencyInjectionSample.IocConfig; using StructureMap.Web.Pipeline; namespace AspNetIdentityDependencyInjectionSample { public class MvcApplication : HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); setDbInitializer(); //Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); } protected void Application_EndRequest(object sender, EventArgs e) { HttpContextLifecycle.DisposeAndClearAll(); } public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.RawUrl)); return SmObjectFactory.Container.GetInstance(controllerType) as Controller; } } private static void setDbInitializer() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>()); SmObjectFactory.Container.GetInstance<IUnitOfWork>().ForceDatabaseInitialize(); } } }
[Authorize] public class AccountController : Controller { private readonly IAuthenticationManager _authenticationManager; private readonly IApplicationSignInManager _signInManager; private readonly IApplicationUserManager _userManager; public AccountController(IApplicationUserManager userManager, IApplicationSignInManager signInManager, IAuthenticationManager authenticationManager) { _userManager = userManager; _signInManager = signInManager; _authenticationManager = authenticationManager; } [Authorize] public class ManageController : Controller { // Used for XSRF protection when adding external logins private const string XsrfKey = "XsrfId"; private readonly IAuthenticationManager _authenticationManager; private readonly IApplicationUserManager _userManager; public ManageController(IApplicationUserManager userManager, IAuthenticationManager authenticationManager) { _userManager = userManager; _authenticationManager = authenticationManager; } [Authorize(Roles = "Admin")] public class RolesAdminController : Controller { private readonly IApplicationRoleManager _roleManager; private readonly IApplicationUserManager _userManager; public RolesAdminController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) { _userManager = userManager; _roleManager = roleManager; } [Authorize(Roles = "Admin")] public class UsersAdminController : Controller { private readonly IApplicationRoleManager _roleManager; private readonly IApplicationUserManager _userManager; public UsersAdminController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) { _userManager = userManager; _roleManager = roleManager; }
using System;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
/// <summary>
/// کلاسی جهت نمایش تعداد خواننده فید وبلاگ شما
/// <example>CFeedBurner data = new CFeedBurner { FeedID = "fhphjt61bueu08k93ehujpu234" };
/// MessageBox.Show(data.Circulation().ToString());</example>
/// </summary>
class CFeedBurner
{
/// <summary>
/// آی دی فید شما زمانیکه به فید برنر لاگین کردهاید در تایتل صفحه مربوطه
/// </summary>
public string FeedID { get; set; }
/// <summary>
/// نگاشت فید به یک کلاس
/// </summary>
/// <returns>کلاس متناظر با فید</returns>
/// <exception cref="Exception">لطفا شماره شناسایی فید را وارد کنید</exception>
rsp deserializeFromXML()
{
if (FeedID == null)
throw new Exception("لطفا شماره شناسایی فید را وارد کنید");
XmlSerializer deserializer =
new XmlSerializer(typeof(rsp));
using (XmlReader reader = XmlReader.Create(
string.Format("https://feedburner.google.com/api/awareness/1.0/GetFeedData?id={0}", FeedID)))
{
return (rsp)deserializer.Deserialize(reader);
}
}
/// <summary>
/// دریافت تعداد خواننده فید
/// </summary>
/// <returns>آمار فید</returns>
/// <exception cref="Exception">اطلاعات فید شما قابل دریافت نیست</exception>
public int Circulation()
{
rsp data = deserializeFromXML();
if (data == null || data.feed == null || data.feed.Length == 0)
throw new Exception("اطلاعات فید شما قابل دریافت نیست");
if (data.feed[0].entry == null || data.feed[0].entry.Length == 0)
throw new Exception("اطلاعات فید شما قابل پردازش نیست");
return int.Parse(data.feed[0].entry[0].circulation);
}
}
}
xsd.exe GetFeedData.xml
xsd.exe GetFeedData.xsd /c
using System.ComponentModel.DataAnnotations; namespace Models; public record OrderPlace { public Address BillingAddress { get; set; } = new(); public Address ShippingAddress { get; set; } = new(); } public class Address { [Required] public string Name { get; set; } = default!; public string? AddressLine1 { get; set; } public string? AddressLine2 { get; set; } public string? City { get; set; } [Required] public string PostCode { get; set; } = default!; }
@inherits Editor<Models.Address> <div> <label>Name</label> <InputText @bind-Value="Value.Name"/> </div> <div> <label>Address 1</label> <InputText @bind-Value="Value.AddressLine1"/> </div> <div> <label>Address 2</label> <InputText @bind-Value="Value.AddressLine2"/> </div> <div> <label>City</label> <InputText @bind-Value="Value.City"/> </div> <div> <label>Post Code</label> <InputText @bind-Value="Value.PostCode"/> </div>
@page "/checkout" @using Models @if (!_submitted && PlaceModel != null) { <EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout"> <DataAnnotationsValidator/> <h4>Bill To:</h4> <AddressEntry @bind-Value="PlaceModel.BillingAddress"/> <h4>Ship To:</h4> <AddressEntry @bind-Value="PlaceModel.ShippingAddress"/> <button type="submit">Submit</button> <ValidationSummary/> </EditForm> } @if (_submitted && PlaceModel != null) { <div> <h2>Order Summary</h2> <h3>Shipping To:</h3> <dl> <dt>Name</dt> <dd>@PlaceModel.BillingAddress.Name</dd> <dt>Address 1</dt> <dd>@PlaceModel.BillingAddress.AddressLine1</dd> <dt>Address 2</dt> <dd>@PlaceModel.BillingAddress.AddressLine2</dd> <dt>City</dt> <dd>@PlaceModel.BillingAddress.City</dd> <dt>Post Code</dt> <dd>@PlaceModel.BillingAddress.PostCode</dd> </dl> </div> } @code { bool _submitted; [SupplyParameterFromForm] public OrderPlace? PlaceModel { get; set; } protected override void OnInitialized() { PlaceModel ??= GetOrderPlace(); } private void SubmitOrder() { _submitted = true; } private static OrderPlace GetOrderPlace() => new() { BillingAddress = new Address { PostCode = "12345", Name = "Test 1", }, ShippingAddress = new Address { PostCode = "67890", Name = "Test 2", }, }; }
<form method="post"> <input type="hidden" name="_handler" value="checkout" /> <input type="hidden" name="__RequestVerificationToken" value="CfDxxx" /> . . . <button type="submit">Submit</button> </form>
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
[SupplyParameterFromForm] public OrderPlace? PlaceModel { get; set; }
@page "/checkout" <FormMappingScope Name="store-checkout"> <CheckoutForm /> </FormMappingScope>
<form method="post"> <input type="hidden" name="_handler" value="[store-checkout]checkout" /> <input type="hidden" name="__RequestVerificationToken" value="CfDxxxxx" /> . . . <button type="submit">Submit</button> </form>
<form method="post" @onsubmit="SaveData" @formname="MyFormName"> <AntiforgeryToken /> <InputText @bind-Value="Name" /> <button>Submit</button> </form>
@page "/" <form method="GET"> <input type="text" name="q"/> <button type="submit">Search</button> </form> @code { [SupplyParameterFromQuery(Name="q")] public string SearchTerm { get; set; } protected override async Task OnInitializedAsync() { // do something with the search term } }
<button class="nav-link border-0" @onclick="BeginSignOut">Log out</button>
<EditForm Context="ctx" FormName="LogoutForm" method="post" Model="@Foo" OnValidSubmit="BeginSignOut"> <button type="submit" class="nav-link border-0">Log out</button> </EditForm> @code{ [SupplyParameterFromForm(Name = "LogoutForm")] public string? Foo { get; set; } protected override void OnInitialized() => Foo = ""; async Task BeginSignOut() { // TODO: SignOutAsync(); // TODO: NavigateTo("/authentication/logout"); } }
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout" Enhance>
در کتابخانهی Microsoft AspNetCore Identity میتوان با این کد، فیلد Email را منحصر بهفرد کرد:
//Program.cs file builder.Services.AddIdentity<User, Role>(options => { options.User.RequireUniqueEmail = true; }).AddEntityFrameworkStores<DatabaseContext>();
برنامه را اجرا و درخواستها را یکی یکی به سمت سرور ارسال میکنیم و اگر ایمیل تکراری باشد به ما خطا میده و میگه: "ایمیل تکراری است".
ولی مشکل اینجاست که کد بالا فیلد Email رو داخل دیتابیس منحصر بهفرد نمیکنه و فقط از سمت نرم افزار بررسی تکراری بودن ایمیل رو انجام میده. حالا اگه ما با استفاده از نرم افزارهای "تست برنامههای وب" مثل Apache JMeter تعداد زیادی درخواست را به سمت برنامهمان ارسال کنیم و بعد رکوردهای داخل جدول کاربران را نگاه کنیم، با وجود اینکه داخل نرم افزارمان پراپرتی Email را منحصر بهفرد کردهایم، ولی چندین رکورد، با یک ایمیل مشابه در داخل جدول User وجود خواهد داشت.
برای تست این سناریو، برنامه Apache JMeter را از این لینک دانلود میکنیم (در بخش Binaries فایل zip رو دانلود می کنیم).
نکته: داشتن jdk ورژن 8 به بالا پیش نیاز است. برای اینکه بدونید ورژن جاوای سیستمتون چنده، داخل cmd دستور java -version رو صادر کنید.
اگه تمایل به نصب، یا به روز رسانی jdk را داشتید، میتونید از این لینک استفاده کنید و بسته به سیستم عاملتون، یکی از تبهای Windows, macOS یا Linux رو انتخاب کنید و فایل مورد نظر رو دانلود کنید (برای Windows فایل x64 Compressed Archive رو دانلود و نصب میکنیم).
حالا فایل دانلود شده JMeter رو استخراج میکنیم، وارد پوشهی bin میشیم و فایل jmeter.bat رو اجرا میکنیم تا برنامهی JMeter اجرا بشه.
قبل از اینکه وارد برنامه JMeter بشیم، کدهای برنامه رو بررسی میکنیم.
موجودیت کاربر:
public class User : IdentityUser<int>;
ویوو مدل ساخت کاربر:
public class UserViewModel { public string UserName { get; set; } = null!; public string Email { get; set; } = null!; public string Password { get; set; } = null!; }
کنترلر ساخت کاربر:
[ApiController] [Route("/api/[controller]")] public class UserController(UserManager<User> userManager) : Controller { [HttpPost] public async Task<IActionResult> Add(UserViewModel model) { var user = new User { UserName = model.UserName, Email = model.Email }; var result = await userManager.CreateAsync(user, model.Password); if (result.Succeeded) { return Ok(); } return BadRequest(result.Errors); } }
حالا وارد برنامه JMeter میشیم و اولین کاری که باید انجام بدیم این است که مشخص کنیم چند درخواست را در چند ثانیه قرار است ارسال کنیم. برای اینکار در برنامه JMeter روی TestPlan کلیک راست میکنیم و بعد:
Add -> Threads (Users) -> Thread Group
حالا باید بر روی Thread Group کلیک کنیم و بعد در بخش Number of threads (users) تعداد درخواستهایی را که قرار است به سمت سرور ارسال کنیم، مشخص کنیم؛ برای مثال عدد 100.
گزینه Ramp-up period (seconds) برای اینه که مشخص کنیم این 100 درخواست قرار است در چند ثانیه ارسال شوند که آن را روی 0.1 ثانیه قرار میدهیم تا درخواستها را با سرعت بسیار زیاد ارسال کند.
الان باید مشخص کنیم چه دیتایی قرار است به سمت سرور ارسال شود:
برای اینکار باید یک Http Request اضافه کنیم. برای این منظور روی Thread Group که از قبل ایجاد کردیم، کلیک راست میکنیم و بعد:
Add -> Sampler -> Http Request
حالا روی Http Request کلیک میکنیم و متد ارسال درخواست رو که روی Get هست، به Post تغییر میدیم و بعد Path رو هم به آدرسی که قراره دیتا رو بهش ارسال کنیم، تغییر میدهیم:
https://localhost:7091/api/User
حالا پایینتر Body Data رو انتخاب میکنیم و دیتایی رو که قراره به سمت سرور ارسال کنیم، در قالب Json وارد میکنیم:
{ "UserName": "payam${__Random(1000, 9999999)}", "Email": "payam@gmail.com", "Password": "123456aA@" }
چون بخش UserName در پایگاه داده منحصر بهفرد است، با این دستور:
${__Random(1000, 9999999)}
یک عدد Random رو به UserName اضافه میکنیم که دچار خطا نشیم.
حالا فقط باید یک Header رو هم به درخواستمون اضافه کنیم، برای اینکار روی Http Request که از قبل ایجاد کردیم، کلیک راست میکنیم و بعد:
Add -> Config Element -> Http Header Manager
حالا روی دکمهی Add در پایین صفحه کلیک میکنیم و این Header رو اضافه میکنیم:
Name: Content-Type Value: application/json
همچنین میتونیم یک View result رو هم اضافه کنیم تا وضعیت تمامی درخواستهای ارسال شده رو مشاهده کنیم. برای اینکار روی Http Request که از قبل ایجاد کردیم، کلیک راست میکنیم و بعد:
Add -> Listener -> View Results Tree
فایل Backup، برای اینکه مراحل بالا رو سریعتر انجام بدید:
File -> Open
حالا بر روی دکمهی سبز رنگ Play در Toolbar بالا کلیک میکنیم تا تمامی درخواست ها را به سمت سرور ارسال کنه و همچنین میتونیم از طریق View result tree ببینیم که چند درخواست موفقیت آمیز و چند درخواست ناموفق انجام شدهاست.
حالا اگر وارد پایگاه داده بشیم، میبینیم که چندین رکورد، با Email یکسان، در جدول User وجود داره:
در حالیکه ایمیل رو در تنظیمات کتابخانه Microsoft AspNetCore Identity به صورت Unique تعریف کردهایم:
//Program.cs file builder.Services.AddIdentity<User, Role>(options => { options.User.RequireUniqueEmail = true; }).AddEntityFrameworkStores<DatabaseContext>();
دلیل این مشکل این است که درخواستها در قالب یک صف، یک به یک اجرا نمیشوند؛ بلکه به صورت همزمان فریم ورک ASP.NET Core برای بالا بردن سرعت اجرای درخواستها از تمامی Thread هایی که در اختیارش هست استفاده میکند و در چندین Thread جداگانه، درخواستهایی رو به کنترلر User میفرسته و در نتیجه، در یک زمان مشابه، چندین درخواست ارسال میشه که آیا یک ایمیل برای مثال با مقدار payam@yahoo.com وجود داره یا خیر و در تمامی درخواستها چون همزمان انجام شده، جواب خیر است. یعنی ایمیل تکراری با آن مقدار، در پایگاه داده وجود ندارد و تمامی درخواستهایی که همزمان به سرور رسیدهاند، کاربر جدید را با ایمیل مشابهی ایجاد میکنند.
این مشکل را میتوان حتی در سایتهای فروش بلیط نیز پیدا کرد؛ یعنی چند نفر یک صندلی را رزرو کردهاند و همزمان وارد درگاه پرداخت شده و هزینهایی را برای آن پرداخت میکنند. اگر آن درخواستها را وارد صف نکنیم، امکان دارد که یک صندلی را به چند نفر بفروشیم. این سناریو برای زمانی است که در پایگاه داده، فیلدها را Unique تعریف نکرده باشیم. هر چند که اگر فیلدها را نیز Unique تعریف کرده باشیم تا یک صندلی را به چند نفر نفروشیم، در آن صورت هم برنامه دچار خطای 500 خواهد شد. پس بهتر است که حتی در زمانهایی هم که فیلدها را Unique تعریف میکنیم، باز هم از ورود چند درخواست همزمان به اکشن رزرو صندلی جلوگیری کنیم.
راه حل
برای حل این مشکل میتوان از Lock statement استفاده کرد که این راه حل نیز یک مشکل دارد که در ادامه به آن اشاره خواهم کرد.
Lock statement به ما این امکان رو میده تا اگر بخشی از کد ما در یک Thread در حال اجرا شدن است، Thread دیگری به آن بخش از کد، دسترسی نداشته باشد و منتظر بماند تا آن Thread کارش با کد ما تموم شود و بعد Thread جدید بتونه کد مارو اجرا کنه.
نحوه استفاده از Lock statement هم بسیار سادهاست:
public class TestClass { private static readonly object _lock1 = new(); public void Method1() { lock (_lock1) { // Body } } }
حالا باید کدهای خودمون رو در بخش Body اضافه کنیم تا دیگر چندین Thread به صورت همزمان، کدهای ما رو اجرا نکنند.
اما یک مشکل وجود داره و آن این است که ما نمیتوانیم در Lock statement، از کلمه کلیدی await استفاده کنیم؛ در حالیکه برای ساخت User جدید باید از await استفاده کنیم:
var result = await userManager.CreateAsync(user, model.Password);
برای حل این مشکل میتوان از کلاس SemaphoreSlim بجای کلمهی کلیدی lock استفاده کرد:
[ApiController] [Route("/api/[controller]")] public class UserController(UserManager<User> userManager) : Controller { private static readonly SemaphoreSlim Semaphore = new (initialCount: 1, maxCount: 1); [HttpPost] public async Task<IActionResult> Add(UserViewModel model) { var user = new User { UserName = model.UserName, Email = model.Email }; // Acquire the semaphore await Semaphore.WaitAsync(); try { // Perform user creation var result = await userManager.CreateAsync(user, model.Password); if (result.Succeeded) { return Ok(); } return BadRequest(result.Errors); } finally { // Release the semaphore Semaphore.Release(); } } }
این کلاس نیز مانند lock عمل میکند، ولی تواناییهای بیشتری را در اختیار ما قرار میدهد؛ برای مثال میتوان تعیین کرد که همزمان چند ترد میتوانند به این کد دسترسی داشته باشند؛ در حالیکه در lock statement فقط یک Thread میتوانست به کد دسترسی داشته باشد. مزیت دیگر کلاس SemaphoreSlim این است که میتوان برای اجرای کدمان Timeout در نظر گرفت تا از بلاک شدن نامحدود Thread جلوگیری کنیم.
با فراخوانی await semaphore.WaitAsync، دسترسی کد ما توسط سایر Thread ها محدود و با فراخوانی Release، کد ما توسط سایر Thread ها قابل دسترسی میشود.
مشکل قفل کردن Thread ها
هنگام قفل کردن Thread ها، مشکلی وجود دارد و آن این است که اگر برنامهی ما روی چندین سرور مختلف اجرا شود، این روش جوابگو نخواهد بود؛ چون قفل کردن Thread روی یک سرور تاثیری در سایر سرورها جهت محدود کردن دسترسی به کد ما ندارد. اما به صورت کلی میتوان از این روش برای بخشهایی خاص از برنامههایمان استفاده کنیم.
پیاده سازی با کمک الگوی AOP
برای اینکه کارمون راحت تر بشه، میتونیم کدهای بالا رو به یک Attribute انتقال بدیم و از اون Attribute در بالای اکشنهامون استفاده کنیم تا کل عملیات اکشنهامونو رو در یک Thread قفل کنیم:
[AttributeUsage(AttributeTargets.Method)] public class SemaphoreLockAttribute : Attribute, IAsyncActionFilter { private static readonly SemaphoreSlim Semaphore = new (1, 1); public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // Acquire the semaphore await Semaphore.WaitAsync(); try { // Proceed with the action await next(); } finally { // Release the semaphore Semaphore.Release(); } } }
حالا میتونیم این Attribute را برای هر اکشنی استفاده کنیم:
[HttpPost] [SemaphoreLock] public async Task<IActionResult> Add(UserViewModel model) { var user = new User { UserName = model.UserName, Email = model.Email }; var result = await userManager.CreateAsync(user, model.Password); if (result.Succeeded) { return Ok(); } return BadRequest(result.Errors); }
static void AddChartButton( Workbook workBook, Worksheet xlWorkSheetNew, Range currentRange, int macroId, int startRow, int endRow, int startCol, int endCol, string buttonImagePath ) { var cell = currentRange.Next; var width = cell.Width; var height = 24; var left = cell.Left; var top = System.Math.Max( cell.Top + cell.Height - height, 0 ); var button = xlWorkSheetNew.Shapes.AddPicture( buttonImagePath, MsoTriState.msoFalse, MsoTriState.msoCTrue, left, top, width, height ); var module = workBook.VBProject.VBComponents.Add( vbext_ComponentType.vbext_ct_StdModule ); module.CodeModule.AddFromString( GetMacro( macroId, startRow, endRow, startCol, endCol ) ); button.OnAction = "Macro" + macroId; } static string GetMacro( int macroId, int startRow, int endRow, int startCol, int endCol ) { var sb = new StringBuilder(); var range = "ActiveSheet.Range(Cells(" + startRow + "," + startCol + "), Cells(" + endRow + "," + endCol + ")).Select"; sb.AppendLine( "Sub Macro" + macroId + "()" ); sb.AppendLine( "On Error Resume Next" ); sb.AppendLine( range ); sb.AppendLine( "ActiveSheet.Shapes.AddChart.Select" ); sb.AppendLine( "ActiveChart.ChartType = xlColumn" ); sb.AppendLine( "ActiveChart.SetSourceData Source:=" + range ); sb.AppendLine( "On Error GoTo 0" ); sb.AppendLine( "End Sub" ); return sb.ToString(); }
و برای استفاده از آن میتوان مانند مثال زیر عمل کرد:
var excelApp = new Microsoft.Office.Interop.Excel.Application(); var fileName = @"C:\Users\Vahid\Desktop\VBA.xlsm"; var workBook = excelApp.Workbooks.Open( fileName ); var sheet = workBook.Sheets[1]; AddChartButton( workBook, sheet, sheet.Range["B1"], 1231, 1, 10, 1, 2, @"C:\Users\Vahid\Desktop\BarChart.png"); excelApp.DisplayAlerts = false; workBook.Close( true, fileName );
خروجی مثال بالا
نکته: در صورتیکه بعد از اجرای برنامه، خطای " programmatic access to visual basic project is not trusted" رخ داد از این طریق میتوانید مشکل را حل کنید.
File -> Options -> Trust Center -> Trust Center Settings -> Macro Settings -> Trust Access to the VBA Project object model
class Program { volatile bool _shouldPartyContinue = true; static void Main() { var firstDimension = new Program(); var secondDimension = new Thread( firstDimension.StartPartyInAnotherDimension ); secondDimension.Start( firstDimension ); Thread.Sleep( 5000 ); firstDimension._shouldPartyContinue = false; Console.WriteLine( "Party Finish" ); } void StartPartyInAnotherDimension( object input ) { var currentDimensionInput = (Program)input; Console.WriteLine( "let the party begin" ); while ( currentDimensionInput._shouldPartyContinue ) {} Console.WriteLine( "Party ends: (" ); } }
class Program { public static void Main(string[] args) { Console.WriteLine( Number ); // Error global::System.Console.WriteLine( "Console: " + Console ); //OK } public class System { } // Define a constant called ‘Console’ to cause more problems. const int Console = 7; const int Number = 67; }
[DebuggerDisplay( "{DebuggerDisplay}" )] public class DebuggerDisplayTest { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } [DebuggerBrowsable( DebuggerBrowsableState.Never )] string DebuggerDisplay => $"{FirstName} {LastName} {Age} years old"; }
[DebuggerDisplay( "Age {Age > 0 ? Age : 25}" )] public class DebuggerDisplayTest { //... }