Skip to content

Latest commit

 

History

History
207 lines (186 loc) · 8.44 KB

README.md

File metadata and controls

207 lines (186 loc) · 8.44 KB

PetShop ASP.NET Core web application Project

Asp.Net Core MVC web appplication using mssql, ef6, docker, jquery, sass, CORS, restAPI, socket, hubs & signalR, vanilla js, bootstrap library

Main catalog page where you can scroll and choose animel to explore and comment

Docker instuctions

pulling images to your local device:
  $ docker pull bloodshop/petshopapp:1.0  # https://hub.docker.com/repository/docker/bloodshop/petshopapp
  $ docker pull bloodshop/petshopdb:1.0  # https://hub.docker.com/repository/docker/bloodshop/petshopdb
  $ docker-compose up -d

docker-compose.yml

version: '3.3'

services:
  db:
    image: bloodshop/petshopdb:1.0
    restart: always

  app:
    depends_on:
      - db
    image: bloodshop/petshopapp:1.0
    ports: 
        - "3000:80" 
        - "3001:433"
    networks: 
       - db-bridge
    restart: always

Clear Dependency Injection

Here is an implementation of adding all the additional services

pet-shop/Program.cs

Lines 15 to 17 in bcf9df3

builder.Services.InstallerServices(
builder.Configuration,
typeof(IServiceInstaller).Assembly);
public class InfrastuctureServiceInstaller : IServiceInstaller
{
    public void Install(IServiceCollection services, IConfiguration configuration)
    {
        services.AddTransient<IRepository, PetRepository>();
        string connectionString = configuration["ConnectionStrings:DefaultConnection"];
        services.AddDatabaseDeveloperPageExceptionFilter();
        services.AddControllersWithViews().AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.PropertyNamingPolicy = null;
        });
        services.AddDbContext<ICallCenterContext, PetDbContext>(options => options.UseLazyLoadingProxies().UseSqlServer(connectionString));
    }
}

public interface IServiceInstaller
{
    void Install(IServiceCollection services,IConfiguration configuration);
}

What's in this project

