SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授權服務端

来源:http://www.cnblogs.com/frankzhou/archive/2016/12/21/simplsso.html
-Advertisement-
Play Games

目錄 前言 OAuth2.0簡介 授權模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模擬OpenID認證 通過authorization code授權模式申請令牌 通過implicit授權模式申請令牌 通過password模式申請令牌 通過c ...


目錄

前言

  之前有分享這個項目源碼及簡介,不過因為文字講解太少,被和諧了。我重新總結下:

  

 

   源碼:https://github.com/zhoufeihong/SimpleSSO

   OAuth 2.0協議:http://www.rfcreader.com/#rfc6749

-------------------------------------------分割線

  

  記得那個酷熱的夏天,面試官翹著二郎腿問:“知道單點登錄不?”,我毫不遲疑答到:“不就是限制用戶只能在一個地方登錄嗎!”。面試完回家,查資料,也是似懂非懂,COOKIE、跨域、令牌、主站都是些啥玩意!其實我就是個VS都沒摸過幾次的畢業生,單點登錄這種玩意是不是太高級了。

  這次就是寫個項目練練手(這兩年手生了太多),想到當初在網上找了半天,關於單點登錄、OAuth 2.0也沒找到個完整的實例(概念、理論倒是比較多),就寫了這個項目。分享出來,希望可以給那些對單點登錄、OAuth 2.0實現比較困惑的C#開發人員一些幫助。同時項目裡面有對於Autofac、AutoMapper、EF等等技術實踐方式(當然複製了很多代碼,我會儘量把源項目的License放上),希望在這些技術上也可以給你一些參考,項目可以直接運行(用戶名:admin密碼:123)。

  昨天的文章因為文字講解太少了,被和諧了。不得不佩服博客園管理人員的專業水平,是你們如此細緻的工作造就了博客園這麼多優秀的文章,也造就了博客園的今天(拍個馬屁)。其實我就想貼幾張圖,你們看到效果後,自己去看代碼、敲代碼,這樣子會比較好些(其實我就是表達能力不好,怕詞不達意)。

  廢話不多說了,這篇文章我簡單介紹下:

  SimpleSSO授權第三方應用系統獲取用戶信息(OpenID認證)(類似於我們在新浪上點擊QQ快捷登錄,採用的授權碼模式(authorization code))

  SimpleSSO授權基於瀏覽器應用系統獲取用戶信息(類似於我們通過微信瀏覽器點開第三方應用,採用的簡化模式(implicit))

  第三方系統使用用戶名密碼申請獲取用戶令牌,然後用令牌獲取用戶信息(採用的密碼模式(password))

  第三方系統申請自己的訪問令牌(類似於微信公眾號用申請令牌訪問自己公眾號信息(採用的客戶端模式client credentials))

  第三方系統刷新用戶(本身)令牌(refreshtoken)

 

OAuth2.0簡介

   OAuth2.0(開放授權)是一個開放標準,允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源,而無需將用戶名和密碼提供給第三方應用。具體你可以去百度(oauth2.0 阮一峰),文章關於oauth2.0理論的講解非常到位,網上的理論也非常多,之前沒有基礎的可以先去腦補下。 

      具體場景:QQ用戶在XX網站分享文章到QQ空間

      剖析:

  

 

授權模式 (SimpleSSO授權示例)

