ASP.net MVC возвращает JSONP

Я хочу вернуть JSON через домены, и я понимаю, что способ сделать это через JSONP, а не чистый JSON. Я использую ASP.net MVC, поэтому я думал просто о расширении типа JSONResult, а затем о расширении контроллера, чтобы он также реализовал метод Jsonp. Это лучший способ сделать это или есть встроенный ActionResult, который может быть лучше?

Изменить: я пошел вперед и сделал это. Просто для справки я добавил новый результат:

public class JsonpResult : System.Web.Mvc.JsonResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            HttpResponseBase response = context.HttpContext.Response;

            if (!String.IsNullOrEmpty(ContentType))
            {
                response.ContentType = ContentType;
            }
            else
            {
                response.ContentType = "application/javascript";
            }
            if (ContentEncoding != null)
            {
                response.ContentEncoding = ContentEncoding;
            }
            if (Data != null)
            {
                // The JavaScriptSerializer type was marked as obsolete prior to .NET Framework 3.5 SP1
#pragma warning disable 0618
                HttpRequestBase request = context.HttpContext.Request;

                JavaScriptSerializer serializer = new JavaScriptSerializer();
                response.Write(request.Params["jsoncallback"] + "(" + serializer.Serialize(Data) + ")");
#pragma warning restore 0618
            }
        }
    }

а также пара методов для суперкласса всех моих контроллеров:

protected internal JsonpResult Jsonp(object data)
        {
            return Jsonp(data, null /* contentType */);
        }

        protected internal JsonpResult Jsonp(object data, string contentType)
        {
            return Jsonp(data, contentType, null);
        }

        protected internal virtual JsonpResult Jsonp(object data, string contentType, Encoding contentEncoding)
        {
            return new JsonpResult
            {
                Data = data,
                ContentType = contentType,
                ContentEncoding = contentEncoding
            };
        }

Работает как шарм.

6 ответов

Вот простое решение, если вы не хотите определять фильтр действий

Код на стороне клиента с использованием jQuery:

  $.ajax("http://www.myserver.com/Home/JsonpCall", { dataType: "jsonp" }).done(function (result) {});

Действие контроллера MVC. Возвращает результат содержимого с помощью кода JavaScript, выполняющего функцию обратного вызова, снабженную строкой запроса. Также устанавливает тип JavaScript MIME для ответа.

 public ContentResult JsonpCall(string callback)
 {
      return Content(String.Format("{0}({1});",
          callback, 
          new JavaScriptSerializer().Serialize(new { a = 1 })),    
          "application/javascript");
 }

Вместо того, чтобы создавать подклассы для моих контроллеров с помощью методов Jsonp(), я пошел по пути метода расширения, так как он кажется мне чище. Хорошая вещь о JsonpResult заключается в том, что вы можете протестировать его точно так же, как и JsonResult.

Я сделал:

public static class JsonResultExtensions
{
    public static JsonpResult ToJsonp(this JsonResult json)
    {
        return new JsonpResult { ContentEncoding = json.ContentEncoding, ContentType = json.ContentType, Data = json.Data, JsonRequestBehavior = json.JsonRequestBehavior};
    }
}

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

Сообщение Ранджу в блоге (также известное как "Это сообщение в блоге, которое я нашел") превосходно, и его чтение позволит вам продвинуть решение, приведенное ниже, чтобы ваш контроллер мог элегантно обрабатывать запросы JSON и междоменного домена того же домена в одном действии контроллера без дополнительный код [в действии].

Несмотря на это, для типов "дай мне код", это так, на случай, если блог снова исчезнет.

В вашем контроллере (этот фрагмент является новым / не блог-кодом):

[AllowCrossSiteJson]
public ActionResult JsonpTime(string callback)
{
    string msg = DateTime.UtcNow.ToString("o");
    return new JsonpResult
    {
        Data = (new
        {
            time = msg
        })
    };
}

JsonpResult найден в этом отличном сообщении в блоге:

/// <summary>
/// Renders result as JSON and also wraps the JSON in a call
/// to the callback function specified in "JsonpResult.Callback".
/// http://blogorama.nerdworks.in/entry-EnablingJSONPcallsonASPNETMVC.aspx
/// </summary>
public class JsonpResult : JsonResult
{
    /// <summary>
    /// Gets or sets the javascript callback function that is
    /// to be invoked in the resulting script output.
    /// </summary>
    /// <value>The callback function name.</value>
    public string Callback { get; set; }

    /// <summary>
    /// Enables processing of the result of an action method by a
    /// custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>.
    /// </summary>
    /// <param name="context">The context within which the
    /// result is executed.</param>
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        HttpResponseBase response = context.HttpContext.Response;
        if (!String.IsNullOrEmpty(ContentType))
            response.ContentType = ContentType;
        else
            response.ContentType = "application/javascript";

        if (ContentEncoding != null)
            response.ContentEncoding = ContentEncoding;

        if (Callback == null || Callback.Length == 0)
            Callback = context.HttpContext.Request.QueryString["callback"];

        if (Data != null)
        {
            // The JavaScriptSerializer type was marked as obsolete
            // prior to .NET Framework 3.5 SP1 
#pragma warning disable 0618
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            string ser = serializer.Serialize(Data);
            response.Write(Callback + "(" + ser + ");");
#pragma warning restore 0618
        }
    }
}

