Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 11, 2019 @ 14:00
    Owain Williams
    1

    custom dashboard, submit button not firing

    Hi

    I've been given some code which should allow me to submit a file from the backoffice via a simple upload form. The issue is, when I click submit, the JS controller doesn't seem to be hit. This is mostly due to my lack of knowledge when it comes to JS so would really appreciate any help.

    My dashboard code is:

     <div class="umb-pane">
        <form class="csv-upload" action="javascript:;" enctype="multipart/form-data" method="post">
            <label>
                <span>Add or Update Csv:</span>
                <input type="file" name="File" />
            </label>
    
            <button type="submit" class="btn umb-button__button btn-success"><span>Upload</span><div class="spinner"></div></button>
        </form>
    </div>
    

    and I'm calling upload-file-controller.js from my package.manifest file. within this file I have the following code:

    $('.csv-upload').on('submit', function (e) {
    e.preventDefault();
    var $form = $(e.target);
    var formData = new FormData(this);
    
    $.ajax({
        url: "/umbraco/backoffice/api/csvUploader/ReadCsvColumnsAsync", //Your c# api controller
        type: "POST",
        data: formData,
        cache: false,
        contentType: false,
        processData: false,
    
        complete: function (xhr) {....
    

    I did have a look at some code that Dave Woestenborghs suggested but this blew my mind and felt over complicated for what I'm wanting to do - upload a CSV file from the backoffice and save it in to a DB.

    Thanks in advance.

  • Søren Gregersen 441 posts 1884 karma points MVP 2x c-trib
    Aug 11, 2019 @ 14:28
    Søren Gregersen
    0

    Try removing the action-attribute instead of that thing.

    Also, you can add your event-handler via an onsubmit-attribute to elimate your event wiring from being wrong / called / not called

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 11, 2019 @ 18:14
    Anders Bjerner
    4

    Hi Owain,

    Mixing Angular and jQuery together should generally be avoided. For instance the call with $.ajax happens outside of Angular's context, so if you update something in your model once the $.ajax completes, Angular won't know about it right away. This could for instance mean that the UI isn't updated right away either.

    There are a few tricks that lets you tell Angular that your model has been updated, but luckily Angular also comes with a service for making HTTP request - it's called $http and works similar to jQuery's $.ajax.

    Your view from your example didn't specify a controller, but you probably have this somewhere. To get it up and running on my machine, I ended up with the following (pretty much the same):

    <div ng-controller="OwainDashboard">
        <div class="umb-pane">
            <label>
                <span>Add or Update Csv:</span>
                <input type="file" name="File" />
            </label>
            <button type="submit" class="btn umb-button__button btn-success" ng-click="upload()"><span>Upload</span><div class="spinner"></div></button>
        </div>
    </div>
    

    The changes instead lies in the controller. Besides $scope, I've included the $element and $http resources.

    $element is a reference to the element which holds the ng-controller attribute. It's a Angular selection, so we need to call $element[0] to get the actual HTML element, which we can then use for finding the file input field.

    $http is like mentioned earlier a resource/service in Angular used for making HTTP requests. Uploading is a little special, so we need to configure it to our needs. Here is a why the code looks like it does:

    • FormData is a special class in JavaScript (won't work in IE9) that is particular useful for representing POST data - including file uploads. In this case we only append the file, but you could append regular POST data as well.

    • When making a POST request with one or more files, the content type of the request must be multipart/form-data, but using this content type you must already specify a boundary (which is basically a separator). Luckily we can set the content type to undefined instead, and the $http resource will take care of the rest. Then you don't need to worry about setting the correct content type and boundary.

    • the $http resource will normally try to transform/encode the data you give it, which we don't really want in this case. We can control this via the transformRequest field. angular.identity is a special function that simply returns back the value you specify as the first argument, so using it here, the transformation will simply return the input value - meaning it isn't changed.

    So to summarize this, my controller ended up looking like this:

    angular.module("umbraco").controller("OwainDashboard", function ($scope, $element, $http) {
    
        // Get a reference to the file input field
        var input = $element[0].querySelector("input[type=file]");
    
        $scope.upload = function () {
    
            // Ignore if no files has been selected
            if (input.files.length === 0) return;
    
            // Get the file object
            var file = input.files[0];
    
            // Use form data to send multipart POST data
            var formData = new FormData();
            formData.append("file", file);
    
            // Upload the file using $http
            $http({
                method: "POST",
                url: "/umbraco/api/Hest/Upload",
                headers: { "Content-Type": undefined },
                transformRequest: angular.identity,
                data: formData
            }).success(function () {
                // Yeah
            }).error(function () {
                // Oh noes
            });
    
        };
    
    });
    
  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 11, 2019 @ 19:37
    Owain Williams
    0

    Anders, this is amazing!

    This is my first attempt at using Angular and I'm not a front end person so struggling with this a bit. I'm sort of hoping that once I get this all hooked up then I can bring C# in somewhere to handle the database requests etc.

    The other issue is a lot of the tutorials online seem to be for newer versions of Angular which doesn't help things.

    I'll re-read your post and then see if I can get my project working.

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 12, 2019 @ 00:00
    Anders Bjerner
    1

    Usually you can search for "angularjs", which is used for Angular 1 and 2. For the newer versions, they've dropped the "js" suffix, so it makes it a bit easier to find relevant information.

    For the C# part, your controller could look something like this:

    using System.Web;
    using System.Web.Mvc;
    using Umbraco.Web.WebApi;
    
    namespace UmbracoSevenTwelveTwo
    {
    
        public class CsvController : UmbracoApiController
        {
    
            [HttpGet]
            public object Upload()
            {
    
                HttpPostedFile file = HttpContext.Current.Request.Files["file"];
    
                // the rest of your logic here
    
            }
    
        }
    
    }
    
  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 12, 2019 @ 11:30
    Owain Williams
    0

    Do I need to do some routing for this in the backoffice? I think I read somewhere that I might need to?

    Just now the url I have for calling the controller is /umbraco/api/csvupload/get

    which should then call my CsvUploadController which has a Get method in it. Just now that method looks like

    public string Get()
    {
         return "Hello";
    }
    

    When I click the Upload button in the backoffice I get a 404 error

    'Request error: The URL returned a 404 /umbraco/api/csvupload/get' with data: {}

  • louisjrdev 107 posts 344 karma points c-trib
    Aug 12, 2019 @ 12:11
    louisjrdev
    0

    Try going to yoursite.com/umbraco/api/csvupload/get and see what you get, if you get a 404, it means the controller is not setup correctly or it has not built properly

    Aslong as your controller is inheriting UmbracoApiController or UmbracoAuthorizedApiController then it will get auto routed, so your url is correct.

    Also check that you have the [HttpGet] data annotation above your method name

  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 12, 2019 @ 12:45
    Owain Williams
    0

    I'm wondering if the issue is that my c# controller is in a different project? Site.Core but I don't think it should really cause an issue.

    This is really annoying me now as I think I'm pretty close.

    Thanks for all your help Louis and Anders :)

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 12, 2019 @ 12:47
    Anders Bjerner
    0

    As Louis mentions, Umbraco should handle the routing - at least most of the ways.

    If you inherit from UmbracoApiController (as in my example), the URL will be /umbraco/api/{controller}/{method}. Inheriting from this class will however mean that the controller is public.

    So for something that is only used in the backoffice, you should use UmbracoAuthorizedApiController instead. This changes the URL a bit, so it will now be /umbraco/backoffice/api/{controller}/{method} instead.

    Umbraco/WebApi will automatically create routes for methods that start with the HTTP verb they are for. So a method called GetProduct will automatically have a route when using GET, but not POST.

    In my example, I had added the [HttpGet] attribute, which is actually wrong, as uploading a file should be a POST request. But WebAPI probably also automatically creates a route for a method called Upload.

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 12, 2019 @ 12:49
    Anders Bjerner
    0

    Btw - if you can post the controller you have so far, I can try have a look and see if I can spot why it's not working.

    That the controller is defined in another project shouldn't be a problem as long as the web project references the project with the controller.

  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 12, 2019 @ 12:57
    Owain Williams
    1

    This is my controller within my RBM.Core project

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Hosting;
    using System.Web.Http;
    using Umbraco.Web;
    using Umbraco.Web.WebApi;
    
    
    namespace RunningBesideMe
    { 
    
        public class CsvUploadController : UmbracoApiController
        {
    
    
            [HttpPost]
            public async Task UploadCSV()
            {
    
    
                if (!Request.Content.IsMimeMultipartContent())
                    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    
                var provider = new MultipartMemoryStreamProvider();
                await Request.Content.ReadAsMultipartAsync(provider);
    
                var fileData = provider.Contents[0];
                var fileName = fileData.Headers.ContentDisposition.FileName.Trim('\"');
                var buffer = await fileData.ReadAsByteArrayAsync();
                File.WriteAllBytes(HostingEnvironment.ApplicationPhysicalPath + $"/TestFolder/{fileName}", buffer);
                //your code
    
                return;
            }
    
    
    
    
        }
    }
    

    And my RunningBesideMe project (which is the main Umbraco project) has a dependancy on the RBM.Core project - I've tried it the other way around too as it catches me out each time.

     $http({
                method: "POST",
                url: "/umbraco/backoffice/api/csvupload/uploadcsv",
                headers: { "Content-Type": undefined },
                transformRequest: angular.identity,
                data: formData
    
  • louisjrdev 107 posts 344 karma points c-trib
    Aug 12, 2019 @ 13:08
    louisjrdev
    0

    Try changing the content of your controller method to this as per Anders' example:

    HttpPostedFile file = HttpContext.Current.Request.Files["file"];
    
  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 12, 2019 @ 13:13
    Owain Williams
    0

    Still getting the 404, it just can't find my controller - might move it in to a controller folder in the main project and see if that helps.

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 12, 2019 @ 13:27
    Anders Bjerner
    0

    Try changing so that your controller inherits from UmbracoAuthorizedApiController instead of UmbracoApiController.

    The /backoffice/ part in your URL means that it should be UmbracoAuthorizedApiController.

  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 12, 2019 @ 13:32
    Owain Williams
    0

    Sorry, thought I'd changed that. Changed it now, cleared my cache etc and still getting a 404 error.

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 12, 2019 @ 13:50
    Anders Bjerner
    0

    I just tried copying your controller to my own test solution, and this works fine. I've now successfully uploaded a file to TestFolder.

    Are both projects in the same solution? Are you sure the web project references your code project? And do they both have the same target framework?

  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 12, 2019 @ 14:47
    Owain Williams
    0

    So, after a screenshare with Nik, my main issues was the missed reference as you said Anders. I was 100% sure I had set that up so hadn't checked it again. Sorry to have wasted your time.

    Now at least the controller is being hit and I can hopefully go about reading the file in and parsing it in to a DB.

    Thanks for all your help.

  • Anders Bjerner 487 posts 2989 karma points MVP 7x admin c-trib
    Aug 12, 2019 @ 16:42
    Anders Bjerner
    0

    No worries. I think that happens to all of us from time to time ;)

  • Kevin Jump 2310 posts 14695 karma points MVP 7x c-trib
    Aug 13, 2019 @ 07:44
    Kevin Jump
    0

    Hi,

    I think this is already sorted, but I've just added a file upload example to my DoStuffWithUmbraco Repo which might help with the file upload bit - it took me agest the first time to navigate all the controls for this, but when you find them lots of bits exist in Umbraco.

    https://github.com/KevinJump/DoStuffWithUmbraco/tree/master/Src/DoStuff.Core/FileUpload

  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 13, 2019 @ 08:03
    Owain Williams
    0

    Cheers Kevin. Will look at this too. I'm currently working on how to upload a CSV and load it in to a SQL DB table.

  • Kevin Jump 2310 posts 14695 karma points MVP 7x c-trib
    Aug 13, 2019 @ 08:27
    Kevin Jump
    0

    Cool, so is the upload bit working for you now?

  • Owain Williams 480 posts 1411 karma points MVP 6x c-trib
    Aug 13, 2019 @ 08:30
    Owain Williams
    1

    Ye, well, I'm currently hitting the C# controller which is better than I had this time yesterday. :)

  • Kevin Jump 2310 posts 14695 karma points MVP 7x c-trib
    Aug 13, 2019 @ 08:36
    Kevin Jump
    0

    Woot!

Please Sign in or register to post replies

Write your reply to:

Draft