I have some interface and classes
public inteface IRole
{
int Id { get; }
string Name { get; set; }
}
public class Role : IRole
{
public int Id { get; }
[Display("Role Name")]
public string Name { get; set; }
}
public class Member
{
[Display("Login")]
public string Login { get; set; }
[Display("Password")]
public string Password { get; set; }
public IRole Role { get; set; }
}
on View I try to use, View is strongly type of Member
on this line displays correct message from DisplayAttribute
<%= Html.LabelFor(m => m.Login) %>
on this line it does not display correct label from DisplayAttribute
<%= Html.LabelFor(m => m.Role.Name) %>
How can I fix this to have correct labels in such architecture?
Thanks
P.S. I know about adding DisplayAttribute to the interface field and all will work, but maybe there are different solution.
Everything is working as designed here.
You already know your answer. If you display a form for IRole you must also have the attributes on IRole.
In order to have the correct labels you'd have to implement your own TypeDescriptors or ModelMetadataProvider in order to "smush" together the metadata for an interface into any concrete classes that implement them.
This will become really complex real fast.
Why can't you just add the attribute to the IRole interface?
Related
I have an ASP.NET MVC application that is using a single view to display the properties and children (with their properties) of a model entity.
My model looks something like this:
public class Market
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<EmailAddress> EmailAddresses { get; set; }
}
public class EmailAddress
{
public virtual int ID { get; set; }
public virtual int MarketID { get; set; }
public virtual string Email { get; set; }
}
On the view, I want to use a table to display the list of related email addresses. To do this, I am using Html.Grid.
<%= Html.Grid(Model.EmailAddresses).Columns( column =>
{
column.For(x => x.Email + Html.Hidden("ID", x.ID)).Encode(false).Sortable(false);
})
.Attributes(style => "width:100%")
.Attributes(id => "emailGrid")
.Empty("There are no Email Addresses set up") %>
However, when I do this, the hidden ID is that of the parent entity Market, not that of the EmailAddress.
How do I remedy this?
It seems it could be a bug in the WebGrid. Have you tried renaming your ID field in the EmailAddress class, e.g. EmailID and pass that to the WebGrid and see if it displays correctly?
This works for me, could it be that you have something wrong in the filling of your model?
Since you're using the lambda expression for the Column.For() method, the x parameter is re referring to a single email. I think you mean to refer to the Model, not a single email ID
Instead of doing x.ID, I think you just want Model.ID
Here's a sample of how it works now:
[MetadataType(typeof(PersonMetadata))]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public class PersonMetadata
{
[Required]
public string Name { get; set; }
[Range(0,150]
public int Age { get; set; }
}
However I don't want the MetadataAttribute to be put on the Person class, instead I want some way of telling the MVC framework "hey if you encounter Person class the metadate is in the PersonMetadata class".
It's just reversing the direction which for me feels more compositional and following the Open-Close Principle since I don't have to touch the Person class to "extend" it with metadata.
I know this creates a problem of possible multiple and conflicting metadatas being attached, also increases complexity but flexibility always comes at a price.
I want either a programmatic or declarative way or preferably both :)
So one could be something like :
MetadataTypeMappings.Add(typeof(Person), typeof(PersonMetadata));
Another could be:
[MetadataFor(typeof(Person)]
public class PersonMetadata
{
[Required]
public string Name { get; set; }
[Range(0,150]
public int Age { get; set; }
}
I'm using MvcExtensions. One of the features is a nice way to describe metadata like this:
public class ProductEditModelConfiguration :
ModelMetadataConfiguration<ProductEditModel>
{
public ProductEditModelConfiguration()
{
Configure(model => model.Id).Hide();
Configure(model => model.Name)
.DisplayName(() => LocalizedTexts.Name)
.Required(() => LocalizedTexts.NameCannotBeBlank)
.MaximumLength(64, () =>
LocalizedTexts.NameCannotBeMoreThanSixtyFourCharacters);
}
}
Neat thing is - this allows to use resources for localization in strongly typed fashion.
Drawback for using this - there's bunch of other things bundled which You might not want to use.
Perhaps I'm missing something here, but it seems that anything in the object model tree 3 or more levels down, is ignored when using TryUpdateModel.
For example (simplified):
public virtual ActionResult SomeAction(int id, FormCollection form)
{
IValueProvider vpFrom = form.ToValueProvider();
/*
At this stage, vpForm contains:
1)PropertyA
2) PropertyB.SubPropertyA
3) PropertyB.SubPropertyB.SubSubPropertyA
*/
TryUpdateModel(someObjectModel, null, null, null, vpFrom);
//The first two properties are applied, number (3) seems to be ignored
Am I missing something here? If this is just the way it is, has anyone come up with a workaround?
A quick project created with the following model.
public class TestModel {
public TestModelA A { get; set; }
public string Name { get; set; }
}
public class TestModelA {
public TestModelB B { get; set; }
public string Name { get; set; }
}
public class TestModelB {
public TestModelC C { get; set; }
public string Name { get; set; }
}
public class TestModelC {
public TestModelD D { get; set; }
public string Name { get; set; }
}
public class TestModelD {
public TestModelE E { get; set; }
public string Name { get; set; }
}
public class TestModelE {
public string Name { get; set; }
}
Here's my edit - which is essentially the same as yours
[HttpPost]
public ActionResult Edit(FormCollection form) {
IValueProvider vpFrom = form.ToValueProvider();
Models.TestModel t = new Models.TestModel();
TryUpdateModel(t, null, null, null, vpFrom);
return View(t);
}
This all works exactly as expected with all the models created properly. The only problem that I can see happening is that you possibly aren't passing the same property names back from the form. (by not using <%: Html.TextBoxFor(model => model.A.B.C.CName)%> for example)
The models require parameterless constructors. But I'm sure you would have gotten an error about that - unless you're consuming the error.
So without more information about your project it will be hard to help as a basic setup produces expected results.
I believe the problem is in one of your model classes. Check, please, if PropertyB.SubPropertyB.SubSubPropertyA is really a property but not a field. A property should have get and set accessors.
Here's my checklist:
Make sure you're getting the value back in the form request. Request["A.B.C.Name"] and etc.
All the required fields are on the form.
I had deleteOnNull issue with Linq to SQL: How to set DeleteOnNull from designer for future ref if you're using L2SQL.
i'd like to know, I have a application in asp.net mvc and nhibernate. I've read about that in the Views on asp.net mvc, shouldn't know about the Domain, and it need use a DTO object. So, I'm trying to do this, I found the AutoMapper component and I don't know the correct way to do my DTOS, for some domain objects. I have a domain class like this:
public class Entity
{
public virtual int Id { get; set; }
public virtual bool Active { get; set; }
}
public class Category : Entity
{
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
public Category() { }
}
public class Product : Entity
{
public virtual string Name { get; set; }
public virtual string Details { get; set; }
public virtual decimal Prince { get; set; }
public virtual int Stock { get; set; }
public virtual Category Category { get; set; }
public virtual Supplier Supplier { get; set; }
public Product() { }
}
public class Supplier : Entity
{
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
public Supplier() { }
}
I'd like to get some example of how can I do my DTOs to View ? Need I use only strings in DTO ? And my controllers, it should get a domain object or a DTO and transform it on a domain to save in repository ?
Thanks a lot!
Cheers
There is no guidelines on this matter and it depends on your personal chice. I have few advices that have proven useful in practice:
1. Use flat DTOs - this means that the properties of the DTO must be as primitive as possible. This saves you the need for null reference checking.
For example if you have a domain object like this:
public class Employee
{
prop string FirstName{get; set;}
prop string LastName{get; set;}
prop Employee Boss{get; set;}
...
}
And you need to output in a grid a list of employees and display information for their 1st level boss I prefer to create a DTO
public class EmployeeDTO
{
prop string FirstName{get; set;}
prop string LastName{get; set;}
prop bool HaveABoss{get;set}
prop string BossFirstName{get; set;}
prop string BossLastName{get; set;}
...
}
or something like this (-:
2. Do not convert everything to sting - this will bind the DTO to a concrete view because you'll apply special formatting. It's not a problem to apply simple formatting directly in the view.
3. Use DTOs in your post actions and than convert them to domain objects. Usually controller's actions are the first line of deffence against incorrect data and you cannot expect to be able to allways construct a valid domain object out of the user's input. In most cases you have to do some post-processing like validation, setting default values and so on. After that you can create your DTOs.
i'd like to know how can I get a property like an entity, for example:
My Model:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
}
View:
Name: <%=Html.TextBoxFor(x => x.Name) %>
Category: <%= Html.DropDownList("Category", IEnumerable<SelectListItem>)ViewData["Categories"]) %>
Controller:
public ActionResult Save(Product product)
{
/// produtct.Category ???
}
and how is the category property ? It's fill by the view ? ASP.Net MVC know how to fill this object by ID ?
Thanks!
This is one of the reasons why it's bad to bind directly to entities. Consider
public class ProductForm {
public int Id { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
}
public ActionResult Save(ProductForm form)
{
var product = new Product
{
Id = form.Id,
Name = form.Name,
Category = database.GetCategory(form.CategoryId)
};
}
In case of view models as above, it may be OK to use custom model binders to automatically get entities by Id from database. See here for sample implementation (in S#arp Architecture) that binds IDs to entities from database. But I think for now you better go with simpler implementation like above.
You can also use AutoMapper to simplify form->entity mapping.