關於WEB Service&WCF&WebApi實現身份驗證之WebApi篇

来源:http://www.cnblogs.com/zuowj/archive/2016/01/20/5123943.html
-Advertisement-
Play Games

之前先後總結併發表了關於WEB Service、WCF身份驗證相關文章,如下:關於WEB Service&WCF&WebApi實現身份驗證之WEB Service篇、關於WEB Service&WCF&WebApi實現身份驗證之WCF篇(1)、關於WEB Service&WCF&WebApi實現身份...


之前先後總結併發表了關於WEB Service、WCF身份驗證相關文章,如下:

關於WEB Service&WCF&WebApi實現身份驗證之WEB Service篇

關於WEB Service&WCF&WebApi實現身份驗證之WCF篇(1)關於WEB Service&WCF&WebApi實現身份驗證之WCF篇(2)

今天再來總結關於如何實現WebApi的身份驗證,以完成該系列所有文章,WebApi常見的實現方式有:FORM身份驗證、集成WINDOWS驗證、Basic基礎認證、Digest摘要認證

 第一種:FORM身份驗證(若在ASP.NET應用程式使用,則該驗證方式不支持跨域,因為cookie無法跨域訪問)

1.定義一個FormAuthenticationFilterAttribute,該類繼承自AuthorizationFilterAttribute,並重寫其OnAuthorization,在該方法中添加從請求頭中獲取有無登錄的Cookie,若有則表示登錄成功,否則失敗,代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Filters;
using System.Web.Security;
using System.Net.Http;
using System.Collections.ObjectModel;
using System.Net.Http.Headers;
using System.Threading;
using System.Security.Principal;
using System.Net;
using System.Text;

namespace WebApplication1.Models
{
    public class FormAuthenticationFilterAttribute : AuthorizationFilterAttribute
    {
        private const string UnauthorizedMessage = "請求未授權,拒絕訪問。";
        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0)
            {
                base.OnAuthorization(actionContext);
                return;
            }

            if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)
            {
                base.OnAuthorization(actionContext);
                return;
            }

            var cookies = actionContext.Request.Headers.GetCookies();
            if (cookies == null || cookies.Count < 1)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage, Encoding.UTF8) };
                return;
            }

            FormsAuthenticationTicket ticket = GetTicket(cookies);
            if (ticket == null)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage, Encoding.UTF8) };
                return;
            }

            //這裡可以對FormsAuthenticationTicket對象進行進一步驗證

            var principal = new GenericPrincipal(new FormsIdentity(ticket), null);
            HttpContext.Current.User = principal;
            Thread.CurrentPrincipal = principal;

            base.OnAuthorization(actionContext);
        }

        private FormsAuthenticationTicket GetTicket(Collection<CookieHeaderValue> cookies)
        {
            FormsAuthenticationTicket ticket = null;
            foreach (var item in cookies)
            {
                var cookie = item.Cookies.SingleOrDefault(c => c.Name == FormsAuthentication.FormsCookieName);
                if (cookie != null)
                {
                    ticket = FormsAuthentication.Decrypt(cookie.Value);
                    break;
                }
            }
            return ticket;
        }
    }
}

  

2.在需要認證授權後才能訪問的Controller中類或ACTION方法上添加上述授權過濾器FormAuthenticationFilterAttribute,也可在global文件中將該類添加到全局過濾器中,同時定義一個登錄ACTION,用於登錄入口,示例代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Security;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
    [FormAuthenticationFilter]
    public class TestController : ApiController
    {

        [AllowAnonymous]
        [AcceptVerbs("Get")]
        [Route("Api/Test/Login")]
        public HttpResponseMessage Login(string uname, string pwd)
        {
            if ("admin".Equals(uname, StringComparison.OrdinalIgnoreCase) && "api.admin".Equals(pwd))
            {
                //創建票據
                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, uname, DateTime.Now, DateTime.Now.AddMinutes(30), false, string.Empty);
                //加密票據
                string authTicket = FormsAuthentication.Encrypt(ticket);
                //存儲為cookie
                HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, authTicket);
                cookie.Path = FormsAuthentication.FormsCookiePath;
                HttpContext.Current.Response.AppendCookie(cookie);

                //或者
                //FormsAuthentication.SetAuthCookie(uname, false, "/");

                return Request.CreateResponse(HttpStatusCode.OK, "登錄成功!");
            }
            else
            {
                HttpContext.Current.Response.AppendCookie(new HttpCookie(FormsAuthentication.FormsCookieName) { Expires = DateTime.Now.AddDays(-10) });//測試用:當登錄失敗時,清除可能存在的身份驗證Cookie
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, "登錄失敗,無效的用戶名或密碼!");
            }

        }

        // GET api/test
        public IEnumerable<string> GetValues()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/test/5
        public string GetValue(int id)
        {
            return "value";
        }
    }
}

