I have a function with a List return type. I'm using this in a JSON-enabled WebService like:
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public List<Product> GetProducts(string dummy) /* without a parameter, it will not go through */
{
return new x.GetProducts();
}
this returns:
{"d":[{"__type":"Product","Id":"2316","Name":"Big Something ","Price":"3000","Quantity":"5"}]}
I need to use this code in a simple aspx file too, so I created a JavaScriptSerializer:
JavaScriptSerializer js = new JavaScriptSerializer();
StringBuilder sb = new StringBuilder();
List<Product> products = base.GetProducts();
js.RegisterConverters(new JavaScriptConverter[] { new ProductConverter() });
js.Serialize(products, sb);
string _jsonShopbasket = sb.ToString();
but it returns without a type:
[{"Id":"2316","Name":"Big One ","Price":"3000","Quantity":"5"}]
Does anyone have any clue how to get the second Serialization work like the first?
Thanks!
When you create the JavaScriptSerializer, pass it an instance of SimpleTypeResolver.
new JavaScriptSerializer(new SimpleTypeResolver())
No need to create your own JavaScriptConverter.
Ok, I have the solution, I've manually added the __type to the collection in the JavaScriptConverter class.
public class ProductConverter : JavaScriptConverter
{ public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Product p = obj as Product;
if (p == null)
{
throw new InvalidOperationException("object must be of the Product type");
}
IDictionary<string, object> json = new Dictionary<string, object>();
json.Add("__type", "Product");
json.Add("Id", p.Id);
json.Add("Name", p.Name);
json.Add("Price", p.Price);
return json;
}
}
Is there any "offical" way to do this?:)
Building on Joshua's answer, you need to implement a SimpleTypeResolver
Here is the "official" way that worked for me.
1) Create this class
using System;
using System.Web;
using System.Web.Compilation;
using System.Web.Script.Serialization;
namespace XYZ.Util
{
/// <summary>
/// as __type is missing ,we need to add this
/// </summary>
public class ManualResolver : SimpleTypeResolver
{
public ManualResolver() { }
public override Type ResolveType(string id)
{
return System.Web.Compilation.BuildManager.GetType(id, false);
}
}
}
2) Use it to serialize
var s = new System.Web.Script.Serialization.JavaScriptSerializer(new XYZ.Util.ManualResolver());
string resultJs = s.Serialize(result);
lblJs.Text = string.Format("<script>var resultObj = {0};</script>", resultJs);
3) Use it to deserialize
System.Web.Script.Serialization.JavaScriptSerializer(new XYZ.Util.ManualResolver());
var result = json.Deserialize<ShoppingCartItem[]>(jsonItemArray);
Full post here: http://www.agilechai.com/content/serialize-and-deserialize-to-json-from-asp-net/
Related
Is there any way to implement #Html.Action method in ASP.NET Core (like it was in ASP.NET MVC)? I know about the ViewComponent feature of ASP.NET Core. But there is a scenario when we have to use Action method.
This is the way to implement this as a HtmlHelper extension.
You can use it as follows:
The last parameter is an anonymous type.
#Html.Action("Action");
#Html.Action("Action", new { string a = "a", int i = 5 }
#Html.Action("Action", "Controller");
#Html.Action("Action", "Controller", new (string a = "a", int i = 5 }
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.Rendering
{
public static class HtmlHelperViewExtensions
{
public static IHtmlContent Action(this IHtmlHelper helper, string action, object parameters = null)
{
var controller = (string)helper.ViewContext.RouteData.Values["controller"];
return Action(helper, action, controller, parameters);
}
public static IHtmlContent Action(this IHtmlHelper helper, string action, string controller, object parameters = null)
{
var area = (string)helper.ViewContext.RouteData.Values["area"];
return Action(helper, action, controller, area, parameters);
}
public static IHtmlContent Action(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
if (action == null)
throw new ArgumentNullException(nameof(controller));
if (controller == null)
throw new ArgumentNullException(nameof(action));
var task = ActionAsync(helper, action, controller, area, parameters);
return task.Result;
}
private static async Task<IHtmlContent> ActionAsync(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
// fetching required services for invocation
var currentHttpContext = helper.ViewContext.HttpContext;
var httpContextFactory = GetServiceOrFail<IHttpContextFactory>(currentHttpContext);
var actionInvokerFactory = GetServiceOrFail<IActionInvokerFactory>(currentHttpContext);
var actionSelector = GetServiceOrFail<IActionDescriptorCollectionProvider>(currentHttpContext);
// creating new action invocation context
var routeData = new RouteData();
var routeParams = new RouteValueDictionary(parameters ?? new { });
var routeValues = new RouteValueDictionary(new { area, controller, action });
var newHttpContext = httpContextFactory.Create(currentHttpContext.Features);
newHttpContext.Response.Body = new MemoryStream();
foreach (var router in helper.ViewContext.RouteData.Routers)
routeData.PushState(router, null, null);
routeData.PushState(null, routeValues, null);
routeData.PushState(null, routeParams, null);
var actionDescriptor = actionSelector.ActionDescriptors.Items.First(i => i.RouteValues["Controller"] == controller && i.RouteValues["Action"] == action);
var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor);
// invoke action and retreive the response body
var invoker = actionInvokerFactory.CreateInvoker(actionContext);
string content = null;
await invoker.InvokeAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
content = task.Exception.Message;
}
else if (task.IsCompleted)
{
newHttpContext.Response.Body.Position = 0;
using (var reader = new StreamReader(newHttpContext.Response.Body))
content = reader.ReadToEnd();
}
});
return new HtmlString(content);
}
private static TService GetServiceOrFail<TService>(HttpContext httpContext)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
var service = httpContext.RequestServices.GetService(typeof(TService));
if (service == null)
throw new InvalidOperationException($"Could not locate service: {nameof(TService)}");
return (TService)service;
}
}
}
While it does work and renders the content correctly, when you try to access the IHttpContextAccessor.HttpContext property afterwards it is empty for some reason. I.e. you are opening another controller method which has a reference to IHttpContextAccessor via dependency injection and try to access the HttpContext property after you have renderend an Html.Action-element inside a partial view for example. If I remove the Html.Action-Element, HttpContext is filled correctly. I assume it somehow destroys the context.
I m trying to add html attribute in extension method for kendo's datetime picker
public static DatePickerBuilder Readonly(this DatePickerBuilder builder, bool isReadOnly)
{
if (isReadOnly)
{
var dic = new Dictionary<string, object>();
dic.Add("readonly", "readonly");
builder.HtmlAttributes(dic);
}
return builder;
}
The code above is working. But its unnecessary creating dictionary. The HtmlAttribute method takes object as parameter, how do i make a use of it so i don't have to create dictionary?
Note: I do not want to use razor syntax to add readonly attribute
Update 1
So i solved the above issue by changing the code like below
public static DatePickerBuilder Readonly(this DatePickerBuilder builder, bool isReadOnly)
{
if (isReadOnly)
{
var comp = builder.ToComponent();
comp.HtmlAttributes.Add("readonly", "readonly");
}
return builder;
}
However i have to make sure in razor i call readonly() extension method as the last method in the chain.
Code blow does not work
#(Html.Kendo().DatePickerFor(x => x.Deadline)
.Format("MM/dd/yyyy")
.Readonly(Model.IsEnabled)
.HtmlAttributes(new { data_inherit_value = Model.InheritDeadline }))
I think its a issue with Kendo date picker control. It overwrites existing htmlattributes
If i change the order and use readonly at the end then code blow works.
#(Html.Kendo().DatePickerFor(x => x.Deadline)
.Format("MM/dd/yyyy")
.HtmlAttributes(new { data_inherit_value = Model.InheritDeadline })
.Readonly(Model.IsEnabled))
They actually clear the HtmlAttributes collection before they Merge, the now cleared, HtmlAttributes collection with whatever attributes you are passing in. I'd call it a bug...
/// <summary>Sets the HTML attributes.</summary>
/// <param name="attributes">The HTML attributes.</param>
/// <returns></returns>
public virtual TBuilder HtmlAttributes(object attributes)
{
return this.HtmlAttributes(attributes.ToDictionary());
}
/// <summary>Sets the HTML attributes.</summary>
/// <param name="attributes">The HTML attributes.</param>
/// <returns></returns>
public virtual TBuilder HtmlAttributes(IDictionary<string, object> attributes)
{
this.Component.HtmlAttributes.Clear();
Kendo.Mvc.Extensions.DictionaryExtensions.Merge(this.Component.HtmlAttributes, attributes);
return this as TBuilder;
}
One way to get around this is to create a MergeHtmlAttributes extension for each control you are using:
public static TextBoxBuilder<T> MergeHtmlAttributes<T>(this TextBoxBuilder<T> builder, object attributes)
{
Kendo.Mvc.Extensions.DictionaryExtensions.Merge(builder.ToComponent().HtmlAttributes, attributes);
return builder;
}
...and in your view:
#(Html.Kendo().TextBoxFor(m => m.FirstName)
.Enable(Model.IsViewEditable)
.MergeHtmlAttributes(new {#class = "k-textbox"}))
Consider this one, in order do not cumulate multiple readonlies
public static DropDownListBuilder Readonly(this DropDownListBuilder builder, bool isReadOnly)
{
const string Readonly = "readonly";
var comp = builder.ToComponent();
comp.HtmlAttributes.Remove(Readonly);
if (isReadOnly)
comp.HtmlAttributes.Add(Readonly, isReadOnly);
return builder;
}
If I have this controller:
public class SomeController : ApiController
{
public SomeInfoDto Get()
{
return new SomeInfoDto();
}
}
When I call /api/Some with a get request I will get JSON in the Content Body because JSON.NET has been kind enough to serialise it for me.
However, if I wanted to send a 500 HTTP code and some JSON I thought I could do this:
public HttpResponseMessage Get()
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = // not sure what to put here
}
}
So I googled to find out what to put for the Content and found this resource
public class JsonContent : HttpContent
{
private readonly JToken _value;
public JsonContent(JToken value)
{
_value = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
protected override Task SerializeToStreamAsync(Stream stream,
TransportContext context)
{
var jw = new JsonTextWriter(new StreamWriter(stream))
{
Formatting = Formatting.Indented
};
_value.WriteTo(jw);
jw.Flush();
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
Which I can easily modify to fit my purposes.
However, my question is, if I get SerializeToStreamAsync to use JsonConvert.SerializeObject(_value) further down the Web Api pipeline will it be serialised again?
I have set the GlobalConfiguration.Configuration like this:
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
My Implementation for the SerializeToStreamAsync is this:
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
using (var streamWriter = new StreamWriter(stream))
{
streamWriter.WriteAsync(JsonConvert.SerializeObject(this.value)).Wait();
streamWriter.FlushAsync().Wait();
}
return Task.FromResult<object>(null);
}
EDIT: Providing a concrete sample with formatter.
If you want to send JSON content with Http Status Code 500, you can use the extension method HttpRequestMessage.CreateResponse. There is no need for any advanced serialization and formatting. More info on that here.
https://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessageextensions.createresponse%28v=vs.118%29.aspx
Request.CreateResponse(HttpStatusCode.InternalServerError, new string[] { "value1", "value2" }, new JsonMediaTypeFormatter())
(Or)
Request.CreateResponse(HttpStatusCode.InternalServerError, new string[] { "value1", "value2" }, 'application/json')
I successfully used System.ServiceModel.Syndication.SyndicationFeed to add some Atom10 output from my ASP.NET 3.5 web site. It was my first production use of ServiceStack, and it all work fine.
My first attempt resulted in UTF-16 instead of UTF-8, which was ok for all browsers except IE. So I had to create XmlWriterResult class to solve this. My solution works, but how should I have done?
public class AsStringService : IService<AsString>
{
public object Execute(AsString request)
{
SyndicationFeed feed = new SyndicationFeed("Test Feed " + request.Name, "This is a test feed", new Uri("http://Contoso/testfeed"), "TestFeedID", DateTime.Now);
SyndicationItem item = new SyndicationItem("Test Item", "This is the content for Test Item", new Uri("http://localhost/ItemOne"), "TestItemID", DateTime.Now);
List<SyndicationItem> items = new List<SyndicationItem>();
items.Add(item);
feed.Items = items;
Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(feed);
return new XmlWriterResult(xmlWriter => atomFormatter.WriteTo(xmlWriter));
}
}
XmlWriterResult is:
public delegate void XmlWriterDelegate(XmlWriter xmlWriter);
/// <summary>
/// From https://groups.google.com/forum/?fromgroups=#!topic/servicestack/1U02g7kViRs
/// </summary>
public class XmlWriterResult : IDisposable, IStreamWriter, IHasOptions
{
private readonly XmlWriterDelegate _writesToXmlWriter;
public XmlWriterResult(XmlWriterDelegate writesToXmlWriter)
{
_writesToXmlWriter = writesToXmlWriter;
this.Options = new Dictionary<string, string> {
{ HttpHeaders.ContentType, "text/xml" }
};
}
public void Dispose()
{
}
public void WriteTo(Stream responseStream)
{
using (XmlWriter xmlWriter = XmlWriter.Create(responseStream))
{
_writesToXmlWriter(xmlWriter);
}
}
public IDictionary<string, string> Options { get; set; }
}
(Yes, I like delegates, I also do a lot of F#)
As this isn't a question with any clear answer I'd just tell you how I'd do it.
Assuming SyndicationFeed is a clean DTO / POCO you should just return that in your service:
public class AsStringService : IService
{
public object Any(AsString request)
{
SyndicationFeed feed = new SyndicationFeed("Test Feed " + request.Name,
"This is a test feed", new Uri("http://Contoso/testfeed"),
"TestFeedID", DateTime.Now);
SyndicationItem item = new SyndicationItem("Test Item",
"This is the content for Test Item",
new Uri("http://localhost/ItemOne"),
"TestItemID", DateTime.Now);
List<SyndicationItem> items = new List<SyndicationItem>();
items.Add(item);
feed.Items = items;
return feed;
}
}
This example uses ServiceStack's New API which is much nicer, you should try using it for future services.
This will allow you to get Content Negotiation in all of ServiceStack's registered Content-Types.
Registering a Custom Media Type
You could then register a Custom Media Type as seen in ServiceStack's Northwind v-card example:
private const string AtomContentType = "application/rss+xml";
public static void Register(IAppHost appHost)
{
appHost.ContentTypeFilters.Register(AtomContentType, SerializeToStream,
DeserializeFromStream);
}
public static void SerializeToStream(IRequestContext requestContext,
object response, Stream stream)
{
var syndicationFeed = response as SyndicationFeed;
if (SyndicationFeed == null) return;
using (XmlWriter xmlWriter = XmlWriter.Create(stream))
{
Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(feed);
atomFormatter.WriteTo(xmlWriter);
}
}
public static object DeserializeFromStream(Type type, Stream stream)
{
throw new NotImplementedException();
}
Now the rss+xml format should appear in the /metadata pages and ServiceStack will let you request the format in all the supported Content-Negotation modes, e.g:
Accept: application/rss+xml HTTP Header
Using the predefined routes, e.g: /rss+xml/syncreply/AsString
Or appending /route?format=rss+xml to the QueryString
Note: you may need to url encode the '+' character
Before I had this code to return an arbitrary json object with just an id property.
How do I convert this to the new RC version of WebApi now that HttpResponseMessage is not supported and it now uses Newtonsofts JSON.NET?
public HttpResponseMessage<JsonValue> Post(MyModel model)
{
var id = _theService.AddEntity(model);
dynamic okResponse = new JsonObject();
okResponse["id"] = id;
return new CreateResponse<JsonValue>(okResponse);
}
And...
public class CreateResponse<T> : ResponseBase<T>
{
public CreateResponse()
: base(HttpStatusCode.Created)
{
}
public CreateResponse(T resource)
: base(resource, HttpStatusCode.Created)
{
}
}
public abstract class ResponseBase<T> : HttpResponseMessage<T>
{
protected ResponseBase(HttpStatusCode httpStatusCode)
: base(httpStatusCode)
{
}
protected ResponseBase(T resource, HttpStatusCode httpStatusCode)
: base(resource, httpStatusCode)
{
if (resource is IApiResource)
{
var apiResource = resource as IApiResource;
var resourceLocation = new ResourceLocation();
apiResource.SetLocation(resourceLocation);
Headers.Location = resourceLocation.Location;
}
}
}
The CreateResponse extention method does not accept dynamic variables.
Please change
dynamic okResponse = new JsonObject();
to something like
var okResponse = new JsonObject();
Please see this link also:
https://aspnetwebstack.codeplex.com/discussions/359242
Use Request.CreateResponse(statuscode, content) inside your controller
Maybe I'm missing something in your question, but you could just do this:
public dynamic Post(MyModel model)
{
var id = _theService.AddEntity(model);
return new { id = id };
}
EDIT: assuming your client sets the Content-type to application/json