简体   繁体   中英

Blazor wasm Web API response is an HTML string

Ok i have a wasm App that call the server side web api endpoints. The problem is that I get the index.html page from the /wwwroot directory for an endpoint as an answer. But when I address the endpoint with Postman I get the json answer as expected. Okay i will show how i do this with my code.

Client Side data flow

Search.razor Page

Here I call the Web API endpoint when a search text has been entered in the form field. That works as expected.

... 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 Client service

With this I call different Web API endpoints that get the data from a database in the backend.

The GetDdsObjectAttributeValueCount() method works as expected.

The method SearchObjectsByNamePhrase(string objTypeUuid, string searchTermPhrase) sends me the file /wwwroot/index.html as an answer. (Show comments in code for details)

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
    }
}

Server Side data flow

DdsDashboardController.cs as the Web API Controller

All methods (routes) in this controller work perfectly when I address them with Postman.

The route [HttpGet("GetDdsObjectAttributeValueCount")] and the route [HttpGet("GetDdsObjectTypeStatistic")] also work with the ServerApiClientService.cs.

Only the route [HttpGet ("SearchObjectsByNamePhrase / {objTypeId} / {searchTerm}")] only works in 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

Configure method

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 method

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;
                                     });
    }
}

Request with Postman

邮递员结果

I hope I have given enough information to be able to give me an indication of why this not work.

Update

Ok. The problem is that the binding for the form control worked only when i make manualy a selection change. Set a selected while rendering is not working.

<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>

And that's why the _searchByNameObjectTypeUuid value is not set. And with that endpoints.MapFallbackToFile(" index.html ") .

I have set the value of _searchByNameObjectTypeUuid in the OnInitializedAsync() method where also i load the _objectTypes .

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

If anyone knows how to set the value with the foreach loop while rendering, I would be grateful to hear about it.

Thanks to @Neil W for help.

I don't have an answer for your question directly, but when I first started encountering challenges with WebAPI from Blazor wasm client, I create a client API base class, thus:

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);
    }
}

Then my derived Client API class would call GetAsync on the base class. That will then either resolve the Json response or if the HttpResponseMessage had a failure status code, it would log the error.

Use from derived class like this:

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);

PS. I'm using querystring instead of route parameters, but the principle is the same.

I've found it a nice pattern to catch exceptions of this type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM