获取 ASP.NET MVC5 WebAPI 令牌有时会失败

Get ASP.NET MVC5 WebAPI token fails sometimes

获取 ASP.NET MVC5 WebAPI 令牌有时会失败

代码

string GetAPITokenSync(string username, string password, string apiBaseUri)
        {
            var token = string.Empty;

            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(apiBaseUri);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.Timeout = TimeSpan.FromSeconds(60);  

                //setup login data
                var formContent = new FormUrlEncodedContent(new[]
                {
                 new KeyValuePair<string, string>("grant_type", "password"),
                 new KeyValuePair<string, string>("username", username),
                 new KeyValuePair<string, string>("password", password),
                 });

                //send request               
                Task t = Task.Run(() =>
                {
                    HttpResponseMessage responseMessage = client.PostAsync("/Token", formContent).Result;
                    var responseJson = responseMessage.Content.ReadAsStringAsync().Result;
                    var jObject = JObject.Parse(responseJson);
                    token = jObject.GetValue("access_token").ToString();
                });

                t.Wait();
                t.Dispose();
                t = null;
                GC.Collect();

                return token;
            }
        }

错误

One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled.
--- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceled Exceptions) at System.Threading.Tasks.Task1.GetResultCore(Boolean waitCompletionNotification) at System.Threading.Tasks.Task1.get_Result()

WebAPI登录方式默认没有变化。

[HttpPost]
[AllowAnonymous]
[Route("Login")]
public HttpResponseMessage Login(string username, string password)
    {
        try
        {
            var identityUser = UserManager.Find(username, password);

            if (identityUser != null)
            {
                var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
                identity.AddClaim(new Claim(ClaimTypes.Name, username));

                AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
                var currentUtc = new SystemClock().UtcNow;
                ticket.Properties.IssuedUtc = currentUtc;
                ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(1440));

                var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);

                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new ObjectContent<object>(new
                    {
                        UserName = username,
                        ExternalAccessToken = token
                    }, Configuration.Formatters.JsonFormatter)
                };

                return response;


            }
        }
        catch (Exception)
        {
        }

        return new HttpResponseMessage(HttpStatusCode.BadRequest);
    }
}

Startup class默认没有变化

 public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }


        public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Configure the application for OAuth based flow
            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true
            };

            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthBearerTokens(OAuthOptions);
        }
    }

有线索吗?

很难确定,但您阻止 HttpClient 调用的方式并没有帮助。 HttpClient 是一个异步库;你可能遇到了僵局。我建议摆脱所有 .Results 和 .Wait()s 并使用 async/await 异步编写所有内容。而你的 Task.Run 一无所获,所以应该去。

我知道这是从控制台应用程序移植过来的 Topshelf 应用程序。我对 Topshelf 不是很熟悉,但我认为,就像控制台应用程序一样,您需要在 某个地方 进行阻止,否则您的应用程序将直接退出。执行此操作的位置位于最顶部 - 应用程序的入口点。

这演示了该模式,并重写了您的 GetApiToken 方法:

// app entry point - the only place you should block
void Main()
{
    MainAsync().Wait();
}

// the "real" starting point of your app logic. do everything async from here on
async Task MainAsync()
{
    ...
    var token = await GetApiTokenAsync(username, password, apiBaseUri);
    ...
}

async Task<string> GetApiTokenAsync(string username, string password, string apiBaseUri)
{
    var token = string.Empty;

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(apiBaseUri);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.Timeout = TimeSpan.FromSeconds(60);  

        //setup login data
        var formContent = new FormUrlEncodedContent(new[]
        {
         new KeyValuePair<string, string>("grant_type", "password"),
         new KeyValuePair<string, string>("username", username),
         new KeyValuePair<string, string>("password", password),
         });

        //send request               
        HttpResponseMessage responseMessage = await client.PostAsync("/Token", formContent);
        var responseJson = await responseMessage.Content.ReadAsStringAsync();
        var jObject = JObject.Parse(responseJson);
        token = jObject.GetValue("access_token").ToString();

        return token;
    }
}