I get this exception from time to time :
The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'. Method Message:, LogException: System.InvalidOperationException: The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'.
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader1.GetValue(DbDataReader reader, Int32 ordinal)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper1.SimpleEnumerator.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at Project.Services.UserService.FindById(Int64 userId)
I'm using Asp.net Identity in MVC project.
My User class like :
public class User : IdentityUser<long, IdentityConfig.UserLogin, IdentityConfig.UserRole, IdentityConfig.UserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(IdentityConfig.CustomUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[MaxLength(256)]
[Index(IsUnique = true)]
[Required]
public override string Email { get; set; }
[Required]
[MaxLength(256)]
public string FirstName { get; set; }
// rest of properties
....
}
UserManager :
public class CustomUserManager : UserManager<User, long>
{
public CustomUserManager(IUserStore<User, long> store, IdentityFactoryOptions<CustomUserManager> options) : base(store)
{
this.UserValidator = new UserValidator<User, long>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
PasswordValidator = new PasswordValidator
{
RequiredLength = 8,
RequireLowercase = true,
RequireUppercase = true,
RequireDigit = true
};
// Configure user lockout defaults
UserLockoutEnabledByDefault = true;
DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
RegisterTwoFactorProvider("Google Authentication", new GoogleAuthenticatorTokenProvider());
var provider = new MachineKeyProtectionProvider();
UserTokenProvider = new DataProtectorTokenProvider<User,long>(provider.Create("ResetPasswordPurpose"));
}
}
UserService:
public class UserService : EntityService<User>, IUserService
{
private readonly IdentityConfig.CustomUserManager _userManager;
public UserService(MyDbContext context, IdentityConfig.CustomUserManager userManager) : base(context)
{
_userManager = userManager;
}
public User FindById(long userId)
{
return _userManager.Users.FirstOrDefault(x => x.Id == userId);
}
// other methods..
}
Register Autofac:
builder.RegisterModule(new ServiceModule());
builder.RegisterModule(new EfModule());
builder.RegisterType<IdentityConfig.RoleStore>().As<IRoleStore<IdentityConfig.Role, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserStore>().As<IUserStore<User, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomSignInManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomRoleManager>().AsSelf().InstancePerRequest();
builder.Register<IAuthenticationManager>(c => HttpContext.Current.GetOwinContext().Authentication);
builder.Register(c => new IdentityFactoryOptions<IdentityConfig.CustomUserManager>
{
DataProtectionProvider = new DpapiDataProtectionProvider("MyWebAppName"),
Provider = new IdentityFactoryProvider<IdentityConfig.CustomUserManager>()
}).InstancePerRequest();
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("Project.Services"))
.Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
public class EfModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType(typeof(MyDbContext)).AsSelf().WithParameter("connectionString", ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString).InstancePerRequest();
}
}
What I noticed also is this error affect some of other entities not just the user !
The problem is the application runs for some time and then gives this kind of errors too much, which does not make any sense to me and makes me mad.
I'm using Azure SQL , Azure web services, Autofac.
Same issue here. It happens on medium to high demand. I don't know what to do anymore. I'm recycling 5x/day.
I couldn't find any standard. The exception throws in many different methods. No standard. Looks completely random.
It seems that i'm trying to retrieve an information on DB and it always returns blank data, so an error is thrown when it tries to cast the null data to the model.
Related
I am developing asp.net core 3.1 GraphQL based APIs. I used the below reference article to setup automatic DI configuration in the API layer and have used the nuget package : NetCore.AutoRegisterDi
https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/
Here goes the code details:
Code:
Startup.cs:
public virtual void ConfigureServices(IServiceCollection services) => services
.AddGraphQLResolvers()
.AddProjectRepositories();
ProjectServiceCollectionExtensions.cs
public static class ProjectServiceCollectionExtensions
{
public static IServiceCollection AddProjectRepositories(this IServiceCollection services) =>
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
.Where(c => c.Name.EndsWith("Persistence"))
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
public static IServiceCollection AddGraphQLResolvers(this IServiceCollection services) =>
services
.AddScoped<ICountriesResolver, CountriesResolver>()
.AddScoped<ICountryGroupsResolver, CountryGroupsResolver>()
.AddScoped<IDisclaimerResolver, DisclaimerResolver>();
}
Here in the above CommonService is part of the Service Layer that ends with Persistence.
CountriesResolver.cs
public class CountriesResolver : Resolver, ICountriesResolver
{
private readonly ICountryService _countryService;
private readonly IHttpContextAccessor _accessor;
private readonly IDataLoaderContextAccessor _dataLoaderContextAccessor;
public CountriesResolver(ICountryService countryService, IHttpContextAccessor accessor, IDataLoaderContextAccessor dataLoaderContextAccessor)
{
_countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
_accessor = accessor;
_dataLoaderContextAccessor = dataLoaderContextAccessor;
}
public void Resolve(GraphQLQuery graphQLQuery)
{
var language = _accessor.HttpContext.Items["language"] as LanguageDTO;
graphQLQuery.FieldAsync<ResponseGraphType<CountryResultType>>("countriesresponse", arguments: new QueryArguments(new QueryArgument<IdGraphType>{Name = "pageNo", Description = "page number"}, new QueryArgument<IdGraphType>{Name = "pageSize", Description = "page size"}), resolve: async context =>
{
var pageNo = context.GetArgument<int>("pageNo") == 0 ? 1 : context.GetArgument<int>("pageNo");
var pageSize = context.GetArgument<int>("pageSize") == 0 ? 100 : context.GetArgument<int>("pageSize");
if (language != null)
{
var loader = _dataLoaderContextAccessor.Context.GetOrAddLoader("GetAllCountries", () => _countryService.GetAllCountriesAsync(language, pageNo, pageSize));
var list = await context.TryAsyncResolve(async c => await loader.LoadAsync());
return Response(list);
}
return null;
}
, description: "All Countries data");
}
}
ICommonService.cs
using Author.Query.Persistence.DTO;
using System.Threading.Tasks;
namespace Author.Query.Persistence.Interfaces
{
public interface ICommonService
{
LanguageDTO GetLanguageFromLocale(string locale);
Task<LanguageDTO> GetLanguageFromLocaleAsync(string locale);
}
}
CommonService.cs
namespace Author.Query.Persistence
{
public class CommonService : ICommonService
{
private readonly AppDbContext _dbContext;
private readonly IOptions<AppSettings> _appSettings;
private readonly IMapper _mapper;
private readonly ICacheService<Languages, LanguageDTO> _cacheService;
public CommonService(AppDbContext dbContext, IOptions<AppSettings> appSettings, IMapper mapper, ICacheService<Languages, LanguageDTO> cacheService)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_appSettings = appSettings;
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
}
//public Languages GetLanguageFromLocale(string locale)
public LanguageDTO GetLanguageFromLocale(string locale)
{
return GetLanguagesByFilter(GetFilterValues(locale, true).ToArray());
}
}
}
ICountryService.cs
namespace Author.Query.Persistence.Interfaces
{
public interface ICountryService
{
Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize);
Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId);
}
}
CountryService.cs
namespace Author.Query.Persistence
{
public class CountryService : ICountryService
{
private readonly AppDbContext _dbContext;
private readonly IOptions<AppSettings> _appSettings;
private readonly ICacheService<Images, ImageDTO> _cacheService;
public CountryService(TaxathandDbContext dbContext, IOptions<AppSettings> appSettings, ICacheService<Images, ImageDTO> cacheService)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
_appSettings = appSettings;
}
public async Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize)
{
var localeLangId = language.LanguageId;
var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
// By default pick the localLanguage value
var countries = await GetAllCountriesDataAsync(localeLangId, pageNo, pageSize);
// If localLanguage data is not available then pull the data based on default language
if (countries.Countries.Count == 0)
{
countries = await GetAllCountriesDataAsync(dftLanguageId, pageNo, pageSize);
}
return countries;
}
public async Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId)
{
var localeLangId = language.LanguageId;
var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
//var country = new CountryDTO();
// By default pick the localLanguage value
var country = await GetCountryDetailsAsync(countryId, localeLangId);
// If localLanguage data is not available then pull the data based on default language
if (country == null)
{
country = await GetCountryDetailsAsync(countryId, dftLanguageId);
}
return country;
}
private async Task<CountryDTO> GetCountryDetailsAsync(int countryId, int languageId)
{
var images = await _cacheService.GetAllAsync("imagesCacheKey");
var country = await _dbContext.Countries.AsNoTracking().FirstOrDefaultAsync(c => c.CountryId.Equals(countryId) && c.IsPublished.Equals(true) && c.LanguageId.Equals(languageId));
if (country == null)
{
return null;
}
var countryDTO = new CountryDTO{Uuid = country.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.SVGImageId)).FilePath, DisplayName = country.DisplayName, DisplayNameShort = country.DisplayName, Name = Helper.ReplaceChars(country.DisplayName), Path = Helper.ReplaceChars(country.DisplayName), CompleteResponse = true};
return countryDTO;
}
private async Task<CountryResult> GetAllCountriesDataAsync(int languageId, int pageNo, int pageSize)
{
var countryList = new CountryResult();
var images = await _cacheService.GetAllAsync("imagesCacheKey");
var countries = await _dbContext.Countries.Where(cc => cc.IsPublished.Equals(true) && cc.LanguageId.Equals(languageId)).Select(c => new
{
c.CountryId, c.DisplayName, c.PNGImageId, c.SVGImageId
}
).OrderByDescending(c => c.CountryId).Skip((pageNo - 1) * pageSize).Take(pageSize).AsNoTracking().ToListAsync();
if (countries.Count == 0)
{
return null;
}
countryList.Countries.AddRange(countries.Select(co => new CountryDTO{Uuid = co.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.SVGImageId)).FilePath, DisplayName = co.DisplayName, DisplayNameShort = co.DisplayName, Name = Helper.ReplaceChars(co.DisplayName), Path = Helper.ReplaceChars(co.DisplayName), CompleteResponse = true}));
return countryList;
}
}
}
Error:
System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountriesResolver':
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.) (Error while validating the service descriptor 'ServiceType:
Author.Query.New.API.GraphQL.Resolvers.ICountryGroupsResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryGroupService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver'.) (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.IDisclaimerResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.IDisclaimerService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at Author.Query.New.API.Program.Main(String[] args) in /src/QueryStack/Author.Query.New.API/Program.cs:line 15
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType:
Author.Query.New.API.GraphQL.Resolvers.CountriesResolver':
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.
Inner Exception 2:
InvalidOperationException: Unable to resolve service for type
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.
Can anyone help me to know how to fix this issue?
The error says that :
Unable to resolve service for type
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'
ICountryService is never registered. AddGraphQLResolvers registers only ICountriesResolver, ICountryGroupsResolver and IDisclaimerResolver.
The method AddProjectRepositories only registers classes whose name ends in Persistence that appear in the same namespace as CommonService. It never registers CommonService itself.
The service must be registered, eg with :
services.AddScoped<ICommonService, CommonService>();
Your method AddProjectRepositories() does not register CountryService, because the Where filter in RegisterAssemblyPublicNonGenericClasses() looks at classes (Type), not at namespaces.
So perhaps you can change your filter:
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
.Where(c => c.Name.EndsWith("Service")) // <-- Change "Persistence" to "Service"
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
That should register all classes that end with "Service" in the Assembly that contains CommonService.
For me it was a simple mistake. I had a class with the same name in another namespace and referenced the wrong class/forgot to delete the duplicate class and add the correct one to my startup.
I had a service called UserService in myapp.Utils and another in myapp.Services. I was referencing myapp.Utils when I meant to delete that one and only use myapp.Services. I was incorrectly injecting the one in myapp.Utils when my controllers were set up to use the one in myapp.Services.
I'm developing multitenant application. I use separate databases for each tenant. UserData are for each tenant in separate database TOO.
My problem is how can i create admin account for each tenant in "custom" database independently on DI. In MVC 5 was possible to instantiate UserManager base on UserStore(connection string). But UserManager in mvc6 depends on HttpContext... No documentation found...
Exist please some way how to do it??? I need in mvc 6 something like this in mvc 5:
UserStore<TenantUser> store = new UserStore<TenantUser>(new TenantDbContext("CONNECTION STRING")); //!!! NO POSSIBLE CREATE USER IN CUSTOM DATABASE
UserManager<TenantUser> t = new UserManager<TenantUser>(store);
t.CreateAsync(user, password);
Update:
public class TenantDbContext : IdentityDbContext<TenantUser, TenantRole, Guid>
{
private string _connectionString { get; set; }
private readonly IHttpContextAccessor _contextAccessor;
private readonly ApplicationDbContext _applicationDbContext;
//THIS SUB UNCOMENT ONLY IF CREATE MIGRATIONS (dnx ef...)
/*
public TenantDbContext(DbContextOptions<TenantDbContext> options) : base(options)
{
this._connectionString = "CONNECTION STRING";
}
*/
public TenantDbContext(DbContextOptions<TenantDbContext> options, IHttpContextAccessor contextAccessor, ApplicationDbContext applicationDbContext) : base(options) {
_contextAccessor = contextAccessor;
_applicationDbContext = applicationDbContext;
TenantResolver resolver = new TenantResolver(_contextAccessor, _applicationDbContext);
string con = resolver.GetConnectionString();
if (con != string.Empty)
{
this._connectionString = con; }
else
{
this._connectionString = "CONNECTION STRING"; //Development connection string
}
}
public TenantDbContext() //Posibility to create TenantDbContext migration and development database with no connectionString in constructor
{
//this._connectionString = "CONNECTION STRING";
}
public TenantDbContext(string ConnectionString)
{
this._connectionString = ConnectionString;
}
public static TenantDbContext Create(string ConnectionString)
{
return new TenantDbContext(ConnectionString);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
I hope that I correctly understand you. Let us we forget about the performance and the caching of multiple connections, which already opened to the databases. I suppose that you have multiple databases, which have the same schema. You need to access the databases using (sharing) the same database context.
I can suggest you two solutions.
The first solution consists from registering one context and reopening it if the one opened co change the connection string.
Let us you have TenantDbContext, which could be opened with different destination databases. For example with
#"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;"
or
#"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;"
First of all you remove OnConfiguring like
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=TenantDb;Trusted_Connection=True;");
}
which could exist in the definition of TenantDbContext and you use the following code in ConfigureServices of Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var connection1 = #"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TenantDbContext>(options => options.UseSqlServer(connection1));
services.AddMvc();
...
}
In the way you inject TenantDbContext with one from the database (TenantDb1). Let us the TenantDbContext contains some entity set like Blog for example. Thus your can define some MVC controller in the following way
public class TenantsController : Controller
{
private TenantDbContext _context;
public TenantsController (TenantDbContext context)
{
_context = context;
}
public IActionResult Index() {
var con = _context.Database.GetDbConnection();
// now the con uses either TenantDb2 or TenantDb2
// con.ConnectionString can be used to get or set the
// connection string
string needConStr = #"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;";
if (con.ConnectionString != needConStr) { // can be compared more carefully
_context.Database.CloseConnection();
con.ConnectionString = needConStr;
}
// get some data from the TenantDbContext
var blog = _context.Blog.ToList();
return View(blog);
}
}
The second solution don't need to inject any TenantDbContext using DependencyInjection. Instead of that you need just add one simple constructor to TenantDbContext:
public TenantDbContext(DbContextOptions optionsBuilder): base (optionsBuilder)
{
}
Such simple constructor will allows you to create the context at any time when you need it:
public class TenantsController : Controller
{
public IActionResult Index() {
var contextOptions = new DbContextOptionsBuilder();
contextOptions.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;");
var context = new BloggingContext(contextOptions.Options);
context.Database.OpenConnection();
// get some data from the TenantDbContext
var blog = context.Blog.ToList();
return View(blog);
}
}
I used all the connection strings directly in the code. You can easy modify the above code to get all connection strings from the config file appsettings.json.
Solved.
1. CreateCustomUserStore
public class TenantUserStore : UserStore<TenantUser, TenantRole, TenantDbContext, Guid>
{
public TenantUserStore(TenantDbContext context, IdentityErrorDescriber describer = null): base(context, describer)
{
}
}
And here is code how to instantiate UserManager with custom database:
IUserStore<TenantUser> CustomStore = new TenantUserStore(new TenantDbContext(coonection), null);
UserManager<TenantUser> manager = new UserManager<TenantUser>(CustomStore, _optionsAccessor, _passwordHasher, _userValidators,
_passwordValidators, _keyNormalizer, _errors, _services, _logger, _contextAccessor);
And DI used only for rest of UserManager Constructor:
public class TenantsController : Controller
{
private readonly IHttpContextAccessor _contextAccessor;
private readonly IOptions<IdentityOptions> _optionsAccessor;
private readonly IPasswordHasher<TenantUser> _passwordHasher;
private readonly IEnumerable<IUserValidator<TenantUser>> _userValidators;
private readonly IEnumerable<IPasswordValidator<TenantUser>> _passwordValidators;
private readonly ILookupNormalizer _keyNormalizer;
private readonly IdentityErrorDescriber _errors;
private readonly IServiceProvider _services;
private readonly ILogger<UserManager<TenantUser>> _logger;
public TenantsController(IHttpContextAccessor contextAccessor,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TenantUser> passwordHasher,
IEnumerable<IUserValidator<TenantUser>> userValidators,
IEnumerable<IPasswordValidator<TenantUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<TenantUser>> logger
)
{
_optionsAccessor = optionsAccessor;
_passwordHasher = passwordHasher;
_userValidators = userValidators;
_passwordValidators = passwordValidators;
_keyNormalizer = keyNormalizer;
_errors = errors;
_services = services;
_logger = logger;
_contextAccessor = contextAccessor;
}
We have a multi-database solution and are passing the connection string to a factory function like so:
container.Register<IDbContextFactory>(
f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
new PerScopeLifetime());
ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).
public class ClientConfig
{
private static ConcurrentDictionary<string, string> ConnectionStringManager
{
get;
set;
}
// etc.
}
My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.
Edit : The question was initially tagged with Autofac
With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :
public class ConnectionStringParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
valueProvider = null;
if (pi.ParameterType == typeof(String)
&& String.Equals(pi.Name, "connectionString",
StringComparison.OrdinalIgnoreCase))
{
valueProvider = () =>
{
// get connectionstring based on HttpContext.Current.Request.Url.Host
return String.Empty;
};
}
return valueProvider != null;
}
}
Then register your Parameter using a Module
public class ConnectionStringModule : Autofac.Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += registration_Preparing;
}
private void registration_Preparing(Object sender, PreparingEventArgs e)
{
Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() };
e.Parameters = e.Parameters.Concat(parameters);
}
}
Module you have to register inside your container using
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.
By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.
public interface IConnectionstringProvider
{
String ConnectionString { get; }
}
public class ConnectionStringProvider : IConnectionstringProvider
{
public ConnectionStringProvider(Strong host)
{
// get connectionstring based on host
this._connectionString = String.Empty;
}
private readonly String _connectionString;
public String ConnectionString
{
get { return this._connectionString; }
}
}
Inside your Parameter you will have to change the valueProvider by
valueProvider = () =>
{
return context.Resolve<IConnectionstringProvider>().ConnectionString;
};
And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
}
private static void container_ChildLifetimeScopeBeginning(
Object sender, LifetimeScopeBeginningEventArgs e)
{
String host = HttpContext.Current.Request.Url.Host;
ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
.As<IConnectionstringProvider>()
.SingleInstance();
childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
}
}
Of course there is many way to do it but you have the idea
In my ASP.NET MVC app, I'm trying to create a custom HttpContent.User object. I've started by creating a Member class, which implements IPrincioal.
public class Member : IPrincipal
{
public string Id { get; set; }
public IIdentity Identity { get; set; }
public bool IsInRole(string role) { throw new NotImplementedException(); }
...
}
Then at authentication time I set HttpContext.User to an instance of a Member class:
FormsAuthentication.SetAuthCookie(email, false);
HttpContext.User = member;
Then later I want to check if the user is authenticated, like so:
if (User.Identity.IsAuthenticated) { ... }
That's where I'm stuck. I'm not sure what I need to do for the public IIdentity Identity property on the instance of the Member. So that I can use the HttpContext.User object something like this:
IsAuthenticated = HttpContext.User.Identity.IsAuthenticated;
ViewBag.IsAuthenticated = IsAuthenticated;
if (IsAuthenticated) {
CurrentMember = (Member)HttpContext.User;
ViewBag.CurrentMember = CurrentMember;
}
A Principal is not something you can just set once when writing the auth cookie and forget later. During subsequent requests, the auth cookie is read and the IPrincipal / IIdentity is reconstructed before executing an action method. When that happens, trying to cast the HttpContext.User to your custom Member type will throw an exception.
One option would be to intercept in an ActionFilter, and just wrap the standard implementation.
public class UsesCustomPrincipalAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var systemPrincipal = filterContext.HttpContext.User;
var customPrincipal = new Member(systemPrincipal)
{
Id = "not sure where this comes from",
};
filterContext.HttpContext.User = customPrincipal;
}
}
public class Member : IPrincipal
{
private readonly IPrincipal _systemPrincipal;
public Member(IPrincipal principal)
{
if (principal == null) throw new ArgumentNullException("principal");
_systemPrincipal = principal;
}
public string Id { get; set; }
public IIdentity Identity { get { return _systemPrincipal.Identity; } }
public bool IsInRole(string role)
{
return _systemPrincipal.IsInRole(role);
}
}
This way, you're not losing anything that comes out of the box with the default IPrincipal and IIdentity implementations. You can still invoke IsAuthenticated on the IIdentity, or even IsInRole(string) on the IPrincipal. The only thing you're gaining is the extra Id property on your custom IPrincipal implementation (though I'm not sure where this comes from or why you need it).
I am writing unit tests for a project in ASP.NET MVC 1.0 using Moq and MvcContrib TestHelper classes. I have run into a problem.
When I come to Roles.AddUserToRole in my AccountController, I get a System.NotSupportedException. The Roles class is static and Moq cannot mock a static class.
What can I do?
You could use a pattern like DI (Dependency Injection). In your case, I would pass a RoleProvider to the AccountController, which would be the default RoleProvider by default, and a mock object in your tests. Something like:
public class AccountController
{
private MembershipProvider _provider;
private RoleProvider roleProvider;
public AccountController()
: this(null, null)
{
}
public AccountController(MembershipProvider provider, RoleProvider roleProvider)
{
_provider = provider ?? Membership.Provider;
this.roleProvider = roleProvider ?? System.Web.Security.Roles.Provider;
}
}
The MVC runtime will call the default constructor, which in turn will initialize the AccountController with the default role provider. In your unit test, you can directly call the overloaded constructor, and pass a MockRoleProvider (or use Moq to create it for you):
[Test]
public void AccountControllerTest()
{
AccountController controller = new AccountController(new MockMembershipProvider(), new MockRoleProvider());
}
EDIT: And here's how I mocked the entire HttpContext, including the principal user.
To get a Moq version of the HttpContext:
public static HttpContextBase GetHttpContext(IPrincipal principal)
{
var httpContext = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
var user = principal;
httpContext.Setup(ctx => ctx.Request).Returns(request.Object);
httpContext.Setup(ctx => ctx.Response).Returns(response.Object);
httpContext.Setup(ctx => ctx.Session).Returns(session.Object);
httpContext.Setup(ctx => ctx.Server).Returns(server.Object);
httpContext.Setup(ctx => ctx.User).Returns(user);
return httpContext.Object;
}
A mock implementation of Principal:
public class MockPrincipal : IPrincipal
{
private IIdentity _identity;
private readonly string[] _roles;
public MockPrincipal(IIdentity identity, string[] roles)
{
_identity = identity;
_roles = roles;
}
public IIdentity Identity
{
get { return _identity; }
set { this._identity = value; }
}
public bool IsInRole(string role)
{
if (_roles == null)
return false;
return _roles.Contains(role);
}
}
A MockIdentity:
public class MockIdentity : IIdentity
{
private readonly string _name;
public MockIdentity(string userName) {
_name = userName;
}
public override string AuthenticationType
{
get { throw new System.NotImplementedException(); }
}
public override bool IsAuthenticated
{
get { return !String.IsNullOrEmpty(_name); }
}
public override string Name
{
get { return _name; }
}
}
And the magic call:
MockIdentity identity = new MockIdentity("JohnDoe");
var httpContext = MoqHelpers.GetHttpContext(new MockPrincipal(identity, null));
Note that I edited the code above to leave out some custom stuff, but I'm quite sure this should still work.
Now I run into another problem when I try to test the ChangePassword() method in ASP.NET MVC.
try
{
if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword))
{
if (!TempData.ContainsKey("ChangePassword_success"))
{
TempData.Add("ChangePassword_success", true);
}
return PartialView("ChangePassword");
}
Now I get that User is null, when I reach this line. In my testclass I have:
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
I thought that this would work, but it doesn't care for that I send "johndoe". And If I were to mock IPrincipal, the User property is readonly.
TypeMock Isolator does mocking of statics etc. But I second (and +1'd) Razzie's answer.
I have done what you coded, but I still get that User is null when it reaches:
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
In my Testclass I have:
//Arrange (Set up a scenario)
var mockMembershipService = new Mock<IMembershipService>();
MockIdentity identity = new MockIdentity("JohnDoe");
var httpContext = MoqHelpers.GetHttpContext(new MockPrincipal(identity, null));
var controller = new AccountController(null, mockMembershipService.Object, null, null, null);
string currentPassword = "qwerty";
string newPassword = "123456";
string confirmPassword = "123456";
// Expectations
mockMembershipService.Setup(pw => pw.MinPasswordLength).Returns(6);
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
Do I call my cp.ChangePassword with wrong parameters? And should MVCContrib Testhelpers classes be able to mock Http context and so on? I just can't find info for how to setup User.Identity.Name with MVCContrib.
I have used this from a tutorial to test something (mock) session:
var builder = new TestControllerBuilder();
var controller = new AccountController(mockFormsAuthentication.Object, mockMembershipService.Object, mockUserRepository.Object, null, mockBandRepository.Object);
builder.InitializeController(controller);
EDIT: I have come a little further:
MockIdentity identity = new MockIdentity("JohnDoe");
var httpContext = MoqHelpers.GetHttpContext(new MockPrincipal(identity, null));
var controller = new AccountController(null, mockMembershipService.Object, null, null, null);
controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);
but my now I can't get my cp.ChangePassword in the expectation to return true:
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
I am sending "johndoe" string, because, it requires a string as a parameter for User.Identity.Name, but it doesn't return true.