Design

Design
asp.net mvc
顯示具有 ASP.NET MVC 標籤的文章。 顯示所有文章
顯示具有 ASP.NET MVC 標籤的文章。 顯示所有文章

2017年5月21日 星期日

ASP.NET MVC 三層架構Three tier

Q 什麼是三層架構
A: 簡單說就是把開發專案分為三大區塊
Controlle:負責導覽資料的責任 我資料該去哪裡 我頁面該怎走,盡量避免把較複雜邏輯寫在這.
Service:資料處理的位置 也算是code可能會最多的地方,例如我想要把A資料跟B資料作處理完再傳給Controller.
Repository:接受DB的資料位置這這部分往往就是要開始定義資料格式的位置然後再把資料傳給Service做處理.

2017年5月17日 星期三

ASP.NET MVC Owin Identity 實作登入驗證

首先我們需要nuget兩個套件

Microsoft.Owin.Host.SystemWeb - 
OWIN server that enables OWIN-based applications to run on IIS using the ASP.NET request pipeline.
使OWIN應用程式能在IIS上面運行

Microsoft.Owin.Security.Cookies -
Middleware that enables an application to use cookie based authentication, similar to ASP.NET's forms authentication.
使應用程式能使用 cookie based authentication, 像asp.net 的forms驗證


 public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                //作為辨識的的Cookie屬性
                AuthenticationType = "ApplicationCookie",
                //如果無權限存取401 最後導頁的位置
                LoginPath = new PathString("/Home/index")
            });
        }
    }


這邊我們先讓所有畫面都需要經過驗證才能存取 建立個FilterConfig
   public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new AuthorizeAttribute());
        }
    }
然後在我們的Global.asax 讓我們啟動程序時去做驗證 這樣我們每個畫面都需要經過我們的驗證才能顯示
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
當然我們需要預設個登入的畫面 這畫面是[AllowAnonymous] 任何人都可以存取的畫面
    [AllowAnonymous]
        public ActionResult Index()
        {
            return View();
        }


然後建立我們 登入的ActionResult 驗證方法就自己看想怎寫 然後把
[HttpPost]
public ActionResult LogIn(LogInModel model)
{
    if (!ModelState.IsValid)
    {
        return View();
    }

    // Don't do this in production!
    if (model.Email == "admin@admin.com" && model.Password == "password")
    {
        var identity = new ClaimsIdentity(new[] {
                new Claim(ClaimTypes.Name, "Ben"),
                new Claim(ClaimTypes.Email, "a@b.com"),
                new Claim(ClaimTypes.Country, "England")
            },    
            "ApplicationCookie");

        var ctx = Request.GetOwinContext();
        var authManager = ctx.Authentication;

        authManager.SignIn(identity);

        return RedirectToAction("Index", "Home");
    }


1.首先建立了一個ClaimsIdentity object,這個物件包含目前使用者資訊。Claim架構提供Client一個持續驗證用的Cookie。 

2.這邊也提供了authentication type,這必須要對應到在Startup中宣告的authentication type,兩者要相同。 

3.接著從Owin Context 中取得IAuthenticationManager instance。會在startup過程中自動註冊。 

4.接著呼叫IAuthenticationManager.SignIn傳送claims identity。這會設定Client端的authentication cookie。

 5.最後把使用者的瀏覽器導回你想要的位置 這時候我們前端就可以 依照是否驗證過去判斷畫面

     //如果登入了
    @if (Request.IsAuthenticated)
                        {
                   //Corrent User 當前登入人的名稱 這裡是對應ClaimTypes.Name
                       <li><a>@User.Identity.Name</a></li>
                      <li><a href="@Url.Action(" logoff="" ome="">登出</a></li>
                        }
                        else
                        {
                          //還沒登入時的畫面
                        <li><a href="@Url.Action(" login="" ome="">登入</a></li>
                         }



當然我們的ClaimsIdentity  還可以加入 ClaimTypes.Role 的部分 讓我們依照每個使用者的權限下去做是否有存取權限的判斷

 
        [Authorize(Roles = "admin")]  

2017年4月8日 星期六

StackExchange.Redis 簡單應用

StackExchange.Redis.Extensions 的使用 以下代碼基於 StackExchange.Redis.Extensions.Newtonsoft(Json.NET)實現對泛型的直接添加和獲取。當然,也可以直接以字符串的形式存儲,自行對內容進行序列化和反序列化。 首先我們先Install
PM> Install-Package StackExchange.Redis.Extensions.Newtonsoft
添加好我們的Web.config https://github.com/imperugo/StackExchange.Redis.Extensions

private RedisHelper()
{
    _client = new StackExchangeRedisCacheClient(new NewtonsoftSerializer());
}
接下來我們就可以根據_client 簡單建立 get set
public T Get(string key)
{
    return _client.Get(key);
}

public bool Set(string key, T data)
{
    return _client.Add(key, data);
}
後續你就可以建立相關 Interface DI後去應用他 非常簡單吧

ASP.NET MVC AJAX AjaxAntiForgeryToken 防範CSRF

維基百科,自由的百科全書

跨站請求偽造英語:Cross-site request forgery),也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF, 是一種挾制用戶在當前已登錄的Web應用程式上執行非本意的操作的攻擊方法。[1] 跟跨網站指令碼(XSS)相比,XSS 利用的是用戶對指定網站的信任,CSRF 利用的是網站對用戶網頁瀏覽器的信任。

