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.

aspxerrorpath in query string can cause custom error pages to fail with Runtime Error

A bit of a strange and obscure problem here, which is why I am posting about it. I think this may be a problem with ASP.NET rather than the application but I’m willing to be proved wrong!

ASP.NET has a reserved query string variable called aspxerrorpath. This is used by default when a custom error page is invoked, i.e. if you have a 404 or 500 error, and catch it via a custom error page set up in the web.config, then the query string will be something like Error?aspexerrorpath=/Path/To/Location/Error/Occurred. The URL after the equals sign can be used to redirect or log the error page. This happens with MVC as well as Webforms.

However if there is a problem with the aspxerropath URL, for instance it is too long, then the application will fall over with a Runtime Error screen. This is probably because ASP.NET is attempting to parse the URL, it fails, but obviously can’t go back to the custom error page because otherwise it would keep going round in an infinite loop.

Although it is easily possible to remove aspxerrorpath from the error page’s URL, simply by adding an alternative query parameter, the problem is that hacker-types can still manually type it in and possibly gain a little bit more information about your site. It was highlighted as a ‘Low risk’ problem by a security company on a website of mine so I thought I should get it sorted.

I found that following ‘fix’ for another problem seemed to work for this as well, basically it just makes ASP.NET stop parsing the passed URL by instructing it to ignore URLs with aspxerrorpath in the querystring.

http://weblogs.asp.net/scottgu/archive/2010/09/24/update-on-asp-net-vulnerability.aspx

Note I am using .Net 4.0, and the above is an old blog post, so there still seems to be some issues with it. I’m not entirely sure why they can’t remove it altogether!

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>
}

Unit testing MVC’s Authorize attribute with Rhino Mocks

If there is one thing I often forget to do at initial development time, it is to include an Authorize attribute on my controllers to allow only certain specified roles have access. So I’ve added a check to my ‘template’ of controller tests now.

If you have a controller declaration similar to this:

[Authorize(Roles = "Admin")]
public class MyController : Controller
{
    ...
}

Then you can test for the presence of the Authorize attribute in your test class like so (include a reference to System.Web.Mvc):

[Test]
public void Check_Auth_Privileges_Are_Correct()
{

     AuthorizeAttribute attribute = typeof(MyController)
            .GetCustomAttributes(typeof(AuthorizeAttribute), true)
            .Cast<AuthorizeAttribute>()
            .FirstOrDefault();

     Assert.NotNull(attribute); // Check the attribute exists
     Assert.That(attribute.Roles.Contains("Admin"));  // Check it contains your role
     // Check that it doesn't contain any other roles (if necessary)
}

If you have just declared the attribute on a particular method rather than the whole controller then you can test for its presence by just adding an extra line:

[Test]
public void Check_Auth_Privileges_Are_Correct()
{
     var methodInfo = typeof(MyController).GetMethod("MyMethod");
     AuthorizeAttribute attribute = methodInfo
            .GetCustomAttributes(typeof(AuthorizeAttribute), true)
            .Cast<AuthorizeAttribute>()
            .FirstOrDefault();

     Assert.NotNull(attribute); // Check the attribute exists
     Assert.That(attribute.Roles.Contains("Admin"));  // Check it contains your role
     // Check that it doesn't contain any other roles (if necessary)
}

You can obviously use this method for other annotations too.

Mocking HttpPostedFileBase InputStream with Rhino Mocks

I wanted to unit test a CSV file upload on a new website. I was using a standard file upload HTML input tag, which in .NET MVC translates into an HttpPostFileBase object. The main problem from the point of view of unit testing is that the InputStream method cannot be set directly, so I had to mock it (I use Rhino Mocks).

Here is my example method which can plugged into the test class. It converts a string into a stream and sets the view model’s UploadedFile property to that value. Just pass in the CSV string you wish to test. Note that you may need to change your Encoding method. Hopefully it will be useful in the future.

// The view model
public class MyViewModel
{
        [Required(ErrorMessage = "Please enter a file name")]
        public HttpPostedFileBase UploadedFile { get; set; }
}
// Creating the view model instance with the mocked input stream
private MyViewModel CreateInputModel(string input)
{
    MyViewModel passedModel = new MyViewModel();
    HttpPostedFileBase file = MockRepository.GenerateMock<HttpPostedFileBase>();
    string str = input ;
    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
    MemoryStream stream = new MemoryStream(buffer);

    file.Stub(x => x.InputStream).Return(stream);
    passedModel.UploadedFile = file;
    return passedModel;
}