/Photos

Photo Gallery Social System built with ASP.NET, .NET Core, MVC architecture

Primary LanguageC#

#Photos

"Photos" is a simple, easy to use, Web application, cource work project, for the Software Technologies Cource at Software University.The project was built with ASP.NET, .NET Core and the MVC architecture.

The project includes external ASP.NET Core extensions from AspNet.Mvc.TypedRouting

home_page

What are the basic functionalities?

  • Without registration:

    • Searching for content by category
  • With registration:

    • Having profile page with the ability to manage the uploaded content
    • Uploading photos
    • Creating alubms
    • Commenting albums
    • Ability to rate content
    • Searching for content by category

Creating an account

register_page

Creating an account is done by filling the fields with appropriate information. The *Required lable is placed above every field that is needed in order to make the process registring clearer and to insure valid inputs.

You can configure the password requirements in the Startup.cs file, Configuration action.

        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>(identity =>
		{
			identity.Password.RequireDigit = true;
			identity.Password.RequireLowercase = false;
			identity.Password.RequireNonAlphanumeric = false;
			identity.Password.RequireUppercase = false;
		})
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

		services.AddMvc().AddTypedRouting();

		services.AddMvc();

        // Add application services.
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();

Signed in

Once signed in, on the navigation bar several new items will appear:the profile picture, upload picture icon, profile icon.

user_navbar

The initial user profile page

user_myprofile_page

With the shortcuts bellow the profile picture, the user can easily manage the account


Creating content

Uploading Image

upload_photo_page

Uploading Album

create_album_page


In the Search Controller

                   // GET: Search/Index
	[HttpGet]
	public IActionResult Index()
	{
		return View();
	}

	// POST: Search/Index
	[HttpPost]
	public IActionResult Index(HomeViewModel model)
	{
		if (model.Search == null)
		{
			return RedirectToAction("Index", "Home");
		}

		if (model.Option == "SingleImages")
		{
			return RedirectToAction("ImagesSearch", "Search", new { @category = model.Search });
		}

		else if (model.Option == "Album")
		{
			return RedirectToAction("AlbumsSearch", "Search", new { @category = model.Search });
		}
		else
		{
			return NotFound();
		}

	}

Depending on the chosen option the Search Controller redirects to a certain action.

Searching for images

         public IActionResult ImagesSearch(string category, int page = 1)
	{
		ViewBag.TotalPages = Math.Ceiling(
			this.db.SingleImages
			.Where(img => img.Category == category).Count() / 5.0);

		ViewBag.CurrentPage = page;


		if (page < 1 || page > ViewBag.TotalPages)
		{
			if (ViewBag.TotalPages != 0)
			{
				return NotFound();
			}
		}
		
		int pageSize = 5;

		var result = this.db.SingleImages
			.Where(img => img.Category == category)
			.OrderByDescending(img => img.CreatedOn)
			.Skip((page - 1) * pageSize)
			.Take(pageSize)
			.Select(img => new SingleImageDetailsViewModel()
			{
				Id = img.Id,
				Description = img.Description,
				Location = img.Location,
				Name = img.Name,
				Path = img.Path,
				Rating = img.Rating,
				UploadedOn = img.CreatedOn,
				Category = img.Category,
				User = img.User
			})
			.ToList();

		return View(result);
	}

