【问题标题】:System.InvalidOperationException: 'The response headers cannot be modified because the response has already started.'System.InvalidOperationException: '无法修改响应标头,因为响应已经开始。'
【发布时间】:2021-09-16 19:45:28
【问题描述】:

我正在使用 Blazor 服务器端 并希望为此设置 Cookie,我正在使用 HttpContext 的 SignInAsync 功能,但给我这个错误“无法修改响应标头,因为响应已经开始”。

当涉及到以下行时,错误会闪烁

await httpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, 
   new ClaimsPrincipal(claimsIdentity), 
    authProperties);

我已经尝试了所有这些但仍然面临错误

我的 Startup.cs 页面


    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using power_SLIC.Data;
    using System.Net.Http;
    using power_SLIC.Services;
    using Microsoft.AspNetCore.Components.Authorization;
    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection.Extensions;
    
    namespace power_SLIC
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddRazorPages();
                services.AddServerSideBlazor();
                services.AddSingleton<WeatherForecastService>();
                // services.AddProtectedBrowserStorage();
                services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
                services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
                        options => {
                            options.LoginPath = "/";
                        });
                //.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, act => {
                //    act.LoginPath = "/";
                //    act.AccessDeniedPath = "/";
                //    act.SlidingExpiration = true;
                //}
    
                // services.AddHttpClient();
                services.AddSingleton<HttpClient>();
              //  services.AddScoped<AuthenticationStateProvider, TokenAuthenticationServices>();
                //services.AddScoped<ILoginservices, TokenAuthenticationServices>();
               /// services.AddSingleton<HttpClient>();
                services.AddAuthorization(option =>
                {
                    option.AddPolicy("Employee", policy => policy.RequireClaim("IsUserEmployee", "true"));
                });
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseStaticFiles();
                app.UseRouting();
                app.UseAuthentication();
                app.UseAuthorization();
    
              
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapBlazorHub();
                    endpoints.MapFallbackToPage("/_Host");
                });
            }
        }
    }

我的 Razor 页面看起来像这样 Loginscreen.razor

@layout LoginLayout
@page "/"

@using System.Threading.Tasks
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Authentication.Cookies
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Mvc
@using Newtonsoft.Json
@using System.Text
@using Newtonsoft.Json.Linq
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@using System.Security.Claims
@using System.Web
@*@inject ProtectedSessionStorage ProtectedSessionStore*@
@inject IJSRuntime JsRuntime;
@inject NavigationManager NavigationManager
@inject HttpClient http
@inject IHttpContextAccessor httpContextAccessor



<div style="margin-left:39%;margin-right:17%"> 
<div class="card">
    <h4 class="card-header">Login</h4>
    <div class="card-body">
        <EditForm Model="@model" >
            @*<DataAnnotationsValidator />*@
            <div class="form-group">
                <label>Username</label>
                <InputText @bind-Value="model.SSam_User" class="form-control" />
                @*<ValidationMessage For="@(() => model.Username)" />*@
            </div>
            <div class="form-group">
                <label>Password</label>
                <InputText @bind-Value="model.SSam_Pwd" type="password" class="form-control" />
                @*<ValidationMessage For="@(() => model.Password)" />*@
            </div>
            <button  class="btn btn-primary" @onclick = "AddItem" >
               
                Login
            </button>
           @* <NavLink href="account/register" @onClick = "window.location.href = 'home'" class="btn btn-link">Register</NavLink>*@
        </EditForm>
    </div>
</div>
</div>


@code{
    private bool isConnected = false;
    private Model.LoginModel model = new Model.LoginModel();
    class Values
    {
       
        public string result {get; set;}

    }


         




    class Result{
        public List<Values>  result { get; set;}
    };

   public async void AddItem()
    {
        var addItem = new Model.LoginModel {SSam_User=model.SSam_User,SSam_Pwd=model.SSam_Pwd, UnitSname = "HFHO" };
        string output = JsonConvert.SerializeObject(addItem);
        var Stringcontent = new StringContent(output,Encoding.UTF8,"application/json");
        
        var op = await http.PostAsync("https://localhost:44392/api/outputs/getpowerbi_token", Stringcontent);
         var resultcontent = op.Content.ReadAsStringAsync().Result;
            
       dynamic oit = JsonConvert.DeserializeObject<dynamic>(resultcontent);

        var accesstoken = oit["result"]["Token_status"].Value;
        var op1 = oit["result"]["login_response"][0]["status"].Value;

        if(op1 == "success")
        {
            var reo = httpContextAccessor.HttpContext.Request.Headers;
            var prerender = !httpContextAccessor.HttpContext.Response.HasStarted;  

             if (!httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Header"))
             {
                 if (!httpContextAccessor.HttpContext.Response.HasStarted)
                 {
                     string result;
                     httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
                     result = JsonConvert.SerializeObject(new { error = "Request doesn't contains header" });
                     httpContextAccessor.HttpContext.Response.ContentType = "application/json";
                     await httpContextAccessor.HttpContext.Response.WriteAsync(result);
                 }
                 else
                 {
                     await httpContextAccessor.HttpContext.Response.HttpContext.Response.WriteAsync(string.Empty);
                 }
                 var reo1 = httpContextAccessor.HttpContext.Response.HttpContext.Response;
    
                 AuthenticateResult.Fail("request doesn't contains header");
             }

          
            var claims = new List<Claim>
{
    new Claim(ClaimTypes.Name, model.SSam_User),
    new Claim("Token", accesstoken),
    new Claim(ClaimTypes.Role, "User"),
};



        var claimsIdentity = new ClaimsIdentity(
    claims, CookieAuthenticationDefaults.AuthenticationScheme);
            

            var authProperties = new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = "/",
                ExpiresUtc = DateTime.UtcNow.AddSeconds(30)
            };
            var j1 = CookieAuthenticationDefaults.AuthenticationScheme;

            
       await httpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, 
   new ClaimsPrincipal(claimsIdentity), 
    authProperties);

     NavigationManager.NavigateTo("/powerbi",true);

        }

      

    }
}
    

【问题讨论】:

  • “我已经尝试了所有(事物)”,特别是什么?
  • @abdusco 我试过它是否是预渲染,也试过让响应为空。请帮忙!!
  • 当涉及到“await httpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);”这一行时出现错误。
  • 这行不通。 Cookie 设置在 HTTP 服务器上。您尝试在 SignalR 客户端中执行此操作。
  • 错误很明显 - 一旦开始发送响应,您就无法修改标头。这与预渲染完全没有关系,并且试图通过“使其为空”来修改响应实际上意味着已经发送了一些东西。

标签: asp.net-core blazor blazor-server-side asp.net-blazor


【解决方案1】:

问题是async void。这适用于 Blazor 中不使用的异步事件处理程序。此类方法不能等待,这意味着调用者不会启动并等待它们完成。由于运行时不知道AddItem 仍在运行,它开始向客户端发送响应。

没有结果的异步方法应该返回async Task,而不是async void

public async Task AddItem()
{
...
}

【讨论】:

  • 我很确定这不会解决任何问题。
  • 是的,上面的代码没有解决我的问题!!有没有其他选择...
【解决方案2】:

研究启用身份验证的模板应用程序:

登录页面是您应用之外的 Razor 页面。登录时强制您离开并重新启动 SPA 应用程序。 Blazor 应用只能从启动它的单个 HTTP 响应中读取 cookie。您无法写入这些 cookie。

因此,如果您想 DIY 身份验证系统,则必须以其他方式(会话存储)存储状态信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-01
    • 1970-01-01
    相关资源
    最近更新 更多