This is the continuation of the part 2 of this series.(https://dev.to/zoltanhalasz/full-stack-asp-net-core-app-bootcamp-project-part-2-the-database-and-razor-pages-2h2k)
Once again, the repository for the full app can be accessed on Github. https://github.com/zoltanhalasz/SmartNotes
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 tutorials before checking this part.
For Ajax/DOM manipulation
- https://dev.to/zoltanhalasz/two-traversymedia-tutorials-i-added-asp-net-core-back-end-hma
- https://dev.to/zoltanhalasz/full-stack-mini-todo-app-with-javascript-ajax-api-controller-and-in-memory-database-asp-net-core-razor-pages-2bbf
For the Web api part:
- the above two tutorials + https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-javascript?view=aspnetcore-3.1
- 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.
There are some additional comments here vs the Github version, for better understanding. Feel free to comment or ask any questions.
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);
}
}
}
Top comments (2)
Could you please format your big expressions to something more readable?
like this maybe:
notes = await _context.Notes
.Where(x => x.Userid == userid)
.OrderBy(x => x.Pinned)
.ThenByDescending(x=>x.Createdat).ToListAsync();
OK, that's a good point. Are you an experienced .net developer? maybe you have some other good ideas for me for the future, as I want to get a job soon.