我們怎麼防止被外部網站攻擊我們的資料,廢話不多 就開始吧


我們先建立我們要在呼叫前的驗證
  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class AjaxValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
    {

        public Task ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation)
        {
         
            if (actionContext == null)
            {
                throw new ArgumentNullException("HttpActionContext");
            }

            if (actionContext.Request.Method != HttpMethod.Get)
            {
             
                return ValidateAntiForgeryToken(actionContext, cancellationToken, continuation);
            }

            return continuation();
        }

        private Task ValidateAntiForgeryToken(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation)
        {
            try
            {
                string cookieToken = "";
                string formToken = "";
                IEnumerable tokenHeaders;

       
                if (actionContext.Request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
                {
                    string[] tokens = tokenHeaders.First().Split(':');
                    if (tokens.Length == 2)
                    {
                        cookieToken = tokens[0].Trim();
                        formToken = tokens[1].Trim();
                    }
                }
                AntiForgery.Validate(cookieToken, formToken);
            }
            catch (System.Web.Mvc.HttpAntiForgeryException ex)
            {
                actionContext.Response = new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.Forbidden,
                    RequestMessage = actionContext.ControllerContext.Request
                };
                return FromResult(actionContext.Response);
            }
            return continuation();
        }
        private Task FromResult(HttpResponseMessage result)
        {
            var source = new TaskCompletionSource();
            source.SetResult(result);
            return source.Task;
        }
    }



加在我們取資料的action上
        [HttpPost]
        [AjaxValidateAntiForgeryToken]
        public async Task GetAccountbyEmail()
        {
            var Email = new GetByMail();
            Email.Email = User.Identity.GetAccountEmail();

            var Request = await _accountService.GetByEmail(Email);

            return Request;
        }

我們再去建立要放在cshtml頁面的HtmlHelper去建立我們的Token
public static MvcHtmlString AjaxAntiForgeryToken(this HtmlHelper helper)
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            TagBuilder builder = new TagBuilder("div");
            string ForgeryToken = string.Concat(cookieToken,":", formToken);
            builder.MergeAttribute("id", "AntiForgery");
            builder.MergeAttribute("Value", ForgeryToken);
            builder.ToString(TagRenderMode.Normal);
            return  MvcHtmlString.Create(builder.ToString());

        }

然後在我們的cshtml頁面使用他 @Html.AjaxAntiForgeryToken()

 接著最重要的就是我們要在前端呼在我們的headers 加上剛剛在cshtml建立的token
this.http.get(url, { headers: { 'RequestVerificationToken': $("#AntiForgery").attr("value") } });

完成我們的驗證步驟 當進入頁面 會產生 token > 接著我們撈取資料時會針對herder上的token 去做驗證 > 接著要做的事情就可以自己處理了

asp.net mvc 常用的ActionFilter