前言:

    關於授權模式如果不太清楚的建議:去百度(oauth2.0 阮一峰),文章關於對於授權模式的講解非常到位。Owin.OAuth的基礎,可以看看dudu寫的在ASP.NET中基於Owin OAuth使用Client Credentials Grant授權發放Token,一篇一篇看下去。

    本節主要演示SimpleSSOTest站點通過各種授權模式到SimpleSSO站點申請令牌。如圖:

      

    其中SimpleSSO站點為:http://localhost:8550,SimpleTest站點為:http://localhost:6111,後續會用到

    SimpleSSO關於OAuthAuthorizationServerOptions的配置:

  builder.Register(c => new OAuthAuthorizationServerOptions
            {
                //授權終結點 /Token
                TokenEndpointPath = new PathString(EndPointConfig.TokenEndpointPath),
                Provider = new SimpleSSOOAuthProvider(),
                // Authorize授權終結點 /GrantCode/Authorize
                AuthorizeEndpointPath = new PathString(EndPointConfig.AuthorizeEndpointPath),
                //RefreshToken令牌創建、接收
                RefreshTokenProvider = new SimpleAuthenticationTokenProvider()
                {
                    //令牌類型
                    TokenType = "RefreshToken",
                    //刷新AccessToken時RefreshToken不需要重新生成
                    TokenKeepingPredicate = data => data.GrantType == GrantTypes.RefreshToken,
                    //過期時間
                    ExpireTimeSpan = TimeSpan.FromDays(60)
                },
                // AccessToken令牌創建、接收
                AccessTokenProvider = new SimpleAuthenticationTokenProvider()
                {
                    //令牌類型
                    TokenType = "AccessToken",
                    //過期時間
                    ExpireTimeSpan = TimeSpan.FromHours(2)
                },
                // AuthorizationCode令牌創建、接收
                AuthorizationCodeProvider = new SimpleAuthenticationTokenProvider()
                {
                    //令牌類型
                    TokenType = "AuthorizationCode",
                    //過期時間
                    ExpireTimeSpan = TimeSpan.FromMinutes(15),
                    //接收令牌,同時移除令牌
                    RemoveWhenReceive = true
                },
                //在生產模式下設 AllowInsecureHttp = false
#if DEBUG
                AllowInsecureHttp = true
#endif
            }).As<OAuthAuthorizationServerOptions>().SingleInstance();
View Code

    其中兩個關於OAuth授權的實現類:

    令牌生成接收:SimpleAuthenticationTokenProvider

    授權匯流排:SimpleSSOOAuthProvider

 

授權示例:

1、使用Microsoft.Owin.Security.SimpleSSO模擬OpenID認證(authorization code模式)

1.1、Demo展示:

 

今天新加了Microsoft.Owin.Security.SimpleSSO組件(感興趣的可以看下Katana項目),主要方便第三方集成SimpleSSO登錄。

SimpleTest集成登錄需要完成如下代碼配置:

    public partial class Startup
    {
        // 有關配置身份驗證的詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            // 並使用 Cookie 來臨時存儲有關使用第三方登錄提供程式登錄的用戶的信息
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
            //simplesso登錄集成配置
        var simpleSSOOption = new SimpleSSOAccountAuthenticationOptions
            {
                //客戶端ID
                ClientId = "3",
                //客戶端秘鑰
                ClientSecret = "123",
                //登錄回調地址
                CallbackPath = new PathString("/login/signin-simplesso"),
                //SimpleSSO Token授權地址
                TokenEndpoint = "http://localhost:8550/token",
                //SimpleSSO authorization code授權地址
                AuthorizationEndpoint = "http://localhost:8550/GrantCode/Authorize",
                //使用令牌到SimpleSSO獲取用戶信息地址
                UserInformationEndpoint = "http://localhost:8550/TicketUser/TicketMessage"
            };
            simpleSSOOption.Scope.Add("user-base");
            app.UseSimpleSSOAccountAuthentication(simpleSSOOption);
            app.UseFacebookAuthentication(
                appId: "",
                appSecret: "");
        }
    }
View Code

 1.2、Demo請求流程(流程圖工具過期了,只能用文字了,省略了很多細節):

1)用戶點擊“使用Microsoft.Owin.Security.SimpleSSO模擬OpenID認證”下進入按鈕,將跳轉到http://localhost:6111/login/authsimplesso

2)authsimplesso接收用戶請求

  1>如果用戶已經使用ExternalCookie在登錄,註銷ExternalCookie信息,獲取返回用戶信息。

  2>當用戶未登錄,則將http返回狀態改為401,並且創建authenticationType為SimpleSSOAuthentication身份驗證,SimpleSSOAccountAuthenticationHandler將用戶重定向到http://localhost:8550/GrantCode/Authorize?client_id={0}&scope={1}&response_type=code&redirect_uri={2}&state={3}。

SimpleSSOAccountAuthenticationHandler重定向代碼:

