.NETowo


.NET Core, web development with React and other JavaScript Frameworks

Dodawanie plików z React JS i .NET Core Web API

tags: C#, dotnet, JavaScript, React, Visual Studio Code

Cześć!

W ostatnim czasie miałem do czynienie z przesyłaniem pliku z poziomu aplikacji napisanej w React do Rest API postawionego w .NET core. Zainteresowanych takim rozwiązaniem zapraszam do przeczytania tego krótkiego wpisu :).

Dodawanie samego pliku

W tym punkcie pokażę, jak przesłać pojedynczy plik na serwer.
Na początek przygotuj kontroler po stronie serwera.

 
Route("uploader/justfile")]
public dynamic UploadJustFile(IFormCollection form)
{
    try
    {
        foreach (var file in form.Files)
        {
            UploadFile(file);
        }

        return new { Success = true };
    }
    catch (Exception ex)
    {
        return new { Success = false, ex.Message };
    }
}

private static void UploadFile(IFormFile file)
{
    if (file == null || file.Length == 0)
        throw new Exception("File is empty!");

    byte[] fileArray;
    using (var stream = file.OpenReadStream())
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        fileArray = memoryStream.ToArray();
    }

    //TODO: You can do it what you want with you file, I just skip this step
}

Jak widzisz utworzyłem prostą akcję kontrolera, która jako argument przyjmuje obiekt typu IFormCollection. Następnie przechodzi po plikach tej kolekcji i wywołuje metodę UploadFile, którą możesz zaimplementować w dowolny sposób.

Przejdźmy teraz do frontendu i utwórzmy prostą formę.

 
render() {
     return (
         <div>
             <form>
                 <h2>Just file</h2>
                 <p><b>{this.state.justFileServiceResponse}</b></p>
                 <input type="file" id="case-one" onChange={this.filesOnChange} />
                 <br />
                 <button type="text" onClick={this.uploadJustFile}>Upload just file</button>
             </form>
         </div>
     );
}

Zawiera ona tylko jeden input oraz button wywołujący metodę uploadu. W metodzie filesOnChange, która jest wywoływana podczas eventu onChange, przypisywany jest do state nasz plik.

 
    filesOnChange(sender) {
        let files = sender.target.files;
        let state = this.state;
        
        this.setState({
            ...state,
            files: files
        });
    }

Metoda uploadJustFile prezentuje się następująco

 
    uploadJustFile(e) {
    e.preventDefault();
        let state = this.state;

        this.setState({
            ...state,
            justFileServiceResponse: 'Please wait'
        });

        if (!state.hasOwnProperty('files')) {
            this.setState({
                ...state,
                justFileServiceResponse: 'First select a file!'
            });
            return;
        }

        let form = new FormData();

        for (var index = 0; index < state.files.length; index++) {
            var element = state.files[index];
            form.append('file', element);
        }

        axios.post('uploader/justfile', form)
            .then((result) => {
                let message = "Success!"
                if (!result.data.success) {
                    message = result.data.message;
                }
                this.setState({
                    ...state,
                    justFileServiceResponse: message
                });
            })
            .catch((ex) => {
                console.error(ex);
            });
    }

Na początek zostaje zwalidowany state pod kątem sprawdzenia czy znajdują się jakieś pliki. Jeżeli tak, tworzę obiekt FormData do którego dodałem pliki z pola input. Na koniec wywołałem Web API.

Dodawanie pliku oraz zawartości formy

W kontrolerze dodaj kolejne dwie metody UploadFile oraz MapFormCollectionToPerson. Dodaj także klasę Person, która będzie modelem dla przychodzących danych.

Person.cs

 
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
}       

UploaderController.cs

 
[Route("uploader/upload")]
public dynamic Upload(IFormCollection form)
{
    try
    {
        Person person = MapFormCollectionToPerson(form);

        foreach (var file in form.Files)
        {
            UploadFile(file);
        }

        return new { Success = true };
    }
    catch (Exception ex)
    {
        return new { Success = false, ex.Message };
    }
}

private static Person MapFormCollectionToPerson(IFormCollection form)
{
    var person = new Person();

    string firstNameKey = "firstName";
    string lastNameKey = "lastName";
    string phoneNumberKey = "phoneNumber";

    if (form.Any())
    {
        if (form.Keys.Contains(firstNameKey))
            person.FirstName = form[firstNameKey];

        if (form.Keys.Contains(lastNameKey))
            person.LastName = form[lastNameKey];

        if (form.Keys.Contains(phoneNumberKey))
            person.PhoneNumber = form[phoneNumberKey];
    }

    return p
}

