In order to make my journey in C# more interesting, I insert small projects where I can learn new things. The new target is now Blazor Server, a very interesting Web Technology from Microsoft.
I am trying to find small things to learn and add them to a bigger project later this year, perhaps.
My main tutors on the net were Tim Corey, and maybe Nick Chapsas. You can search them on youtube, they have great content.
The new application I have created is a regular todo app which uses:
- blazor server
- material design https://www.matblazor.com/
- a simple list (no database)
Prerequisites:
- asp.net core 3.1 installed on your machine
- intermediate C# and html
Code is under:
https://github.com/zoltanhalasz/TodoList_BlazorServer.git
The application is live: https://todolist-blazorserver.zoltanhalasz.net/todos
You can add material design to a blazor server project:
1.Install-Package MatBlazor
2.Add @using MatBlazor in main _Imports.razor
3.add to _Host.cshtml (head section)
<script src="_content/MatBlazor/dist/matBlazor.js"></script>
<link href="_content/MatBlazor/dist/matBlazor.css" rel="stylesheet" />
The toaster is added to the todo page: see instructions at: https://www.matblazor.com/Toast
I will explain some of the code and application below.
A. The main model class for the todos:
public class TodoModel
{
public int Id { get; set; }
public string Todo { get; set; }
public DateTime Deadline { get; set; }
public bool IsCompleted { get; set; }
}
B. The layout:
Please read the below examples
https://www.matblazor.com/TextField
https://www.matblazor.com/Checkbox
https://www.matblazor.com/DatePicker
https://www.matblazor.com/Button
See list of icons available:
https://www.matblazor.com/Icon
Toast, see tutorial example:
https://www.matblazor.com/Toast
Showing posts with label asp.net core. Show all posts
Showing posts with label asp.net core. Show all posts
Friday, May 1, 2020
Thursday, April 30, 2020
My Journey with Blazor Server
In my study of C# alongside Javascript, and working on my projects (asp.net core and WPF), I came across this technology called Blazor.
This attempts to make C# valid across back-end and front-end, possibly avoiding the use of Javascript on the front-end. Blazor, as a part of Asp.Net Core 3.0 and 3.1, has two flavors, Blazor Webassembly and Blazor Server-Side.
My preoccupation is the latter, as it is production ready. The result is, some asp.net applications that are very fast and responsive, and there are lots of free and beautiful components that can add nice design to the web apps.
The tutorials I checked and can recommend, are the following:
Tim Corey's material about Blazor Server:
- introduction : https://youtu.be/8DNgdphLvag
- components: https://youtu.be/JE0tQ4tx0Nc
- free resources: https://youtu.be/i1Kric5tqYk
A tutorial series on youtube from Nick Chapsas:
Checking the free resources I want to highlight now two of them:
1. Material Design Components: https://www.matblazor.com/
My small sample app is here, live.
2. Radzen Blazor Components: https://blazor.radzen.com/
My small sample app is here, live:
And I found another beautiful tutorial, which I did not yet check in detail:
https://www.syncfusion.com/ebooks/blazor-succinctly
Overall, I find Blazor Server-Side a very interesting idea, worth exploring further.
This attempts to make C# valid across back-end and front-end, possibly avoiding the use of Javascript on the front-end. Blazor, as a part of Asp.Net Core 3.0 and 3.1, has two flavors, Blazor Webassembly and Blazor Server-Side.
My preoccupation is the latter, as it is production ready. The result is, some asp.net applications that are very fast and responsive, and there are lots of free and beautiful components that can add nice design to the web apps.
The tutorials I checked and can recommend, are the following:
Tim Corey's material about Blazor Server:
- introduction : https://youtu.be/8DNgdphLvag
- components: https://youtu.be/JE0tQ4tx0Nc
- free resources: https://youtu.be/i1Kric5tqYk
A tutorial series on youtube from Nick Chapsas:
Checking the free resources I want to highlight now two of them:
1. Material Design Components: https://www.matblazor.com/
My small sample app is here, live.
2. Radzen Blazor Components: https://blazor.radzen.com/
My small sample app is here, live:
And I found another beautiful tutorial, which I did not yet check in detail:
https://www.syncfusion.com/ebooks/blazor-succinctly
Overall, I find Blazor Server-Side a very interesting idea, worth exploring further.
Saturday, March 28, 2020
HighCharts with Angular - Part 2 - Stock Portfolio with Dashboard
In continuation of previous post, now I try to implement the final chapter of the book, https://www.apress.com/gp/book/9781484257432.
The app is live: https://stock-portfolio.zoltanhalasz.net/
Zip repo of client side:
The back-end, which uses an In-Memory database, and Asp.Net Core Web Api, is based on Chapter 7, will use a Yahoo Finance Wrapper, and provide data to the Angular app, as shown in the Web API controller (see book starting with, pages 259).
The Asp.Net Core back-end is live under:
https://stockapi.zoltanhalasz.net/api/getstock
Zip Repo of server side:
I changed the back-end and front-end a little bit, correcting Typos, Formatting (indentation) and changing a little bit the main Stock class of the project.
The app is live: https://stock-portfolio.zoltanhalasz.net/
Zip repo of client side:
The back-end, which uses an In-Memory database, and Asp.Net Core Web Api, is based on Chapter 7, will use a Yahoo Finance Wrapper, and provide data to the Angular app, as shown in the Web API controller (see book starting with, pages 259).
The Asp.Net Core back-end is live under:
https://stockapi.zoltanhalasz.net/api/getstock
Zip Repo of server side:
I changed the back-end and front-end a little bit, correcting Typos, Formatting (indentation) and changing a little bit the main Stock class of the project.
HighCharts with Angular - Part 1 - Introduction
To continue the idea from the previous posts, my goal is to gather a number of libraries that can make my business applications look more professional, adding new functionalities and features.
One of these libraries is HighCharts, that can be integrated easily into Asp.Net and Angular (and Javascript) projects.
I would add here this great book which started my interest and whose projects I looked at and tried to implement some of its content.
The example attached will be an updated (corrected, as there are some typos and formatting issues in the book) version of the Chapter 5 example: Angular-Highcharts UI Application (page 125).
I added into my Angular App the following examples also: page 140 - 159: DrillDown Event, LegendItem Event, CheckBoxClick Event.
My application is online:
https://highcharts-angular1.zoltanhalasz.net/
Code can be downloaded :
(you have to add npm install after unzipping it)
The following routes are active:
A. adding a new student with marks:
https://highcharts-angular1.zoltanhalasz.net/addmarks
B. after adding them, their marks will be present under the main link
https://highcharts-angular1.zoltanhalasz.net/
C. events with checkbox
https://highcharts-angular1.zoltanhalasz.net/checkbox
D. events with legenditem click
https://highcharts-angular1.zoltanhalasz.net/legenditem
E. events with drilldown
https://highcharts-angular1.zoltanhalasz.net/drilldown
It also uses a back-end service live under the link: https://studentapi.zoltanhalasz.net/
this application also implements the .Net Wrapper of the Highchart, shown in example at the end of Chapter 5.
It is based on the book, starting with page 160, also adding an in-memory database to serve the data needed by the api controller. This will provide the back-end for the angular project above, and is called in the service typescript file of the angular project.
The code of the back-end is in this zip repo.
One of these libraries is HighCharts, that can be integrated easily into Asp.Net and Angular (and Javascript) projects.
I would add here this great book which started my interest and whose projects I looked at and tried to implement some of its content.
For any Angular project, we can add the HighChart libraries using the below npm commands:
1.
npm install highcharts –save
This command will add Highcharts dependencies into your project.
2.
npm install highcharts-angular –save
The Highcharts angular wrapper is open source. It provides vibrant
and dynamic feature visualization for Highcharts within an Angular
application.
The example attached will be an updated (corrected, as there are some typos and formatting issues in the book) version of the Chapter 5 example: Angular-Highcharts UI Application (page 125).
I added into my Angular App the following examples also: page 140 - 159: DrillDown Event, LegendItem Event, CheckBoxClick Event.
My application is online:
https://highcharts-angular1.zoltanhalasz.net/
Code can be downloaded :
(you have to add npm install after unzipping it)
The following routes are active:
A. adding a new student with marks:
https://highcharts-angular1.zoltanhalasz.net/addmarks
B. after adding them, their marks will be present under the main link
https://highcharts-angular1.zoltanhalasz.net/
C. events with checkbox
https://highcharts-angular1.zoltanhalasz.net/checkbox
D. events with legenditem click
https://highcharts-angular1.zoltanhalasz.net/legenditem
E. events with drilldown
https://highcharts-angular1.zoltanhalasz.net/drilldown
It also uses a back-end service live under the link: https://studentapi.zoltanhalasz.net/
this application also implements the .Net Wrapper of the Highchart, shown in example at the end of Chapter 5.
It is based on the book, starting with page 160, also adding an in-memory database to serve the data needed by the api controller. This will provide the back-end for the angular project above, and is called in the service typescript file of the angular project.
The code of the back-end is in this zip repo.
Saturday, March 7, 2020
Full Stack Mini ToDo-App With Angular, HttpClient, API Controller and In-Memory Database
This tutorial is an Angular version of the plain Javascript one.
The back-end will be exactly identical, for simplicity will call the API from the previous tutorial, please study the above link for this.
The application was created from scratch, new Angular 9 application, with some inspiration from the below Angular HttpClient tutorial.
Prerequisites:
1. you must have Angular 9 (or 7,8) installed on your PC
2. basic / intermediate javascript/typescript
3. review above mentioned tutorials. Web api for back-end is identical.
4. the front-end, styling belong to original tutorial from Dennis Ivy, I just transposed here to Angular.
5. basic tutorial for Angular, which teaches some of the steps: https://www.positronx.io/angular-7-httpclient-http-service/
Application is live under: https://angulartodo.zoltanhalasz.net/
Zip Repo: here.
Steps for creating the app:
1. ng new todolist
2. styling
add bootstrap: npm install bootstrap
css file include it in Angular.json
"styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.scss" ]
in the index html file, I will include some special fonts in the head
app.component.css will include the formatting from Dennis Ivy's original tutorial.(see repo)
3. create class
4. create service - I will not copy the lengthy code here.(see repo)
Some starting points: it is a very close copy of the api service from this tutorial (also mentioned above): https://www.positronx.io/angular-7-httpclient-http-service/
The Api link reference will be identical with the plain javascript tutorial.
5. add various references to app.module.ts
6. app component html
7. app.component.ts
The back-end will be exactly identical, for simplicity will call the API from the previous tutorial, please study the above link for this.
The application was created from scratch, new Angular 9 application, with some inspiration from the below Angular HttpClient tutorial.
Prerequisites:
1. you must have Angular 9 (or 7,8) installed on your PC
2. basic / intermediate javascript/typescript
3. review above mentioned tutorials. Web api for back-end is identical.
4. the front-end, styling belong to original tutorial from Dennis Ivy, I just transposed here to Angular.
5. basic tutorial for Angular, which teaches some of the steps: https://www.positronx.io/angular-7-httpclient-http-service/
Application is live under: https://angulartodo.zoltanhalasz.net/
Zip Repo: here.
Steps for creating the app:
1. ng new todolist
2. styling
add bootstrap: npm install bootstrap
css file include it in Angular.json
"styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.scss" ]
in the index html file, I will include some special fonts in the head
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet">
app.component.css will include the formatting from Dennis Ivy's original tutorial.(see repo)
3. create class
export class todomodel {
constructor (
public id: number,
public title: string,
public completed: boolean){
}
}
4. create service - I will not copy the lengthy code here.(see repo)
Some starting points: it is a very close copy of the api service from this tutorial (also mentioned above): https://www.positronx.io/angular-7-httpclient-http-service/
The Api link reference will be identical with the plain javascript tutorial.
'https://todolist.zoltanhalasz.net/api/TodoModels'
5. add various references to app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { TodoService } from './todo.service';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule
],
providers: [TodoService],
bootstrap: [AppComponent]
})
export class AppModule { }
6. app component html
<div class="container">
<div id="task-container">
<div id="form-wrapper">
<form id="form" (ngSubmit)="onSubmit()" #todoForm="ngForm">
<div class="flex-wrapper">
<div style="flex: 6">
<input type="text" class="form-control" id="title" required [(ngModel)]="mytodo.title" name="title"
#title="ngModel">
</div>
<div style="flex: 1">
<button type="submit" id="submit" class="btn">Submit</button>
</div>
</div>
</form>
</div>
<div id="list-wrapper">
<div class="task-wrapper flex-wrapper" *ngFor="let item of mytodolist">
<div style="flex:7">
<span [className]="item.completed ? 'finish-title' : 'title'"
(click)="FinishTodo(item.id)">{{item.title}}</span>
</div>
<div style="flex:1">
<button class="btn btn-sm btn-outline-info edit" (click)="Edit(item.id)">Edit </button>
</div>
<div style="flex:1">
<button class="btn btn-sm btn-outline-danger delete" (click)="Delete(item.id)">Delete</button>
</div>
</div>
</div>
</div>
</div>
<!-- <router-outlet></router-outlet> -->
7. app.component.ts
import { Component, OnInit } from '@angular/core';
import {todomodel} from "./todomodel";
import { TodoService } from './todo.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
title = 'todolist';
// when a task is being edited
edited = false;
// initialize sample todo
mytodo = new todomodel(0,'',false);
// this array will always store list of todos retrieved from server
mytodolist: todomodel [];
//injecting the dataservice
constructor (private dataservice: TodoService) {
}
// submitting the form
onSubmit() {
this.saveTodo(this.mytodo);
// resetting the mytodo value
this.mytodo = new todomodel(0,'',false);
}
saveTodo(mytodo: todomodel){
// if it is not an editing
if (!this.edited) {
if (this.mytodo.title=='') return;
// saving new todo
this.dataservice.createTodo(mytodo).subscribe(data=> {
this.displayTodoList();
});
}
// if we are editing an existing todo
else {
this.edited=false;
console.log('this is being edited',mytodo);
// update existing todo
this.dataservice.updateTodo(this.mytodo.id,this.mytodo).subscribe(data =>
{
this.displayTodoList();
}
);
}
}
ngOnInit(){
this.displayTodoList();
}
//this function retrieves the whole array of todos from server, using api service injected
displayTodoList() {
this.dataservice.getTodoList().subscribe(data =>
{
// as the Web Api doesn't sort the list of todos, we do here in the frontend
this.mytodolist = data.sort((a,b)=> {
if (a.id>b.id) return -1;
if (a.id<b.id) return 1;
});
console.log('display', this.mytodolist);
});
}
//deleting an existing todo
Delete(id: number) { // without type info
console.log('delete', id);
this.dataservice.deleteTodo(id).subscribe(data =>{
this.displayTodoList();
});
}
//editing an existing todo
Edit(eid: number) { // without type info
console.log('editing',eid);
this.mytodo = this.mytodolist.filter(x=>x.id ==eid)[0];
this.edited = true;
}
//finalizing(crossing) an existing todo
FinishTodo(eid: number) { // without type info
// console.log('finishing', eid);
const mytodofinished = this.mytodolist.filter(x=>x.id ==eid )[0];
mytodofinished.completed = !mytodofinished.completed ;
//calling the update observable
this.dataservice.updateTodo(eid,mytodofinished).subscribe(data =>{
this.displayTodoList();
});
}
}
Thursday, February 27, 2020
Full Stack Asp.Net Core App (Bootcamp Project) - Part 3 - The Web API
This is the continuation of the part 2 of this series.
Once again, the repository for the full app can be accessed on Github.
The Notes page will be containing most of the operations of the whole application. The notes are displayed via html/css using plain javascript Dom manipulation, and some back-end code in the Web Api Part. The data stored in database is retrieved via Fetch Api, and displayed in the page.
Then the posting of notes accesses the Web API also using Fetch, so does the update and delete ones.
I strongly recommend reviewing the below recommended tutorials before checking this part.
For Ajax/DOM manipulation
1. https://mydev-journey.blogspot.com/2020/02/mybooklist-another-traversy-tutorial.html
2. https://mydev-journey.blogspot.com/2020/02/expense-tracker-from-traversy-media.html
3. https://mydev-journey.blogspot.com/2020/02/full-stack-mini-todo-app-with.html
For the Web api part:
1. the above three tutorials + https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-javascript?view=aspnetcore-3.1
2. https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio
So here I will write the code for the Web Apis created using EF CRUD (scaffolded automatically using the models presented in previous post, and then edited for additions).
The API controllers are in the Controller folders.
Users - it's completely Scaffolded from the Users Model Class, using the context, no additions written manually.
Notes:
contains the CRUD operations to create, delete, edit notes.
I recognize, maybe more checks and verification could have been done in the action methods, that could be your homework to do.
Notes Controller
namespace SmartNotes.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class NotesController : ControllerBase
{
private readonly SmartNotesDBContext _context;
public NotesController(SmartNotesDBContext context)
{
_context = context;
}
// GET: api/Notes
[HttpGet]
public async Task<ActionResult<IEnumerable<Notes>>> GetNotes()
{
return await _context.Notes.ToListAsync();
}
// GET: api/Notes/5
[HttpGet("{id}")]
public async Task<ActionResult<Notes>> GetNotes(int id)
{
var notes = await _context.Notes.FindAsync(id);
if (notes == null)
{
return NotFound();
}
return notes;
}
// this is a very important Get action method- retrieving list of notes by user, order and searchstring
[HttpGet("notesbyuser/{userid}/{order}/{searchstring}")]
public async Task<ActionResult<List<Notes>>> GetNotesByUser(int userid, string order="Desc", string searchstring="")
{
var notes = new List<Notes>();
if (searchstring == "(empty)") searchstring = "";
searchstring = searchstring.ToLower();
if (order=="Desc")
{
notes = await _context.Notes.Where(x => x.Userid == userid).OrderBy(x => x.Pinned).ThenByDescending(x=>x.Createdat).ToListAsync();
}
else
{
notes = await _context.Notes.Where(x => x.Userid == userid).OrderBy(x => x.Pinned).ThenBy(x => x.Createdat).ToListAsync();
}
if (notes == null)
{
return NotFound();
}
return notes.Where(x=> x.Title.ToLower().Contains(searchstring) || x.NoteText.ToLower().Contains(searchstring)).ToList();
}
// PUT: api/Notes/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPut("{id}")]
public async Task<IActionResult> PutNotes(int id, Notes notes)
{
if (id != notes.Id)
{
return BadRequest();
}
_context.Entry(notes).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!NotesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Notes
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPost]
public async Task<ActionResult<Notes>> PostNotes(Notes notes)
{
_context.Notes.Add(notes);
await _context.SaveChangesAsync();
return CreatedAtAction("PostNotes", new { id = notes.Id }, notes);
}
// action to pin note
[HttpPost("pinnote/{noteid}")]
public async Task<ActionResult<Notes>> PinNote(int noteid)
{
var myNote = await _context.Notes.FindAsync(noteid);
if (myNote!=null)
{
myNote.Pinned = !myNote.Pinned;
_context.Notes.Update(myNote);
await _context.SaveChangesAsync();
return Ok();
}
return Ok();
}
// action to change the color of a note
[HttpPut("changecolor/{noteid}")]
public async Task<ActionResult<Notes>> ChangeColor(int noteid, Notes notes)
{
var myNote = await _context.Notes.FindAsync(noteid);
if (myNote != null)
{
myNote.Color = notes.Color;
_context.Notes.Update(myNote);
await _context.SaveChangesAsync();
return Ok();
}
return Ok();
}
// a put action to update a note, by id
[HttpPut("updatenote/{noteid}")]
public async Task<ActionResult<Notes>> UpdateNote(int noteid, Notes notes)
{
var myNote = await _context.Notes.FindAsync(noteid);
if (myNote != null)
{
myNote.Title = notes.Title;
myNote.NoteText = notes.NoteText;
_context.Notes.Update(myNote);
await _context.SaveChangesAsync();
return Ok();
}
return Ok();
}
// DELETE: api/Notes/5
// action to delete the note and respective images, by note id
[HttpDelete("{id}")]
public async Task<ActionResult<Notes>> DeleteNotes(int id)
{
var images = await _context.Images.Where(x => x.Noteid == id).ToListAsync();
foreach (var img in images)
{
var filepath =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{img.Image}";
System.IO.File.Delete(filepath);
}
if (images!=null) _context.Images.RemoveRange(images);
var notes = await _context.Notes.FindAsync(id);
if (notes == null)
{
return NotFound();
}
_context.Notes.Remove(notes);
await _context.SaveChangesAsync();
return Ok();
}
private bool NotesExists(int id)
{
return _context.Notes.Any(e => e.Id == id);
}
}
}
Image Controller - will deal with uploading/deleting images
namespace SmartNotes.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ImagesController : ControllerBase
{
private readonly SmartNotesDBContext _context;
public ImagesController(SmartNotesDBContext context)
{
_context = context;
}
// GET: api/Images
[HttpGet]
public async Task<ActionResult<IEnumerable<Images>>> GetImages()
{
return await _context.Images.ToListAsync();
}
// GET: api/Images/5
[HttpGet("{id}")]
public async Task<ActionResult<Images>> GetImages(int id)
{
var images = await _context.Images.FindAsync(id);
if (images == null)
{
return NotFound();
}
return images;
}
// retrieves all images by note id (to display them in the note)
[HttpGet("imagesbynote/{noteid}")]
public async Task<ActionResult<List<Images>>> GetImagesByNote(int noteid)
{
var images = await _context.Images.Where(x=> x.Noteid ==noteid).ToListAsync();
if (images == null)
{
return NotFound();
}
return images;
}
// retrieves all images by user id (to display them in the note page)
[HttpGet("imagesbyuser/{userid}")]
public async Task<ActionResult<List<Images>>> GetImagesByUser(int userid)
{
var images = await _context.Images.ToListAsync();
if (images == null)
{
return NotFound();
}
return images;
}
// PUT: api/Images/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPut("{id}")]
public async Task<IActionResult> PutImages(int id, Images images)
{
if (id != images.Id)
{
return BadRequest();
}
_context.Entry(images).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ImagesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Images
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
//[HttpPost]
//public async Task<ActionResult<Images>> PostImages(Images images)
//{
// _context.Images.Add(images);
// await _context.SaveChangesAsync();
// return CreatedAtAction("GetImages", new { id = images.Id }, images);
//}
// uploading one image, and link it to note having noteid
[HttpPost("uploadimage/{noteid}")]
public async Task<ActionResult<Images>> PostUpload( int noteid, IFormFile image)
{
if (image != null && noteid!=0 && image.Length > 0 && image.Length < 500000)
{
try
{
var fileName = Path.GetFileName(image.FileName);
//Assigning Unique Filename (Guid)
var myUniqueFileName = Convert.ToString(Guid.NewGuid());
//Getting file Extension
var fileExtension = Path.GetExtension(fileName);
// concatenating FileName + FileExtension
var newFileName = String.Concat(myUniqueFileName, fileExtension);
// Combines two strings into a path.
var filepath =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{newFileName}";
using (FileStream fs = System.IO.File.Create(filepath))
{
image.CopyTo(fs);
fs.Flush();
}
var newImage = new Images();
newImage.Image = newFileName;
newImage.Noteid = noteid;
_context.Images.Add(newImage);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
return StatusCode(500);
}
//Getting FileName
var myImageList = await _context.Images.Where(x => x.Noteid == noteid).ToListAsync();
return Ok(myImageList);
}
return NoContent();
}
// DELETE: api/Images/5
[HttpDelete("{id}")]
public async Task<ActionResult<Images>> DeleteImages(int id)
{
var images = await _context.Images.FindAsync(id);
if (images == null)
{
return NotFound();
}
_context.Images.Remove(images);
await _context.SaveChangesAsync();
return images;
}
// delete images by note, when removing a note
[HttpDelete("deleteimagesbynote/{noteid}")]
public async Task<ActionResult<Images>> DeleteImagesByNote(int noteid)
{
var images = await _context.Images.Where(x=> x.Noteid == noteid).ToListAsync();
if (images == null)
{
return NotFound();
}
foreach (var img in images)
{
deleteImage(img.Image);
_context.Images.Remove(img);
}
await _context.SaveChangesAsync();
return Ok();
}
private void deleteImage(string imagefile)
{
var filepath =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{imagefile}";
System.IO.File.Delete(filepath);
}
private bool ImagesExists(int id)
{
return _context.Images.Any(e => e.Id == id);
}
}
}
Once again, the repository for the full app can be accessed on Github.
The Notes page will be containing most of the operations of the whole application. The notes are displayed via html/css using plain javascript Dom manipulation, and some back-end code in the Web Api Part. The data stored in database is retrieved via Fetch Api, and displayed in the page.
Then the posting of notes accesses the Web API also using Fetch, so does the update and delete ones.
I strongly recommend reviewing the below recommended tutorials before checking this part.
For Ajax/DOM manipulation
1. https://mydev-journey.blogspot.com/2020/02/mybooklist-another-traversy-tutorial.html
2. https://mydev-journey.blogspot.com/2020/02/expense-tracker-from-traversy-media.html
3. https://mydev-journey.blogspot.com/2020/02/full-stack-mini-todo-app-with.html
For the Web api part:
1. the above three tutorials + https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-javascript?view=aspnetcore-3.1
2. https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio
So here I will write the code for the Web Apis created using EF CRUD (scaffolded automatically using the models presented in previous post, and then edited for additions).
The API controllers are in the Controller folders.
Users - it's completely Scaffolded from the Users Model Class, using the context, no additions written manually.
Notes:
contains the CRUD operations to create, delete, edit notes.
I recognize, maybe more checks and verification could have been done in the action methods, that could be your homework to do.
Notes Controller
namespace SmartNotes.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class NotesController : ControllerBase
{
private readonly SmartNotesDBContext _context;
public NotesController(SmartNotesDBContext context)
{
_context = context;
}
// GET: api/Notes
[HttpGet]
public async Task<ActionResult<IEnumerable<Notes>>> GetNotes()
{
return await _context.Notes.ToListAsync();
}
// GET: api/Notes/5
[HttpGet("{id}")]
public async Task<ActionResult<Notes>> GetNotes(int id)
{
var notes = await _context.Notes.FindAsync(id);
if (notes == null)
{
return NotFound();
}
return notes;
}
// this is a very important Get action method- retrieving list of notes by user, order and searchstring
[HttpGet("notesbyuser/{userid}/{order}/{searchstring}")]
public async Task<ActionResult<List<Notes>>> GetNotesByUser(int userid, string order="Desc", string searchstring="")
{
var notes = new List<Notes>();
if (searchstring == "(empty)") searchstring = "";
searchstring = searchstring.ToLower();
if (order=="Desc")
{
notes = await _context.Notes.Where(x => x.Userid == userid).OrderBy(x => x.Pinned).ThenByDescending(x=>x.Createdat).ToListAsync();
}
else
{
notes = await _context.Notes.Where(x => x.Userid == userid).OrderBy(x => x.Pinned).ThenBy(x => x.Createdat).ToListAsync();
}
if (notes == null)
{
return NotFound();
}
return notes.Where(x=> x.Title.ToLower().Contains(searchstring) || x.NoteText.ToLower().Contains(searchstring)).ToList();
}
// PUT: api/Notes/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPut("{id}")]
public async Task<IActionResult> PutNotes(int id, Notes notes)
{
if (id != notes.Id)
{
return BadRequest();
}
_context.Entry(notes).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!NotesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Notes
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPost]
public async Task<ActionResult<Notes>> PostNotes(Notes notes)
{
_context.Notes.Add(notes);
await _context.SaveChangesAsync();
return CreatedAtAction("PostNotes", new { id = notes.Id }, notes);
}
// action to pin note
[HttpPost("pinnote/{noteid}")]
public async Task<ActionResult<Notes>> PinNote(int noteid)
{
var myNote = await _context.Notes.FindAsync(noteid);
if (myNote!=null)
{
myNote.Pinned = !myNote.Pinned;
_context.Notes.Update(myNote);
await _context.SaveChangesAsync();
return Ok();
}
return Ok();
}
// action to change the color of a note
[HttpPut("changecolor/{noteid}")]
public async Task<ActionResult<Notes>> ChangeColor(int noteid, Notes notes)
{
var myNote = await _context.Notes.FindAsync(noteid);
if (myNote != null)
{
myNote.Color = notes.Color;
_context.Notes.Update(myNote);
await _context.SaveChangesAsync();
return Ok();
}
return Ok();
}
// a put action to update a note, by id
[HttpPut("updatenote/{noteid}")]
public async Task<ActionResult<Notes>> UpdateNote(int noteid, Notes notes)
{
var myNote = await _context.Notes.FindAsync(noteid);
if (myNote != null)
{
myNote.Title = notes.Title;
myNote.NoteText = notes.NoteText;
_context.Notes.Update(myNote);
await _context.SaveChangesAsync();
return Ok();
}
return Ok();
}
// DELETE: api/Notes/5
// action to delete the note and respective images, by note id
[HttpDelete("{id}")]
public async Task<ActionResult<Notes>> DeleteNotes(int id)
{
var images = await _context.Images.Where(x => x.Noteid == id).ToListAsync();
foreach (var img in images)
{
var filepath =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{img.Image}";
System.IO.File.Delete(filepath);
}
if (images!=null) _context.Images.RemoveRange(images);
var notes = await _context.Notes.FindAsync(id);
if (notes == null)
{
return NotFound();
}
_context.Notes.Remove(notes);
await _context.SaveChangesAsync();
return Ok();
}
private bool NotesExists(int id)
{
return _context.Notes.Any(e => e.Id == id);
}
}
}
Image Controller - will deal with uploading/deleting images
namespace SmartNotes.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ImagesController : ControllerBase
{
private readonly SmartNotesDBContext _context;
public ImagesController(SmartNotesDBContext context)
{
_context = context;
}
// GET: api/Images
[HttpGet]
public async Task<ActionResult<IEnumerable<Images>>> GetImages()
{
return await _context.Images.ToListAsync();
}
// GET: api/Images/5
[HttpGet("{id}")]
public async Task<ActionResult<Images>> GetImages(int id)
{
var images = await _context.Images.FindAsync(id);
if (images == null)
{
return NotFound();
}
return images;
}
// retrieves all images by note id (to display them in the note)
[HttpGet("imagesbynote/{noteid}")]
public async Task<ActionResult<List<Images>>> GetImagesByNote(int noteid)
{
var images = await _context.Images.Where(x=> x.Noteid ==noteid).ToListAsync();
if (images == null)
{
return NotFound();
}
return images;
}
// retrieves all images by user id (to display them in the note page)
[HttpGet("imagesbyuser/{userid}")]
public async Task<ActionResult<List<Images>>> GetImagesByUser(int userid)
{
var images = await _context.Images.ToListAsync();
if (images == null)
{
return NotFound();
}
return images;
}
// PUT: api/Images/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPut("{id}")]
public async Task<IActionResult> PutImages(int id, Images images)
{
if (id != images.Id)
{
return BadRequest();
}
_context.Entry(images).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ImagesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Images
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
//[HttpPost]
//public async Task<ActionResult<Images>> PostImages(Images images)
//{
// _context.Images.Add(images);
// await _context.SaveChangesAsync();
// return CreatedAtAction("GetImages", new { id = images.Id }, images);
//}
// uploading one image, and link it to note having noteid
[HttpPost("uploadimage/{noteid}")]
public async Task<ActionResult<Images>> PostUpload( int noteid, IFormFile image)
{
if (image != null && noteid!=0 && image.Length > 0 && image.Length < 500000)
{
try
{
var fileName = Path.GetFileName(image.FileName);
//Assigning Unique Filename (Guid)
var myUniqueFileName = Convert.ToString(Guid.NewGuid());
//Getting file Extension
var fileExtension = Path.GetExtension(fileName);
// concatenating FileName + FileExtension
var newFileName = String.Concat(myUniqueFileName, fileExtension);
// Combines two strings into a path.
var filepath =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{newFileName}";
using (FileStream fs = System.IO.File.Create(filepath))
{
image.CopyTo(fs);
fs.Flush();
}
var newImage = new Images();
newImage.Image = newFileName;
newImage.Noteid = noteid;
_context.Images.Add(newImage);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
return StatusCode(500);
}
//Getting FileName
var myImageList = await _context.Images.Where(x => x.Noteid == noteid).ToListAsync();
return Ok(myImageList);
}
return NoContent();
}
// DELETE: api/Images/5
[HttpDelete("{id}")]
public async Task<ActionResult<Images>> DeleteImages(int id)
{
var images = await _context.Images.FindAsync(id);
if (images == null)
{
return NotFound();
}
_context.Images.Remove(images);
await _context.SaveChangesAsync();
return images;
}
// delete images by note, when removing a note
[HttpDelete("deleteimagesbynote/{noteid}")]
public async Task<ActionResult<Images>> DeleteImagesByNote(int noteid)
{
var images = await _context.Images.Where(x=> x.Noteid == noteid).ToListAsync();
if (images == null)
{
return NotFound();
}
foreach (var img in images)
{
deleteImage(img.Image);
_context.Images.Remove(img);
}
await _context.SaveChangesAsync();
return Ok();
}
private void deleteImage(string imagefile)
{
var filepath =
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{imagefile}";
System.IO.File.Delete(filepath);
}
private bool ImagesExists(int id)
{
return _context.Images.Any(e => e.Id == id);
}
}
}
Wednesday, February 26, 2020
Full Stack Asp.Net Core App (Bootcamp Project) - Part 2 - The Database and (Razor) Pages
This is the continuation of the material in this link: https://mydev-journey.blogspot.com/2020/02/full-stack-aspnet-core-app-bootcamp.html
Database and entities/models
The main entities of the database will be:
- users: will store the username and their password(not encrypted! bad practice), and their id
- notes: title, content, userid, color
- images: noteid, file name.
Let's have a look at the database script, which defines the relationships.
Using EF Core, the database is scaffolded into a Model folder.
The model classes will look in the following way(as scaffolded by EF Core):
public partial class Users
{
public Users()
{
Notes = new HashSet<Notes>();
}
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<Notes> Notes { get; set; }
// I will use this to store the confirmed password, not save in the DB
[NotMapped]
public string Password2 { get; set; }
}
public partial class Notes
{
public Notes()
{
Images = new HashSet<Images>();
}
public int Id { get; set; }
public int Userid { get; set; }
public string Title { get; set; }
public string NoteText { get; set; }
public DateTime Createdat { get; set; }
public bool Pinned { get; set; }
public string Color { get; set; }
public virtual Users User { get; set; }
public virtual ICollection<Images> Images { get; set; }
}
public partial class Images
{
public int Id { get; set; }
public int Noteid { get; set; }
public string Image { get; set; }
public virtual Notes Note { get; set; }
}
For an existing database, it can be scaffolded into a context and models using the following instructions.
The Github Repo of the project is here.
The structure of wwwroot folder:
css: will contain the manually written css files for each served page
images: will contain the images that belong to the html of the pages
js and lib: can be empty.
uploads: will contain the result of the uploads, images that will appear in each note.
The Pages
The pages are served by Asp.Net Core Razor pages, which is the basic project in Asp.Net Core. (version used here is 3.1, LTS). Each page will have its own css file, present in wwwroot css folder. Their respective html code will be in the cshtml of the Razor Page, only the Notes Page having lots of Javascript included also. I recommend very strongly that you review Razor Pages because they are serving the pages.
The CSS and Html
The Css was written manually (I will not go in detail here) together with the Html, according to a design template. It defines the structure of the files in the view part of the pages below.
Each html file will have its own css. The Layouts will be null in each of the below Razor Pages. You can see the html/css in the Github repo, although this was a part of the bootcamp, I will not go through them.
I will insert more comments here on the blog, than in the Github, to make it more understandable. I will not build the pages step by step, instead just show the comments and explanations regarding the code.
a. Index Page
The PageModel code: - nothing special, here.
The html, you can find on the Github Demo.
b. Signup Page
public class SignUpModel : PageModel
{
private readonly SmartNotesDBContext _context;
public SignUpModel(SmartNotesDBContext context)
{
_context = context;
}
// below property will contain the user that will be created, linked to the page using binding
[BindProperty]
public Users newUser { get; set; }
// below property will be used to display an error message, linked to the page using binding
[BindProperty]
public string errorMessage { get; set; }
// this will display the error message if the user signup did not work well
public void OnGet(string err)
{
errorMessage = err;
}
// basic email validation function
bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
// checks if any other user has the same email, which have to be unique in the database.
bool IsExistingEmail(string email)
{
return _context.Users.Any(x => x.Email == email);
}
// posting the form on the SignUp page, and collecting /saving the user data in the database.
public async Task<IActionResult> OnPost()
{
newUser.Email = newUser.Email.Trim();
if (!IsValidEmail(newUser.Email))
{
errorMessage = "Use a valid email address!";
return RedirectToPage("./SignUp", new { err = errorMessage});
}
if (IsExistingEmail(newUser.Email))
{
errorMessage = "This Email Address has already been used!";
return RedirectToPage("./SignUp", new { err = errorMessage });
}
if (newUser.Password!=newUser.Password2)
{
errorMessage = "The passwords do not match!";
return RedirectToPage("./SignUp", new { err = errorMessage });
}
try
{
await _context.Users.AddAsync(newUser);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
// error message is generated and page redirected
errorMessage = "Error with signup. Please try again later.";
return RedirectToPage("./SignUp", new { err = errorMessage });
}
// when signup was sucessful, user will be redirected to login.
return RedirectToPage("./Login");
}
}
c. Login Page
public class LoginModel : PageModel
{
private readonly SmartNotesDBContext _context;
public LoginModel(SmartNotesDBContext context)
{
_context = context;
}
// the user who tries to log in
[BindProperty]
public Users LoginUser { get; set; }
// the error message which will be shown in the html in case of unsuccesful attempt
[BindProperty]
public string errorMessage { get; set; }
public void OnGet(string err)
{
errorMessage = err;
}
// login, posting the form
public async Task<IActionResult> OnPost()
{
// try to find the user in the table having email and password provided
var myUser = new Users();
try
{
myUser = await _context.Users.FirstAsync(x => x.Email == LoginUser.Email && x.Password == LoginUser.Password);
}
catch (Exception ex)
{
errorMessage = "Invalid User/Password";
// if no user found, error message shown on page in the form.
return RedirectToPage("./Login", new { err = errorMessage });
}
// save the user in the session
SessionHelper.SetObjectAsJson(HttpContext.Session, "loginuser", myUser);
// if user found, it's logged in and redirected to notes.
return RedirectToPage("./Notes");
}
}
d. Notes Page
public class NotesModel : PageModel
{
// this user id will be used in the html/javascript of the page
public int LoginUserID { get; set; }
// this email address will be used in the html/javascript of the page
public string LoginUserEmail { get; set; }
// will take the session value of the logged in user and serve the page. if no user is logged in, will redirect to Login page.
public async Task<IActionResult> OnGet()
{
//check if the user arrived here using the login page, then having the loginuser properly setup
var loginuser = SessionHelper.GetObjectFromJson<Users>(HttpContext.Session, "loginuser");
// if no user logged in using login page, redirect to login
if (loginuser == null)
{
return RedirectToPage("./Login");
}
//just pickup the user id and email to show it on the page (and use them in the js code), see html code
LoginUserID = loginuser.Id;
LoginUserEmail = loginuser.Email;
return Page();
}
}
e. Sign out
public class LogoutModel : PageModel
{
public IActionResult OnGet()
{
// logoout page deleting the logged in user and redirecting to main page.
SessionHelper.SetObjectAsJson(HttpContext.Session, "loginuser", null);
return RedirectToPage("./Index");
}
}
f. Error page
this is mainly a notfound page, written in html/css. Nothing special on the PageModel code.
The Web API back-end of the application will dealt with the CRUD operations, and this will be dealt in the next post.
Database and entities/models
The main entities of the database will be:
- users: will store the username and their password(not encrypted! bad practice), and their id
- notes: title, content, userid, color
- images: noteid, file name.
Let's have a look at the database script, which defines the relationships.
Using EF Core, the database is scaffolded into a Model folder.
The model classes will look in the following way(as scaffolded by EF Core):
public partial class Users
{
public Users()
{
Notes = new HashSet<Notes>();
}
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<Notes> Notes { get; set; }
// I will use this to store the confirmed password, not save in the DB
[NotMapped]
public string Password2 { get; set; }
}
public partial class Notes
{
public Notes()
{
Images = new HashSet<Images>();
}
public int Id { get; set; }
public int Userid { get; set; }
public string Title { get; set; }
public string NoteText { get; set; }
public DateTime Createdat { get; set; }
public bool Pinned { get; set; }
public string Color { get; set; }
public virtual Users User { get; set; }
public virtual ICollection<Images> Images { get; set; }
}
public partial class Images
{
public int Id { get; set; }
public int Noteid { get; set; }
public string Image { get; set; }
public virtual Notes Note { get; set; }
}
For an existing database, it can be scaffolded into a context and models using the following instructions.
The Github Repo of the project is here.
The structure of wwwroot folder:
css: will contain the manually written css files for each served page
images: will contain the images that belong to the html of the pages
js and lib: can be empty.
uploads: will contain the result of the uploads, images that will appear in each note.
The Pages
The pages are served by Asp.Net Core Razor pages, which is the basic project in Asp.Net Core. (version used here is 3.1, LTS). Each page will have its own css file, present in wwwroot css folder. Their respective html code will be in the cshtml of the Razor Page, only the Notes Page having lots of Javascript included also. I recommend very strongly that you review Razor Pages because they are serving the pages.
The CSS and Html
The Css was written manually (I will not go in detail here) together with the Html, according to a design template. It defines the structure of the files in the view part of the pages below.
Each html file will have its own css. The Layouts will be null in each of the below Razor Pages. You can see the html/css in the Github repo, although this was a part of the bootcamp, I will not go through them.
I will insert more comments here on the blog, than in the Github, to make it more understandable. I will not build the pages step by step, instead just show the comments and explanations regarding the code.
a. Index Page
The PageModel code: - nothing special, here.
The html, you can find on the Github Demo.
b. Signup Page
public class SignUpModel : PageModel
{
private readonly SmartNotesDBContext _context;
public SignUpModel(SmartNotesDBContext context)
{
_context = context;
}
// below property will contain the user that will be created, linked to the page using binding
[BindProperty]
public Users newUser { get; set; }
// below property will be used to display an error message, linked to the page using binding
[BindProperty]
public string errorMessage { get; set; }
// this will display the error message if the user signup did not work well
public void OnGet(string err)
{
errorMessage = err;
}
// basic email validation function
bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
// checks if any other user has the same email, which have to be unique in the database.
bool IsExistingEmail(string email)
{
return _context.Users.Any(x => x.Email == email);
}
// posting the form on the SignUp page, and collecting /saving the user data in the database.
public async Task<IActionResult> OnPost()
{
newUser.Email = newUser.Email.Trim();
if (!IsValidEmail(newUser.Email))
{
errorMessage = "Use a valid email address!";
return RedirectToPage("./SignUp", new { err = errorMessage});
}
if (IsExistingEmail(newUser.Email))
{
errorMessage = "This Email Address has already been used!";
return RedirectToPage("./SignUp", new { err = errorMessage });
}
if (newUser.Password!=newUser.Password2)
{
errorMessage = "The passwords do not match!";
return RedirectToPage("./SignUp", new { err = errorMessage });
}
try
{
await _context.Users.AddAsync(newUser);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
// error message is generated and page redirected
errorMessage = "Error with signup. Please try again later.";
return RedirectToPage("./SignUp", new { err = errorMessage });
}
// when signup was sucessful, user will be redirected to login.
return RedirectToPage("./Login");
}
}
c. Login Page
public class LoginModel : PageModel
{
private readonly SmartNotesDBContext _context;
public LoginModel(SmartNotesDBContext context)
{
_context = context;
}
// the user who tries to log in
[BindProperty]
public Users LoginUser { get; set; }
// the error message which will be shown in the html in case of unsuccesful attempt
[BindProperty]
public string errorMessage { get; set; }
public void OnGet(string err)
{
errorMessage = err;
}
// login, posting the form
public async Task<IActionResult> OnPost()
{
// try to find the user in the table having email and password provided
var myUser = new Users();
try
{
myUser = await _context.Users.FirstAsync(x => x.Email == LoginUser.Email && x.Password == LoginUser.Password);
}
catch (Exception ex)
{
errorMessage = "Invalid User/Password";
// if no user found, error message shown on page in the form.
return RedirectToPage("./Login", new { err = errorMessage });
}
// save the user in the session
SessionHelper.SetObjectAsJson(HttpContext.Session, "loginuser", myUser);
// if user found, it's logged in and redirected to notes.
return RedirectToPage("./Notes");
}
}
d. Notes Page
public class NotesModel : PageModel
{
// this user id will be used in the html/javascript of the page
public int LoginUserID { get; set; }
// this email address will be used in the html/javascript of the page
public string LoginUserEmail { get; set; }
// will take the session value of the logged in user and serve the page. if no user is logged in, will redirect to Login page.
public async Task<IActionResult> OnGet()
{
//check if the user arrived here using the login page, then having the loginuser properly setup
var loginuser = SessionHelper.GetObjectFromJson<Users>(HttpContext.Session, "loginuser");
// if no user logged in using login page, redirect to login
if (loginuser == null)
{
return RedirectToPage("./Login");
}
//just pickup the user id and email to show it on the page (and use them in the js code), see html code
LoginUserID = loginuser.Id;
LoginUserEmail = loginuser.Email;
return Page();
}
}
e. Sign out
public class LogoutModel : PageModel
{
public IActionResult OnGet()
{
// logoout page deleting the logged in user and redirecting to main page.
SessionHelper.SetObjectAsJson(HttpContext.Session, "loginuser", null);
return RedirectToPage("./Index");
}
}
f. Error page
this is mainly a notfound page, written in html/css. Nothing special on the PageModel code.
The Web API back-end of the application will dealt with the CRUD operations, and this will be dealt in the next post.
Subscribe to:
Posts (Atom)









