List of class properties - asp.net-mvc

I have ASP.NET MVC 4 application with one view model class and about 20 views representing this view model. This views differs only by fields which user can edit. I want to merge all that views to one and define list of properties available to editing in strongly-typed manner. Ideally, I want something like this:
// Action
public ActionResult EditAsEngineer(int id)
{
//...
viewModel.PropertiesToChange = new List<???>()
{
v => v.LotNumber,
v => v.ShippingDate,
v => v.Commentary
};
return View(viewModel);
}
// View
if (#Model.PropertiesToChange.Contains(v => v.LotNumber)
{
#Html.TextBoxFor(m => m.LotNumber)
}
else
{
#Model.LotNumber
}
Is it possible to do something like this? Or is there a better solution?
Thank you.

Why note something like this (its pseudo code)
public class Prop{
string PropertyName {get;set;}
bool PropertyEditable {get;set;}
}
public ActionResult EditAsEngineer(int id)
{
viewModel.PropertiesToChange = new List<Prop>()
{
new Prop{PropertyName = LotNumber, PropertyEditable = true}
};
return View(viewModel);
}
#foreach (var pin Model.PropertiesToChange)
{
if(p.PropertyEditable){
#Html.TextBoxFor(p)
}else{
#Html.DisplayFor(p)
}
}

This will solve HALF of your problem. You will also need to create a IEqualityComparer<Expression> for your code to work (the default is to check for ref-equals).
return from p in typeof(T).GetProperties()
let param = System.Linq.Expressions.Expression.Parameter(typeof(T), "x")
let propExp = System.Linq.Expressions.Expression.Property(param, p)
let cast = System.Linq.Expressions.Expression.Convert(propExp, typeof(object))
let displayAttribute = p.CustomAttributes.OfType<System.ComponentModel.DataAnnotations.DisplayAttribute>()
.Select(x => x.Order).DefaultIfEmpty(int.MaxValue).FirstOrDefault()
orderby displayAttribute
select System.Linq.Expressions.Expression.Lambda<Func<T, object>>(cast, new [] {param});
This will list out ALL the properties for T. You would also probabily want to use Expression<Func<T, object>> as the type for defining your list of properties.
This will allow you to create a generic view over all properties.
Also you will want to wrap this in some kind of a cache, as this code is SLOW.

Related

"Object Does not Contain definition for Obtained" ASP.Net MVC [duplicate]

can someone tell me what I'm doing wrong? :-)
I have this simple query:
var sample = from training in _db.Trainings
where training.InstructorID == 10
select new { Something = training.Instructor.UserName };
And I pass this to ViewBag.
ViewBag.Sample = sample;
Then I want to access it in my view like this:
#foreach (var item in ViewBag.Sample) {
#item.Something
}
And I get error message 'object' does not contain a definition for 'Something'. If I put there just #item, I get result { Something = SomeUserName }
Thanks for help.
This cannot be done. ViewBag is dynamic and the problem is that the anonymous type is generated as internal. I would recommend you using a view model:
public class Instructor
{
public string Name { get; set; }
}
and then:
public ActionResult Index()
{
var mdoel = from training in _db.Trainings
where training.InstructorID == 10
select new Instructor {
Name = training.Instructor.UserName
};
return View(model);
}
and in the view:
#model IEnumerable<Instructor>
#foreach (var item in ViewBag.Sample) {
#item.Something
}
If you want to send in ViewData For example and don't want to send in model
you could use the same could as in the upper answer
and in the Controller
enter code here
ViewData[Instractor] = from training in _db.Trainings
where training.InstructorID == 10
select new Instructor {
Name = training.Instructor.UserName
};
and in the view you need to cast this to
`IEnumerable<Instructor>`
but to do this you should use
#model IEnumerable<Instructor>
Then you could do something like this
IEnumerable<instructors> Instructors =(IEnumerable<Instructor>)ViewData[Instractor];
then go with foreach
#foreach (var item in Instructors ) {
#item.Something
}

