How can I host ASP.NET API and Blazor Web Assembly like an JavaScript-SPA? - asp.net-core

Context:
We want to create a Single Page Application that runs with Blazor WebAssembly on the client-side. On the server-side, the solution has an ASP.NET MVC which includes some ApiController classes for our REST APIs.
We want to use ASP.NET API on the server-side instead of Blazor Server because we want to provide a REST interface with ApiController classes for unknown consumers.
Here is my client-side (Blazor WebAssembly) and server-side (ASP.NET API) project in a single solution:
A first try to request the API via Blazor´s HttpClient-class in our FetchData-component:
#inject HttpClient Http
...
#code {
private TodoItem[] TodoItems;
protected override async Task OnInitializedAsync()
{
TodoItems = await Http.GetJsonAsync<TodoItem[]>("api/ToDo");
}
}
On server-side the API-Controller looks like:
namespace ToDoListAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
public class ToDoController : ControllerBase
{
[HttpGet]
public string IGetAll()
{
var lResult = new List<ToDoList.TodoItem>();
// create dummies
for (var i = 0; i < 10; i++)
{
lResult.Add(new ToDoList.TodoItem() { Title = $"Title {i}", IsDone = false });
}
return JsonSerializer.Serialize(lResult);
}
}
}
Problem: In my Blazor WebAssembly Project the request to the API fails. The Blazor WebAssembly Project is hosted via https://localhost:44340/ and the API is hosted via https://localhost:44349/. How can I host both projects together as I would it do with a JavaScript Framework?

You can either, depending on how you want to host and deploy your solution :
API and application in 2 different hosts
Enable CORS in the API project Startup class :
public void Configure(IApplicationBuilder app)
{
...
app.UseCors(configure =>
{
// configure here your CORS rule
}
...
}
All in one host
In your API project
add a package reference to Microsoft.AspNetCore.Components.WebAssembly.Server
Setup the blazor server in your Startup class
public void Configure(IApplicationBuilder app)
{
app.UseBlazorFrameworkFiles();
...
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToFile("index.html");
});
}
You can create a sample solution with : dotnet new blazorwasm --hosted. It'll create a solution with a Blazor wasm project and a host.
Docs

With the latest update to the templates dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-preview2.20160.5
You can create a Blazor WebAssembly app setup with authentication using ASP.NET Core Identity and IdentityServer by running the following command:
dotnet new blazorwasm --hosted --auth Individual -o BlazorAppWithAuth1
This creates:
Client Side Blazor
A single Project that can be used for MVC, API and razor pages, that contains an "inline" IdentityServer which can be used to secure the API calls
I was stuck on how to have IS4 in the same project as the APi (it's a small project and a independently hosted IDP would be overkill and I just want to deploy one thing) but this template shows how.
source: https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/

Related

Azure Application Insight wrong shows URL in logs for ASP.NET Core 6 Web API application with API versioning

I have an ASP.NET Web API application running on .NET 4.8. In this app, I'm using standard Microsoft API versioning from Microsoft.AspNet.WebApi.Versioning and Microsoft.AspNet.WebApi.Versioning.ApiExplorer.
For instance:
[ApiVersionExtended(SupportedApiVersions.V9)]
[RoutePrefix("v{version:apiVersion}/telemetry")]
public sealed class TelemetryController : ApiController
{
where ApiVersionExtended - my filter. In Azure Application Insight requests to my API are shown with the correct version. For instance:
But after migration to .NET 6, I lost the correct version number in AI logs, for instance:
My code has several changes after migration to .NET 6
[ApiController]
[AllowAnonymous]
[ApiVersionExtended]
[Route("v{version:apiVersion}/telemetry")]
public sealed class TelemetryController : ApiController
{
[HttpGet("ipInfo")]
public async Task<IActionResult> GetIpInfoAsync(CancellationToken cancellationToken)
{
/* some code here */
}
}
I can't find the analog [RoutePrefix] attribute in .NET 6.
Might someone know what the reason for this issue is? And how I can fix it?
As suggested by #Peter Bons, this can be the issue with your existing Nuget Package.
The Nuget Package required to Implement API Versioning in .NET 6 Core Web API is Microsoft.AspNetCore.Mvc.Versioning
Install Microsoft.AspNetCore.Mvc.Versioning Nuget Package
In Program.cs add the below services
To add versioning for WebAPI
builder.Services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(2, 0);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true;
opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version"));
});
To Add versioning with Swagger, add the below services
builder.Services.AddSwaggerGen(Options => Options.SwaggerDoc("v1", new OpenApiInfo { Title = "Audit Self Serve platform", Version = "v1" }));
Use MapToApiVersion attribute to assign each action to a distinct version
[MapToApiVersion("1.0")]
[HttpGet]
OutPut:

