Interface Model Binding - MVC 4 - c#

I have an object derived from interface. I want to use display template and editor template.Display template works very well. But editor template does not work very well.It does not understand it says "can not create instance of an interface".
I have a custom model binder. But it is really dummy.
protected override object CreateModel(ControllerContext controllerContext,ModelBindingContext bindingContext, Type modelType)
{
if (modelType.Equals(typeof(IExample)))
{
Type instantiationType = typeof(ExampleType1);
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
How can I do it for every class derived from IExample? Any ideas?
[HttpGet]
public ActionResult Index()
{
MyModel model = new MyModel();
model.inter = new ExampleType1();
model.inter.number = 50;
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
//*-*-* I want to get it here.
return View();
}
public class MyModel
{
public IExample inter { get; set; }
}
public interface IExample
{
int number { get; set; }
}
public class ExampleType1 : IExample
{
public int number { get; set; }
public string tip1 { get; set; }
}
public class ExampleType2 : IExample
{
public int number { get; set; }
public string tip2 { get; set; }
}

Without dwelling on reason why you need this ( i think it's a bad design, to have interface in as a parameter for controller methods). I think the simplest solution would be to extend the IExample interface with string property ImplementedType.
public interface IExample
{
string type {get;}
int number { get; set; }
}
Implementation:
public class ExampleType1 : IExample
{
public string type
{ get { return "ExampleType1"; } }
public int number { get; set; }
public string tip1 { get; set; }
}
And model binder:
var type = (string)bindingContext.ValueProvider.GetValue("type");
if (type == "ExampleType1")
{
//create new instance of exampletype1.
}

Related

JSON deserialization of polymorphic and complex objects in ASP.Net Core without Newtonsoft

Deserialization of polymorphic and complex objects in ASP.Net is a well know topic.
Common solutions I came across rely on JsonConverter or JsonSubTypes.
However, the challenge here is NOT to use Newtonsoft.Json at all but rely on the new System.Text.Json and Microsoft.AspNetCore.Mvc.ModelBinding instead. The reason: my classes already are heavily 'Netwtonsoft decorated' but this decoration (class/property attributes) is optimized and customized for purposes other than ASP.Net deserialization.
Microsoft has a solution relying on ModelBinder attribute described here. I am able to correctly deserialize polymorphic objects but not complex objects. That is, polymorphic objects containing collection of other, non-polymorphic objects do not get deserialized properly.
public abstract class Vehicle
{
public abstract string Kind { get; set; }
public string Make { get; set; }
public RepairRecord[]? RepairHistory { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class Car : Vehicle
{
public override string Kind { get; set; } = nameof(Car);
public int CylinderCount { get; set; }
}
public class Bicycle : Vehicle
{
public override string Kind { get; set; } = nameof(Bicycle);
public bool HasStand { get; set; }
}
public class RepairRecord
{
public DateTime DateTime { get; set; }
public string Description { get; set; }
}
[HttpPut]
public IActionResult Create([ModelBinder(typeof(VehicleModelBinder))] Vehicle vehicle)
{
_logger.LogInformation(vehicle.ToString());
return new OkResult();
}
The problem: deserialized vehicle is missing RepairHistory records in the Create() method.
What am I missing? Please advise.
Complete, working code below.
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new VehicleModelBinderProvider());
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.UseAllOfForInheritance(); // enabling inheritance - this allows to maintain the inheritance hierarchy in any generated client model
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
public abstract class Vehicle
{
public abstract string Kind { get; set; }
public string Make { get; set; }
public RepairRecord[]? RepairHistory { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class Car : Vehicle
{
public override string Kind { get; set; } = nameof(Car);
public int CylinderCount { get; set; }
}
public class Bicycle : Vehicle
{
public override string Kind { get; set; } = nameof(Bicycle);
public bool HasStand { get; set; }
}
public class RepairRecord
{
public DateTime DateTime { get; set; }
public string Description { get; set; }
}
[ApiController]
[Route("")]
public class Controller : ControllerBase
{
private readonly ILogger<Controller> _logger;
public Controller(ILogger<Controller> logger)
{
_logger = logger;
}
[HttpPost]
public IActionResult Create([ModelBinder(typeof(VehicleModelBinder))] Vehicle vehicle)
{
_logger.LogInformation(vehicle.ToString());
return new OkResult();
}
}
public class VehicleModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Vehicle))
{
return null;
}
var subclasses = new[] { typeof(Car), typeof(Bicycle), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new VehicleModelBinder(binders);
}
}
public class VehicleModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public VehicleModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Vehicle.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == nameof(Car))
{
(modelMetadata, modelBinder) = binders[typeof(Car)];
}
else if (modelTypeValue == nameof(Bicycle))
{
(modelMetadata, modelBinder) = binders[typeof(Bicycle)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
Unfortunately, currently (September 2022) there is no good solution available.
See my discussion with Microsoft here.
Supposedly, the problem will be solved by [JsonDerivedType] attibute when .Net 7 becomes available.

How to have access to an inherited method in C#?

Am I missing any concept about inheritance? I am trying to call a method that is in a child class but it doesn't work.
This is what I have, so simple:
Vuelo.cs
public class Vuelo
{
private Aeronave _aeronave { get; set; }
public Vuelo(string numero, Aeronave aeronave)
{
ValidarNumero(numero); // validates numero
_aeronave = aeronave;
}
public string modelo_aeronave()
{
return _aeronave.model(); // This is where the error goes, .model()
}
public string RegistroAvion()
{
return _aeronave.Registration(); // This worked perfectly
}
}
Aeronave.cs
public class Aeronave
{
private string _registration { get; set; }
public Aeronave(string registration)
{
_registration = registration;
}
public string Registration()
{
return _registration;
}
}
Airbus319.cs (the child class):
public class AirbusA319 : Aeronave
{
private string _model { get; set; }
public AirbusA319(string model, string registro) : base(registro)
{
_model = model;
}
public string model()
{
return _model;
}
}
I want to show up the model of the Airbus that is in model() like this:
Vuelo vuelo = new Vuelo("AB345", new AirbusA319("G-EUPT", "GG235"));
Console.WriteLine(vuelo.modelo_aeronave());
I can't find solutions in the internet, even in microsoft docs about inheritance.
You would need to modify your classes as shown below. Aeronave should contain model (virtual or abstract) to be overridden in Airbus.
public abstract class Aeronave
{
private string _registration { get; set; }
public Aeronave(string registration)
{
_registration = registration;
}
public string Registration()
{
return _registration;
}
public abstract string model();
}
public class AirbusA319 : Aeronave
{
private string _model { get; set; }
public AirbusA319(string model, string registro) : base(registro)
{
_model = model;
}
public override string model()
{
return _model;
}
}

How to send data to view?

I have tables, to which I can send data, everything is set up, now I only need to understand, how to send data to view. When I want to get data using Model word, I'm getting error: NullReferenceException: Object reference not set to an instance of an object.
index.cshtml
#model FaqVM
#{
Layout = "_Layout";
}
#Model.MainCategoryTitle // Error
Faq.cs
public class Faq : CanBeLocalized, IHaveIntegerId, ICanProvideCreatedTime, IHaveConcurrencyToken
{
public int Id { get; set; }
[Localize] public string MainCategoryTitle { get; set; }
public DateTime Created { get; set; } = DateTime.Now;
public string ConcurrencyToken { get; set; }
public DateTime? Disabled { get; set; }
public int SOrder { get; set; } = 0;
public DateTime? Updated { get; set; }
public ICollection<FaqSubcategory> FaqSubcategories { get; set; } = new HashSet<FaqSubcategory>();
}
FaqVM.cs
public class FaqVM
{
public string MainCategoryTitle { get; set; }
public List<FaqSubcategory> FaqSubcategories { get; set; }
}
public class CmsController : Controller
{
protected ILocale _locale;
protected ICacheManager _cacheManager;
protected RegionProvider _regionProvider;
protected IViewRenderService _viewRenderService;
private IServiceProvider _serviceProvider;
//...
}
public class FaqController : CmsController
{
private readonly Faq _faq;
public FaqController(
RegionProvider regionProvider,
ILocaleAccessor localeAccessor,
ILiteralProvider literalProvider,
CartRepo cartRepo,
IServiceProvider serviceProvider,
Faq faq) : base(serviceProvider)
{
_faq = faq;
}
public async Task<IActionResult> Index()
{
var vm = new FaqVM
{
MainCategoryTitle = _faq.MainCategoryTitle,
FaqSubcategories = _faq.FaqSubcategories.ToList()
};
return View(vm);
}
}
Inside your Index() method in your FaqController you need to create your ViewModel as fx
var vm = new FaqVM(){
MainCategoryTitle = "test",
FaqSubcategories = new List<FaqSubcategory>(){ new FaqSubcategory() }
}
and then you need to pass the ViewModel to the return part of your Index method:
return View(vm);
Typically you would create your vm from some data from a database or some user input.

How to serialize to XML generic classes?

I have these interfaces:
public interface IParameter
{
string Name { get; }
object UntypedValue { get; set; }
}
public interface IValidationPolicy<T>
{
bool Validate(T toValidate);
T Default();
}
A parameter base class
[Serializable]
public abstract class ParameterBase : IParameter
{
public abstract string Name { get; protected set; }
public abstract object UntypedValue { get; set; }
}
A parameter concrete class (I have more but them are quite similar):
public class Parameter<T, V> : ParameterBase where V : IValidationPolicy<T>
{
[XmlAttribute("Name")]
public override string Name { get; protected set; }
[XmlIgnore]
protected V validation_policy_;
[XmlElement("AnyValidation", Type = typeof(AnyValidation<>))]
[XmlElement("MultiOptionsValidation", Type = typeof(MultiOptionsValidation<>))]
[XmlElement("RangeValidation", Type = typeof(RangeValidation<>))]
[XmlElement("TextValidation", Type = typeof(TextValidation))]
public V Validation
{
get
{
return validation_policy_;
}
}
[XmlIgnore]
protected T value_;
[XmlElement("Value")]
public T Value
{
get
{
return value_;
}
set
{
if (validation_policy_.Validate(value))
{
value_ = value;
}
}
}
[XmlIgnore]
public object UntypedValue
{
get
{
return Value;
}
set
{
throw new NotImplementedException();
}
}
}
And an XMLParameter class:
public class XMLParameter : INotifyPropertyChanged
{
public string Description { get; set; }
public int PasswordLevel { get; set; }
public bool Enabled { get; set; }
public ParameterBase Parameter { get; set; }
}
How can I serialize and deserialize a list of XMLParameters?
In particular I have problem on serializing the IParameter objects.
Since the interface is not serializable as first attempt I created a base abstract class ParameterBase and derive the Parameter from it.
But when I try to serialize it in a test method:
var validation = new RangeValidation<int>() { MinValue = 1, MaxValue = 6 };
var parameter = new Parameter<int, RangeValidation<int>>();
parameter.Initialize("NumberOfTrays", validation);
parameter.Value = 6;
XElement par = validation.ToXElement<Parameter<int, RangeValidation<int>>>();
I got an exception: Error at reflection of type 'ConfigurableLibray.Parameter'2[System.Int32,ConfigurableLibray.RangeValidation'1[System.Int32]]'
The inner exception says that ConfigurableLibray.Parameter'2[T,V] is not supported
What am I doing wrong?
Thanks in advance for any suggestion!
I solved implementing manually the serialization and deserialization of the classes using reflection.

ASP.Net MVC request ViewModel not parsing

I have a request like this:
filter[logic]:and
filter[filters][0][value]:a
filter[filters][0][operator]:startswith
filter[filters][0][field]:result
filter[filters][0][ignoreCase]:true
I need to receive it on the Controller but I don't know exactly how. I have tried this view model:
{
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filters { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
//public string operator { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
}
But the Controller receives it all null. operator property is commented because operator is a reserved keyword, I don't know how to make Asp.Net to use it. And I don't know if this is the cause of the problem.
Note that I can't change the request body pattern because it comes from this Kendo Widget.
This is my Controller(test version):
public ActionResult Text(SearchFilterViewModel filter)
{
return Json("", JsonRequestBehavior.AllowGet);
}
Here is working solution
Model:
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filter { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
public string oper { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
Then you can write custom IValueProvider where you can override usual parsing mechanism like this:
public class KendoValueProvider : NameValueCollectionValueProvider
{
public KendoValueProvider(NameValueCollection originalCollection)
: base(UpdateCollection(originalCollection), CultureInfo.InvariantCulture)
{
}
private static NameValueCollection UpdateCollection(NameValueCollection collection)
{
NameValueCollection result = new NameValueCollection();
foreach (string key in collection.Keys)
{
// ignore all other request
if (!key.StartsWith("filter"))
return null;
var newKey = key
.Replace("[filters]", string.Empty)
.Replace("filter[logic]", "logic")
.Replace("[value]", ".value")
.Replace("[operator]", ".oper")
.Replace("[field]", ".field")
.Replace("[ignoreCase]", ".ignoreCase");
var value = collection[key];
result.Add(newKey, value);
}
return result;
}
}
Then you need to write ValueProviderFactory that will register this ValueProvider like this:
public class KendoValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new KendoValueProvider(controllerContext.HttpContext.Request.QueryString);
}
}
And the last step is just register it in Global.asax file
ValueProviderFactories.Factories.Add(new KendoValueProviderFactory());
And sample Action
[HttpGet]
public ActionResult Index(SearchFilterViewModel model)
{
return Json(model, JsonRequestBehavior.AllowGet);
}

Categories