Trong phần này sẽ tìm hiểu về Map ánh xạ URL request đến một Middleware xử lý, đọc các thông tin mà yêu cầu gửi đến server (Request), Xử lý upload file, đọc và ghi cookie, trả về nội dung Json.

Tạo dự án thực hành - ứng dụng Web với .NET Core

Để thực hành tạo lại dự án một ứng dụng Web với .NET Core, ứng dụng Web này là dạng cơ bản để tìm hiểu chi tiết về các nguyên lý nên chưa sử dụng .NET Core MVC. Tạo một thư mục lưu code dự án, ví dụ WebApp, hãy vào thư mục đó, gõ lệnh sau để tạo cấu trúc dự án

dotnet new web

Mở thư mục đó ra bằng Visual Studio Code (code .), Tại VSC chọ menu View > Command Palette ... rồi chọn .NET: Generate Assets for Build and Debug để VSC sinh ra cấu hình build ở thư mục .vscode

Sử dụng Webpack để đóng gói JS, CSS, SCSS

Ở ví dụ trước bạn đã sử dụng BuildBundlerMinifier để gộp CSS, JS. Tuy nhiên để linh hoạt hơn từ phần này sẽ sử dụng Webpack với .NET Core (về Webpack xem chi tiết tại: Sử dụng Webpack ), sẽ thực hiện từng bước để tích hợp BootStrap, JQuery và một file nguồn SCSS tự động build thành CSS.

Sử dụng Webpack

Gõ các lệnh sau

npm init -y                                         # tạo file package.json cho dự án
npm i -D webpack webpack-cli                        # cài đặt Webpack
npm i node-sass postcss-loader postcss-preset-env   # cài đặt các gói để làm việc với SCSS
npm i sass-loader css-loader cssnano                # cài đặt các gói để làm việc với SCSS, CSS
npm i mini-css-extract-plugin cross-env file-loader # cài đặt các gói để làm việc với SCSS
npm install copy-webpack-plugin                     # cài đặt plugin copy file cho Webpack
npm install npm-watch                               # package giám sát file  thay đổi


npm install bootstrap                               # cài đặt thư viện bootstrap
npm install jquery                                  # cài đặt Jquery
npm install popper.js                               # thư viện cần cho bootstrap

Sau các lệnh này các package trên được tải về lưu tại node_modules, giờ đến bước cấu hình Webpack để khi chạy nó có được mục đích sau:

  • Copy jquery.min.js từ package jquery ra thư mục wwwroot/js
  • Copy popper.min.js từ package popper.js ra thư mục wwwroot/js
  • Copy bootstrap.min.js từ package bootstrap ra thư mục wwwroot/js
  • Biên dịch file src/scss/site.scss thành file wwww/css/site.min.css (đã gộp cả CSS của Bootstrap)
1 soạn file nguồn src/scss/site.scss có nội dung cơ bản như sau:
//Gộp Bootstrap
// Có thể thiết lập các biến biến như màu $warning ...
@import '../../node_modules/bootstrap/scss/bootstrap.scss';

// Thêm code SCSS
.mainbackground {
    background-color: #673ab7;
  }
2 Tạo file cấu hình webpack.config.js có nội dung cơ bản như sau: Nội dung file webpack.config.js
3 Cập nhật package.json - mở file này, đảm bảo thêm vào những dòng bôi đậm sau:
{
    /...
    "main": "index.js", 
      "watch": {
        "build": "src/scss/site.scss"
      },
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1", 
        "build": "webpack",
        "watch": "npm-watch"
      },

      "keywords": [],
    /...
}

Nội dung file đầy đủ tại Package.json

4 Chạy Webpack: Với cấu hình như trên, mỗi lần gõ lệnh
nmp run build
Nó sẽ sinh ra các file .js, .css tương ứng lưu ở wwwwroot/css hay wwwroot/js

Hoặc nếu muốn, tự động mỗi khi cập nhật site.scss thì Webpack tự chạy thì kích hoạt script

nmp run watch
Chú ý, ở lớp Startup, phương thức Configure, cần có dòng code sau để truy cập được file tĩnh
app.UseStaticFiles();

RequestDelegate và Map ánh xạ URL tới Middleware

