I am getting an error while executing an application using MVC4, Web API, AngularJS . The error is following:
Self referencing loop detected with type
'System.Data.Entity.DynamicProxies.Product_259FEB40BD6111F44AA3C3CED8DD40E7E44B22CC11A32AE621E84E2239F79B2C'. Path '[0].category.products'.
My products.cs file under model folder is:
public partial class Product
{
[JsonIgnore]
[Key]
public int ProductID { get; set; }
public string ProductName { get; set; }
public Nullable<int> SupplierID { get; set; }
public Nullable<int> CategoryID { get; set; }
public string QuantityPerUnit { get; set; }
public Nullable<decimal> UnitPrice { get; set; }
public Nullable<short> UnitsInStock { get; set; }
public Nullable<short> UnitsOnOrder { get; set; }
public Nullable<int> ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public virtual Category Category { get; set; }
public virtual Supplier Supplier { get; set; }
}
Product controller is:
public class ProductController : JsonController
{
private readonly DBEntities _db = new DBEntities();
public ActionResult GetProduct()
{
var productList = _db.Products;
return Json(productList, JsonRequestBehavior.AllowGet);
}
}
Class that returns JSON and included in product controller is:
public class JsonController : Controller
{
public new ActionResult Json(object data, JsonRequestBehavior behavior)
{
var jsonSerializerSetting = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
if (Request.RequestType == WebRequestMethods.Http.Get && behavior == JsonRequestBehavior.DenyGet)
{
throw new InvalidOperationException("GET is not permitted for this request.");
}
var jsonResult = new ContentResult
{
Content = JsonConvert.SerializeObject(data, jsonSerializerSetting),
ContentType = "application/json"
};
return jsonResult;
}
}
The productController.js file which is using AngularJS is:
myApp.controller('productController',
['$scope', 'productDataService', '$location',
function productController($scope, productDataService) {
$scope.products = [];
$scope.currentPage = 1;
$scope.pageSize = 10;
loadProductData();
function loadProductData() {
productDataService.getProducts()
.then(function () {
$scope.products = productDataService.products;
},
function () {
//Error goes here...
})
.then(function () {
$scope.isBusy = false;
});
$scope.pageChangeHandler = function (num) {
console.log('Product page changed to ' + num);
}
};
}
]);
Data service is like:
myApp.factory('productDataService', ['$http', '$q',
function ($http, $q) {
var _products = [];
var _getProducts = function () {
var deferred = $q.defer();
var controllerQuery = "product/GetProduct";
$http.get(controllerQuery)
.then(function (result) {
// Successful
angular.copy(result.data, _products);
deferred.resolve();
},
function (error) {
// Error
deferred.reject();
});
return deferred.promise;
};
return {
products: _products,
getProducts: _getProducts
};
}
]);
Same type of code is working well for another application but I not clear why the above mentioned code is not working.
Any help will be thankfully accepted.
Thanks
Partha
You can do this:
return new ContentResult
{
Content = JsonConvert.SerializeObject(data, Formatting.None, new JsonSerializerSettings {ReferenceLoopHandling = ReferenceLoopHandling.Ignore}),
ContentType = "application/json"
};
Open WebApiConfig and register following JsonFormatter
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Yeah this is probably due to json trying to serializer your navigation objects.
public virtual Category Category { get; set; }
public virtual Supplier Supplier { get; set; }
1.Do quest test to rule this out.
2.Code up a ignore rule for them or create a DTO object for Product,
Related
I have a Web API for OData services. I have a lot of table with many relations. Here is some of the table:
MSADDRESSCOUNTRY
public partial class MSADDRESSCOUNTRY
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSCOUNTRY()
{
this.MSADDRESSPROVINCEs = new HashSet<MSADDRESSPROVINCE>();
}
public int ID { get; set; }
public string CODE { get; set; }
public string COUNTRYNAME { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSPROVINCE> MSADDRESSPROVINCEs { get; set; }
}
MSADDRESSPROVINCE
public partial class MSADDRESSPROVINCE
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSPROVINCE()
{
this.MSADDRESSDISTRICTs = new HashSet<MSADDRESSDISTRICT>();
}
public int ID { get; set; }
public Nullable<int> COUNTRYID { get; set; }
public string PROVINCENAME { get; set; }
public virtual MSADDRESSCOUNTRY MSADDRESSCOUNTRY { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSDISTRICT> MSADDRESSDISTRICTs { get; set; }
}
MSADDRESSDISTRICT
public partial class MSADDRESSDISTRICT
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MSADDRESSDISTRICT()
{
this.MSADDRESSSUBDISTRICTs = new HashSet<MSADDRESSSUBDISTRICT>();
}
public int ID { get; set; }
public Nullable<int> PROVINCEID { get; set; }
public string DISTRICTNAME { get; set; }
public virtual MSADDRESSPROVINCE MSADDRESSPROVINCE { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MSADDRESSSUBDISTRICT> MSADDRESSSUBDISTRICTs { get; set; }
}
I create DTO object model for every table with the property is the same with Database object model.
I want the client can use $expand keyword to get child data and/or parent data.
For MSADDRESSCOUNTRY I need to write the code like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressCountryObject> Get()
{
return db.MSADDRESSCOUNTRies.Select(c => new MsAddressCountryObject
{
ID = c.ID,
CODE = c.CODE,
COUNTRYNAME = c.COUNTRYNAME,
MSADDRESSPROVINCEs = c.MSADDRESSPROVINCEs.Select(data => new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = new MsAddressCountryObject()
{
ID = data.MSADDRESSCOUNTRY.ID,
CODE = data.MSADDRESSCOUNTRY.CODE,
COUNTRYNAME = data.MSADDRESSCOUNTRY.COUNTRYNAME,
},
MSADDRESSDISTRICTs = data.MSADDRESSDISTRICTs.Select(dist => new MsAddressDistrictObject()
{
ID = dist.ID,
PROVINCEID = dist.PROVINCEID,
DISTRICTNAME = dist.DISTRICTNAME,
})
})
});
}
For MSADDRESSPROVINCE I need to write the code like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressProvinceObject> Get()
{
return db.MSADDRESSPROVINCEs.Select(data => new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = new MsAddressCountryObject()
{
ID = data.MSADDRESSCOUNTRY.ID,
CODE = data.MSADDRESSCOUNTRY.CODE,
COUNTRYNAME = data.MSADDRESSCOUNTRY.COUNTRYNAME,
},
MSADDRESSDISTRICTs = data.MSADDRESSDISTRICTs.Select(dist => new MsAddressDistrictObject()
{
ID = dist.ID,
PROVINCEID = dist.PROVINCEID,
DISTRICTNAME = dist.DISTRICTNAME
})
});
}
That code works fast. But if I add/change/remove column, I have to modify the controller manually, one by one for all controller. For example, if I want to add geological coordinate in MSADDRESSDISTRICT, I have to change the code in Country Controller, Province Controller and District Controller.
So I decide to create extension method like this.
public static MsAddressCountryObject ToDTO(this MSADDRESSCOUNTRY data)
{
return new MsAddressCountryObject()
{
ID = data.ID,
CODE = data.CODE,
COUNTRYNAME = data.COUNTRYNAME,
};
}
public static IQueryable<MsAddressCountryObject ToDTO(this IEnumerable<MSADDRESSCOUNTRY datas)
{
return datas.Select(country =
{
var obj = country?.ToDTO();
obj.MSADDRESSPROVINCEs = country.MSADDRESSPROVINCEs?.ToDTO();
return obj;
}).AsQueryable();
}
public static MsAddressProvinceObject ToDTO(this MSADDRESSPROVINCE data)
{
return new MsAddressProvinceObject()
{
ID = data.ID,
COUNTRYID = data.COUNTRYID,
PROVINCENAME = data.PROVINCENAME,
MSADDRESSCOUNTRY = data.MSADDRESSCOUNTRY?.ToDTO()
};
}
public static IQueryable<MsAddressProvinceObject ToDTO(this IEnumerable<MSADDRESSPROVINCE datas)
{
return datas.Select(province =
{
var obj = province?.ToDTO();
obj.MSADDRESSDISTRICTs = province.MSADDRESSDISTRICTs.ToDTO();
return obj;
}).AsQueryable();
}
public static MsAddressDistrictObject ToDTO(this MSADDRESSDISTRICT data)
{
return new MsAddressDistrictObject()
{
ID = data.ID,
PROVINCEID = data.PROVINCEID,
DISTRICTNAME = data.DISTRICTNAME,
MSADDRESSPROVINCE = data.MSADDRESSPROVINCE?.ToDTO()
};
}
public static IQueryable<MsAddressDistrictObject ToDTO(this IEnumerable<MSADDRESSDISTRICT datas)
{
return datas.Select(district =
{
var obj = district?.ToDTO();
obj.MSADDRESSSUBDISTRICTs = district.MSADDRESSSUBDISTRICTs?.ToDTO();
return obj;
}).AsQueryable();
}
And the controller just like this.
[EnableQuery(MaxExpansionDepth = 4)]
public IQueryable<MsAddressCountryObject Get()
{
return db.MSADDRESSCOUNTRies.ToDTO()
}
And that makes the performance really bad. I think the extension is making a lot of memory allocation or some thing that make the result not being delivered directly to the client.
My goal is to create the code easy to maintain, and the performance not drop significantly.
I have many relation in other table. I want the $expand works without write all parent/child Select statement manually and one by one.
I have try to not calling ToDTO() from all the extension method. The result is the performance is fast. But I lost all the relation or I need to write the parent/child Select statement for all method.
Any suggestion will help.
Thanks.
I'm trying to pass data to an action inside the same controller. I'm using RedirectToAction, but I'm not succeeding.
The action is called but the data I'm trying to pass to it has its values null.
My Model:
public class PlaylistModel
{
public Guid PlayListID { get; set; }
[Display(Name = "Nome da Playlist")]
public string NomePlayList { get; set; }
[Display(Name = "Descrição")]
public string Descricao { get; set; }
[Display(Name = "Código")]
public string CodigoPlayList { get; set; }
public string Estado { get; set; }
public string EmailUser { get; set; }
public List<VideoThumbnails> VideosIdsYoutube { get; set; }
[Display(Name = "Categorias")]
public List<string> Categorias { get; set; }
}
This action receives the data, does the processing and calls the other action
[HttpPost]
public ActionResult CreatePlaylist(string Categorias, string NomePlayList)
{
PlaylistModel playListModel = new PlaylistModel();
playListModel.VideosIdsYoutube = AppCache.Instance.VideoParaAPI.VideoThumbnails.ToList();
playListModel.Categorias = Categorias.Split(',').ToList();
playListModel.NomePlayList = NomePlayList;
playListModel.Estado = EnumEstadoDaPlayList.Nova.ToString();
playListModel.EmailUser = AppUser.User.Email;
var api = new AccessAPI.Playlist.AccessAPIPlaylist();
var playlist = api.CriarNova(playListModel);
if(playlist != null)
{
return RedirectToAction("ExibirPlaylist", new RouteValueDictionary(new { controller = "Playlist", action = "ExibirPlaylist", pPlayList = playlist }));
}
else
{
return Problem("Não foi possível criar a playlist!");
}
}
This other action is called by the previous one, but the model arrives with null values
[HttpGet]
public ActionResult ExibirPlaylist(PlaylistModel pPlayList)
{
var apiVideo = new AccessAPI.Video.AccessAPIVideo();
var videos = apiVideo.ConsultarPorPlayListId(pPlayList.PlayListID).ToList();
if(videos.Count > 0)
{
videos.ForEach(v =>
{
pPlayList.VideosIdsYoutube.Add(new Dommain.Cache.VideoThumbnails() { CanalID = v.CanalID, DataDePublicacao = v.DataDePublicacao, NomeDoCanal = v.NomeDoCanal, NomeDoVideo = v.NomeDoVideo, Thumbnail = v.Thumbnail, VideoId = v.VideoIdYoutube});
});
}
return View();
}
The RouteValueDictionary in RedirectToAction() pass only simple types, like string, int etc. The RedirectToAction() doesn't serialize the complex data types. Therefore, may be the easiest way to use the TempData.
In the CreatePlaylist(string Categorias, string NomePlayList):
TempData["playListModel"] = playlist;
return RedirectToAction("ExibirPlaylist");
And then:
[HttpGet]
public ActionResult ExibirPlaylist()
{
if (TempData["playListModel"] is PlaylistModel pPlayList)
{
// Your code using `pPlayList`
}
return View("Index");
}
The MVC uses the binary serialization, by default. And in some cases the binary serialization can fail: Storing objects (non-string things) in TempDataDictionary does not round trip correctly. Therefore, it is preferably to serialize the specified object to a JSON string by JsonConvert.SerializeObject().
I have json format as below. In that I have multiple records coming, check below json.
{
"metadata":{
"TotalCount":11,
"CurrentPageNumber":1,
},
"records":[
{
"offerId":"e1b75d86-67b1-4557-a381-5474383da3fb",
"isContract":true,
"transportRouteId":"e1b75d86-67b1-4557-a381-5474383da3fb",
"transportOrderId":"SEZYMY-210720-010097",
"schedule":null,
},
]
}
Now I want to add new value in each this records array, so how can I do that.
I want to add this value : ("carrierExpiredDate", (carrierExpiredDate.ComplianceExpiryDate.Value).Date);
So this new json should look like this.
{
"metadata":{
"TotalCount":11,
"CurrentPageNumber":1,
},
"records":[
{
"offerId":"e1b75d86-67b1-4557-a381-5474383da3fb",
"isContract":true,
"transportRouteId":"e1b75d86-67b1-4557-a381-5474383da3fb",
"transportOrderId":"SEZYMY-210720-010097",
"schedule":null,
"carrierExpiredDate": (carrierExpiredDate.ComplianceExpiryDate.Value).Date)
},
]
}
var json =
#"{
"metadata":{
"TotalCount":11,
"CurrentPageNumber":1,
},
"records":[
{
"offerId":"e1b75d86-67b1-4557-a381-5474383da3fb",
"isContract":true,
"transportRouteId":"e1b75d86-67b1-4557-a381-5474383da3fb",
"transportOrderId":"SEZYMY-210720-010097",
"schedule":null
},
]
}";
var jObject = JObject.Parse(json);
var jList=jObject["records"].Children().ToList();
foreach(var jtoken in jList)
{
jtoken["carrierExpiredDat"] = (carrierExpiredDate.ComplianceExpiryDate.Value).Date));
}
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jObject, Newtonsoft.Json.Formatting.Indented);
Try this. The code was tested using Visual Studio and Postman
.....
var expDate=DateTime.Now;
var json = await response.Content.ReadAsStringAsync()
var deserializedJson = JsonConvert.DeserializeObject<TransportRoot>(json);
deserializedJson.Records.ForEach(i=> i.carrierExpiredDate=expDate);
var newJson= JsonConvert.SerializeObject(deserializedJson);
classes
public class Metadata
{
public int TotalCount { get; set; }
public int CurrentPageNumber { get; set; }
}
public class Record
{
public string offerId { get; set; }
public bool isContract { get; set; }
public string transportRouteId { get; set; }
public string transportOrderId { get; set; }
public object schedule { get; set; },
public object carrierExpiredDate { get; set; }
}
public class TransportRoot
{
public Metadata Metadata { get; set; }
public List<Record> Records { get; set; }
}
if you have a problem with a camel case json, I highly recommend to change your startup
services.AddControllers()
// or services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver());
I'm trying to return back JSON with customization , anyone can help to return the result like this :
{
status: 200,
message: "success",
data: {
var1: {
},
var2: {
}
}
}
with this code :
return Ok(new
{
var1,
var2
});
Why do you need to use the OkResult object?
A simple way of returning what you'd like is to use dynamic objets, or a class with properties matching the Json you'd like to get, and a JsonResult object.
dynamic json = new ExpandoObject();
json.Result = 200;
json.Message = "success";
json.Data.var1 = "whatever";
json.Data.var2 = "something else";
Response.StatusCode = json.Result; //matches the HTTP Status Code with the one from your json
return new JsonResult(json);
I used this for taking profile information from google id token , and then generate JWT token from my backend server and retuen back JWT and Profile info for client apps so this is the solution :
var profile = (new RetrnedUserProfile
{
Email = payload.Email,
FirstName = payload.GivenName,
FamilyName = payload.FamilyName,
PictureUrl = payload.Picture
});
return Ok(new ResponseModel
{
StatusCode = HttpStatusCode.OK,
Message = "success",
Data = new
{
profile,
accessToken
}
});
public class RetrnedUserProfile
{
public string FirstName { get; set; }
public string FamilyName { get; set; }
public string Email { get; set; }
public string PictureUrl { get; set; }
}
public class ResponseModel
{
public HttpStatusCode StatusCode { get; set; }
public string Message { get; set; }
public object Data { get; set; }
}
This is my code in adding Fulfillment to Shopify orders but the converted json is not as expected.
Fullfillment product = new Fullfillment();
product.status = "success";
product.tracking_number = orderSent.TrackingNo;
List<LineItems> items = new List<LineItems>();
foreach (var item in orderSent.OrderLines)
{
LineItems line = new LineItems();
line.id = item.ProductName;
items.Add(line);
}
var json = JsonConvert.SerializeObject(product);
json = "{ \"fulfillment\": " + json + "}";
var json1 = JsonConvert.SerializeObject(items);
json = json + "{ \"line_items\": " + json1 + "}";
And this the converted json from this code:
{ "fulfillment": {
"id":0,
"status":"success",
"tracking_number":"xxxx12222",
}}{
"line_items": [
{
"id":"1234566645"
}
]
}
How can I turned like this:
{
"fulfillment": {
"tracking_number": null,
"line_items": [
{
"id": 466157049,
"quantity": 1
}
]
}
}
Model:
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class Fullfillment
{
[JsonProperty(PropertyName = "id")]
public long id { get; set; }
[JsonProperty(PropertyName = "status")]
public string status { get; set; }
[JsonProperty(PropertyName = "tracking_number")]
public string tracking_number { get; set; }
}
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class LineItems
{
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
}
These are the models for Fulfillment and Line Items.
Thank you in advance for giving advices and help.
This works for me:
var json = JsonConvert.SerializeObject(new
{
fullfillment = new
{
product.tracking_number,
line_items = items.Select(x => new
{
x.id,
quantity = 1
})
}
});
That gives me:
{
"fullfillment" : {
"tracking_number" : "xxxx12222",
"line_items" : [{
"id" : "1234566645",
"quantity" : 1
}
]
}
}
I started with this code to build up the JSON above:
Fullfillment product = new Fullfillment();
product.status = "success";
product.tracking_number = "xxxx12222";
List<LineItems> items = new List<LineItems>();
LineItems line = new LineItems();
line.id = "1234566645";
items.Add(line);
Obviously you need to fill in your specific data.
Change your classes like below.
public class Rootobject
{
public Fulfillment fulfillment { get; set; }
}
public class Fulfillment
{
public string tracking_number { get; set; }
public Line_Items[] line_items { get; set; }
}
public class Line_Items
{
public string id { get; set; }
public int quantity { get; set; }
}
public class JsonTest
{
public void Test()
{
var root = new Rootobject();
root.fulfillment = new Fulfillment();
root.fulfillment.tracking_number = "xxxx12222";
root.fulfillment.line_items = new List<Line_Items>() { new Line_Items() { id = "1234566645", quantity = 1 } }.ToArray();
var json = JsonConvert.SerializeObject(root);
Console.WriteLine(json);
}
}
This will give you this json.
{
"fulfillment": {
"tracking_number": "xxxx12222",
"line_items": [
{
"id": "1234566645",
"quantity": 1
}
]
}
}
Try the following
public class Rootobject
{
public Fulfillment fulfillment { get; set; }
}
public class Fulfillment
{
public string tracking_number { get; set; }
public Line_Items[] line_items { get; set; }
}
public class Line_Items
{
public string id { get; set; }
public int quantity { get; set; }
}
public class JsonTest
{
public void Test()
{
var root = new Rootobject();
root.fulfillment = new Fulfillment();
root.fulfillment.tracking_number = "xxxx12222";
root.fulfillment.line_items = new List<Line_Items>() { new Line_Items() { id = "1234566645", quantity = 1 } }.ToArray();
var json = JsonConvert.SerializeObject(root);
Console.WriteLine(json);
}
}