Dosyć intensywnie w swoich projektach używam wywołań $.get i $.post dostępnych z poziomu jQuery. Niestety IE (w moim przypadku IE7) zawsze wykonuje żądanie do serwera jako POST. To skutkuje tym, że nie możemy mieć przykładowo dwóch metod Update rozróżnionych tylko metodą wywołania. Dziś szukałem przyczyny i rozwiązania tego problemu, ale niestety nie udało mi się znaleźć.
Dlatego postanowiłem napisać własny atrybut, który będzie sprawdzał czy żądanie pochodzi z jQuery oraz czy żądanie powinno być wywołane jako POST czy GET. W tym celu w ustawieniach każdego wywołania Ajax po stronie jQuery musiałem dodać własny nagłówek, po którym będę rozpoznawał jaką metodą jest wykonywane wywołanie.
$().ajaxSend(function(event, request, settings) {
request.setRequestHeader("X-Requested-Method", settings.type)
});
Podpiąłem się pod zdarzenie ajaxSend, gdzie dodaję nagłówek do każdego żądania, a jako wartość wstawiana jest metoda wywołania. Kiedy wykonamy metodę $.get to wartość nagłówka będzie GET i analogicznie dla metody $.post.
Teraz po stronie serwera musimy sprawdzić ten nagłówek i zadecydować czy dana akcja może zostać wywołana. Napisałem do tego własny atrybut AcceptAjaxRequest.
AcceptAjaxRequest
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class AcceptAjaxRequestAttribute : ActionMethodSelectorAttribute
{
private const string _x_requested_with = "X-Requested-With";
private const string _x_requested_method = "X-Requested-Method";
public AcceptAjaxRequestAttribute()
{
Verb = null;
}
public AcceptAjaxRequestAttribute(HttpVerbs verb)
{
Verb = verb;
}
private HttpVerbs? Verb { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
bool isAjaxRequest = IsAjaxRequest(controllerContext);
if (!Verb.HasValue)
{
return isAjaxRequest;
}
return isAjaxRequest && IsAjaxRequestedMethod(controllerContext);
}
private bool IsAjaxRequestedMethod(ControllerContext controllerContext)
{
bool hasAjaxRequestedMethod = controllerContext.HttpContext.Request.Headers.AllKeys.Contains(
_x_requested_method,
StringComparer.InvariantCultureIgnoreCase);
if (hasAjaxRequestedMethod)
{
return controllerContext.HttpContext.Request.Headers[_x_requested_method].Equals(
this.Verb.ToString(),
StringComparison.InvariantCultureIgnoreCase);
}
return false;
}
private bool IsAjaxRequest(ControllerContext controllerContext)
{
return controllerContext.HttpContext.Request.Headers.AllKeys.Contains(
_x_requested_with,
StringComparer.InvariantCultureIgnoreCase);
}
}
Atrybut ten robi to samo co atrybut Microsoft.Web.Mvc.AcceptAjax, ale dodatkowo sprawdza nasz nowy nagłówek. Przykładowe użycie wygląda nastepująco:
[Softio.Commons.Mvc.AcceptAjaxRequest(HttpVerbs.Get)]
public ActionResult Update(int id)
{
return View();
}
[Softio.Commons.Mvc.AcceptAjaxRequest(HttpVerbs.Post)]
public ActionResult Update(int id, string name)
{
// zapisujemy dane
return View();
}
Podsumowując
Posługując się tą kombinacją obchodzimy problem wysyłania żądania do serwera metodą POST w Internet Explorerze mimo iż chcieliśmy wykonać żądanie GET. Żądanie $.get w IE oczywiście jest POSTe, ale możemy zareagować na nie, jakby było GETem i nasz kod aplikacji będzie poprawny w IE jak i tych dobrze działających przeglądarkach.