How to return distinct data to DropDownListFor?

I apply .Net MVC structure with C#. In the controller, I want to distinct specific column (IndustryName), and return the result to Html.DropDownListFor in view. But I get a running time error at view:
System.Web.HttpException: DataBinding: 'System.String' not include
'IndustryName' property.
Is there any one meet such problem, and how to solve it?
Thank you very much for your helping.
Controller:
public ActionResult Create()
{
var industrys = this._pmCustomerService.GetAll().Select (x => x.IndustryName).Distinct();
ViewBag.Industrys = new SelectList(industrys, "IndustryName", "IndustryName", null);
return View();
}
View:
#Html.DropDownListFor(model => model.IndustryName, (SelectList)ViewBag.Industrys)
Your query is returning IEnumerable<string> (you select only the IndustryName property in the .Select() clause. string does not contain an property named IndustryName so you get this error. Just change the SelectList to
ViewBag.Industrys = new SelectList(industrys);
This will bind the options value and display text to the value of IndustryName
The following sample implementation may help you fix the problem:
var industries= this._pmCustomerService.GetAll()
.GroupBy(ind => new { ind.IndustryName})
.Select(group => new SelectListItem
{
Text = group.First().Name,
Value = group .First().Name
}
);
ViewBag.Industries= industries;
You can find more about the 'GroupBy & Select' approach instead of using linq's Distinct(), here
View
#Html.DropDownList("ddlIndustries",(#ViewBag.Industries) as IEnumerable<SelectListItem>)
If you like to use DropDownListFor helper instead then modify view code as follows:
#{
var industries = ViewBag.Industriesas IEnumerable<SelectListItem>;
}
#Html.DropDownListFor(m=> industries , industries )
You get this error because you create SelectList with wrong collection. This should work i think.
var industrys = this._pmCustomerService.GetAll().Select(x => new SelectListItem
{
Value = x.IndustryName,
Text = x.IndustryName
}).Distinct();
ViewBag.Industrys = new SelectList(industrys);
return View();
You are only Selecting IndustryName which is obviously of type String, use DistinctBy() from MoreLinq by Jon Skeet, here is the reference SO post:
public ActionResult Create()
{
var industrys = this._pmCustomerService.GetAll().DistinctBy(x => x.IndustryName);
ViewBag.Industrys = new SelectList(industrys, "IndustryName", "IndustryName", null);
return View();
}

Multiple IEnumerable implement in mvc3 on VIew

