For web application in development(ASP.Net MVC), I'm using the telerik grid. The grid is bound to an IQueryable of my list, because it's a big table, and I want that telerik apply it's filter on the list, and then executes this result, not dowloading 10'000 rows(with the joined tables), and then with the filter, use only rows.
I'm using(and I really need it for this page, it's one of the key feature) the filter/order of the grid.
One of the main column(determining the kind of the data) is an enum.
The problem is that I get a "Specified type member is not supported in linq to entities" as soon as I'm trying to filter/sort it.
I've to bind it on the enum(and not the mapped int) because if I use the id, filters/order by will be on an int, and I can't expect that the user knows the id of the foreign table.
I just cannot implement myself again all grids parameter(located in url)(I assume, it's either I do everything, or nothing) and filter it correctly, order it correctly).
Do you have an idea of workaround?
I don't know how your Entity Model looks like but I'll suppose that you've something like this Model:
public partial class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
}
and the Status property represents your enum value then you've this enum:
public enum ProjectStatuses
{
Current = 1,
Started = 2,
Stopped = 3,
Finished = 4,
}
Then just create new ViewModel like this :
public class ProjectDetails
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public ProjectStatuses StatusValue { get { return (ProjectStatuses) Status; } }
// This property to display in telerik ClientTemplate
public string StatusName { get { return Enum.GetName(typeof (ProjectStatuses), Status ); } }
}
And because I love Extension Methods I'll add this one :
public static class ModelListExtensions
{
public static IQueryable<ProjectDetails> ToViewModelDetails(this IQueryable<Project> modelList)
{
return modelList.Select(m => new ProjectDetails
{
Id = m.Id,
Name = m.Name,
Status = m.Status,
};
}
}
Update :
Here is the Controller
public ActionResult Index()
{
int total;
var viewModel = getGridList(out total);
ViewBag.Total = total;
return View(viewModel);
}
//this Action to get ajax pages
[GridAction(EnableCustomBinding = true)]
public ActionResult ReGetIndex(GridCommand command, int roleId)
{
int total;
var list = getGridList(out total, roleId, command);
return View(new GridModel {Data = list, Total = total});
}
private IEnumerable<ProjectDetails> getGridList(out int total, GridCommand command = null)
{
command = command ?? new GridCommand {Page = 1};
foreach (var descriptor in command.SortDescriptors)
{
if (descriptor.Member == "StatusValue")
descriptor.Member = "Status";
}
foreach (FilterDescriptor descriptor in command.FilterDescriptors)
{
if (descriptor.Member == "StatusValue")
descriptor.Member = "Status";
}
var list = modelService.AllAsQuery()
.ToViewModelDetails() // To convert it to our ViewModel if we have one
.Where(command.FilterDescriptors);
total = list.Count();
return (IEnumerable<ProjectDetails>) list.Sort(command.SortDescriptors)
.Page(command.Page - 1, command.PageSize)
.GroupBy(command.GroupDescriptors).ToIList();
}
And this is the View
#model IEnumerable<ProjectDetails>
#{
Html.Telerik()
.Grid(Model)
.Name("ProjectsGrid")
.Sortable()
.Filterable()
.EnableCustomBinding(true)
.DataBinding(dataBinding => dataBinding
.Ajax()
.Select("ReGetIndex", "Projects"))
.Pageable(page => page.Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric).Total(ViewBag.Total))
.Columns(column =>
{
column.Bound(m => m.Id).Hidden(true);
column.Bound(m => m.Name);
column.Bound(m => m.StatusValue).ClientTemplate("<#= StatusName #>");
})
.Render();
}
Update :
If you want to enforce at least one sort order you could use something like this:
if (!command.SortDescriptors.Any())
{
command.SortDescriptors.Add(new SortDescriptor {Member = "YourDefaultProperty"});
}
You don't really have choice (or few annoying choices)
Wether you use a class instead of enum (but if you used an enum, that's because it was better).
Or you "pseudo-sort" your enum, and use the mapped int.
public enum TT
{
Brown = 0,
Green = 1
}
Of course, you'll have to check the actual datas (mapped int) in your DB and update them to conform to the new order (can't change enum order without impact). And you'll have to do that everytime you want to insert a value between existing enum values.
Or you wait for next EF / linq / c# version, which should have enum support in linq2entities
Related
Am trying to Bind Dropdown in MVC using Enum and it works great !! Here is sample code
//Enum Set up
public enum Name
{
Name1,
Name2,
Name3
}
public enum Number
{
11,
12,
13
}
public Name nameDropDown { get; set; }
public Number numberDropDown { get; set; }
//Like this i have have 10 Enums
//View
#Html.EnumDropDownListFor(m => m.numberDropDown) or #Html.EnumDropDownListFor(m => m.nameDropDown)
Here is where i got struck !!I am going to load only one dropdown in my view based on the condition. I can have if else if and load the drop down based on the condition. But as i have large list it wont be that good.
Is there a alternative way i can decide which enum to bind in my view ? Like dynamically locating based on the condition. Please suggest
There is no easy way to achieve this. One way would be to create an extension method, something like:
public enum Name { Name1, Name2, Name3 }
public enum Number { Number1, Number2, Number3 }
public class ViewModel
{
public Name nameDropDown { get; set; }
public Number numberDropDown { get; set; }
// x number of other enum properties
}
public static class MyHtmlHelpers
{
public static MvcHtmlString MyEnumDropDownListFor(this HtmlHelper html, string propertyName)
{
var propertyInfo = typeof(ViewModel).GetProperty(propertyName);
if (propertyInfo == null) return null;
var expParam = Expression.Parameter(typeof(ViewModel));
var expProp = Expression.Property(expParam, propertyName);
var expression = Expression.Lambda(expProp, expParam);
var htmlStr = (MvcHtmlString) typeof(SelectExtensions)
.GetMethods()
.First(m => m.Name == "EnumDropDownListFor" && m.IsGenericMethod)
.MakeGenericMethod(typeof(ViewModel), propertyInfo.PropertyType)
.Invoke(null, new object[] { html, expression });
return htmlStr;
}
}
And your view could fetch the property name from the query string and based on that show the dropdown list:
#model ViewModel
#{
var propertyName = Request["property"] ?? "numberDropDown";
}
#Html.MyEnumDropDownListFor(propertyName)
I am fairly new to MVC, and I've been reading a bit about ViewModels, but how do I go about sending two models to my View, where the queries are like so
public ActionResult Index(int Id)
{
var People = from a in db.Person
select a;
var Data = from a in db.Member
where a.Person.PersonId.Equals(Id)
select new
{
a.Project.ProjectId,
a.Project.Name,
a.Project.Customer,
a.Project.TechProfile.Select(x => new
{
x.TechId,
x.Name,
x.Elements
}),
a.MemberId,
a.Role,
a.Start,
a.End
};
return View(People);
}
I was using #model IQueryable<GeoCV.Models.Person> before so I could use a #foreach in my View but I don't know how to get my other query to the View so I can get data from it too.
Update
And I'm making a custom class for my Data query, but I don't know how to set the property of TechProfile
Right now I have
public IEnumerable<TechProfile> ProjectTechProfile { get; set; }
In my custom class, but it doesn't work, so I guess I have to specify TechId, Name and Elements?
But how?
A ViewModel wraps around the 2 models you are getting with your 2 queries, so you can return it as a single object to your view. In your case we need to adress another issue first. You are returning an anonymous object in your data query.
This means, your data query needs to return a strongly typed object instead of an anonymous object.
Create a class for your data query:
public class MyCustomDataObject
{
public int ProjectId { get; set; }
//... map all properties as needed
}
then edit your data query to return this object:
var Data = from a in db.Member
where a.Person.PersonId.Equals(Id)
select new MyCustomDataObject
{
ProjectId = a.Project.ProjectId,
//assign all properties
};
Now you need to create the actual ViewModel class:
public class MyViewModel
{
public IEnumerable<Person> Persons { get; set; }
public IEnumerable<MyCustomDataObject> Data { get; set; }
}
And after this you just need to assign the values to it in your Actionmethod:
public ActionResult Index(int Id)
{
var People = from a in db.Person
select a;
var Data = from a in db.Member
where a.Person.PersonId.Equals(Id)
select new MyCustomDataObject
{
ProjectId = a.Project.ProjectId,
//...
};
//store data of both queries in your ViewModel class here:
var vm = new MyCustomDataObject();
vm.Persons = People;
vm.Data = Data
//return ViewModel to View.
return View(vm);
}
And then declare it in your view: #model Namespace.Subfolder.MyCustomDataObject
You can use #Html.Action("actionName","controllerName") method in view. You can divide your original view into multiple partial view and then you can render that partial view with dynamic model binding using #Html.Action("actionName","controllerName") method.
For more details with sample code http://devproconnections.com/development/how-use-aspnet-mvc-render-action-helpers
You can have methods like below to get multiple model in single view
private IList<People> GetPeople()
{
return from a in db.Person
select a;
}
private IList<Data> GetData()
{
return from a in db.Member
where a.Person.PersonId.Equals(Id)
select new
{
a.Project.ProjectId,
a.Project.Name,
a.Project.Customer,
a.Project.TechProfile.Select(x => new
{
x.TechId,
x.Name,
x.Elements
}),
a.MemberId,
a.Role,
a.Start,
a.End
};
}
public ActionResult Index(int Id)
{
var MultipleModel = new Tuple<IList<People>,IList<Data>>(GetPeople(),GetData()) { };
return View(MultipleModel);
}
Here's a codeproject tutorial on the subject.
I'm trying to set up a Dropdown list in Kendo UI Grid using HTML helpers.
When I click to edit the grid, the dropdown appears and I can select a value. However, this does not save in the database when I click update (though the simple string WordName field does).
I would also like the CatId value from the WordViewModel to also be displayed as a word/dropdown when you're not editing the fields.
As far as I can tell, I have nothing which links the int CatId to the GetCategories list. How do I go about connecting those two? I've read a little about column.ForeignKey, but I don't understand it. Below is all my relevant code.
My WordViewModel (which loads from a similar, slightly more complex database model)
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public Nullable<int> CatId { get; set; }
}
My Category model (generated by the database)
public partial class Category
{
public Category()
{
this.Words = new HashSet<Word>();
}
public int CatId { get; set; }
public string CategoryName { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Word> Words { get; set; }
}
Here's my razor code for the grid in Index.cshtml
#(Html.Kendo().Grid<WordViewModel>
()
.Name("wordGrid")
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(word => word.WordId); // Specify the property which is the unique identifier of the model
model.Field(word => word.WordId).Editable(false); // Make the ID property not editable
})
.Read(read => read.Action("Read", "Words")) //Populate the grid with Words
.Update(update => update.Action("Update", "Words")) // Action invoked when the user saves an updated data item
)
.Editable(editable => editable.Mode(GridEditMode.InLine)) // Use inline editing mode
.Columns(columns =>
{
columns.Bound(c => c.WordName);
columns.Bound(c => c.CatId).EditorTemplateName("_CategoryDropdown"); //link to EditorTemplate with the same name
columns.Command(commands =>
{
commands.Edit(); // The "edit" command will edit and update data items
}).Title("Commands").Width(200);
})
.Filterable()
)
Editor Template _CategoryDropdown.cshtml
#(
Html.Kendo().DropDownList()
.Name("Category") //is this Name important?
.DataTextField("CategoryName")
.DataValueField("CategoryId")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
My function to get the drop down list from the database.
public JsonResult GetCategories()
{
var items = db.Categories.ToList().Select(c => new Category
{
CatId = c.CatId,
CategoryName = c.CategoryName
});
return Json(items, JsonRequestBehavior.AllowGet);
}
Here is a working solution. Rather than using column.ForeignKey, I ended up manually connecting the CatId with the CategoryName and including both in the WordViewModel.
My final files:
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public string CategoryName { get; set; } //I added this field which is actually displayed on the grid
public Nullable<int> CatId { get; set; } //only used temporarily to transfer data
}
I did not end up referencing the Category model almost at all.
In my Grid I changed the binding on CategoryId to bind on CategoryName instead. Essentially with my solution, I only referenced Category Name in the view, and just matched up CategoryName with CategoryId in the Read/Update functions in the controller.
//The Title string below needs to be the same as the Name field in the EditorTemplate and possibly the same as the name in the model
columns.Bound(c => c.CategoryName).Title("CategoryName").EditorTemplateName("_CategoryDropdown");
The location of this file is important.
Views/Shared/EditorTemplates/_CategoryDropdown.cshtml:
#(
Html.Kendo().DropDownList()
.Name("CategoryName") //This name has to be the same as the Title on the main grid page
.DataTextField("CategoryName")
.DataValueField("CategoryName")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
The Words/GetCategories function was correct.
I had to do some work in Words/Read to get the category name from the category ID
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Select(w => new WordViewModel
{
WordId = w.WordId,
CatId = w.CatId,
CategoryName = "",
WordName = w.WordName
}).ToList(); //need .ToList to be able to iterate through it
//finish building the word
foreach(var item in items)
{
if(item.CatId!=null)
{
//add CategoryName corresponding to each CatId
//In my database I have a table for Categories which matches up CatId to CategoryName
Category cat = db.Categories.Select(c => c).Where(c => c.CatId == item.CatId).FirstOrDefault();
item.CategoryName = cat.CategoryName;
}
}
return Json(items.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
and some stuff in Words/Update to do the reverse Name->Id:
public ActionResult Update([DataSourceRequest]DataSourceRequest request, [Bind(Prefix = "models")] WordViewModel word)
{
if (ModelState.IsValid)
{
// Create a new Product entity and set its properties from the posted ProductViewModel
var entity = new Word
{
WordId = word.WordId,
CategoryName = word.CategoryName,
WordName = word.WordName
};
if (word.CategoryName != "")
{
//match CategoryWord to CatID
Category cat = db.Categories.Select(c => c).Where(c => c.CategoryName == word.CategoryName).FirstOrDefault();
entity.CatId = cat.CatId;
}
// Attach the entity
db.Words.Attach(entity);
// Change its state to Modified so Entity Framework can update the existing product instead of creating a new one
db.Entry(entity).State = EntityState.Modified;
// Update the entity in the database
db.SaveChanges();
}
// Return the updated product. Also return any validation errors.
return Json(new [] { word }.ToDataSourceResult(request, ModelState));
}
There might be some minor errors since this is a little simplified from my real code, but all the important pieces are there. Figuring out all the linkages and what I could depend on Kendo for vs what I had to manually was pretty difficult to figure out. Good luck to anyone else trying to use Kendo Grid, and I hope this example helps!
Nullable CatId is the problem. Check out the fix here Kendo MVC dropdown lists inside inline Kendo MVC grids. Second option is following but this one only works with InLine.
function onSave(e) {
// kendo nullable dropdown bug workaround
$("#wordGrid tbody [data-role=dropdownlist]").each(function () {
var kd = $(this).data("kendoDropDownList");
if (kd) {
var v = kd.value();
var p = kd.list.attr('id').replace('-list', '');
if(p) e.model.set(p, v);
}
})
}
There are also a suggestion to use default value but it never worked for me. see here
I have a table where I store all the different code/value keywords that I need in my app:
public class Keyword
{
public int id { get; set;}
public string name { get; set; }
public string valuecode { get; set; }
public string valuename { get; set; }
}
Then I use Keyword to store records like these
name valuecode valuename
.DealState 1 Draft
.DealState 2 Final
.DealState 3 Cancelled
.DealType NEW New Business
.DealType RNW Renewal
.DealType WFA Waiting for approval
Then in other models I have fields that are filled using these keywords. For example,
public class Deal
{
....
public string state { get; set; }
public string type { get; set; }
....
}
I have managed to have the fields filled with "valuecode" while displaying "valuename" in Create and Edit views (I use DropDownList with a SelectList built in the controller), but I cannot find a way to display valuename instead of valuecode in Index and Details views.
I'm trying to pass the same SelectList in the ViewBag for Index, but then I do not know which syntax to use in order to replace the "state" code with the state "description" for each record returned.
Any hint?
PS: I'm quite new to .net and mvc, usually work with RoR and ActiveRecord...
EDIT
In my KeywordController I have a method
public SelectList selectKeywordValues(string kwname, object selectedKeyword = null)
{
var keywordsQuery = from d in db.Keywords
where d.name == kwname
orderby d.valuename
select d;
SelectList kwlist = new SelectList(keywordsQuery, "valuecode", "valuename", selectedKeyword);
return kwlist;
}
Then in my DealController i have the index method
public ActionResult Index()
{
var kw = new KeywordController();
ViewBag.state = kw.selectKeywordValues(".DealState");
return View(db.Deals.ToList());
}
SOLVED
In DealController the index method is the following
public ActionResult Index()
{
var kw = new KeywordController();
SelectList states = kw.selectKeywordValues(".DealState");
SelectList types = kw.selectKeywordValues(".DealType");
foreach (var item in db.Deals.ToList())
{
SelectListItem mystate = states.Where(row => row.Value == item.state).ElementAt(0);
SelectListItem mytype = types.Where(row => row.Value == item.type).ElementAt(0);
item.state = mystate.Text;
item.type = mytype.Text;
}
return View(db.Deals.ToList());
}
Now the db.Deals.ToList() is filled with descriptions and not with codes.
You can define a view model called DealViewModel that contains DealState and DealType properties. Then populate the DealViewModel with joins in LINQ before passing to the views that reference the view model.
Another approach is to use enums in EF5.
I'm using jqGrid to display some data on a page. Within the controller action, we're using an anonymous object to represent the data that the jqGrid needs. My question is, is there a way we can create a strongly typed object to represent the jqGrid data that we are sending with Json()?
Main reason for this is so that we can do unit testing with the objects that are being sent to it.
Thanks!
EDIT:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GridData(FormCollection form, string alias, string location, string state)
{
int pageSize = Convert.ToInt32(form["rows"]);
int pageIndex = Convert.ToInt32(form["page"]) - 1;
var deviceList = this._device.GetList(CreateFilter(location,alias,state),this._securityCache.GetSecurityContext(),pageSize,pageIndex);
int totalResults = deviceList.TotalRecords;
int totalPages = (int)Math.Ceiling((float)totalResults / (float)pageSize);
var jsonData = new {
total = totalPages,
page = pageIndex + 1,
records = totalResults,
rows = (from device in deviceList.Data
select new {i = device.Alias,cell = new string[]{device.Alias,device.Location,device.RatePlan,device.State,device.DateCreated.ToString()}}).ToArray()
};
return Json(jsonData);
This above here works, but we can't unit test the data that is being passed into the Json() method.
var newJsonData = new JsonJQGridReturnData();
newJsonData.total = totalPages;
newJsonData.page = pageIndex + 1;
newJsonData.records = totalResults;
List<JsonJQGridRow> list = new List<JsonJQGridRow>();
foreach (var device in deviceList.Data)
{
list.Add(new JsonJQGridRow(device.Alias, new string[] { device.Alias, device.Location, device.RatePlan, device.State, device.DateCreated.ToString() }));
}
newJsonData.rows = list.ToArray();
_cookieHelper.SaveCookie("DeviceListIndex", this._securityCache.GetSecurityContext().UserID.ToString(), COOKIE_PAGE_SIZE_KEY, pageSize.ToString());
return Json(newJsonData);
}
Here is my poor attempt at trying to wrap these into strongly typed objects. Unfortunately, running this gives me a "u is undefined" in the jqGrid file. I suspect that this is because the json being passed in is not correctly formatted. Here are the classes....
[DataContract]
public class JsonJQGridReturnData
{
[DataMember]
public int total { get; set; }
[DataMember]
public int page { get; set; }
[DataMember]
public int records { get; set; }
[DataMember]
public JsonJQGridRow[] rows { get; set; }
}
[DataContract]
public class JsonJQGridRow
{
public JsonJQGridRow(string i, string[] columns)
{
this.i = i;
this.cells = columns;
}
[DataMember]
public string i { get; set; }
[DataMember]
public string[] cells { get; set; }
}
If I understand your question you can use Generics to do this:
Model:
// represents one row in the JQGrid
class Customer
{
public string firstname { get; set; }
public string lastname { get; set; }
}
JQGrid class:
class JQGridData<TModel>
{
// add in whatever other properties you want for JQGrid
public int responseTime {get; set; };
public List<TModel> rows = new List<TModel>();
}
Controller Action :
public JsonResult GridData(int page)
{
var gridData = new JQGridData<Customer>();
// Populate your data here, this is just an example:
gridData.rows.Add(new Customer()
{
firstname = "fred", lastname = "pharkas"
});
// return the result
return Json(gridData, JsonRequestBehavior.AllowGet);
}
Result:
{
responseTime: 0
rows: [
{
firstname: "fred"
lastname: "pharkas"
}
]
}
Is that what you were asking?
David,
Here's the kinda thing i use in an app i'm working on at the moment for this type of thing. I know it doesn't provide a strongly typed object as such, but the 'list' could be a part of the model that is then sent ToArray() at the end of the piece.
public JsonResult GridData(int id)
{
// get our messages based on id
var bookingmessagesList = _repository.Find(x => x.ID == id);
var list = new ArrayList();
foreach (var bookingmessage in bookingmessagesList) //populate data containers with read data
{
list.Add(new
{
bookingmessage.ClassRowVersionDate,
bookingmessage.ID,
bookingmessage.BookingID,
bookingmessage.AssignedFrom,
bookingmessage.AssignedTo,
bookingmessage.AssignedDate,
bookingmessage.CompletedDate,
bookingmessage.MessageType,
bookingmessage.Notes
});
}
int totalOjectCount = list.Count;
return Json(new { dataitems = list.ToArray(), totalItems = totalOjectCount });
}
hope it gives you some ideas.. Will be interested to see the suggestions made.
Here's a quick take on a strongly-typed JQGridResult.
public class JQGridResult<T> : JsonResult where T : class
{
public T Model
{
get { return (T)this.Data; }
set { this.Data = value; }
}
}
Used as...
return new JQGridResult<JsonModel> {
Model = new GridModel { ... initialize model here ... }
});
where GridModel is basically a container class holding the strongly typed properties for the grid.
I feel really silly. I had a misspelling in the GridRow that was causing jqGrid to blow up. After I fixed that, I was able to get the jqGrid to work with my strongly typed object...
Now in my unit tests, I can just do...
var result = controllerToTest.GridData(form, null, null, null) as JsonResult;
var data = result.Data as JsonJQGridReturnData;
and now I can access the fields :D