【问题标题】:Blazor wasm Web API response is an HTML stringBlazor wasm Web API 响应是一个 HTML 字符串
【发布时间】:2021-01-03 14:37:10
【问题描述】:

好的,我有一个调用服务器端 Web api 端点的 wasm 应用程序。 问题是我从端点的 /wwwroot 目录中获取 index.html 页面作为答案。但是当我用 Postman 处理端点时,我得到了预期的 json 答案。 好的,我将展示我如何使用我的代码执行此操作。

客户端数据流

Search.razor 页面

在这里,当在表单字段中输入搜索文本时,我会调用 Web API 端点。这按预期工作。

... Snip

// UPDATED INFO
<div class="form-group">
    <label for="objectType">Select object type</label>
    <select id="objectType" class="form-control" @bind="@_searchByNameObjectTypeUuid">
        @if (_objectTypes != null)
        {
            @foreach (var objectType in _objectTypes)
            {
                @if (objectType.TypeName == "Music")
                {
                    @* This selection value is not set. But why?
                    <option value="@objectType.Uuid.ToString("D")" selected="selected">@objectType.TypeName</option>
                }
                else
                {
                    <option value="@objectType.Uuid.ToString("D")">@objectType.TypeName</option>
                }
            }
        }
    </select>
</div>
// UPDATED INFO END

<div class="form-group">
    <label for="objectName">Object name:<br/></label>
    <input type="text" class="form-control" id="objectName" @onkeydown="@SearchByNameOnEnter" @bind-value="@_searchByNameObjectNamePhrase" @bind-value:event="oninput"/>
</div>

...Snip

@code {
    private string _searchByNameObjectNamePhrase = string.Empty;

    private async Task SearchByNameOnEnter(KeyboardEventArgs e)
    {
        if ((e.Code == "Enter" || e.Code == "NumpadEnter") && !string.IsNullOrWhiteSpace(_searchByNameObjectNamePhrase))
        {
            _searchResult = await ServerApiClient.SearchObjectsByNamePhrase(_searchByNameObjectTypeUuid, _searchByNameObjectNamePhrase);
        }
    }
}

ServerApiClientService.cs Web API 客户端服务

通过这个我调用不同的 Web API 端点,从后端的数据库中获取数据。

GetDdsObjectAttributeValueCount() 方法按预期工作。

SearchObjectsByNamePhrase(string objTypeUuid, string searchTermPhrase) 方法将文件 /wwwroot/index.html 作为答案发送给我。 (详情请在代码中显示cmets)

namespace WebAssembly.Client.Services
{
    public class ServerApiClientService : IServerApiClientService
    {
        #region Constants - Static fields - Fields
        private readonly HttpClient _httpClient;
        #endregion
        
        #region Constructors and Destructors
        public ServerApiClientService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }
        #endregion
        
        #region Methods
        // This endpoint request work as expected
        public async Task<IEnumerable<ObjectAttributeValueCount>> GetDdsObjectAttributeValueCount()
        {
            IEnumerable<ObjectAttributeValueCount> result =
                await _httpClient
                    .GetFromJsonAsync<List<ObjectAttributeValueCount>>("/api/DdsDashboard/GetDdsObjectAttributeValueCount");
            return (result ?? Array.Empty<ObjectAttributeValueCount>()).AsQueryable();
        }

        // This endpoint request NOT work as expected
        public async Task<IEnumerable<SearchResultItem>> SearchObjectsByNamePhrase(string objTypeUuid, string searchTermPhrase)
        {
            // For test i have called as string and i get HTML response. wwwroot/index.html is comming back.
            var asJsonString =
                await _httpClient
                        .GetStringAsync($"/api/DdsDashboard/SearchObjectsByNamePhrase/{objTypeUuid}/{searchTermPhrase}");

            // And here i get the exception "System.Text.Json.JsonReaderException"
            // '<' is an invalid start of a value
            IEnumerable<SearchResultItem> result =
                await _httpClient
                    .GetFromJsonAsync<List<SearchResultItem>>($"/api/DdsDashboard/SearchObjectsByNamePhrase/{objTypeUuid}/{searchTermPhrase}");

            return (result ?? Array.Empty<SearchResultItem>()).AsQueryable();
        }
        #endregion
    }
}