Add Blazor Client to ASP .Net Core Web API (.NET 6)

I was wondering if there was a specific way to add a Blazor front end to an ASP .NET Core project. I know when creating an Blazor WebAssembly App there is the option to select ASP.Net Core Hosted and it would create a template, but I would like to add the client to the host manually.
Specifically, I've been looking at this ASP.Net to ASP.Net Core tutorial here and would like to add a Blazor front-end to the ASP.NET Core Web API project that is created.
Sorry in advance if the question isn't clear, this is my first time doing this sort of project.
If you want to add the API to the host manually you can create an ASP.NET Core Web API project. Create your post/get/ect controllers in the Web API, and in the Blazor Project inject an HttpClient with a BaseUri that points at your Web API controllers.
This is how I set mine up too. I keep all my components and services separate in class library and razor library projects. Let me know if you need further assistance.
Update
Seems like their might be some confusion on the project layout. For this example I named my Blazor Server App BlazorServerFrontEnd. I named my ASP.NET Core Web API BlazorServerBackEnd. I created mine on .NET Core 6.0. It doesn't use a Startup.cs like 3.1. Instead everything is done in the Program.cs file.
BlazorServerBackEnd (Web Api) will not have services.AddServerSideBlazor() like our BlazorServerFrontEnd (Blazor Server App). If you're seeing this in both projects you probably selected the wrong type. When you run BlazorServerBackEnd (Web Api) you'll see it has one controller built in (Controllers/WeatherForecastController.cs).
BlazorServerFrontEnd (Blazor Server App) will have services.AddServerSideBlazor(). You'll see in Data folder there is also a WeatherForecatService.cs. When you look at the service it doesn't connect to any Api. The service just grabs the data from a hardcoded readonly string[].
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
This is what we need to change. Instead of the data being fetched from within BlazorServerFrontEnd (Blazor Server App) we want to connect to our BlazorServerBackEnd (Web Api). Open up Program.cs in the BlazorServerFrontEnd (Blazor Server App) and change the injected WeatherForecastService from a Singleton to an HttpClient.
//builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddHttpClient<WeatherForecastService>(client =>
{
// Path pointing to BlazorServerBackEnd (Web Api) baseUri
client.BaseAddress = new Uri("https://localhost:7246/");
});
Open up WeatherForecastService.cs in the BlazorServerFrontEnd (Blazor Server App) and add a constructor to inject our HttpClient that we just setup. We'll use the HttpClient to connect to our BlazorServerBackEnd (Web Api) using _httpClient.GetAsync("WeatherForecast"). Now it looks like this;
public class WeatherForecastService
{
private readonly HttpClient _httpClient;
public WeatherForecastService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
var response = await _httpClient.GetAsync("WeatherForecast");
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
WeatherForecast[] forecasts = JsonConvert.DeserializeObject<WeatherForecast[]>(json);
return forecasts;
}
return null;
}
}
Please keep in mind this is a very brief example to get you on your feet. Ideally you'll want to have an interface for your services, and you'll want to inject the interface into your pages instead of the class. So in the real world it would look more like this;
builder.Services.AddHttpClient<IWeatherForecastService, WeatherForecastService>(client =>
{
// Path pointing to BlazorServerBackEnd (Web Api) baseUri
client.BaseAddress = new Uri("https://localhost:7246/");
});
And you would be injecting your interface into the page like so;
#inject IWeatherForecastService ForecastService
I also recommend separating components, services, controllers, models ect. into seperate razor/class libraries. Keep everything very loosely coupled. This will make it easy for you to port your application over to something like a MAUI or Windows Forms App BlazorWebView.
P.S. Swagger comes built into the Web Api project. It is very useful when developing and debugging. I would recommend familiarizing yourself with it if your new.

Asp.Net Core 3.1 Web Application, Api page not found issue

