-1

I have a problem and to this moment I haven't been able to resolve it.

Let me start by explaining how things are set up on this simple page.

I have a view that is used for entering new and editing existing data. This view will get a strongly typed model that is of type category.

public class Category
{
     public int CategoryID { get; set; }
     public string CategoryName { get; set; }
     public string CategoryDescription { get; set; }
     public string CategoryImage { get; set; }
}

My controler action is set up like this.

[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult Edit(Category category)
{
    Database db = new Database();

    if (!category.IsNew)
    db.Category.Attach(category);

    if (Request.Files.Count > 0)
    {
        var uploadedFile = Request.Files[0];        
        string fileName = string.Format("{0}.{1}", category.CategoryName, uploadedFile.ContentType.Split('/')[1]);
        var path = Path.Combine(Server.MapPath("~/images/category"), fileName);
        Request.Files[0].SaveAs(path);
        category.CategoryImage = string.Format("/images/category/{0}", fileName);
    }

    if (db.Entry<Category>(category).State == System.Data.Entity.EntityState.Detached)
    db.Category.Add(category);

    db.SaveChanges();
    Response.StatusCode = (int)HttpStatusCode.InternalServerError;

    return Json(Newtonsoft.Json.JsonConvert.SerializeObject(category));
}

So I get strongly typed object and I return JSON result. This is only so I can replace the image on the client side since whatever the image user selects will get renamed to the category name.

This is my view.

@model Category

@{
    var id = Model.CategoryID;
    var name = Model.CategoryName;
    var description = Model.CategoryDescription;
    var image = Model.IsNew ? "/images/image_default.png" : Model.CategoryImage;
}

<style>
    img {
        width: 128px;
    }
</style>

@{
    ViewBag.Title = "Category";
}

<br />

<div class="card">
    <div class="card-content">
        <div class="card-title">
            <h2 class="header">Category</h2>
        </div>
        <form id="editForm" method="post" enctype="multipart/form-data">
            @Html.AntiForgeryToken()
            <div class="center-align">
                <img id="image" class="responsive-img circle" src="@image" />
            </div>
            <br />
            <input id="CategoryID" name="CategoryID" type="hidden" value="@id" />
            <div class="input-field">
                <input name="CategoryName" type="text" class="" value="@name">
                <label for="CategoryName" data-errord="">Name</label>
            </div>
            <div class="input-field">
                <textarea name="CategoryDescription" class="materialize-textarea">@description</textarea>
                <label for="CategoryDescription" data-error="">Description</label>
            </div>
            <div class="file-field input-field">
                <div class="btn">
                    <span>Image</span>
                    <input name="CategoryImage" type="file" accept=".jpg,.gif,.png">
                </div>
                <div class="file-path-wrapper">
                    <input name="CategoryImagePath" class="file-path" placeholder="Upload category image" type="text">
                    <label for="CategoryImagePath" data-error=""></label>
                </div>
            </div>
            <br />
            <br />
            <div class="card-action">
                <button id="save" type="submit" class="waves-effect waves-light btn-large">Save</button>
                <a id="delete" class="waves-effect waves-light btn-large">Delete</a>
                <a id="back" href="@Url.Action("Index", "Category")" class="waves-effect waves-light btn-large">Back</a>
            </div>
        </form>
    </div>
</div>
@section Script{
    <script>
        $('#delete').click(function(event){
            event.preventDefault();
            var form = $('#editForm');
            $.ajax(
                {
                    url: '@Url.Action("Delete", "Category")',
                    data: JSON.stringify({id: $('#CategoryID').val()}),
                    type: 'DELETE',
                    contentType: "application/json;charset=utf-8"
                });
        });

        $("#editForm").validate({
            errorClass: 'invalid',
            validClass: "valid",
            rules: {
                CategoryName: {
                    required: true,
                    maxlength: 64
                },
                CategoryDescription: {
                    required: true,
                    maxlength: 512
                },
                CategoryImagePath: {
                    required: @Model.IsNew.ToString().ToLower()
                }
            },
            //For custom messages
            messages: {
                CategoryName: {
                    required: "Name is required",
                    maxlength: $.validator.format("Maximum nuber of characters is {0}")
                },
                CategoryDescription: {
                    required: "Description is required",
                    maxlength: $.validator.format("Maximum nuber of characters is {0}")
                },
                CategoryImagePath: {
                    required: "Please select image for category"
                }
            },
            submitHandler: function () {
                var form = $('#editForm');
                if(form.valid() == false)
                    return;
                var formData = new FormData($('#editForm')[0]);
                $.ajax(
                {
                    async: true,
                    type: 'POST',
                    data: formData,
                    dataType: 'json',
                    contentType: 'application/json; charset=utf-8',
                    success: function(data, textStatus, jqXHR){
                        alert('');
                        $('#image').attr('src', jQuery.parseJSON(data).CategoryImage);
                        Materialize.toast("Successfully saved.", 3000 );
                    },
                    error: function(jqXHR, textStatus, errorThrown){
                        alert('');
                    }
                })
            },
            errorPlacement: function (error, element) {
                element.next("label").attr("data-error", error.contents().text());
            }
        });
    </script>
}

The problem is that the whole page gets replaced by Json result. Furthermore, success method never gets invoked. Neither does error. I can see in network that I got response 200 OK.

This is error console I get but it makes no sense to me.

enter image description here

Any ideas why this is happening? I checked the Json result with lint tool and it reports that it is valid so no malformed Json. I also like to note that I played with ajax options and went with dataType and without it as as well as with content type and without it. It makes no difference.

Robert
  • 2,407
  • 1
  • 24
  • 35
  • Add `processData: false` to your ajax post please. – Berkay Yaylacı May 22 '16 at 11:12
  • You doing a normal submit as well as your ajax submit. –  May 22 '16 at 11:13
  • @B.Yaylaci If I add processData: false then my action will not be called at all. I will just get internal server error. In this case error method will be called and I will get Internal server error. – Robert May 22 '16 at 11:29
  • @StephenMuecke I am curious. What do you mean I am doing submit as well as ajax submit. My submit handler is part of validator so if everything goes well that method will be called and that method in itself has ajax. I think this is just one post to the server. – Robert May 22 '16 at 11:31
  • Yes, it calls your ajax, but no where do you cancel the default action of the submit button. (and once you do cancel it, the ajax code will all fail anyway) –  May 22 '16 at 11:33
  • @StephenMuecke Okay. So what's my next step. I am in validator submit handler and I am not sure if I can prevent default behavior there. Any ideas? – Robert May 22 '16 at 11:47
  • @StephenMuecke validation plugin should do that internally assuming no errors and `$("#editForm")` exists when code is run – charlietfl May 22 '16 at 11:48
  • Wrap code in document.ready. Aslo note warnings that somewhere you are making ajax calls with `async:false` which is a terrible practice and as warning says is being deprecated – charlietfl May 22 '16 at 11:48
  • Why in the world are your not generating your html correctly using the strongly typed `HtmlHelper` methdods to bind to your model. And add validation attributes and use `ValidationMessageFor()` in association with `jquery.validate.unobtrusive.js`? –  May 22 '16 at 11:50
  • @charlietfl, If the form is valid, it will submit the form normally, in addition to making the ajax call (which will fail) –  May 22 '16 at 11:57
  • @StephenMuecke no, calling `validate()` will prevent default submit internally in plugin when using `submitHandler` option as per plugin docs. OP needs to implement `debug` mode to troubleshoot – charlietfl May 22 '16 at 11:59
  • @charlietfl, OP is saying its redirecting and displaying the `JsonResult` so it cant be. –  May 22 '16 at 12:02
  • @StephenMuecke right, something is definitely not working as expected. The error thrown may not even be related to code shown. not sure what causes `illegal invocation` but whatever it is is blocking script and thus form submits without plugin working – charlietfl May 22 '16 at 12:05
  • As @B.Yaylaci, noted, it must be `processData: false` and it also needs to be `contentType: false,` (refer [this answer](http://stackoverflow.com/questions/29293637/how-to-append-whole-set-of-model-to-formdata-and-obtain-it-in-mvc/29293681#29293681) –  May 22 '16 at 12:10
  • @StephenMuecke If I use those two options I will get 500(Internal Server Error). Note that my action does indeed get called but something goes bad on the client side. – Robert May 22 '16 at 12:29
  • But your method is returning a `500 (Internal Server Error)` - you specify that with `Response.StatusCode =(int)HttpStatusCode.InternalServerError;`. And why in the world are you serializing you object using Newtonsoft and then serializing it again using `JsonResult()`? –  May 22 '16 at 12:31
  • @StephenMuecke That response was there only to check if error handler method would be called. Thanks for pointing out that I do not need to use Newtonsoft. I wan't aware that JsonResult would automatically serialize an object. – Robert May 22 '16 at 12:46
  • Whoever down voted the question...I could really use some written criticism on how to form a question better so it helps future readers with the same problem. – Robert May 22 '16 at 12:58

1 Answers1

0

First off I want to thank all. I got some nice comments that helped me alot.

Secondly I do not know what did the trick since I am new to this kind of web programming. I want to say that I didn't change anything on the server side. Only thing I did was play around with ajax options.

submitHandler: function (form, event) {
                    var formObj = $('#editForm');
                    if(formObj.valid() == false)
                        return false;
                    var formData = new FormData($('#editForm')[0]);
                    $.ajax(
                    {
                        type: 'POST',
                        data: formData,
                        dataType: 'json',
                        processData: false,
                        contentType: false,
                        success: function(responseData){
                            $('#image').attr('src', responseData.CategoryImage);
                            Materialize.toast("Successfully saved.", 3000 );
                        }
                    });
                }

It would seem that processData and contentType did the trick to some extent.

If I leave processData on false and completely remove contentType I will get Internal Server Error. If I completely remove processData and leave contentType to false I will get full JSON result that will completely replace my page and my success method will never get called.

If anyone wishes to explain why is this necessary that would be great.

I also think that because I am posting a file as well this got more complicated since I have to post form data rather than pure string.

Robert
  • 2,407
  • 1
  • 24
  • 35