服务器端数据流

DdsDashboardController.cs 作为 Web API 控制器

当我使用 Postman 处理此控制器中的所有方法(路由)时,它们都可以正常工作。

路由 [HttpGet("GetDdsObjectAttributeValueCount")] 和路由 [HttpGet("GetDdsObjectTypeStatistic")] 也适用于 ServerApiClientService.cs。

只有路线 [HttpGet ("SearchObjectsByNamePhrase / {objTypeId} / {searchTerm}")] 只适用于 Postman。

namespace WebAssembly.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DdsDashboardController : ControllerBase
    {
    #region Constants - Static fields - Fields

        private readonly IDdsRepository _repository;

    #endregion

    #region Constructors and Destructors

        public DdsDashboardController(IDdsRepository repo)
        {
            _repository = repo;
        }

    #endregion

    #region Methods

        [HttpGet("GetDdsObjectAttributeValueCount")]
        public async Task<IEnumerable<ObjectAttributeValueCount>> GetDdsObjectAttributeValueCount()
        {
            return await _repository.GetDdsObjectAttributeValueCount();
        }

        [HttpGet("GetDdsObjectTypeStatistic")]
        public async Task<IEnumerable<ObjectTypeStatistic>> GetDdsObjectTypeStatistic()
        {
            return await _repository.GetDdsObjectTypeStatistic();
        }

        // This method is called and worked as expected. When i call this endpoint with Postman all is fine. Correct JSON response.
        [HttpGet("SearchObjectsByNamePhrase/{objTypeId}/{searchTerm}")]
        public async Task<IEnumerable<SearchResultItem>> SearchObjectsByNamePhrase(string objTypeId, string searchTerm)
        {
            // Correct result from my database. I have checked with an breakpoint.
            var result = await _repository.SearchObjectsByNamePhrase(objTypeId, searchTerm);

            return result;
        }

    #endregion
    }
}

Startup.cs

配置方法

public void Configure(IApplicationBuilder app)
{

    if (Env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseWebAssemblyDebugging();
    }
    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.UseBlazorFrameworkFiles();

    app.UseStaticFiles();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
                     {
                         endpoints.MapRazorPages();
                         endpoints.MapControllers();
                         endpoints.MapFallbackToFile("index.html");
                     });
}

ConfigureServices 方法

public void ConfigureServices(IServiceCollection services)
{
    SqlMapper.AddTypeHandler(new MySqlGuidTypeHandler());
    SqlMapper.RemoveTypeMap(typeof(Guid));
    SqlMapper.RemoveTypeMap(typeof(Guid?));

    services.AddControllersWithViews();
    services.AddRazorPages();

    services.AddScoped<IDdsRepository, DdsRepository>();

    var dbConnectionSettings = new DdsDbConnectionConfiguration(Configuration.GetSection("DdsDbSettings"));
    services.AddSingleton(dbConnectionSettings);

    if (!Env.IsDevelopment())
    {
        services.AddHttpsRedirection(options =>
                                     {
                                         options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
                                         options.HttpsPort          = 443;
                                     });
    }
}

向邮递员提出要求

我希望我已经提供了足够的信息来说明为什么这不起作用。

更新

好的。问题是表单控件的绑定仅在我手动进行选择更改时才起作用。在渲染不工作时设置一个选择。

