In my MVC2 AdminArea I'd like to create an overview table for each of my domain models.
I am using DataAnnotations like the following for the properties of those domain model objects:
[DisplayName("MyPropertyName")]
public string Name { get; set; }
Now my question is: How can I access the DisplayName Attribute if my view receives a collection of my domain models? I need this to build the table headers which are defined outside of the usual
<% foreach (var item in Model) { %>
loop. Inside this loop I can write
<%: Html.LabelFor(c => item.Name) %>
but is there any way to access this information using the collection of items instead of a concrete instance?
Thanks in advance!
There is a ModelMetaData class that has a static method called FromLambdaExpression. If you call it and pass in your property, along with your ViewData, it will return an instance of ModelMetaData. That class has a DisplayName property that should give you what you need. You can also get other meta data information from this object.
For example, you can create an empty ViewDataDictionary object to get this information. It can be empty because the ModelMetaData doesn't actually use the instance, it just needs the generic class to define the type being used.
//This would typically be just your view model data.
ViewDataDictionary<IEnumerable<Person>> data = new ViewDataDictionary<IEnumerable<Person>>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.First().Name, data);
string displayName = result.DisplayName;
The First() method call doesn't break even if you have no actual Person object because the lambda is simply trying to find the property you want the meta data about. Similarly, you could d this for a single Person object:
//This would typically be just your view model data.
ViewDataDictionary<Person> data = new ViewDataDictionary<Person>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.Name, data);
You could clean this up significantly with a helper or extension method, but this should put you on the right path.
Alright, I followed sgriffinusa's advise (thanks again!) and created a strongly typed HtmlHelper:
public static MvcHtmlString MetaDisplayName<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
return MvcHtmlString.Create(metadata.GetDisplayName());
}
Of course TModel still is a collection of domain models like stated in my inital question but we can call the helper in the view like this:
<%: Html.MetaDisplayName(p => p.First().Name) %>
Related
I am working on an asp.net mvc core 2.0 project and entityframework.
I am working with viewmodel concept.
This mean i do not pass entities instances directly to my view, i pass a viewmodel instance which only contains what the view needs.
in one case, i need to pass the entire entity fields plus other informations.
What i've done is a derivated my ViewModel class from my Entity Class. and i add the extra fields:
public MyViewModel: Person
{
// I will automaticly have Person fields in MyViewModel
public bool IsSelected {get;set;}
public String otherinformation {get;set;}
...
}
I am fed up with assigning each fields of my viewmodel from my entity instance.
myviewmodelinstance.field1 = myentity.field1;
myviewmodelinstance.field2 = myentity.field2;
myviewmodelinstance.field3 = myentity.field3;
myviewmodelinstance.IsSelected = false;
...
And i need to do the inverse operation in the postback.
Is there a way to "copy" or "clone" every fields, like this:
myentity.CopyTo(myviewmodelinstance);
myviewmodelinstance.IsSelected = false;
Thanks
You could use the AutoMapper to achieve it. It is the ideal way to perform it. Another option is that you can serialize and deseriliaze the object as json.
var myviewmodelinstance = JsonConvert.DeserializeObject<MyViewModel>(JsonConvert.SerializeObject(myentity));
Note : The code I provided required Json.Net
I'm using entity framework with POCOs and the repository pattern and am wondering if there is any way to filter a child list lazy load. Example:
class Person
{
public virtual Organisation organisation {set; get;}
}
class Organisation
{
public virtual ICollection<Product> products {set; get;}
}
class Product
{
public bool active {set; get;}
}
Currently I only have a person repository because I'm always starting from that point, so ideally I would like to do the following:
Person person = personRepo.GetById(Id);
var products = person.organisation.products;
And have it only load products where active = true from the database.
Is this possible and if so how?
EDIT My best guess would be either a filter can be added to the configuration of the entity. Or there might be a way to intercept/override the lazy load call and modify it. Obviously if I created an Organisation Repository I could manually load it as I please but I am trying to avoid that.
There's not a direct way to do this via lazy loading, but if you were willing to explicitly load the collection, you could follow whats in this blog, see the Applying filters when explicitly loading related entities section.
context.Entry(person)
.Collection(p => p.organisation.products)
.Query()
.Where(u => u.IsActive)
.Load();
You can do what Mark Oreta and luksan suggest while keeping all the query logic within the repository.
All you have to do is pass a Lazy<ICollection<Product>> into the organization constructor, and use the logic they provided. It will not evaluate until you access the value property of the lazy instance.
UPDATE
/*
First, here are your changes to the Organisation class:
Add a constructor dependency on the delegate to load the products to your
organization class. You will create this object in the repository method
and assign it to the Person.Organization property
*/
public class Organisation
{
private readonly Lazy<ICollection<Product>> lazyProducts;
public Organisation(Func<ICollection<Product>> loadProducts){
this.lazyProducts = new Lazy<ICollection<Product>>(loadProducts);
}
// The underlying lazy field will not invoke the load delegate until this property is accessed
public virtual ICollection<Product> Products { get { return this.lazyProducts.Value; } }
}
Now, in your repository method, when you construct the Person object you will assign the Organisation property with an Organisation object containing the lazy loading field.
So, without seeing your whole model, it will looks something like
public Person GetById(int id){
var person = context.People.Single(p => p.Id == id);
/* Now, I'm not sure about the cardinality of the person-organization or organisation
product relationships, but let's assume you have some way to access the PK of the
organization record from the Person and that the Product has a reference to
its Organisation. I may be misinterpreting your model, but hopefully you
will get the idea
*/
var organisationId = /* insert the aforementioned magic here */
Func<ICollection<Product>> loadProducts = () => context.Products.Where(product => product.IsActive && product.OrganisationId == organisationId).ToList();
person.Organisation = new Organisation( loadProducts );
return person;
}
By using this approach, the query for the products will not be loaded until you access the Products property on the Organisationinstance, and you can keep all your logic in the repository. There's a good chance that I made incorrect assumptions about your model (as the sample code is quite incomplete), but I think there is enough here for you to see how to use the pattern. Let me know if any of this is unclear.
This might be related:
Using CreateSourceQuery in CTP4 Code First
If you were to redefine your properties as ICollection<T> rather than IList<T> and enable change-tracking proxies, then you might be able to cast them to EntityCollection<T> and then call CreateSourceQuery() which would allow you to execute LINQ to Entities queries against them.
Example:
var productsCollection = (EntityCollection<Product>)person.organisation.products;
var productsQuery = productsCollection.CreateSourceQuery();
var activeProducts = products.Where(p => p.Active);
Is your repository using something like:
IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> expression)
If so you can do something like this:
var person = personRepo.Find(p => p.organisation.products.Any(e => e.active)).FirstOrDefault();
You could possibly use Query() method to achieve this. Something like:
context.Entry(person)
.Collection(p => p.organisation.products)
.Query()
.Where(pro=> pro.Active==true)
.Load();
Have a look at this page click here
I have an Html helper with the following signature:
public static MvcHtmlString UiAutoCompleteForWithId<TModel, TProperty>(this htmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
I understand how to get my hands on the value of the passed in member and it's metadata, but is there anyway to access the containing Model, or more specifically the value of a particular sibling member (a property) of the passed in member by name?
Cheers,
Matthew
Edit: OK I think I can do this using the ModelMetadata.FromStringExpression method (sometimes it takes asking before you see it eh?), but I is this the best way to go about this?
If you need to access the value of a sibling member it means that you assume that the view model has this sibling member. This means that your html helper no longer needs to be generic. You could do this:
public static MvcHtmlString UiAutoCompleteForWithId<TProperty>(
this HtmlHelper<MyViewModel> helper,
Expression<Func<MyViewModel, TProperty>> expression,
object htmlAttributes
)
{
MyViewModel model = helper.ViewData.Model;
var value = model.SomeOtherSiblingProperty;
// TODO: do something with this property
...
}
Or if your view models implement some common base interface that contains the sibling member in question you could specify a generic constraint:
public static MvcHtmlString UiAutoCompleteForWithId<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes
) where TModel: ISomeInterface
{
ISomeInterface model = helper.ViewData.Model;
var value = model.SomeOtherSiblingProperty;
// TODO: do something with this property
...
}
I'm having some problems with parsing in some data to a PartialView.
When parsing in a Dictionary the properties Values and Keys is set in the ViewData ...
How can I merge the Dictionary with the ViewData ... so I can access my Dictionary items with the Keys like this:
ViewData["key"] as IList<T>;
Instead of
ViewData["Values] <- Which is a List that Contains my list.
I'm going to use it like this ... just dont want anonymous/magic string names.
<%: Html.EditorFor(x => x.GroupId, "SimpleSelectList", new { Selected = 10}) %>
I'm hoping to do something like this.
<%: Html.EditorFor(x => x.GroupId, "SimpleSelectList", Html.AddViewData(Model.List)) %>
With this extension method:
public static IDictionary AddViewData<T>(this HtmlHelper helper, T item)
{
var dictionary = new Dictionary<string, object>();
dictionary.Add(typeof(T).Name, item);
return dictionary;
}
Then I will always know what the SimpleSelectList template should look for ... and dont have to depends on yet again another magic string ...
Or how do people do this? Just trying to get into the code base and how people do this kind of thing ...
Personally I wouldn't use ViewData atall in this context.
It's a lot cleaner and clearer to have a view model that impliments your dictionary as a property. You can then pass this view model to your view, and to your partial view...or just part of the view model to your partial view (depending on what data you need).
There are a few related questions, but I can't find an answer that works.
Assuming I have the following models:
public class EditorViewModel
{
public Account Account {get;set;}
public string SomeSimpleStuff {get;set;}
}
public class Account
{
public string AccountName {get;set;}
public int MorePrimitivesFollow {get;set;}
}
and a view that extends ViewPage<EditorViewModel> which does the following:
<%= Html.TextBoxFor(model => model.Account.AccountName)%>
<%= Html.ValidationMessageFor(model => model.Account.AccountName)%>
<%= Html.TextBoxFor(model => model.SomeSimpleStuff )%>
<%= Html.ValidationMessageFor(model => model.SomeSimpleStuff )%>
and my controller looks like:
[HttpPost]
public virtual ActionResult Edit(EditorViewModel account)
{ /*...*/ }
How can I get the DefaultModelBinder to properly bind my EditorViewModel? Without doing anything special, I get an empty instance of my EditorViewModel with everything null or default.
The closest I've come is by calling UpdateModel manually:
[HttpPost]
public virtual ActionResult Edit(EditorViewModel account)
{
account.Account = new Account();
UpdateModel(account.Account, "Account");
// this kills me:
UpdateModel(account);
This successfully updates my Account property model, but when I call UpdateModel on account (to get the rest of the public properties of my EditorViewModel) I get the completely unhelpful "The model of type ... could not be updated." There is no inner exception, so I can't figure out what's going wrong.
What should I do with this?
The binder is getting confused because it sees that the parameter to your action method is named account, and it sees incoming form fields named account.accountname, so it's looking for an AccountName property on your EditorViewModel.
You can fix this by renaming the parameter to something else that doesn't conflict with an incoming form field, or you can stick an [Bind(Prefix = "")] attribute on the parameter. This attribute says "ignore the fact that the parameter is named account, and pretend I had given it an empty string name instead." Then the binder will look for account.accountname instead of account.account.accountname.
Edit - further info:
When the binder sees a complex parameter named foo, it looks at the current request for anything named *foo.**. So if your parameter were named foo and its type had a property named FirstName, the incoming value would be expected to be foo.FirstName=John, for example.
However, if the binder does not see a *foo.** as part of the request, it just looks for the * directly (without the foo prefix). So as long as there wasn't a *foo.** present in the request, you could submit FirstName=John and the binder would understand this correctly. But if there is any *foo.** as part of the request, the FirstName=John value will not match the FirstName property.
You can see now how giving the parameter to your action method the same name as one of its properties would throw off this logic.