Как обрабатывать запрос Ajax JQUERY POST с помощью WCF-хоста

Существует множество причин создания RESTful WCF-сервера (это легко), и даже лучше, если вы можете избежать ASP и его безопасности (если все, что вы делаете, это простые запросы на возврат информации). См. http://msdn.microsoft.com/en-us/library/ms750530.aspx о том, как это сделать.

Я обнаружил, что обработка запросов AJAX (JQUERY) GET очень проста. Но работать с 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, де-сериализуется в объект Plain.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, вы найдете SURPRISE:

Он, указав тип содержимого "application/json", вы обнаружите, что есть проверка "предполетного" кода, которая отключена браузером, чтобы узнать, разрешено ли вам POST что-либо, кроме www-url-encloded сообщение. (есть примечания в переполнении стека об этом).

    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) должен сделать дополнительный вызов на сервер с помощью сообщения OPTIONS HTTP, чтобы узнать, разрешен ли POST (этого типа контента).

Все статьи об исправлении этого вопроса касаются редактирования GLOBAL.ASAX, который хорош, если вы находитесь в ASP.NET, но бесполезны, если вы делаете самостоятельный хост WCF.

Итак, теперь вы видите вопрос (извините за то, что вы так долго наматывались, но я хотел сделать это полной статьей, чтобы другие могли следить за результатами).

Ответ 1

Хорошо, теперь есть некоторые настоящие гуру MSDN, у которых есть письменные решения, но я не могу понять их: http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx p >

Но я придумал простое решение. По крайней мере, в WCF 4.5 вы можете добавить свой собственный OperationContract для работы с запросами OPTIONS:

    [OperationContract]
    [WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
    void GetOptions();

Обратите внимание, что подпись метода недействительна и не имеет аргументов. Сначала это вызывается, а затем вызывается сообщение 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, переполнении стека, в блогах), и я надеюсь, что это поможет другим, кто пытается делать подобные проекты.

Ответ 2

Еще одно дополнение к ответу Dr.YSG, если вам нужно поддерживать метод OPTIONS для конечных точек, которые принимают POSTS для отдельных идентификаторов, вам придется реализовать несколько методов GetOptions:

    [WebInvoke(Method = "OPTIONS", UriTemplate = "")]
    void GetOptions();
    [WebInvoke(Method = "OPTIONS", UriTemplate = "{id}")]
    void GetOptions(string id);

Его действительно разочаровывает то, что WCF/Microsoft не может автоматически автоматически генерировать правильный ответ OPTIONS на основе сигнатуры конечной точки, но по крайней мере его можно обрабатывать вручную.

Ответ 3

В дополнение к тому, что указано в списке 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, похоже, рассмотрел проблему.

Ответ 4

После многих дней поиска и чтения многих сообщений и предлагаемых решений, я думаю, этот вопрос доктора YSG был лучшим ресурсом для понимания и решения моей проблемы с angular/wcf/post/CORS и т.д.

Но то, что действительно работало для меня, было следующим:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.End();
    }
}

Я понимаю, что это не полное (или красивое) решение, так как я использую global.asax и существует так много возможных сценариев, но я просто хочу поделиться этой альтернативой, так как это может помочь кому-то еще в конце концов.

(Помимо добавления заголовков)