Tak jak widzisz za dużo się nie zmieniło. Oprócz naszego pliku w obiekcie form zostanie również przekazana kolekcja pól naszej formy, a następnie zostaną one zmapowane.

Po stronie frontendu dodaj kolejną formę z kilkoma polami tekstowymi i polem typu file. Do pól tekstowych należy przypisać event, aktualizujący obiekt state oraz nową metodę przekazującą dane do web API.

 
    uploadForm(e) {
       e.preventDefault();
        let state = this.state;

        this.setState({
            ...state,
            formServiceResponse: 'Please wait'
        });

        if (!state.hasOwnProperty('files')) {
            this.setState({
                ...state,
                formServiceResponse: 'First select a file!'
            });
            return;
        }

        let form = new FormData();
        for (var index = 0; index < state.files.length; index++) {
            var element = state.files[index];
            form.append('file', element);
        }

        for (var key in state.fields) {
            if (state.fields.hasOwnProperty(key)) {
                var element = state.fields[key];
                form.append(key, element);
            }
        }

        axios.post('uploader/upload', form)
            .then((result) => {
                let message = "Success!"
                if (!result.data.success) {
                    message = result.data.message;
                }
                this.setState({
                    ...state,
                    formServiceResponse: message
                });
            })
            .catch((ex) => {
                console.error(ex);
            });
    }

    filesOnChange(sender) {
        let files = sender.target.files;
        let state = this.state;
        this.setState({
            ...state,
            files: files
        });
    }

    fieldOnChange(sender) {
        let fieldName = sender.target.name;
        let value = sender.target.value;
        let state = this.state;

        this.setState({
            ...state,
            fields: {...state.fields, [fieldName]: value}
        });
    }

    render() {
        return (
            <div>
                <form>
                    <h2>Just file</h2>
                    <p><b>{this.state.justFileServiceResponse}</b></p>
                    <input type="file" id="case-one" onChange={this.filesOnChange} />
                    <br />
                    <button type="text" onClick={this.uploadJustFile}>Upload just file</button>
                </form>
                <hr />
                <form>
                    <h2>Form</h2>
                    <p><b>{this.state.formServiceResponse}</b></p>
                    <div>
                        <input name="firstName" type="text" placeholder="First name" onChange={this.fieldOnChange} />
                    </div>
                    <div>
                        <input name="lastName" type="text" placeholder="Last name" onChange={this.fieldOnChange} />
                    </div>
                    <div>
                        <input name="phoneNumber" type="text" placeholder="Phone number" onChange={this.fieldOnChange} />
                    </div>
                    <input type="file" onChange={this.filesOnChange} />
                    <br />
                    <button type="text" onClick={this.uploadForm}>Upload form </button>
                </form>
            </div>
        );
    }

Metoda uploadForm wygląda podobnie do poprzedniej. Jedyna zmiana to dodanie wartości pól z obiektu state do naszego FormData i przesłanie obiektu na serwer.

Podsumowanie

Powyżej pokazałem jak w prostu sposób przekazać pliki z aplikacji klienckiej utworzonej w React na serwer, gdzie znajduje się Web API. Życzę miłej zabawy i z góry dziękuję za feedback.

GIT repository: https://github.com/artzie92/dotnetowo-upload-file-react-dotnet-core


Comments

Author: artur

2017-06-22 06:36:19 | Cześć, Świetna uwaga, wielkie dzięki. Juz poprawiam! :)

Author: MKS

2017-06-21 21:23:57 | Cześć, Bardzo istotna uwaga: We wszystkich przykładach ze state mutujesz go, tak nie można, setState wymaga nowego obiektu. Tutaj wytłumaczenie: https://facebook.github.io/react/docs/state-and-lifecycle.html#do-not-modify-state-directly jeśli używasz ES6, możesz skorzystać z operatora spread (...): np, zamiast: let state = this.state; state.fields[fieldName] = value; this.setState(state); użyłbyś this.setState({ ...state, fields: { ...state.fields, fieldName: value } }); Pozdrawiam :)

Got Something To Say:

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *