tag:blogger.com,1999:blog-59465307047421309702024-03-06T16:20:07.273+08:00Jax 的工作紀錄除了在整理學習上的經驗,同時也能幫助其他需要的人Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.comBlogger18125tag:blogger.com,1999:blog-5946530704742130970.post-62018283885626750192021-10-16T23:40:00.003+08:002023-02-25T18:14:07.492+08:00C# MVC 的 UserException 攔截處理<p>為了將錯誤訊息呈現給使用者,要完成幾件事:</p>
<ul>
<li>用 Filter 攔截 UserException</li>
<li>將錯誤訊息存到 TempData,之後呈現在畫面上</li>
<li>回到原本的畫面,需要產生 ViewResult</li>
<li>針對 Ajax 請求用 400 狀態回應,並將訊息放在 http content</li>
</ul>
<br/>
<br/>
<h3>.Net Framework MVC</h3>
<pre class="cs" name="code">
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace XXXXX.Mvc.Filters
{
/// <summary>例外訊息過濾器</summary>
public class ExceptionMessageActionFilter : ActionFilterAttribute
{
/// <summary></summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* 自動將 Action 的 Parameters 中的 ViewModel 賦予給 ViewData
* 不然要自己在 Action 的第一行寫 ViewData.Model = domain;
*/
string paramName = filterContext.ActionDescriptor.GetParameters()
.OrderBy(x => Regex.IsMatch(x.ParameterType.ToString(), @"(ViewModel|Domain)\b") ? 0 : 1)
.Select(x => x.ParameterName)
.FirstOrDefault();
if (paramName != null)
{ filterContext.Controller.ViewData.Model = filterContext.ActionParameters[paramName]; }
}
/// <summary></summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
/* 不處理子 Action */
if (filterContext.IsChildAction) { return; }
/* 判斷 Exception 是否已經被處理了 */
if (filterContext.ExceptionHandled) { return; }
/* 只針對 UserException 進行錯誤處理*/
var ex = filterContext.Exception as UserException;
if (ex == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
/* 對 Ajax 請求的處理 */
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.Result = new ContentResult { Content = ex.Message };
}
else if (ex is JwNoDataException)
{
/* 資料不存在的處理 */
filterContext.Controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new HttpNotFoundResult("[" + ex.Message + "]");
}
else
{
/* 一般畫面的處理 */
filterContext.Controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new ViewResult
{
ViewData = filterContext.Controller.ViewData,
TempData = filterContext.Controller.TempData
};
}
}
}
}
</pre>
<p>在 FilterConfig.cs 配置</p>
<pre class="cs" name="code">
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//...
filters.Add(new ExceptionMessageActionFilter());
//...
}
}
</pre>
<br/>
<br/>
<h3>Net core 3.1 MVC</h3>
<pre class="cs" name="code">
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Orion.Mvc.Filters
{
/// <summary>例外訊息過濾器</summary>
public class ExceptionMessageActionFilter : ActionFilterAttribute
{
/// <summary></summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* 自動將 Action 的 Arguments 中的 ViewModel 賦予給 ViewData
* 不然要自己在 Action 的第一行寫 ViewData.Model = domain;
*/
var arguments = filterContext.ActionArguments.Values.Where(x => x != null);
object model = arguments
.OrderBy(x => Regex.IsMatch(x.GetType().Name, @"(ViewModel|Domain)\b") ? 0 : 1)
.FirstOrDefault();
var controller = filterContext.Controller as Controller;
controller.ViewData.Model = model;
}
/// <summary></summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
/* 判斷 Exception 是否已經被處理了 */
base.OnActionExecuted(filterContext);
if (filterContext.ExceptionHandled) { return; }
/* 只針對 UserException 進行錯誤處理*/
var ex = filterContext.Exception as UserException;
if (ex == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
filterContext.ExceptionHandled = true;
var controller = filterContext.Controller as Controller;
var headers = filterContext.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
/* 對 Ajax 請求的處理 */
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.Result = new ContentResult { StatusCode = 400, Content = ex.Message };
}
else if (ex is UserNoDataException)
{
/* 資料不存在的處理 */
controller.TempData["StatusError"] = ex.Message;
filterContext.HttpContext.Response.StatusCode = 404;
}
else
{
/* 一般畫面的處理 */
controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new ViewResult
{
ViewData = controller.ViewData,
TempData = controller.TempData
};
}
}
}
}
</pre>
<p>在 Startup.cs 配置</p>
<pre class="cs" name="code">
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddMvc(options =>
{
//...
options.Filters.Add(new ExceptionMessageActionFilter());
//...
})
.AddControllersAsServices()
;
//...
}
</pre>
<br/>
<br/>
<h3>Net core 3.1 Razor Page</h3>
<pre class="cs" name="code">
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace XXXXX.Api.Filters
{
public class ExceptionMessagePageFilter : AbstractPageFilter
{
public override void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
if (context.ExceptionHandled) { return; }
/* 判斷是否有指定的 Exception */
var ex = context.Exception;
if (ex is UserException userEx) { handleUserException(context, userEx); return; }
if (ex is HttpException httpEx) { handleHttpException(context, httpEx); return; }
}
private void handleUserException(PageHandlerExecutedContext context, UserException ex)
{
/* 只針對 PageModel 進行錯誤處理*/
var page = context.HandlerInstance as PageModel;
if (page == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
context.ExceptionHandled = true;
var headers = filterContext.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
/* 對 Ajax 請求的處理 */
context.HttpContext.Response.StatusCode = 400;
context.Result = new ContentResult
{
StatusCode = 400,
Content = ex.Message
};
}
else if (ex is UserNoDataException)
{
/* 資料不存在的處理 */
page.TempData["StatusError"] = ex.Message;
context.HttpContext.Response.StatusCode = 404;
}
else
{
/* 一般畫面的處理 */
page.TempData["StatusError"] = ex.Message;
context.Result = page.Page();
}
}
private void handleHttpException(PageHandlerExecutedContext context, HttpException ex)
{
context.ExceptionHandled = true;
var headers = context.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
context.HttpContext.Response.StatusCode = ex.StatusCode;
context.Result = new ContentResult
{
StatusCode = ex.StatusCode,
Content = ex.Message,
};
}
else
{
context.HttpContext.Response.StatusCode = ex.StatusCode;
context.Result = new StatusCodeResult(ex.StatusCode);
}
}
}
}
</pre>
<p>在 Startup.cs 配置</p>
<pre class="cs" name="code">
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddRazorPages(options =>
{
//...
})
.AddMvcOptions(options =>
{
//...
options.Filters.Add(new ExceptionMessagePageFilter());
//...
})
;
//...
}
</pre>
<br/>
<br/>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-57180131894898724472021-10-16T12:50:00.005+08:002023-02-25T18:15:30.082+08:00週期性執行的子執行緒範本<p>關於週期性執行的子執行緒有幾個要點:</p>
<ul>
<li>單次 Sleep 的時間不要太久,會影響程式的關閉</li>
<li>不可以用 SpinWait 來做程式暫停,要用 Sleep 來暫停,這樣才會釋放 CPU</li>
<li>Error log 要注意會重複出現相同的 error</li>
<li>有排除重複 error 時,要記得加上[錯誤解除]的 log,這樣才能知道程式是否有回到正常執行</li>
<li>要執行的程式邏輯移到一個 method 裡,這樣可以避免 return 造成的迴圈中斷,也能分離邏輯的關注點</li>
</ul>
<br/>
<br/>
<h3>週期小於 10 秒的範本</h3>
<pre class="cs" name="code">
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
private int _cycleMSec = 1000;
private bool _runFlag = false;
private string _prevError;
public void Start()
{
if (_runFlag) { return; }
_runFlag = true;
var thread = new Thread(() =>
{
while (_runFlag)
{
/* 先睡可以避免在程式啟動時過於忙碌 */
Thread.Sleep(_cycleMSec);
try
{
/* 要執行的程式邏輯 */
cycleHandler();
if (_prevError != null) { _log.Info("錯誤結束"); }
_prevError = null;
}
catch (Exception ex)
{
/* 避免相同的錯誤一直被記錄 */
if (_prevError == ex.Message) { continue; }
_prevError = ex.Message;
_log.Fatal(ex, "執行錯誤");
}
}
});
thread.Start();
}
public void Stop()
{
_runFlag = false;
}
private void cycleHandler()
{
// 主要的邏輯程式寫在這裡
}
</pre>
<br/>
<br/>
<h3>週期大於 10 秒的範本</h3>
<pre class="cs" name="code">
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
private int _cycleSec = 30;
private bool _runFlag = false;
private string _prevError;
private DateTime _nextTime = DateTime.Now;
public void Start()
{
if (_runFlag) { return; }
_runFlag = true;
var thread = new Thread(() =>
{
while (_runFlag)
{
/* 先睡可以避免在程式啟動時過於忙碌 */
Thread.Sleep(1000);
/* 檢查是否符合執行時間 */
if (_nextTime > DateTime.Now) { continue; }
/* 更新下一次的執行時間 */
_nextTime = DateTime.Now.AddSeconds(_cycleSec);
try
{
/* 要執行的程式邏輯 */
cycleHandler();
if (_prevError != null) { _log.Info("錯誤結束"); }
_prevError = null;
}
catch (Exception ex)
{
/* 避免相同的錯誤一直被記錄 */
if (_prevError == ex.Message) { continue; }
_prevError = ex.Message;
_log.Fatal(ex, "執行錯誤");
}
}
});
thread.Start();
}
public void Stop()
{
_runFlag = false;
}
private void cycleHandler()
{
// 主要的邏輯程式寫在這裡
}
</pre>
<br/>
<br/>
<h3>停啟頻繁的範本</h3>
<pre class="cs" name="code">
public enum CycleStatus
{
Stop,
Start,
Stoping,
}
public CycleStatus RunStatus { get; private set; } = CycleStatus.Stop;
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
private int _cycleMSec = 1000;
private string _prevError;
public void Start()
{
if (RunStatus != CycleStatus.Stop)
{ throw new Exception("程序還在進行中"); }
RunStatus = CycleStatus.Start;
var thread = new Thread(() =>
{
while (RunStatus == CycleStatus.Start)
{
/* 先睡可以避免在程式啟動時過於忙碌 */
Thread.Sleep(_cycleMSec);
try
{
/* 要執行的程式邏輯 */
cycleHandler();
if (_prevError != null) { _log.Info("錯誤結束"); }
_prevError = null;
}
catch (Exception ex)
{
/* 避免相同的錯誤一直被記錄 */
if (_prevError == ex.Message) { continue; }
_prevError = ex.Message;
_log.Fatal(ex, "執行錯誤");
}
}
RunStatus = CycleStatus.Stop;
});
thread.Start();
}
public void Stop()
{
if (RunStatus == CycleStatus.Stop)
{ throw new Exception("程序已經停止"); }
if (RunStatus == CycleStatus.Stoping)
{ throw new Exception("程序正在停止"); }
RunStatus = CycleStatus.Stoping;
}
private void cycleHandler()
{
// 主要的邏輯程式寫在這裡
}
</pre>
<br/>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-75816354379789685932021-10-15T17:26:00.004+08:002023-02-25T18:17:08.463+08:00全域的 Exception 處理<p>大部分具有 Exception 機制的程式語言都有提供全域的 Exception 處理,如果你已經有用 log 去記錄錯誤的習慣的話,與其在程式裡佈滿了 try catch,不如在全域處裡中去記錄沒有被 catch 的 Exception,這樣程式就可以更乾淨了,而且程式如果發生異常的關閉時也會進入全域處裡,可以確保 Exception 妥善地被記錄。</p>
<br/>
<h3>Console, WinForm 程式</h3>
<pre class="cs" name="code">
using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Forms;
using NLog;
namespace AppThreadException
{
static class Program
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
[STAThread]
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
public static void Main(string[] args)
{
/* ThreadException 用來攔截 UI 錯誤 */
Application.ThreadException += threadExceptionHandler;
/* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
Application.Run(new MyForm());
}
/// <summary>攔截 UI 錯誤</summary>
private static void threadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
_log.Fatal(e.Exception, "操作錯誤");
MessageBox.Show(e.Exception.Message, "操作錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
/// <summary>攔截不可挽回的錯誤,不能阻止程式關閉</summary>
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
_log.Fatal(ex, "執行錯誤");
MessageBox.Show(ex.Message, "執行錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
}
</pre>
<br/>
<h3>Web 程式</h3>
<p>在 Global.asax.cs 中可以設定 Application_Error 就可以攔截未處裡的 Exception,除了用這個方法記錄錯誤,還可以用現有的套件(<a href="https://elmah.github.io/a/mvc/" target="_blank">elmah</a>)幫我們完成。</p>
<pre class="cs" name="code">
using System;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using NLog;
namespace MvcReport
{
public class MvcApplication : System.Web.HttpApplication
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
//...
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
_log.Fatal(ex, ex.Message);
}
//...
}
}
</pre>
<br/>
<h3>Net core 程式</h3>
<p>Net core 的程式都是相同的方式,Web Request 的要另外配置</p>
<pre class="cs" name="code">
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using EverTop.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using NLog;
namespace MyWebApp
{
public class Program
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
public static void Main(string[] args)
{
/* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
/* 用來攔截 Task 錯誤 */
TaskScheduler.UnobservedTaskException += unobservedTaskException;
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webHostBuilder => webHostBuilder
.UseStartup<Startup>()
)
.Build();
host.Run(); /* 啟動網站 */
}
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
_log.Fatal(ex, "執行錯誤");
}
private static void unobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
_log.Fatal(e.Exception, "執行錯誤");
e.SetObserved();
}
}
}
</pre>
<br/>
<h3>Net core 3.1 Web 程式</h3>
<p>在 Startup.cs 中配置 middleware 進行 Exception 攔截</p>
<pre class="cs" name="code">
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (_env.IsProduction())
{
app.UseExceptionHandler("/Error");
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePagesWithReExecute("/Error");
/* 利用 middleware 進行 Exception 攔截 */
/* 這裡的順序很重要,不然會被前面 ExceptionHandler 處理掉就拿不到 Exception */
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
_log.Fatal(ex, "執行錯誤");
throw; /* 把 Exception 再丟出去給別人處理 */
}
});
}
</pre>
<br/>
<h3>週期性 Thread 的處裡方式</h3>
<p>將主要邏輯寫在另外 method 裡,這樣可以專注在 Exception 上。</p>
<pre class="cs" name="code">
private bool _runFlag = false;
public void Start()
{
if (_runFlag) { return; }
_runFlag = true;
var thread = new Thread(() =>
{
while (_runFlag)
{
try
{
cycleHandler();
}
catch (Exception ex)
{
_log.Fatal(ex, "執行錯誤");
}
Thread.Sleep(1000);
}
});
thread.Start();
}
public void Stop()
{
_runFlag = false;
}
private void cycleHandler()
{
// 主要的邏輯程式寫在這裡
}
</pre>
<br/>
<h3>PHP 錯誤處理</h3>
<p>Ref: <a href="https://www.php.net/manual/zh/function.set-error-handler.php" target="_blank">set_error_handler</a>, <a href="https://www.php.net/manual/zh/function.set-exception-handler.php" target="_blank">set_exception_handler</a></p>
<pre class="php" name="code">
<?php
function error_handler($errno, $errstr, $errfile, $errline) {
if(error_reporting() === 0){ return; }
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('error_handler', E_ALL^E_NOTICE);
function exception_handler($exception) {
// 在這記錄 log
throw $exception;
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
//$a = 1 / 0;
</pre>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-72397727694823726482021-10-15T11:27:00.002+08:002023-02-25T18:17:55.785+08:00讓 Net core 3.1 的 PageModel handler 可以用 AuthorizeAttribute<p>Razor Page 的 AuthorizeAttribute 只能用在 class 上,這樣就做不到細部的權限管控,為了讓 handler 也能用 AuthorizeAttribute 可以從 IPageFilter 進行處裡:</p>
<pre class="cs" name="code">
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace XXXXX.Api.Filters
{
public class HandlerAuthorizeFilter : IPageFilter
{
/// <summary>在完成模型系結之後,于處理常式方法執行之前呼叫。</summary>
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
if(context.HandlerMethod == null) { return; }
/* 取得 handler 上的 AuthorizeAttribute */
var attr = context.HandlerMethod.MethodInfo
.GetCustomAttribute<AuthorizeAttribute>();
if (attr == null) { return; }
/* 當前登入的使用者 */
ClaimsPrincipal user = context.HttpContext.User;
/* 檢查是否符合腳色權限 */
bool isAuth = attr.Roles.Split(',').Any(user.IsInRole);
/* 沒權限就給予 ForbidResult */
if (!isAuth) { context.Result = new ForbidResult(); }
}
/// <summary>在選取處理常式方法之後,但在進行模型系結之前呼叫。</summary>
public void OnPageHandlerSelected(PageHandlerSelectedContext context) { }
/// <summary>在處理常式方法執行之後,在動作結果執行之前呼叫。</summary>
public void OnPageHandlerExecuted(PageHandlerExecutedContext context) { }
}
}
</pre>
<br/>
<p>接著在 Startup.cs 進行過濾器配置:</p>
<pre class="cs" name="code">
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddRazorPages(options =>
{
//...
})
.AddMvcOptions(options =>
{
//...
options.Filters.Add(new HandlerAuthorizeFilter());
})
;
//...
}
</pre>
<br/>
<p>然後在 PageModel 就可以用下面的方式撰寫:</p>
<pre class="cs" name="code">
public class IndexModel : PageModel
{
[Authorize(Roles = "Admin")]
public IActionResult OnGet()
{
return Page();
}
}
</pre>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-81056075182551738782021-10-15T11:09:00.001+08:002023-02-25T18:18:10.585+08:00讓 Net core 3.1 的 PageModel 可以用 POST 的參數決定 handler<p>Razor Page 的 handler 預設只能從 URL 的 QueryString 參數 ?handler=XXXX 決定要去的 handler,這有點不方便,因為我會用 <code><button type="submit" name="handler" value="Delete"></code> 的方式去傳送 handler,所以需要用 POST 也能決定 handler。</p>
<p>為了做到這點可以在 Startup.cs 進行 middleware 配置:</p>
<pre class="cs" name="code">
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
/* 使用 FormData 給路由 handler */
app.Use((context, next) =>
{
HttpRequest req = context.Request;
/* 判斷是否為 POST,且具有 handler 參數,然後覆蓋 Route 的值 */
if (req.HasFormContentType && req.Form.ContainsKey("handler"))
{ req.RouteValues["handler"] = req.Form["handler"]; }
return next();
});
//...
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-26829223319221978952021-10-15T10:34:00.004+08:002023-02-25T18:18:30.071+08:00讓 Net core 3.1 的 PageModel 可以 Properties Autowired<p>會考慮使用 Properties Autowired 是經過考慮跟評估的,畢竟 Properties Autowired 會造成負面的影響,就是 class 所相依的 class 會不清楚,很難像 Constructor 那樣清楚明白,但一般正常不會自己去建構 Controller 跟 PageModel,要建構的成本太大了,所以 Controller 跟 PageModel 很適合用 Properties Autowired。</p>
<br/>
<p>為了做到 Properties Autowired 可以用 IPageFilter 進行處理:</p>
<pre class="cs" name="code">
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
namespace XXXXX.Api.Filters
{
public class PageModelInjectFilter : IPageFilter
{
/// <summary>注入器的快取</summary>
private readonly ConcurrentDictionary<Type, Action<PageModel, IServiceProvider>> _injecterCache =
new ConcurrentDictionary<Type, Action<PageModel, IServiceProvider>>();
/// <summary>建構注入器</summary>
private Action<PageModel, IServiceProvider> buildInjecter(Type type)
{
/* delegate 具有疊加的能力,先建構一個空的 delegate */
Action<PageModel, IServiceProvider> action = (page, provider) => { };
/* 取得 Properties 且是可以寫入,並具有 [Inject] Attribute */
var props = type.GetProperties()
.Where(p => p.CanWrite)
.Where(p => p.IsDefined(typeof(InjectAttribute)));
foreach (var prop in props)
{
action += (page, provider) =>
{
/* 如果 Property 是已經有 Value 的就不要進行注入 */
if (prop.GetValue(page) != null) { return; }
/* 從 provider 取得依賴的物件 */
object value = provider.GetRequiredService(prop.PropertyType);
prop.SetValue(page, value);
};
}
return action;
}
/// <summary>在選取處理常式方法之後,但在進行模型系結之前呼叫。</summary>
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
/* 判斷是否是 PageModel */
var page = context.HandlerInstance as PageModel;
if (page == null) { return; }
/* 取得或建構注入器 */
Action<PageModel, IServiceProvider> injecter =
_injecterCache.GetOrAdd(page.GetType(), buildInjecter);
/* 進行注入 */
injecter(page, context.HttpContext.RequestServices);
}
/// <summary>在完成模型系結之後,于處理常式方法執行之前呼叫。</summary>
public void OnPageHandlerExecuting(PageHandlerExecutingContext context) { }
/// <summary>在處理常式方法執行之後,在動作結果執行之前呼叫。</summary>
public void OnPageHandlerExecuted(PageHandlerExecutedContext context) { }
}
}
</pre>
<br/>
<p>接著在 Startup.cs 進行過濾器配置</p>
<pre class="cs" name="code">
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddRazorPages(options =>
{
//...
})
.AddMvcOptions(options =>
{
//...
options.Filters.Add(new PageModelInjectFilter());
})
;
//...
}
</pre>
<br/>
<p>然後在 PageModel 就可以用下面的方式撰寫:</p>
<pre class="cs" name="code">
public class IndexModel : PageModel
{
[Inject] public IMenuProvider MenuProvider { private get; set; }
public IActionResult OnGet()
{
return Page();
}
}
</pre>
<br/>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-61888603785399785712015-02-26T14:40:00.001+08:002023-02-25T21:30:47.901+08:00[PHP][Java][C#] 用 XSD 驗證 XML<strong>menu_config.xml</strong> 要驗證的 XML<br />
<pre class="xml:nocontrols" name="code"><?xml version="1.0" encoding="utf-8"?>
<menu_config>
<menu title="文章管理" url="~/Article" target="" allow="">
<submenu title="列表" url="~/Article/list" target="" allow=""/>
<submenu/>
<submenu title="新增" url="~/Article/add" target="" allow=""/>
</menu>
<menu/>
<menu title="帳號管理" url="~/Admin"/>
</menu_config>
</pre><br />
<br />
<strong>menu_config.xsd</strong> 結構定義<br />
<pre class="xml:nocontrols" name="code"><?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="menu_config">
<xs:complexType>
<xs:sequence>
<xs:element name="menu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="menuType">
<xs:sequence>
<xs:element name="submenu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="title" type="xs:string"/>
<xs:attribute name="url" type="xs:string"/>
<xs:attribute name="target" type="xs:string"/>
<xs:attribute name="allow" type="xs:string"/>
</xs:complexType>
</xs:schema>
</pre><dl><dt>element minOccurs</dt>
<dd>最少的出現次數,不設置為至少出現 1 次,設置 0 為可有可無。</dd>
<dt>element maxOccurs</dt>
<dd>最大的出現次數,設置 unbounded 為無上限。</dd>
<dt>attribute type</dt>
<dd>型別必須為 QName,常用的有 xs:string, xs:date, xs:int, xs:integer, xs:decimal, xs:boolean, xs:double, xs:float。</dd> </dl><br />
<br />
<strong>PHP</strong><br />
<pre class="php:nocontrols" name="code">$xmlFile = 'menu_config.xml';
$xsdFile = 'menu_config.xsd';
/* 啟用自行錯誤處裡 */
libxml_use_internal_errors(true);
$xml = new DOMDocument();
$xml->load($xmlFile);
if (!$xml->schemaValidate($xsdFile)) {
/* 顯示錯誤訊息 */
print_r(libxml_get_errors());
libxml_clear_errors();
}
</pre><br />
<br />
<strong>C#</strong><br />
<pre class="cs:nocontrols" name="code">var menuConfig = XDocument.Load("menu_config.xml");
schemas.Add("", XmlReader.Create("menu_config.xsd"));
menuConfig.Validate(schemas, (o, e) => {
e.Message.Dump();
});
</pre><br />
<br />
<strong>Java</strong><br />
<pre class="java:nocontrols" name="code">import java.io.File;
import java.io.IOException;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXException;
public class TestXsd {
public static void main(String[] args) {
File xsd = new File("menu_config.xsd");
File xml = new File("menu_config.xml");
try {
SchemaFactory factory = SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI
);
Schema schema = factory.newSchema(xsd);
Validator validator = schema.newValidator();
validator.validate(new StreamSource(xml));
System.out.println("xml is valid");
}
catch (SAXException|IOException e) {
System.out.println("Reason: " + e.getLocalizedMessage());
}
}
}
</pre><br />
<br />
<br />
參考資料:<br />
<a href="https://msdn.microsoft.com/zh-tw/library/ms256142(v=vs.110).aspx" target="_blank">XML 結構描述項目 - MSDN</a><br />
<a href="http://www.w3schools.com/schema/default.asp" target="_blank">XML Schema Tutorial W3Schools</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-13726928689445292052014-03-09T17:47:00.001+08:002023-02-25T21:31:58.782+08:00理解 PredicateBuilder 實作方式PredicateBuilder 提供了以 OR 串接 bool 的 Lambda Expression,使用上會像下面的程式:<br />
<pre class="cs" name="code">var predicate = PredicateBuilder.False<Product>();
predicate = predicate.Or(p => p.Name == 'Shoe');
predicate = predicate.Or(p => p.Price > 100);
</pre><br />
<br />
它的原始碼如下:<br />
<pre class="cs" name="code">public static Expression<Func<T, bool>> Or<T> (
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2
)
{
var invokedExpr = Expression.Invoke (
expr2,
expr1.Parameters.Cast<Expression>()
);
return Expression.Lambda<Func<T, bool>>(
Expression.OrElse (expr1.Body, invokedExpr),
expr1.Parameters
);
}
</pre><br />
<br />
上面的程式雖然不長,但實在有點小難懂,搞不懂他為什麼要這麼寫??讓我們逐步解釋吧!<br />
<br />
<br />
假設 expr1 是 <code>x => x.Name == 'Shoe'</code><br />
假設 expr2 是 <code>y => y.Price > 100</code><br />
<br />
<pre class="cs:nogutter:nocontrols" name="code">var invokedExpr = Expression.Invoke(
expr2,
expr1.Parameters.Cast<Expression>()
);
</pre>先組合出( 利用 expr1 的參數去執行 expr2 )的表示式 invokedExpr<br />
產生出了語法會像 <code>(y => y.Price > 100) (x)</code><br />
<br />
<pre class="cs:nogutter:nocontrols" name="code">Expression.OrElse(expr1.Body, invokedExpr)
</pre>將 expr1.Body 與 invokedExpr 以 || 運算子組合起來<br />
產生出了語法會像 <code>x.Name == 'Shoe' || (y => y.Price > 100) (x)</code><br />
<br />
<pre class="cs:nogutter:nocontrols" name="code">Expression.Lambda<Func<T, bool>>(
Expression.OrElse(expr1.Body, invokedExpr),
expr1.Parameters
);
</pre>將剛剛組合好的 || 運算表示式再組合成 Lambda 表示式<br />
產生出了語法會像 <code>x => (x.Name == 'Shoe' || (y => y.Price > 100) (x))</code><br />
<br />
<br />
參考來源:<br />
<a href="http://www.albahari.com/nutshell/predicatebuilder.aspx">Dynamically Composing Expression Predicates</a> <br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-33163877699696296622014-03-05T23:38:00.001+08:002023-02-25T21:32:20.715+08:00利用 HTTP Status Codes 傳遞 Ajax 成功失敗的狀態一般處理 Ajax 回應時會傳送的資訊種類有:資料、成功訊息、錯誤訊息、失敗訊息以及處理狀態,傳遞的資訊種類並不一致,再加上除了資料之外,通常還希望能傳遞處理狀態,這種情況大部分會選擇是以 JSON 的方式傳遞這兩個訊息,以下是常見的幾種格式:<br />
<br />
<pre class="js" name="code">{ code: 1, msg: "OK" }
{ success: true, result: "data", errorMsg: "" }
{ status: 'success', result: [], errorMsg: "" }
//...
</pre><br />
但以執行狀態跟操作行為作一個歸納,可以區分以下幾種回傳結果:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr class="header">
<th>資料操作</th>
<th>HTTP Method</th>
<th>成功</th>
<th>錯誤/失敗</th>
</tr>
<tr>
<td>檢視(Read)</td>
<td>GET</td>
<td>資料</td>
<td>錯誤/失敗訊息</td>
</tr>
<tr>
<td>新增(Create)<br />
修改(Update)<br />
刪除(Delete)</td>
<td>POST</td>
<td>成功訊息</td>
<td>錯誤/失敗訊息</td>
</tr>
</table><br />
從上面的歸納可以看出規律性,接著只要有方法可以傳送處理的狀態,以及能夠區分資料的種類,其實就單純很多,而 HTTP Status Codes 就是用來傳遞 HTTP 的處理狀態,如果利用這個方式來傳遞自訂的處理狀態,這樣 HTTP Content 就可以很單純傳遞資料,讓資料格式不受限於 JSON,還可以使用其他格式(text, xml, html),而且 XMLHttpRequest 本身就有處理 HTTP Status Codes 的能力,而 jQuery.ajax 也有提供 error status 的處理,所以可以利用這個來定義狀態的處理,在 HTTP Status Codes 有幾個已經定義狀態,很適合用來回傳處理狀態的資訊:<br />
<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr>
<th>400</th>
<td>Bad Request</td>
<td>錯誤的請求</td>
<td>適用在表單內容的錯誤,如必填欄位未填、Email 格式錯誤</td>
</tr>
<tr>
<th>403</th>
<td>Forbidden</td>
<td>沒有權限,被禁止的</td>
<td>適用在沒有登入或權限不足</td>
</tr>
<tr>
<th>500</th>
<td>Internal Server Error</td>
<td>內部服務器錯誤</td>
<td>適用在程式的錯誤</td>
</tr>
</table><br />
<br />
jQuery 接收資訊的範例<br />
<pre class="js" name="code">$.ajax({
type: "POST",
url: document.location,
success: function (data, textStatus, jqXHR) {
alert(data);
},
error: function (jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText);
}
});
</pre> <br />
<br />
PHP 傳遞錯誤訊息的範例 <br />
<pre class="php" name="code">if (php_sapi_name() == 'cgi'){
header("Status: 400 Bad Request");
}else{
header("HTTP/1.0 400 Bad Request");
}
exit("儲存失敗!!");
</pre><br />
<br />
C# MVC 傳遞錯誤訊息的範例 <br />
<pre class="cs" name="code">Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 400;
return Content("儲存失敗!!");
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-40523079476201578702014-02-21T17:43:00.002+08:002023-02-25T21:33:00.264+08:00[T4] url, base64, sprite 三種格式的 icons.css 產生器之前有寫過 <a href="/2013/08/php-make-icons-css.html">[PHP] url, base64, sprite 三種格式的 icons.css 產生器</a>,這次換用 C# T4 Text Templates 來製作這個功能:<br />
<br />
<pre class="cs" name="code">int spriteGap = 30;
var scanTypes = new string[]{".jpg", ".gif", ".png"};
string workDirectory =
Path.GetDirectoryName(this.Host.TemplateFile);
/* 取得圖檔資訊 */
List<ImageInfo> imageList = Directory
.EnumerateFiles(Path.Combine(workDirectory, "icons"))
.Where(path => scanTypes.Contains(Path.GetExtension(path)))
.Select(path =>
{
var image = Image.FromFile(path);
return new ImageInfo{
FilePath = path,
FileName = Path.GetFileName(path),
IconImage = image,
IconName = Path.GetFileNameWithoutExtension(path),
TopOffset = 0,
Width = image.Width,
Height = image.Height,
IsAnimated = ImageAnimator.CanAnimate(image),
};
})
.OrderBy(info => info.IsAnimated)
.ToList();
/*檢查重複的圖檔名稱*/
List<string> repeatList = imageList.GroupBy(info => info.IconName)
.Where(g => g.Count() > 1)
.SelectMany(g => g.Select(x => x.FileName))
.ToList();
if(repeatList.Count > 0){
throw new Exception(
"出現重複的圖檔名稱[" + String.Join(", ", repeatList) + "]"
);
}
/* 製作 CSS Sprite */
int sumHeight = imageList.Sum( info => info.Height);
int spriteWidth = imageList.Max( info => info.Width);
int spriteHeight = sumHeight + (imageList.Count - 1) * spriteGap;
var bitmap = new Bitmap(spriteWidth, spriteHeight);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
int nextTop = 0;
for(int i=0; i< imageList.Count; i++){
/*忽略具有動畫的 GIF 圖片*/
if(imageList[i].IsAnimated){ continue; }
imageList[i].TopOffset = nextTop;
graphics.DrawImage(imageList[i].IconImage, 0, nextTop);
nextTop = nextTop + imageList[i].Height + spriteGap;
}
graphics.Save();
}
SaveOutput("icons.sprite.png");
bitmap.Save(
Path.Combine(workDirectory, "icons.sprite.png"),
ImageFormat.Png
);
bitmap.Dispose();
</pre><br />
下載完整程式: <a href="https://dl.dropboxusercontent.com/u/16784943/code-demo/t4_make_icons_css.zip">t4_make_icons_css.zip</a><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-19108392657915972032014-02-15T14:48:00.001+08:002023-02-25T21:33:29.518+08:00[C#] 取得 Domain 的 IP 列表<pre class="cs" name="code">//using System.Net;
IPAddress[] ipList = Dns.GetHostAddresses("www.google.com");
</pre> <br />
<br />
來源: <br />
<a target="_blank" href="http://msdn.microsoft.com/zh-tw/library/system.net.dns.gethostaddresses%28v=vs.100%29.aspx">Dns.GetHostAddresses 方法 (System.Net)</a> <br />
<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-35414923386735070402014-02-15T14:45:00.002+08:002023-02-25T21:34:02.982+08:00[C#] delegate 到 Lambda Expressions 語法演進一開始要看懂 Lambda Expressions 有點困難,下面會以演進方式來介紹如何做到語法省略。<br />
<br />
首先定義一個單參數的 delegate<br />
<pre class="cs:nogutter:nocontrols" name="code">delegate int Del(int x);
</pre><br />
以傳統 delegate 的語法來建構 delegate <br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = delegate(int x) { return x + 2; };
</pre><br />
去掉 delegate 改成 Lambda 表示式<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = (int x) => { return x + 2; };
</pre><br />
由於大括號裡只有一句陳述式,而且是一個 return 的陳述式,所以可以省略大括號跟 return<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = (int x) => x + 2;
</pre><br />
在 delegate 已經有定義輸入參數的型別,所以在小括號裡的型別可以省略<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = (x) => x + 2;
</pre><br />
由於小括號裡面只有一個輸入參數,所以可以再進一步省略小括號<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = x => x + 2;
</pre><br />
參考來源:<br />
<a target="_blank" href="http://msdn.microsoft.com/zh-tw/library/bb397687(v=vs.100).aspx">Lambda 運算式 (C# 程式設計手冊)</a><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com1tag:blogger.com,1999:blog-5946530704742130970.post-17679267041939195262014-02-10T23:43:00.001+08:002023-02-25T21:34:43.604+08:00[C#] 對 FirstOrDefault 新的認識FirstOrDefault 會依據最後的型別去決定 Default 時回傳的值,例如下面的範例:<br />
<pre class="cs" name="code">int? a = (new List<int>{2}).Select(x => x).FirstOrDefault();
// 2
int? b = (new List<int>{}).Select(x => x).FirstOrDefault();
// 0
int? c = (new List<int>{2}).Select(x => (int?)x).FirstOrDefault();
// 2
int? d = (new List<int>{}).Select(x => (int?)x).FirstOrDefault();
// null
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-78398738467580377542014-02-10T23:38:00.003+08:002023-02-25T21:34:49.498+08:00[C#] 用 LINQ 將字串切割成整數陣列<pre class="cs" name="code">int id;
int[] idArray = "1,2,3,4,5".Split(',')
.Where(idStr => Int32.TryParse(idStr, out id))
.Select(Int32.Parse)
.ToArray();
</pre><br />
這裡用到將 Int32.Parse 這個一般 method 指向給 delegate 的技巧。 Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-68430560756383572742014-02-05T20:53:00.003+08:002023-02-25T21:35:02.492+08:00[C#] HttpUtility.ParseQueryString 的隱藏密技在使用 Request.QueryString 發現 ToString 會產生 URL 的 query 字串,嘗試用 NameValueCollection 的 ToString 卻不是產生 URL 的 query 字串,這一整個就很奇怪,明明都是 NameValueCollection 確有不一樣的結果,透過 Reflector 發現 Request.QueryString 的 instance 型別是一個 HttpValueCollection,想說可以直接 new HttpValueCollection 出來使用,但 HttpValueCollection 卻是 System.Web 的內部 Class,外部是無法直接 new 出來使用,還好在又發現 HttpUtility.ParseQueryString 回傳的 NameValueCollection 的 instance 是 HttpValueCollection 這個型別,所以可以透過 HttpUtility.ParseQueryString 來建立 HttpValueCollection。<br />
<br />
<pre class="cs" name="code">// using System.Web;
var qs1 = HttpUtility.ParseQueryString("id=5&type=1");
qs1.ToString(); // "id=5&type=1"
var qs2 = HttpUtility.ParseQueryString(String.Empty);
qs2["id"] = "11";
qs2["name"] = "Tom";
qs2.ToString(); // "id=11&name=Tom"
</pre><br />
<br />
<strong>HttpValueCollection 的簽名</strong><br />
<pre class="cs" name="code">[Serializable]
internal class HttpValueCollection : NameValueCollection
{
}
</pre><br />
<br />
<strong>ParseQueryString 的簽名</strong><br />
<pre class="cs" name="code">public static NameValueCollection ParseQueryString(string query, Encoding encoding)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if (encoding == null)
{
throw new ArgumentNullException("encoding");
}
if ((query.Length > 0) && (query[0] == '?'))
{
query = query.Substring(1);
}
return new HttpValueCollection(query, false, true, encoding);
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-61519613561956618302014-01-30T20:33:00.001+08:002023-02-25T21:35:54.725+08:00[C#] 圖片縮圖<a href="http://msdn.microsoft.com/zh-tw/library/system.drawing(v=vs.100).aspx" target="_blank">System.Drawing namespace</a> 提供對 GDI+ 基本繪圖功能的存取,可以方便處理圖片的操作,下面是一個處理圖片縮圖的範例:<br />
<br />
<pre class="cs" name="code">//using System.Drawing;
var image = Image.FromFile(@"D:\001.jpg");
int width = 120, height = 100;
float targetRatio = (float)width / (float)height;
float imageRatio = (float)image.Width / (float)image.Height;
if(imageRatio < targetRatio)
{
width = Math.Max(1, height * image.Width / image.Height);
}
else
{
height = Math.Max(1, width * image.Height / image.Width);
}
var thumbnail = image.GetThumbnailImage(width, height, null, IntPtr.Zero);
thumbnail.Save(@"D:\thumb.jpg");
</pre><br />
這個方式可以容易的做到圖片縮圖,但使用這個方式會很佔用記憶體,在處理尺寸大的圖片時有可能會出現 out of memory,而且在 MSDN 有以下的警示:<br />
<blockquote>Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. For a supported alternative, see Windows Imaging Components.</blockquote>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-2069599425708385922013-05-01T21:59:00.003+08:002023-02-25T21:36:03.320+08:00[C#] 利用 LINQ 解析 XML 至 POCO<b>menu_config.xml</b><br />
<pre class="xml" name="code"><?xml version="1.0" encoding="utf-8"?>
<menu_config>
<menu title="文章管理" url="~/Article" target="" allow="">
<submenu title="列表" url="~/Article/list" allow="" />
<submenu />
<submenu title="新增" url="~/Article/add" allow="" />
</menu>
<menu />
<menu title="帳號管理" url="~/Admin" />
</menu_config>
</pre><br />
<br />
<pre class="cs" name="code">//using System.Collections.Generic;
//using System.IO;
//using System.Linq;
//using System.Xml.Linq;
//using System.Text;
public class MenuDataModel
{
/*POCO 資料欄位*/
public string Title { get; set; }
public string Url { get; set; }
public string Target { get; set; }
public string Allow { get; set; }
public string Icon { get; set; }
public List<MenuDataModel> Submenu { get; set; }
/*取得資料清單*/
public static List<MenuDataModel> GetList(string menuConfigPath)
{
/*讀取 XML 檔案*/
var xmlContent = File.ReadAllText(menuConfigPath, Encoding.UTF8);
var menuDocument = XDocument.Parse(xmlContent);
/*利用 LINQ 轉成 POCO*/
return menuDocument.Root.Elements("menu")
.Select(menu => new MenuDataModel
{
/*取得 Element 上的 Attribute*/
Title = (string) menu.Attribute("title"),
Url = (string) menu.Attribute("url"),
Target = (string) menu.Attribute("target"),
Icon = (string) menu.Attribute("icon") ?? "Item",
Allow = (string) menu.Attribute("allow"),
/*取得子層級 Element 上的 Attribute*/
Submenu = menu.Elements("submenu")
.Select(sub => new MenuDataModel
{
Title = (string) sub.Attribute("title"),
Url = (string) sub.Attribute("url"),
Target = (string) sub.Attribute("target"),
Icon = (string) sub.Attribute("icon") ?? "Item",
Allow = (string) sub.Attribute("allow"),
}).ToList(),
}).ToList();
}
}
void Main()
{
var path = @"D:\menu_config.xml";
List<MenuDataModel> list = MenuDataModel.GetList(path);
list.Dump();
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-74912770117433044762013-05-01T21:42:00.004+08:002023-02-25T21:36:08.205+08:00[C#] 取得 URL 頁面上的 title 內容<pre class="cs" name="code">//using System.Net;
//using System.IO;
//using System.Text;
string url = @"http://msdn.microsoft.com/en-us/library/az24scfc.aspx";
string title = String.Empty;
WebResponse response = null;
WebRequest request = WebRequest.Create(url);
/*設定最長執行的毫秒數*/
request.Timeout = 10000;
try{
/*取得 URL 頁面資料*/
response = request.GetResponse();
StreamReader stream = new StreamReader(
response.GetResponseStream(), Encoding.UTF8
);
/*只取得前 4096 個字*/
char[] buf = new char[4096];
stream.Read(buf, 0, buf.Length);
/*尋找標題字串*/
string pageText = new String(buf);
string pattern = @"(?<=<title[^>]*>)([^<]*)(?=</title>)";
title = Regex.Match(pageText, pattern, RegexOptions.IgnoreCase)
.Value.Trim();
}catch(WebException e){
}finally{
if(response!=null){ response.Close(); }
}
title.Dump();
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0