NotSupportedException for custom collection - c#

Problem:
I have an API endpoint which takes an IEmail input:
[HttpPost]
[Route("SendAsync")]
public async Task SendAsync(IEmail email)
{
await this._emailService.SendAsync(email);
}
The problem seems to be with the model binding of the Attachments property of this input. The model binder doesn't seem to be able to deserialise to my implementation. This is strange becuase the IEmail seems to be ok...
Exception:
System.NotSupportedException: The collection type 'IAttachmentCollection' on 'IEmail.Attachments' is not supported.
Setup:
See below for my contract and implementation for IEmail:
public interface IEmail
{
string To { get; set; }
string Subject { get; set; }
string Body { get; set; }
IAttachmentCollection Attachments { get; set; }
bool HasAttachments { get; }
}
public class EmailMessage : IEmail
{
public EmailMessage()
{
this.Attachments = new AttachmentCollection();
}
public string To { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public IAttachmentCollection Attachments { get; set; }
public bool HasAttachments => this.Attachments != null && this.Attachments.Any(x => x.Content.Length > 0);
}
I have created a custom collection so that I can add custom validation into the Add() routine of the collection:
public interface IAttachmentCollection : ICollection<IAttachment>
{
}
public class AttachmentCollection : IAttachmentCollection
{
private ICollection<IAttachment> _attachments;
public AttachmentCollection()
{
this._attachments = new HashSet<IAttachment>();
}
public void Add(IAttachment item)
{
// Do some custom validation on the item we are trying to add
this._attachments.Add(item);
}
// Other implemented methods for ICollection<T>...
}

For complex types, Models are suppose to be concrete non-abstract classes with a default constructor by default in order for the action to be able to bind. It is also unable to determine what class to use for the interface property Attachments.
Reference Model Binding in ASP.NET Core
Create a model that mirrors the desired object graph
public class EmailModel {
public EmailModel() {
this.Attachments = new List<AttachmentModel>();
}
public string To { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public List<AttachmentModel> Attachments { get; set; }
public bool HasAttachments => this.Attachments != null && this.Attachments.Any(x => x.Content.Length > 0);
}
public class AttachmentModel {
//...members
}
Explicitly use that for the action
[HttpPost]
[Route("SendAsync")]
public async Task<IActionResult> SendAsync([FromBody]EmailModel model) {
if(ModelState.IsValid) {
IEmain email = new EmailMessage();
//...code here to map model to the desired type
await this._emailService.SendAsync(email);
return Ok();
}
return BadRequest(ModelState);
}
Making sure to map the posted data to the desired type

Related

Class has no parameterless constructor exception in controller

I'm trying to do the Entity Framework Code First Approach. I made my models then the DbContext and added it to the controller. I followed an online tutorial as I've never used C# before.
However the tables don't create until I add a call to the db in the controller.
public ActionResult Index()
{
db.posts.ToList();
return View();
}
The call however throws.
InvalidOperationException: The class 'SocialMediaMining.Models.SocialMedia.Facebook.posts' has no parameterless constructor.
posts class:
public class posts
{
public dynamic jsonObj { get; set; }
public posts(dynamic json)
{
jsonObj = json;
if (jsonObj != null)
{
id = jsonObj.id;
name = jsonObj.name;
if(jsonObj.feed !=null)
{
feed = new feed(jsonObj.feed);
}
}
}
public string id { get; set; }
public string name { get; set; }
public virtual feed feed { get; set; }
public int postsId { get; set; }
}
The controller:
public class FacebookController : Controller
{
//The dbcontext call
FacebookEntities db = new FacebookEntities();
public ActionResult Index()
{
// the error
db.posts.ToList();
return View();
}
// more code here
}
//DbContext
public class FacebookEntities : DbContext
{
public FacebookEntities() : base("SocialMediaDb")
{
}
public DbSet<posts> posts { get; set; }
//more code here
}
Any help is appreciated
The exception message is pretty straightforward - you need to specify a parameterless constructor for posts class, which should look like this:
public class posts
{
// add this constructor
public posts()
{
}
public dynamic jsonObj { get; set; }
public posts(dynamic json)
{
jsonObj = json;
if (jsonObj != null)
{
id = jsonObj.id;
name = jsonObj.name;
if(jsonObj.feed !=null)
{
feed = new feed(jsonObj.feed);
}
}
}
public string id { get; set; }
public string name { get; set; }
public virtual feed feed { get; set; }
public int postsId { get; set; }
}
Note that any entity class which want to be included in DbSet<T> type parameter must have parameterless constructor to enable binding with EF. Also it is recommended to use PascalCase for entity class names and property names, e.g. Posts.

Return complex object from .Net core API

I'm using a .Net core backend with Entity Framework to retrieve objects to my angular2 frontend.
As long as I return flat objects from the backend I have not problems. But when I use an Linq Include I get an net::ERR_CONNECTION_RESET error. The error seems to occur as soon as the call returns from backend, but it never enters the result handler.
How can I return a complex object with lists inside the objects?
DocumentsController.cs:
[Produces("application/json")]
[Route("api/[controller]")]
public class DocumentsController : Controller
{
private readonly DatabaseContext _context;
public DocumentsController(DatabaseContext context)
{
_context = context;
}
// GET: api/Documents
[HttpGet]
public IEnumerable<Document> GetDocuments()
{
var documents = _context.Documents
.Include(x => x.Pages);
return documents;
}
}
Document.cs:
public class Document : IEntity
{
public int Id { get; set; }
public string Title { get; set; }
public string AlternativeTitle { get; set; }
public ICollection<Page> Pages { get; set; }
}
Page.cs:
public class Page : IEntity
{
public Document Document { get; set; }
public int DocumentId { get; set; }
public int Id { get; set; }
public string OriginalText { get; set; }
public int Sorting { get; set; }
}
documents.components.ts:
import { Component, Inject } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'documents',
templateUrl: './documents.component.html'
})
export class DocumentsComponent {
public documents: Document[];
constructor(http: Http, #Inject('BASE_URL') baseUrl: string) {
http.get(baseUrl + 'api/Documents').subscribe(result => {
this.documents = result.json() as Document[];
}, error => console.error(error));
}
}
interface Document {
title: string;
alternativeTitle: string;
pageCount: number;
pages: Array<Page>;
}
interface Page {
originalText: string;
sorting: number;
}
The connection reset error is due to your Action method throwing an unhandled exception, returning a 500 to the client. This happens because of the infinite loop Document => Page => Document when the serializer kicks in.
An easy way to solve this problem without breaking the relationship (you might need them even when using DTOs) is to change the default serialization process:
public IActionResult GetDocuments()
{
var documents = ...;
var options = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
return Json(documents, options);
}
With that said, you should always return either IActionResult or async Task<IActionResult> instead of the type directly (like IEnumerable<Document>).
Also, if you find yourself having to use the above code continuously, you might want to change the default options at the application level in Startup
After a while a thought struck me; it could be a problem with a never ending loop of Documents with Pages with nested Document with Pages and so on.
I created a DTO (data transfer object) that doesn't include the back references.
public class DocumentDto
{
public int Id { get; set; }
public string Title { get; set; }
public string AlternativeTitle { get; set; }
public ICollection<PageDto> Pages { get; set; }
public string OriginalLanguage { get; set; }
public string TranslationLanguage { get; set; }
}
public class PageDto
{
public int Id { get; set; }
public string OriginalText { get; set; }
public int Sorting { get; set; }
public string Translation { get; set; }
}
Worked like a charm.