<div class="form-group">
    <label for="objectType">Select object type</label>
    <select id="objectType" class="form-control" @bind="@_searchByNameObjectTypeUuid">
        @if (_objectTypes != null)
        {
            @foreach (var objectType in _objectTypes)
            {
                @if (objectType.TypeName == "Music")
                {
                    <option value="@objectType.Uuid.ToString("D")" selected="selected">@objectType.TypeName</option>
                }
                else
                {
                    <option value="@objectType.Uuid.ToString("D")">@objectType.TypeName</option>
                }
            }
        }
    </select>
</div>

这就是为什么没有设置_searchByNameObjectTypeUuid 值的原因。 还有endpoints.MapFallbackToFile(" index.html ")

我在OnInitializedAsync() 方法中设置了_searchByNameObjectTypeUuid 的值,我还加载了_objectTypes

protected override async Task OnInitializedAsync()
{
    _objectTypes = await DdsApiClient.GetObjectTypes();
    _searchByNameObjectTypeUuid = _objectTypes.SingleOrDefault(x => x.TypeName == "Music")?.Uuid.ToString("D");
}

如果有人知道如何在渲染时使用 foreach 循环设置值,我将不胜感激。

感谢@Neil W 的帮助。

【问题讨论】:

  • 尝试首先将您的 clientAPI 中的结果放入 HttpResponseMessage 中。然后检查那个。你要返回什么状态码? >>>> HttpResponseMessage res = await _httpClient .GetAsync($"/api/DdsDashboard/SearchObjectsByNamePhrase/{objTypeUuid}/{searchTermPhrase}"); >>> 这可能会给你一个线索。
  • 感谢 Neil W。这确实给了一个提示。 {objTypeUuid} 未设置为初始值。我会更新我的问题。

标签: c# asp.net-core blazor-webassembly


【解决方案1】:

我没有直接回答您的问题,但是当我第一次开始遇到来自 Blazor wasm 客户端的 WebAPI 挑战时,我创建了一个客户端 API 基类,因此:

public abstract class ClientAPI
{
    protected readonly HttpClient Http;
    private readonly string BaseRoute;

    protected ClientAPI(string baseRoute, HttpClient http)
    {
        BaseRoute = baseRoute;
        Http = http;
    }

    protected async Task<TReturn> GetAsync<TReturn>(string relativeUri)
        => await ProcessHttpResponse<TReturn>(await Http.GetAsync($"{BaseRoute}/{relativeUri}"));

    protected async Task<TReturn> PostAsync<TReturn, TRequest>(string relativeUri, TRequest request)
        => await ProcessHttpResponse<TReturn>(await Http.PostAsJsonAsync($"{BaseRoute}/{relativeUri}", request));

    private static async Task<TReturn> ProcessHttpResponse<TReturn>(HttpResponseMessage response)
    {
        if (response.IsSuccessStatusCode)
            return await response.Content.ReadFromJsonAsync<TReturn>();

        string msg = await response.Content.ReadAsStringAsync();
        Console.WriteLine(msg);
        throw new Exception(msg);
    }
}

然后我的派生客户端 API 类将在基类上调用 GetAsync。然后,这将解决 Json 响应,或者如果 HttpResponseMessage 有失败状态代码,它将记录错误。

像这样从派生类中使用:

public class BackOfficeClientAPI : ClientAPI
{
    public BackOfficeClientAPI(HttpClient http) : base("api/backoffice", http) { }

    public async Task<IEnumerable<Category>> GetCategoriesAsync(Guid organisationId)
        => await GetAsync<IEnumerable<Category>>($"getcategories?organisationId={organisationId}");

    public async Task<Category> AddCategoryAsync(AddCategoryRequest request)
        => await PostAsync<Category, AddCategoryRequest>("addcategory", request);

附言。我用的是querystring而不是路由参数,但是原理是一样的。

我发现捕获这种类型的异常是一个很好的模式。

【讨论】:

  • 感谢这个模式。这是个好主意。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-18
  • 1970-01-01
  • 2020-12-27
  • 2020-09-17
  • 2021-07-07
  • 2021-07-26
  • 2021-12-11
相关资源
最近更新 更多