Asp.Net Core: How to json serialize and deserialize IFormCollection? - asp.net

In a controller I serialized form data to json and saved to database:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(IFormCollection formData)
{
var json = JsonConvert.SerializeObject(formData);
var doc = new Doc()
{
Subject = formData["subject"],
Content = json
};
_context.Docs.Add(doc);
_context.SaveChanges();
return RedirectToAction("Edit", new { Id = doc.Id });
}
Now, I'd like to deserialize form data and reconstruct the form:
public IActionResult Edit(int id)
{
var doc = _context.Docs
.Where(o => o.Id == id).FirstOrDefault();
if (doc == null)
{
ViewData["ErrorMessage"] = "Not found";
return View("Error");
}
var formData = JsonConvert.DeserializeObject<IFormCollection>(doc.Content);
ViewData["FormData"] = formData;
return View(doc);
}
The above will throw an exception at deserialization:
JsonSerializationException: Cannot create and populate list type Microsoft.AspNetCore.Http.IFormCollection. Path '', line 1, position 1.
If I do not specify type, then deserialization succeeds; but I prefer it
to be deserialized to IFormCollection. What is the proper way to deserialize IFormCollection?
Also, the reason I'm saving json is because, I'm dealing with 30 or so types of forms, and I do not want to create strongly typed model objects for each of them. Any advice is welcome.

The way I used to deal with json object in Database:
In your entity object add an other class to handle mapping with your json object :
public class Contact
{
public int Id { get; set; }
internal string _Data { get; set; }
[NotMapped]
public UserData Data
{
get { return _Data == null ? null : JsonConvert.DeserializeObject<UserData>(_Data); }
set { _Data = JsonConvert.SerializeObject(value); }
}
}
public class UserData
{
public string Name { get; set; }
//Add your json data here
}
And my controller look like
public IActionResult Add(AddContactViewModel model)
{
var contact = new Contact()
{
Data = JsonConvert.DeserializeObject<UserData>(model.Data.ToString())
};
_contactService.Add(contact);
return new OkObjectResult(contact);
}
public class AddContactViewModel
{
public JObject Data { get; set; }
}
Thank's to mapping, when you access the object data contained by the object contact you can access all your data préviously defined in the UserData object (Object names can change in your case)
var contact = new Contact()
{
Data = JsonConvert.DeserializeObject<UserData>(model.Data.ToString())
};
contact.Data.Name;

Related

System.Text.Json Deserialize Fails

With this DTO:
public class QuestionDTO {
public Guid Id { get; set; }
public string Prompt { get; set; }
public List<Answer> Choices { get; set; }
public QuestionDTO() {
}
public QuestionDTO(Question question) {
this.Id = question.Id;
this.Prompt = question.Prompt;
this.Choices = question.Choices;
}
}
I was getting an error about Unable to Parse without a parameterless constructor. I have since fixed that, but now my objects are de-serialized empty:
using System.Text.Json;
var results = JsonSerializer.Deserialize<List<QuestionDTO>>(jsonString);
The jsonString contains 3 items with the correct data, and the deserialized list contains 3 items, but all the properties are empty.
The new json library is case sensitive by default. You can change this by providing a settings option. Here is a sample:
private JsonSerializerOptions _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
private async Task SampleRequest()
{
var result = await HttpClient.GetStreamAsync(QueryHelpers.AddQueryString(queryString, queryParams));
_expenses = await JsonSerializer.DeserializeAsync<List<Common.Dtos.Expenses.Models.Querys.ExpensesItem>>(result, _options);
}

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

A circular reference was detected while serializing entities with one to many relationship

How to solve one to many relational issue in asp.net?
I have Topic which contain many playlists.
My code:
public class Topic
{
public int Id { get; set; }
public String Name { get; set; }
public String Image { get; set; }
---> public virtual List<Playlist> Playlist { get; set; }
}
and
public class Playlist
{
public int Id { get; set; }
public String Title { get; set; }
public int TopicId { get; set; }
---> public virtual Topic Topic { get; set; }
}
My controller function
[Route("data/binding/search")]
public JsonResult Search()
{
var search = Request["term"];
var result= from m in _context.Topics where m.Name.Contains(search) select m;
return Json(result, JsonRequestBehavior.AllowGet);
}
When I debug my code I will see an infinite data because Topics will call playlist then playlist will call Topics , again the last called Topic will recall playlist and etc ... !
In general when I just use this relation to print my data in view I got no error and ASP.NET MVC 5 handle the problem .
The problem happens when I tried to print the data as Json I got
Is there any way to prevent an infinite data loop in JSON? I only need the first time of data without call of reference again and again
You are getting the error because your entity classes has circular property references.
To resolve the issue, you should do a projection in your LINQ query to get only the data needed (Topic entity data).
Here is how you project it to an anonymous object with Id, Name and Image properties.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you have a view model to represent the Topic entity data, you can use that in the projection part instead of the anonymous object
public class TopicVm
{
public int Id { set;get;}
public string Name { set;get;}
public string Image { set;get;}
}
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new TopicVm
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you want to include the Playlist property data as well, you can do that in your projection part.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image,
Playlist = x.Playlist
.Select(p=>new
{
Id = p.Id,
Title = p.Title
})
});
return Json(result, JsonRequestBehavior.AllowGet);
}

