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
Related
I spent a good few hours rummaging around on the internet, tried various solutions, and unfortunately to no avail. The problem is that he wants to download data for the currently logged in user (his object or at least the identifier for retrieving the object). Unfortunately, with every possible solution I get null, I have no ideas what to do, and I would like to fix this problem. Any ideas? .NET 6.0 version, see title.
Hope we can solve the problem together. :)
Code:
Program.cs
using KursAspNetBackend.Database;
using KursAspNetBackend.Domain.Entities;
using KursAspNetBackend.Database.Repositories;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using KursAspNetBackend.Domain.Interfaces.Identity;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.Configure<IdentityOptions>(options => options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
//builder.Services.AddHttpContextAccessor();
///builder.Services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
//builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Adding Transients
builder.Services.AddTransient<IMessagesRepository, MessagesRepository>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
}
app.UseAuthentication();
//app.UseIdentityServer();
app.UseAuthorization();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true)
.AllowCredentials()); // allow credentials
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html"); ;
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
dbContext.Database.EnsureCreated();
}
//app.Services.GetRequiredService<ApplicationDbContext>().Database.EnsureCreated();
app.Run();
AccountController.cs:
using KursAspNetBackend.Domain.Entities;
using KursAspNetBackend.Domain.Dtos;
using KursAspNetBackend.Domain.Entities;
using KursAspNetBackend.Domain.Dtos;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace KursAspNetBackend.Controllers
{
[Route("account/")]
public class AccountController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IHttpContextAccessor _httpContextAccessor;
public AccountController(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IHttpContextAccessor httpContextAccessor)
{
_userManager = userManager;
_signInManager = signInManager;
_httpContextAccessor = httpContextAccessor;
}
[HttpGet]
[Route("getCurrentUser")]
public async Task<IActionResult> GetCurrentUser()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.GetUserAsync(_httpContextAccessor.HttpContext.User);
if(user == null)
{
return Unauthorized();
}
return Ok(user);
}
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register([FromBody] UserRegisterDto userRegisterDto)
{
var newUser = new ApplicationUser
{
Email = userRegisterDto.Email,
UserName = userRegisterDto.Email,
FirstName = userRegisterDto.FirstName,
LastName = userRegisterDto.LastName,
Address = "",
};
var result = await _userManager.CreateAsync(newUser, userRegisterDto.Password);
if (result.Succeeded)
{
var token = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
await _userManager.ConfirmEmailAsync(newUser, token);
return Ok();
}
else
{
foreach (IdentityError error in result.Errors)
Console.WriteLine($"Oops! {error.Description} ({error.Code}");
}
return NotFound();
}
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] UserLoginDto 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();
}
}
}
lounchSettings.json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:12761",
"sslPort": 0
}
},
"profiles": {
"KursAspNetBackend": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5054",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
}
}
}
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
i am trying to make an angular application that takes an api made from asp.net core but while making the api , it didn't work and appear as planned and didn't know where was the problem...
I made an asp.net core web app.
This is the student.cs file made in the model folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebAPI101.Model
{
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public bool Pass { get; set; }
}
}
This is the studentmanager also in model folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebAPI101.Model
{
public class StudentManager
{
public List<Student> GetResults()
{
List<Student> oList = new List<Student>();
var r = new Random();
for(int i = 0; i < 10; i++)
{
var x = new Student();
x.ID = i;
x.Name = String.Format("Name{0}", i, ToString());
x.Pass = (r.Next() % 2 == 0);
oList.Add(x);
}
return oList;
}
}
}
This is startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebAPI101
{
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.AddRazorPages();
}
// 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();
}
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.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}
This is the code that should return aaaaa
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebAPI101.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class StudentController : ControllerBase
{
[Route("mariam")]
public string test()
{
return "aaaaa";
}
}
}
I tried to launch the code and it didn't work as planned o the link:https://localhost:5001/api/Student/mariam
While running the application on the previous link it appeared like that:
browser status after running
This is the lanchsettings.json:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:40854",
"sslPort": 44361
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebAPI101": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "api/Student/mariam",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
The configuration is missing AddControllers and MapControllerRoute calls. Checkout the example below:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace WebAPI101
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// 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 void ConfigureServices(IServiceCollection services)
{
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.UseDefaultFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"api/{controller}/{action=Index}");
});
}
}
}
Lots of posts here on how to do this, but no matter what configuration I try, I can't seem to get my database connection string. The startup.cs is configured from the Microsoft project template for Core 2.2 automatically, and as far as I can tell there is nothing wrong with it. I'm not using EF nor do I wish to load some 3rd party black box to get this to work.
Here's the Startup.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace TestWebApplication1
{
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// 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.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Here's the appsettings.json file:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=mydbserver;Initial Catalog=mydatabase;Integrated Security=True;Persist Security Info=False;"
}
}
From another post, the following SHOULD work, but it does not:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TestAppWithService
{
public class TestDB
{
string conString = Microsoft.Extensions.Configuration.ConfigurationExtensions.GetConnectionString(this.Configuration, "DefaultConnection");
}
}
The file, called TestDB.cs is set to compile and, for kicks, I put it in the root folder (doesn't matter where I put the class: model, controller, etc)
I get the Keyword 'this' is not available in the current context. (with a squiggly line under it).
I have no idea how to proceed or what to look for and the answers here are numerous with all sorts of tweaks, but as per MS, this should work fine.
I'm new to dotnetcore and thought I had this dependency injection stuff figured out, but am still stuck.
This wont compile
public class TestDB
{
string conString = Microsoft.Extensions.Configuration.ConfigurationExtensions.GetConnectionString(this.Configuration, "DefaultConnection");
}
given the context under which it is trying to be used.
Access to IConfiguration should be restricted to the composition root, which in this case is Startup
Having to inject IConfiguration outside of the composition root can be seen as a code smell and the current self answer has some design issues that should be refactored.
Firstly, addressing the connection string issue, the following supporting abstractions and implementation should be introduced.
public class ConnectionStrings {
public string DefaultConnection { get; set; }
}
public interface IDbConnectionFactory {
IDbConnection Create(string connectionString);
}
public class SqlConnectionFactory : IDbConnectionFactory {
public IDbConnection Create(string connectionString) {
return new SqlConnection(connectionString);
}
}
public interface IDataProvider {
List<DropDownOption> CalcSelectDDSizeAndTilesPerBoxAll();
}
and the data class refactored to follow a more SOLID design approach
public class MyDataProvider : IDataProvider {
static string LastErrorMsg = string.Empty;
private readonly string connectionString;
private readonly IDbConnectionFactory connectionFactory;
public MyDataProvider(ConnectionStrings connections, IDbConnectionFactory connectionFactory) {
this.connectionString = connections.DefaultConnection;
this.connectionFactory = connectionFactory;
}
public List<DropDownOption> CalcSelectDDSizeAndTilesPerBoxAll() {
var options = new List<DropDownOption>();
try {
using (IDbConnection connection = connectionFactory.Create(connectionString)) {
using (IDbCommand command = connection.CreateCommand()) {
command.CommandText = "CalcSelectDDSizeAndTilesPerBoxAll";
command.CommandType = CommandType.StoredProcedure;
command.CommandTimeout = 30;
connection.Open();
using (IDataReader r = command.ExecuteReader(CommandBehavior.CloseConnection)) {
while (r.Read()) {
DropDownOption option = new DropDownOption {
value = r["SizeAndNumInBox"].ToString(),
text = r["Descr"].ToString()
};
options.Add(option);
}
}
LastErrorMsg = string.Empty;
}
}
} catch (Exception ex) {
LastErrorMsg = ex.Message;
//consider logging error
options = new List<DropDownOption>();
}
return options;
}
}
Note the explicit injection of the supporting ConnectionStrings and IDbConnectionFactory and how they affect the implementation of the target CalcSelectDDSizeAndTilesPerBoxAll function.
With that, all the supporting abstractions and implementations should be registered at startup
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => {
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
//bind object model
ConnectionStrings connections = Configuration.Get<ConnectionStrings>();
//add it to the service collection so that is accessible for injection
services.AddSingleton(connections);
//register connection factory
services.AddSingleton<IDbConnectionFactory, SqlConnectionFactory>();
//register data provider
services.AddSingleton<IDataProvider, MyDataProvider>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Finally the controller can depend only on what it actually needs to perform its function instead of having to act as a messenger and pass injected members along
public class ServicesController : Controller {
private readonly IDataProvider myData;
public ServicesController(IDataProvider myData) {
this.myData = myData;
}
public IActionResult Index() {
return View();
}
// service returning json for dropdown options fill for tile calculator
public IActionResult GetCalcDDOptions() {
var calcOptions = myData.CalcSelectDDSizeAndTilesPerBoxAll();
return Ok(calcOptions);
}
}
The default template from VS2019 (dotnetcore 2.2), the Startup.cs doesn't need any changes.
In a controller I added a couple of things:
using Microsoft.Extensions.Configuration;
In my controller class, I added:
private readonly IConfiguration configuration;
public ServicesController(IConfiguration config)
{
this.configuration = config;
}
I changed the method in the model class to accept the configuration as a parameter.
Here's what it looks like as called from the controller:
var calcOptions = MyData.CalcSelectDDSizeAndTilesPerBoxAll(this.configuration);
The complete controller code (for reference):
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using TestAppWithService.Models;
namespace TestAppWithService.Controllers
{
public class ServicesController : Controller
{
private readonly IConfiguration configuration;
public ServicesController(IConfiguration config)
{
this.configuration = config;
}
public IActionResult Index()
{
return View();
}
// service returning json for dropdown options fill for tile calculator
public IActionResult GetCalcDDOptions()
{
var calcOptions = MyData.CalcSelectDDSizeAndTilesPerBoxAll(this.configuration); //note: pass the config to the model
return new ObjectResult(calcOptions);
}
}
}
in the model, I added:
using Microsoft.Extensions.Configuration;
then to the method I added the connection info parameter:
public static List<DropDownOption> CalcSelectDDSizeAndTilesPerBoxAll(IConfiguration config)
and inside the method, to get the db connection string is simply:
string dbconn = config.GetConnectionString("DefaultConnection");
The complete code for the model:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Microsoft.Extensions.Configuration;
namespace TestAppWithService.Models
{
// This is for custom database functions for services
public class MyData
{
static string LastErrorMsg = string.Empty;
public static List<DropDownOption> CalcSelectDDSizeAndTilesPerBoxAll(IConfiguration config)
{
Boolean HasErrors = false;
var retval = new List<DropDownOption>();
string dbconn = config.GetConnectionString("DefaultConnection");
using (SqlConnection conn = new SqlConnection(dbconn))
{
using (SqlCommand cmd = new SqlCommand("CalcSelectDDSizeAndTilesPerBoxAll", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 30;
try
{
conn.Open();
using (SqlDataReader r = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
if (r.HasRows)
{
while (r.Read())
{
DropDownOption ddo = new DropDownOption();
ddo.value = r["SizeAndNumInBox"].ToString();
ddo.text = r["Descr"].ToString();
retval.Add(ddo);
}
}
}
LastErrorMsg = string.Empty;
}
catch (Exception ex)
{
LastErrorMsg = ex.Message;
HasErrors = true;
}
}
if (!HasErrors)
{
return retval;
}
else
{
return new List<DropDownOption>(); //just an empty list returned
}
}
}
}
}
And for kicks, here is the View w/javascript (Test Page) to consume the service:
#{
ViewData["Title"] = "Test";
}
<script type="text/javascript">
$(function () {
$("#btnFillDD").click(function () {
RetrieveCalcOptionsDD();
});
function RetrieveCalcOptionsDD() {
var ddl = $("#TilesInCartonBySize");
var oldEvent = ddl.attr("onchange");
ddl.attr("onchange", ""); //remove change event
$.ajax({
url: '../Services/GetCalcDDOptions',
dataType: 'json',
method: 'get',
success: function (retdata) {
ddl.empty();
$.each(retdata, function () {
ddl.append($("<option></option>").val(this['value']).html(this['text']));
});
},
error: function (err) {
console.log('Error (RetrieveCalcOptionsDD): ' + JSON.stringify(err, null, 2));
}
});
ddl.attr("onchange", oldEvent); //add change event back
};
});
</script>
<h1>Test</h1>
<p><button id="btnFillDD">Click Me</button></p>
<p>
<select id="TilesInCartonBySize" class="calcText" onchange="calculate(this.form);">
</select>
</p>
Note that this "service" is just a view that returns json (so you can use this for anything).
All is good.
There was no problem until I added a new controller to my ASP.NET Core (v2.1) Web API. Now, all my controllers are giving 404 error. The last added controller is UsersController. I'm not sure if I've changed something else.
UsersController.cs
using System.Threading.Tasks;
using DatingApp.API.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DatingApp.API.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IDatingRepository _repo;
public UsersController(IDatingRepository repo)
{
_repo = repo;
}
[HttpGet]
public async Task<IActionResult> GetUsers()
{
var users = await _repo.GetUsers();
return Ok(users);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _repo.GetUser(id);
return Ok(user);
}
}
}
launchSettings.json
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"DatingApp.API": {
"commandName": "Project",
"launchBrowser": false,
"launchUrl": "api/values",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, Seed seeder)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder => {
builder.Run(async context => {
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if(error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message);
}
});
// app.UseHsts();
});
// app.UseHttpsRedirection();
// seeder.SeedUsers();
app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseAuthentication();
app.UseMvc();
}
}
I've tried to restart Kestrel web server until now. Did not work for me.