I have a JS file making an AJAX request to a controller. The code below works without the .Include() method but when I add it, the app runs but it doesn't populate my code. I tried running in debug mode but I'm not getting clear answers.
JS file
$(function () {
$.ajax({
type: "GET",
url: "https://localhost:5001/api/SortData/All",
contentType: "application/json; charset=utf-8",
dataType: "json"
}).done(response => {
// For each element in the response, add the submission card
$(response).each(index => {
// Formatting the submission date to be legible
var date = getFormattedDate(response[index].submitDate);
// Adding each submission card to the dashboard
$(".proposals").append(` ...some html...`)}});
API
[Route("api/[controller]/[action]")]
public class SortDataController : ControllerBase
{
[HttpGet, ActionName("All")]
public IQueryable<Proposals> GetAllProposals()
{
return _context.Proposals.Include(p => p.DeveloperName.Name);
}
}
Proposals Model
public class Proposals
{
public Proposals()
{
// Adding values to fields automatically. These fields are not on the form for users to see and update.
SubmitDate = DateTime.Now;
StatusId = 14;
AssignedTo = "johnDoe";
}
// other properties
[NotMapped]
public Users DeveloperName { get; set; }
// a few more properties
}
Users Model
public partial class Users
{
// other properties
public string Name { get; set; }
//some more properties
public IList<Proposals> Proposals { get; set; }
}
The Include is for the Navigation property only, so instead of using Include(p => p.DeveloperName.Name) try to use Include(p => p.DeveloperName) which will also contains Name when you retrieve data.
return _context.Proposals.Include(p => p.DeveloperName);
If you only would like a few specific fields,you need to select them explicitly. Something like this would work:
_context.Proposals.Include(p => p.DeveloperName)
.Select(p => new Proposals
{
StatusId = p.StatusId ,
// etc... The fields you need from Proposals go here
DeveloperName = new Users
{
Name = p.Category.Name
}
}
OK, I figured out Blue's Clues.
Turns out, I had to add
.AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to the end of services.AddMvc() in my StartUp. Apparently, there was some looping issue that this code prevents.
I also updated my API Controller to...
[Route("api/SortData")]
public class SortDataController : Controller
{
[HttpGet("All")]
public IActionResult GetAllProposals()
{
return Ok(_context.Proposals.Include(p => p.DeveloperName).Include(p => p.Status).ToList());
}
}
From there, I was able to see the JSON response in the XHR and add it accordingly to my JS file.
Related
I've a requirement where I've to return an id from Ajax call to Index method of the `HomeController' and I can pass the id to the controller with Ajax as follows:
Update 1:
ProductRating Class:
public class ProductRating
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double UserRating { get; set; }
public double? RatingCount { get; set; } //Messed up with this earlier and when Luqman told me about the model, it reminded to add this property and the rest is done
public double? Total { get; set; }
}
Ajax Call:
<script>
$(document).ready(function (response) {
if (response) {
$.ajax({
url: '#Url.Action("AddRating")',
data: { id: '#item.ProductId', rating: rate },
type: 'POST',
success: function (data) {
$('.#item.ProductId').html(data);
}
});
}
}).fail(function () {
alert("Failed to get total ratings!");
});
</script>
Controller:
[HttpPost]
public JsonResult AddRating(int id, double rating)
{
var con = new ProductRating
{
RatingCount = db.Ratings.Where(c => c.ProductId == id).Count()
};
if (con.RatingCount >= 300)
{
return Json("Rating limit already exits", JsonRequestBehavior.AllowGet);
}
else
{
Ratings aRatings = new Ratings();
aRatings.ProductId = id;
aRatings.UserRating = rating;
db.Ratings.Add(aRatings);
db.SaveChanges();
return Json(con.RatingCount, JsonRequestBehavior.AllowGet);
}
}
See in the above method, I've set the id into a session variable and would pass this variable value into the Index method as a parameter. The problem is the session variable doesn't work in the first attempt until I refresh the page. But while debugging, I can see the values and every time, it gets erased. I guess, that isn't the correct way to do so. ViewBag and Session don't seem to be helpful. My requirement is to count ratings of individual products respect to their product id and that should be returned with the same Ajax call. In the Index method, I am doing the following:
CountAll = db.Ratings.Where(c => c.ProductId == id).Count() //id - I am trying to pass from Ajax call
I hope, there would be a better way to do this and currently struggling with it.
Note: Thanks #Luqman and #Stephen Muecke. #Stephen Muecke Thanks for the call. I was making two Ajax calls for demo purpose and apology for the delay to update the post.
Here is the working sample right now:
Why you use the session to get store the id. Please use a Model or Tempdata to store that id.
I have a grid with Web Api server side sorting, which works. I have a requirement to add filtering. When the grid's datasource sends the filtering arguments to the Web Api controller, the Filter object is always 'empty', not null. Here is my setup
Grid datasource:
var myTeamGridDataSource = new kendo.data.DataSource({
serverPaging: true,
serverSorting: true,
serverFiltering: true,
schema: {
data: "data",
total: "count"
},
pageSize: 10,
transport: {
read: {
url: baseMyTeamUrl, // web api
dataType: "json",
type: "POST",
}
},
sort: {
field: "Name",
dir: "asc"
}
});
The Action:
public HttpResponseMessage KendoGridMyTeam(GridInputModel inputModel)
{
...
}
GridInputModel.cs
public class GridInputModel
{
public int Page { get; set; }
public int PageSize { get; set; }
public int Skip { get; set; }
public int Take { get; set; }
public List<GridInputSortModel> Sort { get; set; }
public List<GridInputFilterModel> Filter { get; set; }
}
GridInputFilterModel.cs
public class GridInputFilterModel
{
public GridInputFilterModel()
{
filters = new List<GridInputFilterModel>();
}
public string field { get; set; }
public string Operator { get; set; }
public object value { get; set; }
public string logic { get; set; }
public List<GridInputFilterModel> filters { get; set; }
}
The Request Body
take=10&skip=0&page=1&pageSize=10&sort[0][field]=Name&sort[0][dir]=asc
&filter[filters][0][field]=Name
&filter[filters][0][operator]=eq
&filter[filters][0][value]=cling
&filter[logic]=and
The GridInputModel "GridInputModel inputModel" is instantiated, and the sort object is preset and that feature works just fine. But the filter, when set in the client and sent to the server is empty. After a bunch of searching, I cannot find a modern example of server side grid filtering. You may suggest that I use the Kendo.Mvc library with:
[DataSourceRequest] DataSourceRequest request
This ALSO creates a Filter object in the Request but the filters are also empty. Any suggestions? One option, that I would hesitate to use, is to use parameterMap and send the filter along on the querystring. If I have to, fine, but somebody has to have this working.
Whats happening is that your application serverside isn't deserializing the filter objects properly. You can view this if you set the parameter to an object and view the actual JSON that is sent. Your filter will be present in the string. Use Parameter map and format the filter array before its sent and you might one to overload the DataSourceClass that kendo uses. I had the same issue and had to create my own class and format the filters before sending. The issue that messing up your filters is the operator property in the filter array.
In your javascript try this:
var grid = $("#YourGridId").data("kendoGrid");
var $filter = new Array();
$filter.push({ field: "Name", operator: "eq", value: "cling" });
grid.dataSource.filter($filter);
and in your controller method you need this signature:
public ActionResult ReadData([DataSourceRequest]DataSourceRequest request){
...
}
I am using MVC instead of WebApi, but I was getting the same symptom of a null filter passed to my controller method. I, too, went down the rabbit hole of creating my own model to handle the request parameter sent to my controller method, trying to parse Kendo's filter structure, etc. I finally got it to work by following this example on Falafel's blog. Specifically, you need to make the changes listed below to your code.
In the end, the technique shown below makes it very easy to implement server processing for Kendo's DataSource (once you know all the required pieces to make it work).
Grid DataSource: You have to match all the (case-sensitive) fields coming back from the server. I left out errors, and it stopped working. You may have to change the type to get it working with WebApi.
type: "aspnetmvc-ajax",
schema: {
data: "Data",
total: "Total",
errors: "Errors",
model: {
id: "YourKeyFieldName"
}
},
The Action: Yes, you need to use Kendo's DataSourceRequest object with its attribute .
using Kendo.MVC.UI;
using Kendo.MVC.Extensions;
[HttpPost]
public ActionResult KendoGridMyTeam([DataSourceRequest] DataSourceRequest request)
{
...
return Json(yourDataVariable.ToDataSource(request));
}
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());
I'm trying to update a Customer in my database using ASP.NET Web API and Entity Framework 5 code-first, but it's not working. My entities look like this:
public class CustomerModel
{
public int Id { get; set; }
public string Name { get; set; }
// More fields
public ICollection<CustomerTypeModel> CustomerTypes { get; set; }
}
public class CustomerTypeModel
{
public int Id { get; set; }
public string Type { get; set; }
[JsonIgnore]
public ICollection<CustomerModel> Customers { get; set; }
}
Nothing all that special. I've built a web interface where users can add a customer by supplying the name and checking one or more customer types. When hitting the submit button, the data is sent to my Web API method:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
context.Customers.Attach(customer);
context.Entry(customer).State = EntityState.Modified;
context.SaveChanges();
}
}
This updates the customer fields, but the related customer types are ignored. The incoming customer object does contain a list of CustomerTypes it should be associated with:
[0] => { Id: 1, Type: "Finance", Customers: Null },
[1] => { Id: 2, Type: "Insurance", Customers: Null }
[2] => { Id: 3, Type: "Electronics", Customers: Null }
But instead of looking at this list and adding/removing associated entities, EF just ignores it. New associations are ignored and existing associations remain even if they should be deleted.
I had a similar problem when inserting a customer into the database, this was fixed when I adjusted the state of these entities to EntityState.Unchanged. Naturally, I tried to apply this same magic fix in my update scenario:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
foreach (var customertype in customer.CustomerTypes)
{
context.Entry(customertype).State = EntityState.Unchanged;
}
context.Customers.Attach(customer);
context.Entry(customer).State = EntityState.Modified;
context.SaveChanges();
}
}
But EF keeps displaying the same behavior.
Any ideas on how to fix this? Or should I really just do a manual clear to the list of CustomerTypes and then manually add them?
Thanks in advance.
JP
This is not really solvable by only setting entity states. You must load the customer from the database first including all its current types and then remove types from or add types to the loaded customer according to the updated types collection of the posted customer. Change tracking will do the rest to delete entries from the join table or insert new entries:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
var customerInDb = context.Customers.Include(c => c.CustomerTypes)
.Single(c => c.Id == customer.Id);
// Updates the Name property
context.Entry(customerInDb).CurrentValues.SetValues(customer);
// Remove types
foreach (var typeInDb in customerInDb.CustomerTypes.ToList())
if (!customer.CustomerTypes.Any(t => t.Id == typeInDb.Id))
customerInDb.CustomerTypes.Remove(typeInDb);
// Add new types
foreach (var type in customer.CustomerTypes)
if (!customerInDb.CustomerTypes.Any(t => t.Id == type.Id))
{
context.CustomerTypes.Attach(type);
customerInDb.CustomerTypes.Add(type);
}
context.SaveChanges();
}
}
A cleaner solution would be:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
var customerInDb = context.Customers.Include(c => c.CustomerTypes)
.Single(c => c.Id == customer.Id);
// Updates the Name property
context.Entry(customerInDb).CurrentValues.SetValues(customer);
// Remove types
customer.CustomerTypes.Clear();
// Add new types
foreach (var type in customer.CustomerTypes)
{
context.CustomerTypes.Attach(type);
customerInDb.CustomerTypes.Add(type);
}
context.SaveChanges();
}
}
I have a View that gets some bits of data via Action methods that return JSON data.
Depending on the combination of selected options, the user can fill some fields in a page.
What is the best way to pass the data back to a controller, in order to be saved?
The fields that contain data vary on the options selected;
I don't have a ViewModel object with all fields bound to the View.
At the moment I have this:
#Ajax.BeginForm("MyAction", null, new AjaxOptions
{
}, new { #id = "SaveForm" } )
{
.....
#Html.RadioButton("SomeRadioButton", "bla", false, new { #id = "SomeRadioButton" })
.....
#Html.TextArea("SomeTextArea", new { #id = "SomeTextArea" })
.....
Save
}
How do I get all of those control values in the Action?
I can add something like:
public void MyAction(FormCollection form)
{
.........
}
But I don't really like this option.
What's the cleanest way to implement this?
Thanks in advance
You could define a view model:
public class MyViewModel
{
public string SomeRadioButton { get; set; }
public string SomeTextArea { get; set; }
...
}
and then have your controller action take this view model as argument and leave the default model binder do its job:
[HttpPost]
public void MyAction(MyViewModel model)
{
...
}
I would also take advantage of this view model in the view in order to use strongly typed versions of the helpers:
#Ajax.BeginForm("MyAction", null, new AjaxOptions { }, new { #id = "SaveForm" })
{
#Html.RadioButtonFor(x => x.SomeRadioButton)
...
#Html.TextAreaFor(x => x.SomeTextArea)
...
<button type="submit">Save</button>
}
You can (and mostly should) use custom class for this, which will hold all fields. Read further about Model Binding - that's the way to do it with MVC.