測試用法一:可直接在瀏覽器中訪問需要授權的方法(即:Login除外),如:http://localhost:11099/api/test/,響應結果如下:

請求頭信息如下:

若成功調用Login方法後(http://localhost:11099/api/test/login?uname=admin&pwd=api.admin),再調用上述方法,則可以獲得正常的結果,如下圖示:

看一下請求時附帶的Cookie,如下圖示:

測試用法二:採用HttpClient來調用Api的相關方法,示例代碼如下:

        public async static void TestLoginApi()
        {
            HttpClientHandler handler = new HttpClientHandler();
            handler.UseCookies = true;//因為採用Form驗證,所以需要使用Cookie來記錄身份登錄信息
            HttpClient client = new HttpClient(handler);

            Console.WriteLine("Login>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            var response = await client.GetAsync("http://localhost:11099/api/test/login/?uname=admin&pwd=api.admin");
            var r = await response.Content.ReadAsAsync<dynamic>();
            Console.WriteLine("StatusCode:{0}", response.StatusCode);
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine("Msg:{1}", response.StatusCode, r.Message);
                return;
            }
            Console.WriteLine("Msg:{1}", response.StatusCode, r);

            var getCookies = handler.CookieContainer.GetCookies(new Uri("http://localhost:11099/"));
            Console.WriteLine("獲取到的cookie數量:" + getCookies.Count);
            Console.WriteLine("獲取到的cookie:");
            for (int i = 0; i < getCookies.Count; i++)
            {
                Console.WriteLine(getCookies[i].Name + ":" + getCookies[i].Value);
            }


            Console.WriteLine("GetValues>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            response = await client.GetAsync("http://localhost:11099/api/test/");
            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();
            foreach (string item in r2)
            {
                Console.WriteLine("GetValues - Item Value:{0}", item);
            }

            Console.WriteLine("GetValue>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            response = await client.GetAsync("http://localhost:11099/api/test/8");
            var r3 = await response.Content.ReadAsAsync<string>();
            Console.WriteLine("GetValue - Item Value:{0}", r3);
        }

結果如下圖示:

 如果Web Api作為ASP.NET 或MVC的一部份使用,那麼完全可以採用基於預設的FORM身份驗證授權特性(Authorize),或採用web.config中配置,這個很簡單,就不作說明瞭,大家可以網上參考關於ASP.NET 或ASP.NET MVC的FORM身份驗證。

第二種:集成WINDOWS驗證

首先在WEB.CONFIG文件中,增加如下配置,以開啟WINDOWS身份驗證,配置如下:

    <authentication mode="Windows">
    </authentication>

然後在需要認證授權後才能訪問的Controller中類或ACTION方法上添加Authorize特性,Controller與上文相同不再貼出,當然也可以在WEB.CONFIG中配置:

    <authorization>
      <deny users="?"/>
    </authorization>

最後將WEB API寄宿到(或者說發佈到)IIS,且需要在IIS中啟用WINDOWS身份驗證,如下圖示:

這樣就完成了該身份驗證模式(理論上WEB服務、WCF若都以IIS為宿主,都可以採用集成WINDOWS身份驗證模式),測試方法很簡單,第一種直接在瀏覽器中訪問,第二種採用HttpClient來調用WEB API,示例代碼如下:

        public async static void TestLoginApi2()
        {
            HttpClientHandler handler = new HttpClientHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.Credentials = new NetworkCredential("admin", "www.zuowenjun.cn");
            HttpClient client = new HttpClient(handler);

            var response = await client.GetAsync("http://localhost:8010/api/test/");
            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();
            foreach (string item in r2)
            {
                Console.WriteLine("GetValues - Item Value:{0}", item);
            }

            response = await client.GetAsync("http://localhost:8010/api/test/8");
            var r3 = await response.Content.ReadAsAsync<string>();
            Console.WriteLine("GetValue - Item Value:{0}", r3);
        }

第三種:Basic基礎認證

1.定義一個繼承自AuthorizationFilterAttribute的HttpBasicAuthenticationFilter類,用於實現Basic基礎認證,實現代碼如下:

using System;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Net.Http;
using System.Web.Http;
using System.Security.Principal;
using System.Threading;
using System.Net.Http.Headers;

namespace WebApplication1.Models
{
    public class HttpBasicAuthenticationFilter : AuthorizationFilterAttribute
    {
        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0)
            {
                base.OnAuthorization(actionContext);
                return;
            }

            if (Thread.CurrentPrincipal != null && Thread.CurrentPrincipal.Identity.IsAuthenticated)
            {
                base.OnAuthorization(actionContext);
                return;
            }

            string authParameter = null;

            var authValue = actionContext.Request.Headers.Authorization;
            if (authValue != null && authValue.Scheme == "Basic")
            {
                authParameter = authValue.Parameter;  //authparameter:獲取請求中經過Base64編碼的(用戶:密碼)
            }

            if (string.IsNullOrEmpty(authParameter))
            {
                Challenge(actionContext);
                return;
            }

            authParameter = Encoding.Default.GetString(Convert.FromBase64String(authParameter));

            var authToken = authParameter.Split(':');
            if (authToken.Length < 2)
            {
                Challenge(actionContext);
                return;
            }

            if (!ValidateUser(authToken[0], authToken[1]))
            {
                Challenge(actionContext);
                return;
            }

            var principal = new GenericPrincipal(new GenericIdentity(authToken[0]), null);
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }

            base.OnAuthorization(actionContext);
        }

        private void Challenge(HttpActionContext actionContext)
        {
            var host = actionContext.Request.RequestUri.DnsSafeHost;
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "請求未授權,拒絕訪問。");
            //actionContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", host));//可以使用如下語句
            actionContext.Response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", string.Format("realm=\"{0}\"", host)));
        }

        protected virtual bool ValidateUser(string userName, string password)
        {
            if (userName.Equals("admin", StringComparison.OrdinalIgnoreCase) && password.Equals("api.admin")) //判斷用戶名及密碼,實際可從資料庫查詢驗證,可重寫
            {
                return true;
            }
            return false;
        }

    }
}

  

 2.在需要認證授權後才能訪問的Controller中類或ACTION方法上添加上述定義的類HttpBasicAuthenticationFilter,也可在global文件中將該類添加到全局過濾器中,即可