This is a sample which shows most of the common features of ASP.NET Identity. For more information on it, please visit http://asp.net/identity
  • ViewModels & models
    Models - pure models which are used in our database and repositories
    • Animal
    • Category
    • Comment
    ViewModels - added value for implementing a model and binding it with form in-order to create an instance with limitations
    • AddAnimalViewModel
    • AddCategoryViewModel
    • AddCommentViewModel
    • EditAnimalViewModel
    • LoginViewModel
    • ManageUsersViewModel
    • RegisterViewModel
    • SearchAnimalViewModel

    examlpe viewmodel binded with form/view


    Viewmodel

    public class SearchAnimalViewModel
    {
    [Required]
    [MaxLength(25, ErrorMessage = "{0} cannot exceed 25 characters.")]
    [RegularExpression(@"^[a-zA-Z\s]+$")]
    public string Content { get; set; } = null!;
    }
    View

    @{
      var searchModel = new SearchAnimalViewModel();
    }

    <form enctype="multipart/form-data" method="post" class="d-flex" asp-controller="Home" asp-action="Search">
    @*asp-validation-for="Content"*@ <span asp-validation-for="@searchModel.Content" class="text-danger"></span>
    @*asp-for="Content"*@ <input name="text" asp-for="@searchModel.Content" class=" form-control me-2" type="search" placeholder="Search" aria-label="Search">
    <button class="btn btn-outline-success" type="submit">Search</button>
    </form>
  • Initialize ASP.NET Identity You can initialize ASP.NET Identity when the application starts. Since ASP.NET Identity is Entity Framework based in this sample, you can create DatabaseInitializer which is configured to get called each time the app starts. Please look in Program.cs

    pet-shop/Program.cs

    Lines 16 to 29 in bca1335

    builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
    {
    options.Password.RequiredUniqueChars = 0;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireLowercase = false;
    options.Password.RequireUppercase = false;
    options.Password.RequireDigit = false;
    }).AddEntityFrameworkStores<PetDbContext>();
    builder.Services.AddControllersWithViews();
    builder.Services.Configure<PasswordHasherOptions>(options =>
    options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2
    );
    builder.Services.ConfigureApplicationCookie(config => config.LoginPath = "/Login");
    This code shows the following
    • Create user
    • Create user with password
    • Create Roles
    • Add Users to Roles
  • Validation When you create a User using a username or password, the Identity system performs validation on the username and password, and the passwords are hashed before they are stored in the database. You can customize the validation by changing some of the properties of the validators such as Turn alphanumeric on/off, set minimum password length or you can write your own custom validators and register them with the Administrator. You can use the same approach for UserManager and RoleManager.
    • Look at Controllers\AccountController.cs Default Action on how to tweak the default settings for the Validators
    • Look at Models\DataAnnotations\ValidateFileAttribute.cs to see how you can implement the different validators
    • [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
      public class ValidateFileAttribute : ValidationAttribute
      {
      double _maxContent = 1 * 1024 * 1024; //1 MB
      string[] _sAllowedExt = new string[] { ".jpg", ".gif", ".png", ".jpeg", ".jpeg2000" };
      public ValidateFileAttribute(long maxContent) => _maxContent = maxContent;
      public ValidateFileAttribute(long maxContent, params string[] extentions)
      {
      _maxContent = maxContent;
      _sAllowedExt = extentions;
      }
      public override bool IsValid(object? value)
      {
      var file = value as IFormFile;
      if (file == null)
      return true;
      if (!_sAllowedExt.Contains(file.FileName.Substring(file.FileName.LastIndexOf('.'))))
      {
      ErrorMessage = "Please upload Your Photo of type: " + string.Join(" / ", _sAllowedExt);
      return false;
      }
      if (file.Length > _maxContent)
      {
      ErrorMessage = "Your Photo is too large, maximum allowed size is : " + (_maxContent / 1024).ToString() + "MB";
      return false;
      }
      return true;
      }
    • Look at Controllers\AccountController.cs Cutomize Action on how you can use the custom validators with the Managers
  • Register a user, Login Click Register and see the code in AccountController.cs and Register Action. Click Login and see the code in AccountController.cs and Login Action.
  • Basic Role Management Do Create, Update, List and Delete Roles. Only Users In Role Admin can access this page. This uses the [Authorize] on the controller.
  • Basic User Management Do Create, Update, List and Delete Users. Assign a Role to a User. Only Users In Role Admin can access this page. This uses the [Authorize] on the controller.
  • SignalR

    public class CallCenterHub : Hub<ICallCenterHub>
    {
    public async Task NewCallReceivedAsync(Call newCall) =>
    await Clients.Group("CallCenters").NewCallReceivedAsync(newCall);
    public async Task CallDeletedAsync() =>
    await Clients.Group("CallCenters").CallDeletedAsync();
    public async Task CallEditedAsync(Call editCall) =>
    await Clients.Group("CallCenters").CallEditedAsync(editCall);
    public async Task JoinCallCenters() =>
    await Groups.AddToGroupAsync(Context.ConnectionId, "CallCenters");
    }
    ASP.NET Core SignalR is an open-source library that simplifies adding real-time web functionality to apps. Real-time web functionality enables server-side code to push content to clients instantly.
    !!Importent!! -- Providing the CallsController that Hub implementing the `ICallCenterHub` interface inorder to get the dependencies (Dependency Injection).
    $(() => {
        LoadCallsData();
    
        let $theWarning = $("#theWarning");
        $theWarning.hide();
    
        var connection = new signalR.HubConnectionBuilder().withUrl("/callcenter").build();
        connection.start()
            .then(() => connection.invoke("JoinCallCenters"))
            .catch(err => console.error(err.toString()));
    
        connection.on("NewCallReceivedAsync", () => LoadCallsData());
        connection.on("CallDeletedAsync", () => LoadCallsData());
        connection.on("CallEditedAsync", () => LoadCallsData());
    
        function LoadCallsData() {
            var tr = '';
            $.ajax({
                url: '/Calls/GetCalls',
                method: 'GET',
                success: (result) => {
                    $.each(result, (k, v) => {
                        tr += `<tr>
                            <td>${v.Name}</td>
                            <td>${v.Email}</td>
                            <td>${moment(v.CallTime).format("llll")}</td>
                            <td>
                                <a href="../Calls/Details?id=${v.Id}" class="btn btn-sm btn-success deatils-button" data-id="${v.Id}">Details</a>
                                <a href="../Calls/Edit?id=${v.Id}" class="btn btn-sm btn-danger edit-button" data-id="${v.Id}">Edit</a>
                                <a href="../Calls/Delete?id=${v.Id}" class="btn btn-sm btn-warning delete-button" data-id="${v.Id}">Delete</a>
                            </td>
                        </tr>`;
                    })
                    $("#logBody").html(tr);
                },
                error: (error) => {
                    $theWarning.text("Failed to get calls...");
                    $theWarning.show();
                    console.log(error)
                }
            });
        }
    });

    Bonus

    A global dark theme for the web. Dark Mode is an extension that helps you quickly turn the screen (browser) to dark at night time
    document.addEventListener('DOMContentLoaded', () => {
    const body = document.querySelector('body');
    const inputs = document.querySelectorAll('input');
    const containers = document.querySelectorAll('.container');
    const dropdown = document.querySelector('.dropdown-menu');
    const ulss = document.querySelectorAll('li.list-group-item.d-flex.justify-content-between.align-items-start');
    const navBar = document.querySelector('body>nav');
    const aas = document.querySelectorAll('body div.container-fluid a, form>button.nav-link.btn.btn-link.py-0').forEach(a => {
    a.classList.toggle('text-light');
    });
    const toggle = document.getElementById('toggle');
    toggle.onclick = function () {
    toggle.classList.toggle('active');
    body.classList.toggle('active');
    containers.forEach(e => {
    e.classList.toggle('active');
    //let children = e.children;
    //for (let i = 0; i < children.length; i++) {
    // children[i].classList.toggle('active');
    });
    dropdown.classList.toggle('bg-dark');
    dropdown.querySelectorAll('a').forEach(a => {
    a.classList.toggle('text-dark');
    });
    navBar.classList.toggle('bg-opacity-25');
    inputs.forEach(i => {
    i.classList.toggle('bg-black');
    });
    ulss.forEach(li => {
    li.classList.toggle('bg-dark');
    });
    };
    });
    path: ~\wwwroot\js\theme-mode.js