ASP.NET [WebMethod], does it always return XML?
I know it can only return serializable data types however can it for instance return a JSON?
As I'm aware of, you can return XML or JSON.
To return JSON add this annotation or your method:
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
And on your class allow ScriptService
[ScriptService]
An example:
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public Dictionary<string, object> Test()
{
var ret = new Dictionary<string, object>();
ret.Add("Test", 1);
return ret;
}
// result:
{d:{Test:1}}
Related
I have a data object with an dictionary.
Now I want to serialize this dictionary to a json string.
Is it possible to do this inside the template?
public string GenerateTest()
{
Dictionary<string, object> dataDictionary = new Dictionary<string, object>();
dataDictionary.Add("Testdata1", "Value1");
dataDictionary.Add("Testdata2", "Value2");
string result = Smart.Format(CultureInfo.InvariantCulture, "{data.someFormattertoGetAnJsonString}", new {data= dataDictionary });
Console.WriteLine(result);
return result;
}
Sure you could do that, but not in a generic way. SmartFormat is a formatter, rather than a serializer. So in general, SmartFormat is best in filling a text template with data, like it is required with mail merge.
In your case, you'll be better off using serializers like System.Text.Json or Newtonsoft.Json.
For the latter, here is an example how simple this works: https://www.newtonsoft.com/json/help/html/serializedictionary.htm
I have attached my solution. You have to register the ToJSONFormatter with the AddExtensions Method. After that you can call it like this: {MyVariable:ToJSON()}
Smart.Default.AddExtensions(new ToJSONFormatter());
public class ToJSONFormatter : IFormatter
{
public string Name { get; set; } = "ToJSON";
public bool CanAutoDetect { get; set; } = false;
private JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings { DateFormatString = "yyyy-MM-ddTHH:mm:ss" };
//{Data:ToJSON()}
public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
{
formattingInfo.Write(JsonConvert.SerializeObject(formattingInfo.CurrentValue));
return true;
}
}
Let's assume we have the following simple ajax-call:
$.ajax({
url: "/somecontroller/someaction",
data: JSON.stringify({
someString1: "",
someString2: null,
someArray1: [],
someArray2: null
}),
method: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8"
})
.done(function (response) {
console.log(response);
});
The ajax call targets an action of an asp.net controller. The asp.net website has default ("factory") settings when it comes to the handling json-serialization with the only tweak being that Newtonsoft.Json.dll is installed via nuget and thus the web.config contains the following section:
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
The configuration sections for both webapi and mvc inside global.asax.cs have remained as they where. Having said all this, I noticed that if the controller 'somecontroller' is a webapi controller:
public class FooController : ApiController
{
public class Some
{
public string SomeString1 { get; set; }
public string SomeString2 { get; set; }
public long[] SomeArray1 { get; set; }
public long[] SomeArray2 { get; set; }
}
[HttpPost]
public IHttpActionResult Bar([FromBody] Some entity)
{
return Ok(new {ping1 = (string) null, ping2 = "", ping3 = new long[0]});
}
}
then the data received in the c# world inside the 'someaction' method are like so:
entity.someString1: "",
entity.someString2: null,
entity.someArray1: [],
entity.someArray2: null
However, if the controller is an mvc controller (mvc4 to be precise):
public class FooController : System.Web.Mvc.Controller
{
public class Some
{
public string SomeString1 { get; set; }
public string SomeString2 { get; set; }
public long[] SomeArray1 { get; set; }
public long[] SomeArray2 { get; set; }
}
[HttpPost]
public System.Web.Mvc.JsonResult Bar([FromBody] Some entity)
{
return Json(new { ping1 = (string)null, ping2 = "", ping3 = new long[0] });
}
}
then the data received in the csharp world inside the method look like so:
entity.someString1: null,
entity.someString2: null,
entity.someArray1: null,
entity.someArray2: null
It's apparent that there is a deviation between webapi and mvc controllers in terms of how deserialization of parameters works both when it comes to empty arrays and empty strings. I have managed to work around the quirks of the MVC controller so as to enforce the "webapi" behaviour both for empty strings and empty arrays (I will post my solution at the end for completeness).
My question is this:
Why does this deviation in regards to deserialization exist in the first place?
I can't come to terms that it was done merely for the sake of "convenience" given how much room the default mvc-settings leave for bugs that are just nerve-racking to discern and fix clearly and consistently at the action/dto-level.
Addendum: For anyone interested here's how I forced the mvc controller to behave the "webapi" way when it comes to deserializing parameters before feeding them into the action-methods:
//inside Application_Start
ModelBinders.Binders.DefaultBinder = new CustomModelBinder_Mvc();
ValueProviderFactories.Factories.Remove(
ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault()
);
ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory_Mvc());
Utility classes:
using System.Web.Mvc;
namespace Project.Utilities
{
public sealed class CustomModelBinder_Mvc : DefaultModelBinder //0
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
Binders = new ModelBinderDictionary { DefaultBinder = this };
return base.BindModel(controllerContext, bindingContext);
}
}
//0 respect empty ajaxstrings aka "{ foo: '' }" gets converted to foo="" instead of null http://stackoverflow.com/a/12734370/863651
}
And
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Web.Mvc;
using IValueProvider = System.Web.Mvc.IValueProvider;
// ReSharper disable RedundantCast
namespace Project.Utilities
{
public sealed class JsonNetValueProviderFactory_Mvc : ValueProviderFactory //parameter deserializer
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException(nameof(controllerContext));
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
var jsonReader = new JsonTextReader(new StreamReader(controllerContext.HttpContext.Request.InputStream));
if (!jsonReader.Read())
return null;
var jsonObject = jsonReader.TokenType == JsonToken.StartArray //0
? (object)JsonSerializer.Deserialize<List<ExpandoObject>>(jsonReader)
: (object)JsonSerializer.Deserialize<ExpandoObject>(jsonReader);
return new DictionaryValueProvider<object>(AddToBackingStore(jsonObject), InvariantCulture); //1
}
private static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
private static readonly JsonSerializer JsonSerializer = new JsonSerializer //newtonsoft
{
Converters =
{
new ExpandoObjectConverter(),
new IsoDateTimeConverter {Culture = InvariantCulture}
}
};
//0 use jsonnet to deserialize object to a dynamic expando object if we start with a [ treat this as an array
//1 return the object in a dictionary value provider which mvc can understand
private static IDictionary<string, object> AddToBackingStore(object value, string prefix = "", IDictionary<string, object> backingStore = null)
{
backingStore = backingStore ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
var d = value as IDictionary<string, object>;
if (d != null)
{
foreach (var entry in d)
{
AddToBackingStore(entry.Value, MakePropertyKey(prefix, entry.Key), backingStore);
}
return backingStore;
}
var l = value as IList;
if (l != null)
{
if (l.Count == 0) //0 here be dragons
{
backingStore[prefix] = new object[0]; //0 here be dragons
}
else
{
for (var i = 0; i < l.Count; i++)
{
AddToBackingStore(l[i], MakeArrayKey(prefix, i), backingStore);
}
}
return backingStore;
}
backingStore[prefix] = value;
return backingStore;
}
private static string MakeArrayKey(string prefix, int index) => $"{prefix}[{index.ToString(CultureInfo.InvariantCulture)}]";
private static string MakePropertyKey(string prefix, string propertyName) => string.IsNullOrEmpty(prefix) ? propertyName : $"{prefix}.{propertyName}";
}
//0 here be dragons its vital to deserialize empty jsarrays "{ foo: [] }" to empty csharp array aka new object[0]
//0 here be dragons without this tweak we would get null which is completely wrong
}
Why does this deviation in regards to deserialization exist in the first place?
History.
When ASP.NET MVC was first created in 2009, it used the native .NET JavaScriptSerializer class to handle JSON serialization. When Web API came along three years later, the authors decided to switch to using the increasingly popular Json.Net serializer because it was much more robust and full-featured than the older JavaScriptSerializer. However, they apparently felt that they could not change MVC to match for backward compatibility reasons-- existing projects which relied on specific JavaScriptSerializer behaviors would break unexpectedly when upgraded. So, that decision created the discrepancy between MVC and Web API.
In ASP.NET MVC Core, the internals of MVC and Web API have been unified and use Json.Net.
I have a problem. I'd like to pass a dictionary via RedirectToAction via RouteValueDictionary. Is there a possibility to do this?
I have a POST method:
[HttpPost]
public ActionResult Search(MyViewModel _myViewModel)
{
IDictionary<string, string> parameters = new Dictionary<string, string>();
foreach (var item in _myViewModel)
{
parameters.Add(item.ValueId, item.ValueName);
}
return RedirectToAction("Search", new RouteValueDictionary(parameters));
}
I'd like to have an url like this:
http://localhost:26755/Searcher/Search?id1=value1&id2=value2&id3=value3
How the GET method should look like?
[HttpGet]
public ActionResult Search( **what's here?** )
{
(...)
return View(myViewModel);
}
First we need to fix your Search action that performs the redirect. You should use an IDictionary<string, object> instead of IDictionary<string, string> if you want to get the desired query string parameters when redirecting:
[HttpPost]
public ActionResult Search(MyViewModel _myViewModel)
{
IDictionary<string, object> parameters = new Dictionary<string, object>();
foreach (var item in _myViewModel)
{
parameters.Add(item.ValueId, item.ValueName);
}
return RedirectToAction("Search", new RouteValueDictionary(parameters));
}
and once you have done that in your target controller action you could just use the QueryString dictionary on the request:
[HttpGet]
public ActionResult Search()
{
// this.Request.QueryString is the dictionary you could use to access the
// different keys and values being passed
// For example:
string value1 = this.Request.QueryString["id1"];
...
// or you could loop through them depending on what exactly you are trying to achieve:
foreach (string key in this.Request.QueryString.Keys)
{
string value = this.Request.QueryString[key];
// do something with the value here
}
...
}
{"__type":"CountryModel","dt":null,"ds":null,"dtRow":null,"strSQL":{"Capacity":16,"MaxCapacity":2147483647,"Length":0},"iCnt":0,"ConnType":"FP","Code":"AE","Value":"United Arab Emirates"},{"__type":"CountryModel","dt":null,"ds":null,"dtRow":null,"strSQL":{"Capacity":16,"MaxCapacity":2147483647,"Length":0},"iCnt":0,"ConnType":"FP","Code":"AF","Value":"Afghanistan"},{"__type":"CountryModel","dt":null,"ds":null,"dtRow":null,"strSQL":{"Capacity":16,"MaxCapacity":2147483647,"Length":0},"iCnt":0,"ConnType":"FP","Code":"AG","Value":"Antigua and Barbuda"},
this is the webmethod that I return by json, but the problem is, asp.net return value from base class that I inherit from, how to solve this? although is working, but the data are wasted as I don't wish to return the value, any idea?
BaseModel.cs
public string ConnType="";
public datatable dt;
CountryModel.cs:
public class CountryModel:BaseModel{
public List<MenuFunctionModel> AssignList()
{
var _List = new List<MenuFunctionModel>();
foreach (DataRow dr in dt.Rows)
{
_List.Add(new MenuFunctionModel
{
Code = dr["Code"].ToString(),
Title = dr["Title"].ToString(),
Description = dr["Description"].ToString(),
Location = dr["Location"].ToString() + dr["FileName"].ToString()
});
}
return _List;
}
}
webservices api:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static List<CountryModel> loadCt()
{
CountryModel _Country = new CountryModel();
_Country.SelectAll();
return _Country.AssignList();
}
You can return anynomous types from your WebMethod as:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public static Object loadCt()
{
CountryModel _Country = new CountryModel();
_Country.SelectAll();
return _Country.AssignList().Select(m=> new{ Code=m.Code, Title =m.Title}); //select those fields only which you want to return
}
For more references, Click here
I have an object called MyObject that has several properties. MyList is a list of MyObject that I populate with a linq query and then I serialize MyList into json. I end up with something like this
List<MyObject> MyList = new List<MyObject>();
MyList = TheLinqQuery(TheParam);
var TheJson = new System.Web.Script.Serialization.JavaScriptSerializer();
string MyJson = TheJson.Serialize(MyList);
What I want to do is serialize only parts of MyObject. For instance, I might have Property1, Property2...Propertyn and I want MyJson to only include Property3, Property5 and Property8.
I thought of a way to do this by creating a new object with only the properties I want and from there create a new list for the serialization. Is this the best way or is there a better/faster way?
Thanks.
// simple dummy object just showing what "MyObject" could potentially be
public class MyObject
{
public String Property1;
public String Property2;
public String Property3;
public String Property4;
public String Property5;
public String Property6;
}
// custom converter that tells the serializer what to do when it sees one of
// the "MyObject" types. Use our custom method instead of reflection and just
// dumping properties.
public class MyObjectConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
throw new ApplicationException("Serializable only");
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
// create a variable we can push the serailized results to
Dictionary<string, object> result = new Dictionary<string, object>();
// grab the instance of the object
MyObject myobj = obj as MyObject;
if (myobj != null)
{
// only serailize the properties we want
result.Add("Property1", myobj.Property1);
result.Add("Property3", myobj.Property3);
result.Add("Property5", myobj.Property5);
}
// return those results
return result;
}
public override IEnumerable<Type> SupportedTypes
{
// let the serializer know we can accept your "MyObject" type.
get { return new Type[] { typeof(MyObject) }; }
}
}
And then where ever you're serializing:
// create an instance of the serializer
JavaScriptSerializer serializer = new JavaScriptSerializer();
// register our new converter so the serializer knows how to handle our custom object
serializer.RegisterConverters(new JavaScriptConverter[] { new MyObjectConverter() });
// and get the results
String result = serializer.Serialize(MyObjectInstance);