Примечание. Следуя комментариям к OP от @Ranju и других, я решил, что стоит опубликовать "минимальный" функциональный код из поста Ранджу в блоге в виде вики сообщества. Хотя можно с уверенностью сказать, что Ранджу добавил вышеуказанный и другой код в свой блог для свободного использования, я не собираюсь копировать его слова здесь.

Для ASP.NET Core, а НЕ для ASP.NET MVC.
Это адаптированная версия для ASP.NET CORE решения, содержащегося в ответе.

public class JsonpResult : JsonResult
{
    public JsonpResult(object value) : base(value)
    {
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        HttpResponse response = context.HttpContext.Response;

        if (!String.IsNullOrEmpty(ContentType))
            response.ContentType = ContentType;
        else
            response.ContentType = "application/javascript";

        if (Value != null)
        {
            HttpRequest request = context.HttpContext.Request;
            string serializedJson = JsonConvert.SerializeObject(Value);
            string result = $"{request.Query["callback"]}({serializedJson})";
            await response.WriteAsync(result);
        }
    }
}

Статьи по ссылкам на стимы и ранью были очень полезны и прояснили ситуацию.

Тем не менее, мне пришлось почесать голову об использовании расширений, подклассов в контексте кода MVC, который я нашел в Интернете.

Было два ключевых момента, которые меня поймали:

  1. Код, который я получил из ActionResult, но в ExecuteResult был некоторый код, возвращающий либо XML, либо JSON.
  2. Затем я создал ActionResult на основе Generics, чтобы гарантировать, что тот же ExecuteResults использовался независимо от типа возвращаемых данных.

Итак, объединяя эти два - мне не нужно было никаких дополнительных расширений или подклассов, чтобы добавить механизм возврата JSONP, просто изменив мои существующие ExecuteResults.

Что меня смутило, так это то, что я действительно искал способ получить или расширить JsonResult, без перекодирования ExecuteResult. Поскольку JSONP, по сути, является строкой JSON с префиксом и суффиксом, это кажется пустой тратой. Однако подчиненный ExecuteResult использует respone.write - так что самый безопасный способ изменить это перекодировать ExecuteResults, как это удобно в различных публикациях!

Я могу опубликовать некоторый код, если это будет полезно, но в этой теме уже достаточно много кода.

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Web;
        using System.Web.Mvc;
        using System.Web.Script.Serialization;

        namespace Template.Web.Helpers
        {
            public class JsonpResult : JsonResult
            {
                public JsonpResult(string callbackName)
                {
                    CallbackName = callbackName;
                }

                public JsonpResult()
                    : this("jsoncallback")
                {
                }

                public string CallbackName { get; set; }

                public override void ExecuteResult(ControllerContext context)
                {
                    if (context == null)
                    {
                        throw new ArgumentNullException("context");
                    }

                    var request = context.HttpContext.Request;
                    var response = context.HttpContext.Response;

                    string jsoncallback = ((context.RouteData.Values[CallbackName] as string) ?? request[CallbackName]) ?? CallbackName;

                    if (!string.IsNullOrEmpty(jsoncallback))
                    {
                        if (string.IsNullOrEmpty(base.ContentType))
                        {
                            base.ContentType = "application/x-javascript";
                        }
                        response.Write(string.Format("{0}(", jsoncallback));
                    }

                    base.ExecuteResult(context);

                    if (!string.IsNullOrEmpty(jsoncallback))
                    {
                        response.Write(")");
                    }
                }
            }

            public static class ControllerExtensions
            {
                public static JsonpResult Jsonp(this Controller controller, object data, string callbackName = "callback")
                {
                    return new JsonpResult(callbackName)
                    {
                        Data = data,
                        JsonRequestBehavior = JsonRequestBehavior.AllowGet
                    };
                }

                public static T DeserializeObject<T>(this Controller controller, string key) where T : class
                {
                    var value = controller.HttpContext.Request.QueryString.Get(key);
                    if (string.IsNullOrEmpty(value))
                    {
                        return null;
                    }
                    JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
                    return javaScriptSerializer.Deserialize<T>(value);
                }
            }
        }

//Example of using the Jsonp function::
  //  1-
    public JsonResult Read()
            {
                IEnumerable<User> result = context.All();        

                return this.Jsonp(result);
            }
    //2-
    public JsonResult Update()
            {
                var models = this.DeserializeObject<IEnumerable<User>>("models");
                if (models != null)
                {
                    Update(models); //Update properties & save change in database
                }
                return this.Jsonp(models);
            }

Решение, приведенное выше, является хорошим способом работы, но его следует расширять новым типом результата, вместо того, чтобы иметь метод, который возвращает JsonResult, вы должны написать методы, которые возвращают ваши собственные типы результатов

public JsonPResult testMethod() {
    // use the other guys code to write a method that returns something
}

public class JsonPResult : JsonResult
{
    public FileUploadJsonResult(JsonResult data) {
        this.Data = data;
    }      

    public override void ExecuteResult(ControllerContext context)
    {
        this.ContentType = "text/html";
        context.HttpContext.Response.Write("<textarea>");
        base.ExecuteResult(context);
        context.HttpContext.Response.Write("</textarea>");
    }
}
Другие вопросы по тегам