Submitting a multipart AJAX form with CKEditor textarea

OK, the subject of this post is pretty niche, but that’s why I am posting about it because I do it so little I’d only forget about it for the next time!

With ASP.NET MVC if you want to allow a file upload on a form submitted by AJAX then you have to intercept the form submit event and do the submit it yourself via Javascript, rather than using MVC’s helpers. This is detailed in this useful Stackoverflow answer: How to do a aspnet mvc ajax form post with multipart form data

What the post doesn’t mention (as it wasn’t asked for!) was that if your form contains an instance of the popular rich text editor CKEditor then this code alone will not transfer the data the editor contains to the form. I think this must be because the CKEditor is waiting for the submit event to be triggered before it does the copy into the field.

It took a bit of digging, as I have not had to look at the CKEditor API before, but if you take the code from the above link as a start point, then add the following code to somewhere between the event.preventDefault() call and the ajax() call, it seems to work OK.


            for (instance in CKEDITOR.instances) {
                CKEDITOR.instances[instance].updateElement();
            }

Adding CKEditor to an ASP.MVC AJAX dialog

CKEditor is one of the most popular rich text editors for the Web out there, but although I’ve used it many times I’ve never added it to an MVC site before. Although following the instructions is simple enough for a static view, adding it to a dynamically created view within a jQuery dialog box proved a little bit trickier. As usual there are bits and bobs around on the Net about it, but never the whole thing in one place! So I’ve collated what I’ve found, and added some bits I discovered myself.

OK, so say you want to popup an Edit screen in a jQuery dialog, generating the Edit view to go within the dialog on-the-fly. Download the CKEditor and put the scripts into your site, it doesn’t matter what configuration you use. I usually just put it under the Scripts directory. Also download the jQuery plugin jquery.livequery and put it into your site.

You need the following script links in your page header. whatever.js is just the name of a JS file that you are going to create in a minute, obviously change the paths of the CKEditor and livequery scripts if necessary. It is crucial to include the jQuery adaptor.

<script src="@Url.Content("~/Scripts/jquery.livequery.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ckeditor/ckeditor.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ckeditor/adapters/jquery.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/whatever.js")" type="text/javascript"></script>

To start with you would put an Ajax.ActionLink on your View with a call to the Edit screen a bit like this (Razor view syntax):

@Ajax.ActionLink("Edit", "Edit", "Whatever", new AjaxOptions { UpdateTargetId = "editWhatever", OnBegin = "showEdit()"})

Where editWhatever is just an empty div somewhere else on the page, I’ll come onto the showEdit() Javascript in a minute.

The Edit view (and view model) would be something like below. Note that for some reason your field must be an Html.TextArea or TextAreaFor, a regular HTML textarea tag does not work, even though it is fine to use that tag in a non-AJAX form.

@model MyProject.ViewModels.WhateverEditViewModel

@using (Ajax.BeginForm("Save", "Whatever", new AjaxOptions { UpdateTargetId = "editWhatever", OnSuccess = "hideEditOnSuccess()" })) 
{
    @Html.AntiForgeryToken()
    @Html.HiddenFor(m => m.Site.Code)

    <div class="question-editor-label" >
       @Html.LabelFor(model => model.WhateverPropertyToEdit)
    </div>
        
    <div class="section-editor-field" >
            @Html.TextAreaFor(model => model.WhateverPropertyToEdit)
     </div>
  
     @Html.ValidationSummary()

    
    <button title="Save" type="submit" value="Save"  >Save</button> 
    <button type="button" value="Cancel" onclick="doCancel()" title="Cancel" >Cancel</button>
}

And you could probably work out the view model, but here it is:

namespace MyProject.ViewModels
{
    public class WhateverEditViewModel
    {
        [Display(Name = "Whatever Description")]
        public string WhateverPropertyToEdit { get; set; }
    }
}

And remember the ‘Save’ controller action method needs to accept HTML, so you have to decorate it with the annotation [ValidateInput(false)].

