I'm preparing WebAPI where the client (Angular) is asking via HTTP for logging in and current user.It works fine, when I'm sending POST and GET requests from Swagger (works on https://localhost:44322/swagger/index.html). I receive all necessary answers, but fun thing happens when I'm trying to do so from Angular (works on https://localhost:4200). CORS origin turned on, headers allowed, any method allowed, credentials allowed...
I think I run into a cookie-related issue, because, when I open both cards (swagger and angula) in the same browser window, I'm able to do everything find, but when I separate them, swagger works, but Angular stop seeing cookies which come from the server-side.
I think I tried everything. I tried to play withCredentials paremeter in HTTP requests, I tried to parametrize CORS to allow switch on AllowCredentials(); method. Nothing worked.
So, Swagger can send requests like below.
I also implemented HTTP requests from Angular.
Below login.component.ts
import { HttpClient } from '#angular/common/http';
import { Message } from '#angular/compiler/src/i18n/i18n_ast';
import { Component, OnInit } from '#angular/core';
import { first } from 'rxjs';
import { UserService } from '../user.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
response: any;
currentUser = {
firstName: "",
lastName: ""
};
user: any;
userLogin = {
email: "",
password: ""
}
firstName: string = "";
lastName: string = "";
constructor(private http: HttpClient, private service: UserService) { }
ngOnInit(): void {
this.getCurrentUser();
}
loginAction(): any {
this.response = this.service.loginUser(this.userLogin);
if(this.response){
this.service.currentUser().subscribe((response: any) =>{
this.currentUser.firstName = (response as any).firstName;
});
}
}
logoutAction():any{
this.service.logoutUser();
}
getCurrentUser(){
this.service.currentUser().subscribe((response: any) =>{
this.currentUser.firstName = (response as any).firstName;
});
}
}
And user.service.ts
export class UserService {
readonly taskAPIUrl = "https://localhost:44322/api";
constructor(private http: HttpClient) { }
loginUser(userLogin :any) {
return this.http.post("https://localhost:44322/api/UserLogin",userLogin).subscribe();
}
logoutUser(): any {
return this.http.post<any>("https://localhost:44322/api/UserLogin/logout", {withCredentials: true}).subscribe();
}
currentUser(): any {
return this.http.get<any>("https://localhost:44322/api/UserLogin/getCurrentUser", {withCredentials: true});
}
Here is Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ToDoListAPI.Data;
using ToDoListAPI.Models;
namespace ToDoListAPI
{
public class Startup
{
private string myAllowSpecificOrigins = "_myAllowSpecificOrigins";
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ToDoListAPI", Version = "v1" });
});
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ConnectionString"));
});
//Enable CORS
services.AddCors(options =>
{
options.AddPolicy(name: myAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://localhost:4200").
AllowAnyMethod().
AllowAnyHeader().
AllowCredentials();
});
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.ClaimsIdentity.UserNameClaimType = "UserID";
}).
AddEntityFrameworkStores<DataContext>().
AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
});
}
// 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();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ToDoListAPI v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(myAllowSpecificOrigins);
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
UserLoginController.cs where I send HTTP requests
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using ToDoListAPI.Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace ToDoListAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UserLoginController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public UserLoginController(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
// GET: api/<UserLoginController>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<UserLoginController>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
[HttpGet]
[Route("getCurrentUser")]
public async Task<IActionResult> GetCurrentUser()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return Unauthorized();
}
return Ok(user);
}
// POST api/<UserLoginController>
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLogin userLoginDto)
{
var foundUser = await _userManager.FindByEmailAsync(userLoginDto.Email);
if (foundUser == null)
{
return NotFound();
}
var result = await _signInManager.PasswordSignInAsync(
foundUser, userLoginDto.Password, true, false);
if (result.Succeeded)
{
return Ok();
}
return NotFound();
}
// POST api/<UserLoginController>
// in progress
[HttpPost]
[Route("logout")]
public async void Logout()
{
await _signInManager.SignOutAsync();
}
// DELETE api/<UserLoginController>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
Please help, I think I stuck somewhere...
Here is example of UserLogin request from Swagger
And here from angular client
As you can see, Swagger has a lot more in the request and response stay the same. The biggest problem is when I send getCurrentUser() request.
Swagger:
and angular
Ok. For angular it should look something like this.
In user.service.ts methods should return Observalbe.
For an example:
loginUser(userLogin : "here should be model class): Observable<Any> {
return this.http.post("https://localhost:44322/api/UserLogin",userLogin).subscribe(repond => {return respond});
return this.httpClient
.post("https://localhost:44322/api/UserLogin",userLogin)
.pipe(map(resposne =>{
return resposne;
}),
catchError(error => {
console.log(error);
}));
}
In login.component.ts login should look something like this:
loginAction() {
this.service.loginUser(this.userLogin)
.pipe(first())
.subscribe( response =>{
this.currentUser.firstName = response.firstName;
}, error => {
console.log(error);
});
}
For GetCurrentUser in Controller file try tu parse tu yours id type instead of User this User.Identity.Name or User.Identity.Id
Related
I need to make a way for app user to listen to server , and get updated HTTPGET request every time database is updated. Client side implementation is not needed, only server side. I have small experience in SignalR and would appreciate any help on Hub side .
My code so far
Startup
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
services.AddScoped<IContactRepository, ContactRepository>();
services.AddControllers();
services.AddSignalR();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Projekt", Version = "v1" });
});
}
// 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();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Projekt v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<UpdatesHub>("hubs/");
});
} ```
here are my ApiCalls user can use
[ApiController]
[Route("api/[controller]")]
public class ContactsController : ControllerBase
{
private readonly IContactRepository _contactRepository;
private readonly IMapper _mapper;
public ContactsController(IContactRepository contactRepository, IMapper mapper)
{
_contactRepository = contactRepository;
_mapper = mapper;
}
#region API CALLS
[HttpGet]
public async Task<ActionResult<IEnumerable<AppUserDto>>> GetUsers([FromQuery]UserParams userParams)
{
//Ego loading phone numbers, gives circular reference problem,
//use DTOs and Mapping instead
var users = await _contactRepository.GetUsersAsync(userParams);
Response.AddpaginationHeader(users.CurrentPage, users.PageSize,
users.TotalCount, users.TotalPages);
var usersToReturn = _mapper.Map<IEnumerable<AppUserDto>>(users);
return Ok(usersToReturn);
}
//~/api/Contacts/1
[HttpGet("{id}")]
public async Task<ActionResult<AppUser>> GetUser(int id)
{
var user = await _contactRepository.GetUserByIdAsync(id);
var userToReturn = _mapper.Map<AppUserDto>(user);
return Ok(userToReturn);
}
[HttpPost("addContact")]
public async Task<ActionResult> AddContact(AppUser appUser)
{
var IsConstrained = await _contactRepository.CheckIfConstrained(appUser);
if (IsConstrained)
{
return BadRequest("user already exists");
}
else
{
_contactRepository.AddContact(appUser);
var result = await _contactRepository.SaveAllAsync();
if (result)
return Ok();
else
return BadRequest("user not saved to database");
}
}
[HttpPut("update")]
public async Task<ActionResult> Update(AppUser user)
{
//Check if user changed his name, if changed ->check if it's unique then update,
//if name not changed just update
var UserBeforeUpdate = _contactRepository.GetUserByIdAsync(user.Id);
var IsNameChangedBool = _contactRepository.CheckIfNameChanged(user , UserBeforeUpdate.Result);
if (IsNameChangedBool)
{
var IsConstrained = await _contactRepository.CheckIfConstrained(user);
if (IsConstrained)
{
return BadRequest("user already exists");
}
else
{
_contactRepository.Update(user);
if (await _contactRepository.SaveAllAsync()) return Ok("Contact updated");
return BadRequest("user not saved to database");
}
}
else
{
_contactRepository.Update(user);
if (await _contactRepository.SaveAllAsync()) return Ok("Contact updated");
return BadRequest("user not saved to database");
}
} ```
This is Hub part i don't know how to send updated httpget request to user
{
public class UpdatesHub : Hub
{
public async Task SendMessageToCaller()
{
await Clients.Caller.SendAsync("RecieveMessage");
}
}
}
Please, be sure that the route to your hub is correct. For example, if your hub is in the hubs folder you need to add endpoints.MapHub<UpdatesHub>("/hubs/hubName");
Clients can connect to SignalR in your javaScript with:
var connection = new signalR.HubConnectionBuilder().withUrl("/hubs/hubName").build();
The clients can subscribe to messages sent from the server. Something like that:
connection.on("ReceiveMessage", function (message) {
alert(message);
});
Create or modify an overloaded constructor of the class in which the database update function is implemented, in order to access the hub:
...
public class DatabaseClass
{
private IHubContext<UpdatesHub> _hub;
public DatabaseClass(IHubContext<UpdatesHub> hub)
{
_hub = hub;
}
...
Now you can use the hub to send a message to the clients, after updating your database:
...
public class DatabaseClass
{
private IHubContext<UpdatesHub> _hub;
public DatabaseClass(IHubContext<UpdatesHub> hub)
{
_hub = hub;
}
public MyUpdateFunction()
{
//Update database
//
//
_hub.Clients.All.SendAsync("ReceiveMessage", "Database updated!!");
}
...
I am using MongoDB to setup and host my database for my project, I've been following the official Microsoft doc on how to set up a Web API for MongoDB. I've done everything just as the doc had but routing isn't working. When I try http://localhost:55536/api/user It returns a 404 error.
The controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using StudyAPI.Models;
using StudyAPI.Services;
namespace StudyAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController(UserService userService)
{
_userService = userService;
}
[HttpGet]
public ActionResult<List<User>> Get() =>
_userService.Get();
[HttpGet("{id:length(24)}", Name = "GetUser")]
public ActionResult<User> Get(string id)
{
var user = _userService.Get(id);
if (user == null)
{
return NotFound();
}
return user;
}
[HttpPost]
public ActionResult<User> Create(User user)
{
_userService.Create(user);
return CreatedAtRoute("GetUser", new { id = user.Id.ToString() }, user);
}
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, User userIn)
{
var user = _userService.Get(id);
if (user == null)
{
return NotFound();
}
_userService.Update(id, userIn);
return NoContent();
}
[HttpDelete("{id:length(24)}")]
public IActionResult Delete(string id)
{
var user = _userService.Get(id);
if (user == null)
{
return NotFound();
}
_userService.Remove(user.Id);
return NoContent();
}
}
}
CRUD Operations Service
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StudyAPI.Models;
namespace StudyAPI.Services
{
public class UserService
{
private readonly IMongoCollection<User> _users;
public UserService(IStudyDBSettings settings)
{
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
_users = database.GetCollection<User>(settings.UserCollection);
}
public List<User> Get() =>
_users.Find(user => true).ToList();
public User Get(string id) =>
_users.Find<User>(user => user.Id == id).FirstOrDefault();
public User Create(User user)
{
_users.InsertOne(user);
return user;
}
public void Update(string id, User userIn) =>
_users.ReplaceOne(user => user.Id == id, userIn);
public void Remove(User userIn) =>
_users.DeleteOne(user => user.Id == userIn.Id);
public void Remove(string id) =>
_users.DeleteOne(user => user.Id == id);
}
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using StudyAPI.Models;
using StudyAPI.Services;
namespace StudyAPI
{
public class Startup
{
// 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 Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<StudyDBSettings>(
Configuration.GetSection(nameof(StudyDBSettings)));
services.AddSingleton<IStudyDBSettings>(sp =>
sp.GetRequiredService<IOptions<StudyDBSettings>>().Value);
services.AddSingleton<UserService>();
services.AddControllers();
}
// 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();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
}
Running IIS Express works and displays "Hello World!" but aside from that, routing to /api/etc doesn't work.
You should add endpoints.MapControllers(); to the Startup file.
Change the app.UseEndpoints to :
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapControllers();
});
I hope you find this helpful.
I'm trying Angular with .Net Core and I get trouble calling api controller from my components:
I called my controller with Postman with fake data return to check wether it's my app or my controller and everything is ok with my controller, so the problem lays in my app.
I spent many hours on this trying many things (creating another "service" component or using httpclient straight in my component, in the constructor, in ngInit, outside both, etc etc....) and now I'm really stuck... On all httpClient examples I can find, nothing tell me my call si wrong so I don't understand...
When I try to debug (source explorer on chrome) I can reach the httpClient call, the url is good (same as postman) but then it's the black box, only anonymous functions but I can see my "userlist" is still undefined after the http.get().subsrcibe();
startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.RegisterServiceServices();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseExceptionHandler("/default");
// 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();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
loggerFactory.AddFile("logs/Log_" + DateTime.Today.ToString()+".txt");
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { HttpClientModule } from '#angular/common/http';
import { RouterModule } from '#angular/router';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { FooterComponent } from './footer/footer.component';
import { HomeComponent } from './content/home/home.component';
#NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent,
HomeComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
home.component.ts
import { Component, Injectable } from '#angular/core';
import { user } from '../../../model/test/user';
import { Observable } from 'rxjs';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'home',
templateUrl: './home.component.html',
})
#Injectable()
export class HomeComponent {
public userlist: user[];
constructor(private httpClient: HttpClient) {
var base_url: string = window.location.origin;
var controller: string = base_url + "/svc/Test/GetUserList";
var userlistresult = this.httpClient.get<user[]>(controller).subscribe((data: any[]) => {
console.log(data);
this.userlist = data;
});
}
}
user.ts //used the same case as the .cs class to be sure that's not the reason
export interface user {
UserName: string;
FirstName: string;
LastName: string;
}
TestController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Business.Interfaces;
using Newtonsoft.Json;
using Models.Test;
using Microsoft.AspNetCore.Mvc;
namespace AnguTest.Controllers
{
[ApiController]
[Route("svc/Test")]
public class TestController : ControllerBase
{
#region Attributes
private ITestService TestService;
#endregion
#region Constructor
public TestController(ITestService testService)
{
TestService = testService;
}
#endregion
#region Methods
[HttpGet]
[Route("GetUserList")]
public JsonResult GetUserList()
{
User[] totos = new User[]
{
new User(){UserName = "toto1", FirstName = "tata1", LastName = "titi1"},
new User(){UserName = "toto2", FirstName = "tata2", LastName = "titi2"},
new User(){UserName = "toto3", FirstName = "tata3", LastName = "titi3"}
};
return new JsonResult(totos);
//return new JsonResult(TestService.GetUserList());
}
#endregion
}
}
Any help is welcome :)
I noticed that after I restart my ASP .NET API and send a POST request the API routes to the GET request method and then my POST request method. This only happens on the first POST request after I restart the API. Each POST request after this one routes directly to my POST method without processing the GET request method. Below is the class methods from my API.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
//public class variable
private readonly fujimiContext _context;
//constructor for ValuesController class
public ValuesController(fujimiContext context)
{
_context = context;
}
// GET api/values
// [Authorize(Policy = "RequireReadRole")]
[HttpGet("bins")]
public async Task<IActionResult> GetValues()
{
var values = await _context.FcbinCnfg.ToListAsync();
return Ok(values);
}
// POST api/values
// [Authorize(Policy = "RequireEditRole")]
[HttpPost("sitepost")]
public async Task<IActionResult> Post([FromBody] FcbinCnfg [] fcbincnfg)
{
if (fcbincnfg == null)
{
throw new ArgumentNullException(nameof(fcbincnfg));
}
string WindUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
string AppName = System.AppDomain.CurrentDomain.FriendlyName;
if (ModelState.IsValid)
{
int i = 0;
foreach (var obj in fcbincnfg){
_context.Update(fcbincnfg[i]);
i++;
}
await _context.SaveChangesAsync();
return StatusCode(201);
}
return BadRequest("this does not work");
}
Startup.cs file
namespace FcBin.API
{
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
var connection = #"Server=XXXXX\XXXX;Database=XXXXX;Trusted_Connection=True;";
services.AddDbContext<fujimiContext>(options => options.UseSqlServer(connection));
services.AddCors();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// app.UseHsts();
}
app.UseCors(x => x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
app.UseMvc();
}
}
}
Angular Route
import { Routes} from '#angular/router';
import { BinConfigComponent } from './BinConfig/BinConfig.component';
import { BinResolver } from './_resolver/bin.resolver';
export const appRoutes: Routes = [
{path: 'binconfig'
, component: BinConfigComponent
, resolve: {binconfig: BinResolver}, runGuardsAndResolvers: 'always'},
{path: '', redirectTo: 'binconfig', pathMatch: 'full', runGuardsAndResolvers: 'always'}
];
So the issue had nothing to do with my routes or the API. The issue was with my save() function in my Angular front end. I approached the function as a sequential problem when in actuality the browser/client approached my function from and efficiency stand point. Below is what I had that the browser would try to optimize
save() {
if (this.dirtyFlag) {
this.dbService.dbsave(this.binconfig).subscribe( () => {
}, error => {
console.log(error);
});
}
if (this.isFoo && this.valid) {
this.dbService.dbsavebin(this.newbin).subscribe( error => {
console.log(error);
});
} else if (!this.valid && this.isFoo) {
this.toastr.warning('Enter a Bin Id');
}
this.router.navigate(['/binconfig']);
}
Here I was having a route resolver reload the page which I triggered after a save. Tacking the route at the end of this save would result in the browser trying to optimize the POST / GET methods in the save() function and in the route resolver. I solved the issue by instead using the arrow function to execute the router navigation after a successful save.
save() {
if (this.dirtyFlag) {
this.dbService.dbsave(this.binconfig).subscribe( () => {
}, error => {
console.log(error);
}, () => {
this.router.navigate(['/binconfig']);
this.toastr.success('Changes Saved');
this.dirtyFlag = false;
});
}
if (this.isFoo && this.valid) {
this.dbService.dbsavebin(this.newbin).subscribe( () => {
this.router.navigate(['/binconfig']);
this.toastr.success('New Bin Added');
this.isFoo = false;
}, error => {
console.log(error);
});
} else if (!this.valid && this.isFoo) {
this.toastr.warning('Enter a Bin Id');
}
}
I'm working on building an API for handling identity stuff in .NET Core, but every time I try and make a call I get a 404.
There didn't seem to be anything clear when I looked around for an answer, since the code posted seems quite minimal. Here's all the stuff I think is pertinent.
The Controller:
using Common.Extensions;
using Identity.Database.Contexts.Models;
using Identity.WebApi.Models;
using Identity.WebApi.Models.Tokens;
using Identity.WebApi.Services.Access;
using Identity.WebApi.Services.Accounts;
using Identity.WebApi.Services.Tokens;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Controller = Microsoft.AspNetCore.Mvc.Controller;
using Get = Microsoft.AspNetCore.Mvc.HttpGetAttribute;
using Post = Microsoft.AspNetCore.Mvc.HttpPostAttribute;
using Route = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace Identity.WebApi.Controllers
{
[Route("api/[controller]")]
public class IdentityController : Controller
{
private readonly IApplicationUserService _userService;
private readonly IAccessService _accessService;
private readonly ITokenService _tokenService;
private readonly SignInManager<ApplicationUser> _signInManager;
public IdentityController(IApplicationUserService userService, IAccessService accessService, ITokenService tokenService, SignInManager<ApplicationUser> signInManager)
{
_userService = userService;
_accessService = accessService;
_tokenService = tokenService;
_signInManager = signInManager;
}
[Get]
[AllowAnonymous]
public string Index()
{
return new Dictionary<string,string>
{
{ "status", "live" }
}.Serialize();
}
[Post]
[Route("create")]
[AllowAnonymous]
public Task<ISet<IdentityResult>> Create(string user)
{
var decodedUser = DecodeUser(user);
var applicationUser = new ApplicationUser(new User
{
Id = Guid.NewGuid(),
Name = decodedUser.Username,
LastActive = DateTime.UtcNow
});
return _userService.Add(applicationUser, decodedUser.Password);
}
private (string Username, string Password) DecodeUser(string encodedUser)
{
var decodedUser = encodedUser.DecodeFrom64().Split(':');
return (Username: decodedUser[0], Password: decodedUser[1]);
}
private async Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
=> await _signInManager.UserManager.CheckPasswordAsync(user, password);
}
}
The Startup:
using Identity.Database.Contexts;
using Identity.Database.Contexts.Access;
using Identity.Database.Contexts.Extensions;
using Identity.Database.Contexts.Models;
using Identity.WebApi.Models;
using Identity.WebApi.Services.Access;
using Identity.WebApi.Services.Accounts;
using Identity.WebApi.Services.Certs;
using Identity.WebApi.Services.Tokens;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Runtime.CompilerServices;
namespace Identity.WebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new CertService(Configuration) as ICertService)
.AddTransient<IApplicationUserService, ApplicationUserService>()
.AddTransient<IApplicationRoleService, ApplicationRoleService>()
.AddTransient<IAccessService, AccessService>()
.AddTransient<ICertService, CertService>()
.AddTransient<ITokenService, TokenService>()
.AddTransient<ICrudDao<AppDbContext, Role>, RoleDao>()
.AddIdentities<ApplicationUser, ApplicationRole>()
.AddScoped<UserManager<ApplicationUser>, UserManager<ApplicationUser>>()
.AddScoped<SignInManager<ApplicationUser>, SignInManager<ApplicationUser>>()
.AddScoped<RoleManager<ApplicationRole>, RoleManager<ApplicationRole>>()
.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (c, n) =>
{
await n();
if (c.Response.StatusCode == 404)
{
c.Request.Path = "/identity";
await n();
}
});
app.UseStaticFiles();
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(r => { r.MapRoute(name: "default", template: "{controller=identity}/{action=Index}"); });
}
}
}
The launch settings:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55048/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/identity/index",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebApplication1": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/identity/index",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:55048/"
}
}
}
At the top of your controller you have:
[Route("api/[controller]")]
public class IdentityController : Controller
Which means that if your route starts with api/ only then it will match the controller. Also, your Index action doesn't have any extra routing attributes on it, so its looking for api/identity only. However, your launch settings don't match that part, and since you don't have any other routes matching it, you get a 404.
The default route in app.UseMvc won't work for this reason.
Simple fix: change launchUrl to just api/identity in your launch settings... and then follow #Nkosi's answer
If using attribute routing then there is no api/identity/index as [HttpGet] or Get in your example with a route prefix, is the same as
[Get] //<-- Matches GET api/identity
[AllowAnonymous]
public IActionResult Index() {
var result = new Dictionary<string,string>
{
{ "status", "live" }
}.Serialize();
return Ok(result);
}
And since this appears to be a Web API that is not expected to return a View then the Http{Verb} attribute with a route template would be the option to use for routing
When building a REST API, it's rare that you will want to use [Route(...)] on an action method. It's better to use the more specific Http*Verb*Attributes to be precise about what your API supports. Clients of REST APIs are expected to know what paths and HTTP verbs map to specific logical operations.
[Post("create")] //<-- Matches POST api/identity/create
[AllowAnonymous]
public async Task<IActionResult> Create(string user) {
var decodedUser = DecodeUser(user);
var applicationUser = new ApplicationUser(new User
{
Id = Guid.NewGuid(),
Name = decodedUser.Username,
LastActive = DateTime.UtcNow
});
ISet<IdentityResult> result = await _userService.Add(applicationUser, decodedUser.Password);
return Ok(result);
}
Reference Routing to Controller Actions