Ở ví dụ trước đã biết, cấu hình ứng dụng ở lớp Startup ta có đối tượng app kiểu IApplicationBuilder tại phương thức Configure, đối tượng này cho phép chúng ta đưa vào nó deleage kiểu RequestDelegate (phương thức async có một tham số kiểu HttpContext) như là một middleware cuối cùng xử lý Request/Respone, đưa vào bằng phương thức Run

Để có một phương thức RequestDelegate viết nhanh thì như sau:

async (HttpContext context) => {
   /...
}

Áp dụng với Run, có thể viết

app.Run(async (HttpContext context) => {
   /..
   await context.Response.WriteAsync("Nội dung");
});

Mặc định khởi tạo đang khai báo có một RequestDelegate, nó nhận xử lý tất cả các truy vấn đến ứng dụng nếu truy vấn không bị chuyển hướng xử lý bởi RequestDelegate khác.

Sử dụng phương thức Map để chuyển hướng xử lý yêu cầu

Phương thức Map của IApplicationBuilder có thể thiết lập, nếu truy vấn có Uri với path thích hợp thì chuyển hướng xử lý bởi một RequestDelegate khác. Cú pháp cơ bản như sau:

app.Map(pathstring, (IApplicationBuilder app1) =>
{
    app1.Run(async (HttpContext context) => {
        // Code xử lý ở đây
    });
});

Trong đó tham số pathstring có kiểu PathString, chuỗi string tự động convert được. Ví dụ:

app.Map("Abc", (IApplicationBuilder app1) =>
{
    app1.Run(async (HttpContext context) => {
        // Code xử lý khi truy vấn đến Url là: /Abc
    });
});

app.Map("Xyz", (IApplicationBuilder app1) =>
{
    app1.Run(async (HttpContext context) => {
        // Code xử lý khi truy vấn đến Url là: /Xyz
    });
});

Bằng cách như vậy, có thể rẽ nhánh xử lý Request, tùy thuộc vào Url truy vấn.

Trong ứng dụng này ta sẽ xây dựng nó có các chức năng thực hiện tùy thuộc vào Url truy vấn, mỗi chức năng này sẽ do một RequestDeleage xử lý (khai báo bởi app.Map), cụ thể

URL Chức năng
/RequestInfo Đọc và hiện thị các thông tin về Request truy cập
/Form Demo - Hiện thị Form HTML, xử lý đọc thông tin từ Form, kể cả xử lý upload file
/Encoding Demo tính năng encoding dữ liệu khi xuất HTML
/Cookies Demo - Đọc và ghi cookie
/Json Demo trả về dữ liệu JSON

Xây dựng lớp tiện ích HtmlHelper

Để hỗ trợ sinh ra HTML nhanh chóng và có sử dụng Bootstrap trình bày, xây dựng lớp tĩnh với các phương thức phát sinh HTML như sau: Tạo file HtmlHelper.cs và định nghĩa lớp như tại HtmlHelper.cs (copy code này và lưu lại), lớp này cũng sinh ra HTML Menu ở trên đầu trang ...

Mở Startup, cập nhật cho app.Run (RequestDelegate) mặc định thành

app.Run(async (HttpContext context) =>
{
    string menu     = HtmlHelper.MenuTop(HtmlHelper.DefaultMenuTopItems(),context.Request);
    string content  = HtmlHelper.HtmlTrangchu();
    string html     = HtmlHelper.HtmlDocument("Trang chủ", menu + content);
    await context.Response.WriteAsync(html);
});

Gõ lệnh dotnet run, kiểm tra http://localhost:5000

Trang trên có dùng Bootstrap, nên giao diện của nó cũng responsive (thay đổi phù hợp kích thức màn hình)

Đọc thông tin HttpRequest cơ bản

Tra truy vấn đến trang web, thông tin của nó sẽ chứa trong đối tượng lớp HttpRequest, đối tượng này lấy được từ HttpContext là tham số của RequestDelegate. Từ đối tượng lớp HttpRequest chỉ việc đọc các thuộc tính, ta sẽ biết được thông tin request như: Phương thức gì (POST, GET, UPDATE ...), Path, Query ...