[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Save(WhateverEditViewModel passedModel)
{
   ...
}

Below is the Javascript for the page contained in whatever.js. In the $(document).ready() function it is crucial to use livequery to detect the appearance of the field on the edit form, as it does not exist when the document is first created. Use the jQuery call to ckeditor() to launch CKEditor. The rest should hopefully be obvious.

$(document).ready(function () {

    $('#WhateverPropertyToEdit').livequery(function () {
        $('#WhateverPropertyToEdit').ckeditor();
    });
});

function showEdit() {
    $('#editWhatever').dialog(
        {
            modal: true,
            width: 1000,
            title: 'Edit Whatever',
            dialogClass: 'edit_whatever_class',
            ....
        });
}


function doCancel() {
    $('#editWhatever').dialog('close');
}

function hideEditOnSuccess() {
    if ($('#editWhatever').find(".validation-summary-errors").size() == 0) {
        $('#editWhatever').dialog('close');
        /* do anything to the 'host' screen you need to here. */
    }
}

And you should now see the editor appear on your dialog box.

Internet Explorer does not refresh an ASP.NET MVC Ajax form correctly

I’ve come across a problem with Internet Explorer (I’ve looked at versions 8 and 10) when using a form called via Ajax in MVC. Basically, if you have a standard call to a Ajax.ActionLink such as this:

@Ajax.ActionLink("Edit Thingy", "Edit", "Thingy", new { id = thingy.Code }, new AjaxOptions { UpdateTargetId = "editMyThingy" })
 

Then IE appears to cache your form, so that the next time you view it the values in it are exactly the same as when you first loaded it up, making it look like nothing has been saved (it has been saved, it just looks like it hasn’t to the end user).

The obvious thing to try is to use the OutputCache attribute on your method like so:

[HttpGet]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")] 
public ActionResult Edit(string id)
{
 ...
}
 

However IE seems to ignore this. Eventually I got around the problem in a clunky way by forcing the ActionLink to call the method via a Post rather than a Get (see below). This seems to kick IE back into gear and doesn’t cache. Seems a bit hacky though, so if anyone has any other ideas please let me know!

@Ajax.ActionLink("Edit Thingy", "Edit", "Thingy", new { id = thingy.Code }, new AjaxOptions { UpdateTargetId = "editMyThingy", HttpMethod="Post"  })
 
[HttpPost]
public ActionResult Edit(string id)
{
 ...
}
 

Using MVC’s Html.RenderPartial in a form with the Razor view engine

I’ve found a little quirk with the Razor view engine (MVC3). Had me head-scratching for an hour or so, so I thought I should post it here! The following might only apply inside a partial view with Ajax, I’ve not had a look at non-Ajax or full views yet.

If your view/partial view has an Ajax form, and within that form a call to RenderPartial, like so:

@using (Ajax.BeginForm("MyAction", "MyController", new AjaxOptions() { /* various form options */}))
{
    <div>
       Some stuff
    </div>

    @{ 
         Html.RenderPartial("My Partial", Model); 
     }
}

Then although this looks fine you will get the following error message: CS1501: No overload for method ‘Write’ takes 0 arguments

To get it working simply surround the call to RenderPartial with a div tag, like below:

@using (Ajax.BeginForm("MyAction", "MyController", new AjaxOptions() { /* various form options */}))
{
    <div>
       Some stuff
    </div>

    <div> 
    @{ 
         Html.RenderPartial("My Partial", Model); 
     }
    </div>
}

Bug/feature in jQuery tablesorter pager

I’ve just come across a feature/bugette of the otherwise excellent jQuery tablesorter.pager plugin which is worth making a note of:

If your table has no rows and you try to make the call to the plugin then it appears to throw a wobbly and possibly an exception too. If you are doing this within an ‘OnSuccess’ event from an MVC Ajax.BeginForm() call then it appears to cancel out of the call and possibly messes up your screen too, depending on what else you are doing in this call.

To get around it I simply returned a different PartialView that didn’t contain the table, and then in the OnSuccess event checked to see if the table existed:

 

if ($("#tblSearchResults").length > 0) {
        $("#tblSearchResults").tablesorter();
        $("#tblSearchResults").tablesorterPager({ container: $("#pager") });
    }

Another way might be to tag individual rows with a class name, and see if any instances of that class exist on the page. The above way suited me better this time.

ASP.NET MVC redirect to login page with AJAX

If you use the [Authorize] attribute on your MVC controller then your users will automatically be redirected to the login page when they GET or POST if their session or cookie has expired. However if you are using the AJAX helpers to redirect output into a panel on the page this just results in the login page appearing in the panel, rather than the whole page. And quite often you will find that it does not work anyway as it seems to lose any information that it might have had about what virtual directories it is in.

A solution for this is quite simple, but I’ve not as yet found anywhere else on the web that puts it all together. So instead of [Authorise] apply the following to your controller constructor (or action):

[CustomAuthorise]
public class MyController : Controller

The source for the CustomAuthorise attribute is as below (obviously substitute in your own login URL!). Note that you can still use other custom authorise attributes if you do this, e.g. a role checker.

 public class CustomAuthoriseAttribute: AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
            if (filterContext.Result == null || (filterContext.Result.GetType() != typeof(HttpUnauthorizedResult)
                || !filterContext.HttpContext.Request.IsAjaxRequest()))
                return;

            var redirectToUrl = "Security/LogOn?returnUrl=" + filterContext.HttpContext.Request.UrlReferrer.PathAndQuery;

            filterContext.Result =  new JavaScriptResult() { Script="window.location = '" + redirectToUrl + "'"};
    
        }
    }

Thanks to these posts elsewhere on the web for helping out:
http://craftycodeblog.com/2010/05/15/asp-net-mvc-ajax-redirect/
http://beckelman.net/2010/04/01/custom-aspnet-mvc-authorization-attribute-for-ajax-requests/

FileUpload control with UpdatePanel (WebForms)

The asp:FileUpload control rather irritatingly does not work within an UpdatePanel in ASP.NET Webforms. This is probably something to do with security (which always seems to be an issue with AJAX!).

To get round this problem you can of course take it out of the UpdatePanel and have it on a separate form, but that is not really that slick, and I expect the main reason you are trying to do this is for slickness!

The solution is to put the upload control and associated upload functionality on another aspx page, and then include the page within your form using an iframe with the frameborder property set to zero. Of course that is not perfect, as you will need to provide a separate button for uploading, which might be irritating to the user. On the other hand that also allows you to display things like photo uploads relatively easily and apparently seamlessly.

Your main problem will be giving the upload page the information about where to store the file/photo (if it associated with a specific data record). When updating a record this can be passed as a parameter to the page request. Adding a record is more tricky. In this case if you generate your data’s key prior to saving then no problem, but if you allow the database to do it after saving it is a problem, as you can’t pass it through to the upload page! In this scenario the best process flow might be not allow file uploads in insert mode – just ask the user to save the data record, then have the page automatically go into edit mode afterwards for photos.

It all feels a little bit hacky but sometimes that is just the way AJAX works!