MVC - Nested models with interfaces and annotations - c#

I have basic class:
public abstract class AbstractBaseModel : IModel
{
[Display(Name = "Some Name")]
[DisplayFormat(DataFormatString = "{0:d0}")]
[RegularExpression(#"[0-9]{1,10}", ErrorMessage = "error")]
public virtual string SomeName{ get; set; }
}
The IModel interface is just simple declaration of properties:
public interface IModel
{
string SomeName{ get; set; }
}
From the base model I have 2 derived models
public class ClientModel : AbstractBaseModel
{
[Required(ErrorMessage = "Some error message for customer only")]
public override string SomeName{ get; set; }
}
public class PowerUserModel : AbstractBaseModel
{
[Required(ErrorMessage = "Different message for the admin")]
public override string SomeName{ get; set; }
}
This model, or rather interface is part of another model that combines multiple models:
public class ComboEndModel
{
public IModel Model { get; set; }
public IDifferentModel DifferentModel { get; set; }
}
Depending on the View/Controler that is currently used, I pass new ClientModel or PowerUserModel as a Model in ComboEndModel
When the view is rendered, I'm only getting the annotations from the base, abstract model, not the ones added in the derivative type. I suspect that's because I'm using interface as a nested model property instead of type.
Whats the correct way of implementing this relation, or working around the issue with incorrect annotations? Should I try with custom binding?

One thing that might work for you would be to create editor templates for each derived model.
If you define your ComboEndModel like this
var model = new ComboEndModel() { Model = new PowerUserModel() };
return View(model);
and in your view you use EditorFor
#model WebApplication.Models.ComboEndModel
#Html.EditorFor(m => m.Model)
depending on what Type Model is, it will pick the associated editor template and apply the annotations for that derived type. In this case you'd need a partial view in your EditorTemplates folder named PowerUserModel.cshtml with a model type of PowerUserModel

Ok I have some problems with that too, and what I understood so far is (someone correct me if I'm wrong):
Why this problem occurs?
Because the DataAnnotation that is retrieved are retrived from the type of the model. Let me show you some samples:
If you have in your .cshtml file:
Sample 1:
#model AbstractBaseModel
// The type that will be retrived is `AbstractBaseModel`.
Sample 2:
#model IModel
// The type that will be retrived is `IModel`.
Or in other case if you have actions:
public ActionResult SomeAction(AbstractBaseModel model) { /* ... */ }
Sample 2:
public ActionResult SomeAction(IModel model) { /* ... */ }
So what matters is what type you specified in #model or in the action parameter. Doesn't matter if is interface, abstract class, base class or other type of class, matter only the type you described in your use.
In your example specifically you wrote:
public class ComboEndModel
{
public IModel Model { get; set; }
// ...
}
So the DataAnnotation will be retrive from IModel, but IModel don't have a RequiredAttribute defined so you get one step further in the chain of inheritance/implementation and get AbstractBaseModel. (Try this to confirm what I'm writing: put a required in IModel.SomeName with a different message and see what message will get).
Solution/Suggestion
If I'm right, there is no solution for your problem without change the strategy. So here some suggestions about what you can do:
Remove Required attribute and do yourself the validation (in action or javascript, depending of how type of validation you're using).
Replace the error message after validation occurs, (I did this in my case).
Give up of this inheritance and make independent models.

Related

Controller accepting different models

I want to accept different model type from body based on query param value.
Example:
[HttpGet]
[Route("GetSystemdetails")]
public string Getdeatils([FromBody] SystemDetail sysdetails, string type)
{
//some code here
string details = getdetails(sysdetails);
}
// abc model
public class abc
{
public int UserID { get; set; }
public string Email { get; set; }
}
//xyz model
public class xyz
{
public int xyzid { get; set; }
public string systemval { get; set; }
public string snum { get; set; }
}
type abc and xyz will have it's own model. So based on type I receive in query param I wanted to pick the model and proceed.
Sample url:
localhost/GetSystemdetails/type=abc
localhost/GetSystemdetails/type=xyz
I thought of creating a new model SystemDetail which holds these two models(xyz and abc) and based on system pick them.
I wanted to know what are possible ways to achieve this kind of requirements without creating multiple methods in controller(I don't want to change the format of the URL).
That's not something that's supported out of the box. Your linked solution is probably the closest you'll get to that.
ASP.NET Core is not supposed to take values of the parameters into account when routing, except for validation.
There are several possible ways to do so
Having multiple model objects
As in the link you provided, you can declare multiple model objects. The site has given the example of
public class PostUserGCM
{
public User User { get; set; }
public GCM GCM { get; set; }
}
but you can use your own examples.
Base model
Your models can inherit from some base model. If you only need a single model at a time and they share some similarities, then you could just create a base model which the other two are inheriting from, be agnostic at implementation time and your use cases will mainly differ on instantiation inside the controller, while some service methods could handle other differences.

Simple way to bind abstract class model, ASP.Net Core 5

In short, I have a model class which inside of it has some properties as well as some properties which types are abstract classes.
The model looks something like this:
public class ModelClass
{
public string SomeProp { get; set; }
public AbstractOne FirstAbstract { get; set; }
public AbstractTwo SecondAbstract { get; set; }
}
And the abstract classes:
public abstract class AbstractOne
{
public virtual Type TwoType { get; set; }
public string SomeProp { get; set; }
public string SomeOtherProp { get; set; }
}
public abstract class AbstractTwo
{
// Its empty
}
So in the actual code, I have a class that inherits each one of those abstract classes, and in the controller I initially give the model as:
return View(new ModelClass()
{
SomeProp = "Somevalue",
AbstractOne = valueUpInTheCode
AbstractTwo = (AbstractTwo)Activator.CreateInstance(valueUpInTheCode.TwoType)
}
Which works fine, the View gets the model, and I can use the html helpers to generate form fields.
I have a form which has a Html.HiddenFor for SomeProp and AbstractOne, DisplayFor for AbstractOne and EditorFor AbstractTwo which is specific to the class that inherits the AbstractTwo, so the EditorFor line looks something like this:
#Html.EditorFor(model => model.SecondAbstract, Model.SecondAbstract.GetType().Name)
Which, on the UI seems fine at least, all of the default values or values passed when giving creating model for the view appear and seem to be there.
The problem is when that form is submitted, I have a function that should handle it, for now its empty, but I'm still debugging so I just need some place to put a breakpoint:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Submit(ModelClass model)
{
if (ModelState.IsValid == false) return View("Index", model);
return View("Index", model);
}
The problem is, that model ends up having all of its properties set how they were entered, but the FirstAbstract and SecondAbstract don't seem to properly bind and end up both being null.
So far I've tried some stuff I found on here which seemed to be either outdated or not working for my use case, the closest I got to something working was with this blog on how to make a custom Model Binder:
https://www.palmmedia.de/Blog/2018/5/13/aspnet-core-model-binding-of-abstract-classes
It didn't end up working in the end, but gave me an idea of how something like this should be fixed... And I've got no clue how to do that, or where to start even.

Domain Model Object to View Model Object in MVC

I want to use view model for displaying instead of domain model. I have got these view models classes:
public class ArticleDescriptionViewModel
{
public string Title { get; set; }
public DateTime DateCreated { get; set; }
}
public class HomePage
{
public List<ArticleDescriptionViewModel> Articles { get; set; }
}
In the domain model i have got:
public class ArticleDescription
{
public string Title { get; set; }
public DateTime DateCreated { get; set; }
}
And this service method:
public List<ArticleDescription> GetArticlesDescription()
{
var articleDescription= from a in _ctx.Articles
select new ArticleDescription
{ Title = a.Title, DateCreated = a.DateCreated };
return articleDescription.ToList();
}
in the controller i want to match my list inside of my view model class with the list returned by my domain model class.
public ActionResult Index()
{
HomePage HomePageInstance = new HomePage();
HomePageInstance.Articles = _repo.GetArticlesDescription();
return View(HomePageInstance);
}
I have got an error:
"Cannot implicitly convert type System.Collections.Generic.List (DBayonaCode.Domain.Services.Models.ArticleDescription)' to 'System.Collections.Generic.List(DBayonaCode.Models.ArticleDescriptionViewModel)'"
But these two classes are equivalent? I am doing something wrong. I appreciate your help?
ArticleDescription and ArticleDescriptionViewModel are two different types, so there's no implicit conversion between them. You need to map your Domain Model object to your View Model object, you can do that either manually or with a tool like AutoMapper.
You could write extension methods like these to do the mapping:
public static class Mappings
{
public static ArticleDescriptionViewModel ConvertToView(this ArticleDescription article)
{
// Mapping Code
// return new ArticleDescriptionViewModel { ... }
}
public static List<ArticleDescriptionViewModel> ConvertToViews(this List<ArticleDescription> articles)
{
List<ArticleDescriptionViewModel> articleViews = new List<ArticleDescriptionViewModel>();
foreach (ArticleDescription article in articles)
{
articleViews.Add(article.ConvertToView())
}
return articleViews;
}
}
While MVC default project template offer just one model folder, thus implicitly presenting the idea that models are one thing, in fact There are three types of data models potentially involved in an ASP.NET MVC application:
- Domain model objects will be passed from and to a middle tier services interfacing with databases.
- View Model objects are those that the Controller pass to the View.
- Input model objects are those that the default modelBinder or some custom modelBinder generates from the view, although in many cases the input models are the same view model objects.
Hope it helps.

Model items passed to Dictionary is of type error

I have the following in my MVC 5 Application. I have 3 Tabs for cars of Fuel Type which are Diesel/Petrol/Electric. I have these in 3 Partial Views - something like:
#model MyProj.DieselviewModel
<p>
#{ Html.RenderPartial("_CarDetails"); }
</p>
The partial for Petrol and Electric then take a PetrolViewModel and ElectricViewModel respectively and all three render the partial view _CarDetails which takes a CarDetailsViewModel.
Note my DieselViewModel is at below:
public class DieselViewModel : CarDetailsViewModel
{
public override CarFuelTypeEnum CarFuelType
{
get { return CarFuelTypeEnum.Diesel; }
set { base.CarFuelType = value; }
}
}
So the fuel type Enum is set here for each Fuel Type and then in the CarDetailsViewModel I have:
public virtual CarFuelTypeEnum CarFuelType{ get; set; }
So in my _CarDetails Partial view I know which FuelType is set using a ModelBuilder class I have which uses Generics to new up a model of Type T() which will be either the DieselViewModel/PetrolViewModel or ElectricViewModel.
This works nicely until I want to create another partial view in my _CarDetails page - to render some other detail:
So my CarDetailsViewModel is something like (most propertys removed for simplicity)
public class CarDetailsViewModel
{
public string Name { get; set; }
public SomeOtherClass OtherClass{ get; set; }
public class SomeOtherClass
{
public double Field1{ get; set; }
}
}
My _CarDetails partial then is like:
#model MyProj.CarDetailsViewModel
#Html.RenderPartial("_AnotherPartial", Model.OtherClass);
the model defined at the top of _AnotherPartial is:
#model MyProj.CarDetailsViewModel.OtherClass
However even though I am attempting to pass that model class into _AnotherPartial I get an error thrown here saying I am passing a CarDetailsViewModel but the dictionary requires a model of type 'DieselViewModel' or PetrolViewModel or ElectricViewModel which is my very first parent view
However with my definition of a DieselViewModel - public class DieselViewModel : CarDetailsViewModel isnt it a CarDetailsViewModel that I can then pass to _AnotherPartial page?
You will get this error if you have not initialized a new instance of the nested class. In CarDetailsViewModel, add a constructor to ensure its initialized
public CarDetailsViewModel()
{
OtherClass = new SomeOtherClass();
}
When a model is null, it falls back to the ViewDataDictionary, which is DieselViewModel, but you actually want DieselViewModel.SomeOtherClass. In this case the error message is slightly misleading because the it should say ..but the dictionary requires a model of type DieselViewModel.SomeOtherClass. This appears to be a minor bug in that it does not include the nested class.
Another option is to move SomeOtherClass outside CarDetailsViewModel (i.e. not nested) and change _AnotherPartial.cshtml to #model MyProj.OtherClass. In this case if you did not initialise it, you would receive the error message ..but the dictionary requires a model of type DieselViewModel.SomeOtherClass which confirms my suspicion that its a bug
The problem seems to be that your first layer, where you have DieselViewModel, et al. as model for the view is getting a CarDetailsViewModel. You can pass DieselViewModel for CarDetailsViewModel, but not the other way around.

How do I prevent hidden fields from interfering with server side validation in MVC?

I have a partial view that displays a number of inputs based on a view model. In some situations, some of those inputs are not rendered by the partial view, but are still decorated with [Required] attributes in the view model. As a result, when the form is posted back to my controller, ModelState.IsValid returns false. Is there a way to bypass this?
You can use Foolproof to validate your fields conditionally. This way, they'll be required only when they need to, as you can see in the example of the link.
private class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool Married { get; set; }
[RequiredIfTrue("Married")]
public string MaidenName { get; set; }
}
In this example, MaidenName will only change your ModelState.IsValid to false if Married == true
I'd recommend separating your validation from your base model.
public class MyModel
{
public string MyString { get; set; }
public string MyHiddenField { get; set; }
}
public interface IMyModel_ValidateMystringOnly
{
[Required]
string MyString { get; set; }
}
[MetadataType(TypeOf(IMyModel_ValidateMystringOnly))]
public class MyModel_ValidateMystringOnly : MyModel
This allows you to create any number of validation types, and only validate what you want when you want.
public ActionResult ShowMyModel()
{
var model = new MyModel(); // or Respository.GetMyModel() whatever..
View(model);
}
public ActionResult ValidateModel(MyModel_ValidateMystringOnly model)
{
if (ModelState.IsValid)
{
// Hey Validation!
}
// MyModel_ValidateMyStringOnly is a MyModel
// so it can be passed to the same view!
return View("ShowMyModel", model);
}
This is just an example, but should be clear on how-to reuse the same model with or without validation.
I have used method at times where the form changes slightly based on specific DropDown or Radio Button selections.
Inside your Action method before you check ModelState.IsValid you can do something like ModelState.Remove("Object.PropertyName")
Note: The property name should be the same as the ID rendered to the client. Use a "." for any underscores.
If isSomeCondition Then
ModelState.Remove("Property1")
ModelState.Remove("Property2")
End If
If ModelState.IsValid() Then
...
End If
You should always separate your VIEW model from your DOMAIN model. There is a very good reason for this and it has to do with security. When you use your domain models as your view models you are vulnerable to an overposting and/or underposting attacks. You can read more about it on these pages:
http://odetocode.com/blogs/scott/archive/2012/03/12/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
https://hendryluk.wordpress.com/tag/asp-net-mvc/
In short if you don't need a field then it should not be in your view model. You should convert - map your view models to domain models. Although it can be tedious it makes your application much more secure. There are libraries you can use to help you with mapping such as Automapper.
EDIT: Since my original answer, I have come to a conclusion that the easiest way to deal with this type of scenario is to have your view model implement IValidatableObject interface and then write your validation logic inside the Validate method. It does not give you client side validation but it is the most effective and clean way to accomplish custom/scenario based validation without writing your own custom filters.
You can read more about it here: http://weblogs.asp.net/scottgu/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3

Categories