測試方法很簡單,第一種直接在瀏覽器中訪問(同上),第二種採用HttpClient來調用WEB API,示例代碼如下:

        public async static void TestLoginApi3()
        {
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = CreateBasicHeader("admin", "api.admin");

            var response = await client.GetAsync("http://localhost:11099/api/test/");
            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();
            foreach (string item in r2)
            {
                Console.WriteLine("GetValues - Item Value:{0}", item);
            }

            response = await client.GetAsync("http://localhost:11099/api/test/8");
            var r3 = await response.Content.ReadAsAsync<string>();
            Console.WriteLine("GetValue - Item Value:{0}", r3);
        }

        public static AuthenticationHeaderValue CreateBasicHeader(string username, string password)
        {
            return new AuthenticationHeaderValue("Basic",
                    Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", username, password))));
        }

實現Basic基礎認證,除了通過繼承自AuthorizationFilterAttribute來實現自定義的驗證授權過濾器外,還可以通過繼承自DelegatingHandler來實現自定義的消息處理管道類,具體的實現方式可參見園子里的這篇文章:

http://www.cnblogs.com/CreateMyself/p/4857799.html

 第四種:Digest摘要認證

 1.定義一個繼承自DelegatingHandler的HttpDigestAuthenticationHandler類,用於實現在消息管道中實現Digest摘要認證,同時定義該類所需關聯或依賴的其它類,源代碼如下:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace WebApplication1.Models
{

    public class HttpDigestAuthenticationHandler : DelegatingHandler
    {
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                HttpRequestHeaders headers = request.Headers;
                if (headers.Authorization != null)
                {
                    Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method);

                    if (Nonce.IsValid(header.Nonce, header.NounceCounter))
                    {
                        string password = "www.zuowenjun.cn";//預設值

                        //根據用戶名獲取正確的密碼,實際情況應該從資料庫查詢
                        if (header.UserName.Equals("admin", StringComparison.OrdinalIgnoreCase))
                        {
                            password = "api.admin";//這裡模擬獲取到的正確的密碼
                        }

                        #region 計算正確的可授權的Hash值

                        string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash();

                        string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();

                        string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
                                            ha1, header.Nonce, header.NounceCounter, header.Cnonce, "auth", ha2).ToMD5Hash();
                        #endregion

                        if (String.CompareOrdinal(header.Response, computedResponse) == 0) //比較請求的Hash值與正確的可授權的Hash值是否相同,相則則表示驗證通過,否則失敗
                        {
                            // digest computed matches the value sent by client in the response field.
                            // Looks like an authentic client! Create a principal.
                            //    var claims = new List<Claim>
                            //{
                            //                new Claim(ClaimTypes.Name, header.UserName),
                            //                new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
                            //};

                            //    ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });

                            //    Thread.CurrentPrincipal = principal;

                            //    if (HttpContext.Current != null)
                            //        HttpContext.Current.User = principal;

                            var principal = new GenericPrincipal(new GenericIdentity(header.UserName), null);
                            Thread.CurrentPrincipal = principal;
                            if (HttpContext.Current != null)
                            {
                                HttpContext.Current.User = principal;
                            }
                        }
                    }
                }

                HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", Header.GetUnauthorizedResponseHeader(request).ToString()));
                }

                return response;
            }
            catch (Exception)
            {
                var response = request.CreateResponse(HttpStatusCode.Unauthorized);
                response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", Header.GetUnauthorizedResponseHeader(request).ToString()));

                return response;
            }
        }
    }




    public class Header
    {
        public Header() { }

        public Header(string header, string method)
        {
            string keyValuePairs = header.Replace("\"", String.Empty);

            foreach (string keyValuePair in keyValuePairs.Split(','))
            {
                int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);
                string key = keyValuePair.Substring(0, index).Trim();
                string value = keyValuePair.Substring(index + 1).Trim();

                switch (key)
                {
                    case "username": this.UserName = value; break;
                    case "realm": this.Realm = value; break;
                    case "nonce": this.Nonce = value; break;
                    case "uri": this.Uri = value; break;
                    case "nc": this.NounceCounter = value; break;
                    case "cnonce": this.Cnonce = value; break;
                    case "response": this.Response = value; break;
                    case "method": this.Method = value; break;
                }
            }

            if (String.IsNullOrEmpty(this.Method))
                this.Method = method;
        }

        public string Cnonce { get; private set; }
        public string Nonce { get; private set; }
        public string Realm { get; private set; }
        public string UserName { get; private set; }
        public string Uri { get; private set; }
        public string Response { get; private set; }
        public string Method { get; private set; }
        public string NounceCounter { get; private set; }

        // This property is used by the handler to generate a
        // nonce and get it ready to be packaged in the
        // WWW-Authenticate header, as part of 401 response
        public static Header GetUnauthorizedResponseHeader(HttpRequestMessage request)
        {
            var host = request.RequestUri.DnsSafeHost;
            return new Header()
            {
                Realm = host,
                Nonce = WebApplication1.Models.Nonce.Generate()
            };
        }

        public override string ToString()
        {
            StringBuilder header = new StringBuilder();
            header.AppendFormat("realm=\"{0}\"", Realm);
            header.AppendFormat(",nonce=\"{0}\"", Nonce);
            header.AppendFormat(",qop=\"{0}\"", "auth");
            return header.ToString();
        }
    }



    public class Nonce
    {
        private static ConcurrentDictionary<string, Tuple<int, DateTime>>
        nonces = new ConcurrentDictionary<string, Tuple<int, DateTime>>();

        public static string Generate()
        {
            byte[] bytes = new byte[16];

            using (var rngProvider = new RNGCryptoServiceProvider())
            {
                rngProvider.GetBytes(bytes);
            }

            string nonce = bytes.ToMD5Hash();

            nonces.TryAdd(nonce, new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));

            return nonce;
        }

        public static bool IsValid(string nonce, string nonceCount)
        {
            Tuple<int, DateTime> cachedNonce = null;
            //nonces.TryGetValue(nonce, out cachedNonce);
            nonces.TryRemove(nonce, out cachedNonce);//每個nonce只允許使用一次

            if (cachedNonce != null) // nonce is found
            {
                // nonce count is greater than the one in record
                if (Int32.Parse(nonceCount) > cachedNonce.Item1)
                {
                    // nonce has not expired yet
                    if (cachedNonce.Item2 > DateTime.Now)
                    {
                        // update the dictionary to reflect the nonce count just received in this request
                        //nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), cachedNonce.Item2);

                        // Every thing looks ok - server nonce is fresh and nonce count seems to be 
                        // incremented. Does not look like replay.
                        return true;
                    }

                }
            }

            return false;
        }
    }
}

 

