How to pass a parameter to the Read method of signalr transport for Kendo UI grid data source - kendo-ui

I was looking at the demo:
Telerik's Demo
and this
Telerik's Example
I still cannot figure out how, using this technique, to pass a parameter
to the Read method, so instead of reading ALL games or products, it reads ONLY a
subset of games or products, by category ID lest say. If I change the Read method in
the server-side code to take a parameter, it never gets hit anymore,
and I cannot figure out how to pass a parameter from the transport
client-side definition definition... Any help would be highly
appreciated!

I posted the question to the Telerik's forums and got this which should work!
Telerik's Answer

In case anyone else comes along in the future and the link in the other answer is gone, or for those who don't want to download a project file and sift through it themselves, here's more detail from the answer given on the Telerik forums:
They use Kendo.DynamicLinq to "bind the request parameters."
They created a custom class that mirrors the structure of the existing DataSourceRequest class typically used in AJAX grid actions.
using System.Collections.Generic;
using Kendo.DynamicLinq;
namespace SignalRLocalHub.Models
{
public class MyDataSourceRequest
{
public int Take { get; set; }
public int Skip { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public Filter Filter { get; set; }
public IEnumerable<Sort> Sort { get; set; }
public IEnumerable<Aggregator> Aggregates { get; set; }
}
}
In the ProductHub (SignalR hub) class, this is the Read method:
public DataSourceResult Read(MyDataSourceRequest request)
{
return productService.Read()
.ToDataSourceResult(
request.Take,
request.Skip,
request.Sort,
request.Filter,
request.Aggregates);
}
The productService.Read method it calls, for reference, looks like this:
//int take, int skip, IEnumerable<Sort> sort, Filter filter, IEnumerable<Aggregator> aggregates
public IQueryable<ProductViewModel> Read()
{
return entities.Products
.OrderBy(p => p.ProductID)
.Select(product => new ProductViewModel
{
ProductID = product.ProductID,
ProductName = product.ProductName,
UnitPrice = product.UnitPrice.HasValue ? product.UnitPrice.Value : default(decimal),
UnitsInStock = product.UnitsInStock.HasValue ? product.UnitsInStock.Value : default(short),
Discontinued = product.Discontinued
});
}
And finally, here is the grid's DataSource configuration:
.DataSource(d => d
.SignalR()
.AutoSync(true)
.Transport(tr => tr
.Promise("hubStart")
.Hub("hub")
.Client(c => c.Read("read").Create("create").Update("update").Destroy("destroy"))
.Server(s => s.Read("read").Create("create").Update("update").Destroy("destroy"))
)
.ServerFiltering(true)
.Filter(f => f.Add(m => m.UnitPrice).IsEqualTo(10))
.ServerPaging(true)
.PageSize(10)
.Schema(s => s
.Data("Data")
.Total("Total")
.Aggregates("Aggregates")
.Model(m =>
{
m.Id(e => e.ProductID);
m.Field(e => e.ProductID).Editable(false);
})
)
)
This allows sorting, paging, and filtering by any field that is part of the grid view model. So as long as your "category ID" is a property of the grid view model and the grid is configured to be able to filter by that field, this method should work.

Related

Queryable Attribute allow to get some properties not all of them

I have a web api 2 application, and in my controller , I have this code :
[Queryable]
public IQueryable<Title> GetTitles()
{
return db.Titles;
}
and here is the Title entity :
public partial class Title
{
public Title()
{
this.People = new HashSet<Person>();
}
public short Id { get; set; }
public string NameT { get; set; }
public virtual ICollection<Person> People { get; set; }
}
When people query the Titles, they must get only "NameT" property. but now they get all of the properties. and yes, I know about $select, but I want another way. means even they use $select, they should not able to get "Id" property for example. if I have to bring more information, please tell me. thanks.
There are two ways to solve your problem when you use ODataController. However, they won't affect ApiController non-query part.
In that condition, you can try what Zoe suggested.
1.Ignore those properties while building your model with model builder.
builder.EntityType<Title>().Ignore(title => title.Id);
2.Add ignore member attributes on those properties.
[IgnoreDataMember]
public short Id { get; set; }
More than these, we provide support for limiting the set of allowed queries in Web API 2.2 for OData v4.0.
http://blogs.msdn.com/b/webdev/archive/2014/03/13/getting-started-with-asp-net-web-api-2-2-for-odata-v4-0.aspx
We can use attributes like Unsortable, NonFilterable, NotExpandable or NotNavigable on the properties of the types in our model, or we can configure this explicitly in the model builder.
Maybe you can have filter in your action GetTitles(), like:
[Queryable]
public IQueryable<Title> GetTitles()
{
return db.Titles.Select(t=>t.Name);
}
Use the ODataModelBuilder class as opposed to the ODataConventionModelBuilder class.
var modelBuilder = new ODataModelBuilder();
var titles = modelBuilder.EntitySet<Title>("titles");
var title = titles.EntityType;
title.HasKey(x => x.Id);
title.Ignore(x => x.Id);
title.Property(x => x.TName);
titles.HasIdLink(x => { return x.GenerateSelfLink(true); }, true);
config.Routes.MapODataRoute("odata", "odata", modelBuilder.GetEdmModel());

MVC3 EF 4.1 Include Condition Linq

Apologies if this has been asked before as I am learning EF 4.1 and LINQ, I needed an expert opinion.
I have a viewmodel called HomeIndexData. My code looks something like the following:
string _categoryIDs = "100,101,102,103,104";
List < int > CategoryIDs = _categoryIDs.Split(',').Select(t => int.Parse(t));
Then I got the following view model:
public class HomeIndexData
{
public IEnumerable<Genre> Genres { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<SubCategory> SubCategories { get; set; }
public IEnumerable<Country> Countries { get; set; }
}
At the moment, in my Controller I have the following code -
HomeIndexData viewModel = new HomeIndexData
{
Genres = db.Genres
.Include(i => i.Categories.Where(i => CategoryIDs.Contains(i.CategoryId)))
.Include("Categories.SubCategories")
.OrderBy(g => g.DisplaySequence),
Countries = db.countries
};
But I am getting error :
Cannot convert lambda expression to type 'string' because it is not a delegate type
for the following line :
.Include(i => i.Categories.Where(i => CategoryIDs.Contains(i.CategoryId)))
Could you please put me to the right direction how can I write the lamda expression.
Any help on this will be much appreciated.
Thanks in advance.
EF doesn't support complex expressions in the Include extension at this point, only simple property getters. If you feel this is an important enhancement scenario for EF, consider voting for it at http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1015345-allow-filtering-for-include-extension-method.
For now, your best option is to project your filter in the Select clause, but that gets tricky with a generic repository.

Using RavenDB, how do I efficiently retrieve a list of items related by a 'foreign key'

I store User objects in RavenDB. Each User has a User.Id property.
I also have a Relationship class that links two User.Ids together to create a Mentor/Mentee relationship, like this:
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
... more properties
}
public class Relationship
{
public string Id { get; set; }
public string MentorId { get; set; }
public string MenteeId { get; set; }
public RelationshipStatus Status { get; set; }
}
Now I want to retrieve a list of Mentees for a given Mentor. I have done this in the following way:
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
var mentees = new List<User>();
db.Query<Relationship>()
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id)));
return mentees;
}
This seems to work just fine but the coding-angel on my shoulder is wrinkling her nose at the smells emanating from the nested use of the IDocumentSession (db) and the need for multiple Load calls to fill the Mentees List.
How can I optimise this method using best practice RavenDB syntax?
Edit
Thanks to #Jonah Himango (see accepted answer below) who solved the problem of multiple calls to the database for me. In addition I have also created a new extension method called 'Memoize' to eliminate the need for the external 'mentees' result List (see code above).
Here is the optimised code. Please feel free to comment and refine further.
The Linq
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
return db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Memoize()
.Select(r => db.Load<User>(r.MenteeId))
.ToList();
}
The extension method
public static List<T> Memoize<T>(this IQueryable<T> target)
{
return target.ToList();
}
Note : This extension method may seem completely superfluous (it is really) but it irritates my geek-gland that I have to call a function called ToList(), not to create a list, but to force the execution of the Linq statement. So my extension method just renames ToList() to the far more accurate Memoize().
You'll want to use the .Include query customization to tell Raven to include the related user object off of each Relationship:
db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id))); // .Load no longer makes a call to the DB; it's already loaded into the session!
Relevant documentation here:
The call to Load() is resolved completely client side (i.e. without
additional requests to the RavenDB server) because the [related]
object has already been retrieved via the .Include call.