protected override Task ApplyResponseChallengeAsync()
        {
            if (Response.StatusCode != 401)
            {
                return Task.FromResult<object>(null);
            }
            AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
            if (challenge != null)
            {
                string baseUri = Request.Scheme + Uri.SchemeDelimiter + Request.Host + Request.PathBase;
                string currentUri = baseUri + Request.Path + Request.QueryString;
                string redirectUri = baseUri + Options.CallbackPath;
                AuthenticationProperties extra = challenge.Properties;
                if (string.IsNullOrEmpty(extra.RedirectUri))
                {
                    extra.RedirectUri = currentUri;
                }
                // OAuth2 10.12 CSRF
                GenerateCorrelationId(extra);
                // OAuth2 3.3 space separated                
        string scope = string.Join(" ", Options.Scope);
                // LiveID requires a scope string, so if the user didn't set one we go for the least possible.
        if (string.IsNullOrWhiteSpace(scope))
                {
                    scope = "user-base";
                }
                string state = Options.StateDataFormat.Protect(extra);
                string authorizationEndpoint =
                    Options.AuthorizationEndpoint +
        "?client_id=" + Uri.EscapeDataString(Options.ClientId) +
        "&scope=" + Uri.EscapeDataString(scope) +
        "&response_type=code" +
        "&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
        "&state=" + Uri.EscapeDataString(state);
                var redirectContext = new SimpleSSOAccountApplyRedirectContext(
                    Context, Options,
                    extra, authorizationEndpoint);
                Options.Provider.ApplyRedirect(redirectContext);
            }
            return Task.FromResult<object>(null);
        }
View Code

3)GrantCode/Authorize接收用戶請求

  1>如果為可信應用則不需要用戶同意,直接生成code讓用戶跳轉到http://localhost:6111/login/signin-simplesso?code={0}&state={1}

  2>如果不是可信應用則跳轉到http://localhost:8550/OAuth/Grant用戶授權頁面,用戶點擊授權時跳轉到

