เปลี่ยนจาก string เป็น Stream

public static Stream GenerateStreamFromString(string s)
{
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(s);
    writer.Flush();
    stream.Position = 0;
    return stream;
}
using (var stream = GenerateStreamFromString("a,b \n c,d"))
{
    // ... Do stuff to stream
}

เปลี่ยนจาก Stream เป็น StreamReader

ปัญหา Cannot implicitly convert type 'System.IO.Stream' to 'System.IO.StreamReader

using (reader = new StreamReader(openFileDialog1.Filename))
{
    // ...
}

JWT Authentication ด้วย .NET 5 WebApi

สร้างโปรเจ็กส์แบบ ASP.NET Core Web API

ติดตั้ง Package Microsoft.IdentityModel.Tokens 6.15.0

PM> Install-Package Microsoft.IdentityModel.Tokens -Version 6.15.0

ติดตั้ง Package System.IdentityModel.Tokens.Jwt 6.15.0

PM> Install-Package System.IdentityModel.Tokens.Jwt -Version 6.15.0

สร้างคลาส Entities\User.cs

using System.Text.Json.Serialization;

namespace WebApi5.Entities
{
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }
        [JsonIgnore]
        public string Password { get; set; }
    }
}

สร้างคลาส Models\AuthenticateRequest.cs

using System.ComponentModel.DataAnnotations;

namespace WebApi5.Models
{
    public class AuthenticateRequest
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

สร้างคลาส Models\AuthenticateResponse.cs

using WebApi5.Entities;

namespace WebApi5.Models
{
    public class AuthenticateResponse
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }
        public string Token { get; set; }
        public AuthenticateResponse(User user, string token)
        {
            Id = user.Id;
            FirstName = user.FirstName;
            LastName = user.LastName;
            Username = user.Username;
            Token = token;
        }
    }
}

สร้างคลาส Helpers\AppSettings.cs

namespace WebApi5.Helpers
{
    public class AppSettings
    {
        public string Secret { get; set; }
    }
}

สร้างคลาส Helpers\AuthorizeAttribute.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using WebApi5.Entities;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = (User)context.HttpContext.Items["User"];
        if (user == null)
        {
            // not logged in
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
        }
    }
}

สร้างคลาส Services\UserService.cs

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using WebApi5.Entities;
using WebApi5.Helpers;
using WebApi5.Models;

namespace WebApi5.Services
{
    public interface IUserService
    {
        AuthenticateResponse Authenticate(AuthenticateRequest model);
        IEnumerable<User> GetAll();
        User GetById(int id);
    }
    public class UserService : IUserService
    {
        // users hardcoded for simplicity, store in a db with hashed passwords in production applications
        private List<User> _users = new List<User>
        {
            new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }
        };
        private readonly AppSettings _appSettings;
        public UserService(IOptions<AppSettings> appSettings)
        {
            _appSettings = appSettings.Value;
        }
        public AuthenticateResponse Authenticate(AuthenticateRequest model)
        {
            var user = _users.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);
            // return null if user not found
            if (user == null) return null;
            // authentication successful so generate jwt token
            var token = generateJwtToken(user);
            return new AuthenticateResponse(user, token);
        }
        public IEnumerable<User> GetAll()
        {
            return _users;
        }
        public User GetById(int id)
        {
            return _users.FirstOrDefault(x => x.Id == id);
        }
        // helper methods
        private string generateJwtToken(User user)
        {
            // generate token that is valid for 7 days
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
                //Expires = DateTime.UtcNow.AddDays(7),
                Expires = DateTime.UtcNow.AddMinutes(1),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }
}

สร้างคลาส Helpers\JwtMiddleware.cs

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebApi5.Services;

namespace WebApi5.Helpers
{
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly AppSettings _appSettings;
        public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
        {
            _next = next;
            _appSettings = appSettings.Value;
        }
        public async Task Invoke(HttpContext context, IUserService userService)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            if (token != null)
                attachUserToContext(context, userService, token);
            await _next(context);
        }
        private void attachUserToContext(HttpContext context, IUserService userService, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
                    ClockSkew = TimeSpan.Zero
                }, out SecurityToken validatedToken);
                var jwtToken = (JwtSecurityToken)validatedToken;
                var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
                // attach user to context on successful jwt validation
                context.Items["User"] = userService.GetById(userId);
            }
            catch
            {
                // do nothing if jwt validation fails
                // user is not attached to context so request won't have access to secure routes
            }
        }
    }
}

แก้ไขไฟล์ appsettings.json

{
  "AppSettings": {
    "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

แก้ไขไฟล์ Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using WebApi5.Helpers;
using WebApi5.Services;

namespace WebApi5
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApi5", Version = "v1" });
            });

            // configure strongly typed settings object
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

            // configure DI for application services
            services.AddScoped<IUserService, UserService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApi5 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseMiddleware<JwtMiddleware>();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