Searching for albums

              public IActionResult AlbumsSearch(string category, int page = 1)
	    {
		ViewBag.TotalPages = Math.Ceiling(
			this.db.Album
			.Where(al => al.Category == category).Count() / 5.0);

		ViewBag.CurrentPage = page;


		if (page < 1 || page > ViewBag.TotalPages)
		{
			if (ViewBag.TotalPages != 0)
			{
				return NotFound();
			}
		}

		int pageSize = 5;

		var result = this.db.Album
			.Where(al => al.Category == category)
			.Skip((page - 1) * pageSize)
			.Take(pageSize)
		      .OrderByDescending(al => al.CreatedOn)
		      .Select(al => new HomeAlbumsDetailsViewModel()
		      {
		        Id = al.Id,
		        Name = al.Name,
		        User = al.User,
				Category = al.Category,
		        Images = this.db.Images
		     	.Where(img => img.Album.Id == al.Id)
		     	.Select(img => new AlbumImageDetailsViewModel()
		     	{
		     		Rating = img.Rating,
		     		Album = al,
		     		Path = al.UserId + "/" + al.Id.ToString() + "/" + img.Name
		     	})
		     	.ToList()
		      }).ToList();

		return View(result);

Rendering the search results

The example below shows how the images from the model are listed.

pagination

      @if(Model.Capacity == 0)
      {
      	<h1 class="text-center">There are no images with matching category</h1>
      }
      
       <div class="col-md-6 col-md-offset-3">
      
      
      	@foreach (var image in Model)
      	{
      		<div class="u_image text-center" style="width:100%;">
      			<img src="~/uploads/@image.Path" style="height: initial; width:100%;" />
      			<h4><strong>@image.Name</strong> @image.Description</h4>
      			<div class="row">
      				<div class="col-md-6 col-md-offset-3 text-center">
      					<a href="@(Url.Action("DislikeImage", "Image", new { @imageId = image.Id}))">
      						<span class="glyphicon glyphicon-chevron-left"></span>
      					</a>
      					<span><strong>@image.Rating</strong></span>
      					<a href="@(Url.Action("LikeImage", "Image", new { @imageId = image.Id }))">
      						<span class="glyphicon glyphicon-chevron-right"></span>
      					</a>
      				</div>
      			</div>
      		</div>
      	}
      	<hr />

The same idea is used when searching for albums. The change here is in the way albums are showcased. The bootstrap plugin used here is called "Carousel", you can find more information on how to implement it here.

  @if(Model.Capacity == 0)
  {
  	<h1 class="text-center">There are no albums with matching category!</h1>
  
  }
  
<div class="row">
  	<div class="col-md-6 col-md-offset-3">
  
  @Html.Partial("_PaginationAlbumsPartial");
  
  @foreach (var album in Model)
  {
    <div class="col-md-6 col-md-offset-3 u_album text-center" style="width: 100%;">

	<h3 class="text-center">@album.Name</h3>

	@if (album.Images.Capacity > 0)
	{
  	<div id="album-with-pictures" class="carousel slide" data-ride="carousel" data-interval="4000" data-delay="3000">
  
  		<div class="carousel-inner" role="listbox">
  
  			@foreach (var image in album.Images)
  			{
  				var firstImage = album.Images.First();

  				if (image == firstImage)
  				{
  					<div class="item active">
  						<img src="~/uploads/@image.Path" alt="Chania" class="u_album_img" />
  					</div>
  				}
  				else
  				{
  					<div class="item">
  						<img src="~/uploads/@image.Path" alt="Chania" class="u_album_img" />
  					</div>
  				}
  			}
  		</div>
  					</div>
  				}
  	else
  	{
  		<img src="~/images/album_with_no_images.jpg" />
  	}
  
  	    <div class="a_details_link">
  	       <a href="@(Url.Action("Details", "Albums", new { @albumId = album.Id, @userId = album.User.Id }))" title="View" class="pull-right">
	         <span class="glyphicon glyphicon-option-horizontal"></span>
      </a>
   </div>
    </div>
    }

How the pagination is done?

paging

The pagination used in this project is not the best example and it is not recomended for big projects, because it gives errors when the pages are over 100, but for the small projects like the one here it works fine.

When we call an action, by default we have to set the page to first Here is how is done:

              public IActionResult ImagesSearch(string category, int page = 1) 

In the exaple from the Search Controller, we pass to the action the category of the images and set the page to one. Next we have to manipulate the code so that every time we call this action it passes to the view the next set of images.

By using the LINQ library, we can easily write the following query:

              .Skip((page - 1) * pageSize).Take(pageSize)

The purpose of this query is to pick "pageSize" count images after skipping pageSize multiplied by the current page minus one. It may sounds complicated, but it is very simple, imagine we are on page one. Lets say that the pageSize = 5 and the page is the first (1), the algorith will skip (1-1) * 5 images and will take 5, so it skips 0 and takes 5. On the second page the algorithm will skip (2 - 1) * 5 and will take 5 again. This time it skips 5 and takes the next set of 5 images, or if there are less than 5 it takes all.

The next step is to pass to the view the current page and the total number of pages

Taking advantage of the Razor syntax, this task is fairly easy.All we have to do is to calculate the total page and save them to a ViewBag:

             ViewBag.TotalPages = Math.Ceiling(this.db.SingleImages
			.Where(img => img.Category == category).Count() / 5.0);

Next we have to save the current page:

              ViewBag.CurrentPage = page;

To insure correct results and to avoid inappropriate UX we have to write a simple condition:

                 if (page < 1 || page > ViewBag.TotalPages)
        	{
        		if (ViewBag.TotalPages != 0)
        		{
        			return NotFound();
        		}
        	}

What it does is simply not showing error messages to the user, but a blank page.

The HTML View

            @model List<Code.Models.SingleImageViewModels.SingleImageDetailsViewModel>

            <div class="text-center">
            	<ul class="pagination">
            		@if (ViewBag.CurrentPage > 1)
            			{
            			<li>
				<a href="ImagesSearch?category=@Model.First().Category&page=@(ViewBag.CurrentPage - 1)" style="background-color: #1f7dd7; color: white;">Previous</a>
				</li>
            			}
            		@for (int i = 1; i <= ViewBag.TotalPages; i++)
            			{
            			@if (i == ViewBag.CurrentPage)
            				{
            				<li>
					<a href="ImagesSearch?category=@Model.First().Category&page=@i" style="background-color: gray; color: white;">@i</a>
					</li>
            				}
            				else
            				{
            				<li>
					<a href="ImagesSearch?category=@Model.First().Category&page=@i" style="background-color: #1f7dd7; color: white;">@i</a>
					</li>
            				}
            			}
            		@if (ViewBag.TotalPages >= 2)
            		{
            			if (Model.Count == 5)
            			{
            				<li>
					<a href="ImagesSearch?category=@Model.First().Category&page=@(ViewBag.CurrentPage + 1)" style="background-color: #1f7dd7; color: white;">Next</a>
					</li>
            			}
            		}
            	</ul>
            </div>                     

Administrator Panel

This panel gives the admins rights to inspect users and to edit their content if needed

admin_panel_main_page

Inspect User Page Example:

admin_inspect_user_page


CEO Panel

This panel gives the site owner rights to make certain user an admin or to take his/her admin rights.

ceo_panel_main_page


Profile Page Example

my_profile_page


Album Examples

album_details_page

album_edit_page