4)http://localhost:6111/login/signin-simplesso?code={0}&state={1}請求處理,由SimpleSSOAccountAuthenticationHandler類處理

    SimpleSSOAccountAuthenticationHandler代碼:

 internal class SimpleSSOAccountAuthenticationHandler : AuthenticationHandler<SimpleSSOAccountAuthenticationOptions>
    {
        private readonly ILogger _logger;
        private readonly HttpClient _httpClient;
        public SimpleSSOAccountAuthenticationHandler(HttpClient httpClient, ILogger logger)
        {
            _httpClient = httpClient;
            _logger = logger;
        }
        public override async Task<bool> InvokeAsync()
        {
            if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
            {
                return await InvokeReturnPathAsync();
            }
            return false;
        }
        protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
        {
            AuthenticationProperties properties = null;
            try
            {
                string code = null;
                string state = null;
                IReadableStringCollection query = Request.Query;
                IList<string> values = query.GetValues("code");
                if (values != null && values.Count == 1)
                {
                    code = values[0];
                }
                values = query.GetValues("state");
                if (values != null && values.Count == 1)
                {
                    state = values[0];
                }
                properties = Options.StateDataFormat.Unprotect(state);
                if (properties == null)
                {
                    return null;
                }
                // OAuth2 10.12 CSRF
        if (!ValidateCorrelationId(properties, _logger))
                {
                    return new AuthenticationTicket(null, properties);
                }
                var tokenRequestParameters = new List<KeyValuePair<string, string>>()
                {
                    new KeyValuePair<string, string>("client_id", Options.ClientId),
                    new KeyValuePair<string, string>("redirect_uri", GenerateRedirectUri()),
                    new KeyValuePair<string, string>("client_secret", Options.ClientSecret),
                    new KeyValuePair<string, string>("code", code),
                    new KeyValuePair<string, string>("grant_type", "authorization_code"),
                };
                var requestContent = new FormUrlEncodedContent(tokenRequestParameters);
                HttpResponseMessage response = await _httpClient.PostAsync(Options.TokenEndpoint, requestContent, Request.CallCancelled);
                response.EnsureSuccessStatusCode();
                string oauthTokenResponse = await response.Content.ReadAsStringAsync();
                JObject oauth2Token = JObject.Parse(oauthTokenResponse);
                var accessToken = oauth2Token["access_token"].Value<string>();
                // Refresh token is only available when wl.offline_access is request.
                // Otherwise, it is null.
        var refreshToken = oauth2Token.Value<string>("refresh_token");
                var expire = oauth2Token.Value<string>("expires_in");
                if (string.IsNullOrWhiteSpace(accessToken))
                {
                    _logger.WriteWarning("Access token was not found");
                    return new AuthenticationTicket(null, properties);
                }
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                HttpResponseMessage graphResponse = await _httpClient.GetAsync(
                    Options.UserInformationEndpoint);
                graphResponse.EnsureSuccessStatusCode();
                string accountString = await graphResponse.Content.ReadAsStringAsync();
                JObject accountInformation = JObject.Parse(accountString);
                var context = new SimpleSSOAccountAuthenticatedContext(Context, accountInformation, accessToken,
                    refreshToken, expire);
                context.Identity = new ClaimsIdentity(
                    new[]
                    {
                        new Claim(ClaimTypes.NameIdentifier, context.Id, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType),
                        new Claim(ClaimTypes.Name, context.Name, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType),
                        new Claim("urn:simplesso:id", context.Id, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType),
                        new Claim("urn:simplesso:name", context.Name, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)
                    },
                    Options.AuthenticationType,
                    ClaimsIdentity.DefaultNameClaimType,
                    ClaimsIdentity.DefaultRoleClaimType);
                if (!string.IsNullOrWhiteSpace(context.Email))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType));
                }
                await Options.Provider.Authenticated(context);
                context.Properties = properties;
                return new AuthenticationTicket(context.Identity, context.Properties);
            }
            catch (Exception ex)
            {
                _logger.WriteError("Authentication failed", ex);
                return new AuthenticationTicket(null, properties);
            }
        }
        protected override Task ApplyResponseChallengeAsync()
        {
            if (Response.StatusCode != 401)
            {
                return Task.FromResult<object>(null);
            }
            AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
            if (challenge != null)
            {
                string baseUri = Request.Scheme + Uri.SchemeDelimiter + Request.Host + Request.PathBase;
                string currentUri = baseUri + Request.Path + Request.QueryString;
                string redirectUri = baseUri + Options.CallbackPath;
                AuthenticationProperties extra = challenge.Properties;
                if (string.IsNullOrEmpty(extra.RedirectUri))
                {
                    extra.RedirectUri = currentUri;
                }
                // OAuth2 10.12 CSRF
                GenerateCorrelationId(extra);
                // OAuth2 3.3 space separated                
        string scope = string.Join(" ", Options.Scope);
                // LiveID requires a scope string, so if the user didn't set one we go for the least possible.
        if (string.IsNullOrWhiteSpace(scope))
                {
                    scope = "user-base";
                }
                string state = Options.StateDataFormat.Protect(extra);
                string authorizationEndpoint =
                    Options.AuthorizationEndpoint +
        "?client_id=" + Uri.EscapeDataString(Options.ClientId) +
        "&scope=" + Uri.EscapeDataString(scope) +
        "&response_type=code" +
        "&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
        "&state=" + Uri.EscapeDataString(state);
                var redirectContext = new SimpleSSOAccountApplyRedirectContext(
                    Context, Options,
                    extra, authorizationEndpoint);
                Options.Provider.ApplyRedirect(redirectContext);
            }
            return Task.FromResult<object>(null);
        }
        public async Task<bool> InvokeReturnPathAsync()
        {
            AuthenticationTicket model = await AuthenticateAsync();
            if (model == null)
            {
                _logger.WriteWarning("Invalid return state, unable to redirect.");
                Response.StatusCode = 500;
                return true;
            }
            var context = new SimpleSSOReturnEndpointContext(Context, model);
            context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType;
            context.RedirectUri = model.Properties.RedirectUri;
            model.Properties.RedirectUri = null;
            await Options.Provider.ReturnEndpoint(context);
            if (context.SignInAsAuthenticationType != null && context.Identity != null)
            {
                ClaimsIdentity signInIdentity = context.Identity;
                if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
                {
                    signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType);
                }
                Context.Authentication.SignIn(context.Properties, signInIdentity);
            }
            if (!context.IsRequestCompleted && context.RedirectUri != null)
            {
                if (context.Identity == null)
                {
                    // add a redirect hint that sign-in failed in some way
                    context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied");
                }
                Response.Redirect(context.RedirectUri);
                context.RequestCompleted();
            }
            return context.IsRequestCompleted;
        }
        private string GenerateRedirectUri()
        {
            string requestPrefix = Request.Scheme + "://" + Request.Host;
            string redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath; // + "?state=" + Uri.EscapeDataString(Options.StateDataFormat.Protect(state));            
        return redirectUri;
        }
    }
