Отправка картинки на сервер WPF - Web Api Core - C#

420
27 июня 2018, 14:20

Не совсем понимаю некоторые вещи в веб технологиях... Сейчас тренируюсь и делаю простое приложение для отправки картинок на сервер. Задачу себе расписал такую: Клиент на WPF (без MVVM), просто формочка. Поля: Имя, Фамилия, Отдел и кнопка открыть картинку (загрузка фото человека), ну и время, когда были отправлены данные так же записываю в БД MSSQL пока локально. Для работы с БД использую EF + Code First. С текстовыми данными у меня вроде бы получилось. А вот с картинкой совсем не понимаю как быть. BLOB отметаю. Помогите реализовать. Мне необходима именно реализация, так как она у меня и хромает. Буду благодарен, если кто-то дополнит мой код для передачи картинок и сохранения их путей и тд.. (Контекст и регистрацию показывать не буду, что бы не нагромождать код).

Сервер Модель:

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Department { get; set; }
    public DateTimeOffset DateTime { get; set; }
    public string Path { get; set; }
}

Интерфейс хранилища и реализация:

public interface IPersonRepository
{
    Person Add(Person person);
    Person Get(Guid id);
    IEnumerable<Person> GetAll();
    void Remove(Guid id);
    bool Update(Person person);
}
public class PersonRepository : IPersonRepository
{
    private readonly PersonContext _personContext;

    public PersonRepository(PersonContext personContext)
    {
        _personContext = personContext;
    }

    public Person Add(Person person)
    {
        try
        {
             person.Id = Guid.NewGuid();
            _personContext.Persons.Add(person);
            _personContext.SaveChanges();
        }
        catch (Exception err)
        {
            throw new Exception(err.Message);
        }
        return person;
    }

    public Person Get(Guid id)
    {
        return _personContext.Persons.SingleOrDefault(c => c.Id == id);
    }

    public IEnumerable<Person> GetAll()
    {
        try
        {
            return _personContext.Persons.AsQueryable();
        }
        catch (Exception err)
        {
            throw new Exception(err.Message);
        }
    }
    public void Remove(Guid id)
    {
        try
        {
            Person employeeToRemove = new Person { Id = id };
            _personContext.Persons.Attach(employeeToRemove);
            _personContext.Persons.Remove(employeeToRemove);
            _personContext.SaveChanges();
        }
        catch (Exception err)
        {
            throw new Exception(string.Format("Сотрудника не существует или запись удалена!" + err.Message));
        } 
    }
    public bool Update(Person person)
    {
        throw new NotImplementedException();
    }
}

Контроллер:

[Route("api/[controller]")]
public class PersonController : Controller
{
    public IPersonRepository Person { get; set; }
    public PersonController(IPersonRepository person)
    {
        Person = person;
    }
    public IEnumerable<Person> GetAll()
    {
        return Person.GetAll();
    }
    [HttpGet("{id}", Name = "GetTodo")]
    public IActionResult GetById(Guid id)
    {
        var item = Person.Get(id);
        if (item == null)
        {
            return NotFound();
        }
        return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] Person person)
    {
        if (person == null)
        {
            return BadRequest();
        }
        Person.Add(person);
        return CreatedAtRoute("GetTodo", new { id = person.Id }, person);
    }
    [HttpPut("{id}")]
    public IActionResult Update(string id, [FromBody] Person person)
    {
        //Do something
    }
    [HttpPatch("{id}")]
    public IActionResult Update([FromBody] Person item, string id)
    {
        // Do something
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(Guid id)
    {
        var todo = Person.Get(id);
        if (todo == null)
        {
            return NotFound();
        }
        Person.Remove(id);
        return new NoContentResult();
    }
}

Клиент Получение всех данных и добавление пользователя.