สร้าง API Controller Controllers\UsersController.cs

using Microsoft.AspNetCore.Mvc;
using WebApi5.Models;
using WebApi5.Services;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace WebApi5.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private IUserService _userService;
        public UsersController(IUserService userService)
        {
            _userService = userService;
        }
        [HttpPost("authenticate")]
        public IActionResult Authenticate(AuthenticateRequest model)
        {
            var response = _userService.Authenticate(model);
            if (response == null)
                return BadRequest(new { message = "Username or password is incorrect" });
            return Ok(response);
        }
        [Authorize]
        [HttpGet]
        public IActionResult GetAll()
        {
            var users = _userService.GetAll();
            return Ok(users);
        }
    }
}

ทดลองเรียกใช้งาน https://localhost:7078/Users จะได้

{
    "message": "Unauthorized"
}

ต้องไปเรียก https://localhost:7078/Users/authenticate เพื่อขอ token มาก่อน

Request

{
    "username": "test",
    "password": "test"
}

Response

{
    "id": 1,
    "firstName": "Test",
    "lastName": "User",
    "username": "test",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYmYiOjE2Mzk1OTM0OTYsImV4cCI6MTYzOTU5MzU1NiwiaWF0IjoxNjM5NTkzNDk2fQ.ingwuhAl4TB7n7lOMILt-hWpN07ggjwQu4VJF6Lky2U"
}

ถ้า username หรือ password ผิดจะได้

{
    "message": "Username or password is incorrect"
}

ทีนี้เรียก https://localhost:7078/Users พร้อมให้ค่า Bearer Token จะเรียกได้ละ (token มีอายุ 1 นาที ถ้าเกิน 1 นาทีก็จะ Unauthorized)

[
    {
        "id": 1,
        "firstName": "Test",
        "lastName": "User",
        "username": "test"
    }
]

VS2019 สร้าง PDF ด้วย ReportViewerCore.NETCore

สร้างโปรเจ็กส์แบบ ASP.NET Core Web API

ติดตั้ง ReportViewerCore.NETCore

PM> Install-Package ReportViewerCore.NETCore -Version 15.1.15

สร้างโฟลเดอร์ Reports

Add Report ชื่อ Reports/Report0.rdlc

เพิ่ม API Controller ชื่อ Controllers/ValuesController.cs

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Reporting.NETCore;

namespace WebApi5.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly ILogger<ValuesController> _logger;
        private IWebHostEnvironment _env;

        public ValuesController(ILogger<ValuesController> logger
             , IWebHostEnvironment env)
        {
            _logger = logger;
            _env = env;
        }

        [HttpGet("{id}")]
        public string Get(int id)
        {
            string rootPath;
            if (!string.IsNullOrEmpty(_env.WebRootPath))
                rootPath = _env.WebRootPath;
            else
                rootPath = _env.ContentRootPath;
            _logger.LogInformation($"rootPath  = {rootPath}");

            LocalReport report = new LocalReport();
            report.ReportPath = $"{rootPath}\\Reports\\Report0.rdlc";

            byte[] pdf = report.Render("PDF");
            string pdfPathOP = System.IO.Path.Combine(rootPath, "reportOut.pdf");
            System.IO.File.WriteAllBytes(pdfPathOP, pdf);

            return "success";
        }
    }
}

ลองเรียก https://localhost:44395/values/5 จะได้ไฟล์ reportOut.pdf

ตัวอย่างการส่งค่าด้วย DataSet

ให้ add | new item | dataset ชื่อ DataSetTable1

ที่หน้า design ของ DataSetTable1 สร้างตาราง DataTable1 แล้วเพิ่มคอลัมน์ title, name

DataSetTable1 dsTable1 = new DataSetTable1();
DataRow row01 = dsTable1.DataTable1.NewRow();
row01["title"] = "Mr.";
row01["name"]  = "Jack";
dsTable1.DataTable1.Rows.Add(row01);

LocalReport localReport1 = new LocalReport();
localReport1.ReportPath = $"{rootPath}\\Reports\\Report1.rdlc";
localReport1.DataSources.Add(new ReportDataSource("DataSetTable1", dsTable1.DataTable1));

กรณีที่ Report มีมากกว่า 1 DataSet

ถ้าลากฟิลด์มาวางจะได้

=Fields!first_name.Value

ให้ระบุ DataSet ลงไป

=First(Fields!first_name.Value, "DataSet1")

Visual Studio 2019 ติดตั้ง Microsoft RDLC Report Designer

ไปที่เมนู Extensions | Manage Extensions แล้วพิมพ์ RDLC

เลือก Microsoft RDLC Report Designer เพื่อติดตั้ง

การใช้งานก็ใช้คู่กับ Microsoft.Reporting.NETCore

ตัวอย่างการใช้งาน