Xây dựng một lớp, để đọc thông tin này như sau - lớp tĩnh đặt tên là RequestProcess, đầu tiên là định nghĩa phương thức RequestInfo:

using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Threading.Tasks;
using System.Text.Encodings.Web;
using System;
using Newtonsoft.Json;
namespace WebApp
{
    public static class RequestProcess
    {

        // Đọc các thông tin cơ bản của Request
        // Trả về HTML trình  bày các thông tin đó
        public static string RequestInfo(HttpRequest request) {

            var sb = new StringBuilder();

            // Lấy http scheme (http|https)
            var scheme  =  request.Scheme;
            sb.Append(("scheme".td() + scheme.td()).tr());

            // HOST Header
            var host = (request.Host.HasValue ? request.Host.Value : "no host");
            sb.Append(("host".td() + host.td()).tr());


            // Lấy pathbase (URL Path - cho Map)
            var pathbase = request.PathBase.ToString();
            sb.Append(("pathbase".td() + pathbase.td()).tr());

            // Lấy Path (URL Path)
            var path = request.Path.ToString();
            sb.Append(("path".td() + path.td()).tr());

            // Lấy chuỗi query của URL
            var QueryString = request.QueryString.HasValue ? request.QueryString.Value : "no query string";
            sb.Append(("QueryString".td() + QueryString.td()).tr());

            // Lấy phương thức
            var method = request.Method;
            sb.Append(("Method".td() + method.td()).tr());

            // Lấy giao thức
            var Protocol = request.Protocol;
            sb.Append(("Protocol".td() + Protocol.td()).tr());

            // Lấy ContentType
            var ContentType = request.ContentType;
            sb.Append(("ContentType".td() + ContentType.td()).tr());

            // Lấy danh sách các Header và giá trị  của nó, dùng Linq để lấy
            // Header gửi đến lưu trong thuộc tính Header  kiểu Dictionary
            var listheaderString = request.Headers.Select((header) => $"{header.Key}: {header.Value}".HtmlTag("li"));
            var headerhmtl = string.Join("", listheaderString).HtmlTag("ul"); // nối danh sách thành 1
            sb.Append(("Header".td() + headerhmtl.td()).tr());

            // Lấy danh sách các Header và giá trị  của nó, dùng Linq để lấy
            var listcokie = request.Cookies.Select((header) => $"{header.Key}: {header.Value}".HtmlTag("li"));
            var cockiesHtml = string.Join("", listcokie).HtmlTag("ul");
            sb.Append(("Cookies".td() + cockiesHtml.td()).tr());


            // Lấy tên và giá trí query
            var listquery = request.Query.Select((header) => $"{header.Key}: {header.Value}".HtmlTag("li"));
            var queryhtml = string.Join("", listquery).HtmlTag("ul");
            sb.Append(("Các Query".td() + queryhtml.td()).tr());

            //Kiểm tra thử query tên abc có không
            Microsoft.Extensions.Primitives.StringValues abc;
            bool existabc = request.Query.TryGetValue("abc",  out abc);
            string queryVal = existabc ? abc.FirstOrDefault() : "không có giá trị";
            sb.Append(("abc query".td() + queryVal.ToString().td()).tr());

            string info =  "Thông tin Request".HtmlTag("h2") + sb.ToString().HtmlTag("table", "table table-sm table-bordered");
            return  info;
        }

    }
}

Tiếp theo, mở lớp Startup thêm vào RequestDelegate - chuyên xử lý Url truy vấn /RequestInfo thì gọi đến RequestInfo ở trên để lấy HTML chứa thông tin Request

app.Map("/RequestInfo", app01 => {
    app01.Run(async (context) => {
        string menu         = HtmlHelper.MenuTop(HtmlHelper.DefaultMenuTopItems(), context.Request);
        string requestinfo  = RequestProcess.RequestInfo(context.Request).HtmlTag("div", "container");
        string html         = HtmlHelper.HtmlDocument("Thông tin Request", (menu + requestinfo));
        await context.Response.WriteAsync(html);
    });
});

Truy cập http://localhost:5000/RequestInfo/abc/xyz?id=10

Đọc thông Post từ Form HTML và xử lý Upload File

Trước tiên tạo một file formtest.html chứa Form sẽ hiện thị, nội dung file đó như sau: Form Test (lưu file này lại). Nó sẽ được đọc vào bằng phương thức File.ReadAllTextAsync("formtest.html") khi cần nội dung của nó.

Khi Form submit, dữ liệu Form gửi đến biểu diễn bằng đối tượng kiểu IFormCollection tại thuộc tính HttpRequest.Form

Từ đối tượng này để đọc một dữ liệu có tên trên Form HTML nào đó thì sử dụng indexer để truy cập, ví dụ lấy giá trị phần tử Form tên abc

var abc = _form["abc"].FirstOrDefault() ?? null;

Đối với file, thì các file upload lưu tại thuộc tính HttpRequest.Files, nó là danh sách các phần tử kiểu IFormFile - có thể duyệt qua Files để xử lý tất cả các file upload đến.

Từ đối tượng IFormFile, toàn bộ dữ liệu file có thể chuyển vào một stream (file trên server, hay bộ nhớ .. tùy cách tạo ra stream) bằng

IFormFile.CopyToAsync(stream);

Tiếp tục, xây dựng phương thức FormProcess để xử lý khi Form gửi đến (submit) như sau:

public static class RequestProcess
{
    /..

    // Xử lý khi HTML Form post dữ liệu
    public static async Task<string> FormProcess(HttpRequest request) {
        //Xử lý đọc dữ liệu Form - khi post - dữ liệu này trình  bày trên Form
        string hovaten  = "";
        bool   luachon  = false;
        string email    = "";
        string password = "";
        string thongbao = "";

        if (request.Method ==  "POST") {
            // Đọc dữ liệu từ Form
            IFormCollection _form = request.Form;

            email    = _form["email"].FirstOrDefault() ?? "";
            hovaten  = _form["hovaten"].FirstOrDefault() ?? "";
            password = _form["password"].FirstOrDefault() ?? "";
            luachon  =  (_form["luachon"].FirstOrDefault() == "on");

            thongbao = $"Dữ liệu post - email: {email} - hovaten: {hovaten} - password: {password} - luachon: {luachon} ";

            // var filePath = Path.GetTempFileName();
            // Xử lý nếu có file upload (hình ảnh,  ... )
            if (_form.Files.Count > 0) {
                string thongbaofile = "Các file đã upload: ";
                foreach (IFormFile formFile in _form.Files)
                {
                    if (formFile.Length > 0)
                    {
                        var filePath = "wwwroot/upload/"+formFile.FileName;    // Lấy tên  file
                        if (!Directory.Exists("wwwroot/upload/"))  Directory.CreateDirectory("wwwroot/upload/");
                        thongbaofile += $"{filePath} {formFile.Length} bytes";
                        using (var stream = new FileStream(filePath, FileMode.Create)) // Mở stream để lưu file, lưu file ở thư mục wwwroot/upload/
                        {
                             await formFile.CopyToAsync(stream);
                        }
                    }

                }
                thongbao += "<br>" + thongbaofile;
            }

        }
        string format   =  await File.ReadAllTextAsync("formtest.html");   // Đọc nội dung HTML từ file
        string formhtml = string.Format(format, hovaten, email, luachon ? "checked" : "");
        return formhtml + thongbao;
    }

    /..

}

Tiếp theo, mở lớp Startup thêm vào:

app.Map("/Form", app01 => {
    app01.Run(async (context) => {
        string menu     = HtmlHelper.MenuTop(HtmlHelper.DefaultMenuTopItems(), context.Request);
        string formhtml = await RequestProcess.FormProcess(context.Request);
               formhtml = formhtml.HtmlTag("div", "container");
        string html     = HtmlHelper.HtmlDocument("Form Post", (menu + formhtml));
        await context.Response.WriteAsync(html);
    });
});

Truy cập http://localhost:5000/Form, điền thông tin và chọn file rồi bấm Submit kiểm tra:

Chú ý: mặc định kích thước file lớn nhất cho Upload là 30MB, nếu muốn điều chỉnh thì điều chỉnh tại lớp Program

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>().UseKestrel(options => {
            options.Limits.MaxRequestBodySize = 104857600; // 100 MB - Cho phép upload
        });