using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace WebApplication1.Models
{
    public static class HashHelper
    {
        public static string ToMD5Hash(this byte[] bytes)
        {
            StringBuilder hash = new StringBuilder();
            MD5 md5 = MD5.Create();

            md5.ComputeHash(bytes)
                  .ToList()
                  .ForEach(b => hash.AppendFormat("{0:x2}", b));

            return hash.ToString();
        }

        public static string ToMD5Hash(this string inputString)
        {
            return Encoding.UTF8.GetBytes(inputString).ToMD5Hash();
        }
    }

}

2.將上述自定義的HttpDigestAuthenticationHandler類添加到全局消息處理管道中,代碼如下:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.MessageHandlers.Add(new HttpDigestAuthenticationHandler());//添加到消息處理管道中
        }
    }

3.在需要認證授權後才能訪問的Controller中類或ACTION方法上添加Authorize特性即可。

測試方法很簡單,第一種直接在瀏覽器中訪問(同上),第二種採用HttpClient來調用WEB API,示例代碼如下:

        public async static void TestLoginApi4()
        {
            HttpClientHandler handler = new HttpClientHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.Credentials = new NetworkCredential("admin", "api.admin");

            HttpClient client = new HttpClient(handler);

            var response = await client.GetAsync("http://localhost:11099/api/test/");
            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();
            foreach (string item in r2)
            {
                Console.WriteLine("GetValues - Item Value:{0}", item);
            }

            response = await client.GetAsync("http://localhost:11099/api/test/8");
            var r3 = await response.Content.ReadAsAsync<string>();
            Console.WriteLine("GetValue - Item Value:{0}", r3);

        }

