So I'm trying to render an Accounts component that would just render what returns from a C# back end. In Swagger, the API call returns JSON, great, but if I hit the exact same point in Postman or my React front-end, I get the main.jsx page, which is...less than useful.
The relevant snippet of my API:
var group = routes.MapGroup("/api/tblAccount").WithTags(nameof(tblAccount));// We only want active (not soft-deleted) accounts.group.MapGet("/", async (my_solution_name_Context db) =>{ var accounts = await db.tblAccounts.ToListAsync(); var ret = JsonSerializer.Serialize(accounts.Where(a => a.IsDeleted == false)); return Results.Ok(ret);}).WithName("GetAlltblAccounts").WithOpenApi();
And my component (yeah, with some WIP sections):
import { useEffect, useMemo, useState, Fragment } from 'react';import './App.css';function Accounts() { const [accounts, showAccounts] = useState([]); //useEffect(() => { useMemo(() => { populateAccountsData(); }, [accounts]); const contents = accounts === undefined ? <p><em>Loading... Please refresh once the ASP.NET backend has started.</em></p> : <table className="table table-striped" aria-labelledby="tableLabel" ><thead><tr><th>First Name</th><th>Last Name</th><th>Assigned Unit</th><th>Primary Account Holder</th><th>Acct. Creation Dt.</th><th> </th></tr></thead><tbody> {accounts.map(a => { //needs to eventually change to accounts.filter where IsDeleted is false, unless I figure out how to do this better on the API end. debugger; //return (<Fragment key={a.pk_int_id}><tr><td>a.primary_account_holder.first_name</td><td>a.primary_account_holder.last_name</td><td>a.unit_id</td><td>a.primary_account_holder.primary_account_holder</td><td>a.creation_date</td><td><button className='btn' role="button">Edit</button> <button className='button' rule="button">Mark Inactive</button></td></tr></Fragment> //); }) //end accounts.map (technically below, but JSX) }</tbody></table>; return (<div className="container"><h1>My Solution</h1><h2>Accounts</h2> {contents}<button className="btn btn-primary" id="newAccount" type="button" onClick={addAcct}>Create a new account!</button></div> ); async function populateAccountsData() { try { const response = await fetch('https://localhost:5173/api/tblAccounts/', { headers: {"Accept": "application/json", //"mode": "no-cors","Access-Control-Allow-Origin": "*" } }); if (!response.ok) { console.error('API request failed:', response.statusText); return; // Prevent further execution if the fetch fails } //end if console.log(response.headers); const contentType = response.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { throw new TypeError("Oops, we haven't got JSON!"); } const data = await response.json(); debugger; // This should now get hit if the request is successful showAccounts(data); } catch (error) { console.error('Error fetching accounts:', error); } } //end populateAccountsData function addAcct() { }} //end Accountsexport default Accounts;
As you can see, I've tried messing with headers and etc. The console statement that prints response.headers prints an object that's empty except for the [Prototype] part. My debugger;
call in <tbody>
isn't being hit, nor the one in my actual call that populates the thing.
I'm an utter React newb; any time I've done something like this in the past, it's been with jQuery and AJAX directly manipulating my DOM.
Side question: is there a way to avoid hard-coding "localhost:port" in the JSX? I just have a really bad feeling about doing that...
I already tried everything listed here that would pertain to this stack: https://www.reddit.com/r/dotnet/comments/193432s/what_causes_apis_to_return_html_content_instead/
EDIT: Per request, posting my package.json (granted with a lot of the metadata parts omitted for privacy.)
"dependencies":{"bootstrap": "^5.3.3","immer": "^10.1.1","prettier": "^3.3.3","react": "^18.3.1","react-bootstrap": "^2.10.4","react-dom": "^18.3.1","use-immer": "^0.10.0" },"devDependencies": {"@eslint/js": "^9.9.0","@types/bootstrap": "^5.2.10","@types/eslint__js": "^8.42.3","@types/eslint-plugin-jsx-a11y": "^6.9.0","@types/react": "^18.3.3","@types/react-dom": "^18.3.0","@vitejs/plugin-react": "^4.3.1","eslint": "^9.9.0","eslint-plugin-jsx-a11y": "^6.10.0","eslint-plugin-react": "^7.35.2","eslint-plugin-react-hooks": "^5.1.0-rc.0","eslint-plugin-react-refresh": "^0.4.11","glob": "^11.0.0","globals": "^15.9.0","jest": "^29.7.0","lru-cache": "^11.0.0","vite": "^5.4.1" }
EDIT 2: vite.config.js below:
import { fileURLToPath, URL } from 'node:url';import { defineConfig } from 'vite';import plugin from '@vitejs/plugin-react';import fs from 'fs';import path from 'path';import child_process from 'child_process';import { env } from 'process';const baseFolder = env.APPDATA !== undefined && env.APPDATA !== '' ? `${env.APPDATA}/ASP.NET/https` : `${env.HOME}/.aspnet/https`;const certificateName = "solutionname.client";const certFilePath = path.join(baseFolder, `${certificateName}.pem`);const keyFilePath = path.join(baseFolder, `${certificateName}.key`);if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { if (0 !== child_process.spawnSync('dotnet', ['dev-certs','https','--export-path', certFilePath,'--format','Pem','--no-password', ], { stdio: 'inherit', }).status) { throw new Error("Could not create certificate."); }}const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` : env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:7194';// https://vitejs.dev/config/export default defineConfig({ plugins: [plugin()], resolve: { alias: {'@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { proxy: {'^/weatherforecast': { target, secure: false } }, port: 5173, https: { key: fs.readFileSync(keyFilePath), cert: fs.readFileSync(certFilePath), } }})
EDIT 3: So I'm getting something JSON-like in Postman, but now Swagger and Postman think the return type is text/plain, rather than application/json. If I take out the check for application/json, the JSX proceeds, buuuuut...why am I getting text/plain instead of application/json?
EDIT 4 (or was it 6?) I found I forgot FKs between two tables so I had EF Core Power Tools regen my Models and controllers. Edited the C# above. Swagger? Perfect, down to getting application/json! Postman and React? 500 error, just says "Internal Server Error."
I guess the more specific question is why Swagger's getting valid returns and nothing else is.