I have an action that returns a JsonResult in my ASP.Net MVC4 application. I'm setting the Data property to an array of pre-defined classes. My issue is that I want to serialize with different property names. No matter what attributes I use, the object is serialized with the pre-defined property names. I've tried the following with no results:
[DataMember(Name = "iTotalRecords")]
[JsonProperty(PropertyName = "iTotalRecords")]
public int TotalRecords { get; set; }
I know "iTotalRecords" seems silly, but this action is for supporting a jQuery plugin that expects "iTotalRecords" and not "TotalRecords". Of course, I want to use names that make sense in my code-behind.
What serializer is used to parse JsonResult? Is there anything I can do or do I have to re-think returning JsonResult as an action result?
What serializer is used to parse JsonResult?
JavaScriptSerializer.
Is there anything I can do or do I have to re-think returning JsonResult as an action result?
Two possibilities come to mind:
define a view model and then map your domain model to the view model
write a custom action result that uses Json.NET or DataContractJsonSerializer and which allow you to control the names of the serialized properties. The following question illustrates this.
Thanks for the suggestions. I went ahead and created an ActionResult that uses Json.Net:
public class JsonNetActionResult : ActionResult
{
public Object Data { get; private set; }
public JsonNetActionResult(Object data)
{
this.Data = data;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.Write(JsonConvert.SerializeObject(Data));
}
}
FYI, it looks like Json.Net respects both [DataMember] and [JsonProperty], but [JsonProperty] will trump [DataMember] if they differ.
I've found part of the solution here and on SO
public class JsonNetResult : ActionResult
{
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonSerializerSettings SerializerSettings { get; set; }
public Formatting Formatting { get; set; }
public JsonNetResult(object data, Formatting formatting)
: this(data)
{
Formatting = formatting;
}
public JsonNetResult(object data):this()
{
Data = data;
}
public JsonNetResult()
{
Formatting = Formatting.None;
SerializerSettings = new JsonSerializerSettings();
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType)
? ContentType
: "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
if (Data == null) return;
var writer = new JsonTextWriter(response.Output) { Formatting = Formatting };
var serializer = JsonSerializer.Create(SerializerSettings);
serializer.Serialize(writer, Data);
writer.Flush();
}
}
So that in my controller, I can do that
return new JsonNetResult(result);
In my model, I can now have:
[JsonProperty(PropertyName = "n")]
public string Name { get; set; }
Note that now, you have to set the JsonPropertyAttribute to every property you want to serialize.
Related
I am building a simple search, sort, page feature. I have attached the code below.
Below are the usecases:
My goal is to pass the "current filters" via each request to persist them particularly while sorting and paging.
Instead of polluting my action method with many (if not too many) parameters, I am thinking to use a generic type parameter that holds the current filters.
I need a custom model binder that can be able to achieve this.
Could someone please post an example implementation?
PS: I am also exploring alternatives as opposed to passing back and forth the complex objects. But i would need to take this route as a last resort and i could not find a good example of custom model binding generic type parameters. Any pointers to such examples can also help. Thanks!.
public async Task<IActionResult> Index(SearchSortPage<ProductSearchParamsVm> currentFilters, string sortField, int? page)
{
var currentSort = currentFilters.Sort;
// pass the current sort and sortField to determine the new sort & direction
currentFilters.Sort = SortUtility.DetermineSortAndDirection(sortField, currentSort);
currentFilters.Page = page ?? 1;
ViewData["CurrentFilters"] = currentFilters;
var bm = await ProductsProcessor.GetPaginatedAsync(currentFilters);
var vm = AutoMapper.Map<PaginatedResult<ProductBm>, PaginatedResult<ProductVm>>(bm);
return View(vm);
}
public class SearchSortPage<T> where T : class
{
public T Search { get; set; }
public Sort Sort { get; set; }
public Nullable<int> Page { get; set; }
}
public class Sort
{
public string Field { get; set; }
public string Direction { get; set; }
}
public class ProductSearchParamsVm
{
public string ProductTitle { get; set; }
public string ProductCategory { get; set; }
public Nullable<DateTime> DateSent { get; set; }
}
First create the Model Binder which should be implementing the interface IModelBinder
SearchSortPageModelBinder.cs
public class SearchSortPageModelBinder<T> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
SearchSortPage<T> ssp = new SearchSortPage<T>();
//TODO: Setup the SearchSortPage<T> model
bindingContext.Result = ModelBindingResult.Success(ssp);
return TaskCache.CompletedTask;
}
}
And then create the Model Binder Provider which should be implementing the interface IModelBinderProvider
SearchSortPageModelBinderProvider.cs
public class SearchSortPageModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType.GetTypeInfo().IsGenericType &&
context.Metadata.ModelType.GetGenericTypeDefinition() == typeof(SearchSortPage<>))
{
Type[] types = context.Metadata.ModelType.GetGenericArguments();
Type o = typeof(SearchSortPageModelBinder<>).MakeGenericType(types);
return (IModelBinder)Activator.CreateInstance(o);
}
return null;
}
}
And the last thing is register the Model Binder Provider, it should be done in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
.
.
services.AddMvc(options=>
{
options.ModelBinderProviders.Insert(0, new SearchSortPageModelBinderProvider());
});
.
.
}
I am working on a project that uses an MVC API with Entity Framework 6.1.3. We need to implement Get, GET/ID, Insert, Update and Delete API's.
for Insert and Update we have a parameter class that is not the same one as the DB, i am not sure what will be the best solution for that.
For an Update (the one that i have more questions) i can find a record and then update all properties manually, this is something i want to avoid. If i use the currentvalues from the entity then i will have to set the ExtraValues properties in all the apis that i am going to write, that kind of looks weird.
Note: i want to have a child class since most of the entities uses the same fields (Created/Updated) so instead of having those in all the classes i rather have them as inheritance.
There has to be a better solution for this problem, could someone help with ideas or best ways to do this.
public class DBClassA : ExtraValues
{
public int ID { get; set; }
public string Name { get; set; }
}
public class DBClassB : ExtraValues
{
public int ID { get; set; }
public string Name { get; set; }
}
//This will be use in all the classes
public class ExtraValues
{
public string SameValueInOtherClasses { get; set; }
public string SameValueInOtherClasses2 { get; set; }
public string SameValueInOtherClasses3 { get; set; }
}
[HttpGet]
public List<DBClassA> Get()
{
return new List<DBClassA>();
}
[HttpGet]
public DBClassA Get(int ID)
{
return new DBClassA();
}
[HttpPost]
public HttpResponseMessage Insert(DBClassA obj)
{
using (var context = new DBEntities())
{
context.Entity.Attach(DBClassA);
context.SaveChanges();
}
return Request.CreateResponse(HttpStatusCode.OK);
}
[HttpPut]
public HttpResponseMessage Update(int ID, DBClassA obj)
{
using (var context = new DBEntities())
{
var entity = context.Entity.Find(ID);
//I will have to put the ExtraValues here
obj.ExtraValues = "";
_context.Entry(entity).CurrentValues.SetValues(obj);
context.SaveChanges();
}
return Request.CreateResponse(HttpStatusCode.OK);
}
using (var db = new DBEntities())
{
db.Entry(obj).State = EntityState.Modified;
db.SaveChanges();
}
more info: http://www.entityframeworktutorial.net/EntityFramework4.3/update-entity-using-dbcontext.aspx
I have:
VIEW
<script type="text/javascript">
function postData() {
var urlact = '#Url.Action("createDoc")';
var model = '#Html.Raw(Json.Encode(Model))';
alert(model);
$.ajax({
data: stringify(model),
type: "POST",
url: urlact,
datatype: "json",
contentType: "application/json; charset=utf-8",
success: function (result) {
window.location.href = '#Url.Action("CreateWordprocessingDocument","Movies")'
}
});
}
</script>
Controller
[HttpPost]
public void createDoc(string mov)
{
var movies = new JavaScriptSerializer().Deserialize<IEnumerable<Movie>>(mov);
//using movies...
}
Model
//[Serializable]
public class Movie
{
//Added Data Annotations for Title & Genre
public int ID { get; set; }
[Required(ErrorMessage = "Insert name")]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required(ErrorMessage = "Inserist genre")]
public string Genre { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
}
Why when I post the stringified data from View (through the Ajax post function) to Controller (createDoc) it stops throwing a ArgumentNullException (seems the Model passed is empty)?
Any workaround/solution?
Note: without stringifying the model it all works, but I'm trying to stringify it cause the other way I've got some issues with the DateTime format.
Note/2: I've also tried replacing the string mov in the input parameters of the Controller action with IEnumerable movies, but it didn't work either.
You'r model is already JSON encoded hence you do not need to Stringify it. This is probably causing Invalid Json data hence why it is not decoded.
if your DateTime format is the issue then explain that issue so we can help solve it.
Adding to the comment #Jaimal's answer about JSON.NET configuration. I use the following:
public class JsonNetResult : JsonResult
{
public JsonNetResult()
{
Formatting = Formatting.None;
}
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonSerializerSettings SerializerSettings { get; set; }
public Formatting Formatting { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
if (Data == null)
return;
// If you need special handling, you can call another form of SerializeObject below
var serializedObject = JsonConvert.SerializeObject(Data, Formatting, new JavaScriptDateTimeConverter());
response.Write(serializedObject);
}
}
with
public abstract class BaseController : Controller
{
/// <summary>
/// Use JSON.NET
/// </summary>
protected override JsonResult Json(object data, string contentType, Encoding contentEncoding)
{
var result = new JsonNetResult
{
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding,
Formatting = Formatting.Indented
};
return result;
}
}
and
public JsonResult Get()
{
return Json(myModel, null, null);
}
You may use different Converters.
I'm using LINQ-to-SQL for CRUD functionality, and DataContractJsonSerializer to serialize the object to JSON. I am also using ASP.NET MVC's data binding to post values to an MVC action that does the inserting. The problem is that it will serialize all of the properties except the Id property. I've got the model set up as so:
[Serializable]
[DataContract(Name = "campaign")]
[Table(Name = "hl.campaigns")]
public class Campaign
{
[DataMember(Name = "id")]
[Column(Name = "id", AutoSync = AutoSync.OnInsert, IsDbGenerated = true, IsPrimaryKey = true)]
public Int32 Id { get; set; }
[DataMember(Name = "createdBy")]
[Column(Name = "created_by")]
public Int32 CreatedBy { get; set; }
[DataMember(Name = "createdOnUtc")]
[Column(Name = "created_on_utc")]
public DateTime CreatedOnUtc { get; set; }
[DataMember(Name = "name")]
[Column(Name = "name", DbType = "NVarChar(256)")]
public String Name { get; set; }
/* more properties here */
}
Here is my custom JsonDataContractActionResult:
public class JsonDataContractActionResult : ActionResult
{
public JsonDataContractActionResult(Object data)
{
this.Data = data;
}
public Object Data { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
var serializer = new DataContractJsonSerializer(this.Data.GetType());
String output = String.Empty;
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, this.Data);
output = Encoding.Default.GetString(ms.ToArray());
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.Write(output);
}
}
Here's the action (JsonContract() returns a JsonDataContractActionResult):
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Modify([Bind(Prefix = "campaign")] Campaign campaign)
{
if (campaign.Id == 0)
{
try
{
CoreDB.Campaigns.InsertOnSubmit(campaign);
CoreDB.SubmitChanges();
return JsonContract(campaign);
}
catch(Exception ex)
{
// TODO: error handling
}
}
return null; // TODO: modification
}
The only thing I can think of is that somehow data binding is preventing the Id property from being serialized because it was populated after it was deserialized from the form data. Any suggestions?
What does the regular Json() method return for this object?
According to this post... there might be an issue with the automatic backing fields in C#:
http://aaron-powell.spaces.live.com/blog/cns!91A824220E2BF369!150.entry
Is there a way to control the JSON output of JsonResult with attributes, similar to how you can use XmlElementAttribute and its bretheren to control the output of XML serialization?
For example, given the following class:
public class Foo
{
[SomeJsonSerializationAttribute("bar")]
public String Bar { get; set; }
[SomeJsonSerializationAttribute("oygevalt")]
public String Oygevalt { get; set; }
}
I'd like to then get the following output:
{ bar: '', oygevalt: '' }
As opposed to:
{ Bar: '', Oygevalt: '' }
I wanted something a bit more baked into the framework than what Jarrett suggested, so here's what I did:
JsonDataContractActionResult:
public class JsonDataContractActionResult : ActionResult
{
public JsonDataContractActionResult(Object data)
{
this.Data = data;
}
public Object Data { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
var serializer = new DataContractJsonSerializer(this.Data.GetType());
String output = String.Empty;
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, this.Data);
output = Encoding.Default.GetString(ms.ToArray());
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.Write(output);
}
}
JsonContract() method, added to my base controller class:
public ActionResult JsonContract(Object data)
{
return new JsonDataContractActionResult(data);
}
Sample Usage:
public ActionResult Update(String id, [Bind(Exclude="Id")] Advertiser advertiser)
{
Int32 advertiserId;
if (Int32.TryParse(id, out advertiserId))
{
// update
}
else
{
// insert
}
return JsonContract(advertiser);
}
Note: If you're looking for something more performant than JsonDataContractSerializer, you can do the same thing using JSON.NET instead. While JSON.NET doesn't appear to utilize DataMemberAttribute, it does have its own JsonPropertyAttribute which can be used to accomplish the same thing.
Here's my implementation of Daniel Schaffer's answer, with the suggested improvements by Justin Rusbatch and Daniel incorporated.
using System;
using System.Runtime.Serialization.Json;
using System.Web.Mvc;
public class JsonDataContractActionResult : JsonResult
{
public JsonDataContractActionResult( Object data )
{
this.Data = data;
}
public override void ExecuteResult( ControllerContext context )
{
var serializer = new DataContractJsonSerializer( this.Data.GetType() );
context.HttpContext.Response.ContentType = "application/json";
serializer.WriteObject( context.HttpContext.Response.OutputStream,
this.Data );
}
}
This is the solution to use NewtonSoft Json.Net (for performance)
I've found part of the solution here and on SO
public class JsonNetResult : ActionResult
{
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonSerializerSettings SerializerSettings { get; set; }
public Formatting Formatting { get; set; }
public JsonNetResult(object data, Formatting formatting)
: this(data)
{
Formatting = formatting;
}
public JsonNetResult(object data):this()
{
Data = data;
}
public JsonNetResult()
{
Formatting = Formatting.None;
SerializerSettings = new JsonSerializerSettings();
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType)
? ContentType
: "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
if (Data == null) return;
var writer = new JsonTextWriter(response.Output) { Formatting = Formatting };
var serializer = JsonSerializer.Create(SerializerSettings);
serializer.Serialize(writer, Data);
writer.Flush();
}
}
So that in my controller, I can do that
return new JsonNetResult(result);
In my model, I can now have:
[JsonProperty(PropertyName = "n")]
public string Name { get; set; }
Note that now, you have to set the JsonPropertyAttribute to every property you want to serialize.
I know this is an old question but for those looking for just how to avoid properties from being serialized use the ScriptIgnoreAttribute in the namespace System.Web.Script.Serialization. Sadly still can't controll the name of the serialized properties but somebody might find this helpfull.
public class MyClass {
[ScriptIgnoreAttribute]
public bool PropertyNotSerialized { get; set; }
public bool AnyProperty { get; set; }
}
Will output as Json result the following:
{"AnyProperty ": false}
Easy answer: the DataContractJsonSerializer should respect the [DataContract] and [DataMember] attributes in the System.Runtime.Serialization namespace of the BCL.
These answers were helpful to me, but coming to this problem a few years later than everyone else I found that this code didn't work with the current framework version. This version works with Newtonsoft.Json and ASP NET Core 3.1:
/*
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
*/
public class JsonDataContractActionResult : IActionResult
{
public JsonDataContractActionResult(object data)
{
this.Data = data;
}
public object Data { get; private set; }
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.ContentType = "application/json";
JsonSerializer serializer = new JsonSerializer();
serializer.NullValueHandling = NullValueHandling.Ignore;
using (MemoryStream ms = new MemoryStream()) {
using (StreamWriter sw = new StreamWriter(ms))
{
using (JsonWriter writer = new JsonTextWriter(sw))
{
serializer.Serialize(writer, Data);
}
}
byte[] b = ms.ToArray();
await context.HttpContext.Response.Body.WriteAsync(b);
}
}
}