該實現方法,參考了該篇文章:http://zrj-software.iteye.com/blog/2163487

實現Digest摘要認證,除了上述通過繼承自DelegatingHandler來實現自定義的消息處理管道類外,也可以通過繼承自AuthorizationFilterAttribute來實現自定義的驗證授權過濾器,Basic基礎認證與Digest摘要認證流程基本相同,區別在於:Basic是將密碼直接base64編碼(明文),而Digest是用MD5進行加密後傳輸,所以兩者實現認證方式上,也基本相同。

最後說明一下,WEB SERVICE、WCF、WEB API實現身份驗證的方法有很多,每種方法都有他所適用的場景,我這個系列文章僅是列舉一些常見的實見身份驗證的方法,一是給自己複習並備忘,二是給大家以參考,文中可能有不足之處,若發現問題,可以在下麵評論指出,謝謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 反射可以動態獲取數據的類型,Type 類可以獲取其中的 欄位、方法、屬性等。尤其是將欄位與屬性做區分可以讓我們可以獲取,自己想獲得的。廢話不多說上代碼。先將數據導入的類,寫下: 1 using System; 2 using UnityEngine; 3 4 namespace ARPGSimpl....
  • 10-3. 返回結果是一個標量值問題想取得存儲過程返回的一個標量值.解決方案假設我們有如Figure 10-2所示的ATM機和ATM機取款記錄的模型Figure 10-2. 一個ATM機和ATM機取款記錄的模型我們想要用一個存儲過程在指定機器和日期內返回所有的取款總額Listing 10-9 是這個...
  • 【工具】-【選項】-【環境】-【區域設置】-【語言】-【獲取其他語言】 安裝後重啟即可。
  • Autofac是傳說中速度最快的一套.NET高效的依賴註入框架。Autofac的介紹與使用請去參考Autofac全面解析系列(版本:3.5)。這裡介紹的已經挺詳細的啦。 下麵我就先來說說MVC4中使用Autofac吧,至於工廠模式與依賴註入的區別的話,這個我簡單的解釋一下,也只是我的個人觀點。使.....
  • 基於Nancy.Hosting.Aspnet的Nancy小Demo
  • 10-2. 返回輸出參數問題想獲取存儲過程里的一個或多個輸出參數的值解決方案假設我們有一個像Figure 10-1所示的,計程車輛與租金收入的模型Figure 10-1.計程車輛與租金收入的模型我們想知道在指定日期里,收入了幾筆租金和金額, 以及車輛的租憑情況. 存儲過程Listing 10-7 就...
  • 應用開發中,開發者時常需要獲取一些系統、用戶信息用於數據統計遙測、問題反饋、用戶識別等功能。本文旨在介紹在 Windows UWP 應用中獲取一些常用系統、用戶信息的方法。示例項目代碼可參見 Github:https://github.com/validvoid/UWP-SystemInfoColl...
  • 還沒正式登場就死了?不能怪我標題黨,是大神Scott在他博客上這麼說的,我只是翻譯了一下。在1月20號最新的ASP.NET Community Standup視頻中,微軟aspnet開發組的大帥哥 大面·愛德華茲(Damian Edwards)聊了聊在未來版本RC2將要進行的重命名工作。然而由於我聽...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...