Blazor C# Tutorials
30 videos
In this playlist, I am going through all the fundamentals and sharing my journey to be a full stack Blazor developer. This is the future of web development in ASP.NET world. If you want to learn Blazor this is the best place to start.
1. Build Your First App - EP01
2. Getting Started - EP02
3. #Routing - EP03
4. Dependency #Injection - EP04
5. Forms & #Validations - EP05
6. JavaScript #Interop - EP06
7. #Razor #Components | Re-usability - EP07
8. Razor Components | #Lifecycle Methods - EP08
9. Razor Component #Libraries - EP09
10. Call #REST #API - #CRUD Methods - EP10
11. #Authentication | Out of the box- EP11
12. Custom AuthenticationStateProvider - EP12
13. Layouts | Login Pages - EP13
14. HttpClient | Login User
15. IHttpClientFactory | Login User
16. Sending JWT token & Request Middleware
17. Handling Exceptions
Summary
JavaScript is a language written for websites to run in the client’s browser.
AJAX is a way for JavaScript to request data from a server without refreshing the page or blocking the application.
jQuery is a JavaScript library built to automate and simplify common web tasks like AJAX or animation.
Angular is a hip JavaScript framework which is made for building large, single-page web applications.
Node.js allows JavaScript to be run without a browser, and is commonly used to run web servers.
This webcast is a code-focused introduction to developing workflow-enabled Microsoft Windows platform applications. We cover the basics of developing, designing, and debugging workflow solutions. Gain the knowledge and insight you need to be confident choosing workflow for everyday applications.
Intro to Windows Workflow Foundation (Part 2 of 7): Simple Human Workflow Using E-mail (Level 200)
Have you thought about how you might apply the workflow concept to e-mail? In this webcast New Zealand based regional director, Chris Auld, leads attendees through a simple worked example of the use of SMTP e-mail as part of a workflow solution. Chris demonstrates how to create custom activities to query Active Directory to retrieve user data, send e-mail, and wait for e-mail responses to continue the workflow process. This code-intensive session gives users taking their first steps with workflow a good grounding in some of the key extensibility concepts.
Intro to Windows Workflow Foundation (Part 3 of 7): Hosting and Communications Options in Workflow Scenarios (Level 300)
The session looks at options for hosting workflow applications. We cover managing events, instance tracking, and persistence, and provide a close look at the simple communications mechanisms that are available for you to use in your workflow applications.
Intro to Windows Workflow Foundation (Part 4 of 7): Workflow, Messaging, and Services: Developing Distributed Applications with Workflows (Level 300)
Web service technologies have typically taken a "do-it-yourself" approach to maintaining the interoperation state of services. Using workflow, developers now have tools that allow them to describe the long-running state of their services and delegate much of the state management to the underlying platform. Managing this state correctly becomes even more challenging in applications that coordinate work across multiple services either within an organization or at an Internet scale. This session looks at how developers who use either Microsoft ASMX or Microsoft's framework for building service-oriented applications, code-named "Indigo", can create workflow-oriented applications that are both faster to write and more manageable and flexible once deployed.
Intro to Windows Workflow Foundation (Part 5 of 7): Developing Event Driven State Machine Workflows (Level 300)
State machines used to be something that you had to first draw on paper and then implement in code. This session shows how to use technologies to create event-driven workflows and how to apply this to a typical programming problem. We introduce the concept of a flexible process and show how this can help with modeling real-world processes using state and sequential workflow. Plenty of coding is included to illustrate how you can seamlessly merge state machine design and your code.
Intro to Windows Workflow Foundation (Part 6 of 7): Extending Workflow Capabilities with Custom Activities (Level 300)
It is helpful to think of activities as controls within a workflow, similar to controls used with Microsoft ASP.NET Pages or Microsoft Windows Forms. You can use activities to encapsulate execution logic, communicate with the host and decompose a workflow into reusable components. This session examines the simple process of creating custom activities. If you want to expose activities to other developers designing workflows, you are likely to find this session valuable.
Intro to Windows Workflow Foundation (Part 7 of 7): Developing Rules Driven Workflows (Level 300)
Rules can be a powerful business tool when combined with workflow. In this session, learn how to develop more advanced activities that support the modeling of rich business behavior such as human workflow. Understand when to use rules for business logic, and see how rule policies allow for the description of sophisticated behavior in an integrated and flexible way. This session gives you an interesting insight into the power of using workflow at the core of a line of business application.
The Problem
What they neglect to say is all that testability and persistence ignorance flies right out the window when you create a new ASP.NET Web Application using the MVC template and "Individual User Accounts" authentication. What you get is a single-layered application, tightly coupled to Entity Framework, that:
-
Ignores the patterns that facilitate testing, including: the repository pattern, unit of work pattern, and dependency injection;
-
Forces you to implement their
IUser
interface in your application’s User entity, thereby coupling it to ASP.NET Identity; -
Eliminates any clear separation between your entities, persistence concerns, and business logic. Persistence ignorance? Forget about it.
Thankfully, due to the extensibility designed into ASP.NET Identity, it is possible to ditch the reference to the Microsoft.AspNet.Identity.EntityFramework
assembly and write a custom implementation that can address these and other architectural issues. Just be forewarned: it is not a trivial undertaking, and you’ll have to put up with some code smell that is baked into the Microsoft.AspNet.Identity.Core
assembly.
آموزش ساخت یک بلاگ با Blazor .Net 8
In this video we will build a complete full stack Blog Web App using the new Blazor Web App with SSR with .Net 8 | Step by Step | From Scratch to End
You will learn: New Static Server Side Rendering with Blazor, QuickGrid, Enhanced Navigation, Enhanced Forms, Stream Rendering, Image Preview & Upload, Identity Authentication, Interactive Server Render Mode for Admin Panel, and much more
In this tutorial, you'll build a Nest.js application to get yourself familiar with its building blocks as well as the fundamental principles of building modern web applications. You'll approach this project by separating the application into two different sections: the frontend and the backend. Firstly, you'll concentrate on the RESTful back-end API built with Nest.js. You'll then focus on the frontend, which you will build with Vue.js. Both applications will run on different ports and will function as separate domains.
همچنین باید درنظر داشت، ASP.NET Core Identity یک سیستم اعتبارسنجی مبتنی بر کوکیها است. دقیقا زمانیکه کار AddIdentity را انجام میدهیم، در پشت صحنه همان services.AddAuthentication().AddCookie قسمت قبل فراخوانی میشود. بنابراین بکارگیری آن با JSON Web Tokens هرچند مشکلی را به همراه ندارد و میتوان یک سیستم اعتبارسنجی «دوگانه» را نیز در اینجا داشت، اما ... سربار اضافی تولید کوکیها را نیز به همراه دارد؛ هرچند برای کار با میانافزار اعتبارسنجی، الزامی به استفادهی از ASP.NET Core Identity نیست و عموما اگر از آن به همراه JWT استفاده میکنند، بیشتر به دنبال پیاده سازیهای پیشفرض مدیریت کاربران و نقشهای آن هستند و نه قسمت تولید کوکیهای آن. البته در مطلب جاری این موارد را نیز همانند مطلب اعتبارسنجی مبتنی بر کوکیها، خودمان مدیریت خواهیم کرد و در نهایت سیستم تهیه شده، هیچ نوع کوکی را تولید و یا مدیریت نمیکند.
تنظیمات آغازین برنامه جهت فعالسازی اعتبارسنجی مبتنی بر JSON Web Tokens
اولین تفاوت پیاده سازی یک سیستم اعتبارسنجی مبتنی بر JWT، با روش مبتنی بر کوکیها، تنظیمات متد ConfigureServices فایل آغازین برنامه است:
public void ConfigureServices(IServiceCollection services) { services.Configure<BearerTokensOptions>(options => Configuration.GetSection("BearerTokens").Bind(options)); services .AddAuthentication(options => { options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(cfg => { cfg.RequireHttpsMetadata = false; cfg.SaveToken = true; cfg.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = Configuration["BearerTokens:Issuer"], ValidAudience = Configuration["BearerTokens:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["BearerTokens:Key"])), ValidateIssuerSigningKey = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; cfg.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents)); logger.LogError("Authentication failed.", context.Exception); return Task.CompletedTask; }, OnTokenValidated = context => { var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>(); return tokenValidatorService.ValidateAsync(context); }, OnMessageReceived = context => { return Task.CompletedTask; }, OnChallenge = context => { var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents)); logger.LogError("OnChallenge error", context.Error, context.ErrorDescription); return Task.CompletedTask; } }; });
{ "BearerTokens": { "Key": "This is my shared key, not so secret, secret!", "Issuer": "http://localhost/", "Audience": "Any", "AccessTokenExpirationMinutes": 2, "RefreshTokenExpirationMinutes": 60 } }
سپس کار فراخوانی services.AddAuthentication صورت گرفتهاست. تفاوت این مورد با حالت اعتبارسنجی مبتنی بر کوکیها، ثوابتی است که با JwtBearerDefaults شروع میشوند. در حالت استفادهی از کوکیها، این ثوابت بر اساس CookieAuthenticationDefaults تنظیم خواهند شد.
البته میتوان متد AddAuthentication را بدون هیچگونه پارامتری نیز فراخوانی کرد. این حالت برای اعتبارسنجیهای دوگانه مفید است. برای مثال زمانیکه پس از AddAuthentication هم AddJwtBearer را ذکر کردهاید و هم AddCookie اضافه شدهاست. اگر چنین کاری را انجام دادید، اینبار باید درحین تعریف فیلتر Authorize، دقیقا مشخص کنید که حالت مبتنی بر JWT مدنظر شما است، یا حالت مبتنی بر کوکیها:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
بررسی تنظیمات متد AddJwtBearer
در کدهای فوق، تنظیمات متد AddJwtBearer یک چنین مفاهیمی را به همراه دارند:
- تنظیم SaveToken به true، به این معنا است که میتوان به توکن دریافتی از سمت کاربر، توسط متد HttpContext.GetTokenAsync در کنترلرهای برنامه دسترسی یافت.
در قسمت تنظیمات TokenValidationParameters آن:
- کار خواندن فایل appsettings.json برنامه جهت تنظیم صادر کننده و مخاطبین توکن انجام میشود. سپس IssuerSigningKey به یک کلید رمزنگاری متقارن تنظیم خواهد شد. این کلید نیز در تنظیمات برنامه قید میشود.
- تنظیم ValidateIssuerSigningKey به true سبب خواهد شد تا میانافزار اعتبارسنجی، بررسی کند که آیا توکن دریافتی از سمت کاربر توسط برنامهی ما امضاء شدهاست یا خیر؟
- تنظیم ValidateLifetime به معنای بررسی خودکار طول عمر توکن دریافتی از سمت کاربر است. اگر توکن منقضی شده باشد، اعتبارسنجی به صورت خودکار خاتمه خواهد یافت.
- ClockSkew به معنای تنظیم یک تلرانس و حد تحمل مدت زمان منقضی شدن توکن در حالت ValidateLifetime است. در اینجا به صفر تنظیم شدهاست.
سپس به قسمت JwtBearerEvents میرسیم:
- OnAuthenticationFailed زمانی فراخوانی میشود که اعتبارسنجهای تنظیمی فوق، با شکست مواجه شوند. برای مثال طول عمر توکن منقضی شده باشد و یا توسط ما امضاء نشدهباشد. در اینجا میتوان به این خطاها دسترسی یافت و درصورت نیاز آنها را لاگ کرد.
- OnChallenge نیز یک سری دیگر از خطاهای اعتبارسنجی را پیش از ارسال آنها به فراخوان در اختیار ما قرار میدهد.
- OnMessageReceived برای حالتی است که توکن دریافتی، توسط هدر مخصوص Bearer به سمت سرور ارسال نمیشود. عموما هدر ارسالی به سمت سرور یک چنین شکلی را دارد:
$.ajax({ headers: { 'Authorization': 'Bearer ' + jwtToken },
const string tokenKey = "my.custom.jwt.token.key"; if (context.HttpContext.Items.ContainsKey(tokenKey)) { context.Token = (string)context.HttpContext.Items[tokenKey]; }
تهیه یک اعتبارسنج توکن سفارشی
قسمت OnTokenValidated تنظیمات ابتدای برنامه به این صورت مقدار دهی شدهاست:
OnTokenValidated = context => { var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>(); return tokenValidatorService.ValidateAsync(context); },
public class TokenValidatorService : ITokenValidatorService { private readonly IUsersService _usersService; private readonly ITokenStoreService _tokenStoreService; public TokenValidatorService(IUsersService usersService, ITokenStoreService tokenStoreService) { _usersService = usersService; _usersService.CheckArgumentIsNull(nameof(usersService)); _tokenStoreService = tokenStoreService; _tokenStoreService.CheckArgumentIsNull(nameof(_tokenStoreService)); } public async Task ValidateAsync(TokenValidatedContext context) { var userPrincipal = context.Principal; var claimsIdentity = context.Principal.Identity as ClaimsIdentity; if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any()) { context.Fail("This is not our issued token. It has no claims."); return; } var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber); if (serialNumberClaim == null) { context.Fail("This is not our issued token. It has no serial."); return; } var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value; if (!int.TryParse(userIdString, out int userId)) { context.Fail("This is not our issued token. It has no user-id."); return; } var user = await _usersService.FindUserAsync(userId).ConfigureAwait(false); if (user == null || user.SerialNumber != serialNumberClaim.Value || !user.IsActive) { // user has changed his/her password/roles/stat/IsActive context.Fail("This token is expired. Please login again."); } var accessToken = context.SecurityToken as JwtSecurityToken; if (accessToken == null || string.IsNullOrWhiteSpace(accessToken.RawData) || !await _tokenStoreService.IsValidTokenAsync(accessToken.RawData, userId).ConfigureAwait(false)) { context.Fail("This token is not in our database."); return; } await _usersService.UpdateUserLastActivityDateAsync(userId).ConfigureAwait(false); } }
- آیا توکن دریافتی به همراه Claims تنظیم شدهی درحین لاگین هست یا خیر؟
- آیا توکن دریافتی دارای یک Claim سفارشی به نام SerialNumber است؟ این SerialNumber معادل چنین فیلدی در جدول کاربران است.
- آیا توکن دریافتی دارای user-id است؟
- آیا کاربر یافت شدهی بر اساس این user-id هنوز فعال است و یا اطلاعات او تغییر نکردهاست؟
- همچنین در آخر کار بررسی میکنیم که آیا اصل توکن دریافتی، در بانک اطلاعاتی ما پیشتر ثبت شدهاست یا خیر؟
اگر خیر، بلافاصله متد context.Fail فراخوانی شده و کار اعتبارسنجی را با اعلام شکست آن، به پایان میرسانیم.
در قسمت آخر، نیاز است اطلاعات توکنهای صادر شده را ذخیره کنیم. به همین جهت نسبت به مطلب قبلی، جدول UserToken ذیل به برنامه اضافه شدهاست:
public class UserToken { public int Id { get; set; } public string AccessTokenHash { get; set; } public DateTimeOffset AccessTokenExpiresDateTime { get; set; } public string RefreshTokenIdHash { get; set; } public DateTimeOffset RefreshTokenExpiresDateTime { get; set; } public int UserId { get; set; } // one-to-one association public virtual User User { get; set; } }
از اطلاعات آن در دو قسمت TokenValidatorService فوق و همچنین قسمت logout برنامه استفاده میکنیم. در سیستم JWT، مفهوم logout سمت سرور وجود خارجی ندارد. اما با ذخیره سازی هش توکنها در بانک اطلاعاتی میتوان لیستی از توکنهای صادر شدهی توسط برنامه را تدارک دید. سپس در حین logout فقط کافی است tokenهای یک کاربر را حذف کرد. همینقدر سبب خواهد شد تا قسمت آخر TokenValidatorService با شکست مواجه شود؛ چون توکن ارسالی به سمت سرور دیگر در بانک اطلاعاتی وجود ندارد.
سرویس TokenStore
public interface ITokenStoreService { Task AddUserTokenAsync(UserToken userToken); Task AddUserTokenAsync( User user, string refreshToken, string accessToken, DateTimeOffset refreshTokenExpiresDateTime, DateTimeOffset accessTokenExpiresDateTime); Task<bool> IsValidTokenAsync(string accessToken, int userId); Task DeleteExpiredTokensAsync(); Task<UserToken> FindTokenAsync(string refreshToken); Task DeleteTokenAsync(string refreshToken); Task InvalidateUserTokensAsync(int userId); Task<(string accessToken, string refreshToken)> CreateJwtTokens(User user); }
پیاده سازی کامل این سرویس را در اینجا میتوانید مشاهده کنید.
تولید Access Tokens و Refresh Tokens
پس از تنظیمات ابتدایی برنامه، اکنون میتوانیم دو نوع توکن را تولید کنیم:
تولید Access Tokens
private async Task<string> createAccessTokenAsync(User user, DateTime expires) { var claims = new List<Claim> { // Unique Id for all Jwt tokes new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // Issuer new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer), // Issued at new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim("DisplayName", user.DisplayName), // to invalidate the cookie new Claim(ClaimTypes.SerialNumber, user.SerialNumber), // custom data new Claim(ClaimTypes.UserData, user.Id.ToString()) }; // add roles var roles = await _rolesService.FindUserRolesAsync(user.Id).ConfigureAwait(false); foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role.Name)); } var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration.Value.Issuer, audience: _configuration.Value.Audience, claims: claims, notBefore: DateTime.UtcNow, expires: expires, signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" /> </ItemGroup>
پس از تهیهی Claims، اینبار بجای یک کوکی، یک JSON Web Toekn را توسط متد new JwtSecurityTokenHandler().WriteToken تهیه خواهیم کرد. این توکن حاوی Claims، به همراه اطلاعات طول عمر و امضای مرتبطی است.
حاصل آن نیز یک رشتهاست که دقیقا به همین فرمت به سمت کلاینت ارسال خواهد شد. البته ما در اینجا دو نوع توکن را به سمت کلاینت ارسال میکنیم:
public async Task<(string accessToken, string refreshToken)> CreateJwtTokens(User user) { var now = DateTimeOffset.UtcNow; var accessTokenExpiresDateTime = now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes); var refreshTokenExpiresDateTime = now.AddMinutes(_configuration.Value.RefreshTokenExpirationMinutes); var accessToken = await createAccessTokenAsync(user, accessTokenExpiresDateTime.UtcDateTime).ConfigureAwait(false); var refreshToken = Guid.NewGuid().ToString().Replace("-", ""); await AddUserTokenAsync(user, refreshToken, accessToken, refreshTokenExpiresDateTime, accessTokenExpiresDateTime).ConfigureAwait(false); await _uow.SaveChangesAsync().ConfigureAwait(false); return (accessToken, refreshToken); }
جهت بالا رفتن امنیت سیستم، این Guid را هش کرد و سپس این هش را در بانک اطلاعاتی ذخیره میکنیم. به این ترتیب دسترسی غیرمجاز به این هشها، امکان بازیابی توکنهای اصلی را غیرممکن میکند.
پیاده سازی Login
پس از پیاده سازی متد CreateJwtTokens، کار ورود به سیستم به سادگی ذیل خواهد بود:
[AllowAnonymous] [HttpPost("[action]")] public async Task<IActionResult> Login([FromBody] User loginUser) { if (loginUser == null) { return BadRequest("user is not set."); } var user = await _usersService.FindUserAsync(loginUser.Username, loginUser.Password).ConfigureAwait(false); if (user == null || !user.IsActive) { return Unauthorized(); } var (accessToken, refreshToken) = await _tokenStoreService.CreateJwtTokens(user).ConfigureAwait(false); return Ok(new { access_token = accessToken, refresh_token = refreshToken }); }
پیاده سازی Refresh Token
پیاده سازی توکن به روز رسانی همانند عملیات لاگین است:
[AllowAnonymous] [HttpPost("[action]")] public async Task<IActionResult> RefreshToken([FromBody]JToken jsonBody) { var refreshToken = jsonBody.Value<string>("refreshToken"); if (string.IsNullOrWhiteSpace(refreshToken)) { return BadRequest("refreshToken is not set."); } var token = await _tokenStoreService.FindTokenAsync(refreshToken); if (token == null) { return Unauthorized(); } var (accessToken, newRefreshToken) = await _tokenStoreService.CreateJwtTokens(token.User).ConfigureAwait(false); return Ok(new { access_token = accessToken, refresh_token = newRefreshToken }); }
پیاده سازی Logout
در سیستمهای مبتنی بر JWT، پیاده سازی Logout سمت سرور بیمفهوم است؛ از این جهت که تا زمان انقضای یک توکن میتوان از آن توکن جهت ورود به سیستم و دسترسی به منابع آن استفاده کرد. بنابراین تنها راه پیاده سازی Logout، ذخیره سازی توکنها در بانک اطلاعاتی و سپس حذف آنها در حین خروج از سیستم است. به این ترتیب اعتبارسنج سفارشی توکنها، از استفادهی مجدد از توکنی که هنوز هم معتبر است و منقضی نشدهاست، جلوگیری خواهد کرد:
[AllowAnonymous] [HttpGet("[action]"), HttpPost("[action]")] public async Task<bool> Logout() { var claimsIdentity = this.User.Identity as ClaimsIdentity; var userIdValue = claimsIdentity.FindFirst(ClaimTypes.UserData)?.Value; // The Jwt implementation does not support "revoke OAuth token" (logout) by design. // Delete the user's tokens from the database (revoke its bearer token) if (!string.IsNullOrWhiteSpace(userIdValue) && int.TryParse(userIdValue, out int userId)) { await _tokenStoreService.InvalidateUserTokensAsync(userId).ConfigureAwait(false); } await _tokenStoreService.DeleteExpiredTokensAsync().ConfigureAwait(false); await _uow.SaveChangesAsync().ConfigureAwait(false); return true; }
آزمایش نهایی برنامه
در فایل index.html، نمونهای از متدهای لاگین، خروج و فراخوانی اکشن متدهای محافظت شده را مشاهده میکنید. این روش برای برنامههای تک صفحهای وب یا SPA نیز میتواند مفید باشد و به همین نحو کار میکنند.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
درک مفهوم شردینگ دیتابیس
یک الگوی طراحی دیتابیس در حالت پارتیشن بندی افقی است. بیشتر برای زمانی مفید است که توسعه دیتابیس به صورت پویا مدنظر باشد.
Any application or website that sees significant growth will eventually need to scale in order to accommodate increases in traffic. For data-driven applications and websites, it's critical that scaling is done in a way that ensures the security and integrity of their data. It can be difficult to predict how popular a website or application will become or how long it will maintain that popularity, which is why some organizations choose a database architecture that allows them to scale their databases dynamically.