View Code

  1>使用code獲取令牌

  2>獲取用戶信息

  3>SignIn(ExternalCookie)

  4>重新跳轉到http://localhost:6111/login/authsimplesso,回到1.2-2)

 

2、通過authorization code授權模式申請令牌

2.1、Demo展示(這個demo請求實際上是可以跨域的):

2.2、Demo請求流程

1)用戶點擊“通過authorization code授權模式申請令牌”下進入按鈕,使用div載入url地址http://localhost:8550/GrantCode/Authorize?client_id=1&scope=user-base&response_type=code&redirect_uri=http://localhost:6111/api/Code/App1&state={隨機}。如果用戶沒有登錄的情況下請求這個路徑,會跳轉到登錄界面。

2)因為client_id=1應用為可信應用,所以直接生成code,請求http://localhost:6111/api/Code/App1?code=?&state={請求過來的值}

由SimpleSSOOAuthProvider方法AuthorizeEndpoint完成可信應用驗證,用戶令牌信息註冊,SimpleAuthenticationTokenProvider完成code生成

3)/api/Code/App1接收code、state

1)使用code獲取Access_Token

2)使用Access_Token獲取用戶信息

3)使用Refresh_Token刷新Access_Token

4)使用刷新後的Access_Token獲取用戶信息

/api/Code/App1代碼:

        [HttpGet]
        [Route("App1")]
        public async Task<string> App1(string code = "")
        {
            return await AppData(code, "App1", "1", "123");
        }
 private async Task<string> AppData(string code,
            string appName, string clientID, string clientSecret)
        {
            StringBuilder strMessage = new StringBuilder();
            if (!string.IsNullOrWhiteSpace(code))
            {
                string accessToken = "";
                string codeResult = await AuthorizationCode(appName, clientID, clientSecret, code);
                var obj = JObject.Parse(codeResult);
                var refreshToken = obj["refresh_token"].Value<string>();
                accessToken = obj["access_token"].Value<string>();
                strMessage.Append($"<font color='black'><b>應用{appName}使用</b></font></br>code:{code}獲取到</br>refresh_token:{refreshToken}</br>access_token:{accessToken}");
                if (!string.IsNullOrEmpty(accessToken))
                {
                    strMessage.Append($"</br><font color='black'><b>使用AccessToken獲取到信息:</b></font>{ await GetTicketMessageData(accessToken) }");
                    obj = JObject.Parse(await RefreshToken(clientID, clientSecret, refreshToken));
                    refreshToken = obj["refresh_token"].Value<string>();
                    accessToken = obj["access_token"].Value<string>();
                    strMessage.Append($"</br><font color='black'><b>應用{appName}刷新秘鑰獲取到</b></font></br>refresh_token:{refreshToken}</br>access_token:{accessToken}");
                    strMessage.Append($"</br><font color='black'><b>使用刷新後AccessToken獲取到信息:</b></font>{ await GetTicketMessageData(accessToken) }");
                }
            }
            else
            {
                strMessage.AppendLine("獲取code失敗.");
            }
            return await Task.FromResult(strMessage.ToString());
        }
View Code

 

 

3、通過implicit授權模式申請令牌

 3.1、Demo展示:

  

implicit模式是比較特別一種模式,由基於瀏覽器應用訪問用戶信息,所以生成的令牌直接為Access_Token,且Url為http://localhost:6111/TokenClient/ShowUser#access_token={0}&token_type={1}&state={2},瀏覽器端需要通過window.location.hash訪問。

3.2、Demo請求流程

1)用戶點擊""下進入,http://localhost:8550/GrantCode/Authorize?client_id=2&redirect_uri=http://localhost:6111/TokenClient/ShowUser&response_type=token&scope=user_base&state={隨機}

2)跳轉到用戶授權頁面,用戶授權後,返回http://localhost:6111/TokenClient/ShowUser#access_token={0}&token_type=bearer&state={2}

3)點擊Try Get Data,js使用access_token請求獲取用戶信息。