Error loading images from database

I have a question about showing images loaded from a mysql database in an Index view.
In my database table "deliverables" I have "item_id", "deliverable_image" and "afstudeerrichting_id". "item_id" and "afstudeerrichting_id" are FK from other tables.
I want to show the images when afstudeerrichting_id = ..
Controller:
public ActionResult Index()
{
var model = repository.GetIdsOfImages(1);
return View(model.ToList());
}
public ActionResult ShowImage(int id)
{
IQueryable<byte[]> data = repository.GetImages(id);
byte[] firstimage = data.First();
return File(firstimage, "image/png");
}
Repository:
public IQueryable<long> GetIdsOfImages(int afstudeerrichtingid)
{
return from deliverable in entities.deliverables
where deliverable.afstudeerichting_id.Equals(afstudeerrichtingid)
select deliverable.item_id;
}
public IQueryable<byte[]> GetImages(int itemID)
{
return from deliverable in entities.deliverables
where deliverable.item_id.Equals(itemID)
select deliverable.deliverable_image;
}
View:
#foreach(var imgID in Model.DeliverablesIDsList)
{
<img src="#Url.Action("ShowImage", "Deliverable", new { DeliverableID = imgID })" />
}
In my Viewmodel I have:
public List<long> DeliverablesIDsList { get; set; }
public int DeliverableID { get; set; }
But now I always get this error:
he model item passed into the dictionary is of type 'System.Collections.Generic.List`1[System.Int64]', but this dictionary requires a model item of type 'GDMfrontEnd.Models.DeliverableViewModel'.
Does someone knows what I'm doing wrong?
you're sending to the view a list of int64 repository.GetIdsOfImages(1).ToList() and the view requires a DeliverableViewModel, so you must create a model and put the list into the model and send it to the view
the action should looks like:
public ActionResult Index()
{
var model = repository.GetIdsOfImages(1);
DeliverableViewModel model = new DeliverableViewModel()
model.DeliverablesIDsList = repository.GetIdsOfImages(1).ToList();
return View(model); //send to the view a model type of DeliverableViewModel
}
now with ActionResult ShowImage, the action expect id parmeter and you're sending DeliverableID, so change de var name
public ActionResult ShowImage(int DeliverableID)
{
IQueryable<byte[]> data = repository.GetImages(DeliverableID);
byte[] firstimage = data.First();
return File(firstimage, "image/png");
}

How to post JSON data to SQL using ajax post & knockout

I have a pretty straightforward view model:
var ProjectViewModel = {
ProjectName: ko.observable().extend({ required: "" }),
ProjectDescription: ko.observable().extend({ required: "" }),
ProjectStartDate: ko.observable(),
ProjectEndDate: ko.observable()
};
I want to save this data that is located in my viewmodel to my SQL server.
I have a class defining this View Model in my Server Side Code:
public class Projects
{
public string ProjectName { get; set; }
public DateTime ProjectStartDate { get; set; }
public DateTime ProjectEndDate { get; set; }
public string ProjectDescription { get; set; }
}
I also have this web method to receive the code:
[WebMethod]
public bool SaveProject(string[] JSONDATA)
{
TaskNinjaEntities entities = new TaskNinjaEntities();
foreach (var item in JSONDATA)
{
Console.WriteLine("{0}", item);
}
return true;
}
And finally I have this POST that does not want to send the data to the server:
function SaveMe() {
var data = ko.toJSON(ProjectViewModel);
$.post("CreateProject.aspx/SaveProject", data, function (returnedData) {
});
}
I get nothing from the returned data in this post method, also added breakpoint in server side code, and it doesn't hit it at all. My URL is correct and the Viewmodel converts to JSON without hassle.
Make the web method static.
[WebMethod]
public static bool SaveProject(string[] JSONDATA)
{
TaskNinjaEntities entities = new TaskNinjaEntities();
foreach (var item in JSONDATA)
{
Console.WriteLine("{0}", item);
}
return true;
}

Resources