0

I am new to ASP.NET, ASP.NET MVC and Entity Framework (generally) and with the .NET Core variants (specifically). Currently I am trying to get my first example/test project running (in Visual Studio 2015) but having a couple of problems I couldn't find solutions for on Google.

Part of the tutorials & instructions I followed so far:

Those tutorials & instructions only describe a snippet of the solution each but those snippets do not fit together and cause problems. So I am trying to get the missing pieces together.

What I want to achieve is a (as simple as possible) example project:

  • ASP.NET Core Web API demo/example project (in Visual Studio 2015)
  • which stores data in a (SQL) database (not some handwritten repository) using Entity Framework Core (just 1 table Person holding 3 columns: id as primary key identity, 2 columns firstname and lastname defined as nvarchar(30))
  • where one can
    • request (GET) all persons (WORKS in the code below)
    • (GET) a specific person by id or by lastname (works in the code below)
    • create (POST) a new person (works in the code below)
    • (DELETE) a person by id (works in the code below)
    • full replace (PUT) by id (HOW TO DO?)
    • modify (PATCH) the last name (people still marry) only sending id and new last name (HOW TO DO?)
  • using a repository between the controller and the dbContext (for reusability of the repository functions)
  • have the controller to be standard conform (return correct error code/error results)
  • have working exception handling

My implementation in question:

IPersonRepository interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PersonExample.Models;

namespace PersonExample.Repository
{
    public interface IPersonRepositoy
    {
        IEnumerable<Person> GetAll();
        Person GetById(int id);
        IEnumerable<Person> GetByLastname(string lastname);
        IEnumerable<Person> SearchByLastname(string namePart);

        int Create(Person item);
        int Delete(int id);
        int Replace(int id, Person item);
        int Modify(int id, string newLastname);
    }
}

PersonRepository implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PersonExample.Models;

namespace PersonExample.Repository
{
    public class PersonRepository : IPersonRepositoy
    {
        private readonly PersonDbContext _dbContext;
        private readonly ILogger<PersonRepository> _logger;

        public PersonRepository(PersonDbContext dbContext, ILogger<PersonRepository> logger)
        {
            _dbContext = dbContext;
            _logger = logger;
        }

        public IEnumerable<Person> GetAll()
        {
            //always returns an IEnumerable (even if it is empty)
            _logger.LogDebug(string.Format("{0}.GetAll()", GetType().Name));
            return _dbContext.Person;
        }

        public Person GetById(int id)
        {
            //SingleOrDefault() returns an instance of Person or null 
            _logger.LogDebug(string.Format("{0}.GetById({1})", GetType().Name, id));
            return _dbContext.Person.Where(i => i.Id == id).SingleOrDefault();
        }

        public IEnumerable<Person> GetByLastname(string lastname)
        {
            //always returns an IEnumerable (even if it is empty)
            _logger.LogDebug(string.Format("{0}.GetByLastname({1})", GetType().Name, lastname));
            return _dbContext.Person.Where(i => i.Lastname == lastname);
        }

        public IEnumerable<Person> SearchByLastname(string namePart)
        {
            //always returns an IEnumerable (even if it is empty)
            _logger.LogDebug(string.Format("{0}.SearchByLastname({1})", GetType().Name, namePart));
            return _dbContext.Person.Where(i => i.Lastname.Contains(namePart));
        }

        public int Create(Person item)
        {
            _logger.LogDebug(string.Format("{0}.Create({1}) (id: {2}, firstname: {3}, lastname: {4})", 
                    GetType().Name, item, item.Id, item.Firstname, item.Lastname));
            //Add seems to be atomic > Attach would save linked objects too but seems to fail on simple objects
            //what exceptions could occur to catch somewhere else (e.g. if lastname would have a unique constraint)?
            _dbContext.Person.Add(item);
            int res;

            try 
            {
                res = _dbContext.SaveChanges();
            } 
            catch (Microsoft.EntityFrameworkCore.DbUpdateException e)
            {
                _logger.LogError(string.Format("", GetType().Name));
                res = -1;
            }

            if (res == 0)
            {
                _logger.LogError(string.Format("{0}.Create({1}) -> no items were created/changed", GetType().Name, item));
            }
            else
            {
                _logger.LogDebug(string.Format("{0}.Create({1}) -> {2} item(s) were created/changed", GetType().Name, item, res));
            }

            return res;
        }

        public int Delete(int id)
        {
            _logger.LogDebug(string.Format("{0}.Delete({1}", GetType().Name, id));
            Person item = _dbContext.Person.Where(i => i.Id == id).SingleOrDefault();

            if (item != null)
            {
                _dbContext.Person.Remove(item);
                int res = _dbContext.SaveChanges();

                if (res == 0)
                {
                    _logger.LogError(string.Format("{0}.Delete({1} -> no items deleted", GetType().Name, id));
                } 
                else
                {
                    _logger.LogDebug(string.Format("{0}.Delete({1} -> {2} item(s) deleted", GetType().Name, id, res));
                }

                return res;
            }
            else
            {
                _logger.LogError(string.Format("{0}.Delete({1} -> not item found by id", GetType().Name, id));
                return -1; // better way to indicate not found?
            }
        }

        public int Replace(int id, Person item)
        {
            // how to implement replace
            throw new NotImplementedException();
        }

        public int Modify(int id, string newLastname)
        {
            // how to implement modify
            throw new NotImplementedException();
        }
    }
}

PersonController:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using PersonExample.Repository;
using PersonExample.Models;

namespace PersonExample.Controllers
{
    [Route("api/[controller]")]
    public class PersonController : Controller
    {
        private readonly IPersonRepositoy _repo;
        private readonly ILogger<PersonRepository> _logger;

