Как обработать AJAX-запрос JQUERY POST с помощью собственного хоста WCF
Существует много причин для создания сервера RESTful WCF (это легко), и даже лучше, если вы можете избежать ASP и его окна безопасности (если все, что вы делаете, это простые запросы на возврат информации). См. http://msdn.microsoft.com/en-us/library/ms750530.aspx о том, как это сделать.
Я обнаружил, что обработка запросов GET AJAX (JQUERY) очень проста. Но иметь дело с JSON в POST сложно.
Вот пример простого контракта GET-запроса:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
String Version();
И реализация здесь (которая возвращает JSON)
public partial class CatalogService : ICatalogService
{
public String Version()
{
mon.IsActive = true;
this.BypassCrossDomain();
ViewModel.myself.TransactionCount++;
return ViewModel.myself.VersionString;
}
}
Ах, но что делать, если вы хотите отправить JSON. Вы найдете много статей о переполнении стека, которые сообщают вам все, что вам нужно сделать, это:
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
BuildResponse BuildToby(BuildRequest request);
который получит сообщение JSON, десериализуется в обычный объект.NET (PONO) и позволит вам работать с ним. И действительно, это работало нормально, когда я создал запрос в Fiddler.
POST /BuildToby HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:4326
Content-Length: 1999
Однако, когда вы используете следующий AJAX в JQUERY 1.8, вы обнаружите СЮРПРИЗ:
Если вы укажете тип содержимого "application/json", вы обнаружите, что браузер запускает проверку "preflight", чтобы узнать, разрешено ли вам POST что-то отличное от почтового сообщения в кодировке www-url. (об этом есть переполнение стека).
var request = JSON.stringify({ FrameList: ExportData.buildList });
var jqxhr = $.ajax({
type: "POST",
url: "http://localhost:4326/BuildToby",
data: request,
contentType: "application/json; charset=utf-8",
dataType: "json"
});
и вот что сообщает fiddler: (Обратите внимание, что это не сообщение POST, а сообщение OPTIONS).
OPTIONS http://localhost:4326/BuildToby HTTP/1.1
Host: localhost:4326
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://ysg4206
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Произошло то, что браузер (в данном случае Firefox) должен сделать дополнительный вызов серверу с HTTP-сообщением OPTIONS, чтобы узнать, разрешен ли POST (этого типа контента).
Все статьи о том, как это исправить, касаются редактирования GLOBAL.ASAX, что хорошо, если вы находитесь в ASP.NET, но бесполезно, если вы делаете WCF с собственным хостом.
Итак, теперь вы видите вопрос (извините за то, что так долго ломали голову, но я хотел сделать эту статью полной, чтобы другие могли следить за результатами).
4 ответа
Хорошо, теперь есть некоторые настоящие гуру MSDN, которые написали решения, но я не могу понять их: http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx
Но я пришел к простому решению. По крайней мере, в WCF 4.5 вы можете добавить свой собственный OperationContract для обработки запросов OPTIONS:
[OperationContract]
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
void GetOptions();
Обратите внимание, что подпись метода void и не имеет аргументов. Сначала будет вызвано, а затем будет вызвано сообщение POST.
Реализация GetOptions:
public partial class CatalogService : ICatalogService
{
public void GetOptions()
{
mon.IsActive = true;
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
}
}
и это действительно все, что вам нужно сделать.
Вы также можете добавить этот атрибут в свой класс обслуживания, чтобы вы могли сериализовать большой JSON:
//This defines the base behavior of the CatalogService. All other files are partial classes that extend the service
[ServiceBehavior(MaxItemsInObjectGraph = 2147483647)] // Allows serialization of very large json structures
public partial class CatalogService : ICatalogService
{
PgSqlMonitor mon = new PgSqlMonitor();
private void BypassCrossDomain()
{
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
}
}
Примечание. У меня есть небольшой вспомогательный метод, называемый BypassCrossDomain(), который я вызываю для всех своих методов POST и GET, чтобы я мог обрабатывать междоменные вызовы.
Я потратил много времени на исследования (на форумах MSDN, переполнении стека, блогах), и я надеюсь, что это поможет другим, пытающимся делать подобные проекты.
Еще одно дополнение к ответу Dr.YSG: если вам нужно поддерживать метод OPTIONS на конечных точках, которые переносят POSTS на отдельные идентификаторы, вам нужно реализовать несколько методов GetOptions:
[WebInvoke(Method = "OPTIONS", UriTemplate = "")]
void GetOptions();
[WebInvoke(Method = "OPTIONS", UriTemplate = "{id}")]
void GetOptions(string id);
Это действительно разочаровывает, что WCF/Microsoft не могут автоматически генерировать правильный ответ OPTIONS на основе подписи конечной точки автоматически, но по крайней мере это может быть обработано вручную.
После многих дней поиска по этому поводу и прочтения множества постов и предлагаемых решений, я думаю, что этот вопрос доктора YSG был лучшим ресурсом для понимания и решения моей проблемы с angular/wcf/post/CORS и т. Д.
Но что действительно сработало для меня, так это:
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (Request.HttpMethod == "OPTIONS")
{
Response.End();
}
}
Я понимаю, что это не полное (и не красивое) решение, так как я использую global.asax и существует очень много возможных сценариев, но я просто хочу поделиться этой альтернативой, поскольку она может в конечном итоге помочь кому-то другому.
(Помимо добавления заголовков)
В дополнение к тому, что Dr.YSG указал в качестве своего ответа, я обнаружил, что получаю уведомление от Firefox о том, что происходит перенаправление и ошибка "405 Method Not Allowed". Добавление
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Max-Age", "1728000");
к классу GetOptions, кажется, обратился к проблеме.