HtmlEncoder

Khi dữ liệu xuất không thuộc cấu trúc HTML đã kiểm duyệt, bạn cần encode để an toàn, hoặc bạn muốn in các chuỗi với những ký tự như <, > ...

Để Encode chuỗi nào đó, sử dụng

stringencode = HtmlEncoder.Default.Encode(stringvalue);
public static string Encoding(HttpRequest request) {

    Microsoft.Extensions.Primitives.StringValues data;
    bool   existdatavalue = request.Query.TryGetValue("data",  out data);
    string datavalue      = existdatavalue ? data.FirstOrDefault() : "không có giá trị";

    Microsoft.Extensions.Primitives.StringValues e;
    bool   existevalue    = request.Query.TryGetValue("e",  out e);
    string evalue         = existevalue ? e.FirstOrDefault() : "không có giá trị";

    string dataout;
    if (evalue == "0") {
        dataout = datavalue;
    }
    else {
        dataout = HtmlEncoder.Default.Encode(datavalue);
    }
    string encoding_huongdan =  File.ReadAllText("encoding.html");

    return dataout.HtmlTag("div", "alert alert-danger") + encoding_huongdan;
}
Startup
 app.Map("/Encoding", app01 => {
    app01.Run(async (context) => {
        string menu     = HtmlHelper.MenuTop(HtmlHelper.DefaultMenuTopItems(), context.Request);
        string htmlec   = RequestProcess.Encoding(context.Request).HtmlTag("div", "container");
        string html     = HtmlHelper.HtmlDocument("Encoding", (menu + htmlec));
        await context.Response.WriteAsync(html);
    });
});

Sử dụng Cookie

Định nghĩa về Cookie xem tại Cookie

Phương thức sau, có lưu và đọc Cookie

public static string Cookies(HttpRequest request, HttpResponse response) {

    string tb = "";
    switch (request.Path) {
        case "/read":
            var listcokie = request.Cookies.Select((header) => $"{header.Key}: {header.Value}".HtmlTag("li"));
            tb = string.Join("", listcokie).HtmlTag("ul");
        break;
        case "/write":
            response.Cookies.Append("masanpham", "12345",
                new CookieOptions {
                        Path = "/Cookies",
                        Expires = DateTime.Now.AddDays(1)}
            );
            tb = "Đã lưu Cookie  -  masanpham - hết hạn 1 ngày".HtmlTag("div", "alert alert-danger");
        break;
    }

    string cookies_huongdan =  File.ReadAllText("cookies.html");


    return tb + cookies_huongdan;
}

File cookies.html có nội dung cookies.html

Cập nhật lớp Startup
app.Map("/Cookies", app01 => {
    app01.Run(async (context) => {
        string menu     = HtmlHelper.MenuTop(HtmlHelper.DefaultMenuTopItems(), context.Request);
        string cookies  = RequestProcess.Cookies(context.Request, context.Response).HtmlTag("div", "container");
         string html    = HtmlHelper.HtmlDocument("Đọc / Ghi Cookies", (menu + cookies));
        await context.Response.WriteAsync(html);
    });
});

Trả về nội dung Json

Trả về nội dung Json (nhất là khi xây dựng cá API ứng dụng), để làm việc với Json thì cần cài đặt thêm Newtonsoft.Json

dotnet add package Newtonsoft.Json

Sau đó để sử dụng cần nạp namespace

using Newtonsoft.Json;

Ở đây, để chuyển đối tượng lớp vô danh thành Json thì làm như sau với JsonConvert.SerializeObject:

public static string GetJson() {
    var productjson = new {
        name  = "IPhone 11",
        price =  1000
    };
    return JsonConvert.SerializeObject(productjson);
}

Một Respone thiết lập cho biết nó trả về Json thì cần gán ContentType của Response bằng "application/json"

Cập nhật lớp Startup
app.Map("/Json", app01 => {
    app01.Run(async (context) => {
        string Json  = RequestProcess.GetJson();
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(Json);
    });
});

MÃ NGUỒN TẠI SOURCE CODE

Đăng ký theo dõi ủng hộ kênh