#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No").Filterable(true);
})
But i want to do something like that :
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
But its not working can anybody have some idea about it.
Now
if i do something like this it works
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
}
My problem is that APPLICATION_NO property present in both Model class so i don`t want to use
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
Twice in my code.
Your problem is that you are not using when of the most important concepts in MVC: view models.
As for your last comment, what you want to use is a view model, i.e. a class created specifically to send data to the view to show it.
To do so:
create a public class which has the APPLICATION_NO (needed for the view model class)
create another public class which will be your view model. That's a class that shapes the data as you need it on the razor template (in this case, it will hold a list of the class defined in 1)
in your controller, return the view passing the model as second parameter. I.e. don't use the ViewBag/ViewData, but a view model instead, like this return View("ViewName", model)
use the model in your view: declare the model type using #model and use it inside the Razor templae with the provided Model variable
In this way, you shape the data on the server, and avoid including a lot of code in you Razor template (which is not advisable). And, of course, you have intellisense, and your templates become typed.
Code for 1:
public class ApplicationModel
{
public int APPLICATION_NO {get; set;}
}
Code for 2:
public class ApplicationsViewModel
{
public List<ApplicationModel> Applications { get; set; }
}
Code for 3 (inside the controller)
var model = new ApplicationsViewModel();
if (...) // include the condition to choose the kind of data inside the list
{
model.Applications = list.Select(item =>
new ApplicationModel { APPLICATION_NO = item.APPLICATION_NO } ).ToList()
}
else
{
// same code here, for the other kind of data in the list
}
// return the view with this model
return View("ApplicationView", model);
Code for 4:
// decalre the model type at the beginning
#model YourNamespace.ApplicationViewModel;
// access the model using the Model variable
#Html.Grid(Model.Applications).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
This allows you to build MVC applications with several advantages, for example testability, code reusing, readability or availability of a ModelState. Belive me, many of theses things are really,really important, specially the ModelState.
Besides you can use code annotations (attributes that give extra info to the HTML helpers) in your view model, that attributes can provide labels, validation and some other automatic functionality.
Don't doubt to include all the needed properties inside your view model.
Using a view model allows you to create an ad-hoc class without the need to change you domain or business layer classes, i.e. you don't need to use interfaces, code annotations and so on. However many times it's interesting to add the code annotations in the business classes and nest them inside the view models.
Remember tha sometimes you can use the same view model to post the data back to the server, specifying it as the paramter type of your POST action.
By the way, the interface solution is a good one, but this solution doesn't required the interface. (This solution would have a better implementationusing that interface, but that's your choice).
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
The code can't work because data is declared into if blocks.
If the grid has to work only on shared fields of the two classes you can think about using an Interface that APPLICATION and RIGHTS will implement and change the code like this:
#{IEnumerable<BC.Models.IAPPLICATION_NO> data = (IEnumerable<BC.Models.IAPPLICATION_NO>)ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
where IAPPLICATION_NO is an interface like:
public interface IAPPLICATION_NO
{
string APPLICATION_NO { get; }
}
I don't know what APPLICATION_NO is, so I used string and the interface can define only get for grid.
Otherwise, if you need to display different data for those two types you should consider using two views or different grid declaration in the if blocks.
I worked on a sample of my answer on VS:
I attach you the code:
public interface AInterface
{
string AProperty { get; }
}
public class AList : AInterface
{
public string AProperty { get; set; }
}
public class BList : AInterface
{
public string AProperty { get; set; }
}
these are the classes
now the controller:
public class TestController : Controller
{
public ActionResult Index()
{
var random = new Random((int)DateTime.Now.Ticks);
if (random.Next() % 2 == 0)
ViewBag.List = new List<AList> { new AList { AProperty = "Atestvalue" } };
else
ViewBag.List = new List<BList> { new BList { AProperty = "Atestvalue" } };
return View();
}
}
and the view:
#{
ViewBag.Title = "Index";
IEnumerable<TestMvcApplication.Interfaces.AInterface> test = ViewBag.List;
}
<h2>Index</h2>
#foreach (var item in test)
{
<div>
#item.AProperty
</div>
}
this solves your problem as you can see
Without using interfaces:
#{IEnumerable<dynamic> data = (IEnumerable<dynamic>)ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
but you lose the IntelliSense completion and if the member is missing I think you receive a runtime error.
I tried your Grid assembly but it uses c# Expression and it's incompatible with dynamic.
Another solution could be casting one list to another using LINQ in the controller:
IEnumerable<BC.Models.Application> data;
if (some condition)
{
data = applicationList; //applicationList's type is IEnumerable<BC.Models.Application>
}
else
{
data = rightsList.Select(t => new Application { APPLICATION_NO = t.APPLICATION_NO }); //rightsList's type is IEnumerable<BC.Models.RIGHTS>
}
ViewBag.list = data;
In the view you can keep the working code you posted at the top of the question. You have not multitype IEnumerable support because you use only one type but without using a common interface between these classes I think we must go to reflection but I think it's hard to write that code.
Why not
#{string columnName = "default column name";
if(some_condition)
{
// I'm not sure what's going on with the data variable
columnName = "alternate column name";
}
else
{
// Again, do your stuff with the data variable
}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled(columnName);
})
I think that you're going to end up with (at best) confusing code if you try to be too clever in your views. A grid for rendering IEnumerable<A> isn't adaptable to rendering IEnumerable<B> unless A:ISomeInterface and B:ISomeInterface and the grid renders ISomeInterface. Alternatively just pass the column name as a property of the view model.

'object' does not contain a definition for 'CategoryName'

public ActionResult Index()
{
var groups = db.SHP_Products
.GroupBy(c => c.SHP_Category.Name,
(category, items) => new
{
CategoryName = category,
ItemCount = items.Count(),
Items = items
}
);
ViewBag.group = groups.ToList();
return View();
}
When running this it will show an error like this:
<ul>
#foreach (var m in ViewBag.group)
{
<h2>#m.CategoryName</h2>
PreviousNext
<li></li>
}
</ul>
'object' does not contain a definition for 'CategoryName'
You are passing a list of anonymous objects to the View.
Take a look at this answer Dynamic Anonymous type in Razor causes RuntimeBinderException
i think you are trying to access the <h2>#m.CategoryName</h2> directly, may be you can access it like #m.SHP_Category.Name i don't really know you the sequence on class in your code. try #m.
See this answer MVC Razor dynamic model, 'object' does not contain definition for 'PropertyName'
The reason for the error is that the dynamic type created by your GroupBy statement has an access level of "internal", which is not visible to the View. You can rectify by declaring a type or using an Explando - as discussed in this and other answers.
From
The reason for this is that the anonymous type being passed in the controller in internal, so it can only be accessed from within the assembly in which it’s declared. Since views get compiled separately, the dynamic binder complains that it can’t go over that assembly boundary.
One way to solve this is to use System.Dynamic.ExpandoObject.
public static ExpandoObject ToExpando(this object obj)
{
IDictionary<string, object> expandoObject = new ExpandoObject();
new RouteValueDictionary(obj).ForEach(o => expandoObject.Add(o.Key, o.Value));
return (ExpandoObject) expandoObject;
}
Then:
ToExpando(groups); // might need toList() it too.
Please use ViewData instead of ViewBag here like.
Controller:
public ActionResult Index()
{
var groups = db.SHP_Products
.GroupBy(c => c.SHP_Category.Name,
(category, items) => new
{
CategoryName = category,
ItemCount = items.Count(),
Items = items
}
);
ViewData["groups"] = groups.ToList();
return View();
}
View:
<ul>
#foreach (var m in (dynamic) ViewData["groups"])
{
<h2>#m.CategoryName</h2>
PreviousNext
<li></li>
}

ASP.NET MVC show collection of data in View inside of TextArea

I access data with:
public ActionResult Index()
{
//IEnumerable<ChatLogs> c = from p in db.ChatLogs select p;
//return View(c);
using (var db = new ChatLogContext())
{
var list = db.ChatLogs.ToList();
return View(list);
}
}
I would like to know how to save this collection of data inside of TextArea in View? When we used webforms we could just textBox.Text = textBox.Text + "some data from database";
View:
#model IEnumerable<Chat.Models.ChatLogs>
#Html.TextArea("chatScreen", new { #Class = "chatScreen" })
Thank you.
I'd suggest that you create a view model. For example:
class ChatLogsViewModel
{
public string LogListString { get; set; }
}
Pass that to the view, instead of passing the raw list:
var list = db.ChatLogs.ToList();
var vm = new ChatLogsViewModel { LogListString = /* convert list to single string here */ };
return View(vm);
And in the view, just do something like this:
#model Your.Namespace.ChatLogsViewModel
#Html.TextAreaFor(model => model.LogListString)
Using view models will make your life easier as soon as you decide that you want to pass more information to the view than what a single domain model can carry.
In you .cshtml view, you can access data using #Model
Now, since you have a list, I'd recommend you join it and then assign it to TextArea like
#{var strList = string.Join(" ", Model)}
#Html.TextArea("myTextArea",strList)

Resources