My Environment Windows 10. Visual Studio 2019 Professional, Asp.Net Core 3.1
I am following a Pluralsight course to teach myself Asp.Net Core 3.1. Following the instructor, I have created the web application. Everything goes well until the instructor adds an api controller to the application. It works for him but not for me.
Here's my api controller
namespace OdeToFood.Api
{
[Route("api/[controller]")]
[ApiController]
public class RestaurantsController : ControllerBase
{
private readonly OdeToFoodDbContext _context;
public RestaurantsController(OdeToFoodDbContext context)
{
_context = context;
}
// GET: api/Restaurants
[HttpGet]
public async Task<ActionResult<IEnumerable<Restaurant>>> GetRestaurants()
{
return await _context.Restaurants.ToListAsync();
}
// GET: api/Restaurants/5
[HttpGet("{id}")]
public async Task<ActionResult<Restaurant>> GetRestaurant(int id)
{
var restaurant = await _context.Restaurants.FindAsync(id);
if (restaurant == null)
{
return NotFound();
}
return restaurant;
}
. . . . .
Here's my project structure and hierarchy.
When I rebuild my project, and call the app from local IIS Express, i.e. https://localhost:44351/ It loads fine. I can interact fully, browse and CRUD entities. However when I point to any api url, e.g. https://localhost:44351/api/Restaurants or https://localhost:44351/api/Restaurants/2 I get "This localhost page can’t be found". The api simply does not load or respond in any way.
I am familiar with MVC5 where, when creating a new project, in the create project wizard scaffolding, you could check a box to add api functionality. I am not seeing this in VS2019 for Asp.Net Core 3.1 We Apps.
I promise you have have done my homework before asking this question here. I have googled to death. I have seen MS articles on core 3.1 breaking changes. I have looked at online project templates. I have searched stackoverflow. Maybe my search terms are flawed and I'm simply missing something simple.
Questions:
Why is the api shown above not loading?
Is there a way to add api functionality to an existing Asp.Net Core 3.1 Web Application or do I need to create a separate api project?
Is there a way to create a new Asp.Net Core 3.1 Web Application with api functionality included?
My thanks in advance
Kieran
If you'd like to add web APIs feature into an existing Razor Pages project, you need to do additional configuration, like below.
public void ConfigureServices(IServiceCollection services)
{
//add services for controllers
services.AddControllers();
services.AddRazorPages();
//...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseRouting();
//...
app.UseEndpoints(endpoints =>
{
//add endpoints for controller actions
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
Testing code of controller and action(s)
[Route("api/[controller]")]
[ApiController]
public class RestaurantsController : ControllerBase
{
public IActionResult GetRestaurants()
{
return Ok("Restaurants List Here");
}
}
Test Result

404 Deploying asp.net core hosted blazor webassembly to Netlify

I am attempting to deploy an asp.net core hosted blazor webassembly app to Netlify. I have published the release version of the Server project to a directory on my desktop, and uploaded it to github. I set Netlify's publish directory to wwwroot and the site does render just fine. However, if I attempt a call to an api controller, it returns a 404. Specifically, here is my code:
//Register.razor in the Client project
if (Model.Password.Length >= 6 && Model.Password == Model.ConfirmPassword)
{
await HttpClient.PostAsJsonAsync<RegisterModel>("api/Register/Post", Model);
NavigationManager.NavigateTo("/");
}
//In my controller
[Route("api/Register")]
public class RegisterController : Controller
{
private UserContext UserContext { get; set; }
private IHasher Hasher = new Pbkdf2Hasher();
public RegisterController (UserContext userContext)
{
UserContext = userContext;
}
[RequireHttps]
[HttpPost]
[Route("Post")]
public async Task Post([FromBody]RegisterModel model)
{
var user = new UserModel
{
Email = model.Email,
Password = Hasher.Hash(model.Password)
};
await UserContext.AddAsync(user);
await UserContext.SaveChangesAsync();
}
}
The url request I send is: https://(NetlifyDefaultDomain)/api/Register/Post. However, I get a 404 response. On localhost it works just fine. I'm imagining that there's a setting that I have to change somewhere in order for the request URL to work. I've tried looking but have been unable to find guidance. What do I need to change? Thanks
Edit
Here is the Program.cs file of my Client project
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddTransient(sp => new HttpClient {
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider,
ApiAuthenticationStateProvider>();
builder.Services.AddScoped<IAuthService, AuthService>();
await builder.Build().RunAsync();
}
}
Target framework is netstandard2.1, and it's webassembly 3.2.0.
Netlify is a static file host. They do not run any server-side applications like .NET core on their servers.
So you can host your Blazor client-side application on Netlify but if you want server side code to run you must host it somewhere else.
If you are looking for free hosting for your API there are some cloud providers that have a free tier. Azure has free App Service with some limits, Google cloud has a free micro VPS that can host a small app, heroku also has a free offering.
A cheap VPS from Digital Ocean, Vultr or Linode is another alternative.

ASP.Net Core SignalR simple host - 404 error

I am wanting to have 1 simple console app host that is solely for self-hosting the SignalR component.
I have created an "Empty Web Application" using the template. I have created a very simple StartUp file that does not contain anything like MVC etc as it is not needed. However I am getting a 404 not found error from the browser when attempting to negotiate.
The Startup file is as follows:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
app.UseSignalR(routes =>
{
routes.MapHub<Masterhub>("masterhub");
});
}
}
As you can see, it is very basic, but as I don't want any MVC/Web API functionality, I didn't include all of that setup. There is setup for CORS and SignalR, that is all.
Is what I am attempting to do possible?
It turns out that the JavaScript file I was using from the web client to connect to the self-hosted SignalR Console Application, was the old full .Net version, and not the new version you can get from NPM.