We have different Customers and each of them own a Database.
the client send an Request with an HttpHeader DatabaseOwner:Samsung
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("DatabaseOwner", "Samsung");
In my ApiController i would like that my Context is based on that Value and connects to the correct Database
public class AuthController : ApiController
{
public CmpContext db { get; set; }
public AuthController(): base()
{
db = new CmpContext(VcsCmpConnectionstring.GetCMPConnectionString(this.Request));
}
// GET api/Auth
public IQueryable<DealerModel> GetDealer()
{
return db.Dealer;
}
public static string GetCMPConnectionString(HttpRequestMessage request)
{
string returnvalue;
if (request.Headers.Contains("DatabaseOwner"))
{
SqlConnectionStringBuilder ConnStringBuilder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["CMPContext"].ConnectionString);
ConnStringBuilder.InitialCatalog = "vcs_cmp_" + request.Headers.GetValues("DatabaseOwner").First();
returnvalue = ConnStringBuilder.ToString();
}
else
{
throw new MissingFieldException("Header DatabaseOwneris null");
}
return returnvalue;
}
When i try to get this.Request it is always null
When is Request access able or is there a better way to switch between Databases?
Greetings from Bavaria
Patrick
Request object is only initialized after APIController's Initialize function is called. So you cannot access Request before base.initialize is called. Try doing this:
public class AuthController : ApiController
{
protected override void Initialize(
System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
db = new CmpContext(
VcsCmpConnectionstring.GetCMPConnectionString(this.Request));
}
}
Related
For audit purposes I'm trying to get the current logged in user in my DbContext. However I'm having some issues with this. A few things to take into account:
In Blazor Server we have to use AddDbContextFactory
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
I created a custom DbContext that injects AuthenticationStateProvider.
public partial class CustomDbContext : DbContext
{
private AuthenticationStateProvider _authenticationStateProvider;
#region construction
public CustomDbContext ()
{
}
public CustomDbContext (AuthenticationStateProvider stateProvider)
{
_authenticationStateProvider = stateProvider;
}
[ActivatorUtilitiesConstructor]
public CustomDbContext (DbContextOptions<CustomDbContext> options, AuthenticationStateProvider stateProvider) : base(options)
{
_authenticationStateProvider = stateProvider;
}
public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options)
{
}
#endregion
...
In this DbContext, when overwriting the SaveChanges I get the User and their claims:
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
var userIdClaim = state.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value;
userId = userIdClaim != null && !string.IsNullOrEmpty(userIdClaim ) ? userIdClaim : string.Empty;
...
However when I call .CreateDbContext(); on the injected DbContextFactory, I get the following exception:
'Cannot resolve scoped service
'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'
from root provider.'
I've found some topics about this, but the suggested solution there is to create a custom DbContextFactory that is scoped. But then you lose the reason why you are using the DbContextFactory, no?
Any ideas on how to solve this?
Thank you
The DBContextFactory is a singleton registered in the root application DI container, while the AuthenticationStateProvider is a scoped service that is registered in the Hub session DI container. You can't access a lower order service from a higher order service.
You need to rethink your design and provide the user information from whatever scoped service is making whatever call to need a DbConbtext.
Additional Information
I'm not sure what your data pipeline looks like so this example uses the Blazor template weather forecast.
First a View Service that components inject and use.
This injects the AuthenticationStateProvider. It gets the current user for each request and passes it to the data pipeline in a request object.
public class WeatherForecastViewService
{
private AuthenticationStateProvider _authenticationStateProvider; // scoped service
private WeatherForecastService _weatherForecastService; //Singleton Service
public WeatherForecastViewService(AuthenticationStateProvider authenticationStateProvider, WeatherForecastService weatherForecastService)
{
_authenticationStateProvider = authenticationStateProvider;
_weatherForecastService = weatherForecastService;
}
public async ValueTask SaveWeatherForecast(WeatherForecast record)
{
var user = await GetCurrentUser();
var request = new RecordRequest<WeatherForecast>(record, user );
await _weatherForecastService.SaveRecord(request);
}
private async ValueTask<ClaimsPrincipal> GetCurrentUser()
{
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
return state.User ?? new ClaimsPrincipal();
}
}
Here are the request and result objects:
public readonly struct RecordRequest<TRecord>
{
public TRecord Record { get; init; }
public ClaimsPrincipal Identity { get; init; }
public RecordRequest(TRecord record, ClaimsPrincipal identity)
{
this.Record = record;
this.Identity = identity;
}
}
public record RecordResult
{
public bool SuccessState { get; init; }
public string Message { get; init; }
private RecordResult(bool successState, string? message)
{
this.SuccessState = successState;
this.Message = message ?? string.Empty;
}
public static RecordResult Success(string? message = null)
=> new RecordResult(true, message);
public static RecordResult Failure(string message)
=> new RecordResult(false, message);
}
And here's the singleton data service
public class WeatherForecastDataService
{
// This is a singleton
private readonly IDbContextFactory<DbContext> _factory;
public WeatherForecastDataService(IDbContextFactory<DbContext> factory)
=> _factory = factory;
public async ValueTask<RecordResult> SaveRecord(RecordRequest<WeatherForecast> request)
{
if (!request.Identity.IsInRole("SomeRole"))
return RecordResult.Failure("User does not have authority");
// simulates some async DB activity
await Task.Delay(100);
// Get your DbContext from the injected Factory
// using var dbContext = this.factory.CreateDbContext();
// do your db stuff
return RecordResult.Success();
}
}
PS I haven'y actually run this code so there may be some typos!
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
Nothing to do with whether IHttpContextAccessor is not thread safe... It's simply because the HttpContext object is not available in Blazor Server App, as communication between the client side (browser) and server side is done through the SignalR protocol, not HTTP. But there is a way how to access the HttpContext object before the Blazor App is rendered, as the initial call to the app is always made through HTTP request; that is, when you enter a url into the address bar of your browser and hit the enter button. See here how to do that...
The following code snippet describes how to inject an AuthenticationStateProvider into the ApplicationDbContext object created by default when you select Individual Accounts in Blazor Server App.
Copy and test. It should work...
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
public async Task<Employee> CreateEmployee(Employee employee)
{
CancellationTokenSource cancellationTokenSource = new
CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
// Created by the default template
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
// This is your code...
services.AddScoped<ApplicationDbContext>(p =>
p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>
().CreateDbContext());
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider,
RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
UPDATE:
but does that no against the the recommendations of Microsoft? They ae suggesting to always use using
var context = DbFactory.CreateDbContext();
You mean:
using var context = DbFactory.CreateDbContext();
No, it is not against the recommendations of Microsoft. It's another way to instantiate the DbContext. I did it that way in order to stick to this code by you:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
Anyhow, these are the changes you should make in order to reflect "Microsoft's recommendations"
Change:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
To:
services.AddScoped<ApplicationDbContext>();
Change:
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
To:
private readonly IDbContextFactory<ApplicationDbContext>
DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext>
_DbFactory)
{
DbFactory = _DbFactory;
}
And change:
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
To:
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
Also add:
using var context = DbFactory.CreateDbContext();
at the beginning of the EmployeeRepository.CreateEmployee method
Run and test.
Hope this work...
New Version
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly IDbContextFactory<ApplicationDbContext> DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext> _DbFactory)
{
DbFactory = _DbFactory;
}
public async Task<Employee> CreateEmployee(Employee
employee)
{
using var context = DbFactory.CreateDbContext();
// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
services.AddScoped<ApplicationDbContext>();
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
}
I am developing an Xamarin.Forms app in VS 2019. My REST API is hosted on GoDaddy.
When I call the api I get back my json converted object fine in my viewmodel. But the object is null
from my xaml page. See this code:
public class NewOrderViewModel : BaseViewModel
{
public NewOrderDetails NewOrderDetails { get; set; }
public ICommand OkCommand { get; private set;}
public ICommand CancelCommand { get; private set; }
readonly IPageService _pageService;
public NewOrderViewModel(IPageService pageService, int custId)
{
_pageService = pageService;
OkCommand = new Command(NewOrder);
CancelCommand = new Command(CancelOrder);
NewOrderDetails = new NewOrderDetails();
LoadNewOrderDetails(custId);
}
private async void LoadNewOrderDetails(int custId)
{
using (var client = new HttpClient(new System.Net.Http.HttpClientHandler()))
{
var response = await client.GetStringAsync("http://api.lates.com.au/api/Customers/" + custId.ToString());
var customer = JsonConvert.DeserializeObject<Customer>(response);
await _pageService.DisplayAlert("Value", customer.CustomerName, "OK"); //This confirms the correct customer is returned.
NewOrderDetails.CustomerName = customer.CustomerName;
foreach (var cd in customer.CustomerDepartments)
{
NewOrderDetails.CustomerDepartments.Add(cd);
}
NewOrderDetails.OrderDate = DateTime.Today;
NewOrderDetails.DeliveryDate = DateTime.Today;
NewOrderDetails.CustomerId = custId;
}
}
private void NewOrder()
{
_pageService.PopAsync();
_pageService.PushModalAsync(new CustomerOrder());
}
private void CancelOrder()
{
_pageService.PopAsync();
}
}
public partial class NewOrder : ContentPage
{
public NewOrder()
{
InitializeComponent();
imgAddIcon.Source = FileImageSource.FromFile("AddDocument64By64.png");
}
protected override void OnAppearing()
{
BindingContext = new NewOrderViewModel(new PageService(), 1);
//If i put a break point here the NewOrderDetails property of NewOrderViewModel is null - WHY???
}
}
It seems to be something to do with asynchronous timing. Let me know if you need more info.
Malcolm
If i put a break point here the NewOrderDetails property of
NewOrderViewModel is null - WHY???
At that time your break point hit, the data in NewOrderDetails has not be set because the httpRequest is still requesting and you have to await the request finish to get the data from Api.
To solve your problem, you have to implement INotifyPropertyChanged in both NewOrderDetails and NewOrderViewModel to notify the View update value after you get the data from Api. I will give you some code snippets:
In NewOrderDetails :
public class NewOrderDetails : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public NewOrderDetails()
{
}
public string CustomerName
{
set
{
if (customerName != value)
{
customerName = value;
OnPropertyChanged("CustomerName");
}
}
get
{
return customerName;
}
}
string customerName { get; set; }
}
In NewOrderViewModel :
public class NewOrderViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public NewOrderDetails NewOrderDetaila
{
set
{
if (newOrderDetails != value)
{
newOrderDetails = value;
OnPropertyChanged("NewOrderDetaila");
}
}
get
{
return newOrderDetails;
}
}
NewOrderDetails newOrderDetails { get; set; }
public NewOrderViewModel( int custId)
{
NewOrderDetaila = new NewOrderDetails();
LoadNewOrderDetails(custId);
}
private async void LoadNewOrderDetails(int custId)
{
//...
NewOrderDetaila.CustomerName = "133";
//...
}
}
And in Xaml binding:
<Label Text="{Binding NewOrderDetaila.CustomerName}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
Try and let me know if it works for you.
One problem in your code is here:
using (var client = new HttpClient(new System.Net.Http.HttpClientHandler()))
{
var response = await client.GetStringAsync("http://api.lates.com.au/api/Customers/" + custId.ToString());
var customer = JsonConvert.DeserializeObject<Customer>(response);
await _pageService.DisplayAlert("Value", customer.CustomerName, "OK"); //This confirms the correct customer is returned.
NewOrderDetails.CustomerName = customer.CustomerName;
foreach (var cd in customer.CustomerDepartments)
{
NewOrderDetails.CustomerDepartments.Add(cd);
}
NewOrderDetails.OrderDate = DateTime.Today;
NewOrderDetails.DeliveryDate = DateTime.Today;
NewOrderDetails.CustomerId = custId;
}
HttpClient should be defined as static class, and reused during your application lifetime. Disposing and recreating HttpClient leads to socket errors. Your code is causing multiple requests. I suggest also move this method to Task, that returns the object.
Example method:
internal class SendData
{
private static HttpClient client = new HttpClient();
internal static async Task<string> MakeServerRequest(string url, string content)
{
try
{
var request = new StringContent(content, Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, request);
var response = await result.Content.ReadAsStringAsync();
return response;
}
catch (Exception ex)
{
YOUR ADDITIONAL LOGIC HERE
return null;
}
}
}
This will return JSON string that you can serialize to object, and do whatever your app requires.
So for school I got the task to make an API so you can login on a database and after you logged in, you can access some data. To login, we're using AspNetUsers made from another project that uses MVC. My current code:
AccountController.cs
namespace Klantenzone.API2.Controllers
{
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private AuthRepository _repo = null;
public AccountController()
{
_repo = new AuthRepository();
}
}
}
WebApiConfig.cs
namespace Klantenzone.API2
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var jsonFomatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFomatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
}
AuthContext.cs
namespace Klantenzone.API2.Internal
{
public class AuthContext : IdentityDbContext<ApplicationUser>
{
public AuthContext()
: base("DefaultConnection")
{
}
}
}
AuthRepository.cs
namespace Klantenzone.API2.Internal
{
public class AuthRepository : IDisposable
{
private AuthContext _ctx;
private UserManager<ApplicationUser> _userManager;
public AuthRepository()
{
_ctx = new AuthContext();
}
public async Task<ApplicationUser> FindUser(string userName, string password)
{
UserStore<ApplicationUser> userStore = new UserStore<ApplicationUser>(_ctx);
_userManager = new UserManager<ApplicationUser>(userStore);
//_userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(_ctx));
//IdentityUser user = await _userManager.FindAsync(userName, password);
ApplicationUser user2 = await _userManager.FindAsync(userName, password);
return user2;
}
public void Dispose()
{
_ctx.Dispose();
_userManager.Dispose();
}
}
}
BeginAPI.cs
[assembly: OwinStartup("APIOwin",typeof(Klantenzone.API2.Internal.BeginAPI))]
namespace Klantenzone.API2.Internal
{
public class BeginAPI
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
//Token generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
}
However, every time I try to log in using this API (I use Postman to test it), I get the error: The entity type ApplicationUser is not part of the model for the current context (in AuthRepository). So far I've done all I could. The connectionstring is not based on the edmx of the database. I'm using ApplicationUser because the AspNetUsers tables were made from MVC. Yet it still doesn't want to work. Anyone who could help me out? I'd appreciate it.
I want to use IOC with my service and I want to instead inject a class not an interface in the constructor as below in the services layer but I do not want to create a new object from the calling layer like var service = new InvoiceService(new ChangeInvoiceDueDateCommand()) instead I want to create something like this from my controller in MVC where the IInvoiceService is injected into the controller constructor but the problem I see is that
public InvoiceController(IInvoiceService invoiceService, IMapper mapper)
{
_invoiceService = invoiceService;
_mapper = mapper;
}
and then called like this
public ActionResult ChangeInvoiceDueDate(InvoiceChangeDueDateViewModel invoiceChangeDueDateViewModel )
{
var request = _mapper.Map<InvoiceChangeDueDateViewModel, ChangeInvoiceDuedateRequest>(invoiceChangeDueDateViewModel);
InvoiceChangeDueDateResponse response = _invoiceService.ChangeDueDate(request);
return View();
}
Service Layer
public class InvoiceService : IInvoiceService
{
private readonly ChangeInvoiceDueDateCommand _changeInvoiceDueDateCommand;
public InvoiceService(ChangeInvoiceDueDateCommand changeInvoiceDueDateCommand)
{
_changeInvoiceDueDateCommand = changeInvoiceDueDateCommand;
}
public InvoiceChangeDueDateResponse ChangeDueDate(ChangeInvoiceDuedateRequest invoiceChangeDueDateRequest)
{
_changeInvoiceDueDateCommand.Execute(invoiceChangeDueDateRequest);
return new InvoiceChangeDueDateResponse {Status = new Status()};
}
}
Command
public class ChangeInvoiceDueDateCommand : ICommand<ChangeInvoiceDuedateRequest>
{
private readonly IRepository<Invoice> _invoiceRepository;
readonly InvoiceDueDateChangeValidator _validator;
public ChangeInvoiceDueDateCommand(IRepository<Invoice> invoiceRepository)
{
_invoiceRepository = invoiceRepository;
_validator = new InvoiceDueDateChangeValidator();
}
public void Execute(ChangeInvoiceDuedateRequest request)
{
if (_validator.IsDuedateValid(request.NewDuedate))
{
Invoice invoice = _invoiceRepository.GetById(request.Id);
invoice.ChangedDueDate(request.NewDuedate);
_invoiceRepository.SaveOrUpdate(invoice);
}
else
{
throw new InvalidDueDateException();
}
}
}
ICommand
public interface ICommand<T> where T : IRequest
{
void Execute(T request);
}
IRequest
public interface IRequest
{
int Id { get; set; }
}
I worked it out. It was just a Windsor syntax issue. It ended up being as simple as registering the Command using the container.Register(Component.For<ChangeInvoiceDueDateCommand>());
The situation
I'm building a web application using the in the title mentioned techniques. This application will something like a CMS system for multiple clients. The client has to login to this system using his company name and login credentials.
With the provided company name, I connect to a database (static DbContext, same connection string every time) where all clients database information is stored and search for this clients specific database(every client has his own with exact same design) login information. That all works fine.
Now here is the tricky part. To continue the login procedure I need to somehow inject or lazy load the repository using the other DbContext with a connection string that is build up from the result of the other database.
What I have
2 DbContexts generated from an existing database, one static and one if possible dynamic.
Then the generic repository classes/interfaces:
public interface IRepository
{
void Submit();
}
public interface IRepository<TEntity, TContext> : IRepository
where TEntity : class
where TContext : DbContext
{
//crud stuff
}
public abstract class GenericRepository<TEntity, TContext> : IRepository<TEntity, TContext>
where TEntity : class
where TContext : DbContext
{
private TContext _dataContext;
private IUnitOfWork _unitOfWork;
private readonly IDbSet<TEntity> dbset;
protected GenericRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_unitOfWork.Register(this);
}
}
Unit of work class/interface
public interface IUnitOfWork
{
void Register(IRepository repository);
void Commit();
}
public class UnitOfWork : IUnitOfWork
{
private readonly Dictionary<string, IRepository> _repositories;
private HttpContextBase _httpContext;
public UnitOfWork(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public void Register(IRepository repository)
{
_repositories.Add(repository.GetType().Name, repository);
}
public void Commit()
{
_repositories.ToList().ForEach(x => x.Value.Submit());
}
}
Then a context/entity specific repository
public class EmployeeRepository : GenericRepository<tbl_Medewerker, CustomerDbEntities>, IEmployeeRepository
{
public EmployeeRepository(IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
}
public interface IEmployeeRepository : IRepository<tbl_Medewerker, CustomerDbEntities>
{
}
Then the service that implements the repository
public interface IEmployeeLoginService
{
tbl_Medewerker GetEmployeeByLogin(string username, string password);
tbl_Medewerker GetEmployeeByID(Guid id);
}
public class EmployeeLoginService : IEmployeeLoginService
{
private readonly IEmployeeRepository _employeeRepository;
public EmployeeLoginService(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
public tbl_Medewerker GetEmployeeByLogin(string username, string password)
{
return _employeeRepository.Get(e => e.MedewerkerNaam.ToLower() == username.ToLower() && e.Password == password);
}
public tbl_Medewerker GetEmployeeByID(Guid id)
{
return _employeeRepository.GetById(id);
}
}
Finally the controller that implements that service and uses it in the login action
public class AccountController : BaseController
{
IConnectionService _connectionService;
IEmployeeLoginService _employeeService;
public AccountController(IConnectionService connectionService, IEmployeeLoginService employeeService)
{
_connectionService = connectionService;
_employeeService = employeeService;
}
[AllowAnonymous, HttpPost]
public ActionResult Login(LoginModel login)
{
if ((Settings)Session["Settings"] == null)
{
Settings settings = new Settings();
settings.company = _connectionService.GetCompanyName(login.CompanyName);
if (settings.company != null)
{
settings.licence = _connectionService.GetLicenceByCompanyID(settings.company.Company_id);
if (settings.licence != null)
{
settings.connectionStringOrName = string.Format(#"Data Source={0};Initial Catalog={1};User ID={2};Password={3};Application Name=EntityFrameworkMUE", settings.licence.WS_DatabaseServer, settings.licence.WS_DatabaseName, settings.licence.WS_DatabaseUID, settings.licence.WS_DatabasePWD);
Session["Settings"] = settings;
settings.user = _employeeService.GetEmployeeByLogin(login.UserName, login.Password);
if (settings.user != null)
{
FormsAuthentication.SetAuthCookie(string.Format("{0},{1}", settings.company.Company_id.ToString(), settings.user.Medewerker_ID.ToString()) , login.RememberMe);
return RedirectToAction("index", "home");
}
}
}
}
else
{
return RedirectToAction("index", "home");
}
return View();
}
}
And of course the autofac bootstrapper:
private static void SetAutoFacContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterType(typeof(UnitOfWork)).As(typeof(IUnitOfWork)).InstancePerHttpRequest();
builder.RegisterAssemblyTypes(typeof(UserRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().InstancePerHttpRequest();
builder.RegisterAssemblyTypes(typeof(ConnectionService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerHttpRequest();
builder.Register(c => new HttpContextWrapper(HttpContext.Current)).As<HttpContextBase>().InstancePerLifetimeScope();
builder.RegisterModule(new AutofacWebTypesModule());
builder.Register(att => new AuthorizeFilter(att.Resolve<IConnectionService>(), att.Resolve<IEmployeeLoginService>())).AsAuthorizationFilterFor<Controller>().InstancePerHttpRequest();
builder.RegisterFilterProvider();
IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
My idea how to do this, is setting a session variable with the connection string after data retrieval from the one static database where the info is stored and inject session in the unit of work and somehow use it there, but I can't wrap my head around it.
The question(s):
Am I heading in the right direction trying to achieve this, or even is it possible? If not what steps would you take to achieve this
I know it's a long read I hope you guys can help me, I'm quite new to using these techniques all together. Thanks in advance - I really appreciate it!
Your on the right track, I have used
var mtc = new MultitenantContainer(container.Resolve<ITenantIdentificationStrategy>(), container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(mtc));
The identification strategy would be based on the logged in user. With defaults for when they aren't logged in.
public class CompanyNameIdentificationStrategy : ITenantIdentificationStrategy
{
public bool TryIdentifyTenant(out object tenantId)
{
var context = HttpContext.Current;
if(context != null)
{
var myUser = context.User as MyUserObject;
if(myUser != null)
{
tenantId = myUser.CompanyName;
return true;
}
}
return false;
}
}
Then you add to your autofact setup:
var s = c.Resolve<ITenantIdentificationStrategy>();
object id;
if (s.TryIdentifyTenant(out id) && id != null)
{
return id;
}
return "default";
}).Keyed<string>("CompanyName");
builder.Register<Settings>(c =>
{
var companyName = c.ResolveKeyed<string>("companyName");
if (companyName == "default")
{
return new DefaultSettings();
}
var settings = new Settings();
return settings;
}).InstancePerLifetimeScope();
You can resolve stuff inside these code blocks. I would probably setup a keyed default settings, and then when the user is logged in the settings would switch to their setup and the rest of the application should work.