Struggling to get AutoMapper to map my ViewModel to my Domain, What could I be doing wrong?

I've been fiddling about and trying multiple things, but I'm going wrong somewhere. I tried to make my first attempt using AutoMapper as simple as possible. I'm trying to create a new Brand and save it to the database, using a CreateBrandViewModel. Some of this might look a bit fruity, but I was trying to get it to work in the simplest way possible.
Domain:
public class Brand : EntityBase
{
public virtual string Name { get; set; } //Not Nullable
public virtual bool IsActive { get; set; } // Not Nullable
public virtual Product DefaultProduct { get; set; } // Nullable
public virtual IList<Product> Products { get; set; } // Nullable
}
ViewModel:
public class CreateBrandViewModel
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
Controller
this is where I've been playing about the most for a while, so it looks a bit strange now. The commented out code hasn't resolved my problem.
[HttpPost]
public ActionResult Create(CreateBrandViewModel createBrandViewModel)
{
if(ModelState.IsValid)
{
Mapper.CreateMap<Brand, CreateBrandViewModel>();
//.ForMember(
// dest => dest.Name,
// opt => opt.MapFrom(src => src.Name)
//)
//.ForMember(
// dest => dest.IsActive,
// opt => opt.MapFrom(src => src.IsActive)
//);
Mapper.Map<Brand, CreateBrandViewModel>(createBrandViewModel)
Session.SaveOrUpdate(createBrandViewModel);
return RedirectToAction("Index");
}
else
{
return View(createBrandViewModel);
}
}
Just for the record, BrandController inherits from SessionController (Ayendes way), and transactions are managed through an ActionFilter. Though thats is a bit irrelevant I think. I've tried various different ways so I have different error messages - if you can take a look at whats happening and tell me how you might expect to use it that would be great.
For reference, my fluent nhibernate mapping for Brand:
public class BrandMap : ClassMap<Brand>
{
public BrandMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Not.Nullable()
.Length(50);
Map(x => x.IsActive)
.Not.Nullable();
References(x => x.DefaultProduct);
HasMany(x => x.Products);
}
}
Edit 1
I just tried the following code, but putting a breakpoint on Session.SaveOrUpdate(updatedModel) the fields are null and false, when they shouldn't be:
var brand = new Brand();
var updatedBrand = Mapper.Map<Brand, CreateBrandViewModel>(brand, createBrandViewModel);
Session.SaveOrUpdate(updatedBrand);
return RedirectToAction("Index");
}
You appear to be doing your mapping the wrong way around on your return trip from the post. The alternative syntax may help out here, try:
// setup the viewmodel -> domain model map
// (this should ideally be done at initialisation time, rather than per request)
Mapper.CreateMap<CreateBrandViewModel, Brand>();
// create our new domain object
var domainModel = new Brand();
// map the domain type to the viewmodel
Mapper.Map(createBrandViewModel, domainModel);
// now saving the correct type to the db
Session.SaveOrUpdate(domainModel);
let me know if that cracks the egg... or just egg on yer face again :-)

