Skip to content

ASP.NET core server with WEB API, SignalR hub, SQL dataBase used by EntityFramework, authentication with Cookies or/and JWT, Unit tests

Notifications You must be signed in to change notification settings

BalancingRay/NeighborHelp

Repository files navigation

NeighborHelp

The project main goals are

  • to have a practice of creating a server application for different types of clients,

  • to use new technologies,

  • to follow clean architecture and clean code approaches.

Server functionality

  • User registration. Update user profile.
  • Authentication both with Cookies or/and Json Web Token schemes
  • Different rights to use API following with user Role.
  • Creating, reading and updating models (User and Order) by WebAPI
  • Chat beetween clients based on SignalR

Also there are following simple clients:

Tehnologies

Practices

  • Clean Architecture
  • Clean Code
  • SOLID Principles
  • Separation of Concerns
  • Unit testing

Structure of projects:

Data Transfer Objects and their constants. Extensions to compare, modify and dublicate models. Use on both sides: clients and server.

     public class Order
    {
        public int Id { get; set; }
        public int AuthorId { set; get; }
        public UserProfile Author { set; get; }

        public string Product { set; get; }
        public string ProductDescription { set; get; }
        public double Cost { set; get; }
        public string OrderType { set; get; }

        public string Status { get; set; }
        public int ClientId { set; get; }
    }

Names of controllers and their actions, which used for routing on the server side. Use by clients to get API paths and chatHub commands.

    public static class PathConst
    {
        private const string API_AREA = "API";
        private const string USER_CONTROLLER = "USER";
        private const string ORDER_CONTROLLER = "ORDER";
        private const string LOGIN_CONTROLLER = "Authentification";

        public static readonly string LOGIN_BY_JWT_PATH = $"/{LOGIN_CONTROLLER}/{AuthenticationConsts.LOGIN_BY_JWT}";
        public static readonly string LOGIN_BY_COOKIES_PATH = $"/{LOGIN_CONTROLLER}/{AuthenticationConsts.LOGIN_BY_COOKIES}";

        private static readonly string USER_API = $"/{API_AREA}/{USER_CONTROLLER}/";

        public static readonly string ADD_USER_PATH = $"{USER_API}{UserControllerConsts.ADD_USER}";
        public static readonly string CURRENT_USER_PATH = $"{USER_API}{UserControllerConsts.GET_CURRENT_ACTION}";
        public static readonly string GET_USER_PATH = $"{USER_API}{UserControllerConsts.GET_ACTION}";
        public static readonly string GET_USERS_PATH = $"{USER_API}{UserControllerConsts.GET_ALL_ACTION}";
        public static readonly string PUT_USER_PATH = $"{USER_API}{UserControllerConsts.UPDATE_ACTION}";

        private static readonly string ORDER_API = $"/{API_AREA}/{ORDER_CONTROLLER}/";

        public static readonly string ADD_ORDER_PATH = $"{ORDER_API}{OrderControllerConsts.ADD_ACTION}";
        public static readonly string GET_ORDER_PATH = $"{ORDER_API}{OrderControllerConsts.GET_ACTION}";
        public static readonly string GET_ORDERS_PATH = $"{ORDER_API}{OrderControllerConsts.GET_ALL_ACTION}";
        public static readonly string GET_ORDERS_BY_USER_PATH = $"{ORDER_API}{OrderControllerConsts.GET_BY_USER_ACTION}";
        public static readonly string PUT_ORDER_PATH = $"{ORDER_API}{OrderControllerConsts.PUT_ACTION}";
        public static readonly string RESPONCE_ORDER_PATH = $"{ORDER_API}{OrderControllerConsts.RESPONSE_ACTION}";
    }

Interfaces of services for working with models. Common utils for authorization and configuration

    public interface IUserDirectoryServise
    {
        public User GetUser(string login, string password);
        public User GetUser(int id, bool useTracking = ContractConsts.DefaultTracking);
        public IList<User> GetUsers(bool useTracking = ContractConsts.DefaultTracking);
        public bool TryAddUser(User user);
        public bool TryPutUser(User user);
        public bool TryRemoveUser(int id, bool removeRelatedOrders);
    }