public partial class MainWindow : Window
{
    String urlAddress = ConfigurationManager.AppSettings["serverUriString"];
    public MainWindow()
    {
        InitializeComponent();
        BindTodoList();
    }
    private void BindTodoList()
    {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(urlAddress);
        // Add an Accept header for JSON format.
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        using (HttpResponseMessage response = client.GetAsync("api/person/").GetAwaiter().GetResult())
        {
            if (response.IsSuccessStatusCode)
            {
                var employees = response.Content.ReadAsAsync<IEnumerable<Person>>().GetAwaiter().GetResult();
                grdEmployee.ItemsSource = employees;
            }
            else
            {
                MessageBox.Show("Error Code" + response.StatusCode + " : Message - " + response.ReasonPhrase);
            }
        }
    }
    private void btnAdd_Click(object sender, RoutedEventArgs e)
    {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(urlAddress);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var todo = new Person();
        todo.Name = txtName.Text;
        todo.LastName = txtLastName.Text;
        todo.Department = txtDepartment.Text;
        todo.Path = txtPath.Text;
        todo.DateTime = DateTime.Now;
        using (var response = client.PostAsJsonAsync("api/person/", todo).GetAwaiter().GetResult())
        {
            if (response.IsSuccessStatusCode)
            {
                MessageBox.Show("Employee Added");
                txtName.Text = "";
                txtLastName.Text = "";
                txtDepartment.Text = "";
                txtPath.Text = "";
                BindTodoList();
            }
            else
            {
                MessageBox.Show("Error Code" + response.StatusCode + " : Message - " + response.ReasonPhrase);
            }
        }
    }
    private void btnShowAll_Click(object sender, RoutedEventArgs e)
    {
        BindTodoList();
    }
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        BindTodoList();
    }
    private void btnOpenFile_Click(object sender, RoutedEventArgs e)
    {
        // OpenFileDialog
    }
    public bool SendData(byte[] image)
    {
       // Не знаю что...
       return true;
    }
}
Answer 1

Во-первых, храните в базе только имя файла (а сам файл в некоторой папке /uploads/ - причём имя файла генерируйте сами, чтобы при загрузке Image1.jpg не перетирало предыдущий загруженный файл). В принципе, можно и в BLOB засунуть, если ваш вопрос увидит Майоров он наверное так и посоветует, будем считать, что можно и так и так - выбор на ваш вкус.

Во-вторых, можете почитать как грузятся файлы через Request.Files - тынц: https://metanit.com/sharp/articles/mvc/16.php - там уже готовый код для серверной части (Upload, который разбирает Request.Files и сохраняет принятые файлы на диск)

Глядя на ваш код думаю этих двух подсказок будет достаточно.

А, нет. Вот ещё одна. Сделайте ещё одну модель - UploadPersonRequest, нагляднее будет:

public class UploadPersonRequest
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Department { get; set; }
    public DateTimeOffset DateTime { get; set; }
    public HttpPostedFileBase File { get; set; }
}

Также просто поищите на so по тегу c# и слову HttpPostedFileBase вопросы, будет вам образцом для вдохновения.

Возникло несколько вопросов: Скажите пожалуйста, для данной модели необходим свой репозиторий и контекст для Code First? Как потом связать две таблицы, ведь основная у меня существует и мне необходимо тянуть данные основные. Зачем еще одна модель?

Для данной модели не нужен ни свой репозиторий и его не надо тащить в EF Code First. Почитайте в этом вопросе то, что я пишу про RegisterViewModel: видите там два поля пароля и проверку на их одинаковость? В базе же мы храним только один раз и не в явном виде, а хешированное и часто ещё - солёное.

С этой моделью точно такая же ситуация: этот класс - лишь группировка входных параметров, чтобы вы могли сделать единый .Validate (допустим, проверить, что число переданных файлов не ноль или скажем ровно 1).

И эти данные request мы после валидации отправим на сохранение частично в одно место (на диск сам файл) и частично в базу (там у вас будет модель Person, при этом вы можете как сделать это просто dto-объектом, так и навесить дополнительную логику - зависит от архитектуры). И вот для работы с БД вам нужен отдельный репозиторий и для работы с диском вам нужен отдельный репозиторий.

Разумеется, вы можете работать и отделив HttpPostedFileBase от модели, просто тогда у вас будет часть информации в одном месте, часть в другом месте и я считаю такой способ менее удобным, если у вас есть цельная модель и на неё можно писать цельные юнит-тесты.

READ ALSO
Можно ли избавиться от повтора заменив на WHERE rn&lt;30 или типа того?

Можно ли избавиться от повтора заменив на WHERE rn<30 или типа того?

Напишите детерминированную функцию от двух параметров

205
помогите сопоставить SQL запрос

помогите сопоставить SQL запрос

помогите сопоставить SQL запрос

225
Правильный JOIN для выборки из двух таблиц

Правильный JOIN для выборки из двух таблиц

запутался я в джойнахПрошу помощи)) Короче, есть таблицы products и categories

203