我們開發時常常需要再Action或Action後 進行一下邏輯處理 這時候我們就會用到我們的ActionFilter

在 ActionFilterAttribute 中有提供四個覆蓋方法
 OnActionExecuting – Action 之前執行
OnActionExecuted – Action 之後執行
OnResultExecuting – Action Result 之前執行
OnResultExecuted – Action Result 之後執行

 我們先建一個 class 去繼承 ActionFilterAttribute class 名稱規則就是 名稱+Attribute 例如下面
 public class DosomethingAttribute : ActionFilterAttribute
    {
        /// 
        /// Action 之前執行
        /// 
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            //想做啥就做啥
        }
        /// 
        /// Action 之後執行
        /// 
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            //想做啥就做啥
        }
        /// 
        /// Action Result 之前執行
        /// 
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            //想做啥就做啥
        }
        /// 
        /// Action Result 之後執行
        /// 
        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            //想做啥就做啥
        }
    }

再來例如我想再進入index action 引用 我就可以這樣使用

        [Dosomething]
        public ActionResult Index()
        {
            return View();
        }

2016年8月2日 星期二

Asp.net mvc Salt Hashing 建立安全的會員註冊

首先為什麼我們需要 Salt Hashing我們的密碼 目的是為了防範資料庫在不慎外洩的情況下密碼也不會透露,那實際該怎做了 主要思路為我們會員在註冊時隨機產生個值我們稱值為salt再加上我們的密碼最後經過hash加密儲存在我們的資料庫,最後在多個欄位儲存我們隨機產生的salt值,因為我們hash是不可逆的所以就算在資料庫外洩的情況下也不會透漏我們真實的密碼,所以其實真正的密碼是如何也只有本人知道,就這是為什麼我們在很多網站忘記密碼時官方選擇給你重新設定新密碼而不是直接給你密碼的原因,現在我們有了hash(salt+password)的值跟salt的值就會下面圖一樣

這樣我們就是在登入時透過使用者輸入的password加上我們資料庫儲存的salt的值再透過hash去比對我們資料庫passwordhash的值是否一樣達到安全的目的



