.net Core 3 / API / DI / Repository pattern - api

I'm trying to create a web API in .net Core. I would like this API calls a DLL with repository design pattern implemented in it. In this whole thing I tried to use dependency injection but I have some issues to manage context for database in repository.
I would like than the context one new context per lifetime of a call to the API.
When I try to execute my code I have an exception at line CreateHostBuilder(args).Build().Run(); in Main.
This Exception is :
'Some services are not able to be constructed'
InvalidOperationException : Unable to resolve service for type 'BX_Security_AccessBase.Context.SecurityContext' while attempting to activate 'BX_Security_AccessBase.Repository.UsersRepository'.
I know the code is incomplete and won't work completely but at least it should break way later than actually. I think I made a mistake in the architecture.
There is a lot of code below but I couldn't isolate my problem.
Thank you everybody.
In my API I have :
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IUsersRepository, UsersRepository>();
services.AddScoped<IUserService, UserService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
User.cs
public class User
{
public User() {}
public int UserId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime Birthdate { get; set; }
}
UserService.cs
public class UserService : IUserService
{
private readonly AppSettings _appSettings;
private readonly IUsersRepository _userRepository;
public UserService(IOptions<AppSettings> appSettings, IUsersRepository userRepository)
{
_appSettings = appSettings.Value;
_userRepository = userRepository;
}
public IEnumerable<User> GetAll()
{
return _userRepository.GetAllUsers().Select(u=> new User());
}
}
IUserService.cs
public interface IUserService
{
public IEnumerable<User> GetAll();
}
AppSettings.cs
public class AppSettings
{
public string Secret { get; set; }
}
UsersController.cs
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private IUserService _userService { get; }
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
}
In the DLL I have :
SecurityContext.cs
public partial class SecurityContext : DbContext
{
public SecurityContext(DbContextOptions<SecurityContext> options) : base(options) { }
public DbSet<Users> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Data Source=; Database=BXDB; User Id=sa; Password=;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Users>(entity =>
{
entity.HasKey(e => e.UserId).HasName("PK_User_UserId");
entity.ToTable("Users", "sec");
entity.Property(e => e.Birthdate).HasColumnType("date");
entity.Property(e => e.FirstName)
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.LastName)
.HasMaxLength(50)
.IsUnicode(false);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
Users.cs
public class Users
{
public Users() { }
public int UserId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime Birthdate { get; set; }
}
IUsersRepository.cs
public interface IUsersRepository
{
public IQueryable<Users> GetAllUsers();
}
UsersRepository.cs
public class UsersRepository : IUsersRepository
{
public readonly SecurityContext _dbContext;
public UsersRepository(SecurityContext dbContext)
{
_dbContext = dbContext;
}
public IQueryable<Users> GetAllUsers()
{
return _dbContext.Users;
}
}

'Some services are not able to be constructed' InvalidOperationException : Unable to resolve service for type 'BX_Security_AccessBase.Context.SecurityContext' while attempting to activate 'BX_Security_AccessBase.Repository.UsersRepository'.
From the error , you should register the DbContext as a service as follows:
public void ConfigureServices(IServiceCollection services)
{
var connection = #"Server=(localdb)\mssqllocaldb;Database=BXDB;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<DLL.Models.SecurityContext>(options => options.UseSqlServer(connection, x => x.MigrationsAssembly("DLL")));
services.AddControllers();
services.AddScoped<IUsersRepository, UsersRepository>();
services.AddScoped<IUserService, UserService>();
}

Related

Custom role based auth .NET CORE

I have a project in which users can have multiple roles, such as cashier and stock clerk. These roles have the same rights, however someone can also have the roles admin and cashier. In this case he can access more features than a admin/cashier on its own.
I've searched far and wide but I don't get any wiser from the documentation, as I first thought policies were the way to go, but now I think we need claim-based authorization.
After searching and playing around I found no answers on the following questions:
What tables/entities do I need?
Can this be done without scaffolding tools?
How does this whole process work, how does .NET CORE know what roles to look at? How can I use custom roles?
If someone could help me out with this I would appreciate it.
Cheers.
One way is to use Identity and authorize the user by using [Authorize(Roles ="Admin")].
If you want to do without scaffolding tools,you could use jwt token authentication or cookie authentication.
Here is a simple demo about how to use cookie authentication:
Model:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public List<UserRole> UserRoles { get; set; }
}
public class Role
{
public int Id { get; set; }
public string RoleName { get; set; }
public List<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int UserId { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
}
public class LoginModel
{
public string Name { get; set; }
public string Password { get; set; }
}
Controller:
public class HomeController : Controller
{
private readonly YouDbContext _context;
public HomeController(YouDbContext context)
{
_context = context;
}
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(LoginModel model)
{
var claims = new List<Claim>{};
var user = _context.User
.Include(u=>u.UserRoles)
.ThenInclude(ur=>ur.Role)
.Where(m => m.Name == model.Name).FirstOrDefault();
if(user.Password==model.Password)
{
foreach(var role in user.UserRoles.Select(a=>a.Role.RoleName))
{
var claim = new Claim(ClaimTypes.Role, role);
claims.Add(claim);
}
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties{};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
return View("Index");
}
public IActionResult Index()
{
return View();
}
//allow Cashier
[Authorize(Roles = "Cashier")]
public IActionResult Privacy()
{
return View();
}
//allow Admin
[Authorize(Roles = "Admin")]
public IActionResult AllowAdmin()
{
return View();
}
//allow both of the Admin and Cashier
[Authorize(Roles = "Admin,Cashier")]
public IActionResult AllowBoth()
{
return View();
}
//user has no rights to access the page
public IActionResult AccessDenied()
{
return View();
}
//log out
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index");
}
}
DbContext:
public class YouDbContext: DbContext
{
public YouDbContext(DbContextOptions<YouDbContext> options)
: base(options)
{
}
public DbSet<User> User { get; set; }
public DbSet<Role> Role { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserRole>()
.HasKey(bc => new { bc.UserId, bc.RoleId });
modelBuilder.Entity<UserRole>()
.HasOne(bc => bc.User)
.WithMany(b => b.UserRoles)
.HasForeignKey(bc => bc.UserId);
modelBuilder.Entity<UserRole>()
.HasOne(bc => bc.Role)
.WithMany(c => c.UserRoles)
.HasForeignKey(bc => bc.RoleId);
}
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Home/Login";
options.AccessDeniedPath = "/Home/AccessDenied";
});
services.AddDbContext<WebApplication1Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WebApplication1Context")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Result:

Asp.net Core 2 migration not working correctly

I am developing an API project with asp.net core 2 and ef core 2. when I am doing migration every time it wants to drop all tables and then create, but I want only changes will be included.
public class TicketingContext : DbContext
{
public TicketingContext(DbContextOptions<TicketingContext> options) : base(options)
{
Database.Migrate();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<TZone> TZones { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<Point> Points { get; set; }
public DbSet<Bus> Buses { get; set; }
}
Here is my startup class and DesignTimeDbContextFactory
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, TicketingContext context)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
And here is
DesignTimeDbContextFactory implementation
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<TicketingContext>
{
public TicketingContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<TicketingContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new TicketingContext(builder.Options);
}
}
Edit Configure method in your Startup.cs. Add your TicketingContext as a parameter:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, TicketingContext context)
{
...
context.Database.Migrate();
}

