This post shows the code necessary to make ASP.NET Core Identity work with NHibernate and Postgres. Including the external logins such as Facebook, Google, Twitter.
Working code can be downloaded from
https://github.com/MichaelBuen/AspNetCoreExample
Important codes:
AspNetCoreExample.Infrastructure/_DDL.txt
AspNetCoreExample.Ddd/IdentityDomain/User.cs
AspNetCoreExample.Ddd/IdentityDomain/Role.cs
AspNetCoreExample.Identity/Data/UserStore.cs
AspNetCoreExample.Identity/Data/RoleStore.cs
AspNetCoreExample.Identity/Startup.cs
Database:
create schema identity;
create extension citext;
create table identity.user
(
id int generated by default as identity primary key,
user_name citext not null,
normalized_user_name citext not null,
email citext,
normalized_email citext,
email_confirmed boolean not null,
password_hash text,
phone_number text,
phone_number_confirmed boolean not null,
two_factor_enabled boolean not null,
security_stamp text,
concurrency_stamp text,
lockout_end timestamp with time zone,
lockout_enabled boolean not null default false,
access_failed_count int not null default 0
);
create table identity.external_login
(
user_fk int not null references identity.user(id),
id int generated by default as identity primary key,
login_provider text not null,
provider_key text not null,
display_name text not null
);
create unique index ix_identity_user__normalized_user_name ON identity.user (normalized_user_name);
create unique index ix_identity_user__normalized_email ON identity.user (normalized_email);
create table identity.role
(
id int generated by default as identity primary key,
name citext not null,
normalized_name citext not null,
concurrency_stamp text
);
create table ix_identity_role__normalized_name ON identity.role (normalized_name);
CREATE TABLE identity.user_role
(
user_fk int not null references identity.user(id),
role_fk int not null references identity.role(id),
primary key (user_fk, role_fk)
);
DDD Models (user and role)
Identity User model:
namespace AspNetCoreExample.Ddd.IdentityDomain
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
public class User : IdentityUser<int>
{
/// <summary>
/// One-to-many to external logins
/// </summary>
/// <value>The external logins.</value>
public IEnumerable<ExternalLogin> ExternalLogins { get; protected set; } = new Collection<ExternalLogin>();
/// <summary>
/// Many-to-many between Users and Roles
/// </summary>
/// <value>The roles.</value>
public IEnumerable<Role> Roles { get; protected set; } = new Collection<Role>();
public User(string userName) : base(userName) { }
public User(string userName, string email)
{
this.UserName = userName;
this.Email = email;
}
public void AddExternalLogin(string loginProvider, string providerKey, string providerDisplayName)
{
var el = new ExternalLogin(this)
{
LoginProvider = loginProvider,
ProviderKey = providerKey,
DisplayName = providerDisplayName
};
this.ExternalLogins.AsCollection().Add(el);
}
public async Task RemoveExternalLoginAsync(string loginProvider, string providerKey)
{
var externalLogin =
await this.ExternalLogins.AsQueryable()
.SingleOrDefaultAsyncOk(el => el.LoginProvider == loginProvider && el.ProviderKey == providerKey);
if (externalLogin != null)
{
this.ExternalLogins.AsCollection().Remove(externalLogin);
}
}
public async Task<IList<string>> GetRoleNamesAsync() =>
await this.Roles.AsQueryable().Select(r => r.Name).ToListAsyncOk();
public async Task AddRole(Role roleToAdd)
{
var isExisting = await this.Roles.AsQueryable().AnyAsyncOk(role => role == roleToAdd);
if (!isExisting)
{
this.Roles.AsCollection().Add(roleToAdd);
}
}
public async Task RemoveRoleAsync(string roleName)
{
string normalizedRoleName = roleName.ToUpper();
var role =
await this.Roles.AsQueryable()
.Where(el => el.NormalizedName == normalizedRoleName)
.SingleOrDefaultAsyncOk();
if (role != null)
{
this.Roles.AsCollection().Remove(role);
}
}
public async Task<bool> IsInRole(string roleName) =>
await this.Roles.AsQueryable()
.AnyAsyncOk(role => role.NormalizedName == roleName.ToUpper());
public void SetTwoFactorEnabled(bool enabled) => this.TwoFactorEnabled = enabled;
public void SetNormalizedEmail(string normalizedEmail) => this.NormalizedEmail = normalizedEmail;
public void SetEmailConfirmed(Boolean confirmed) => this.EmailConfirmed = confirmed;
public void SetPhoneNumber(string phoneNumber) => this.PhoneNumber = phoneNumber;
public void SetPhoneNumberConfirmed(Boolean confirmed) => this.PhoneNumberConfirmed = confirmed;
public void SetPasswordHash(string passwordHash) => this.PasswordHash = passwordHash;
public void SetEmail(string email) => this.Email = email;
public void SetNormalizedUserName(string normalizedUserName) => this.NormalizedUserName = normalizedUserName;
public void SetUserName(string userName) => this.UserName = userName;
public void UpdateFromDetached(User user)
{
this.UserName = user.UserName;
this.NormalizedUserName = user.NormalizedUserName;
this.Email = user.Email;
this.NormalizedEmail = user.NormalizedEmail;
this.EmailConfirmed = user.EmailConfirmed;
this.PasswordHash = user.PasswordHash;
this.PhoneNumber = user.PhoneNumber;
this.PhoneNumberConfirmed = user.PhoneNumberConfirmed;
this.TwoFactorEnabled = user.TwoFactorEnabled;
}
public async static Task<User> FindByLoginAsync(
IQueryable<User> users, string loginProvider, string providerKey
) =>
await users.SingleOrDefaultAsyncOk(au =>
au.ExternalLogins.Any(el => el.LoginProvider == loginProvider && el.ProviderKey == providerKey)
);
public async Task<IList<UserLoginInfo>> GetUserLoginInfoListAsync() =>
await this.ExternalLogins.AsQueryable()
.Select(el =>
new UserLoginInfo(
el.LoginProvider,
el.ProviderKey,
el.DisplayName
)
)
// The cache of a user's external logins gets trashed when another user updates his/her external logins.
// Explore how to make collection caching more robust. Disable for the meantime.
// .CacheableOk()
.ToListAsyncOk();
}
public class ExternalLogin
{
///
/// Many-to-one to a user
///
/// The user.
protected User User { get; set; }
internal ExternalLogin(User applicationUser) => this.User = applicationUser;
// Was:
// public int Id { get; protected set; }
// Below is better as we don't need to expose primary key of child entities
// But the above could be useful if we want to directly update, delete
// based on Id, for performance concern.
protected int Id { get; set; }
public string LoginProvider { get; internal protected set; } // provider: facebook, google, etc
public string ProviderKey { get; internal protected set; } // user's id from facebook, google, etc
public string DisplayName { get; internal protected set; } // seems same as provider
}
}
Role model:
namespace AspNetCoreExample.Ddd.IdentityDomain
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
public class Role : IdentityRole<int>
{
/// <summary>
/// Many-to-many between Roles and Users
/// </summary>
/// <value>The users.</value>
public IEnumerable<User> Users { get; protected set; } = new Collection<User>();
public Role(string roleName) : base(roleName) { }
public void UpdateFromDetached(Role role)
{
this.Name = role.Name;
this.NormalizedName = role.NormalizedName;
}
public void SetRoleName(string roleName) => this.Name = roleName;
public void SetNormalizedName(string normalizedName) => this.NormalizedName = normalizedName;
public static async Task<IList<User>> GetUsersByRoleNameAsync(IQueryable<User> users, string normalizedRoleName)
{
var criteria =
from user in users
where user.Roles.AsQueryable().Any(role => role.NormalizedName == normalizedRoleName)
select user;
return await criteria.ToListAsyncOk();
}
}
}
Data stores (user store and role store)
User store:
namespace AspNetCoreExample.Identity.Data
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using AspNetCoreExample.Ddd.Connection;
using AspNetCoreExample.Ddd.IdentityDomain;
public class UserStore :
IUserStore<User>,
IUserEmailStore<User>,
IUserPhoneNumberStore<User>,
IUserTwoFactorStore<User>,
IUserPasswordStore<User>,
IUserRoleStore<User>,
IUserLoginStore<User>
{
IDatabaseFactory DbFactory { get; }
public UserStore(IDatabaseFactory dbFactory) => this.DbFactory = dbFactory;
async Task<IdentityResult> IUserStore<User>.CreateAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
await ddd.PersistAsync(user);
await ddd.CommitAsync();
}
return IdentityResult.Success;
}
async Task<IdentityResult> IUserStore<User>.DeleteAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
await ddd.DeleteAggregateAsync(user);
await ddd.CommitAsync();
}
return IdentityResult.Success;
}
async Task<User> IUserStore<User>.FindByIdAsync(string userId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var user = await ddd.GetAsync<User>(int.Parse(userId));
return user;
}
}
async Task<User> IUserStore<User>.FindByNameAsync(
string normalizedUserName, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
var au =
await ddd.Query<User>()
.SingleOrDefaultAsyncOk(u => u.NormalizedUserName == normalizedUserName);
return au;
}
}
Task<string> IUserStore<User>.GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.NormalizedUserName);
Task<string> IUserStore<User>.GetUserIdAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.Id.ToString());
Task<string> IUserStore<User>.GetUserNameAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.UserName);
Task IUserStore<User>.SetNormalizedUserNameAsync(
User user, string normalizedName, CancellationToken cancellationToken
)
{
user.SetNormalizedUserName(normalizedName);
return Task.FromResult(0);
}
Task IUserStore<User>.SetUserNameAsync(User user, string userName, CancellationToken cancellationToken)
{
user.SetUserName(userName);
return Task.FromResult(0);
}
async Task<IdentityResult> IUserStore<User>.UpdateAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var au = await ddd.GetAsync<User>(user.Id);
au.UpdateFromDetached(user);
await ddd.CommitAsync();
}
return IdentityResult.Success;
}
Task IUserEmailStore<User>.SetEmailAsync(User user, string email, CancellationToken cancellationToken)
{
user.SetEmail(email);
return Task.FromResult(0);
}
Task<string> IUserEmailStore<User>.GetEmailAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.Email);
Task<bool> IUserEmailStore<User>.GetEmailConfirmedAsync(
User user, CancellationToken cancellationToken
) => Task.FromResult(user.EmailConfirmed);
Task IUserEmailStore<User>.SetEmailConfirmedAsync(
User user, bool confirmed, CancellationToken cancellationToken
)
{
user.SetEmailConfirmed(confirmed);
return Task.FromResult(0);
}
async Task<User> IUserEmailStore<User>.FindByEmailAsync(
string normalizedEmail, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
var au = await ddd.Query<User>()
.SingleOrDefaultAsyncOk(u => u.NormalizedEmail == normalizedEmail);
return au;
}
}
Task<string> IUserEmailStore<User>.GetNormalizedEmailAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.NormalizedEmail);
Task IUserEmailStore<User>.SetNormalizedEmailAsync(
User user, string normalizedEmail, CancellationToken cancellationToken
)
{
user.SetNormalizedEmail(normalizedEmail);
return Task.FromResult(0);
}
Task IUserPhoneNumberStore<User>.SetPhoneNumberAsync(
User user, string phoneNumber, CancellationToken cancellationToken
)
{
user.SetPhoneNumber(phoneNumber);
return Task.FromResult(0);
}
Task<string> IUserPhoneNumberStore<User>.GetPhoneNumberAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.PhoneNumber);
Task<bool> IUserPhoneNumberStore<User>.GetPhoneNumberConfirmedAsync(
User user, CancellationToken cancellationToken
) => Task.FromResult(user.PhoneNumberConfirmed);
Task IUserPhoneNumberStore<User>.SetPhoneNumberConfirmedAsync(
User user, bool confirmed, CancellationToken cancellationToken
)
{
user.SetPhoneNumberConfirmed(confirmed);
return Task.FromResult(0);
}
Task IUserTwoFactorStore<User>.SetTwoFactorEnabledAsync(
User user, bool enabled, CancellationToken cancellationToken
)
{
user.SetTwoFactorEnabled(enabled);
return Task.FromResult(0);
}
Task<bool> IUserTwoFactorStore<User>.GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.TwoFactorEnabled);
Task IUserPasswordStore<User>.SetPasswordHashAsync(
User user, string passwordHash, CancellationToken cancellationToken
)
{
user.SetPasswordHash(passwordHash);
return Task.FromResult(0);
}
Task<string> IUserPasswordStore<User>.GetPasswordHashAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.PasswordHash);
Task<bool> IUserPasswordStore<User>.HasPasswordAsync(User user, CancellationToken cancellationToken)
=> Task.FromResult(user.PasswordHash != null);
async Task IUserRoleStore<User>.AddToRoleAsync(User user, string roleName, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var roleByName =
await ddd.Query<Role>()
.SingleOrDefaultAsyncOk(role => role.Name == roleName);
if (roleByName == null)
{
roleByName = new Role(roleName);
ddd.Persist(roleByName);
}
var userGot = await ddd.GetAsync<User>(user.Id);
await userGot.AddRole(roleByName);
await ddd.CommitAsync();
}
}
async Task IUserRoleStore<User>.RemoveFromRoleAsync(
User user, string roleName, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var userLoaded = await ddd.GetAsync<User>(user.Id);
await userLoaded.RemoveRoleAsync(roleName);
await ddd.CommitAsync();
}
}
async Task<IList<string>> IUserRoleStore<User>.GetRolesAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
var userGot = await ddd.GetAsync<User>(user.Id);
return await userGot.GetRoleNamesAsync();
}
}
async Task<bool> IUserRoleStore<User>.IsInRoleAsync(
User user, string roleName, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
var userGot = await ddd.GetAsync<User>(user.Id);
return await userGot.IsInRole(roleName);
}
}
async Task<IList<User>> IUserRoleStore<User>.GetUsersInRoleAsync(
string roleName, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
string normalizedRoleName = roleName.ToUpper();
var usersList = await Role.GetUsersByRoleNameAsync(ddd.Query<User>(), normalizedRoleName);
return usersList;
}
}
async Task IUserLoginStore<User>.AddLoginAsync(
User user, UserLoginInfo login, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null)
throw new ArgumentNullException(nameof(user));
if (login == null)
throw new ArgumentNullException(nameof(login));
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var au = await ddd.GetAsync<User>(user.Id);
au.AddExternalLogin(login.LoginProvider, login.ProviderKey, login.ProviderDisplayName);
await ddd.CommitAsync();
}
}
async Task<User> IUserLoginStore<User>.FindByLoginAsync(
string loginProvider, string providerKey, CancellationToken cancellationToken
)
{
using (var ddd = this.DbFactory.OpenDdd())
{
var user = await User.FindByLoginAsync(ddd.Query<User>(), loginProvider, providerKey);
return user;
}
}
async Task<IList<UserLoginInfo>> IUserLoginStore<User>.GetLoginsAsync(
User user, CancellationToken cancellationToken
)
{
using (var ddd = this.DbFactory.OpenDdd())
{
var au = await ddd.GetAsync<User>(user.Id);
var list = await au.GetUserLoginInfoListAsync();
return list;
}
}
async Task IUserLoginStore<User>.RemoveLoginAsync(
User user, string loginProvider, string providerKey, CancellationToken cancellationToken
)
{
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var au = await ddd.GetAsync<User>(user.Id);
await au.RemoveExternalLoginAsync(loginProvider, providerKey);
await ddd.CommitAsync();
}
}
public void Dispose()
{
// Nothing to dispose.
}
}
}
Role store:
namespace AspNetCoreExample.Identity.Data
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using AspNetCoreExample.Ddd.Connection;
using AspNetCoreExample.Ddd.IdentityDomain;
public class RoleStore : IRoleStore<Role>
{
IDatabaseFactory DbFactory { get; }
public RoleStore(IConfiguration configuration, IDatabaseFactory dbFactory) => this.DbFactory = dbFactory;
async Task<IdentityResult> IRoleStore<Role>.CreateAsync(Role role, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var db = this.DbFactory.OpenDddForUpdate())
{
await db.PersistAsync(role);
await db.CommitAsync();
}
return IdentityResult.Success;
}
async Task<IdentityResult> IRoleStore<Role>.UpdateAsync(Role role, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDddForUpdate())
{
var roleGot = await ddd.GetAsync<Role>(role.Id);
roleGot.UpdateFromDetached(role);
await ddd.CommitAsync();
}
return IdentityResult.Success;
}
async Task<IdentityResult> IRoleStore<Role>.DeleteAsync(Role role, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var db = this.DbFactory.OpenDddForUpdate())
{
await db.DeleteAggregateAsync(role);
await db.CommitAsync();
}
return IdentityResult.Success;
}
Task<string> IRoleStore<Role>.GetRoleIdAsync(Role role, CancellationToken cancellationToken) =>
Task.FromResult(role.Id.ToString());
Task<string> IRoleStore<Role>.GetRoleNameAsync(Role role, CancellationToken cancellationToken) =>
Task.FromResult(role.Name);
Task IRoleStore<Role>.SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken)
{
role.SetRoleName(roleName);
return Task.FromResult(0);
}
Task<string> IRoleStore<Role>.GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken) =>
Task.FromResult(role.NormalizedName);
Task IRoleStore<Role>.SetNormalizedRoleNameAsync(
Role role, string normalizedName, CancellationToken cancellationToken
)
{
role.SetNormalizedName(normalizedName);
return Task.FromResult(0);
}
async Task<Role> IRoleStore<Role>.FindByIdAsync(string roleId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
var role = await ddd.GetAsync<Role>(roleId);
return role;
}
}
async Task<Role> IRoleStore<Role>.FindByNameAsync(
string normalizedRoleName, CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ddd = this.DbFactory.OpenDdd())
{
var role =
await ddd.Query<Role>()
.SingleOrDefaultAsyncOk(r => r.NormalizedName == normalizedRoleName);
return role;
}
}
public void Dispose()
{
// Nothing to dispose.
}
}
}
Wireup:
namespace AspNetCoreExample.Identity
{
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AspNetCoreExample.Ddd.Connection;
using AspNetCoreExample.Identity.Data;
using AspNetCoreExample.Identity.Services;
public class Startup
{
IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) => this.Configuration = configuration;
string ConnectionString => this.Configuration.GetConnectionString("DefaultConnection");
(string appId, string appSecret) FacebookOptions
=> (this.Configuration["Authentication:Facebook:AppId"],
this.Configuration["Authentication:Facebook:AppSecret"]);
(string clientId, string clientSecret) GoogleOptions
=> (this.Configuration["Authentication:Google:ClientId"],
this.Configuration["Authentication:Google:ClientSecret"]);
(string consumerKey, string consumerSecret) TwitterOptions
=> (this.Configuration["Authentication:Twitter:ConsumerKey"],
this.Configuration["Authentication:Twitter:ConsumerSecret"]);
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<NHibernate.ISessionFactory>(serviceProvider =>
AspNetCoreExample.Ddd.Mapper.TheMapper.BuildSessionFactory(this.ConnectionString)
);
services.AddSingleton<IDatabaseFactory, DatabaseFactory>();
services.AddTransient<Microsoft.AspNetCore.Identity.IUserStore<Ddd.IdentityDomain.User>, UserStore>();
services.AddTransient<Microsoft.AspNetCore.Identity.IRoleStore<Ddd.IdentityDomain.Role>, RoleStore>();
services.AddIdentity<Ddd.IdentityDomain.User, Ddd.IdentityDomain.Role>().AddDefaultTokenProviders();
services.AddAuthentication()
.AddFacebook(options => (options.AppId, options.AppSecret) = this.FacebookOptions)
.AddGoogle(options => (options.ClientId, options.ClientSecret) = this.GoogleOptions)
.AddTwitter(options => (options.ConsumerKey, options.ConsumerSecret) = this.TwitterOptions)
;
services.ConfigureApplicationCookie(config =>
{
config.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api"))
{
ctx.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.FromResult(0);
}
};
});
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Happy Coding!