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.
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.
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.
Several views from my project have the same dropdownlist...
So, in the ViewModel from that view I have :
public IEnumerable<SelectListItem> FooDdl { get; set; }
And in the controller I have :
var MyVM = new MyVM() {
FooDdl = fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name)
}
So far so good... But I´m doing the same code in every view/controller that have that ddl...
Is that the best way to do that?
Thanks
I'd say that's fine to be honest, as it's only a repeat of a few lines of code. If it's really bothering you though, you could have all your controllers inherit from a BaseController (if they don't already) and store a method in there to get them all, something like:
public IEnumerable<SelectListItem> GetFoos()
{
return fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name);
}
Then in your controllers you could do:
var MyVM = new MyVM() {
FooDdl = GetFoos()
}
If your DropDownList is exactly the same the approach I would use is:
1) In your Base Controller or in a Helper class, you can create a method that returns a SelectList. That method should receive a nullabe int to get the select list with a value pre selected.
2) It is wise to cache the information you list in the DDL, to not query the database too often.
So, for (1):
public SelectList GetMyDDLData(int? selectedValue){
var data = fooRepository.GetAll().Select(x => new { Value = x.Id, Text = x.Name });
return new SelectList(data, "Id","Name", selectedValue);
}
In the view model:
var myVM = new MyVM();
myVM.DDLData = this.GetMyDDLData(null) // if it is in your BaseController.
myVM.DDLData = YourHelperClass.GetMyDDLData(null) // if it is in a helper static class
In your views:
#Html.DropDownListFor(x => x.FooProp, Model.DDLData, "Select one...")
For number (2):
private IEnumerable<YourModel> GetMyData()
{
var dataItems = HttpContext.Cache["someKey"] as IEnumerable<YourModel>;
if (dataItems == null)
{
// nothing in the cache => we perform some expensive query to fetch the result
dataItems = fooRepository.GetAll().Select(x => new YourModel(){ Value = x.Id, Text = x.Name };
// and we cache it so that the next time we don't need to perform the query
HttpContext.Cache["someKey"] = dataItems ;
}
return dataItems;
}
The "someKey" could be something specific and static is this data is the same to all users, or you can do "someKey" + User.Id if the data is specific to one user.
If your repository is an abstractin layer (not directly EntityFramework) you can place this code there.
We also use a static class :
public static class SelectLists
{
public static IList<SelectListItem> CompanyClasses(int? selected)
{
var session = DependencyResolver.Current.GetService<ISession>();
var list = new List<SelectListItem>
{
new SelectListItem
{
Selected = !selected.HasValue,
Text = String.Empty
}
};
list.AddRange(session.All<CompanyClass>()
.ToList()
.OrderBy(x => x.GetNameForCurrentCulture())
.Select(x => new SelectListItem
{
Selected = x.Id == (selected.HasValue ? selected.Value : -1),
Text = x.GetNameForCurrentCulture(),
Value = x.Id.ToString()
})
.ToList());
return list;
}
}
In the view we have nothing special :
#Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model))
And sometime we also create an EditorTemplate so it's faster to reuse like this
Model :
[Required, UIHint("CompanyClassPicker")]
public int? ClassId { get; set; }
EditorTemplate :
#model int?
#if (ViewBag.ReadOnly != null && ViewBag.ReadOnly)
{
var item = SelectLists.CompanyClasses(Model).FirstOrDefault(x => x.Selected);
if (item != null)
{
<span>#item.Text</span>
}
}
else
{
#Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model))
}
Create object with getter for your dropdown values:
public static class DropDowns
{
public static List<SelectListItem> Items {
get
{
//Return values
}
}
}
Create Razor partial:
#Html.DropDownListFor(m => "ChoosenItem", DropDowns.Items, "")
Call partial:
#Html.RenderPartial("DropDownItems")
And finally receive ChoosenItem value in controller. Simply.
I use an IModelEnricher combined with Automapper and attributes that define relationships between a type of list and select list provider. I return an entity etc using a specific ActionResult that then automaps my entity to a ViewModel and enriches with data required for select lists (and any additional data required). Also keeping the select list data as part of your ViewModel keeps your controller, model, and view responsibilities clear.
Defining a ViewModel ernicher means that anywhere that ViewModel is used it can use the same enricher to get its properties. So you can return the ViewModel in multiple places and it will just get populated with the correct data.
In my case this looks something like this in the controller:
public virtual ActionResult Edit(int id)
{
return AutoMappedEnrichedView<PersonEditModel>(_personRepository.Find(id));
}
[HttpPost]
public virtual ActionResult Edit(PersonEditModel person)
{
if (ModelState.IsValid){
//This is simplified (probably don't use Automapper to go VM-->Entity)
var insertPerson = Mapper.Map<PersonEditModel , Person>(person);
_personRepository.InsertOrUpdate(insertPerson);
_requirementRepository.Save();
return RedirectToAction(Actions.Index());
}
return EnrichedView(person);
}
This sort of ViewModel:
public class PersonEditModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int FavouriteTeam { get; set; }
public IEnumerable<SelectListItem> Teams {get;set;}
}
With this sort of Enricher:
public class PersonEditModelEnricher :
IModelEnricher<PersonEditModel>
{
private readonly ISelectListService _selectListService;
public PersonEditModelEnricher(ISelectListService selectListService)
{
_selectListService = selectListService;
}
public PersonEditModelEnrich(PersonEditModel model)
{
model.Teams = new SelectList(_selectListService.AllTeams(), "Value", "Text")
return model;
}
}
One other option is to decorate the ViewModel with attributes that define how the data is located to populate the select list. Like:
public class PersonEditModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int FavouriteTeam { get; set; }
[LoadSelectListData("Teams")]
public IEnumerable<SelectListItem> Teams {get;set;}
}
Now you can decorate an appropriate method in your select service with an attribute like:
[ProvideSelectData("Teams")]
public IEnumerable Teams()
{
return _teamRepository.All.ToSelectList(a => a.Name, a => a.TeamId);
}
Then for simple models with no complex enrichment just the generic enrichment process can handle it. If you want to do anything more complex you can define an enricher and it will be used if it exists.
Other options could be a convention over configuration approach where the Enricher looks at property name and type e.g. IEnumerable<SelectListItem> PossibleFirstDivisionTeams {get;set;} then matches this if it exists with a select list provider name in a class that say implements a marker interface ISelectListProvider. We went the attribute based one and just created Enums representing the various lists E.g. SelectList.AllFirstDivisionTeams. Could also try interfaces on ViewModel that just have a property collection for a selectlist. I don't really like interfaces on my ViewModels so we never did this
It all really depends on the scale of your application and how frequently same type of select list data is required across multiple models. Any specific questions or points you need clarified let me know
See this question. Also this blog post and this. Also this question on Automapper forum
The first question is if the options-list belongs to the ViewModel. A year or two ago I did the same, but what I see recently more and more as a "best practice" is that people add the list to the ViewBag/ViewData not to the ViewModel. That's an option and I tend to do the same for a one-shot drop-down list, but it doesn't answer the code-reuse question you are facing. For that I see two different approaches (and two more that I rule out).
Shared editor template. Create an editor template for the type that's represented by the dropdown. In this case - because we don't have the list of possible options in the ViewModel or the ViewBag - the template has to reach out for the options to the server. That's possible by adding an action method (that returns json) to a controller class. Either to a shared "LookupsController" (possibly an ApiController) or to the controller that the list-items' type belongs to.
Partial view. The drop down values belong to some type. The Controller of that type could have an action method that returns a partial view.
The benefit of the first one is that a nice #Html.EditorFor call will do the job. But I don't like the ajax dependency. Partly for that reason I would prefer the partial view.
And there is a third one: child action, but I don't see that a good pattern here. You can google what's the difference between child actions and partial views, for this case child action is the wrong choice. I also wouldn't recommend helper methods. I believe they are not designed for this use case either.
You could put that fetch in the default (null) constructor of MyVM if you don't need to vary its content.
Or you could use a PartialView that you render into the views that need t.
I like to use static classes often in a helper class that I can call from any view.
#Html.DropDownListFor(x => x.Field, PathToController.GetDropDown())
and then in your controller have a method built like this
public static List<SelectListItem> GetDropDown()
{
List<SelectListItem> ls = new List<SelectListItem>();
lm = (call database);
foreach (var temp in lm)
{
ls.Add(new SelectListItem() { Text = temp.name, Value = temp.id });
}
return ls;
}
Hopefully it helps.
If you really don't want to duplicate the code, place the code from the controllers into a helper class, and render the dropdown within a shared view (like _Layout.cshtml) that you'd then have to implement into your views by RenderPartial.
Create a partial view, _MyDropdownView.cstml, which uses the helper class you threw the code from the controllers in with something like the following:
#using MyNamespace.MyHelperClass
<div id="myDropdown">#Html.DropDownListFor(model => model.Prop, MyVM as SelectList, "--Select a Property--")</div>
Then, within your views:
#Html.RenderPartial("_MyDropdownView")
Extension methods to the rescue
public interface ISelectFoo {
IEnumerable<SelectListItem> FooDdl { get; set; }
}
public class FooModel:ISelectFoo { /* implementation */ }
public static void PopulateFoo(this ISelectFoo data, FooRepository repo)
{
data.FooDdl = repo.GetAll().ToSelectList(x => x.Id, x => x.Name);
}
//controller
var model=new ViewModel();
model.PopulateFoo(repo);
//a wild idea
public static T CreateModel<T>(this FooRepository repo) where T:ISelectFoo,new()
{
var model=new T();
model.FooDdl=repo.GetAll().ToSelectList(x => x.Id, x => x.Name);
return model;
}
//controller
var model=fooRepository.Create<MyFooModel>();
What about a Prepare method in a BaseController?
public class BaseController : Controller
{
/// <summary>
/// Prepares a new MyVM by filling the common properties.
/// </summary>
/// <returns>A MyVM.</returns>
protected MyVM PrepareViewModel()
{
return new MyVM()
{
FooDll = GetFooSelectList();
}
}
/// <summary>
/// Prepares the specified MyVM by filling the common properties.
/// </summary>
/// <param name="myVm">The MyVM.</param>
/// <returns>A MyVM.</returns>
protected MyVM PrepareViewModel(MyVM myVm)
{
myVm.FooDll = GetFooSelectList();
return myVm;
}
/// <summary>
/// Fetches the foos from the database and creates a SelectList.
/// </summary>
/// <returns>A collection of SelectListItems.</returns>
private IEnumerable<SelectListItem> GetFooSelectList()
{
return fooRepository.GetAll().ToSelectList(foo => foo.Id, foo => x.Name);
}
}
You can use this methods in the controller:
public class HomeController : BaseController
{
public ActionResult ActionX()
{
// Creates a new MyVM.
MyVM myVm = PrepareViewModel();
}
public ActionResult ActionY()
{
// Update an existing MyVM object.
var myVm = new MyVM
{
Property1 = "Value 1",
Property2 = DateTime.Now
};
PrepareViewModel(myVm);
}
}
Have an interface with all your properties that need to be automatically populated:
public interface ISelectFields
{
public IEnumerable<SelectListItem> FooDdl { get; set; }
}
Now all your view models that want to have those properties, implement that interface:
public class MyVM : ISelectFields
{
public IEnumerable<SelectListItem> FooDdl { get; set; }
}
Have a BaseController, override OnResultExecuting, find the ViewModel that is passed in and inject the properties to the interface:
public class BaseController : Controller
{
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult != null)
{
var viewModel = viewResult.Model as ISelectFields;
if (viewModel != null)
{
viewModel.FooDdl = fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name)
}
}
base.OnResultExecuting(filterContext);
}
}
Now your controllers are very simple, everything is strongly typed, you are sticking with the DRY principle and you can just forget about populating that property, it will always be available in your views as long as your controllers inherit from the BaseController and your ViewModels implement the interface.
public class HomeController : BaseController
{
public ActionResult Index()
{
MyVM vm = new MyVM();
return View(vm); //you will have FooDdl available in your views
}
}
Why not use the advantages of RenderAction:
#(Html.RenderAction("ControllerForCommonElements", "CommonDdl"))
Create a controller, and an action that returns the Ddl and and just reference it in the views.
See some tips here on how you could use it
This way you can also cache this result. Actually the guys building StackOverflow talked about the pros of using this combined with different caching rules for different elements (i.e. if the ddl does not need to be 100% up to date you could cache it for a minute or so) in a podcast a while ago.
I have two MVC models that look like this:
public class OtherModel
{
[Required]
[Display(Name = "Another ID")]
public int id{ get; set; }
}
public class MyModel
{
[Required]
[Display(Name = "ID")]
public int id { get; set; }
public PlayerModel otherModel = new OtherModel ();
}
My controller has an [HttpPost] action called USE that looks like this:
[HttpPost]
public ActionResult Use(MyModel myModel)
{
/// myModel.otherModel.id is 0 here!!
}
This action takes in a MyModel. When my form is being posted, the otherModel variable contains a 0 for the id value. Now, the view that contains the form is handed a MyModel and actually displays the otherModel.id on the page. The problem is the post action is not
properly marshalling the form data into the otherModel object and I have no clue why.
Another note: When I examine the form data headers for the post, I clearly see otherModel.id with the value that I expect.
Why is this data not appearing correctly within my otherModel object?
Thank You in advance!
Did you registered binder in Global.asax.cs?
public static void RegisterBinders(ModelBinderDictionary binders)
{
binders.Add(typeof(MyModel), new MyModelBinder());
// other binders
}
This is called in Application_Start like the following:
protected void Application_Start()
{
RegisterBinders(ModelBinders.Binders);
}
PS: I assumed you are using a custom model binder. In case you are using automatic binding see if you respect the naming conventions.
Instead of initializing otherModel with a new object at the line PlayerModel otherModel = new OtherModel();, use a property public PlayerModel otherModel { get; set; }. otherModel needs a property setter for the model binder to assign the value properly. This may require you to also change how you populate the otherModel property when displaying the view - construct an object and assign it explicitly in either the displaying controller method or some other function that hydrates the model.
I had an almost identical issue. The fix for me was as Matt mentioned, to make the inner object a property with the needed accessors.
public class OuterModel
{
public OuterModel ()
{
AuxData= new InnerModel();
}
public InnerModel AuxData{ get; set; }
}
public class InnerModel
{
Int Id {get; set;}
}