MVC 3 Unobtrusive validation of a list

Question
I have created a server-side property level validation attribute. But instead of applying it to an individual field I've applied it to a List. This allows me to validate the model as a whole.
I now need to know how to convert this to work using the unobtrusive client-side validation built into MVC 3.
My current code is below to illustrate my issue...
Scenario
The basic scenario was the ability total up all the Quantity values for every row in a List grouped by the GroupNo field. If the sum of any of the groups was more than 10 then an error should be displayed.
I was kindly given an answer in a previous post to make this work server-side using a validation attribute against a List...
The model:
public class ItemDetails
{
public int SerialNo { get; set; }
public string Description { get; set; }
public int GroupNo { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class MyViewModel
{
[EnsureMaxGroupItems(10, ErrorMessage = "You cannot have more than 10 items in each group")]
public IList<ItemDetails> Items { get; set; }
}
and the validation attribute itself:
[AttributeUsage(AttributeTargets.Property)]
public class EnsureMaxGroupItemsAttribute : ValidationAttribute
{
public int MaxItems { get; private set; }
public EnsureMaxGroupItemsAttribute(int maxItems)
{
MaxItems = maxItems;
}
public override bool IsValid(object value)
{
var items = value as IEnumerable<ItemDetails>;
if (items == null)
{
return true;
}
return items
.GroupBy(x => x.GroupNo)
.Select(g => g.Sum(x => x.Quantity))
.All(quantity => quantity <= MaxItems);
}
}
and finally your controller actions will work with the view model:
public ActionResult ListItems()
{
var model = new MyViewModel
{
Items = ItemsRepository.GetItems()
};
return View(model);
}
[HttpPost]
public ActionResult ListItems(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
...
}
and next the corresponding strongly typed view:
#model MyViewModel
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Items)
<button type="submit">Go go go</button>
}
and the last bit is the corresponding editor template that will automatically be rendered for each element of the Items collection so that you don't even need to write for loops (~/Views/Shared/EditorTemplates/ItemDetails.cshtml):
#model ItemDetails
#Html.HiddenFor(x => x.SerialNo)
#Html.LabelFor(x => x.Description)
#Html.HiddenFor(x => x.GroupNo)
#Html.LabelFor(x => x.Price)
#Html.TextBoxFor(x => x.Quantity)
Client-side unobtrusive validation possible?
I would like it all to validate using unobtrusive MVC validation. But I cannot figure out how to unobtrusively validate the EnsureMaxGroupItemsAttribute attribute against the list as a whole.
I've implemented IClientValidatable in this way:
Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules
Dim result = New List(Of ModelClientValidationRule)
Dim rule = New ModelClientValidationRule() With { _
.ErrorMessage = "You cannot have more than 10 items in each group", _
.ValidationType = "itemscheck"}
result.Add(rule)
Return result
End Function
Note: the mix of VB and C# is only because the previous question I asked was answered in C#. The project is in VB but I don't mind an answer in C#.
I've created the adaptor in my JS file:
jQuery.validator.unobtrusive.adapters.addBool("itemscheck");
... and ...
jQuery.validator.addMethod("itemscheck", function (value, element, params) {
// The check has been omitted for the sake of saving space.
// However this method never gets called
return false;
});
Is there a way to hook this up to work unobtrusively?
This is not possible because your custom attribute is placed in the collection property and there are no HTML5 data-* attributes emitted at all. It is not a supported scenario by the unobtrusive client validation framework. You could write directly a custom jquery validate rule to handle this scenario if you need client validation for it.

Resources