那我們該如何在asp.net mvc 重現呢 如下
首先建立我們的註冊會員model
public class User  
{  
    [Key]  
    public int RegistrationId  
    {  
        get;  
        set;  
    } //This will be primary key column with auto increment  
    public string FirstName  
    {  
        get;  
        set;  
    }  
    public string LastName  
    {  
        get;  
        set;  
    }  
    public string UserName  
    {  
        get;  
        set;  
    }  
    public string EmailId  
    {  
        get;  
        set;  
    }  
    public string Password  
    {  
        get;  
        set;  
    }  
    public string Gender  
    {  
        get;  
        set;  
    }  
    public string VCode  
    {  
        get;  
        set;  
    }  
    public DateTime CreateDate  
    {  
        get;  
        set;  
    }  
    public DateTime ModifyDate  
    {  
        get;  
        set;  
    }  
    public bool Status  
    {  
        get;  
        set;  
    }  
}  
隨自己喜好建立DB 我是用code first migrations update
public class CmsDbContext : DbContext  
{  
    public DbSet ObjRegisterUser { get; set; } // Here User is the class
}  
寫個加密的helper要用的時候可以叫用
public static class Helper  
{  
    public static string ToAbsoluteUrl(this string relativeUrl) //Use absolute URL instead of adding phycal path for CSS, JS and Images     
    {  
        if (string.IsNullOrEmpty(relativeUrl)) return relativeUrl;  
        if (HttpContext.Current == null) return relativeUrl;  
        if (relativeUrl.StartsWith("/")) relativeUrl = relativeUrl.Insert(0, "~");  
        if (!relativeUrl.StartsWith("~/")) relativeUrl = relativeUrl.Insert(0, "~/");  
        var url = HttpContext.Current.Request.Url;  
        var port = url.Port != 80 ? (":" + url.Port) : String.Empty;  
        return String.Format("{0}://{1}{2}{3}", url.Scheme, url.Host, port, VirtualPathUtility.ToAbsolute(relativeUrl));  
    }  
    public static string GeneratePassword(int length) //length of salt    
    {  
        const string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";  
        var randNum = new Random();  
        var chars = new char[length];  
        var allowedCharCount = allowedChars.Length;  
        for (var i = 0; i <= length - 1; i++)  
        {  
            chars[i] = allowedChars[Convert.ToInt32((allowedChars.Length) * randNum.NextDouble())];  
        }  
        return new string(chars);  
    }  
    public static string EncodePassword(string pass, string salt) //encrypt password    
    {  
        byte[] bytes = Encoding.Unicode.GetBytes(pass);  
        byte[] src = Encoding.Unicode.GetBytes(salt);  
        byte[] dst = new byte[src.Length + bytes.Length];  
        System.Buffer.BlockCopy(src, 0, dst, 0, src.Length);  
        System.Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);  
        HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");  
        byte[] inArray = algorithm.ComputeHash(dst);  
        //return Convert.ToBase64String(inArray);    
        return EncodePasswordMd5(Convert.ToBase64String(inArray));  
    }  
    public static string EncodePasswordMd5(string pass) //Encrypt using MD5    
    {  
        Byte[] originalBytes;  
        Byte[] encodedBytes;  
        MD5 md5;  
        //Instantiate MD5CryptoServiceProvider, get bytes for original password and compute hash (encoded password)    
        md5 = new MD5CryptoServiceProvider();  
        originalBytes = ASCIIEncoding.Default.GetBytes(pass);  
        encodedBytes = md5.ComputeHash(originalBytes);  
        //Convert encoded bytes back to a 'readable' string    
        return BitConverter.ToString(encodedBytes);  
    }  
    public static string base64Encode(string sData) // Encode    
    {  
        try  
        {  
            byte[] encData_byte = new byte[sData.Length];  
            encData_byte = System.Text.Encoding.UTF8.GetBytes(sData);  
            string encodedData = Convert.ToBase64String(encData_byte);  
            return encodedData;  
        }  
        catch (Exception ex)  
        {  
            throw new Exception("Error in base64Encode" + ex.Message);  
        }  
    }  
    public static string base64Decode(string sData) //Decode    
    {  
        try  
        {  
            var encoder = new System.Text.UTF8Encoding();  
            System.Text.Decoder utf8Decode = encoder.GetDecoder();  
            byte[] todecodeByte = Convert.FromBase64String(sData);  
            int charCount = utf8Decode.GetCharCount(todecodeByte, 0, todecodeByte.Length);  
            char[] decodedChar = new char[charCount];  
            utf8Decode.GetChars(todecodeByte, 0, todecodeByte.Length, decodedChar, 0);  
            string result = new String(decodedChar);  
            return result;  
        }  
        catch (Exception ex)  
        {  
            throw new Exception("Error in base64Decode" + ex.Message);  
        }  
    }  
}   
然後我們最重要得Controller
public ActionResult Registration()  
{  
    return View();  
}  
[ValidateAntiForgeryToken]  
[HttpPost]  
public ActionResult Registration(User objNewUser)  
{  
    try  
    {  
        using(var context = new CmsDbContext())  
        {  
            var chkUser = (from s in context.ObjRegisterUser where s.UserName == objNewUser.UserName || s.EmailId == objNewUser.EmailId select s).FirstOrDefault();  
            if (chkUser == null)  
            {  
                var keyNew = Helper.GeneratePassword(10);  
                var password = Helper.EncodePassword(objNewUser.Password, keyNew);  
                objNewUser.Password = password;  
                objNewUser.CreateDate = DateTime.Now;  
                objNewUser.ModifyDate = DateTime.Now;  
                objNewUser.VCode = keyNew;  
                context.ObjRegisterUser.Add(objNewUser);  
                context.SaveChanges();  
                ModelState.Clear();  
                return RedirectToAction("LogIn", "Login");  
            }  
            ViewBag.ErrorMessage = "User Allredy Exixts!!!!!!!!!!";  
            return View();  
        }  
    }  
    catch (Exception e)  
    {  
        ViewBag.ErrorMessage = "Some exception occured" + e;  
        return View();  
    }  
} 
最後我們登入需要解密去對應我們DB的hash
public ActionResult Login()  
{  
    return View();  
}  
[ValidateAntiForgeryToken]  
[HttpPost]  
public ActionResult LogIn(string userName, string password)  
{  
    try  
    {  
        using(var context = new CmsDbContext())  
        {  
            var getUser = (from s in context.ObjRegisterUser where s.UserName == userName || s.EmailId == userName select s).FirstOrDefault();  
            if (getUser != null)  
            {  
                var hashCode = getUser.VCode;  
                //Password Hasing Process Call Helper Class Method    
                var encodingPasswordString = Helper.EncodePassword(password, hashCode);  
                //Check Login Detail User Name Or Password    
                var query = (from s in context.ObjRegisterUser where(s.UserName == userName || s.EmailId == userName) && s.Password.Equals(encodingPasswordString) select s).FirstOrDefault();  
                if (query != null)  
                {  
                    //RedirectToAction("Details/" + id.ToString(), "FullTimeEmployees");    
                    //return View("../Admin/Registration"); url not change in browser    
                    return RedirectToAction("Index", "Admin");  
                }  
                ViewBag.ErrorMessage = "Invallid User Name or Password";  
                return View();  
            }  
            ViewBag.ErrorMessage = "Invallid User Name or Password";  
            return View();  
        }  
    }  
    catch (Exception e)  
    {  
        ViewBag.ErrorMessage = " Error!!! contact cms@info.in";  
        return View();  
    }  
}  

