In an MVC4 web application using the Razor engine and entity framework, is it possible to create a template for use with the html helper #Html.EditorForModel , so that entities with links to other tables are better displayed.
The example I am working with is a DbContext containing two DBSets, Regions and Schools. There are many regions, and a school may belong to one region. Ideally I would like the editor for schools to show a dropdown of regions to select from. I would like to make a template that is generic enough so that I can just call the #Html.EditorForModel helper and the form is generated in one go, and that I could make changes to the region or schools tables later on and for the changes to be reflected in the edit form without me needing to make alterations.
Some code:
public class MyContext : DbContext
{
public MyContext ()
: base("DefaultConnection")
{
}
public DbSet<Region> Regions { get; set; }
public DbSet<School> Schools { get; set; }
[Table("Regions")]
public class Region
{
public Region()
{
Schools = new List<School>();
}
[Key]
public int RegionId { get; set; }
[StringLength(256)]
public string RegionName { get; set; }
public ICollection<School> Schools { get; set; }
}
[Table("Schools")]
public class School
{
public School() { }
[Key]
public int SchoolId { get; set; }
[StringLength(256)]
public string SchoolName { get; set; }
[ForeignKey("Region")]
public int RegionId { get; set; }
public virtual Region Region { get; set; }
}
}
I have created a partial view to create display the editor form, with the idea that I can pass in either a Region or a School as the view model.
#model object
#using (Html.BeginForm())
{
#Html.ValidationSummary("Broken stuff:")
#Html.EditorForModel()
}
I don't know if this is possible, but I would really like the new template to loop over the properties of the entity and detect if there is a linked table (e.g. School contains a Region) and display the list of regions in a dropdown.
Well, in general, it's a bad idea to pass your data models directly to your views for rendering. There are security issues there, among other things. But, even if you are going to do this you would not want to pass your entire data context like that because it's not structured in a way that is suitable for rendering in a web page.
Off corse you can. Look at here to see how.
An alternation is that you use scaffolding and let it to create your views. Most of times(!) it creates correct DropDowns for you and you just customize your view if you want.
And in those few situations that yo see strange DDLs, you just need to correct ValueName and DataName parameters in the related action methods whic return list items to the view...
Related
Ive been working with MVC for a while now and am used to creating a View Model class for every MVC view. Now I am trying out Web API and I think I may be hung up on this MVC mentality. My relationship looks like this:
public class Supplier
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<SupplierProduct> SupplierProducts { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<SupplierProduct> SupplierProducts { get; set; }
}
public class SupplierProduct
{
public int Id { get; set; }
public string Title { get; set; }
public int SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
I am working on the creation of the supplier where in the create form the user is able to select multiple products that already exist. In MVC, I would POST a view model that looks something like this:
public class SupplierCreateViewModel
{
public string Title { get; set; }
public ICollection<ProductViewModel> SelectedProducts { get; set; }
}
And in the controller I would first create the new Supplier then create a new SupplierProduct for each SelectedProduct. I implemented something like this in Web API in the POST action of my Supplier oData controller but it doesnt feel right. I think that instead, I need to change my approach and do something like this from the client:
Scrap the View Model design. (There arent really 'views' anymore anyway)
Have both a Supplier and a SupplierProduct Controller with a POST action on both.
On save, send my Supplier create request to POST api/Suppliers/.
Using the Id of the Supplier JSON in the response, send multiple create requests to POST api/SupplierProduct.
So my questions are:
Am I heading in the right direction with this approach?
Instead of View Models is there a different pattern I should use? DTO?
With the example given, am I forced to send 1 - n requests like that? This feels wrong.
Actually, it depends on your use-case. If your API is totally faced publicly, i would advice using DTO's. If it is for yourselve or for an internal team, i would stick with OData EF Models ( because it is quicker)
You can ( as usual) give the entire entity through your api.
You can use a viewmodel ( more like DTO's when using it in an API, but it's the same thing) and transform the methods accordingly. You can use automapper for that - it also transforms the $filter query, an example is found here : Web API Queryable - how to apply AutoMapper?.
Don't forget, an API has a lot of awesome advantages. OData uses Batch and Patch to change your entities. So i personally stick with Odata as entites most of the time, but that's a personal choice.
I am interested in how I can map two entities to same table, by using code first. Here's an example:
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public byte Age { get; set; }
public bool Active { get; set; }
public DateTime Created { get; set; }
}
public class UserViewModel
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public byte Age { get; set; }
}
Basically I'm fed up with building repositories. I want to map all possible models for configuration portal, user portal, other services in modelbuilder and just use DbContext for everything. I want to set User class as top of the hierarchy and a class that builds the database, while all other models should just be there for various applications.
I don't want to use automapper. I've also done fair amount of manual coding which just wasted my time, and every single modification requires me to go back to repository and recode - which annoys me.
I've tried to use this in modelbuilder, but it warns me that hierarchy is not valid:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Map(p => { p.ToTable("Users"); });
modelBuilder.Entity<UserViewModel>().Map(p => { p.ToTable("Users"); });
}
Also keep in mind that I'm not trying to achieve "Table splitting". I don't want my table to be split in two entities, I want rather to have all columns nullable, except one with primary key, and allow different applications/web services/web portals to populate as much data as they've been granted access for.
Thanks for all the tips :)
You can't. One table = one entity (except advanced mappings like mentioned table splitting and TPH inheritance). View model is not and entity. It is just view on data / projection so handle it that way. You will always work with User and project user to view model you need:
var view = from u in context.Users
select new UserViewModel
{
UserId = u.UserId,
Name = u.Name,
Age = u.Age
};
Make this as reusable method returning IQueryable<UserViewModel> and you can do whatever you want.
Table Per Hierarchy TPH inheritance in entity framework with code first
https://www.youtube.com/watch?v=2i7jahkpeQ8&list=PL6n9fhu94yhUPBSX-E2aJCnCR3-_6zBZx&index=19
please have a look at the following POCOs:
public class Country
{
[Key]
public Guid ID { get; set; }
[Required]
public virtual Currency Currency { get; set; }
}
public class Currency1
{
[Key]
public Guid ID { get; set; }
public virtual ICollection<Country> Countries { get; set; }
}
public class Currency2
{
[Key]
public Guid ID { get; set; }
}
I am not exactly sure what I need navigation properties like the ICollection in Currency1 for. If it comes to EF CodeFirst I see no difference in the database structure created. The tables of Currency1 and Currency2 look pretty much the same to me. So why or when does it make sense to add this extra property?
Of course, just thinking of the POCOs I understand that I can't access any countries from a Currency2 object. For example:
var a = currency1.Countries; // works fine
var b = currency2.Countries; // does not even compile
But is this the only difference? In other words: If I do not need to access countries from a Currency2 object, there is no need to add a corresponding navigation property in the Currency2 class for the purposes of EF? Kind of confused here...
Navigation properties are used either for direct access (as you described) or in linq-to-entities queries. If you don't plan to use it you can remove it from your model. Just be aware that you need a navigation property on at least one side to be able to model database realation using the code first approach.
I have two view models:
public class MasterPageViewModel
{
public string Meta { get; set; }
}
public class Entry : MasterPageViewModel
{
public int EntryID { get; set; }
public string Title { get; set; }
public DateTime PubDate { get; set; }
}
Index page returns a list of entries, so in the view contains:
...Inherits="System.Web.Mvc.ViewPage<IEnumerable<bl.Models.Entry>>"
Then the master page contains
...Inherits="System.Web.Mvc.ViewMasterPage<bl.Models.MasterPageViewModel>"
And here is the error that I am getting:
The model item passed into the dictionary is of type 'System.Linq.EnumerableQuery`1[bl.Models.Entry]', but this dictionary requires a model item of type 'bl.Models.MasterPageViewModel'.
I can easily bypass that error by using ViewData dictionary on the master page, but in my case I would prefer strongly typed approach. In future I want to be able to add lists of categories and tags that would appear on the master page.
I have something like the structure you describe in an MVC site I'm working on now. I haven't found a really satisfying answer -- in many cases it feels like you want two distinct models, one for the master page and one for content pages. Unfortunately, that's not how MVC works.
I've only come across one solution, other than the ViewData option you mentioned.
Base Class
Build a base class that all your models inherit off.
The base class contains all the properties you need in your master page.
Good: it's simple inheritance
Bad: you end up with wrapper methods / models for basic things like passing an enumberable collection.
So in your case you would end up with something like ...
public class MasterPageViewModel {
public string Meta { get; set; }
}
public class Entry : MasterPageViewModel {
public IEnumerable<bl.Models.EntryItem> Items {get; set }
}
public class EntryItem{
public int EntryID { get; set; }
public string Title { get; set; }
public DateTime PubDate { get; set; }
}
And your Index page would look like...
...Inherits="System.Web.Mvc.ViewPage<bl.Models.Entry>"
It's kind of a pain in the butt because you end up with lots of little models. Once I got used to it, however, i've stopped thinking about it.
HTH,
-eric
It seems that to skip a member to come on a View you can set ScaffoldColumn attribute to false in your model
[ScaffoldColumn(false)]
public object Id { get; set; }
but here i see that Id is of object type. Is this the only way? I tried with
[ScaffoldColumn(false)]
public int Id { get; set; }
but it didn't work. How can i prevent scaffolding on a primitive type e.g. int,long etc.
Edit
I have define my model as
public class Department
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[Required(ErrorMessage="Name is required")]
[StringLength(25)]
[DisplayName("Name")]
public string Name { get; set; }
public bool Active { get; set; }
}
I have a controller having action Create. When i right click on create action and select add view and create a strongly type view with this model it creates a view but also adds a textbox for Id
<%: Html.TextBoxFor(model => model.Id)%>
which i suppose it shouldn't have
ScaffoldColumn only changes the behavior of methods like Html.DisplayForModel() which actually use the default templated views system introduced in MVC 2. It does not affect the Visual Studio wizards that ship with MVC.
To change that, you need to edit the T4 templates, somewhat like this.
I wouldn't bother, though. If you want scaffolding in MVC 2, I think it's better to use default templated views than the "Add View" scaffolding, which is code generation.