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!
No comments:
Post a Comment