其中JS代碼:

   $(function () {
            $("#get_data").click(function () {
                var hashDiv = getHashStringArgs();
                var token = hashDiv["access_token"];
                var tokenType = hashDiv["token_type"];
                if (token) {
                    var url = "@ViewBag.ServerTicketMessageUrl";
                    var settings = {
                        type: "GET",
                        url: url,
                        beforeSend: function (request) {
                            request.setRequestHeader("Authorization", tokenType + " " + token);
                        },
                        success: function (data, textStatus) {
                            alert(JSON.stringify(data));
                        }
                    };
                    $.ajax(settings);
                }
            });
        });
        function getHashStringArgs() {
            var hashStrings = (window.location.hash.length > 0 ? window.location.hash.substring(1) : ""),
           hashArgs = {},
           items = hashStrings.length > 0 ? hashStrings.split("&") : [],
           item = null,
           name = null,
           value = null,
           i = 0,
           len = items.length;
            for (i = 0; i < len; i++) {
                item = items[i].split("=");
                name = decodeURIComponent(item[0]);
                value = decodeURIComponent(item[1]);
                if (name.length > 0) {
                    hashArgs[name] = value;
                }
            }
            return hashArgs;
        }
View Code

 

4、通過password模式申請令牌

實現代碼:

      [HttpGet]
        [Route("AppPassword")]
        public async Task<string> AppPassword()
        {
            var clientID = "1";
            var clientSecret = "123";
            var userName = "zfh";
            var password = "123";
            var parameters = new Dictionary<string, string>();
            parameters.Add("grant_type", "password");
            parameters.Add("username", userName);
            parameters.Add("password", password);
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "Basic",
                Convert.ToBase64String(Encoding.ASCII.GetBytes(clientID + ":" + clientSecret)));
            var response = await _httpClient.PostAsync(_serverTokenUrl, new FormUrlEncodedContent(parameters));
            var result = await response.Content.ReadAsStringAsync();
            var obj = JObject.Parse(result);
            var refreshToken = obj["refresh_token"].Value<string>();
            var accessToken = obj["access_token"].Value<string>();
            return $"<font color='b

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

-Advertisement-
Play Games
更多相關文章
  • 迴圈、邏輯語句塊 好久不寫博客了,斷更了好幾天了,從上周五到今天,從北京到上海,跨越了1213.0公裡,從一個熟悉的城市到陌生的城市,還好本人適應力比較好,還有感謝小伙伴的接風咯,一切都不是事,好了,進入正題: 本篇還是.NET 基礎部分咯,主要簡述迴圈,判斷: 迴圈: for迴圈 語法: for( ...
  • 一、借鑒說明 1.《Head First Design Patterns》(中文名《深入淺出設計模式》) 2.維基百科,策略模式,https://zh.wikipedia.org/wiki/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F 二、策略模式 基礎知識 將一類相同的... ...
  • "檢索COM類工廠中 CLSID為 {00024500-0000-0000-C000-000000000046}的組件時失敗,原因是出現以下錯誤: 80070005" 問題的解決 ...
  • 菜單收縮有很多種方法具體如何實現還是看個人想法: 第一種通過後臺控制收起與展開: 效果圖: 代碼 : <Grid> <Grid.ColumnDefinitions> <ColumnDefinition x:Name="cd" Width="154"/> <ColumnDefinition /> </ ...
  • 用html實現圖片上傳 後臺採用.net其中在這裡要借用一個js插件 在這裡我會寫一個圖片上傳的一個小Demo,有不全的地方多多包容,和提議, 我把已經寫好的demo已經上傳到百度雲 在這裡可以下載 http://pan.baidu.com/s/1bG2934 開始 html中的內容是 <body> ...
  • 年關將至,對於大部分程式員來說,馬上就可以閑下來一段時間了,然而在這個閑暇的時間里,唯有爭論哪門語言更好可以消磨時光,估計最近會有很多關於java與.net的博文出現,我表示要作為一個吃瓜群眾,靜靜的看著大佬們發表心情。 以上的廢話說的夠多了,這裡就不再廢話了,還是切入正題吧。 在項目開發中,對於系 ...
  • 本文要介紹的是ASP.NET怎樣讀寫文本文件,但更重要的是實現的過程。使用的工具是Visual Studio 2015 ,.NET版本是4.6.1 。一共建立的2個項目,HoverTreePanel和HoverTreeWeb,都是ASP.NET項目。文章末尾附源碼下載。項目結果如下圖:讀寫文件功能在 ...
  • 在多線程(線程同步)中,我們將學習多線程中操作共用資源的技術,學習到的知識點如下所示: 執行基本的原子操作 使用Mutex構造 使用SemaphoreSlim構造 使用AutoResetEvent構造 使用ManualResetEventSlim構造 使用CountDownEvent構造 使用Bar ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...