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
};
}
Работает как шарм.
Ответы
Ответ 1
Вот простое решение, если вы не хотите определять фильтр действий
Клиентский код с помощью 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");
}
Ответ 2
Вместо того, чтобы подклассифицировать мои контроллеры с помощью методов 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.
Ответ 3
Сообщение в блоге Ranju (aka "Это сообщение в блоге, которое я нашел" ) отлично, и его чтение позволит вам продолжить решение ниже так что ваш контроллер может обрабатывать JSON и междоменные JSONP-запросы с одним доменом изящно в одном и том же действии контроллера без дополнительного кода [в действии].
Независимо от того, для типов "дайте мне код", вот он, если блог снова исчезнет.
В вашем контроллере (этот фрагмент является новым/не-блочным кодом):
[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 и других, я подумал, что стоит опубликовать "голый минимум" "функциональный код из сообщения блога Ranju как вики сообщества. Хотя можно с уверенностью сказать, что Раню добавил этот код и другой код в своем блоге, который будет использоваться свободно, я не буду копировать его слова здесь.
Ответ 4
Связанные статьи стимм и радю v были очень полезны и сделали ситуацию ясной.
Тем не менее, я остался почесывать голову об использовании расширений, подклассификации в контексте кода MVC, который я нашел в Интернете.
Было два ключевых момента, которые застали меня:
- Код, который я получил от ActionResult, но в ExecuteResult был некоторый код для возврата XML или JSON.
- Затем я создал основанный на Generics ActionResult, чтобы гарантировать, что те же ExecuteResults использовались независимо от типа возвращаемых данных.
Итак, объединив два - мне не нужны дальнейшие расширения или подклассы, чтобы добавить механизм для возврата JSONP, просто измените мои существующие ExecuteResults.
Что меня смутило, так это то, что я действительно искал способ получить или расширить JsonResult, не перекодируя ExecuteResult. Поскольку JSONP - это действительно строка JSON с префиксом и суффиксом, это казалось пустой тратой. Однако подкласс ExecuteResult использует respone.write - таким образом, самый безопасный способ изменения состоит в том, чтобы перекодировать ExecuteResults так же легко, как и различные публикации!
Я могу опубликовать некоторый код, если бы это было полезно, но в этом потоке уже много кода.
Ответ 5
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);
}
Ответ 6
приведенное выше решение является хорошим способом работы, но его следует расширять с помощью нового типа результата вместо того, чтобы иметь метод, который возвращает 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>");
}
}