2016年7月4日 星期一

asp.net mvc Error Redirect 處理錯誤頁面轉址

public class ErrorsController : Controller
{
    public ActionResult General(Exception exception)
    {
        return Content("General failure", "text/plain");
    }

    public ActionResult Http404()
    {
        return Content("Not found", "text/plain");
    }

    public ActionResult Http403()
    {
        return Content("Forbidden", "text/plain");
    }
}
protected void Application_Error()
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();
    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "General";
    routeData.Values["exception"] = exception;
    Response.StatusCode = 500;
    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 403:
                routeData.Values["action"] = "Http403";
                break;
            case 404:
                routeData.Values["action"] = "Http404";
                break;
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}

2016年5月14日 星期六

ASP.NET MVC 從空白專案新增BundleConfig 實做

NuGet:
Install-Package Microsoft.AspNet.Web.Optimization
建立App_Start\BundleConfig.cs:
自行修改要的
public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles) {
        bundles.Add(new ScriptBundle("~/Scripts/jquery").Include(
            "~/Scripts/Lib/jquery/jquery-{version}.js",
            "~/Scripts/Lib/jquery/jquery.*",
            "~/Scripts/Lib/jquery/jquery-ui-{version}.js")
        );

        bundles.Add(new ScriptBundle("~/Scripts/knockout").Include(
             "~/Scripts/Lib/knockout/knockout-{version}.js",
             "~/Scripts/Lib/knockout/knockout-deferred-updates.js")
        );
    }
}

 Global.asax 新增這行
using System.Web.Optimization;

protected void Application_Start() {
     ...
     BundleConfig.RegisterBundles(BundleTable.Bundles);
     ...
}

最後別忘了web.config 新增命名空間 不然你也不能用



      <namespaces>
        <add namespace="System.Web.Optimization"></add>
      </namespaces>
讓我的開始Bundle吧
    @Scripts.Render("~/Scripts/jquery")
    @Scripts.Render("~/Scripts/bootstrap")

2016年2月29日 星期一

ASP.NET MVC Controller常用的Return 範例


ASP.NET MVC Controller常用的Return 範例  
ttp301永久轉址
return RedirectPermanent("www.yahoo.com.tw");//外部
return RedirectToActionPermanent("Contact"); //內部
http302暫時轉址
return Redirect("www.yahoo.com.tw");
return RedirectToAction("PasswordError", "Login");
跳到錯誤頁面
return HttpNotFound("error123");
return new HttpStatusCodeResult(404, "nod found");
跳到指定的檢視頁面
return View("About");
跳到指定的檢視頁面,並指定主版頁面
return View("About","_LayoutPage1");
回傳值
(也可傳回html)
return Content("回傳內容"); //