Retrieving generic data without knowing the List<Type> to be returned, is that possible?

I dont know if the question is correct but what I need to do is to received the correct entity from the messagetoprocess repository method with the corresponding data, how can I do that in this scenario? (code below)
Im using AutoMapper.
I know that I can create a MessageEntity and eliminate the Interface and put all properties together in it but that is exactly what Im trying not to do.
Here is what I've got:
Interface:
public interface IMessage
{
string MessageFrom { get; set; }
string MessageTo { get; set; }
{
Implementer Entities
public class EmailMessageEntity : IMessage
{
public bool IsMessageBodyHtml { get; set; }
}
public class SmsMessageEntity : IMessage
{
public bool IsMmsMessage { get; set; }
}
Models:
public class EmailMessage
{
public string MessageFrom { get; set; }
public string MessageTo { get; set; }
public bool IsMessageBodyHtml { get; set; }
}
public class SMSMessage
{
public string MessageFrom { get; set; }
public string MessageTo { get; set; }
public bool IsMmsMessage { get; set; }
}
Repositry:
public static List<*****Entity problem*****> RetrieveMessageToProcess()
{
var commandSettings = new CommandSettings
{
CommandText = #"[Schema].[RetrieveMessageToProcess]",
CommandType = CommandType.StoredProcedure
};
return new MsSqlProviderBase(DbConnectionString, commandSettings).ExecuteQuery<*****Entity problem*****>();
}
Using it:
//code excerpt
var messagesToProcess = Db.RetrieveMessageToProcess(); //repository
if (messagesToProcess == null) return;
// Process Message(s)
foreach (var messageEntity in messagesToProcess)
{
if (Email) // this is just the verification example not the actual statement and not a variable
{
Mapper.CreateMap<EmailMessageEntity, EmailMessage>();
var emailMessage = Mapper.Map<EmailMessage>(messageEntity);
}
else if (SMS)
{
Mapper.CreateMap<SMSMessageEntity, SMSMessage>();
var smsMessage = Mapper.Map<SmsMessage>(messageEntity);
}
}
Could you consider having a MessageType member on your IMessage interface instead of the two IsXXX properties ?
You could return an enum, or whatever other value you deem appropriate, and use that in your if(EMAIL) statement.

Using a model with a generic type constraint

I'm trying myself at a model that uses a generic type that I constrained with a base class.
I get the following error when calling my view:
The model item passed into the dictionary is of type 'MVCTest.Models.TestModel`1[MVCTest.Models.SpecialColumn]', but this dictionary requires a model item of type 'MVCTest.Models.TestModel`1[MVCTest.Models.ColumnBase]'.
Here is an example code to demonstrate the issue:
public class ColumnBase
{
public decimal Id { get; set; }
public string Text { get; set; }
public bool Enabled { get; set; }
public ColumnBase()
{
Enabled = true;
}
public string GetFormattedText(string symbol)
{
return string.Format(Text, symbol);
}
}
public class SpecialColumn : ColumnBase
{
public string Baz { get; set; }
}
public class TestModel<TColumn>
where TColumn : ColumnBase
{
Dictionary<string, TColumn> Columns { get; set; }
}
The Controller:
public ActionResult Test ()
{
var testModel = new TestModel<SpecialColumn>();
return View("", testModel);
}
In the view I tried to use ColumnBase as this should be a shared view that just needs the properties provided by ColumnBase:
#model TestModel<ColumnBase>
What is the correct way to implement something like this?

DefaultModelBinder behaviour when property absent from request

I have a model like the following:
public class TestViewModel
{
string UpdateProperty { get; set; }
string IgnoreProperty { get; set; }
ComplexType ComplexProperty { get; set; }
}
where
public class ComplexType
{
long? Code { get; set; }
string Name { get; set; }
}
My controller action:
public Edit(int id, FormColleciton formCollection)
{
var model = service.GetModel(id);
TryUpdateModel(model);
//...
}
When calling the Edit action I have a formCollection parameter containing only a key/value for UpdateProperty.
After the call to TryUpdateModel UpdateProperty is set correctly, IgnoreProperty is left un-touched but ComplexProperty is set to null, even if it previously had a value.
Should TryUpdateModel() only modify properties that are a part of the request? If this is not the case what is the best way to work around this so ComplexProperty is only modified if it is included in the request?
After it was pointed out by Darin that the test case above didn't demonstrate the problem I have added a scenario where this problem really occurs:
public class TestViewModel
{
public List<SubModel> List { get; set; }
}
public class SubModel
{
public ComplexType ComplexTypeOne { get; set; }
public string StringOne { get; set; }
}
public class ComplexType
{
public long? Code { get; set; }
public string Name { get; set; }
}
Controller Action:
public ActionResult Index()
{
var model = new TestViewModel
{
List = new List<SubModel> {
new SubModel{
ComplexTypeOne = new ComplexType{Code = 1, Name = "5"},
StringOne = "String One"
}
}
};
if (TryUpdateModel(model)) { }
return View(model);
}
Sending this request:
/Home/Index?List[0].StringOne=test
updates SubModel.StringOne property but sets ComplexTypeOne to null, even though it is not included in the request.
Is this expected behaviour (given this does not happen unless an enumerable of complex types is used)? How best to work around this?
There must be something wrong with your test case as I was unable to reproduce it. Here's what I tried:
Model (notice that I use public properties):
public class TestViewModel
{
public string UpdateProperty { get; set; }
public string IgnoreProperty { get; set; }
public ComplexType ComplexProperty { get; set; }
}
public class ComplexType
{
public long? Code { get; set; }
public string Name { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new TestViewModel
{
IgnoreProperty = "to be ignored",
UpdateProperty = "to be updated",
ComplexProperty = new ComplexType
{
Code = 1,
Name = "5"
}
};
if (TryUpdateModel(model))
{
}
return View();
}
}
Now I send the following request: /home/index?UpdateProperty=abc and inside the condition only the UpdateProperty is modified with the new value from the query string. All other properties, including the complex property, are left untouched.
Also notice that the FormCollection action parameter is useless.

Categories

Resources