asp.net core 2 Access httpcontext from scoped DI service accessed through hangfire task

I have an asp.net core2 application with hangfire. Configuration works fine for basic tasks, however now I need to enqueue a background job which accesses the dependency injected httpcontext and dbcontext and I am getting null reference exceptions for httpcontext - I understand why this would be, but...
Can I configure hangfire's enqueue such that the httpcontext and dbcontext from which the task is fired are included with the job? The job is always originally fired from within a controller where the contexts are available. The job is a method on ApprovalService which has _userservice injected into its constructor. _Userservice has the httpContext injected in its constructor.
As I understand it, the graph should be able to resolve this, it is just a question of how...
I dont want to refactor to pass these as arguments as the services are used elsewhere where they do have access to the contexts.
The
My startup is as follows (a lot of things removed for clarity)
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment _env;
public static string connection;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc(config => {
config.Filters.Add(new AuthorizeFilter(authorizePolicy));
config.OutputFormatters.OfType<StringOutputFormatter>().Single().SupportedMediaTypes.Add("text/html");
})
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
connection = Configuration.GetConnectionString("(default)");
services.AddDbContext<CpContext>(options =>
{
options.UseSqlServer(connection);
});
services.AddHangfire(configuration => configuration
.UseSqlServerStorage(connection));
services.AddScoped<IApprovalService, ApprovalService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<SystemControlService>();
services.AddScoped<ProjectControlService>();
services.AddIdentity<CpIdentityUser, IdentityRole>().AddUserManager<cpUserManager>();
services.AddScoped<ApprovalService>();
services.AddTransient<IEmailService, EmailService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
Log.Information("In configure");
_env = env;
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseBrowserLink();
}
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
app.UseAuthentication();
app.UseMvc();
app.UseStaticFiles();
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new CustomAuthorizeFilter() }
});
app.UseHangfireServer();
}
public class CustomAuthorizeFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
var httpcontext = context.GetHttpContext();
return httpcontext.User.Identity.IsAuthenticated;
}
}
}
I fire the job like this (non job version commented and works - hangfire job hangs on nullreference when trying to get userID from the context);
[Produces("application/json")]
[Route("api/Approvals")]
public class ApprovalsController : Controller
{
private readonly CpContext _context;
private IUserService _userService;
private IBackgroundJobClient _backgroundJobClient;
private ApprovalService _approvalService;
public ApprovalsController(CpContext context, IUserService userService, ApprovalService approvalService, IBackgroundJobClient backgroundJobClient)
{
_context = context;
_userService = userService;
_approvalService = approvalService;
_backgroundJobClient = backgroundJobClient;
}
public class approvalWrapper
{
public int ApprovalId { get; set; }
public List<string> emailTo { get; set; }
public List<string> ccTo { get; set; }
public string ManualApprCode { get; set; }
public int RequestToId { get; set; }
public DateTime RequestDate { get; set; }
public DateTime RequiredDate { get; set; }
public DateTime ResponseDate { get; set; }
public string RequestText { get; set; }
public string ResponseText { get; set; }
public int ApprovalStatusTypeId { get; set; }
public int ApprovalItemTypeId { get; set; }
public int? NcrLinkId { get; set; }
public int? LotItpDetailLinkId { get; set; }
public int? LotQtyLinkId { get; set; }
}
// POST: api/Approvals/sendRequest
[HttpPost("sendRequest")]
public async Task<IActionResult> sendRequest([FromBody] approvalWrapper approvalInfo)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
Approval approval = new Approval()
{
RequestById = _userService.User_ID,
RequestToId = approvalInfo.RequestToId,
RequestDate = approvalInfo.RequestDate,
RequiredDate = approvalInfo.RequiredDate,
RequestText = approvalInfo.RequestText,
NcrLinkId = approvalInfo.NcrLinkId,
LotItpDetailLinkId = approvalInfo.LotItpDetailLinkId,
LotQtyLinkId = approvalInfo.LotQtyLinkId,
ApprovalItemTypeId = approvalInfo.ApprovalItemTypeId,
ApprovalStatusTypeId = 5,
};
try
{
_context.Approval.Add(approval);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Error(ex, "Error trying to create approval.");
return StatusCode(422);
}
_backgroundJobClient.Enqueue<IApprovalService>(serv => serv.sendRequestEmailAsync(approval.ApprovalId, approvalInfo.emailTo, approvalInfo.ccTo));
//await _approvalService.sendRequestEmailAsync(approval.ApprovalId, approvalInfo.emailTo, approvalInfo.ccTo);
return Ok(1);
}
}
interface IApprovalService
{
Task<string> getApprovalRequestTextForChecklistItem(int checklistItemId);
Task<string> getApprovalRequestTextForNCR(int NCRId);
Task<bool> sendRequestEmailAsync(int apprToRequestID, List<string> emailTo = null, List<string> ccTo = null);
Task<bool> sendResponseEmailAsync(int apprToRequestID, List<string> emailTo = null, List<string> ccTo = null);
Task<bool> isApprovalCodeValidAsync(string qryString, int apprToRequestID);
}
public class ApprovalService: IApprovalService
{
CpContext _context;
IEmailService _emailService;
private ProjectControlService _projectControlService;
private SystemControlService _systemControlService;
private IUserService _userService;
public ApprovalService(CpContext context, IEmailService emailService, SystemControlService systemControlService,
ProjectControlService projectControlService, IUserService userService)
{
_context = context;
_emailService = emailService;
_userService = userService;
_systemControlService = systemControlService;
_projectControlService = projectControlService;
}
public interface IUserService
{
int Project_ID { get; }
int User_ID { get; }
Task<UserCredDto> AuthenticateAsync(string username, string password);
HashSet<string> getUserPermsForProject(int userID, int ProjectID);
IEnumerable<User> GetAll();
Task<User> GetByIdAsync(int id);
Task<User> GetUserAsync();
Task<User> CreateUserAsync(User user, string password);
Task UpdateAsync(User user, string password = null);
Task<User> DeleteAsync(int id);
bool Exists(int id);
string checkRefreshToken(string refreshToken, UserCredDto tokenOwner, int refreshLifeTime);
Task<string> getNewRefreshTokenAsync(UserCredDto tokenOwner, int refreshLifeTime = 60);
string GetUserName();
Task<UserDto> GetUser();
ClaimsPrincipal GetClaimsPrincipal();
}
public class UserService : IUserService
{
private CpContext _context;
private readonly IHttpContextAccessor _httpcontext;
public UserService(CpContext context, IHttpContextAccessor httpcontext)
{
_context = context;
_httpcontext = httpcontext;
}
}

How to use FluentValidation.AspNetCore and FluentValidation.MVC6?

How to use FluentValidation.AspNetCore and FluentValidation.MVC6 to validate Entities in AspNetCore , can anyone give me an example ?
This is working for me:
project.json add:
"FluentValidation.AspNetCore": "6.4.0-beta3"
startup.cs
services
.AddMvc()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
Validation:
public class Foo
{
public string Bar {get; set;}
}
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => x.Bar).NotEmpty().WithMessage("Error Message");
}
}
first you need to add nuget Install-Package FluentValidation.AspNetCore
you can have an action filter which will handle validation:
public class ValidatorActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
filterContext.Result = new BadRequestObjectResult(filterContext.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
Startup.ConfigureServices looks like this to add auto validation and add FluentValidation:
services.AddMvc(opt =>
{
opt.Filters.Add(typeof(ValidatorActionFilter));
}).AddFluentValidation(fvc =>
fvc.RegisterValidatorsFromAssemblyContaining<Startup>())
If you need to read another assembly, name one of its classes instead of startUp
Now you can add a validation to ensure:
public class CreateCustomer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class CreateCustomerValidator : AbstractValidator<CreateCustomer>
{
public CreateCustomerValidator()
{
RuleFor(x => x.FirstName).NotNull().WithMessage(Resource.Validaton.FirstNameRequired);
RuleFor(x => x.LastName).NotNull().WithMessage(Resource.Validaton.LastNameRequired);
RuleFor(x => x.Email).Matches(#"\A(?:[a-z0-9!#$%&'*=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z").WithMessage(Resource.Validaton.EmailFormat);
}
}

RavenDB UniqueConstraint doesn't seem to work

I've been trying for a day to get UniqueConstraint working, but it doesn't seem the are. I have a simple MVC6 site that creates a User on a POST. I'm expecting that on the second POST an exception should be thrown as a user will have already been created with the same properties. I'm wanting to ensure that the email address is unique.
using Raven.Client;
using Raven.Client.Document;
using Raven.Client.UniqueConstraints;
namespace MVC6Test.DomainModel
{
public class User
{
public string Id { get; private set; }
[UniqueConstraint]
public string Email { get; set; }
public string Password { get; set; }
public string Name { get; set; }
}
}
namespace MVC6Test.Web.Controllers
{
public class AdminController : Microsoft.AspNet.Mvc.Controller
{
private IDocumentStore _documentStore { get; set; }
public IDocumentSession Session { get; set; }
[HttpPost]
[AllowAnonymous]
[Route("login")]
public async Task<IActionResult> Login(string userName, string password)
{
User user = new User() {
Email = "test#gmail.com"
};
Session.Store(user);
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (_documentStore.IsDefault()) {
_documentStore = context.HttpContext.RequestServices.GetRequiredService<IDocumentStore>();
}
Session = _documentStore.OpenSession();
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
using (Session) {
if (Session != null && context.Exception == null) {
Session.SaveChanges();
}
}
base.OnActionExecuted(context);
}
}
}
namespace MVC6Test.Web
{
public class Startup
{
private IDocumentStore DocumentStore;
public void ConfigureServices(IServiceCollection services)
{
DocumentStore = new DocumentStore {
DefaultDatabase = "MVC6Test",
Url = "http://localhost:3366"
};
DocumentStore.Listeners.RegisterListener(new UniqueConstraintsStoreListener());
DocumentStore.Initialize();
services.TryAddSingleton(typeof(IDocumentStore), (provider) => {
return DocumentStore;
});
}
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime)
{
lifetime.ApplicationStopped.Register(() => {
DocumentStore.Dispose();
});
}
}
}
I do get this metadata on the items that are created:
{
"Raven-Entity-Name": "Users",
"Raven-Clr-Type": "MVC6Test.DomainModel.User, MVC6Test",
"Ensure-Unique-Constraints": [
{
"Name": "Email",
"CaseInsensitive": false
}
]
}