startup
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
namespace $NAMESPACE$
{
public class Startup
{
public IConfiguration configRoot { get; }
public Startup(IConfiguration configuration)
{
configRoot = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"C:\Users\name\AppData\Local\ASP.NET\DataProtection-Keys"));
//回應壓縮
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/json" });
});
//source : https://stackoverflow.com/questions/67891755/addresponsecompression-not-working-on-net-core-3-1-controllers
//WebApi
services.AddControllers()
.AddNewtonsoftJson();//Newtonsoft.Json 支援
services.AddControllersWithViews(); //MVC
services.AddRazorPages() //Razor
.AddViewComponentsAsServices(); //視圖組件注冊為服務
//註冊 IAntiforgery 服務
services.AddAntiforgery(o =>
{
o.HeaderName = "X-XSRF-TOKEN"; // 客户端要向服務端發送 Header 名稱,XSRF驗證;
o.Cookie.SameSite = SameSiteMode.Unspecified;
o.Cookie.HttpOnly = false;
});
services.AddDirectoryBrowser(); //啟用靜態文件、默認文件和目錄瀏覽的服務
//Session
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.None; //iis 上https後,要改屬性 Always
});
//webApi json
services.AddControllers().AddJsonOptions(option =>
{
option.JsonSerializerOptions.WriteIndented = true;
option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
option.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
//跨來源資源共用
services.AddCors(options =>
{
options.AddPolicy("MyAllowHeadersPolicy", policy =>
{
policy.AllowCredentials();
}); ;
options.AddDefaultPolicy(builder =>
builder.SetIsOriginAllowed(_ => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.SlidingExpiration = true;
options.LoginPath = "/Home/Index";
options.LogoutPath = "/Home/Logout";
});
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.IncludeErrorDetails = true; // 預設值為 true,有時會特別關閉
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configRoot.GetValue<string>("JwtSettings:Issuer"),
ValidateAudience = true,
ValidAudience = "audience",
RequireExpirationTime = true, // 是不是有過期時間
ValidateLifetime = true,
ValidateIssuerSigningKey = false, // 如果 Token 中包含 key 才需要驗證,一般都只有簽章而已
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configRoot.GetValue<string>("JwtSettings:SignKey"))),
ClockSkew = TimeSpan.Zero,
};
});
//appsettings
services.AddDbContext<TestContext>(options => options.UseSqlServer(configRoot.GetSection("TestConn").Value), ServiceLifetime.Scoped);
//httpClient
services.AddHttpClient();
//ssl
services.AddHttpClient("", m => {})
.ConfigureHttpMessageHandlerBuilder(builder =>
{
builder.PrimaryHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (m, c, ch, e) => true
};
});
services.AddHttpContextAccessor(); //HttpContext
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IMemoryCache, MemoryCache>(); //MemoryCache
services.AddOpenApiDocument(); //OpenAPI
}
public void Configure(WebApplication app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseOpenApi(); //OpenAPI
app.UseSwaggerUi3(); //NSwag
app.UseResponseCompression(); //回應壓縮中介軟體
app.UseSession();
app.UseHttpsRedirection(); //HTTP 重新導向至 HTTPS
var rewrite = new RewriteOptions()
.AddRedirect("api/TestUrlWrite/(.*)/(.*)/(.*)", "api/TestUrlWrite?p1=$1&p2=$2&p3=$3");
app.UseRewriter(rewrite);
var path = Path.Combine(env.ContentRootPath, "folder");
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
app.UseStaticFiles(); //靜態檔案
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(path),
RequestPath = "/folder"
});
//source : https://ithelp.ithome.com.tw/articles/10193208
app.UseRouting();
app.UseCors();
app.UseCookiePolicy();
app.UseAuthentication(); //驗證
app.UseAuthorization(); //授權
app.Use((context, next) =>
{
context.Response.Headers.Server = "";
context.Response.Headers.Add("X-XSS-Protection", "0"); //xss 不啟用 XSS 過濾
context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); //xss 通知瀏覽器用header提供的 Content-Type
context.Response.Headers.Add("X-Frame-Options", "DENY"); //xss 不讓網頁載入 frame
context.Response.Headers.Add("Referrer-Policy", "no-referrer"); //xss 伺服器接收到的請求中就不會有來源資訊
context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'none'");
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase) || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!, new CookieOptions { HttpOnly = false });
}
return next(context);
});
public string GenerateToken(string userName, int expireMinutes = 30)
{
var issuer = Configuration.GetValue<string>("JwtSettings:Issuer");
var signKey = Configuration.GetValue<string>("JwtSettings:SignKey");
var claims = new List<Claim>();
//claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
//claims.Add(new Claim(JwtRegisteredClaimNames.Sub, sub));
//claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds().ToString())); // 必須為數字
//claims.Add(new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddHours(expireMinutes)).ToUnixTimeMilliseconds().ToString()));
//claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "auth"));
//claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必須為數字
claims.Add(new Claim(JwtRegisteredClaimNames.Name, name));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // JWT ID
var dtNow = DateTime.Now;
var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey)), SecurityAlgorithms.HmacSha512)),
new JwtPayload(issuer: issuer, audience: userName, claims: claims, notBefore: dtNow, expires: dtNow.AddMinutes(expireMinutes))
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
//Web.config
//<system.webServer>
// <httpProtocol>
// <customHeaders>
// <remove name="X-Powered-By" />
// </customHeaders>
// </httpProtocol>
// <security>
// <requestFiltering removeServerHeader="true" />
// </security>
//</system.webServer>
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/api/csrf") || context.Request.Path.StartsWithSegments("/"))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
context.Response.StatusCode = 200;
await context.Response.WriteAsync("OK");
}
else await next.Invoke();
});
app.MapRazorPages(); //razor pages
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}
Layout
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Project.styles.css" asp-append-version="true" />
<!-- bootstrap table -->
<link rel="stylesheet" href="~/lib/bootstrap-table/bootstrap-table.min.css">
<!-- font-awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/bootstrap-table/bootstrap-table.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="~/lib/sweetalert2/sweetalert2.all.min.js"></script>
<script src="~/lib/moment.js/moment.min.js"></script>
source : https://sweetalert2.github.io/
https://ithelp.ithome.com.tw/m/articles/10318757
https://blog.darkthread.net/blog/sweetalert2/
function SweetAlert(type, text, timer) {
Swal.fire({
icon: type,
text: text,
timer: timer
});
}
function SweetAlert(type, text, timer, fun) {
Swal.fire({
icon: type,
text: text,
timer: timer
})
.then(function() {
fun();
});
}
function SweetAlertConfirm(type, text, confirmedFun) {
Swal.fire({
icon: type,
text: text,
//showDenyButton: true,
showCancelButton: true,
}).then(function(result) {
if (result.isConfirmed) {
confirmedFun();
}
});
}
function SweetAlertConfirmDeny(type, title, confirmText, denyText, confirmedFun, deniedFun) {
Swal.fire({
icon: type,
title: title,
showDenyButton: true,
showCancelButton: true,
confirmButtonText: confirmText,
denyButtonText: denyText
}).then((result) => {
if (result.isConfirmed) {
confirmedFun();
} else if (result.isDenied) {
deniedFun();
}
});
}
source : https://momentjs.com/
https://pjchender.dev/npm/npm-moment-js/
function setDate24Format(date) {
if (date == null) return '';
var date = moment(date).format('YYYY/MM/DD HH:mm:ss');
if (date == '0001/01/01 12:00:00') return '';
return date;
}
function setDateFormat(date) {
if (date == null) return '';
var date = moment(date).format('YYYY/MM/DD hh:mm:ss');
if (date == '0001/01/01 12:00:00') return '';
return date;
}
function setDateFormatToMins(date) {
if (date == null) return '';
var mins = moment(date).format('YYYY/MM/DD hh:mm');
if (mins == '0001/01/01 12:00') return '';
return mins;
}
function setDateFormatToDay(date) {
if (date == null) return '';
var today = moment(date).format('YYYY/MM/DD');
if (today == '0001/01/01') return '';
return today;
}
function setDateFormatToDayBindDate(date) {
if (date == null) return '';
var today = moment(date).format('YYYY-MM-DD');
if (today == '0001/01/01') return '';
return today;
}
function DateIsBefore(endDate, startDate) {
return moment(endDate).isBefore(startDate);
}
function IsData(date) {
return moment.isDate(date);
}
function DateDiff(startdate, enddate, format) {
var start = moment(startdate);
var end = moment(enddate);
return end.diff(start, format);
}
function LoadingStatus(isShow) {
if (isShow) {
$('#loading').modal('show');
} else {
$('#loading').modal('hide');
setTimeout(function() {
$('#loading').modal('hide');
},
3000);
}
}
function CheckIdno(value) {
if (value == null || value.length < 10) return false;
var regex = /^[A-Z]{1}[0-9]{9}$/;
var reg = new RegExp(regex);
return reg.test(value);
}
<div class="container" style="margin-left: 10px !important;">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
source : https://fontawesome.com/start
<!-- The Modal Loading-->
<div class="modal fade main-content bd-example-modal-lg" tabindex="-1" id="loading">
<div class="modal-dialog modal-sm">
<div class="modal-content" style="width: 48px">
<span class="fa fa-spinner fa-spin fa-3x"></span>
</div>
</div>
</div>
BLL
HtmlAgilityPack 1.11.71 拆解html 元素
LinqKit 1.2.5 PredicateBuilder.True
NPOI 2.6.2 Read Write Excel
EntityFrameworkCore.Design 、EntityFrameworkCore.SqlServer 、EntityFrameworkCore.Tools 7
string url = "";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(url);
var content = new FormUrlEncodedContent(new[]{
new KeyValuePair<string, string>("A", A),
});
var result = client.PostAsync("/ProjectName/api/Controller/Action", content).Result;
}
string url2 = "";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(url2);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
StringContent content = new StringContent(JsonConvert.SerializeObject(new { A = a, B = b }), Encoding.UTF8, "application/json");
var result = client.PostAsync("/ProjectName/api/Controller/Action", content).Result;