Проблемы с реализацией простой службы веб-API ASP.NET Core (PersonExample)

Я новичок в ASP.Net, ASP.NET MVC и EntityFrameWork (как правило) и в вариантах .NET Core (в частности). В настоящее время я пытаюсь запустить свой первый пример/тестовый проект (в Visuals Studio 2015), но у меня есть пара проблем, для которых я не смог найти решения в Google.

Часть руководств и инструкций, которым я следовал до сих пор:

Эти учебники и инструкции описывают только фрагмент решения каждый, но эти фрагменты не подходят друг другу и вызывают проблемы. Поэтому я пытаюсь собрать недостающие части воедино.


Чего я хочу добиться, так это (как можно более простого) примера проекта.

  • Демо/пример проекта ASP.NET Core WEB API (в Visual Studio 2015)
  • который хранит данные в базе данных (SQL) (а не в каком-то рукописном репозитории) с использованием ядра EntityFramework (всего 1 таблица Person, содержащая 3 столбца: id как идентификатор первичного ключа, 2 столбца, имя и фамилия как nvarchar (30))
  • where one can
    • request (GET) all persons (WORKS in the code below)
    • (GET) конкретного человека по id или по фамилии (работает в коде ниже)
    • создать (POST) нового человека (работает в приведенном ниже коде)
    • (УДАЛИТЬ) человека по id (работает в коде ниже)
    • полная замена (PUT) по id (КАК СДЕЛАТЬ?)
    • изменить (ИСПРАВИТЬ) фамилию (люди все еще женятся) отправив только идентификатор и новую фамилию (КАК СДЕЛАТЬ?)
  • 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)
    • иметь рабочую обработку исключений

Мои реализации под вопросом

  1. IPersonRepository.cs


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

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

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

        }
    }

  1. PersonRepository.cs


    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 _logger;

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

            public IEnumerable GetAll()
            {
                //always returns an IEnumberable (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 GetByLastname(string lastname)
            {
                //always returns an IEnumberable (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 SearchByLastname(string namePart)
            {
                //always returns an IEnumberable (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 occure to catch somewhere else (e.g. if lastname would have a unique contraint)?
                _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();
            }

        }

    }

  1. PersonController.cs


    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 _logger;

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

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

            [HttpGet("search/{namepart}")]
            public IEnumerable 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 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);
            }


        }
    }


Мои вопросы

В целом:

Каковы правильные (и соответствующие стандарту REST) ​​реализации контроллера и репозитория, включая обработку исключений, проверку данных (необходимо?) и регистрацию ошибок (при возникновении)


person monty    schedule 15.09.2016    source источник
comment
Ух ты ! Это самый большой вопрос, который я видел до сих пор !!!. Потратьте несколько минут на чтение Как создать минимальный, полный и проверяемый пример.   -  person Shyju    schedule 15.09.2016
comment
@Shyju: это не первый мой вопрос (потерял доступ к своей старой учетной записи). Он поддающийся проверке и полный (шаги для воспроизведения моей текущей настройки), он удобочитаемый (форматирование кода заняло больше времени, чем написание кода в VS) и, к сожалению, он настолько минимален, насколько это возможно (если вы разбиваете его на то, что я хочу часть достижения).   -  person monty    schedule 15.09.2016
comment
Я знаю, что это долго. Не было возможности сделать его меньше. Если бы я спросил только, как реализовать то, чего я хочу достичь, тролли сказали бы, что это касается широкополосного доступа, я недостаточно исследовал, попробуйте поискать в Google, это оффтоп, основанное на мнении.... Итак, есть конкретный и воспроизводимый пример   -  person monty    schedule 15.09.2016
comment
Это похоже на 12 вопросов, объединенных в один, и многие из них основаны на мнениях без единого правильного ответа.   -  person stephen.vakil    schedule 15.09.2016
comment
Пожалуйста, укажите мне на часть, основанную на мнении.   -  person monty    schedule 15.09.2016
comment
Монти, у тебя есть список вещей, которых ты хочешь достичь, и список вопросов. Вы сами сказали, что у вас есть несколько вопросов. Я согласен, что это должно быть разбито на более мелкие куски. Вы уже рискуете потерять аудиторию TLDR. Попробуйте сосредоточиться на одном вопросе за раз, действительно думая о том, что вы делаете и чего хотите достичь с помощью этого ОДНОГО вопроса. Если вы это сделаете, вы получите лучший ответ и результат от SO.   -  person ryancdotnet    schedule 15.09.2016
comment
Здесь ночь. Завтра я попробую и добавлю достаточно информации, чтобы указать, что я ищу 1 (начинающую) настройку, и что единственный вопрос заключается в том, как установка должна работать вместе (как задумано разработчиками).   -  person monty    schedule 15.09.2016
comment
@ryancdotnet Как и обещал, я извлек первую часть этого вопроса в другую: stackoverflow.com/questions/39524196/   -  person monty    schedule 16.09.2016