        public PersonController(IPersonRepositoy repo, ILogger<PersonRepository> logger)
        {
            _repo = repo;
            _logger = logger;
        }

        // GET: api/values
        [HttpGet]
        public IEnumerable<Person> Get()
        {
            _logger.LogDebug(string.Format("{0}.GetAll()", GetType().Name));
            IEnumerable<Person> data = _repo.GetAll();
            _logger.LogDebug(string.Format("{0}.GetAll() -> returned {1} result(s)", GetType().Name, "?"));
            return data;
        }

        // GET api/values/5
        [HttpGet("{id:int}", Name = "GetPerson")]
        public IActionResult Get(int id)
        {
            _logger.LogDebug(string.Format("{0}.GetById({1})", GetType().Name, id));
            Person item = _repo.GetById(id);

            if (item == null)
            {
                _logger.LogError(string.Format("{0}.GetById({1}) -> no item found by id", GetType().Name, id));
                return NotFound(id);
            }

            return new ObjectResult(item);
        }

        [HttpGet("{lastname}")]
        public IEnumerable<Person> Get(string lastname)
        {
            // example to demonstrate overloading of types (int for id, string for lastname)
            _logger.LogDebug(string.Format("{0}.GetByLastname()", GetType().Name));
            IEnumerable<Person> data = _repo.GetByLastname(lastname);
            _logger.LogDebug(string.Format("{0}.GetByLastname() -> returned {1} result(s)", GetType().Name, "?"));
            return data;
        }

        [HttpGet("search/{namepart}")]
        public IEnumerable<Person> Search(string namepart)
        {
            //example to demonstrate url modification (how would I do multiple name parts?)
            _logger.LogDebug(string.Format("{0}.Search({1})", GetType().Name, namepart));
            IEnumerable<Person> data = _repo.SearchByLastname(namepart);
            _logger.LogDebug(string.Format("{0}.Search({1}) -> returned {2} result(s)", GetType().Name, namepart, "?"));
            return data;
        }

        // POST api/values
        [HttpPost]
        public IActionResult Post([FromBody]Person value)
        {
            //how to validate data and what to return in error cases?
            _logger.LogDebug(string.Format("{0}.Post({1})", GetType().Name, value));
            if (value == null)
            {
                _logger.LogDebug(string.Format("{0}.Post({1}) -> bad request: item is null", GetType().Name, value));
                return BadRequest();
            }

            //return 409 Conflict if resource exists -> where and how to check?

            int res = _repo.Create(value);

            if (res == 0) //no items changed
            {
                _logger.LogError(string.Format("{0}.Post({1}) -> zero items changed", GetType().Name, value));
                return NotFound(); //what to return? not found isn't the problem
            }
            else if (res == -1) //DbUpdateException
            {
                _logger.LogError(string.Format("{0}.Post({1}) -> DbUpdateException", GetType().Name, value));
                return NotFound(); //what to return? not found isn't the problem
            }

            _logger.LogDebug(string.Format("{0}.Post({1}) -> {2} items changed", GetType().Name, value, res));
            return CreatedAtRoute("GetPerson", new { id = value.Id }, value);
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            _logger.LogDebug(string.Format("{0}.Delete(id: {1})", GetType().Name, id));
            int res = _repo.Delete(id);

            if (res == 0) // zero entries changed
            {
                _logger.LogError(string.Format("{0}.Delete({1}) -> zero items changed", GetType().Name, id));
                //what to return in that case, its a different error than not found???
                return NotFound();
            }
            else if (res == -1) // id not found
            {
                _logger.LogError(string.Format("{0}.Delete({1}) -> not found item by id", GetType().Name, id));
                return NotFound(id);
            }

            return Ok();
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]Person value)
        {
            //example for full update / complete replace with logging and error handling
            // how to implement, what to return?
           // _repo.Replace(id, value);
        }

        // PATCH api/values/5
        [HttpPatch("{id}")]
        public void Patch(int id, [FromBody]Person value)
        {
            //example for partial update  with logging and error handling
            // how to implement, what to return?
            //_repo.Modify(id, lastname);
        }


    }
}

My Questions

In general:

What are the correct (and REST standard conform) implementations of the controller and the repository including exception handling, data validation (necessary?) and logging of errors (when one occurs)

8
  • 3
    Wow ! That is the biggest question i saw so far !!!. Please spend few minutes to read How to create a Minimal, Complete, and Verifiable example. Commented Sep 15, 2016 at 15:02
  • @Shyju: it's not my first question (lost access to my older account). It is verifiable and complete (steps to reproduce my current setup), it is readable (formatting the code took longer than write the code in VS) and unfortunatly it is as minimal as possible (if you break it down to the what I want to achieve part). Commented Sep 15, 2016 at 15:11
  • I know it's long. There was no way to make it smaller. If I would have asked only how to implement the "what I want to achieve part" the trolls would have said it is to broadband, I haven't researched enough, try googling it, it is offtopic, opinion bases.... So there is a concrete and reproduceable example Commented Sep 15, 2016 at 15:13
  • 1
    This is like 12 questions bundled into one, and many are opinion based with no one correct answer. Commented Sep 15, 2016 at 15:25
  • 1
    Monty, you have a list of things you want to achieve, and a list of questions. You said it yourself that you have multiple questions. I agree this needs to be broken down into smaller chunks. You're already risking loosing the TLDR crowd. Try focusing on one question at a time, really thinking about what it is you are doing and wanting to achieve with that ONE question. You will get a better response and result from SO if you do. Commented Sep 15, 2016 at 17:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.