tag:blogger.com,1999:blog-3243839249171507702024-03-28T06:53:57.622+07:00Nhật ký học tậpSuy nghĩ, yêu thương và làm việc hết mìnhUnknownnoreply@blogger.comBlogger491125tag:blogger.com,1999:blog-324383924917150770.post-43286336510023221952024-03-09T11:52:00.016+07:002024-03-09T12:30:23.051+07:00Sử dụng ADO.NET để thực thi Stored Procedure<h2 style="text-align: left;">Giới thiệu</h2><p>ADO.NET (ActiveX Data Objects for .NET) là một tập hợp các công nghệ trong Microsoft .NET Framework được thiết kế để làm việc với dữ liệu trong môi trường .NET. ADO.NET cung cấp một mô hình lập trình cho truy cập và quản lý dữ liệu từ nhiều nguồn khác nhau, chẳng hạn như cơ sở dữ liệu SQL Server, Oracle, MySQL, và các nguồn dữ liệu khác.</p><p>ADO.NET chủ yếu được sử dụng để thực hiện các thao tác thường xuyên liên quan đến cơ sở dữ liệu, bao gồm:<br /></p><ul style="text-align: left;"><li>Kết nối (Connection): ADO.NET cung cấp lớp SqlConnection để thiết lập kết nối với cơ sở dữ liệu. Kết nối là cầu nối giữa ứng dụng và cơ sở dữ liệu.</li><li>Command: ADO.NET sử dụng các lớp SqlCommand, SqlDataAdapter, và SqlDataReader để thực hiện các câu truy vấn SQL, stored procedures, và thao tác khác đối với cơ sở dữ liệu.</li><li>DataReader: SqlDataReader là một cơ chế để đọc dữ liệu từ cơ sở dữ liệu một cách tuần tự và hiệu quả, thích hợp cho việc đọc dữ liệu theo hàng.</li><li>DataAdapter: SqlDataAdapter là một thành phần quan trọng trong ADO.NET, giúp điều khiển luồng dữ liệu giữa DataSet và cơ sở dữ liệu.</li><li>DataSet và DataTable: DataSet là một cấu trúc dữ liệu chứa các đối tượng DataTable và cung cấp khả năng lưu trữ dữ liệu trong bộ nhớ. DataTable thì đại diện cho một bảng dữ liệu.</li></ul><h2 style="text-align: left;">Stored Procedure là gì</h2><p>Stored Procedure (thường được viết tắt là SP) là một khối mã SQL có thể được lưu trữ và tái sử dụng trong cơ sở dữ liệu. Stored Procedure thường được tạo và lưu trữ trước trong hệ thống quản lý cơ sở dữ liệu, và sau đó có thể được gọi và thực thi từ ứng dụng hoặc các truy vấn SQL khác.
</p><p>Ví dụ </p>
<pre><code class="sql">CREATE PROCEDURE GetEmployeeByName
@EmployeeName NVARCHAR(50)
AS
BEGIN
SELECT * FROM Employees
WHERE EmployeeName = @EmployeeName;
END;</code></pre>
Thực thi Stored Procedure trong Azure Data Studio:
<pre><code class="sql">EXEC GetEmployeeByName @EmployeeName = N'Robert Downey Jr.'</code></pre>
<h2>Thực thi Stored Procedure</h2>
<p>Để sử dụng ADO.NET để gọi và lấy dữ liệu từ một Stored Procedure trong C#, bạn cần sử dụng các đối tượng như SqlConnection, SqlCommand, và SqlDataReader</p><p> </p>
<img src='https://yinyangit.files.wordpress.com/2011/08/ado-net-database-connection-model.png' />
<p>Connection string là một chuỗi kết nối chứa thông tin cần thiết để kết nối ứng dụng của bạn với cơ sở dữ liệu. Bao gồm</p>
<ul>
<li>Data Source (hoặc Server, Address, Addr, Network Address)</li>
<li>Initial Catalog (hoặc Database)</li>
<li>Integrated Security (hoặc Trusted_Connection): Xác định cách xác thực. Nếu giá trị là true, connection sẽ sử dụng xác thực Windows (được gọi là xác thực tích hợp). Nếu giá trị là false, connection sẽ sử dụng xác thực SQL Server.</li>
<li>User ID và Password: Khi sử dụng xác thực SQL Server, bạn cần cung cấp tên người dùng (User ID) và mật khẩu (Password) để kết nối.</li>
<li>MultipleActiveResultSets: Cho phép một kết nối mở nhiều bảng kết quả (result sets) đồng thời. Giá trị mặc định là false.</li>
<li>Connect Timeout:
Xác định thời gian chờ (số giây) trước khi quá trình kết nối bị hủy bỏ.</li>
<li>Encrypt (hoặc TrustServerCertificate): Xác định liệu kết nối có sử dụng SSL/TLS để mã hóa dữ liệu hay không.</li>
<li>Application Name: Xác định tên của ứng dụng khi nó kết nối đến cơ sở dữ liệu.</li>
</ul>
Ví dụ:
<pre><code class="sql">SqlConnection conn = new SqlConnection(
"Data Source=DatabaseServer;Initial Catalog=Northwind;User ID=YourUserID;Password=YourPassword;MultipleActiveResultSets=true;Connect Timeout=60;Encrypt=true;Application Name=nhatkyhoctap;");</code></pre>
<h3>SqlDataReader</h3>
<p>
Nhiều thao tác dữ liệu đòi hỏi bạn chỉ lấy một luồng dữ liệu để đọc. Đối tượng data reader cho phép bạn lấy được kết quả của một câu lệnh SELECT từ một đối tượng command. Để tăng hiệu suất, dữ liệu trả về từ một data reader là một luồng dữ liệu fast forward-only. Có nghĩa là bạn chỉ có thể lấy dữ liệu từ luồng theo một thứ tự nhất định. Mặc dù điều này có lợi về mặt tốc độ, nhưng nếu bạn cần phải thao tác dữ liệu, thì một DataSet sẽ là một đối tượng tốt hơn để làm việc.</p>
<h3>DataSet</h3>
<p>
Đối tượng DataSet là một thể hiện của dữ liệu trong bộ nhớ. Chúng chứa nhiều đối tượng DataTable, bên trong DataTable lại có nhiều column và row, giống như các database table thông thường. Bạn thậm chí có thể định nghĩa dữ liệu giữa các table để tạo các quan hệ parent-child. DataSet được thiết kế đặc biệt để giúp quản lý dữ liệu trong bộ nhớ và để hỗ trợ các thao tác không cần kết nối (disconnected) trên dữ liệu. DataSet là một đối tượng được dùng bởi tất cả Data Provider, đó là lý do tại sao nó không có một Data Provider prefix trong tên gọi.</p>
<h3>Tạo Project sử dụng ADO.NET</h3>
<p>Tạo project Console Application sử dụng .NET 8. Sau đó cài đặt package</p>
<pre><code class="bash">Microsoft.Data.SqlClient</code></pre>
<p>Thực thi đoạn code sau</p>
<pre><code class="csharp">string connectionString = "Server=.;Initial Catalog=nhatkyhoctap;Persist Security Info=False;User ID=sa;Password=1111111;MultipleActiveResultSets=true;Connection Timeout=60;Encrypt=False;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("sp_SPName", connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
// Thêm các tham số cho stored procedure (nếu có)
command.Parameters.AddWithValue("@name", "Geogre Washington");
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
var resultColumn1 = reader[0].ToString();
var resultColumn2 = reader[1].ToString();
Console.WriteLine($"{resultColumn1}, {resultColumn2}");
}
}
}
}</code></pre>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-16006671433965637742024-02-11T18:14:00.016+07:002024-02-12T15:24:19.087+07:00CQRS là gì?<h2 style="text-align: left;">Giới thiệu</h2><p>CQRS được lấy cảm hứng từ mô hình Command Query Separation (CQS) do Bertrand Meyer đề xuất trong cuốn sách "Object Oriented Software Construction". Triết lý chính của CQS là</p><p></p><blockquote><p>A method should either change state of an object, or return a result, but not both. In other words, asking the question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.” (Wikipedia)</p><p></p></blockquote><p>Dựa trên nguyên tắc này, CQS phân chia các phương thức thành hai nhóm:</p><p></p><ul style="text-align: left;"><li>Command: Thay đổi trạng thái của đối tượng hoặc toàn bộ hệ thống (còn gọi là modifiers hoặc mutators).</li><li>Query: Trả về kết quả và không thay đổi trạng thái của đối tượng.</li></ul><p></p><p>Trong thực tế, việc phân biệt ra Query và Command khá đơn giản. Bạn có thể tìm hiểu thêm bài viết tại <a href="https://nhatkyhoctap.blogspot.com/2024/02/huong-dan-su-dung-mediatr.html" rel="nofollow" target="_blank">đây</a></p><h3 style="text-align: left;">CQRS: Bản nâng cấp của CQS</h3><p>CQRS (hay Command Query Responsibility Segregation) là một design pattern phân tách các hoạt động read và write dữ liệu. Trong đó chia việc tương tác với dữ liệu thành 2 thành phần Command và Query. Hai thành phần này tách biệt và độc lập với nhau.</p><p>Để giao tiếp với database, chúng ta thường dùng mô hình thông dụng là Repository với 4 phương thức cơ bản Insert, Get, Update, Delete (CRUD). Data được lưu trữ một chỗ và ứng dụng tương tác với chỗ đó để cả tạo, đọc, sửa, xóa. CQRS thì khác, CQRS tách thành hai mô hình Command và Query (tương tự như CQS) nhưng model Read và Write độc lập với nhau.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiH-FQbPG5AlHydB3Yu-4GxjnejV21gZYgJ1XKEJusdOaYNgVYLIUziTNvRx13TbUyUNt0K__zIiRNZtlYnYrJXGIz5gBs1Y6vw4_NB_eJoOJUKDP1NjixtS4AmgMqGYhjsoKyzTw-uj-ZOK16es0Wi4GTgSJV1jxos-DLiHxkDzQGj88LPqYU6q2XktJU/s1024/CQRS-Diagram-e1598922649719.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="494" data-original-width="1024" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiH-FQbPG5AlHydB3Yu-4GxjnejV21gZYgJ1XKEJusdOaYNgVYLIUziTNvRx13TbUyUNt0K__zIiRNZtlYnYrJXGIz5gBs1Y6vw4_NB_eJoOJUKDP1NjixtS4AmgMqGYhjsoKyzTw-uj-ZOK16es0Wi4GTgSJV1jxos-DLiHxkDzQGj88LPqYU6q2XktJU/s16000/CQRS-Diagram-e1598922649719.png" /></a></div><br /><h4 style="text-align: left;">CQRS đang cố gắng giải quyết vấn đề gì?</h4><p>Khi thiết kế hệ thống, chúng ta thường bắt đầu với việc thiết kế lưu trữ dữ liệu. Đầu tiên là chuẩn hóa dữ liệu (database normalization), thêm primary key và foreign keys để thực thi tính toàn vẹn tham chiếu, thêm index, để đảm bảo tối ưu hóa cho việc read-write. Hoặc chúng ta suy nghĩ về case read, thêm data vào database trước...</p><p>Không có cách nào là sai. Vấn đề là sự cân bằng giữa read và write không được đảm bảo. CQRS giải quyết vấn đề này bằng cách tách biệt mô hình Read và Write</p><h2 style="text-align: left;">Mediator Pattern</h2><p>Mediator Pattern là một mô hình thiết kế hành vi giúp giảm thiểu sự phụ thuộc trực tiếp giữa các đối tượng trong hệ thống. Nó hoạt động như một người trung gian để truyền tải thông tin và điều phối các tương tác giữa các đối tượng.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://images.viblo.asia/a4baab44-8a61-40aa-bfb1-151a955d813b.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="337" data-original-width="572" height="337" src="https://images.viblo.asia/a4baab44-8a61-40aa-bfb1-151a955d813b.png" width="572" /></a></div><br /><p>Tháp điều khiển tại sân bay có kiểm soát là một ví dụ về hoạt động của Mediator pattern. Các phi công của các máy bay đang cất cánh hoặc hạ cánh kết nối với tháp chứ không phải giao tiếp rõ ràng với nhau. Những khó khăn về việc ai có thể cất hoặc hạ cánh được thi hành bởi tháp điều khiển. Điều quan trọng cần lưu ý là tháp không kiểm soát toàn bộ chuyến bay. Nó tồn tại chỉ để thực thi các quy định an toàn trong lúc cất và hạ cánh.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu7oaLwA5EnGmQO9vx3x21jXHyrnUqgRGnUTwWqh5LubSjhOFoe2gTYFU0aWUAQBEqHgKeJ9XCJZFBwIwwkjJc29V8GgBEjduqZNqA18gEy59rngLJIP9wxP01qFfPFNnpdopZf0eAhtXEVcnr9h7Pv1UMt_WwmB2nfDGCmWFoeWcVYuMlPxHxrDoIixY/s645/Mediator-Diagram-e1598922852666.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="304" data-original-width="645" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu7oaLwA5EnGmQO9vx3x21jXHyrnUqgRGnUTwWqh5LubSjhOFoe2gTYFU0aWUAQBEqHgKeJ9XCJZFBwIwwkjJc29V8GgBEjduqZNqA18gEy59rngLJIP9wxP01qFfPFNnpdopZf0eAhtXEVcnr9h7Pv1UMt_WwmB2nfDGCmWFoeWcVYuMlPxHxrDoIixY/s16000/Mediator-Diagram-e1598922852666.png" /></a></div><br /><p>Ở hình trên, Some Service sẽ gởi message tới Mediator, Mediator sẽ invoke service để handle message. Không có dependency trực tiếp giữa các blue component.</p><h2 style="text-align: left;">MediatR</h2><div>Là một thư viện mã nguồn mở được xây dựng dựa trên Mediator Pattern cho phép bạn dễ dàng áp dụng pattern này vào các ứng dụng .NET.</div><h3 style="text-align: left;">Cách sử dụng MediatR</h3><p>Cài đặt</p>
<pre><code class="bash">dotnet add package MediatR</code></pre>
Đăng ký các dịch vụ MediatR:
<pre><code class="csharp">builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));</code></pre>
Trường hợp các Query, Command, và Handler ở project khác, bạn tạo method và gọi hàm đăng ký như sau:
<pre><code class="csharp">services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));</code></pre>
<h3>Setup ASP.NET Core với MediatR</h3>
<p>Để bắt đầu đơn giản nhất, chúng ta sẽ lần lượt thực hiện các bước sau:</p><p></p><ul style="text-align: left;"><li>Tạo Model class: Author và Book</li><li>2 model này đại diện cho 2 entity mà chúng ta sẽ khai báo trong Entity Framework</li><li>Cài đặt package EF Core InMemory NuGet.</li><li>Tạo DbContext class</li><li>Khai báo Repository class: IAuthorRepository và AuthorRepository</li><li>Khai báo Dependency Injection</li><li>Cài đặt và khai báo Swagger</li><li>Khai báo Controller</li></ul><p></p><p>Model Author và Book</p>
<pre><code class="csharp">public class Author
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Book> Books { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
}</code></pre>
Install package
<pre><code class="bash">Install-Package Microsoft.EntityFrameworkCore.InMemory</code></pre>
Khai báo BookContext
<pre><code class="csharp">public class BookContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
public BookContext(DbContextOptions<BookContext> options) : base(options)
{
}
}</code></pre>
Khai báo Model trả về
<pre><code class="csharp">public class AuthorModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}</code></pre>
Khai báo Repository
<pre><code class="csharp">public interface IAuthorRepository
{
public List<AuthorModel> GetAuthors(int pageSize, int page);
}
public class AuthorRepository: IAuthorRepository
{
private readonly BookContext _dbContext;
public AuthorRepository(BookContext dbContext)
{
_dbContext = dbContext;
}
public List<AuthorModel> GetAuthors(int pageSize, int page)
{
var list = _dbContext.Authors.Skip(pageSize * page).Take(pageSize).Select(t => new AuthorModel
{
FirstName = t.FirstName,
LastName = t.LastName,
Id = t.Id,
}).ToList();
return list;
}
}</code></pre>
<p>Mình sẽ lược qua đoạn đăng ký Dependency Injection và Swagger</p>
Ở controller, bạn viết như sau:
<pre><code class="csharp">[Route("api/[controller]")]
[ApiController]
public class AuthorsController : ControllerBase
{
private readonly IAuthorRepository _authorRepository;
public AuthorsController(IAuthorRepository authorRepository)
{
_authorRepository = authorRepository;
}
[HttpGet]
public ActionResult<List<Author>> Get(int pageSize, int pageIndex)
{
return Ok(_authorRepository.GetAuthors(pageSize, pageIndex));
}
}</code></pre>
<p>Controller vẫn có dependency vào Repository. Chúng ta sẽ thêm MediatR vào và gọi Query thay vì gọi trực tiếp authorRepository</p>
<p>Install Package cho CqrsSample và CqrsSample.Application</p>
<pre><code class="bash">dotnet add package MediatR</code></pre>
Thêm đoạn đăng ký sau vào Program.cs
<pre><code class="csharp">builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));</code></pre>
Ở CqrsSample.Application, thêm class sau:
<pre><code class="csharp">public class ApplicationServiceRegistration
{
public void Register(IServiceCollection services)
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
}
}</code></pre>
Gọi hàm đăng ký ở Program.cs
<pre><code class="csharp">var applicationModule = new ApplicationServiceRegistration();
applicationModule.Register(builder.Services);</code></pre>
Ở Project CqrsSample.Application, tạo file GetAuthorQuery và GetAuthorQueryHandler
<pre><code class="csharp">using MediatR;
namespace CqrsSample.Application.Queries;
public class GetAuthorQuery : IRequest<List<AuthorModel>>
{
public int PageSize { get; set; }
public int PageIndex { get; set; } = 10;
}</code></pre>
<pre><code class="csharp">using MediatR;
namespace CqrsSample.Application.Queries.Handlers
{
public class GetAuthorQueryHandler : IRequestHandler<GetAuthorQuery, List<AuthorModel>>
{
private readonly IAuthorRepository _authorRepository;
public GetAuthorQueryHandler(IAuthorRepository authorRepository)
{
_authorRepository = authorRepository;
}
public async Task<List<AuthorModel>> Handle(GetAuthorQuery request, CancellationToken cancellationToken)
{
return _authorRepository.GetAuthors(request.PageSize, request.PageIndex);
}
}
}</code></pre>
Sửa file AuthorController lại như sau
<pre><code class="csharp">[Route("api/[controller]")]
[ApiController]
public class AuthorsController : ControllerBase
{
private readonly IMediator _mediator;
public AuthorsController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<ActionResult<List<Author>>> Get(int pageSize, int pageIndex)
{
var authorRequest = new GetAuthorQuery
{
PageIndex = pageIndex,
PageSize = pageSize
};
var result = await _mediator.Send(authorRequest);
return Ok(result);
}
}</code></pre>
Kiểm tra kết quả ở trang Swagger. Bạn có thể làm tương tự với Command.<div>Source code: <a href="https://github.com/anbinhtrong/cqrs-sample-2024" rel="nofollow" target="_blank">Github</a><br />
<h2 style="text-align: left;">Tham khảo</h2><p><a href="https://www.codeproject.com/Articles/555855/Introduction-to-CQRS">https://www.codeproject.com/Articles/555855/Introduction-to-CQRS</a></p><p><a href="https://code-maze.com/cqrs-mediatr-in-aspnet-core/" rel="nofollow" target="_blank">CQRS and MediatR in ASP.NET Core</a><br /></p><p><a href="https://ducmeit.medium.com/net-core-using-cqrs-pattern-with-mediatr-part-1-55557e90931b" target="_blank">[.NET Core] Using CQRS pattern with MediatR [Part 1]</a><br /></p><p><a href="https://viblo.asia/p/design-patterns-mediator-pattern-gDVK2WrjZLj" rel="nofollow" target="_blank">Design Patterns - Mediator Pattern</a><br /></p></div>Minh Tamhttp://www.blogger.com/profile/16116438424668044201noreply@blogger.com1tag:blogger.com,1999:blog-324383924917150770.post-31991735789777004342024-02-02T10:37:00.124+07:002024-02-08T22:39:57.690+07:00Command Query Separation<div style="text-align: left;">Lần đầu tiên mình bắt đầu khám phá design pattern CQRS (Command Query Responsibility Segregation), mình phải thừa nhận rằng nó đôi chút khó khăn. Nhưng đến khi mình đối mặt với những thách thức thực tế trong dự án cá nhân, mình mới thấu hiểu giá trị quan trọng của CQRS. Điều làm mình ấn tượng là CQRS không phải là một khái niệm quá rối bời. Ngược lại, khi bạn tiếp tục làm việc và trau dồi kinh nghiệm, bạn sẽ dần dần thích ứng và làm quen với nó.<br /><br />Như Lỗ Tấn đã nói, "Trên đời này làm gì có đường, người ta đi mãi thì thành đường thôi.". Vì vậy, giai đoạn bắt đầu tìm hiểu luôn là quan trọng nhất. Nếu bắt đầu mọi thứ quá phức tạp từ đầu, có thể các bạn sẽ cảm thấy mất hứng thú và không muốn tiếp tục đọc các phần sau.</div><p style="text-align: left;">Bài viết này gồm nhiều phần, đầu tiên là mình nói về Command Query Separation, sau đó mới là phần Command Query Responsibility Segregation. Mục đích chính là chúng ta đi từ cách tiếp cận đơn giản đến phức tạp.<br /></p><h2 style="text-align: left;">Command Query Separation</h2><div style="text-align: left;"></div><div style="text-align: left;">CQS là một nguyên tắc thiết kế mẫu nơi mà việc gửi lệnh (command) và truy vấn (query) phải được tách biệt hoàn toàn. Nguyên tắc này đề xuất rằng mỗi phương thức trong hệ thống phần mềm sẽ là hoặc một lệnh (command) hoặc một truy vấn (query), nhưng không bao giờ cả hai. Điều này giúp đảm bảo tính rõ ràng và dễ bảo trì của mã nguồn.</div>
<p style="text-align: left;">Ý tưởng cơ bản là chia các method của 1 đối tượng thành 2 loại:<br /></p><div style="text-align: left;"><ul style="text-align: left;"><li>Command (Lệnh): Được sử dụng để thực hiện các thay đổi dữ liệu, như thêm, sửa, xóa.</li><li>Query (Truy vấn): Được sử dụng để đọc dữ liệu, không gây ảnh hưởng đến trạng thái của hệ thống.</li></ul></div><div style="text-align: left;">Bạn có thể tham khảo bài viết của bác Martin Fowler tại <a href="https://martinfowler.com/bliki/CommandQuerySeparation.html" target="_blank">đây</a><br /></div><p style="text-align: left;">Giả sử bạn muốn viết 1 BookService, thông thường bạn sẽ có các hàm như sau:</p>
<pre><code class="csharp">interface IBookService
{
void Edit(string name);
Book GetById(long id);
IEnumerable<Book> GetAllBooks();
}</code></pre><p>
Ở đây bạn có 2 loại method Read và Write</p><ul><li>Hàm Edit không trả về giá trị, chỉ cập nhật Book theo name. Đây là hàm thay đổi dữ liệu (mutable data) => command</li><li>Hàm Get không thay đổi trạng thái (do not mutable the state). Đây là hàm get data => query</li></ul><p>Nếu bạn phân tách ra 2 service IBookQueryService và IBookCommandService thì việc quản lý source code trở nên dễ dàng và dễ hiểu hơn.</p>
<pre><code class="csharp">interface IBookCommandService
{
void Edit(string name);
}
interface IBookQueryService
{
Book GetById(long id);
IEnumerable<Book> GetAllBooks();
}</code></pre>
<h3>Một số sai lầm thường gặp</h3>
<p>Mình rút ra từ kinh nghiệm mình làm nên nhờ các bạn góp ý thêm</p>
<h4>Không phân biệt rõ ràng giữa Command và Query</h4>
Sai lầm
<pre><code class="csharp">interface IBookService
{
void Edit(string name);
Book GetById(long id);
IEnumerable<Book> GetAllBooks();
}</code></pre>
Cách khắc phục
<pre><code class="csharp">interface IBookCommandService
{
void Edit(string name);
}
interface IBookQueryService
{
Book GetById(long id);
IEnumerable<Book> GetAllBooks();
}</code></pre>
<h5>Quiz</h5>
Hàm nào dưới đây là hàm <b>Command</b> hợp lệ
<pre><code class="csharp">1. void getComment (int commentId);
2. Job createJob (Job job);
3. List<Comment> postComment (Comment comment);
4. void approveComment (int commentId);</code></pre>
=> Hàm 4
<p>Hàm nào là hàm Query hợp lệ</p>
<pre><code class="csharp">1. Task<List<Comment>> GetAllCommentAsync();
2. Task<Comment> GetCommentByIdAsync(int commentId);
3. Task GetAllCommentAsync();
4. Task<Comment> GetAllCommentByIdAsync(int commentId);</code></pre>
=> Hàm 1, 2, 4
<h4>Không tuân thủ nguyên tắc Single Responsibility</h4>
<pre><code class="csharp">public class CommentCommandService
{
// ...
public async Task PostCommentAndPostToSlack(string name, string url, string content)
{
var comment = await this.commentRepo.PostComment(name, url, content);
await this.slackService.SendMessage($@"
New comment posted:
=> Name: {name}
=> Url: {url}/commentId/{comment.Id}
=> Content: {content}
");
}
}</code></pre>
Khắc phục
<pre><code class="csharp">public class CommentCommandService
{
public async Task PostComment(string name, string url, string content)
{
await this.commentRepo.PostComment(name, url, content);
}
public async Task PostToSlack(PostComment comment)
{
await this.slackService.SendMessage(comment);
}
}</code></pre>
<h4>Nên sử dụng DTO</h4>
<p>DTO (Data transfer object): là các class đóng gói data để chuyển giữa client - server hoặc giữa các service trong microservice. Mục đích tạo ra DTO là để giảm bớt lượng info không cần thiết phải chuyển đi, và cũng tăng cường độ bảo mật.</p>
<pre><code class="csharp">public class ProductService
{
public Product GetProductDetails(int productId)
{
//...
return product;
}
}</code></pre>
Khắc phục
<pre><code class="csharp">public class ProductQueryService
{
public ProductDTO GetProductDetails(int productId)
{
//...
return productDto;
}
}</code></pre>
<h2>CQS và CRUD: Sự đồng hành trong thiết kế MVC</h2>
<p>Trong lập trình hướng đối tượng, Command Query Separation (CQS) là một design pattern thường gặp, tách biệt các thao tác thay đổi dữ liệu (command) khỏi các thao tác truy vấn dữ liệu (query). Mối liên hệ giữa CQS và CRUD (Create, Read, Update, Delete) trong MVC vô cùng chặt chẽ, mang lại nhiều lợi ích cho việc thiết kế ứng dụng.</p><p>CRUD theo cách thông thường:<br /></p><ul style="text-align: left;"><li>Create: Tạo một bản ghi mới trong database.</li><li>Read: Truy vấn và lấy dữ liệu từ database.</li><li style="text-align: left;">Update: Cập nhật thông tin một bản ghi hiện có.</li><li style="text-align: left;">Delete: Xóa một bản ghi khỏi database.</li></ul><p style="text-align: left;">Phân loại CRUD thành Command và Query: </p><ol style="text-align: left;"><li style="text-align: left;">CRUD Commands: Create, Update, Delete</li><li style="text-align: left;">CRUD Queries: READ</li></ol><p style="text-align: left;">Trong kiến trúc MVC truyền thống, các hành động CRUD thường được trộn lẫn trong các controller, action method, và view model. Nếu bạn sử dụng REST API thì sẽ phân chia theo HTTP Method</p><ol style="text-align: left;"><li style="text-align: left;">HTTP Commands: POST, PUT, DELETE, PATCH</li><li style="text-align: left;">HTTP Queries: GET</li></ol><p style="text-align: left;">Tương ứng bạn sẽ có các hàm trong Use-Case Design.</p><p style="text-align: left;">Ví dụ:</p><ol style="text-align: left;"><li style="text-align: left;">Commands: CreatePost, UpdatePost, DeletePost, PostComment, UpdateComment</li><li style="text-align: left;">Queries: GetPostById, GetAllPosts, GetCommentById, GetAllCommentsForPost</li></ol><h2 style="text-align: left;">Xây dựng hệ thống sử dụng CQS Pattern</h2><p style="text-align: left;">Bạn có thể tham khảo source code ở bài viết: <a href="https://www.dotnetcurry.com/patterns-practices/1461/command-query-separation-cqs">https://www.dotnetcurry.com/patterns-practices/1461/command-query-separation-cqs</a></p>
<p style="text-align: left;">Ở bài viết trên, tác giả đã nói khác rõ về cách implement 1 project console application sử dụng CQS như thế nào. Mình bổ sung thêm 1 số ý khác</p>
<p>Đầu tiên, chúng ta sẽ nói về việc chia tách ứng dụng ra thành nhiều component. Nếu ứng dụng của bạn chỉ có vài dòng code như sau thì việc sử dụng CQS sẽ không có nhiều ý nghĩa</p>
<pre><code class="csharp">//resolve context
var _context = container.Resolve<ApplicationDbContext>();
//save some books if there are none in the database
if (!_context.Books.Any())
{
_context.Books.Add(new Book()
{
Authors = "Andrew Hunt, David Thomas",
Title = "The Pragmatic Programmer",
InMyPossession = true,
DatePublished = new DateTime(1999, 10, 20),
});
_context.Books.Add(new Book()
{
Authors = "Robert C. Martin",
Title = "The Clean Coder: A Code of Conduct for Professional Programmers",
InMyPossession = false,
DatePublished = new DateTime(2011, 05, 13),
});
_context.SaveChanges();
_Log.Info("Books saved..");
}
_Log.Info("Retrieving all books the NON CQS Way..");
foreach (var _book in _context.Books)
{
_Log.InfoFormat("Title: {0}, Authors: {1}, InMyPossession: {2}", _book.Title, _book.Authors, _book.InMyPossession);
}</code></pre>
<p>Bạn nên phân ra làm mô hình 3 lớp chuẩn, ở đây mình phân ra làm 2 lớp để đơn giản hóa vấn đề: Console - Business - Database. Tầng business sẽ chứa các services liên quan tới việc xử lý logic.</p><p>Ở tầng Business, bạn sẽ chia ra làm 2 thành phần: Service Interfaces (cung cấp API) và Services (implement).</p><p>Tới bước này bạn thực hiện việc thêm Query Dispatchers để thực hiện gọi Query hay execute Command tương ứng.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGvQ1aNTCBWV_dTk6Nu9F3AMkb8ZPTNOtP0KQSi4iLdm2FldvI2HlLAsiEwi8nrBPkMLp8RqZr-MJuCLzaqc5i11h-yrtqXz7JkCn2ZdI8VBI83GnMijAF2ZiBc9g1r_6Rqe4_XOkY65hGfuQFvLYrDec_b2OpfariiYKCClfDJD-oJ-EHDwSXONFObYA/s1037/CQS%20design-pattern.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="774" data-original-width="1037" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGvQ1aNTCBWV_dTk6Nu9F3AMkb8ZPTNOtP0KQSi4iLdm2FldvI2HlLAsiEwi8nrBPkMLp8RqZr-MJuCLzaqc5i11h-yrtqXz7JkCn2ZdI8VBI83GnMijAF2ZiBc9g1r_6Rqe4_XOkY65hGfuQFvLYrDec_b2OpfariiYKCClfDJD-oJ-EHDwSXONFObYA/s16000/CQS%20design-pattern.png" /></a></div><p><br /></p>
Query Dispatcher
<pre><code class="csharp">/// <summary>
/// Dispatches a query and invokes the corresponding handler
/// </summary>
public interface IQueryDispatcher
{
/// <summary>
/// Dispatches a query and retrieves a query result
/// </summary>
/// <typeparam name="TParameter">Request to execute type</typeparam>
/// <typeparam name="TResult">Request Result to get back type</typeparam>
/// <param name="query">Request to execute</param>
/// <returns>Request Result to get back</returns>
TResult Dispatch<TParameter, TResult>(TParameter query)
where TParameter : IQuery
where TResult : IResult;
/// <summary>
/// Dispatches a query and retrieves am async query result
/// </summary>
/// <typeparam name="TParameter">Request to execute type</typeparam>
/// <typeparam name="TResult">Request Result to get back type</typeparam>
/// <param name="query">Request to execute</param>
/// <returns>Request Result to get back</returns>
Task<TResult> DispatchAsync<TParameter, TResult>(TParameter query)
where TParameter : IQuery
where TResult : IResult;
}
/// <summary>
/// Passed around to all allow dispatching a command and to be mocked by unit tests
/// </summary>
public interface ICommandDispatcher
{
/// <summary>
/// Dispatches a command to its handler
/// </summary>
/// <typeparam name="TParameter">Command Type</typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="command">The command to be passed to the handler</param>
TResult Dispatch<TParameter, TResult>(TParameter command) where TParameter : ICommand where TResult : IResult;
/// <summary>
/// Dispatches an async command to its handler
/// </summary>
/// <typeparam name="TParameter">Command Type</typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="command">The command to be passed to the handler</param>
Task<TResult> DispatchAsync<TParameter, TResult>(TParameter command) where TParameter : ICommand where TResult : IResult;
}</code></pre>
Implement Dispatcher
<pre><code class="csharp">public class QueryDispatcher : IQueryDispatcher
{
private readonly IComponentContext _Context;
public QueryDispatcher(IComponentContext context)
{
_Context = context ?? throw new ArgumentNullException(nameof(context));
}
public TResult Dispatch<TParameter, TResult>(TParameter query)
where TParameter : IQuery
where TResult : IResult
{
//Look up the correct QueryHandler in our IoC container and invoke the retrieve method
var _handler = _Context.Resolve<IQueryHandler<TParameter, TResult>>();
return _handler.Retrieve(query);
}
public async Task<TResult> DispatchAsync<TParameter, TResult>(TParameter query)
where TParameter : IQuery
where TResult : IResult
{
//Look up the correct QueryHandler in our IoC container and invoke the retrieve method
var _handler = _Context.Resolve<IQueryHandler<TParameter, TResult>>();
return await _handler.RetrieveAsync(query);
}
}
public class CommandDispatcher : ICommandDispatcher
{
private readonly IComponentContext _context;
public CommandDispatcher(IComponentContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public TResult Dispatch<TParameter, TResult>(TParameter command) where TParameter : ICommand where TResult : IResult
{
//Look up the correct CommandHandler in our IoC container and invoke the Handle method
var _handler = _context.Resolve<ICommandHandler<TParameter, TResult>>();
return _handler.Handle(command);
}
public async Task<TResult> DispatchAsync<TParameter, TResult>(TParameter command) where TParameter : ICommand where TResult : IResult
{
//Look up the correct CommandHandler in our IoC container and invoke the async Handle method
var _handler = _context.Resolve<ICommandHandler<TParameter, TResult>>();
return await _handler.HandleAsync(command);
}
}</code></pre>
Handler
<pre><code class="csharp">/// <summary>
/// Base interface for command handlers
/// </summary>
/// <typeparam name="TParameter"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface ICommandHandler<in TParameter, TResult> where TParameter : ICommand
where TResult : IResult
{
/// <summary>
/// Executes a command handler
/// </summary>
/// <param name="command">The command to be used</param>
TResult Handle(TParameter command);
/// <summary>
/// Executes an async command handler
/// </summary>
/// <param name="command">The command to be used</param>
Task<TResult> HandleAsync(TParameter command);
}
/// <summary>
/// Base interface for query handlers
/// </summary>
/// <typeparam name="TParameter">Request type</typeparam>
/// <typeparam name="TResult">Request Result type</typeparam>
public interface IQueryHandler<in TParameter, TResult> where TResult : IResult where TParameter : IQuery
{
/// <summary>
/// Retrieve a query result from a query
/// </summary>
/// <param name="query">Request</param>
/// <returns>Retrieve Request Result</returns>
TResult Retrieve(TParameter query);
/// <summary>
/// Retrieve a query result async from a query
/// </summary>
/// <param name="query">Request</param>
/// <returns>Retrieve Request Result</returns>
Task<TResult> RetrieveAsync(TParameter query);
}</code></pre>
Implement Handler
<pre><code class="csharp">public abstract class QueryHandler<TParameter, TResult> : IQueryHandler<TParameter, TResult>
where TResult : IResult, new()
where TParameter : IQuery, new()
{
protected readonly ILog Log;
protected ApplicationDbContext ApplicationDbContext;
protected QueryHandler(ApplicationDbContext applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
Log = LogManager.GetLogger(GetType().FullName);
}
public TResult Retrieve(TParameter query)
{
var _stopWatch = new Stopwatch();
_stopWatch.Start();
TResult _queryResult;
try
{
//do authorization and validatiopn
//handle the query request
_queryResult = Handle(query);
}
catch (Exception _exception)
{
Log.ErrorFormat("Error in {0} queryHandler. Message: {1} \n Stacktrace: {2}", typeof(TParameter).Name, _exception.Message, _exception.StackTrace);
//Do more error more logic here
throw;
}
finally
{
_stopWatch.Stop();
Log.DebugFormat("Response for query {0} served (elapsed time: {1} msec)", typeof(TParameter).Name, _stopWatch.ElapsedMilliseconds);
}
return _queryResult;
}
public async Task<TResult> RetrieveAsync(TParameter query)
{
var _stopWatch = new Stopwatch();
_stopWatch.Start();
Task<TResult> _queryResult;
try
{
//do authorization and validatiopn
//handle the query request
_queryResult = HandleAsync(query);
}
catch (Exception _exception)
{
Log.ErrorFormat("Error in {0} queryHandler. Message: {1} \n Stacktrace: {2}", typeof(TParameter).Name, _exception.Message, _exception.StackTrace);
//Do more error more logic here
throw;
}
finally
{
_stopWatch.Stop();
Log.DebugFormat("Response for query {0} served (elapsed time: {1} msec)", typeof(TParameter).Name, _stopWatch.ElapsedMilliseconds);
}
return await _queryResult;
}
/// <summary>
/// The actual Handle method that will be implemented in the sub class
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected abstract TResult Handle(TParameter request);
/// <summary>
/// The actual async Handle method that will be implemented in the sub class
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected abstract Task<TResult> HandleAsync(TParameter request);
}
public abstract class CommandHandler<TRequest, TResult> : ICommandHandler<TRequest, TResult>
where TRequest : ICommand
where TResult : IResult, new()
{
protected readonly ILog Log;
protected ApplicationDbContext ApplicationDbContext;
protected CommandHandler(ApplicationDbContext context)
{
ApplicationDbContext = context;
Log = LogManager.GetLogger(GetType().FullName);
}
public TResult Handle(TRequest command)
{
var _stopWatch = new Stopwatch();
_stopWatch.Start();
TResult _response;
try
{
//do data validation
//do authorization
_response = DoHandle(command);
}
catch (Exception _exception)
{
Log.ErrorFormat("Error in {0} CommandHandler. Message: {1} \n Stacktrace: {2}", typeof(TRequest).Name, _exception.Message, _exception.StackTrace);
throw;
}
finally
{
_stopWatch.Stop();
Log.DebugFormat("Response for query {0} served (elapsed time: {1} msec)", typeof(TRequest).Name, _stopWatch.ElapsedMilliseconds);
}
return _response;
}
public async Task<TResult> HandleAsync(TRequest command)
{
var _stopWatch = new Stopwatch();
_stopWatch.Start();
Task<TResult> _response;
try
{
//do data validation
//do authorization
_response = DoHandleAsync(command);
}
catch (Exception _exception)
{
Log.ErrorFormat("Error in {0} CommandHandler. Message: {1} \n Stacktrace: {2}", typeof(TRequest).Name, _exception.Message, _exception.StackTrace);
throw;
}
finally
{
_stopWatch.Stop();
Log.DebugFormat("Response for query {0} served (elapsed time: {1} msec)", typeof(TRequest).Name, _stopWatch.ElapsedMilliseconds);
}
return await _response;
}
// Protected methods
protected abstract TResult DoHandle(TRequest request);
protected abstract Task<TResult> DoHandleAsync(TRequest request);
}</code></pre>Còn đây là Source code từ dotnetcurry: <a href="https://www.mediafire.com/file/615wkai93s000yn/Cqs.SampleApp.Console.7z/file" rel="nofollow" target="_blank">Mediafire</a><h2>Tham khảo</h2><p>
<a href="https://martinfowler.com/bliki/CommandQuerySeparation.html" rel="nofollow" target="_blank">Command Query Separation</a></p><p><a href="https://www.dotnetcurry.com/patterns-practices/1461/command-query-separation-cqs">Command Query Separation (CQS) - A simple but powerful pattern</a></p><p><a href="https://khalilstemmler.com/articles/oop-design-principles/command-query-separation/" rel="nofollow" target="_blank">Command Query Separation | Object-Oriented Design Principles w/ TypeScript</a> </p><p><a href="https://medium.com/aeturnuminc/microservices-using-mediatr-on-net-core-3-1-with-exception-handling-c273a7aa4a70" target="_blank">Microservices using MediatR on .Net Core 3.1 with exception handling</a>
<a href="https://www.hieutech.vn/2022/09/trien-khai-cqrs-pattern-voi-mediatr-trong-netcore.html" target="_blank">Triển khai CQRS Pattern với MediatR trong .NET Core</a></p><p><a href="https://kariera.future-processing.pl/blog/cqrs-simple-architecture/" rel="nofollow" target="_blank">CQRS – Simple architecture</a> </p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-84598153366538393172024-01-27T21:48:00.020+07:002024-01-28T11:44:14.660+07:00Containerizing Web Apps trên Azure App Service<p>Trong bài viết này, mình sẽ hướng dẫn các bạn deploy web app trên Azure App Service sử dụng image. Trước tiên, chúng ta cần nhắc lại 1 số khái niệm: image, container, container registry, Azure App Service.</p><h2 style="text-align: left;">Khái niệm <br /></h2><div style="text-align: left;"><b>Docker image</b>: Một dạng tập hợp các file của ứng dụng, được tạo ra bởi Docker engine. Nội dung của các Docker image sẽ không bị thay đổi khi di chuyển. Docker image được dùng để chạy các Docker container. Do tính chất read-only của chúng, những images này đôi khi được gọi là snapshots.</div><p><b>Container</b>: một dạng runtime của các Docker image, dùng để làm môi trường chạy ứng dụng.</p><h4 style="text-align: left;">Container Registry</h4><p>Container Registry là dịch vụ lưu trữ tập trung cho các Docker image. Container Registry giúp bạn dễ dàng quản lý, chia sẻ và triển khai các image container.
</p><p>Giả sử bạn có một ứng dụng web ASP.NET Core đơn giản. Bạn có thể sử dụng Docker để đóng gói ứng dụng này vào một image container. Sau đó, bạn có thể push image này lên Container Registry. Khi bạn muốn triển khai ứng dụng, bạn có thể pull image từ Container Registry và chạy nó trên bất kỳ server nào có cài đặt Docker.</p>Các loại Container Registry phổ biến:<ul style="text-align: left;"><li>Docker Hub: Docker Hub là public registry lớn nhất cho các Docker image. Bạn có thể tìm thấy hàng triệu image miễn phí trên Docker Hub.</li><li>Azure Container Registry: Azure Container Registry là dịch vụ Container Registry được cung cấp bởi Microsoft. Azure Container Registry cung cấp nhiều tính năng nâng cao, chẳng hạn như tích hợp với Azure DevOps và Azure Kubernetes Service.</li><li>Amazon Elastic Container Registry (ECR): Amazon ECR là dịch vụ Container Registry được cung cấp bởi Amazon Web Services (AWS). ECR cung cấp nhiều tính năng nâng cao, chẳng hạn như tích hợp với AWS Lambda và Amazon ECS.</li></ul><p><b>Azure App Service</b> là dịch vụ Platform as a Service (PaaS) được cung cấp bởi Microsoft. App Service giúp bạn dễ dàng triển khai và quản lý các ứng dụng web, API và mobile backend.</p><h2 style="text-align: left;">Cài đặt Extension cho Visual Studio Code</h2><ol style="text-align: left;"><li style="text-align: left;">Azure Tools</li><li style="text-align: left;">Docker </li></ol><h2 style="text-align: left;">Containerizing trên Docker Engine</h2><p>Bạn tạo file Docker có nội dung như sau trong Visual Studio code</p>
<pre><code class="Dockerfile">FROM mcr.microsoft.com/appsvc/dotnetcore:lts
ENV PORT 8080
EXPOSE 8080
ENV ASPNETCORE_URLS "http://*:${PORT}"
ENTRYPOINT ["dotnet", "/defaulthome/hostingstart/hostingstart.dll"]
</code></pre>
Bạn có thể tham khảo Dockerfile cho các ngôn ngữ khác:<a href="https://learn.microsoft.com/en-us/azure/app-service/quickstart-custom-container?tabs=dotnet&pivots=container-linux-vscode"> https://learn.microsoft.com/en-us/azure/app-service/quickstart-custom-container?tabs=dotnet&pivots=container-linux-vscode</a><p>Build image và run container</p>
<pre><code class="bash">docker build:latest -t demo -f Dockerfile .
docker run --name test -p 8080:8080 -d demo</code></pre>
Giải thích:
<ul><li>-d: run container ở chế độ background</li>
<li>-p: setup port từ internal port (docker) ra bên ngoài external port</li>
</ul>
Log
<pre><code class="bash">2024-01-27 21:22:34 Hosting environment: Production
2024-01-27 21:22:34 Content root path: /defaulthome/hostingstart/
2024-01-27 21:22:34 Now listening on: http://[::]:8080
2024-01-27 21:22:34 Application started. Press Ctrl+C to shut down.
2024-01-28 10:39:16 Hosting environment: Production
2024-01-28 10:39:16 Content root path: /defaulthome/hostingstart/
2024-01-28 10:39:16 Now listening on: http://[::]:8080
2024-01-28 10:39:16 Application started. Press Ctrl+C to shut down.</code></pre>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjghrIyR9DlePwkev7CyBH2ox26XyIFaDk-rD05gmy42BqGcdnKGHiA95u2L-p-l5daKmt8bjfkJCKXQbeKdgn920nj1MtOaUv3nOps1MGmZOl5EUFmFKvNX0kgdcgFIRDw42lJt2RL0bnYpCdDj9E3iJArCaT6gn8-ExPh-96cSN38jhCyVzJ5guPtb3Q/s1128/Screenshot%202024-01-28%20at%2010-57-10%20Microsoft%20Azure%20App%20Service%20-%20Welcome.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="575" data-original-width="1128" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjghrIyR9DlePwkev7CyBH2ox26XyIFaDk-rD05gmy42BqGcdnKGHiA95u2L-p-l5daKmt8bjfkJCKXQbeKdgn920nj1MtOaUv3nOps1MGmZOl5EUFmFKvNX0kgdcgFIRDw42lJt2RL0bnYpCdDj9E3iJArCaT6gn8-ExPh-96cSN38jhCyVzJ5guPtb3Q/s16000/Screenshot%202024-01-28%20at%2010-57-10%20Microsoft%20Azure%20App%20Service%20-%20Welcome.png" /></a></div>
<h2 style="text-align: left;">Deploy Web Apps trên Azure App Service</h2><p><b>Build image bằng Visual Studio Code</b></p>
<p>Bấm F1, chọn Docker images: Build Image</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyAqX5ho_E7FEE-H5GcYI9ukzrBK3iEaxGgOFCsN1IzDjFDH24_lqE4ec8rXuX85l6ZtC9NEQBoPHeVljG-VyqIJtU6cjBptimaQW963UMDe501Eo83e6diXCXeDm7UQSJt6Iv3kGZdxf5dgtdnL75JuNKb6ffzuVAxXu95ZF-wrG6QPyoAVVTjI3k5SM/s985/build-image.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="483" data-original-width="985" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyAqX5ho_E7FEE-H5GcYI9ukzrBK3iEaxGgOFCsN1IzDjFDH24_lqE4ec8rXuX85l6ZtC9NEQBoPHeVljG-VyqIJtU6cjBptimaQW963UMDe501Eo83e6diXCXeDm7UQSJt6Iv3kGZdxf5dgtdnL75JuNKb6ffzuVAxXu95ZF-wrG6QPyoAVVTjI3k5SM/s16000/build-image.png" /></a></div><br /><p>Bạn có thể build bằng command docker build</p><p>Tạo Instance Azure Container Registry</p><p>Trên Azure Portal => Create a resource => Chọn Container Registries</p><p>Gõ Registry Name, ví dụ: <b>nhatkyhoctap</b>.azurecr.io</p><p>Sau khi tạo xong, bạn vào Container Registry => Settings => Access keys, sau đó enable Admin user. Bạn sẽ dùng settings này để deploy image lên App Service</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbS8_AVuz_DDXhNweILtdloJTlKVQ6200wgMPzhI2f2QFjr2tjHHm_L_pHIxwK8bc4jcqK3zZ7RruVwejM9zgYkYT1kNf_TC2ERm_lkNNx2FeWHcpAKMs09wJP1xdqjhpPGldtJAXI718Mz8UKtce_r9DkI4Epz6Oo-VPgszDMqP3G-qEZeMjV8zOZ1zk/s1094/Create%20a%20Container%20Registry.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="488" data-original-width="1094" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbS8_AVuz_DDXhNweILtdloJTlKVQ6200wgMPzhI2f2QFjr2tjHHm_L_pHIxwK8bc4jcqK3zZ7RruVwejM9zgYkYT1kNf_TC2ERm_lkNNx2FeWHcpAKMs09wJP1xdqjhpPGldtJAXI718Mz8UKtce_r9DkI4Epz6Oo-VPgszDMqP3G-qEZeMjV8zOZ1zk/s16000/Create%20a%20Container%20Registry.png" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div>Mở Visual Studio lên, chọn Terminal => Git Bash
<pre><code class="bash">$ docker login nhatkyhoctap.azurecr.io
Authenticating with existing credentials...
Login did not succeed, error: Error response from daemon: Get "https://whizregistry.azurecr.io/v2/": unauthorized: authentication required, visit https://aka.ms/acr/authorization for more information.
Username (nhatkyhoctap): nhatkyhoctap
Password:
Login Succeeded</code></pre>
Trong Visual Studio Code, nhấp chọn biểu tượng Docker, chọn image => Push
<pre><code class="bash">Executing task: docker image push nhatkyhoctap.azurecr.io/demo:latest
The push refers to repository [nhatkyhoctap.azurecr.io/nhatkyhoctap]
7a6051a3589b: Preparing
a63d75c8cef3: Preparing</code></pre>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-324383924917150770.post-68295903196633982024-01-08T14:26:00.006+07:002024-01-08T21:39:31.248+07:00Microsoft Defender for Cloud<p>Trong bài viết này, mình sẽ giới thiệu về Microsoft Defender for Cloud, cách bật tắt và sử dụng Ms Defender cho Blob Storage<br /></p><h2 style="text-align: left;">Giới thiệu</h2><p>Microsoft Defender for Cloud là một nền tảng bảo vệ cloud-native application (CNAPP) được tạo thành từ các biện pháp và biện pháp bảo mật được thiết kế để bảo vệ các ứng dụng dựa trên cloud khỏi nhiều mối đe dọa và lỗ hổng mạng khác nhau</p><h4 style="text-align: left;">Bật Microsoft Defender cho Blob Storage</h4><p>Bạn vào Azure Portal => Your blob Storage => Security + Networking => Microsoft Defender for Cloud => Settings => Bật các option từ Off => On</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqZK-PpS7kmW-PwUIrzAJ1PNLbXh8RdRtzI1kUf0P7RlHOA4zxif2k2NKw4bLFvIJYVT6mEOojQ1SGAatD6XkFNBdV7y43oJBIAARTBlI8_Ujr9OW_AWT9_WZZOpwUS65vzoWvUNBlkSz2aPqqJCjAQh604vU01bc566Pgnpy6pTe4BDBWiWWFz9vAK1E/s1153/Turn-on-Ms-Defender.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="761" data-original-width="1153" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqZK-PpS7kmW-PwUIrzAJ1PNLbXh8RdRtzI1kUf0P7RlHOA4zxif2k2NKw4bLFvIJYVT6mEOojQ1SGAatD6XkFNBdV7y43oJBIAARTBlI8_Ujr9OW_AWT9_WZZOpwUS65vzoWvUNBlkSz2aPqqJCjAQh604vU01bc566Pgnpy6pTe4BDBWiWWFz9vAK1E/s16000/Turn-on-Ms-Defender.png" /></a></div><br /><p>Lưu ý bạn cần có quyền Administrator để thực hiện thay đổi settings này.</p><p>Ngoài ra, bạn cần tạo Event Grid để nhận kết quả từ Microsoft Defender for Cloud </p><h4 style="text-align: left;">Tắt Microsoft Defender cho Blob Storage</h4><p>Bạn vào trang Microsoft Defender for Cloud > Environment settings > Your subscription.</p><p>Switch value Defender for Storage plan thành Off => chọn Save </p><h2 style="text-align: left;">Hướng dẫn nhận kết quả từ Microsoft Defender</h2><p>Chúng ta có các bước:</p><ol style="text-align: left;"><li>Upload file lên Blob Storage: https://nhatkyhoctap.blogspot.com/2023/06/azure-functions-su-dung-azurite-e.html</li><li>Nhận kết quả scan Malware</li><li>Xóa file nếu Malware detected</li></ol><p>Ở bước 1 và 3, bạn xem thêm API sử dụng Blob Storage để upload file</p><p>Ở bước 2, chúng ta có 2 cách:</p><ol style="text-align: left;"><li>Sử dụng Event Grid để nhận kết quả</li><li>Get Blob Index tag sau khi upload để nhận kết quả</li></ol>
<pre><code class="csharp">public static class MalwareHandler
{
[FunctionName("MalwareHandler")]
public static async Task Run([EventGridTrigger] EventGridEvent eventGridEvent,
ExecutionContext context,
ILogger logger)
{
try
{
// your code here
}
catch (Exception exception)
{
logger.LogError(exception, $"Unable to proceed message handler");
throw;
}
}
}</code></pre>
Để debug Event Grid, bạn dùng Postman
<pre><code class="json">{
"id": "f99520ad-3dfb-4adc-b703-04cba7b76549",
"topic": "/subscriptions/.../resourceGroups/rg-nhatkyhoctap-cloud-defender/providers/Microsoft.EventGrid/topics/evgt-nhatkyhoctap-cloud-defender",
"subject": "storageAccounts/nhatkyhoctapstorage/containers/abc/blobs/TEMP/f9cb50bf-2ef3-40d2-9776-7fd6a2ef77cd",
"data": {
"correlationId": "f99520ad-3dfb-4adc-b703-04cba7b76549",
"blobUri": "https://nhatkyhoctapstorage.blob.core.windows.net/abc/TEMP/f9cb50bf-2ef3-40d2-9776-7fd6a2ef77cd",
"eTag": "0x8DBDEB12F571CD3",
"scanFinishedTimeUtc": "2024-01-08T10:14:43.5217279Z",
"scanResultType": "CleanVerdict",
"scanResultDetails": {
"malwareNamesFound": [
"DOS/EICAR_Test_File"
],
"sha256": "..."
}
},
"eventType": "Microsoft.Security.MalwareScanningResult",
"eventTime": "2024-01-08T10:14:43.5223210Z",
"dataVersion": "1.0"
}</code></pre>
<p>Xem thêm: <a href="https://nhatkyhoctap.blogspot.com/2023/11/debug-event-grid-o-localhost.html">Debug Event Grid ở localhost </a></p>
<p>Ngoài ra, bạn có thể get Blob Index tag để kiểm tra trạng thái của Blob file. Sau khi scan file xong, Index tag sẽ cập nhật như sau:</p>
<pre><code class="json">Blob index tags
Key Value
Malware Scanning scan result No threats found
Malware Scanning scan time UTC 2023-11-20 05:56:29Z</code></pre>
Tham khảo thêm về cách lấy Index tag: <a href="https://nhatkyhoctap.blogspot.com/2023/12/azure-functions-blob-index-tag-va.html " target="_blank">Azure Functions: Blob Index tag và metadata </a><h2>Tham khảo</h2><p>
<a href="https://nhatkyhoctap.blogspot.com/2023/06/azure-functions-su-dung-azurite-e.html">https://nhatkyhoctap.blogspot.com/2023/06/azure-functions-su-dung-azurite-e.html</a></p><p><a href="https://nhatkyhoctap.blogspot.com/2023/12/azure-functions-blob-index-tag-va.html" target="_blank">Azure Functions: Blob Index tag và metadata </a><br /></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-37143122968453530262024-01-06T00:41:00.001+07:002024-01-06T00:41:04.994+07:00Tạo Azure Web App bằng Azure CLI<p>Trên môi trường Azure, bạn mở Bash và gõ 2 câu lệnh sau:</p>
<pre><code class="bash">az appservice plan create --name <App service plan name> --resource-group <resource group name>
az webapp create --resource-group <resource group name> --plan <App service plan name> --name <web app name></code></pre>
<p>Thay đổi <resource group name> với resource group có sẵn.</p>
<p><web app name>: Tên web app duy nhất không trùng trong phạm vi Global</p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-57569535312792163472024-01-01T22:55:00.008+07:002024-01-01T23:03:59.172+07:00Azure Bicep<h2 style="text-align: left;">Bicep là gì?</h2><p>Azure Bicep là một ngôn ngữ triển khai Infrastructure as Code (IaC) dành cho Azure. Được thiết kế để thay thế cho JSON, Bicep giúp đơn giản hóa quá trình viết và duy trì các file mô tả cấu hình hạ tầng Azure. Nó cung cấp cú pháp đơn giản, đọc hiểu dễ dàng và hỗ trợ tính tái sử dụng mã nguồn.</p><h2 style="text-align: left;">Tại sao phải sử dụng Bicep</h2><p>Bicep được thiết kế để thay thế cho ngôn ngữ ARM template, giúp giảm độ phức tạp của mã nguồn và cung cấp trải nghiệm phát triển tốt hơn. </p><p></p><p>Tham khảo thêm <a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep#benefits-of-bicep">https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep#benefits-of-bicep</a><br /></p><p><img alt="Bicep deployment comparison" data-linktype="relative-path" src="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/media/overview/bicep-processing.png" /></p><h3 style="text-align: left;">Cú pháp cơ bản</h3><p>Azure Bicep cung cấp cú pháp đơn giản và hiệu quả để triển khai cấu hình
hạ tầng trên Azure. Dưới đây là một số điểm quan trọng về cú pháp cơ
bản, bao gồm khai báo biến, sử dụng vòng lặp, và ví dụ minh họa. <br /></p>
<pre><code class="bash">var location = 'East US'
var resourceGroupName = 'MyResourceGroup'</code></pre>
Vòng lặp
param storageAccountNames array = ['storage1', 'storage2', 'storage3']
<pre><code class="bash">resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = [for name in storageAccountNames: {
name: name
location: 'East US'
sku: {
name: 'Standard_LRS'
}
}]</code></pre>
Ví dụ: Trong ví dụ này, chúng ta đã kết hợp cả khai báo biến, sử dụng vòng lặp để tạo storage account, và kết quả đầu ra để hiển thị các ID của tài khoản lưu trữ đã tạo.
<pre><code class="bash">var location = 'East US'
var resourceGroupName = 'MyResourceGroup'
param storageAccountNames array = ['storage1', 'storage2', 'storage3']
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = [for name in storageAccountNames: {
name: name
location: location
sku: {
name: 'Standard_LRS'
}
}]
output storageAccountIds array = [for account in storageAccount: account.id]</code></pre><p>
Bạn có thể thử thực hành deploy sử dụng Bicep ở link sau: <a href="https://learn.microsoft.com/en-us/azure/container-instances/container-instances-quickstart-bicep?tabs=CLI " target="_blank">Quickstart: Deploy a container instance in Azure using Bicep</a></p><p>Dưới đây là quickstart nếu bạn muốn sử dụng Bash</p><p>Bạn chuẩn bị file main.bicep</p><p>Mở Bash Shell trên Azure, gõ lệnh như sau:</p>
<pre><code class="bash">az deployment group create \
--resource-group <resource-group-name> \
--template-file <path-to-template-file> \
--parameters name=<container-name></code></pre>
<p> Bạn thay thế
resource-group-name: </p><p><resource-group-name>: tên resource group mà bạn muốn chứa resource </p><p><path-to-template>: đường dẫn tới file json vừa được upload </p><p><container-name>: tên container mà bạn muốn tạo</p>
<p>VD: </p>
<pre><code class="bash">az deployment group create \
--resource-group rg_eastus_123456 \
--template-file main.bicep \
--parameters name=abc</code></pre><p>
Kiểm tra kết quả bằng cách vào View all resource</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbT3HQpaXE6BjJqZF6nYw76_hyphenhyphenW7lWG873313XDXUjwa_HhyxOXgFtzO612WKxg6kF51QJiVNkpLxmCk90yrQzE2uamiDlkWE5PPTnV5SYpL2wjisEpvT8NLvtKmga69yt3fKvHmJrbq6IrIaqq35IZvjVyGM8lXIAeq-n72SNsdjBH61ZYjbaxN61xuI/s800/CheckArmDeploy.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="460" data-original-width="800" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbT3HQpaXE6BjJqZF6nYw76_hyphenhyphenW7lWG873313XDXUjwa_HhyxOXgFtzO612WKxg6kF51QJiVNkpLxmCk90yrQzE2uamiDlkWE5PPTnV5SYpL2wjisEpvT8NLvtKmga69yt3fKvHmJrbq6IrIaqq35IZvjVyGM8lXIAeq-n72SNsdjBH61ZYjbaxN61xuI/s16000/CheckArmDeploy.png" /> </a></div><h2 class="separator" style="clear: both; text-align: left;">Tham khảo</h2><div class="separator" style="clear: both; text-align: left;"><a href="https://blog.lionpham.com/2020/09/30/azure-bicep-iac/" target="_blank">Triển khai tài nguyên trên Azure với Bicep</a></div><div class="separator" style="clear: both; text-align: left;"> <br /></div><br /> <p></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-23594666270580645252023-12-31T18:21:00.001+07:002023-12-31T18:21:12.468+07:00ARM Template<h2 style="text-align: left;">ARM là gì?</h2><p>Azure Resource Manager (ARM) là một dịch vụ quản lý tài nguyên của Microsoft Azure. Nó là một framework cho phép bạn triển khai và quản lý tài nguyên trong môi trường Azure. ARM cung cấp một cách thức đơn giản và linh hoạt để triển khai và quản lý các tài nguyên của bạn trong Azure, từ máy ảo và dịch vụ lưu trữ đến các tài nguyên mạng và các thành phần khác.</p><h3 style="text-align: left;">Declarative Programming là gì?</h3><p>Declarative programming là mô hình lập trình trong đó lập trình viên chỉ định kết quả mong muốn, thay vì chỉ định các bước cụ thể để đạt được kết quả đó. Mô hình này thường được sử dụng để viết các chương trình có logic đơn giản, hoặc các chương trình cần được tối ưu hóa về hiệu suất.</p><p>Ví dụ: Với cách viết code thông thường (Imperative programming)</p><pre><code class="javascript">let array = [1, 2, 3, 4, 5, 6]
var reduced = 0
var filtered = []
for element in array {
reduced += element
if element % 2 == 1 {
filtered.append(element)
}
}
</code></pre>
Viết theo kiểu Declarative Progrmming
<pre><code class="javascript">let array = [1, 2, 3, 4, 5, 6]
let numbers = [1, 2, 3, 4, 5, 6]
let sum = reduce(numbers, 0, +)
let odds = filter(numbers, { $0 % 2 == 1})
</code></pre>
<p>Lưu ý là bạn cần có implement các hàm hoặc sử dụng hàm trong thư viện.</p><h2 style="text-align: left;">Deploy ARM Template bằng Azure CLI</h2>
Tạo file template.json như sau:
<pre><code class="json">{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.5.6.12127",
"templateHash": "17016281914347876853"
}
},
"parameters": {
"name": {
"type": "string",
"defaultValue": "acilinuxpublicipcontainergroup",
"metadata": {
"description": "Name for the container group"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"image": {
"type": "string",
"defaultValue": "mcr.microsoft.com/azuredocs/aci-helloworld",
"metadata": {
"description": "Container image to deploy. Should be of the form repoName/imagename:tag for images stored in public Docker Hub, or a fully qualified URI for other registries. Images from private registries require additional registry credentials."
}
},
"port": {
"type": "int",
"defaultValue": 80,
"metadata": {
"description": "Port to open on the container and the public IP address."
}
},
"cpuCores": {
"type": "int",
"defaultValue": 1,
"metadata": {
"description": "The number of CPU cores to allocate to the container."
}
},
"memoryInGb": {
"type": "int",
"defaultValue": 2,
"metadata": {
"description": "The amount of memory to allocate to the container in gigabytes."
}
},
"restartPolicy": {
"type": "string",
"defaultValue": "Always",
"allowedValues": [
"Always",
"Never",
"OnFailure"
],
"metadata": {
"description": "The behavior of Azure runtime if container has stopped."
}
}
},
"resources": [
{
"type": "Microsoft.ContainerInstance/containerGroups",
"apiVersion": "2021-09-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"properties": {
"containers": [
{
"name": "[parameters('name')]",
"properties": {
"image": "[parameters('image')]",
"ports": [
{
"port": "[parameters('port')]",
"protocol": "TCP"
}
],
"resources": {
"requests": {
"cpu": "[parameters('cpuCores')]",
"memoryInGB": "[parameters('memoryInGb')]"
}
}
}
}
],
"osType": "Linux",
"restartPolicy": "[parameters('restartPolicy')]",
"ipAddress": {
"type": "Public",
"ports": [
{
"port": "[parameters('port')]",
"protocol": "TCP"
}
]
}
}
}
],
"outputs": {
"containerIPv4Address": {
"type": "string",
"value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name'))).ipAddress.ip]"
}
}
}</code></pre>
<p>Bạn mở Azure Portal, mở Bash session từ Cloud Shell panel. Sau đó bấm chọn Show advanced settings.</p>
<p>Thêm thông tin storage account và fileshare. Sau đó bấm Create storage</p><p>Trong Cloud Shell Panel, bấm Upload rồi chọn file để upload lên.</p><p>VD: upload file template.json<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnsIfsiMhdxQVoMMYxNvXhJBTXiA783Aj4MwGP5iqmgiiwmVRQ3Y0Vt0WjDFEHpRh7VFFtvbTjDPzQ-Fj7GQO59FqJIIRW5rjyugUCaNR2fB2QbqFkb9s7bZtKHrHSO_fNs_AaQZWRxuYxaIPN2-mN-s_9pWzsbMR1O3ohE9cmhtRecG7BbuHKBdGaPGE/s756/UploadJsonTemplate.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="371" data-original-width="756" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnsIfsiMhdxQVoMMYxNvXhJBTXiA783Aj4MwGP5iqmgiiwmVRQ3Y0Vt0WjDFEHpRh7VFFtvbTjDPzQ-Fj7GQO59FqJIIRW5rjyugUCaNR2fB2QbqFkb9s7bZtKHrHSO_fNs_AaQZWRxuYxaIPN2-mN-s_9pWzsbMR1O3ohE9cmhtRecG7BbuHKBdGaPGE/s16000/UploadJsonTemplate.png" /></a>
</div>
<p>Sau đó bạn gõ lệnh</p>
<pre><code class="bash">az deployment group create --resource-group <resource-group-name> --template-file <path-to-template-file> --parameters name=<container-name></code></pre><p>
Bạn thay thế </p><ul style="text-align: left;"><li>resource-group-name: tên resource group mà bạn muốn chứa resource</li><li>path-to-template: đường dẫn tới file json vừa được upload</li><li>container-name: tên container mà bạn muốn tạo</li></ul><p>VD:</p>
<pre><code class="bash">az deployment group create --resource-group rg_eastus_1234 --template-file template.json --parameters name=abc</code></pre>
<p>Kiểm tra kết quả bằng cách vào View all resources</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbT3HQpaXE6BjJqZF6nYw76_hyphenhyphenW7lWG873313XDXUjwa_HhyxOXgFtzO612WKxg6kF51QJiVNkpLxmCk90yrQzE2uamiDlkWE5PPTnV5SYpL2wjisEpvT8NLvtKmga69yt3fKvHmJrbq6IrIaqq35IZvjVyGM8lXIAeq-n72SNsdjBH61ZYjbaxN61xuI/s1649/CheckArmDeploy.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="949" data-original-width="1649" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbT3HQpaXE6BjJqZF6nYw76_hyphenhyphenW7lWG873313XDXUjwa_HhyxOXgFtzO612WKxg6kF51QJiVNkpLxmCk90yrQzE2uamiDlkWE5PPTnV5SYpL2wjisEpvT8NLvtKmga69yt3fKvHmJrbq6IrIaqq35IZvjVyGM8lXIAeq-n72SNsdjBH61ZYjbaxN61xuI/s16000/CheckArmDeploy.png" /></a></div><br /><p></p>
<h2 style="text-align: left;">Tham khảo</h2><a href="https://tutorialsdojo.com/azure-101-azure-resource-manager-and-arm-templates/">https://tutorialsdojo.com/azure-101-azure-resource-manager-and-arm-templates/</a>
<p><a href="https://www.netguru.com/blog/imperative-vs-declarative">https://www.netguru.com/blog/imperative-vs-declarative</a></p><p>Labs: <a href="https://learn.microsoft.com/en-us/azure/container-instances/container-instances-quickstart-template">https://learn.microsoft.com/en-us/azure/container-instances/container-instances-quickstart-template</a> <br /></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-41662585044189223202023-12-29T18:17:00.005+07:002023-12-29T18:25:08.367+07:00Azure Functions: Blob Index tag và metadata<p>Trong bài viết này, chúng ta sẽ đi qua các khái niệm về container, blobName, metadata và blob index. Từ đó chúng ta sẽ dễ dàng quản lý file trên container</p><h2 style="text-align: left;">Container</h2><p>Container là một thư mục ảo để chứa các blob. Mỗi container phải có một tên duy nhất trong storage account<br /></p><h2 style="text-align: left;">BlobName<br /></h2><p>BlobName là tên của một blob. BlobName phải là duy nhất trong container.</p>
Ví dụ khi bạn mở Properties của 1 blob file trên Azure
<pre><code class="bash">Overview
Blob: TEMP/026a9132-16a3-4f14-9186-bfcb0eff4d3e
URL: https://abc-xyz.blob.core.windows.net/abc/TEMP/026a9132-16a3-4f14-9186-bfcb0eff4d3e
...
Metadata
Key Value
...
Blob index tags
Key Value
...
</code></pre>
Trong đó:
<ul style="text-align: left;"><li>container: abc</li><li>blobName: TEMP/026a9132-16a3-4f14-9186-bfcb0eff4d3e</li></ul>
Chúng ta triển khai hàm upload file như sau:
<pre><code class="csharp">//...
var blobServiceClient = new BlobServiceClient(connectionString);
_blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);
//...
public BlobClient Upload(byte[] data, string blobName, string containerName)
{
var binaryData = new BinaryData(data);
var blobClient = _blobContainerClient.GetBlobClient($"{containerName}/{blobName}");
blobClient.Upload(binaryData, true);
return blobClient;
}</code></pre>
<h2>Metadata</h2>
<p> Metadata là thông tin mô tả blob, ví dụ như name, status, type, v.v.</p>
Ví dụ:
<pre><code class="csharp">async Task SetMetadata(string storageAccountName, string accessKey, string blobBaseUri, string containerName, string blobName)
{
var sharedKeyCredential = new StorageSharedKeyCredential(storageAccountName, accessKey);
var blobServiceClient = new BlobServiceClient(new Uri(blobBaseUri), sharedKeyCredential);
var containerClient = blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
// Set the metadata.
Dictionary<string, string> metadata = new();
metadata.Add("Status", "1");
blobClient.SetMetadata(metadata);
// Get the metadata.
BlobProperties blobProperties = await blobClient.GetPropertiesAsync();
Console.WriteLine("Blob metadata:");
// Enumerate the blob's metadata.
foreach (var metadataItem in blobProperties.Metadata)
{
Console.WriteLine($"\tKey: {metadataItem.Key}");
Console.WriteLine($"\tValue: {metadataItem.Value}");
}
}</code></pre>
<p>Nhược điểm của metadata là không thể dùng query để search trên Storage Account</p>
<h2>Index tag</h2>
<p>Vào tháng 5/2020, Microsoft thông báo một tính năng mới cho Azure blob Storage là Blob Index. Đây là tính năng cho phép bạn thêm key/value tags vào Blob object. Bạn có thể dễ dạng query Blob object mà không cần sử dụng dịch vụ Azure Search.</p>
<p>Có 1 số giới hạn có Index Tags</p><ul style="text-align: left;"><li>Mỗi blob có tối đa 10 blob index tag</li><li>Tag key có độ dài từ 1-128 kí tự</li><li>Tag value có độ dài từ 0-256 kí tự </li><li>Tag key và value phân biệt chữ hoa và thường</li><li>Chỉ hỗ trợ string</li><li>...</li></ul><p>Dưới đây là Service dùng để upload và kiểm tra index tag</p>
<pre><code class="csharp">public class StorageService
{
private readonly BlobServiceClient _client;
public StorageService()
{
var connectionString = "DefaultEndpointsProtocol=https;AccountName=...";
_client = new BlobServiceClient(connectionString);
}
public async Task UploadFile(string containerName, string filePath, Dictionary<string, string> tags)
{
string fileName = Path.GetFileName(filePath);
BlobContainerClient container = _client.GetBlobContainerClient(containerName);
BlobClient blob = container.GetBlobClient(fileName);
using FileStream fileStream = File.OpenRead(filePath);
await blob.UploadAsync(fileStream, true);
fileStream.Close();
await blob.SetTagsAsync(tags);
}
public async Task<List<TaggedBlobItem>> FindCustomerFiles(string customerName, string containerName = "")
{
var foundItems = new List<TaggedBlobItem>();
string searchExpression = $"\"customer\"='{customerName}'";
if (!string.IsNullOrEmpty(containerName))
searchExpression = $"@container = '{containerName}' AND \"customer\" = '{customerName}'";
await foreach (var page in _client.FindBlobsByTagsAsync(searchExpression).AsPages())
{
foundItems.AddRange(page.Values);
}
return foundItems;
}
}</code></pre>Kiểm tra Service:<br /><pre><code class="csharp">var storageService = new StorageService();
var files = await storageService.FindCustomerFiles(customerName: "Acme Inc.", containerName: "abc");
foreach (var file in files)
{
Console.WriteLine(file.BlobName);
}</code></pre><p>
Lưu ý: Hiện tại hàm FindBlobsByTagsAsync() chỉ hoạt động với Azure Storage Account, không hoạt động được với Emulator Storage Account. </p><h3 style="text-align: left;">Một số quy tắc cho query</h3><p>Sử dụng dấu nháy đơn cho key và dấu nháy kép cho value</p><p>Blob index filter chỉ hoạt động tốt cho query 1 tag hoặc sử dụng >, >=, <, <= trên 1 thẻ duy nhất.<br /></p><h2>Tham khảo</h2>
<a href="https://dev.to/pietervdw/searching-azure-blob-storage-files-using-blob-index-lok" rel="nofollow" target="_blank">Searching Azure Blob Storage using Blob Index</a>
<p>Developing Solutions for Microsoft Azure AZ-204 Exam Guide</p><p><a href="https://medium.com/linkit-intecs/azure-blob-storage-index-tags-f5ff68430ca2" target="_blank">Azure Blob Storage Index Tags</a> </p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-81458469907547973752023-12-11T17:11:00.009+07:002023-12-11T17:28:02.388+07:00Interceptor trong ASP.NET MVC<p>Interceptor là một trong những khái niệm quan trọng trong lập trình, cho phép chúng ta mở rộng và thay đổi hành vi của các phương thức mà không phải sửa đổi code nguồn gốc. Trong bài viết này, chúng ta sẽ khám phá cách sử dụng Interceptor trong Autofac, một container dependency injection phổ biến trong cộng đồng .NET.</p><p>Mình minh họa bằng .NET Framework, các bạn dựa theo đó làm cho ví dụ .NET nha</p><h2 style="text-align: left;">Giới thiệu về Interceptor</h2><p>Interceptor là một cơ chế cho phép chúng ta "giữ lại" việc gọi một phương thức và thực hiện các hành động nào đó trước và sau khi phương thức đó được gọi. Điều này thường được sử dụng để thêm logic chung, theo dõi, đo lường hoặc thậm chí thay đổi hành vi của các phương thức.</p><h2 style="text-align: left;">Cài đặt</h2><p>Tạo Project ASP.NET MVC. Sau đó, cài đặt các package sau:</p>
<pre><code class="bash">Install-Package Autofac.Extensions.DependencyInjection
Install-Package Autofac.Mvc5
Autofac.Extras.DynamicProxy</code></pre>
<p>Bạn cần có service và 1 interceptor để gắn vào service đó.</p>
Thêm IMessageService và MessageService
<pre><code class="csharp">public interface IMessageService
{
string GetMessage();
}
public class MessageService : IMessageService
{
public string GetMessage()
{
return DateTime.Now.ToString();
}
}</code></pre>
Khai báo LoggingInterceptor
<pre><code class="csharp">using Castle.DynamicProxy;
using System;
//...
public class LoggingInterceptor: IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"Before calling method {invocation.Method.Name}");
invocation.Proceed(); //call service method
Console.WriteLine($"After calling method {invocation.Method.Name}");
}
}</code></pre>
Đăng ký Dependency Injection
<pre><code class="csharp">using AutofacInterceptorExample.Interceptors;
using AutofacInterceptorExample.Services;
using Autofac.Extras.DynamicProxy;
//...
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<LoggingInterceptor>();
containerBuilder.RegisterType<MessageService>().As<IMessageService>().EnableInterfaceInterceptors()
.InterceptedBy(typeof(LoggingInterceptor));
containerBuilder.RegisterControllers(typeof(MvcApplication).Assembly);
var container = containerBuilder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}</code></pre>
Cuối cùng ở HomeController, bạn thêm IMessageService vào
<pre><code class="csharp">using AutofacInterceptorExample.Services;
using System;
using System.Web.Mvc;
//...
public class HomeController : Controller
{
private readonly IMessageService _messageService;
public HomeController(IMessageService messageService)
{
_messageService = messageService;
}
public ActionResult Index()
{
var testMessage = _messageService.GetMessage();
Console.Write(testMessage);
return View();
}
}</code></pre>
<p>Trường hợp bạn muốn khai báo Interceptor tự động, bạn khai báo thêm InterceptorExtension</p>
<pre><code class="csharp">public static class InterceptorExtension
{
public static IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> EnableInterceptor
<TLimit, TActivatorData, TSingleRegistrationStyle>(
this IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> registration)
{
return registration.EnableInterfaceInterceptors().InterceptedBy(typeof(LoggingInterceptor));
}
}</code></pre>
Trong hàm đăng ký Depednecy Injection, bạn sửa lại như sau:
<pre><code class="csharp">var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<LoggingInterceptor>();
containerBuilder.RegisterType<MessageService>().As<IMessageService>().EnableInterceptor();</code></pre>
<h2>Tham khảo</h2><p>
<a href="https://blog.ivankahl.com/introduction-to-aspect-oriented-programming-in-dotnet-with-autofac-interceptors/" rel="nofollow" target="_blank">Introduction to Aspect-Oriented Programming (AOP) in .NET with Autofac Interceptors</a></p><p>Chúc các bạn thành công</p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-9679977932136105782023-12-10T21:15:00.020+07:002023-12-10T22:32:44.665+07:00RFC 7807<h2 style="text-align: left;">Giới thiệu</h2><p>Giả sử bạn có nhiều API xử lý data trong một ứng dụng. Khi 1 API xảy ra lỗi, nó sẽ trả về error code</p><p>Có thể trả theo cách này</p>
<pre><code class="json">{
"isSuccess": "true",
"errorDetai": "An internal server error occurred."
}</code></pre>
<p>Hoặc cách này</p>
<pre><code class="json">{
"isSuccess": "true",
"error": {
"errorDetai": "An internal server error occurred.",
"errorCode": "401"
}
}</code></pre><p>Và còn rất nhiều cách khác nữa<br /></p>
<p>Rất may là chúng ta có RFC 7807. RFC 7807 là một tiêu chuẩn Internet xác định một "problem detail" là một cách để mang theo các chi tiết lỗi có thể đọc được bằng máy trong phản hồi HTTP để tránh cần định nghĩa các định dạng phản hồi lỗi mới cho API HTTP.</p>
<p>Trước RFC 7807, các API HTTP thường sử dụng các định dạng phản hồi lỗi riêng lẻ. Điều này có thể gây khó khăn cho các API trong việc xử lý các lỗi, vì các API này cần phải hiểu định dạng lỗi cụ thể của mỗi API.</p><h2 style="text-align: left;">HTTP Code error<br /></h2><p>HTTP code error là một thông báo được gửi từ server về phía client, cho biết đã có lỗi xảy ra trong quá trình xử lý request. Mỗi code error có một ý nghĩa riêng, cung cấp thông tin về bản chất của lỗi và giúp client biết cách xử lý tiếp theo.</p><p>Chữ số đầu tiên của HTTP status code chỉ định 1 trong 5 loại phản hồi quy chuẩn. Các cụm tin nhắn được hiển thị chỉ mang tính tượng trưng, nhưng cũng có thể cung cấp bất kỳ thông tin bổ sung nào để chúng ta có thể đọc được. Trừ khi có những chỉ định khác, HTTP status code được xem như 1 phần của quy chuẩn HTTP/1.1 (RFC 7231).<br /><br />Cơ Quan Cấp Số Được Ấn Định Trên Internet (tức IANA hay The Internet Assigned Numbers Authority) chính là nơi duy trì sổ đăng ký chính thức của các HTTP status code.<br />HTTP Code Categories<br /><br />HTTP code error được phân thành 5 nhóm chính:<br /></p><ol style="text-align: left;"><li>Informational responses (100–199): Chỉ ra rằng request đã được nhận và đang được xử lý. Ví dụ: 100 Continue.</li><li>Successful responses (200–299): Xác nhận rằng request đã được thực hiện thành công. Ví dụ: 200 OK, 201 Created.</li><li>Redirection messages (300–399): Yêu cầu client chuyển hướng đến một URL khác để hoàn thành request. Ví dụ: 301 Moved Permanently, 302 Found.</li><li>Client error responses (400–499): Chỉ ra lỗi do phía client gây ra, chẳng hạn như request không hợp lệ hoặc tài nguyên không được tìm thấy. Ví dụ: 400 Bad Request, 404 Not Found.</li><li>Server error responses (500–599): Xác nhận rằng server gặp lỗi và không thể thực hiện request. Ví dụ: 500 Internal Server Error, 503 Service Unavailable.</li></ol><h3 style="text-align: left;">Giải thích từng nhóm:</h3><p>1. Informational responses (100–199):<br /><br /> 100 Continue: Server đang chờ client gửi tiếp dữ liệu.<br /> 101 Switching Protocols: Server đã chuyển sang giao thức khác.<br /><br />2. Successful responses (200–299):<br /><br /> 200 OK: Request đã thành công và trả về dữ liệu.<br /> 201 Created: Request đã tạo tài nguyên mới.<br /> 202 Accepted: Request đã được nhận và đang được xử lý.<br /> 204 No Content: Request thành công nhưng không có dữ liệu trả về.<br /><br />3. Redirection messages (300–399):<br /><br /> 301 Moved Permanently: Tài nguyên đã được chuyển đến một URL khác.<br /> 302 Found: Tài nguyên tạm thời được chuyển đến một URL khác.<br /> 303 See Other: Client nên truy cập một URL khác để hoàn thành request.<br /> 304 Not Modified: Tài nguyên chưa được thay đổi kể từ lần truy cập trước đó.<br /><br />4. Client error responses (400–499):<br /><br /> 400 Bad Request: Request không hợp lệ.<br /> 401 Unauthorized: Client không được phép truy cập tài nguyên.<br /> 403 Forbidden: Client không có quyền truy cập tài nguyên.<br /> 404 Not Found: Tài nguyên không được tìm thấy.<br /> 405 Method Not Allowed: Server không hỗ trợ phương thức request.<br /> 409 Conflict: Request gây ra xung đột.<br /><br />5. Server error responses (500–599):<br /><br /> 500 Internal Server Error: Server gặp lỗi.<br /> 501 Not Implemented: Server không thể thực hiện request.<br /> 502 Bad Gateway: Server nhận được lỗi từ server khác.<br /> 503 Service Unavailable: Server không thể xử lý request do quá tải.<br /> 504 Gateway Timeout: Server không nhận được phản hồi kịp thời từ server khác.<br /></p>
<p></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://media.licdn.com/dms/image/D4D12AQH3Sd9RI6WeJg/article-inline_image-shrink_1000_1488/0/1663585021845?e=1707955200&v=beta&t=b5tReePD9ny39bIHXIpEF67bTDpoiToC7khHdsI9yog" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="800" data-original-width="651" src="https://media.licdn.com/dms/image/D4D12AQH3Sd9RI6WeJg/article-inline_image-shrink_1000_1488/0/1663585021845?e=1707955200&v=beta&t=b5tReePD9ny39bIHXIpEF67bTDpoiToC7khHdsI9yog" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">HTTP Codes define by RFC-7231 (Source: Cheatography)</td></tr></tbody></table><br /> <p></p><h2 style="text-align: left;">RFC 7807 <br /></h2><p>Mục đích của đặc tả RFC 7807 là xác định các lỗi phổ biến (common error format) cho các ứng dụng khi cần đến chúng</p><p>Ví dụ</p>
<pre><code class="javascript">HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://myexample.com/issues/out-of-credit",
"title": "You do not have enough credit to book a room.",
"detail": "Your current balance is 30 EUR, but a room costs 50 EUR per night.",
"instance": "/accounts/12345/errors/out-of-credit/err_instance_id=123",
"balance": 30,
"account": "/accounts/12345"
}</code></pre>
<p>RFC 7807 giới thiệu 1 cách để standardized các report problem để báo cáo các sự cố cho máy khách. Tiêu chuẩn này định nghĩa media type là application/problem+json hoặc application/problem+xml cho XML</p>
<p>Một Problem JSON payload nên có ít nhất 1 property: the "type" field (➊). "type" là mã định danh duy nhất cho mã hiện tại (URN)</p>
<p>Dưới đây là 2 ví dụ cho thấy việc sử dụng type quan trọng như thế nào</p>
<b>Error 1: user name is already taken</b>
<pre><code class="json">400 BAD REQUEST
{
"message": "A user named 'Lucy' already exists."
}</code></pre>
<b>Error 2: malformed input provided</b>
<pre><code class="json">400 BAD REQUEST
{
"message": "Field 'name' is required."
}</code></pre>
<p>Nếu chúng ta định nghĩa thêm type thì sẽ xác định lỗi cụ thể là gì. Các field sau đây là optional:<br /></p>
<ul style="text-align: left;"><li>title – Ngắn gọn dễ hiểu về mã lỗi</li><li>
status – The HTTP status code được tạo ra khi xuất hiện lỗi</li><li>
detail – Giải thích cụ thể về lỗi xuất hiện</li><li>
instance – Tham chiếu URI xác định sự cố xảy ra cụ thể</li></ul>
Một số field extension khác có thể được thêm vào, nhưng phải đảm bảo có thể được serialize
<pre><code class="json">HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://myexample.com/issues/out-of-credit",
"title": "You do not have enough credit to book a room.",
"detail": "Your current balance is 30 EUR, but a room costs 50 EUR per night.",
"instance": "/accounts/12345/errors/out-of-credit/err_instance_id=123",
"balance": 30,
"account": "/accounts/12345"
}</code></pre>
<h3>Sử dụng Problem Details trong Azure Functions</h3>
<p></p>
<p>Tham khảo</p><p><a href="https://www.linkedin.com/pulse/rfc-7807-error-handling-standard-apis-david-rold%C3%A1n-mart%C3%ADnez">https://www.linkedin.com/pulse/rfc-7807-error-handling-standard-apis-david-rold%C3%A1n-mart%C3%ADnez</a></p><p>https://topdev.vn/blog/http-status-code-la-gi/</p>
<p>https://code-maze.com/using-the-problemdetails-class-in-asp-net-core-web-api/</p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-18047795715373570482023-11-28T08:46:00.015+07:002023-11-28T22:32:00.537+07:00Python: Kiểu dữ liệu - Part 2<h2>Kiểu dữ liệu là gì</h2>
<p>Trong khoa học máy tính và lập trình máy tính, một kiểu dữ liệu (tiếng Anh: data type) hay đơn giản type là một cách phân loại dữ liệu cho trình biên dịch hoặc thông dịch hiểu các lập trình viên muốn sử dụng dữ liệu. Hầu hết các ngôn ngữ hỗ trợ nhiều kiểu dữ liệu khác nhau, như số thực, số nguyên hay Boolean. Một kiểu dữ liệu cung cấp một bộ các giá trị mà từ đó một biểu thức (ví dụ như biến, hàm...) có thể lấy giá trị của nó. Kiểu định nghĩa các toán tử có thể được thực hiện trên dữ liệu của nó, ý nghĩa của dữ liệu, và cách mà giá trị của kiểu có thể được lưu trữ</p>
-Wikipedia
<p>Trong Python, có hai loại kiểu dữ liệu chính: primitive và non-primitive</p><p><b>Primitive </b>trong Python bao gồm:<br /></p><ul style="text-align: left;"><li>int: Kiểu dữ liệu số nguyên, có thể là số nguyên dương, số nguyên âm hoặc số 0.</li><li>float: Kiểu dữ liệu số thực, có thể là số thập phân hoặc số nguyên.</li><li>complex: Kiểu dữ liệu số phức, bao gồm phần thực và phần ảo.</li><li>str: Kiểu dữ liệu chuỗi, lưu trữ một chuỗi các ký tự.</li><li>None: Kiểu dữ liệu rỗng, không lưu trữ bất kỳ giá trị nào. </li></ul><p><b>Non-primitive</b> trong Python bao gồm:<br /></p><ul style="text-align: left;"><li>list: Kiểu dữ liệu danh sách, lưu trữ một tập hợp các giá trị có thứ tự.</li><li>tuple: Kiểu dữ liệu tuple, tương tự như danh sách nhưng không thể thay đổi.</li><li>range: Kiểu dữ liệu range, tạo một tập hợp các số liên tiếp.</li><li>set: Kiểu dữ liệu tập hợp, lưu trữ một tập hợp các giá trị không có thứ tự và không có giá trị trùng lặp.</li><li>frozenset: Kiểu dữ liệu frozenset, tương tự như tập hợp nhưng không thể thay đổi.</li><li>dict: Kiểu dữ liệu từ điển, lưu trữ một tập hợp các cặp khóa-giá trị.</li><li>bool: Kiểu dữ liệu logic, có thể là True hoặc False.</li></ul><p>Ví dụ</p>
<pre><code class="python">a = 10
a = 20
print(f'Hello {a}, type a is {type(a)}')
b = [1, 2, 3]
b.append(4)
print(b)
c = (1, 2, 3)
# c[0] = 4
print(f'Hello {c}, type c is {type(c)}')</code></pre>
<h3>global</h3>
<p>Trong Python, từ khóa global được sử dụng để khai báo một biến ở phạm vi toàn cục, nghĩa là biến được định nghĩa bên ngoài các hàm và có thể truy cập từ bất kỳ đâu trong chương trình.</p>
<pre><code class="python">global x
x = 10
def my_function():
print(x)
my_function()</code></pre>
Kết quả
<pre><code class="bash">10</code></pre>
<p>Nếu không sử dụng từ khóa global, biến x sẽ được khai báo là biến cục bộ trong hàm my_function và chỉ có thể truy cập được từ bên trong hàm đó.</p>
<pre><code class="python">x = 10
def my_function():
x = 20
print(x)
my_function()
print(x)</code></pre>
Kết quả
<pre><code class="bash">20
10</code></pre>
<h3>Decimal</h3>
<p>Giả sử bạn có bài toán sau:</p>
<pre><code class="python">x = 0.1
y = 0.1
z = 0.1
s = x + y + z
print(s)</code></pre>
Kết quả
<pre><code class="bash">0.30000000000000004</code></pre>
<p>Python decimal là một kiểu dữ liệu số thực chính xác, có thể được sử dụng để lưu trữ các số thực với độ chính xác cao.</p>
<pre><code class="python">from decimal import Decimal
x = Decimal('0.1')
y = Decimal('0.1')
z = Decimal('0.1')
s = x + y + z
print(s)</code></pre>
Kết quả
<pre><code class="bash">0.3</code></pre>
<h2 style="text-align: left;">Tham khảo</h2><p><a href="https://www.pythontutorial.net/advanced-python/python-decimal/" target="_blank">Python Decimal</a></p><p><a href="https://viblo.asia/p/kieu-du-lieu-va-cac-kieu-du-lieu-trong-python-4dbZNgGvlYM" target="_blank">Kiểu dữ liệu và các kiểu dữ liệu trong Python </a><br /></p>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-13955622684389906222023-11-21T08:50:00.024+07:002023-11-23T08:12:33.554+07:00Python: Welcome to Python - Part 1<h2 style="text-align: left;">Python là gì?</h2><p>Python là một ngôn ngữ lập trình bậc cao, mã nguồn mở và đa nền tảng. Python được Guido van Rossum giới thiệu vào năm 1991 và đã trải qua 3 giai đoạn phát triển khác nhau tương ứng với các version, mới nhất hiện nay là Python version 3x.</p><p>Lợi ích của việc sử dụng Python để lập trình trí tuệ nhân tạo (AI):<br /></p><ul style="text-align: left;"><li>Dễ học và dễ đọc: Python có cú pháp đơn giản, giúp người mới học lập trình dễ dàng tiếp cận. Điều này làm cho Python trở thành một trong những ngôn ngữ phổ biến cho các nhà phát triển AI.</li><li>Thư viện đa dạng: Python có nhiều thư viện mạnh mẽ hỗ trợ việc phân tích dữ liệu và xây dựng mô hình AI. Các thư viện như NumPy, Pandas, Scikit-learn, và TensorFlow giúp tối ưu hóa quá trình phát triển các ứng dụng AI.</li><li>Linh hoạt và đa dụng: Python không chỉ được sử dụng cho AI, mà còn cho phát triển web, tự động hóa tác vụ, xử lý dữ liệu, và nhiều lĩnh vực khác.</li><li>Cộng đồng lớn: Python có một cộng đồng lập trình viên rộng lớn, với nhiều tài liệu, diễn đàn, và hỗ trợ từ cộng đồng. Điều này giúp bạn dễ dàng tìm kiếm giải pháp khi gặp khó khăn.</li><li>Tích hợp tốt với các công cụ khác: Python có khả năng tích hợp tốt với các công cụ và thư viện khác, giúp bạn kết hợp các phần mềm và dịch vụ khác nhau để xây dựng ứng dụng AI phức tạp. </li></ul><h2 style="text-align: left;">Zen Of Python</h2><p>Zen of Python (dịch: Thiền của Python) là một tập hợp gồm 19 "nguyên tắc chỉ dẫn" cho việc viết chương trình máy tính, góp phần ảnh hưởng đến thiết kế của ngôn ngữ lập trình Python. Kỹ sư phần mềm Tim Peters đã viết bộ các nguyên tắc này và đăng tải lên danh sách thư Python vào năm 1999. Bản liệt kê của Peters bỏ ngỏ một nguyên tắc thứ 20 "để cho Guido điền vào", ám chỉ đến Rossum Guido van Rossum – tác giả</p>
<table class="bs-table">
<caption>
Các nguyên tắc được liệt kê và dịch sang Tiếng Việt ở bảng bên dưới:
</caption>
<thead><tr>
<th class="headerSort" role="columnheader button" tabindex="0" title="Sắp xếp tăng dần">STT
</th>
<th class="headerSort" role="columnheader button" tabindex="0" title="Sắp xếp tăng dần">Nguyên gốc
</th>
<th class="headerSort" role="columnheader button" tabindex="0" title="Sắp xếp tăng dần">Phiên dịch
</th></tr></thead><tbody>
<tr>
<td>1
</td>
<td>Beautiful is better than ugly.
</td>
<td>Đẹp đẽ thì tốt hơn xấu xí.
</td></tr>
<tr>
<td>2
</td>
<td>Explicit is better than implicit.
</td>
<td>Tường minh thì tốt hơn ngầm định.
</td></tr>
<tr>
<td>3 & 4
</td>
<td>Simple is better than complex.
<p>Complex is better than complicated.
</p>
</td>
<td>Đơn giản thì tốt hơn phức tạp.
<p>Phức tạp thì tốt hơn rắc rối.
</p>
</td></tr>
<tr>
<td>5
</td>
<td>Flat is better than nested.
</td>
<td>Bằng phẳng thì tốt hơn lồng ghép.
</td></tr>
<tr>
<td>6
</td>
<td>Sparse is better than dense.
</td>
<td>Rải rác thì tốt hơn dày đặc.
</td></tr>
<tr>
<td>7
</td>
<td>Readability counts.
</td>
<td>Tính dễ đọc rất đáng lưu tâm.
</td></tr>
<tr>
<td>8 & 9
</td>
<td>Special cases aren't special enough to break the rules.
<p>Although practicality beats purity.
</p>
</td>
<td>Trường hợp đặc biệt cũng không đủ đặc biệt đến nỗi phá vỡ quy tắc,
<p>dẫu cho tính thực dụng đánh bật tính thuần túy.
</p>
</td></tr>
<tr>
<td>10
</td>
<td>Errors should never pass silently.
<p>Unless explicitly silenced.
</p>
</td>
<td>Lỗi thì đừng nên bao giờ lặng thinh mà bỏ qua,
<p>trừ phi bắt nó câm lặng một cách tường minh.
</p>
</td></tr>
<tr>
<td>12
</td>
<td>In the face of ambiguity, refuse the temptation to guess.
</td>
<td>Khi đối mặt với sự mơ hồ, hãy từ chối cám dỗ của việc suy đoán.
</td></tr>
<tr>
<td>13 & 14
</td>
<td>There should be one—and preferably only one—obvious way to do it.
<p>Although that way may not be obvious at first unless you're Dutch.
</p>
</td>
<td>Nên có một – và thà chỉ có một – cách rõ ràng để làm điều đó,
<p>mặc dù cách đó ban đầu có thể không hiển nhiên, trừ phi bạn là người Hà Lan.
</p>
</td></tr>
<tr>
<td>15 & 16
</td>
<td>Now is better than never.
<p>Although never is often better than <i>right</i> now.
</p>
</td>
<td>Bây giờ thì tốt hơn không bao giờ,
<p>mặc dù không bao giờ thì thường là tốt hơn <i>ngay</i> bây giờ.
</p>
</td></tr>
<tr>
<td>17 & 18
</td>
<td>If the implementation is hard to explain, it's a bad idea.
<p>If the implementation is easy to explain, it may be a good idea.
</p>
</td>
<td>Nếu bản thực hiện mà khó giải thích, thì đó là một ý tưởng tồi.
<p>Nếu bản thực hiện mà dễ giải thích, thì đó có thể là một ý tưởng hay.
</p>
</td></tr>
<tr>
<td>19
</td>
<td>Namespaces are one honking great idea—let's do more of those!
</td>
<td>Không gian tên là một ý tưởng rất chi là vĩ đại—hãy làm thế nhiều hơn!
</td></tr></tbody><tfoot></tfoot></table><p>Triết lý này thể hiện tư duy thiết kế của Python, với sự tập trung vào việc viết mã lệnh rõ ràng, đơn giản và dễ đọc. Python được thiết kế để giúp người lập trình viết code một cách hiệu quả và dễ dàng hiểu, đồng thời khuyến khích việc sử dụng các cấu trúc ngôn ngữ một cách có chủ đích và tối ưu. </p><h2 style="text-align: left;">Cài đặt</h2><div style="text-align: left;">Đầu tiên, bạn cần cài đặt Python 3: <a href="https://code.visualstudio.com/docs/python/python-tutorial#_install-a-python-interpreter">Python 3</a> </div><div style="text-align: left;"><p style="text-align: left;">IDE: bạn cài đặt Visual Studio Code: <a href="https://code.visualstudio.com/">https://code.visualstudio.com/</a>. Bạn có thể sử dụng <a href="https://www.jetbrains.com/pycharm/download/" target="_blank">PyCharm</a> thay thế<br /></p></div>
Kiểm tra version Python
<pre><code class="bash">python3 --version</code></pre>
<div style="text-align: left;">Các Extension hỗ trợ Python trong Visual Studio Code<br /></div><div style="text-align: left;"><ul style="text-align: left;"><li>Python</li><li>Pylint</li></ul>
<h2>Virtual environment (VE) trong Python</h2>
<p>Virtual Environment trong Python là một cách để tạo ra một môi trường riêng biệt cho mỗi ứng dụng Python, trong đó có các thư viện và các phiên bản cần thiết cho ứng dụng đó. Môi trường ảo giúp tránh xung đột giữa các ứng dụng Python khác nhau, cũng như giữa các phiên bản Python khác nhau. Bạn có thể tạo, kích hoạt, vô hiệu hóa và xóa bỏ các môi trường ảo một cách dễ dàng</p></div>
<p>Mở Command Palette (Ctrl+Shift+P), gõ: Create Environment</p>
<div class="separator" style="clear: both;"><a href="https://code.visualstudio.com/assets/docs/python/environments/create_environment_dropdown.png" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="107" data-original-width="800" src="https://code.visualstudio.com/assets/docs/python/environments/create_environment_dropdown.png" /></a></div>
<p>Chọn phiên bản Python mà bạn muốn sử dụng</p>
<a href="https://code.visualstudio.com/assets/docs/python/environments/interpreters-list.png" rel="nofollow" target="_blank"></a>
Folder /.env sẽ xuất hiện trong Project của bạn
<h3>Cài đặt Package</h3>
<p>pip là trình quản lý gói tiêu chuẩn cho Python. Nó cho phép bạn cài đặt và quản lý các gói bổ sung không phải là một phần của thư viện chuẩn Python</p>
Kiểm tra version
<pre><code class="bash">pip --version
pip 23.2.1 from C:\Python311\Lib\site-packages\pip (python 3.11)</code></pre>
<p>Để cài đặt package, bạn mở terminal, gõ</p>
<pre><code class="bash"># Windows (may require elevation)
py -m pip install <package-name></code></pre>
<p>Để cài đặt một danh sách các gói Python, bạn tạo file requirements.txt. Tên file requirements.txt là một quy ước phổ biến và dễ nhớ. Bạn có thể dùng tên khác.</p>
Tạo file requirements.txt có nội dung như sau
<pre><code class="bash">requests
numpy
Flask</code></pre>
Trường hợp bạn muốn chỉ định version cụ thể
<pre><code class="bash">requests==2.25.1
numpy>=1.18.5,<=1.21.0
Flask~=1.1.2</code></pre>
<p>Trong ví dụ trên: </p><ul style="text-align: left;"><li>== đặt ra một phiên bản chính xác.</li><li>>= và <= chỉ định một khoảng phiên bản. </li><li>~= chỉ định một phiên bản tương đương, nhưng giữ nguyên version chính của package.</li></ul>
Sử dụng lệnh sau để cài đặt các package từ file requirements.txt
<pre><code class="bash">pip install -r requirements.txt</code></pre>
Để xuất danh sách các gói Python đã cài đặt vào một file, bạn có thể sử dụng lệnh pip freeze.
<pre><code class="bash">pip freeze > requirements.txt</code></pre>
<h2 style="text-align: left;">Tham khảo</h2><p><a href="https://glints.com/vn/blog/ngon-ngu-lap-trinh-python-la-gi/" target="_blank">Python Là Gì? Tất Tần Tật Về Ngôn Ngữ Lập Trình Python</a></p><p><a href="https://vi.wikipedia.org/wiki/Zen_of_Python" target="_blank">Zen of Python</a> </p><p><a href="https://code.visualstudio.com/docs/python/python-tutorial#_create-a-virtual-environment" target="_blank">Getting Started with Python in VS Code</a> <br /></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-19621737582999953992023-11-18T10:39:00.003+07:002023-11-18T10:39:56.220+07:00Debug Event Grid ở localhost<h2 style="text-align: left;">Azure Event Grid là gì?</h2><p>Azure Event Grid là một dịch vụ event-driven, giúp bạn dễ dàng nhận và xử lý các sự kiện từ các nguồn khác nhau, chẳng hạn như Azure Blob Storage, Azure Queue Storage, Azure Service Bus, và các dịch vụ của bên thứ ba.<br /><br />Event Grid cung cấp một cách đơn giản để xây dựng các ứng dụng phản ứng với các sự kiện từ thế giới thực. Bạn có thể sử dụng Event Grid để:<br /></p><ul style="text-align: left;"><li>Nhận thông báo khi một tệp được tải lên Azure Blob Storage.</li><li>Xử lý các yêu cầu từ một ứng dụng web.</li><li>Theo dõi các hoạt động trong một cơ sở dữ liệu.</li></ul><h2 style="text-align: left;">Debug Event Grid trigger</h2><p>Sau khi tạo ứng dụng Azure Function, chọn Event Grid trigger, bạn sẽ thấy dòng đầu tiên trong code:</p>
<pre><code class="Csharp">http://localhost:7071/runtime/webhooks/EventGrid?functionName={functionname}</code></pre>
Bạn định nghĩa functionName ở hàm Run, trong trường hợp này là training-export
<pre><code class="Csharp">[FunctionName("training-export")]
public static void Run([EventGridTrigger]EventGridEvent eventGridEvent, ILogger log)
{
log.LogInformation(eventGridEvent.Data.ToString());
}</code></pre>
Mở postman lên, tạo 1 request với URL như sau:
<pre><code class="bash">http://localhost:7071/runtime/webhooks/EventGrid?functionName=Function1</code></pre>
Thêm header cho request
<pre><code class="Csharp">Content-Type = application/json
aeg-event-type = Notification</code></pre>
Ở body
<pre><code class="json">[
{
"id": "'1",
"eventType": "yourEventType",
"eventTime":"10:59:00.000",
"subject": "yoursubject",
"data": "<your custom event json>",
"dataVersion": "1.0"
}
]</code></pre>
Sau đó, bạn bấm Send trong Postman, hàm Run trong Visual Studio Code sẽ được debug
<h2>Tham khảo</h2>
<a href="https://rubberduckdev.com/debugging-eg-function/" target="_blank">Azure Debugging an Event Grid Triggered Azure Function</a>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-73965670636640883592023-11-12T12:16:00.002+07:002023-11-12T12:16:47.921+07:00CQRS là gì?<h2>Giới thiệu</h2>
<p>Trong thiết kế phần mềm, CQRS (Command Query Responsibility Segregation) và CQS (Command Query Separation) là hai mẫu thiết kế quan trọng, giúp tách biệt việc xử lý lệnh (command) và truy vấn (query). Tuy có tên gần nhau và có mục tiêu chung là tạo sự tách biệt giữa lệnh và truy vấn, nhưng chúng có những điểm khác biệt quan trọng. </p><p>Trong bài viết này, chúng ta sẽ tìm hiểu về sự khác biệt giữa CQRS và CQS.</p>
<h2>Command Query Separation (CQS)</h2>
<blockquote>Command-query separation (CQS) is a principle of imperative computer programming. It was devised by Bertrand Meyer as part of his pioneering work on the Eiffel programming language. It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.</blockquote>
<p>CQS là một nguyên tắc thiết kế mẫu nơi mà việc gửi lệnh (command) và truy vấn (query) phải được tách biệt hoàn toàn. Nguyên tắc này đề xuất rằng mỗi phương thức trong hệ thống phần mềm sẽ là hoặc một lệnh (command) hoặc một truy vấn (query), nhưng không bao giờ cả hai. Điều này giúp đảm bảo tính rõ ràng và dễ bảo trì của mã nguồn.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://www.dotnetcurry.com/images/codingpatterns/cqs/cqs-pattern-high-level.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="574" data-original-width="1000" src="https://www.dotnetcurry.com/images/codingpatterns/cqs/cqs-pattern-high-level.png" /></a></div><br />Để implement CQS cơ bản nhất, bạn tách Read và Write ra thành hàm riêng
<p></p><pre><code class="csharp">interface IBookService
{
void Edit(string name);
Book GetById(long id);
IEnumerable<Book> GetAllBooks();
}</code></pre>
<p>Hàm Get không làm thay đổi dữ liệu => query</p>
<p>Hàm Edit thay đổi giá trị name của Book, và không có trị trả về => command</p>
<h3>Lợi ích</h3>
<p><b>Separation of Concerns</b></p><p>Chia nhỏ thành các thành phần read và write, nên code có thể dễ đọc, dễ maintain hơn</p><p>Quá trình phát triển nhanh hơn, do các thành phần, tính năng có thể phát triển độc lập với nhau, bởi các team khác nhau</p><p>Các thành phần có thể được update, modify một cách độc với nhau</p><p>Các thành phần có thể sử dụng lại</p><p>Có thể sử dụng các thành phần do bên khác phát triển, mà không cần phải chỉnh sửa, cũng như không cần hiểu rõ chi tiết bên trong nó như thế nào</p><p><b>Unit Test</b></p><p>Dễ dàng viết các đoạn Unit Test do các thành phần đã được chia tách ra.</p><p>...</p><h3 style="text-align: left;">Nhược điểm</h3><p>Tăng số lượng các class do việc chia tách quá nhiều</p><p>Nếu quá tuân thủ nguyên tắc, sẽ làm giảm performance do việc gọi lên server quá nhiều.</p><h2 style="text-align: left;">CQRS (Command Query Responsibility Segregation)</h2><p>CQRS mở rộng nguyên tắc CQS bằng cách tách biệt hoàn toàn việc xử lý lệnh và truy vấn. Trong CQRS, lệnh và truy vấn được xử lý bởi các thành phần riêng biệt và sử dụng lưu trữ dữ liệu riêng biệt. </p><p>Điều này cho phép tối ưu hóa xử lý cho từng loại yêu cầu và cung cấp khả năng mở rộng dễ dàng.</p><p>VD:</p>
<p></p><pre><code class="csharp">public class ProductCommandService
{
private List<Product> products = new List<Product>();
public void AddProduct(Product product)
{
products.Add(product);
}
}
public class ProductQueryService
{
private List<Product> products = new List<Product>();
public List<Product> GetProducts()
{
return products;
}
}</code></pre>
<p><br /></p><p></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-30779260753150466822023-11-11T19:43:00.016+07:002023-11-12T12:09:25.321+07:00Dispatcher Design Pattern<h2>Giới thiệu</h2>
<p>Dispatcher Design Pattern là một mẫu thiết kế phần mềm thuộc nhóm hành vi (behavioral design pattern) được sử dụng để quản lý và điều phối các tasks, events, hoặc notification giữa các thành phần trong hệ thống. Mục tiêu chính của Dispatcher Pattern là giảm sự phụ thuộc trực tiếp giữa các thành phần, từ đó tăng tính tái sử dụng và linh hoạt của mã nguồn.</p>
<h2>So sánh Dispatcher với Service</h2>
<p>Tạo chương trình Console Application gồm có 2 Component A và B đều có hàm HanndleEvent().</p>
<pre><code class="csharp">namespace DispatcherDesignPattern.ServiceA;
public class ComponentA
{
public void HandleEvent()
{
Console.WriteLine("ComponentA handles event");
}
}</code></pre>
<pre><code class="csharp">namespace DispatcherDesignPattern.ServiceB;
public class ComponentB
{
public void HandleEvent()
{
Console.WriteLine("ComponentB handles event");
}
}</code></pre>
<p>Trong ví dụ đầu tiên, chúng ta tạo thêm Service EventService để gọi 2 hàm HandleEvent() của ComponentA và ComponentB. Chúng ta cần khai báo ComponentA và ComponentB trong EventService.</p>
<pre><code class="csharp">using DispatcherDesignPattern.ServiceA;
using DispatcherDesignPattern.ServiceB;
namespace DispatcherDesignPattern;
public class EventService
{
public void NotifyEvent()
{
ComponentA componentA = new ComponentA();
componentA.HandleEvent();
ComponentB componentB = new ComponentB();
componentB.HandleEvent();
}
}</code></pre>
<p>Ở hàm main(), bạn gọi</p>
<pre><code class="csharp">using DispatcherDesignPattern;
EventService eventService = new EventService();
eventService.NotifyEvent();</code></pre>
Đoạn code dưới đây là 1 cách implement theo<b>Dispatch Design Pattern</b>
<pre><code class="csharp">namespace DispatcherDesignPattern;
public class EventDispatcher
{
private readonly Dictionary<string, List<Action>> _subscribers = new Dictionary<string, List<Action>>();
public void Subscribe(string eventName, Action handler)
{
if (!_subscribers.ContainsKey(eventName))
{
_subscribers[eventName] = new List<Action>();
}
_subscribers[eventName].Add(handler);
}
public void Dispatch(string eventName)
{
if (_subscribers.ContainsKey(eventName))
{
foreach (var handler in _subscribers[eventName])
{
handler.Invoke();
}
}
}
}</code></pre>
<pre><code class="csharp">using DispatcherDesignPattern.ServiceA;
using DispatcherDesignPattern.ServiceB;
EventDispatcher eventDispatcher = new EventDispatcher();
ComponentA componentA = new ComponentA();
ComponentB componentB = new ComponentB();</code></pre>
Cả 2 cách đều cho kết quả:
<pre><code class="bash">ComponentA handles event
ComponentB handles event</code></pre>
<p>Trong ví dụ trên, Dispatcher quản lý việc register và gửi event đến các subscriber. Chương trình trên minh họa cách sử dụng Dispatcher Pattern để gửi các message có loại khác nhau đến các subscriber tương ứng.</p>
<p>Với cách gọi bằng Dispatcher, cách thành phần không cần biết sự tồn tại của nhau. Dispatcher quản lý event và component chỉ cần đăng ký với Dispatcher mà không cần biết về các thành phần khác. Điều này giảm sự phụ thuộc (dependency) trực tiếp giữa các component.</p>
<p>Trong cách gọi từ Dispatcher, để thêm một thành phần mới (vd như service), bạn chỉ cần đăng ký nó với Dispatcher mà không làm ảnh hưởng đến code của các thành phần khác. Điều này tăng tính tái sử dụng và giảm độ phức tạp của source code.</p>
<h2>Implement Dispatcher bằng IHandler</h2>
<p>Khai báo IHandler</p>
<pre><code class="csharp">public interface IHandler
{
bool CanHandle(object command);
void Handle(object command);
}</code></pre>
<pre><code class="csharp">public class CommandDispatcher
{
private readonly List<IHandler> _handlers;
public CommandDispatcher()
{
_handlers = new List<IHandler>();
}
public void RegisterHandler(IHandler handler)
{
_handlers.Add(handler);
}
public void Dispatch(object command)
{
var handler = _handlers.Find(t=>t.CanHandle(command));
if (handler != null)
{
handler.Handle(command);
}
}
}</code></pre>
Tạo CreateCustomerHandler
<pre><code class="csharp">public class CreateCustomerHandler : IHandler
{
public bool CanHandle(object command)
{
return command is CreateCustomerCommand;
}
public void Handle(object command)
{
if (CanHandle(command))
{
var createCustomerCommand = (CreateCustomerCommand)command;
Console.WriteLine($"Creating customer: {createCustomerCommand.CustomerName}");
}
}
}</code></pre>
<pre><code class="csharp">public class CreateCustomerCommand
{
public string CustomerName { get; set; }
public CreateCustomerCommand(string customerName)
{
CustomerName = customerName;
}
}</code></pre>
Ở Program.cs, bạn thêm đoạn code sau để gọi command:
<pre><code class="csharp">CommandDispatcher commandDispatcher = new CommandDispatcher();
CreateCustomerHandler createCustomerHandler = new CreateCustomerHandler();
// register Handler with Dispatcher
commandDispatcher.RegisterHandler(createCustomerHandler);
// Create a new Command and send it to Dispatcher
CreateCustomerCommand createCustomerCommand = new CreateCustomerCommand("Nhatkyhoctap");
commandDispatcher.Dispatch(createCustomerCommand);</code></pre>
Kết quả
<pre><code class="bash">Creating customer: Nhatkyhoctap</code></pre>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-27856945562594588682023-11-10T08:05:00.016+07:002023-11-10T08:31:59.207+07:00Promise là gì<h2>Promise</h2>
<p>Promise là một cơ chế trong JavaScript giúp bạn thực thi các tác vụ bất đồng bộ mà không rơi vào callback hell hay pyramid of doom, là tình trạng các hàm callback lồng vào nhau ở quá nhiều tầng.</p><p>Promise có 3 trạng thái chính<br /></p><ul style="text-align: left;"><li>Pending: Khi một Promise được tạo, nó ở trạng thái này.</li><li>Fulfilled: Khi một tác vụ bất đồng bộ hoàn thành thành công, Promise chuyển sang trạng thái này và trả về kết quả.</li><li>Rejected: Khi một tác vụ bất đồng bộ thất bại, Promise chuyển sang trạng thái này và trả về lỗi.</li></ul><div class="separator" style="clear: both; text-align: center;"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/promises.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="801" height="297" src="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/promises.png" width="801" /></a></div><p>Cú pháp</p>
<pre><code class="javascript">let myPromise = new Promise(function(myResolve, myReject) {
// "Producing Code" (May take some time)
myResolve(); // when successful
myReject(); // when error
});
// "Consuming Code" (Must wait for a fulfilled Promise)
myPromise.then(
function(value) { /* code if successful */ },
function(error) { /* code if some error */ }
);</code></pre>
<p>Khi hàm trả về kết quả, sẽ gọi 1 trong 2 hàm sau:</p>
<table class="bs-table">
<tbody><tr><th>Result</th><th>Call</th></tr>
<tr><td>Success</td><td>myResolve(result value)</td></tr>
<tr><td>Error</td><td>myReject(error object)</td></tr>
</tbody></table>
Ví dụ
<pre><code class="javascript">function myDisplayer(some) {
document.getElementById("demo").innerHTML = some;
}
let myPromise = new Promise(function(myResolve, myReject) {
let x = 0;
// The producing code (this may take some time)
if (x == 0) {
myResolve("OK");
} else {
myReject("Error");
}
});
myPromise.then(
function(value) {myDisplayer(value);},
function(error) {myDisplayer(error);}
);</code></pre>
Ví dụ 2:
<pre><code class="javascript">let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.htm");
req.onload = function() {
if (req.status == 200) {
myResolve(req.response);
} else {
myReject("File not Found");
}
};
req.send();
});
myPromise.then(
function(value) {myDisplayer(value);},
function(error) {myDisplayer(error);}
);</code></pre>
<h2>Promise all</h2>
<p>Phương thức này nhận một mảng các lời hứa làm đầu vào và trả về một lời hứa mới thực hiện khi tất cả các lời hứa bên trong mảng đầu vào đã hoàn thành hoặc từ chối ngay khi một trong các lời hứa trong mảng từ chối</p>
<pre><code class="javascript">Promise.all([Promise1, Promise2, Promise3])
.then((result) => {
console.log(result);
})
.catch((error) => console.log(`Error in promises ${error}`));</code></pre>
<h3>Lợi ích của Promise.all</h3>
<ol type="a"><li>Tăng hiệu suất<br />
Khi bạn cần thực hiện nhiều tác vụ bất đồng bộ đồng thời, sử dụng Promise.all giúp tối ưu hóa hiệu suất của ứng dụng. Thay vì chờ từng Promise hoàn thành một, bạn có thể chờ tất cả chúng hoàn thành đồng thời.</li>
<li>Giảm thời gian chờ đợi<br />
Khi sử dụng Promise.all, thời gian chờ đợi của ứng dụng sẽ giảm đi đáng kể, do tất cả các tác vụ bất đồng bộ được thực hiện cùng một lúc.</li>
<li>Xử lý lỗi dễ dàng<br />
Nếu một trong các Promise thất bại, bạn có thể xử lý lỗi một cách dễ dàng bằng cách sử dụng .catch() trên Promise.all.</li></ol>
Ví dụ
<pre><code class="javascript">p1 = Promise.resolve(50);
p2 = 200
p3 = new Promise(function (resolve, reject) {
setTimeout(resolve, 100, 'geek');
});
Promise.all([p1, p2, p3]).then(function (values) {
console.log(values);
});</code></pre>
Output
<pre><code class="javascript">[ 50, 200, 'geek' ]</code></pre>
<h2>Tham khảo</h2>
<a href="https://www.w3schools.com/js/js_promise.asp" target="_blank">JavaScript Promises</a><br />
<a href="https://www.geeksforgeeks.org/javascript-promise-all-method/" target="_blank">JavaScript Promise all() Method</a>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-34318154458523596002023-11-07T23:09:00.013+07:002023-11-08T08:12:43.863+07:00Kubernetes: Deployment - Day 5<h2 style="text-align: left;">Kubernetes Deployment là gì?</h2><p>Một định nghĩa từ trang vmware</p><p></p><blockquote>A Kubernetes Deployment tells Kubernetes how to create or modify instances of the pods that hold a containerized application. Deployments can help to efficiently scale the number of replica pods, enable the rollout of updated code in a controlled manner, or roll back to an earlier deployment version if necessary. Kubernetes deployments are completed using kubectl, the command-line tool that can be installed on various platforms, including Linux, macOS, and Windows.</blockquote>
<h3>Tại sao phải dùng deployment?</h3>
<p>Trong bài viết <a href="https://nhatkyhoctap.blogspot.com/2022/08/kubernetes-setup-project-aspnet-core.html" target="_blank">Kubernetes: Setup project ASP.NET core trên minikube - Day 4 </a>, chúng ta đã xây dựng 1 kubernetes bao gồm 1 Pod và 1 NodePort. Pod là đơn vị nhỏ nhất của kubernetes, nhưng nó không có khả năng tự phục hồi khi bị lỗi hoặc bị xóa. Deployment cho phép bạn cập nhật, quay lại hoặc mở rộng các phiên bản ứng dụng một cách tự động. Ngoài ra, deployment trong kubernetes có tác dụng quản lý các replicaset và các pod để chạy các ứng dụng một cách ổn định và linh hoạt</p>
<h3>Ví dụ</h3>
Quay lại ví dụ bài trước, chúng ta có cấu hình file Pod như sau
<pre><code class="bash">apiVersion: v1
kind: Pod
metadata:
name: client-pod
labels:
component: web
spec:
containers:
- image: anbinhtrong/hostingmvc
name: client
imagePullPolicy: Always
ports:
- containerPort: 80</code></pre>
<p>Để chuyển từ Pod sang Deployment, bạn cần thực hiện các bước sau:</p>
<ul><li>Tạo một file deployment mới. File deployment sẽ mô tả các thông số của Pod, vd như số lượng Pod (replicas), image của container, label của Pod,...</li>
<li>Xóa Pod hiện tại.</li>
<li>Áp dụng deployment mới.</li>
</ul>
Chúng ta sẽ viết như sau
<pre><code class="bash">apiVersion: apps/v1
kind: Deployment
metadata:
labels:
component: web
name: client-deployment
spec:
replicas: 1
selector:
matchLabels:
component: web
template:
<pod-configuration></code></pre>
Dựa vào cấu hình trên, chúng ta có file deployment như sau:
<pre><code class="bash">apiVersion: apps/v1
kind: Deployment
metadata:
labels:
component: web
name: client-deployment
spec:
replicas: 1
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
containers:
- image: anbinhtrong/hostingmvc
name: client
imagePullPolicy: Always
ports:
- containerPort: 80</code></pre>
Apply k8s
<pre><code class="bash">kubectl apply -f deployment.yml
kubectl apply -f service.yml</code></pre>
Get log pod. Example: pod name = client-deployment-dbbdcd4f4-n42ms
<pre><code class="bash">kubectl get pod
kubectl logs client-deployment-dbbdcd4f4-n42ms</code></pre>
Lấy thông tin service và địa chỉ cluster IP, port
<pre><code class="bash">minikube service client-node-port</code></pre>
Dựa trên kết quả xuất ra, chúng ta kiểm tra website có hoạt động hay không
<pre><code class="bash">curl http://192.168.49.2:31515</code></pre>
Tham khảo source code: <a href="https://github.com/anbinhtrong/minikube-asp-net-core/releases/tag/v2.0" target="_blank">Github</a>
<h2>Tham khảo</h2><p>
<a href="https://viblo.asia/p/kubernetes-series-bai-5-deployment-cap-nhat-ung-dung-bang-deployment-RQqKL6q0l7z" target="_blank">Kubernetes Series - Bài 5 - Deployment: cập nhật ứng dụng bằng Deployment </a></p><p><a href="https://www.yogihosting.com/deploy-aspnet-core-app-kubernetes/" target="_blank">Deploy ASP.NET Core App on Kubernetes</a> </p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-37702802949493225272023-11-04T17:29:00.018+07:002023-11-05T23:05:48.335+07:00Kubernetes: Cài đặt minikube trên WSL 2<p>Có 2 cách sử dụng docker và k8s bên trong WSL 2:</p><ul style="text-align: left;"><li>Docker Desktop WSL 2 integration<br /></li><li>Docker CE inside the WSL 2 distro (systemd trên WSL 2)</li></ul><p><img src="https://miro.medium.com/v2/resize:fit:700/1*b2M9Qe8N9j5PgWrcJczFpQ.jpeg" /></p><p>Trong bài viết này, minhfsex hướng dẫn các bạn cài đặt Minikube trong WSL 2 (sử dụng Ubuntu)</p><h2 style="text-align: left;">Cài đặt </h2><h3 style="text-align: left;">Cài đặt Ubuntu</h3><p>Mở Terminal trên Windows, cài đặt WSL2</p>
<pre><code class="bash">wsl --install</code></pre>Restart Window
<p>Liệt kê các bản Linux distribution mà Microsoft hỗ trợ:</p>
<pre><code class="bash">PS >wsl -l --online
The following is a list of valid distributions that can be installed.
Install using 'wsl.exe --install <Distro>'.
NAME FRIENDLY NAME
Ubuntu Ubuntu
Debian Debian GNU/Linux
kali-linux Kali Linux Rolling
Ubuntu-18.04 Ubuntu 18.04 LTS
Ubuntu-20.04 Ubuntu 20.04 LTS
Ubuntu-22.04 Ubuntu 22.04 LTS
OracleLinux_7_9 Oracle Linux 7.9
OracleLinux_8_7 Oracle Linux 8.7
OracleLinux_9_1 Oracle Linux 9.1
openSUSE-Leap-15.5 openSUSE Leap 15.5
SUSE-Linux-Enterprise-Server-15-SP4 SUSE Linux Enterprise Server 15 SP4
SUSE-Linux-Enterprise-15-SP5 SUSE Linux Enterprise 15 SP5
openSUSE-Tumbleweed openSUSE Tumbleweed</code></pre>
Bạn gõ lệnh
<pre><code class="bash">wsl --install -d ubuntu</code></pre>
<h3>Cài đặt Docker</h3>
<pre><code class="bash">
Remove the already docker containers
---------------------------------------------------------------
sudo apt-get remove docker docker-engine docker.io containerd runc
Update the repository
----------------------------------
sudo apt-get update
Install the prerequisite softwares
-----------------------------------------------------
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
Add the pgp key to repos
----------------------------------------
curl -fsSL https://download.docker.com/linux/ubu... | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
Update the repository
------------------------------------
sudo apt-get update
Upgrade the wsl
---------------------------
sudo apt-get upgrade -y
Install the docker container engine
--------------------------------------------------------
sudo apt-get install docker-ce docker-ce-cli containerd.io -y
sudo groupadd docker
sudo usermod -aG docker $USER && newgrp docker
</code></pre>
<p>Tham khảo thêm: <a href="https://nhatkyhoctap.blogspot.com/2022/09/cai-at-docker-engine-tren-wsl-2.html">Docker: Cài đặt Docker Engine trên WSL 2 - Part 1.2</a></p>
<h3 style="text-align: left;">Cài đặt Minikube</h3><p>Binary download</p>
<pre><code class="bash">curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube</code></pre>
Debian package
<pre><code class="bash">curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
sudo dpkg -i minikube_latest_amd64.deb</code></pre>
Start cluster
<pre><code class="bash">minikube start</code></pre>
Dừng cluster nhưng không ảnh hưởng tới quá trình deploy
<pre><code class="bash">minikue pause</code></pre>
<pre><code class="bash">minikube stop</code></pre>
<pre><code class="bash">minikube delete --all</code></pre>
<p>Ngoài docker ra, Minikube còn hỗ trợ các driver khác</p><ul style="text-align: left;"><li>kvm2</li><li>podman</li><li>qemu2</li><li>virtualbox </li></ul>
<pre><code class="bash">
▪ docker: Not healthy: "docker version --format {{.Server.Os}}-{{.Server.Version}}:{{.Server.Platform.Name}}" exit status 1: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/version": dial unix /var/run/docker.sock: connect: permission denied
▪ docker: Suggestion: Add your user to the 'docker' group: 'sudo usermod -aG docker $USER && newgrp docker' <https: docs.docker.com="" engine="" install="" linux-postinstall="">
💡 Alternatively you could install one of these drivers:
▪ kvm2: Not installed: exec: "virsh": executable file not found in $PATH
▪ podman: Not installed: exec: "podman": executable file not found in $PATH
▪ qemu2: Not installed: exec: "qemu-system-x86_64": executable file not found in $PATH
▪ virtualbox: Not installed: unable to find VBoxManage in $PATH</https:></code></pre>
<h3>Cài đặt Kubectl</h3>
<pre><code class="bash">$ sudo snap install kubectl --classic
kubectl 1.28.3 from Canonical✓ installed
$ kubectl version --client
Client Version: v1.28.3
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3</code></pre>
<h2>Tham khảo</h2><div style="text-align: left;"><a href="https://www.devopszones.com/2020/10/how-to-install-minikube-in-ubuntu.html" target="_blank">How to Install Minikube in Ubuntu</a></div><div style="text-align: left;"><a href="https://nhatkyhoctap.blogspot.com/2022/05/cai-at-minikube.html" target="_blank">Day 1: Cài đặt minikube - Docker Desktop </a> <br /></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-27038361497803433022023-11-03T17:37:00.004+07:002023-11-03T17:37:33.089+07:00Azure Event Grid<p>Azure Event Grid là một dịch vụ event routing của Microsoft Azure, cho phép bạn lắng nghe và xử lý các sự kiện từ các nguồn khác nhau, chẳng hạn như Azure Blob Storage, Azure Service Bus, Azure Resource Manager, v.v.<br /><br />Azure Event Grid cung cấp một kiến trúc event-based linh hoạt, cho phép bạn xây dựng các ứng dụng phản ứng với các thay đổi trong dữ liệu hoặc trạng thái của hệ thống.</p><p> </p><h2 style="text-align: left;">Tham khảo</h2><p><a href="https://microsoftlearning.github.io/AZ-204-DevelopingSolutionsforMicrosoftAzure/Instructions/Labs/AZ-204_lab_09.html">https://microsoftlearning.github.io/AZ-204-DevelopingSolutionsforMicrosoftAzure/Instructions/Labs/AZ-204_lab_09.html</a><br /></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-73414897105747747582023-11-03T15:50:00.007+07:002023-11-03T15:54:21.551+07:00Microsoft Defend for cloud: Setup Defender cho Storage<p>Bài lab: <a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Modules/Module%2019%20-%20Defender%20for%20Storage.md">https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Modules/Module%2019%20-%20Defender%20for%20Storage.md</a></p><h2 dir="auto" id="user-content-exercise-1-preparing-the-environment-for-defender-for-storage-plan" tabindex="-1"><a class="heading-link" href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Modules/Module%2019%20-%20Defender%20for%20Storage.md#exercise-1-preparing-the-environment-for-defender-for-storage-plan">👩🏽🍳Exercise 1: Preparing the Environment for Defender for Storage plan<svg aria-hidden="true" class="octicon octicon-link" height="16" viewbox="0 0 16 16" width="16"></svg></a></h2><p dir="auto">To enable the Defender for Storage plan on a specific subscription:</p>
<ol dir="auto"><li>Sign in to the <strong>Azure portal</strong>.</li><li>Navigate to <strong>Microsoft Defender for Cloud</strong>, then <strong>Environment settings</strong>.</li><li>Select the relevant subscription.</li><li>Toggle the <strong>Storage</strong> plan to <strong>On</strong>.
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/enableDforStorage.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="Enable Defender for Storage" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/enableDforStorage.png?raw=true" style="max-width: 100%;" /></a></li><li>Click on <strong>Settings</strong> located in the Monitoring Coverage column, below <strong>Full</strong></li><li>In the <strong>Malware scanning</strong> component, make sure the toggle is <strong>ON</strong> and for the limit of GB scanned per month per storage account, leave the default value of 5000 or click on <strong>Edit configuration</strong>to modify it.
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/GBcap.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="GBCap" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/GBcap.png?raw=true" style="max-width: 100%;" /></a></li><li>In the <strong>Sensitive data discovery</strong> component, make sure the toggle is <strong>ON</strong>.
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/DforStorageComponentsEnablement.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="Defender for Storage components" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/DforStorageComponentsEnablement.png?raw=true" style="max-width: 100%;" /></a></li><li>Select <strong>Continue</strong> and in the next screen <strong>Save</strong>.</li></ol>
<p dir="auto">Now all your existing and upcoming Azure Storage Accounts are protected.</p>
<h2 dir="auto" id="user-content-exercise-2-create-a-storage-account" tabindex="-1"><a class="heading-link" href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Modules/Module%2019%20-%20Defender%20for%20Storage.md#exercise-2-create-a-storage-account">📦Exercise 2: Create a Storage Account<svg aria-hidden="true" class="octicon octicon-link" height="16" viewbox="0 0 16 16" width="16"></svg></a></h2><ol dir="auto"><li>In the <strong>Azure portal</strong> go in the search bar and type <strong>Storage Account</strong>. Click on <strong>Storage Accounts</strong>.
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/createStorageAccount0.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="Search for Storage Accounts" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/createStorageAccount0.png?raw=true" style="max-width: 100%;" /></a></li><li>Click on <strong>Create</strong>
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/createStorageAccount1.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="Create Storage Accounts" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/createStorageAccount1.png?raw=true" style="max-width: 100%;" /></a></li><li>In the <strong>Basics</strong> tab, choose the <strong>subscription</strong> where you enabled Defender for Storage. Then choose a <strong>Resource group</strong> where the Storage Account will live, if you don't have a resource group, you can click on <strong>Create New</strong>.<br />
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/createStorageAccount2.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="Project details" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/createStorageAccount2.png?raw=true" style="max-width: 100%;" /></a></li><li>In the Instance details, input a <strong>storage account name</strong> of 3 to 24 characters long (can contain only lowercase letters and numbers). Then select the <strong>region</strong> for your storage account. For this exercise, leave the <strong>Performance</strong> and <strong>Redundancy</strong> as default. For more information, visit our <a href="https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview" rel="nofollow">documentation</a>.
<a href="https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Images/createStorageAccount3.png?raw=true" rel="noopener noreferrer" target="_blank"><img alt="Instance details" src="https://github.com/Azure/Microsoft-Defender-for-Cloud/raw/main/Labs/Images/createStorageAccount3.png?raw=true" style="max-width: 100%;" /></a></li><li>Hit the button <strong>Review</strong> and then <strong>Create</strong>.</li></ol>
<p dir="auto">The creation of your storage account will take a few seconds.</p>
<blockquote>
<p dir="auto"><strong>Note</strong>: by default, when you create a storage account, you get the roles User Access Administrator and Service Administrator. <strong>To
enable and configure Malware Scanning, you must have Owner roles (such
as Subscription Owner or Storage Account Owner) or specific roles with
the necessary data actions</strong>. Learn more about the <a href="https://learn.microsoft.com/en-us/azure/defender-for-cloud/support-matrix-defender-for-storage" rel="nofollow"><strong>required permissions</strong></a>.</p>
</blockquote><p></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-35984989173883602272023-10-21T20:22:00.016+07:002023-10-21T20:39:10.293+07:00AZ-204: Scale apps trong Azure App Service - Day 5<h2>Giới thiệu</h2>
<p>Azure App Service là một dịch vụ đám mây cung cấp khả năng lưu trữ và chạy các ứng dụng web, API và máy chủ. Một trong những tính năng hữu ích của Azure App Service là tự động mở rộng, cho phép bạn tự động tăng hoặc giảm số lượng server chạy ứng dụng của mình để đáp ứng nhu cầu về tài nguyên.</p>
<h2>Autoscaling rules</h2>
<p>Autoscale rule là một quy tắc xác định cách Azure App Service tự động mở rộng ứng dụng của bạn. Quy tắc bao gồm các điều kiện kích hoạt và hành động. Điều kiện kích hoạt xác định khi nào Azure App Service nên mở rộng ứng dụng của bạn, trong khi hành động xác định cách Azure App Service nên mở rộng ứng dụng của bạn.</p><p>
Có hai loại điều kiện kích hoạt (Autoscale conditions): </p><ul style="text-align: left;"><li>Base rule: Điều kiện kích hoạt cơ bản dựa trên số lượng requrest chờ được xử lý bởi ứng dụng của bạn, hoặc disk size của ứng dụng.<br /></li><li>Advanced rule: Điều kiện kích hoạt nâng cao dựa trên các chỉ số khác, chẳng hạn như sử dụng bộ nhớ hoặc CPU, hoặc tại 1 thời điểm trong ngày hoặc 1 ngày đặc biệt nào đó trong tháng (ví dụ cuối tháng xử lý xuất invoice)<br /></li></ul><h4 style="text-align: left;">Thước đo cho autoscale</h4><ul style="text-align: left;"><li>CPU Percentage</li><li>Memory Percentage</li><li>Disk Queue Length</li><li>Http Queue Length</li><li>Data In</li><li>Data Out</li></ul><p>Có hai loại hành động: </p><ul style="text-align: left;"><li>Scale up: Thêm máy chủ vào ứng dụng của bạn. </li><li>Scale down: Xóa máy chủ khỏi ứng dụng của bạn.</li></ul><h2 style="text-align: left;">Cài đặt Autoscale</h2><p> Mặc định App Service Plan sẽ chọn Manual Scale<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://learn.microsoft.com/en-us/training/wwl-azure/scale-apps-app-service/media/enable-autoscale.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="500" data-original-width="797" src="https://learn.microsoft.com/en-us/training/wwl-azure/scale-apps-app-service/media/enable-autoscale.png" /></a></div><br /><p><br /></p><p><br /></p><h2 style="text-align: left;">Tham khảo</h2><p><a href="https://learn.microsoft.com/en-us/training/modules/scale-apps-app-service/" target="_blank">Scale apps in Azure App Service</a></p><p><a href="https://learn.microsoft.com/en-us/training/modules/scale-apps-app-service/4-autoscale-app-service" target="_blank">Enable autoscale in App Service</a> <br /></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-86666483933554298402023-10-21T11:43:00.006+07:002023-10-21T20:10:27.437+07:00Giới thiệu về .NET Isolated Process Azure Functions<h2>.NET Isolated Process Azure Functions</h2>
<p>.NET Isolated Process Functions là một mô hình triển khai Azure Function cho phép các hàm chạy trong một quy trình riêng biệt (isolated process), tách biệt khỏi Azure Functions Runtime.</p><p>Đây là roadmap của isolated và in-process Azure Function</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/402095iAADCAB758391076C/image-size/large?v=v2&px=999" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="323" data-original-width="970" src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/402095iAADCAB758391076C/image-size/large?v=v2&px=999" /></a></div><br /> <br /><p></p>
<p>Sử dụng Isolated Process Azure Functions có nhiều lợi ích</p>
<ul>
<li>Quản lý quá trình app startup</li>
<li>Cấu hình Dependency Injection</li>
<li>Cấu hình middleware</li>
<li>Tránh xung đột với các version với các class library</li>
</ul>
<h2>Tạo project Azure Function</h2>
<p>Có 2 cách để tạo project Azure Function, 1 là tạo thông qua IDE (Visual Studio, Visual Studio Code), 2 là thông qua command line</p>
<p>1. Để tạo project từ command line, bạn cần cài đặt Azure Functions Core Tools tại <a href="https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-csharp?tabs=windows%2Cazure-cli">đây</a></p>
Gõ lệnh
<pre><code class="bash">func init ProjectName</code></pre>
Chọn worker runtime là donet (isolated process)
<pre><code class="bash">Use the up/down arrow keys to select a worker runtime:
dotnet
dotnet (isolated process)
node
python
powershell
custom</code></pre>
<p>2. Trong Visual Studio, chọn Azure Function => tại Dialog Additional information, chọn .NET 7.0 Isolated cho Function worker</p>
Sau khi tạo ứng dụng, bạn sẽ có câu trúc folder như sau:
<div>ProjectName<div>└──📁Properties</div><div> .gitignore </div><div> Function1.cs</div><div> host.json</div><div> local.settings.json</div><div> Program.cs</div><br /></div>
File ProjectName.csproj sẽ có nội dung như sau:
<pre><code class="xml"><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.14.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.10.0" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
</ItemGroup>
</Project></code></pre>
File local.settings.json
<pre><code class="json">{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}</code></pre>
Đây là file chứa App settings, connection strings, và các settings utilized ở local development và sẽ không được publish lên Azure Functions Host.
<p>Có 1 key value quan trọng là donet-isolated. Ở trang Configuration trên Azure Function, bạn cần update giá trị từ dotnet thành dotnet-isolated</p>
File Program.cs
<pre><code class="csharp">using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();
host.Run();</code></pre>
Trường hợp bạn muốn đăng ký Dependency Injection
<pre><code class="csharp">using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddDbContext(options
=> Options.UseSqlServer(
Environment.GetEnvironmentVariable("MyConnection")));
services.AddOptions()
.Configure((settings, configuration)
=> configuration.GetSection("MyOptions").Bind(settings));
services.AddScoped();
})
.Build();
host.Run();</code></pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-86640520597400468432023-10-12T14:03:00.007+07:002023-10-12T14:37:49.921+07:00AZ-204: Exercise: Create a static HTML web app by using Azure Cloud Shell - Day 4<p><a href="https://learn.microsoft.com/en-us/training/modules/introduction-to-azure-app-service/7-create-html-web-app">Exercise: Create a static HTML web app by using Azure Cloud Shell - Training | Microsoft Learn</a></p><h2 style="text-align: left;">Giải thích</h2><p>Azure Cloud Shell is an interactive, authenticated, browser-accessible terminal for managing Azure resources. It provides the flexibility of choosing the shell experience that best suits the way you work, either Bash or PowerShell.<br /></p>
<pre><code class="bash">resourceGroup=$(az group list --query "[].{id:name}" -o tsv)</code></pre>
<p>az group list: Đây là một lệnh của Azure Command-Line Interface (CLI) được sử dụng để liệt kê các nhóm tài nguyên trong tài khoản Azure.</p>
<p>--query "[].{id:name}": Đây là một tùy chọn sử dụng để truy vấn (query) dữ liệu được trả về từ command. Trong trường hợp này, chúng ta đang yêu cầu trả về danh sách các resource group
và chỉ lấy ra hai thuộc tính của mỗi nhóm tài nguyên, đó là id và name. Biểu thức "[].{id:name}" là một biểu thức JMESPath, được sử dụng để định dạng dữ liệu trả về theo yêu cầu.</p>
<p>JMESPath is a query language for JSON.</p>
<p>-o tsv: Đây là tùy chọn để xác định định dạng đầu ra của lệnh. Trong trường hợp này, đầu ra sẽ được định dạng dưới dạng tab-separated values (TSV), nghĩa là các giá trị sẽ cách nhau bằng tab.</p>
<p>resourceGroup=$(...): Đây là phần gán giá trị. Kết quả của lệnh az group list sau khi áp dụng tùy chọn --query và -o tsv sẽ được lưu vào biến resourceGroup. Có nghĩa là danh sách các tên nhóm tài nguyên được trích xuất từ Azure và lưu trữ trong biến resourceGroup để sử dụng trong các lệnh hoặc kịch bản tiếp theo.</p>
<h2>Update code</h2>
<p>Gõ code <file-name> để mở editor.</p>
<p>Bấm ctrl-s để save và ctrl-q để thoát</p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-324383924917150770.post-29388775226407769832023-10-10T17:17:00.017+07:002023-10-11T02:02:28.412+07:00React: Higher-Order Component - Day 15<h2 style="text-align: left;">Giới thiệu</h2><p>Higher-order component (HOC) là một kỹ thuật nâng cao trong React được sử dụng trong việc sử dụng lại các component. HOCs không là một phần trong React API. Một cách cụ thể, một higher-order component là một hàm và nó nhận đối số là một component và trả về một component mới.</p><p></p><blockquote>A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React's compositional nature. Concretely, a higher-order component is a function that takes a component and returns a new component.</blockquote>
<h2>Higher-Order Component Structure</h2>
<p>Để định nghĩa Higher-Order Component (HOC) trong React, bạn cần qua các bước:</p>
- Define HOC function: Hàm này nhận vào input là component (hoặc có thêm vài parameter khác) và trả về 1 component mới với thêm 1 số tinh năng mới.
<pre><code class="typescript">const higherFunction = (WrappedComponent) => {
// ...
}</code></pre>
Định nghĩa new component
<pre><code class="typescript">const NewComponent = () => {
return (
<>
</>
);
};
export default NewComponent;</code></pre>
<p>Ở câu lệnh return, pass tất cả props (bao gồm các props trong HOC)</p>
<pre><code class="typescript">render() {
return <WrappedComponent props={props} />
}</code></pre>
HOC function sẽ trả về NewComponent, NewComponent sẽ được dùng trong application
<pre><code class="typescript">import NewComponent from "./NewComponent";
const higherFunction = (WrappedComponent: typeof NewComponent) => {
return function NewComponentV2(){
return (
<>
<h3>Wrapped Component</h3>
<WrappedComponent data = {data}></WrappedComponent>
</>
);
}
}
export default higherFunction;</code></pre>
Sử dụng ở App.tsx
<pre><code class="typescript">const newComponent = higherFunction(WrappedComponent);
//...
<newComponent></newComponent></code></pre>
<h3>Ví dụ</h3>
Ví dụ: Tạo UserProfile là component mà chúng ta muốn được extend
<pre><code class="typescript">import React from 'react';
import UserProfile from '../models/UserProfile';
interface ChildComponentProps {
data: UserProfile
}
const UserProfileComponent : React.FC<ChildComponentProps> = ({ data }) => {
return (
<div>
<p>Username: {data.name}</p>
</div>
);
};
export default UserProfileComponent;</code></pre>
Khai báo UserProfile model
<pre><code class="typescript">interface UserProfile {
name: string;
age: number;
}
export default UserProfile;</code></pre>
Enhance với component withUserProfileEnhancement
<pre><code class="typescript">import UserProfileComponent from "./UserProfileComponent";
const withUserProfileEnhancement = (WrappedComponent: typeof UserProfileComponent) => {
return function UserProfileWithUser(){
const data = {
name: 'John',
age: 30,
};
return (
<>
<h3>withUserProfileEnhancement</h3>
<WrappedComponent data = {data}></WrappedComponent>
</>
);
}
};
export default withUserProfileEnhancement;</code></pre>
Khai báo enhanced component
<pre><code class="typescript">
import UserProfileComponent from './components/UserProfileComponent';
//...
const UserProfileWithUserEnhancement = withUserProfileEnhancement(UserProfileComponent);</code></pre>
<h2>Tham khảo</h2><p>
<a href="https://blog.logrocket.com/understanding-react-higher-order-components/" target="_blank">Understanding React higher-order components</a></p><p><a href="https://www.freecodecamp.org/news/higher-order-components-in-react/" target="_blank">How to Use Higher-Order Components in React</a> </p>Minh Tamhttp://www.blogger.com/profile/16116438424668044201noreply@blogger.com0