Initialization and registration services, dataBase and Build middlewares. Include main services implementations, extensions for building services and middlewares, controllers.

    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration) => Configuration = configuration;

        public void ConfigureServices(IServiceCollection services)
        {
            string authArea = AuthenticationConfigurationExtension.ConfigurationArea;
            string dbArea = StartupDataBaseExtension.ConfigurationArea;
            services.ConfigureControllers();
            services.ConfigureChatHub();
            services.ConfigureAuthentication(Configuration.GetSection(authArea));
            services.ConfigureDirectoryServices(Configuration.GetSection(dbArea));
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseWebClientStaticFiles(Configuration, env);
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapChatHub();
            });
        }
    }
    public class ApplicationContext: DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Order> Orders { get; set; }

        public ApplicationContext(DbContextOptions<ApplicationContext> options, bool clearOldData = false) :base(options)
        {
            if (clearOldData)
            {
                Database.EnsureDeleted();
            }
            Database.EnsureCreated();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<User>()
		.HasOne(u => u.Profile).WithOne(p => p.OwnerUser)
	        .HasForeignKey<UserProfile>(up => up.Id);

            modelBuilder.Entity<Order>()
		.HasOne(o => o.Author).WithMany(a => a.Orders)
		.HasForeignKey(o => o.AuthorId);
        }
    }
    public class EntityUserOrderDirectory : IOrderDirectoryServise, IUserDirectoryServise
    {
	protected ApplicationContext DataBase { get; }
        protected DbSet<User> Users => DataBase.Users;

        #region IUserDirectoryServise implementation

        public bool TryAddUser(User user)
        {
            if (user == null)
                return false;

            bool isLoginEmpty = string.IsNullOrWhiteSpace(user.Login);
            bool isLoginExist = Users.Any(u => u.Login == user.Login);
            bool isProfileDublicated = user.Profile != null && Users.Any(u => u.Profile == user.Profile);

            if (isLoginEmpty || isLoginExist || isProfileDublicated)
            {
                return false;
            }
            else
            {
                Users.Add(user);
                DataBase.SaveChanges();

                return true;
            }
        }

        public User GetUser(int id, bool useTraching)
        {
            if (useTraching)
            {
                return Users.Include(u => u.Profile).SingleOrDefault(u => u.Id == id);
            }
            else
            {
                return Users.AsNoTracking().Include(u => u.Profile).SingleOrDefault(u => u.Id == id);
            }
        }

        public bool TryPutUser(User user)
        {
            if (Users.Any(u => u.Login == user.Login && u.Id == user.Id))
            {
                if (IsDetached(user))
                {
                    var originalUser = GetUser(user.Id, true);
                    originalUser.UpdateFrom(user);
                }
                DataBase.SaveChanges();

                return true;
            }
            return false;
        }
       ...
    [ApiController]
    [Route("api/[Controller]/[Action]")]
    public class UserController : Controller
    {
        private IUserDirectoryServise _userDirectory;

        public UserController(IUserDirectoryServise service)
        {
            _userDirectory = service;
        }

        [HttpGet]
        [ActionName(UserControllerConsts.GET_CURRENT_ACTION)]
        [Authorize(AuthenticationSchemes = AuthorizeAttributeHelper.Value)]
        public ActionResult<User> Current()
        {
            User user = null;
            if (AuthorizationHelper.TryGetCurrentUserId(HttpContext?.User, out int id))
            {
                user = _userDirectory.GetUser(id);
            }

            if (user != null)
            {
                return new ActionResult<User>(user);
            }
            else
            {
                return new NotFoundResult();
            }
        }

        [HttpGet("{id}")]
        [ActionName(UserControllerConsts.GET_ACTION)]
        [Authorize(Roles = UserRoles.ADMIN)]
        [Authorize(AuthenticationSchemes = AuthorizeAttributeHelper.Value)]
        public ActionResult<User> Get(int id)
        {
            var user = _userDirectory.GetUser(id);

            if (user != null)
            {
                return new ActionResult<User>(user);
            }
            else
            {
                return new NotFoundResult();
            }
        }
        ...

ChatHub and its services. Include services registration and routing extensions for Startup method.

    [Authorize(AuthenticationSchemes = AuthorizeAttributeHelper.Value)]
    public class ChatHub : Hub
    {
        private IChatUserProvider users;
        public string CurrentUserName => users.GetCurrentUserName(Context);

        public ChatHub(IUserDirectoryServise userService)
        {
            users = new ChatUserProvider(userService);
        }

        [HubMethodName(ChatHubConsts.SendMessage)]
        public async Task Send(string message)
        {
            string username = users.GetCurrentUserName(Context);
            await Clients.All.SendAsync(ChatHubConsts.ReceiveClientsMesage, message, CurrentUserName);
        }

        [HubMethodName(ChatHubConsts.SendToGroup)]
        public async Task SendToGroup(string message, string group)
        {
            await Clients.Group(group).SendAsync(ChatHubConsts.ReceiveClientsMesage, message, CurrentUserName);
        }
        ...

Static html files and js scripts. Include middleware registration extension.

Unit tests for services and controllers

About

ASP.NET core server with WEB API, SignalR hub, SQL dataBase used by EntityFramework, authentication with Cookies or/and JWT, Unit tests

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published