I previously had the following line of code from within my AdminController that was successfully returning a list of relevant subsections from within a course:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetCourseSections(int courseID)
{
var Sections = dbcontext.CourseSection.Where(cs => cs.CourseID.Equals(courseID)).Select(x => new
{
sectionID = x.CourseSectionID,
sectionTitle = x.Title
);
return Json(Sections, JsonRequestBehavior.AllowGet);
}
I was informed to take this out of the controller as it was bad practice to call dbcontext and so i moved this to the AdminViewModel. Within my AdminViewModel I have a variable public List CourseSectionList { get; set; } and I am trying to populate this variable with the JSON request details. My code is as follows:
AdminViewModel
public void GetCourseSectionDetails(int courseID)
{
var Sections = dbcontext.CourseSection.Where(cs => cs.CourseID.Equals(courseID)).Select(x => new CourseSection
{
CourseSectionID = x.CourseSectionID,
Title = x.Title
});
this.CourseSectionList = Sections.ToList();
}
AdminController
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetCourseSections(int courseID)
{
avm.GetCourseSectionDetails(courseID);
var Sections = avm.CourseSectionList.Where(cs => cs.CourseID.Equals(courseID)).Select(x => new
{
sectionID = x.CourseSectionID,
sectionTitle = x.Title
});
System.Diagnostics.EventLog.WriteEntry("Application", "JSON=" + Sections.ToList(), System.Diagnostics.EventLogEntryType.Error);
return Json(Sections, JsonRequestBehavior.AllowGet);
}
I am getting the error The entity or complex type 'MetaLearning.Data.CourseSection' cannot be constructed in a LINQ to Entities query. How can I populate this.CourseSectionList variable using the Sections?
As pointed by your error message, you can't, in linq to entities, use a
.Select(m => new <Entity>{bla bla})
where <Entity>... is one of your model's entity.
So either you use a "non model" class (DTO), which has the properties you need, or you have to enumerate before selecting (because linq to objects has not that limitation)
.ToList()
.Select(m => new <Entity>{bla bla});
You can find some nice explanations of why it's not possible here
EDIT :
you may also do something like that, if you wanna retrive only some properties of your entity, and don't wanna use a DTO :
return ctx
.CourseSection
.Where(cs => cs.CourseID.Equals(courseID))
//use an anonymous object to retrieve only the wanted properties
.Select(x => new
{
c= x.CourseSectionID,
t= x.Title,
})
//enumerate, good bye linq2entities
.ToList()
//welcome to linq2objects
.Select(m => new CourseSection {
CourseSectionID = m.c,
Title = m.t,
})
.ToList();
You don't need to repeat the same code in the controller, but directly pass the list to the view.
This being said I am informing you that placing data access code in your view model is even worse practice than keeping it in the controller. I would recommend you having a specific DAL layer:
public interface IRepository
{
public IList<CourseSection> GetSections(int courseID);
}
which would be implemented:
public class RepositoryEF : IRepository
{
public IList<CourseSection> GetSections(int courseID)
{
using (ctx = new YourDbContextHere())
{
return ctx
.CourseSection
.Where(cs => cs.CourseID.Equals(courseID))
.Select(x => new CourseSection
{
CourseSectionID = x.CourseSectionID,
Title = x.Title,
})
.ToList();
}
}
}
and finally have your controller take the repository as dependency:
public class SomeController : Controller
{
private readonly IRepository repo;
public SomeController(IRepository repo)
{
this.repo = repo;
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetCourseSections(int courseID)
{
var sections = this.repo.GetSections(courseID);
return Json(sections, JsonRequestBehavior.AllowGet);
}
}
I did this as follows using Darin's answer as a guide:
ViewModel
public void GetCourseSectionDetails(int courseID)
{
this.CourseSectionList = dbcontext.CourseSection.AsEnumerable().Where(cs => cs.CourseID.Equals(courseID)).Select(x => new CourseSection
{
CourseSectionID = x.CourseSectionID,
Title = x.Title
}).ToList();
}
Controller
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetCourseSections(int courseID)
{
var sections = avm.CourseSectionList;
return Json(sections, JsonRequestBehavior.AllowGet);
}
Related
I am trying to use Web API to grab certain fields from my MVC controller. I can't seem to match the right type with the right list. I am fine with converting everything to string.
I either get an error in code (can not convert types), or if I get it to compile, I get this error:
"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'."
From other similar posts, people responded with how to create a list, but not with the declaration of the return value of the Get. Please include both.
Also I would prefer not to add additional controllers as I need to do this on a number of my models.
Here is my code--note you can see I tried a number of different ways:
public class APICLIENTsController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET api/<controller>
public IEnumerable<string> Get()
//public IEnumerable<CLIENT> Get()
{
//return db.CLIENTs.OrderBy(x => x.CLIENTNAME).ToList();
string[] listOfUsers = db.CLIENTs.OrderBy(x => x.CLIENTNAME).Select(r => new
{
ID = r.CLIENTID.ToString(),
NAME = r.CLIENTNAME
});
return listOfUsers.ToList();
//return db.CLIENTs.Select(x => new { x.CLIENTNAME }).ToArray();
}
If you want to return JSON use the
JsonResult
type.
public JsonResult Get()
{
//return db.CLIENTs.OrderBy(x => x.CLIENTNAME).ToList();
string[] listOfUsers = db.CLIENTs.OrderBy(x => x.CLIENTNAME).Select(r => new
{
ID = r.CLIENTID.ToString(),
NAME = r.CLIENTNAME
});
return Json(listOfUsers.ToList(), JsonRequestBehavior.AllowGet);
}
Your query is returning a collection of anonymous objects, not string[] so it will throw an exception. Even if you were to generate string[] by concatenating the CLIENTID and CLIENTNAME properties, it would be a little use to the client.
Create a model to represent what you need to return to the view
public class ClientVM
{
public int ID { get; set; }
public string Name { get; set; }
}
and modify your method to
public IEnumerable<ClientVM> Get()
{
IEnumerable<ClientVM> model = db.CLIENTs.OrderBy(x => x.CLIENTNAME).Select(r => new ClientVM
{
ID = r.CLIENTID,
Name = r.CLIENTNAME
});
return model;
}
Side note: depending on how your calling and consuming this in the client, you may need to change the Content-Type to specifically return json (refer these answers for more detail)
I have an MVC Web App and a Web API Service that I am trying to retrieve information from. The problem is in the CapitalMailOrders entity collection is missing items when it is deserialized on the web side.
The Service uses the below to retrieve the information
var result = db.Contacts
.Include(a => a.IDXPageLinks)
.Include(b => b.ReboGatewayLoginInfoes)
.Include(c => c.SocialMedias)
.Include(d => d.WebSiteInfoes)
.Include(e => e.ContactImages)
.Include(f => f.RealtorSetUpProcesses.Select(f1 => f1.CapitalMailOrders)
.Include(g => g.Contact_CarrierCode_Assignments)
.FirstOrDefault(c => c.ContactID == id);
This code is good and returns the below on the service side. The below image shows 3 CapitalMailOrders which is what there should be.
But when it's deserialized on the Web side I only get 2 the 3rd is null
here is the Web Side Repository Code
public Contact Get(int id)
{
var responseStream =
requestMethod.GetResponseStream(
requestMethod.getRequest("GET", "application/json",
string.Format("{0}/api/contact/{1}", restService, id)).GetResponse());
var contacts = deSerialize<Contact>(responseStream) as Contact;
return contacts;
}
deSerialize is in the base repository class
public class BaseRepository
{
protected readonly string restService = ConfigurationManager.AppSettings["restService"];
protected readonly RequestMethod requestMethod = new RequestMethod();
protected ISerialization _serializer;
protected BaseRepository()
{ }
protected object deSerialize<T>(Stream stream)
{
var retval = _serializer.DeSerialize<T>(stream);
return retval;
}
protected string serialize<T>(T value)
{
var retval = _serializer.Serialize<T>(value);
return retval;
}
}
public class JsonNetSerialization : ISerialization
{
public string Serialize<T>(object o)
{
return JsonConvert.SerializeObject((T)o);
}
public object DeSerialize<T>(Stream stream)
{
return JsonConvert.DeserializeObject<T>(new StreamReader(stream).ReadToEnd());
}
}
Any ideas? Thanks
This post pointed me to the problem and suggested solution. In a nutshell the poster #Darin advised to return the data collection from the web api using models rather than the entity collection. Since there is no databinding to the db context when serializing the data back to the web app theres really no reason to carry the overhead and problems of trying to serialize the entity collection. This blog post goes into more detail.
I am using view model to display a dropdownlist and i am also trying to get the value of the selected list, here is my view model
public class CreateJobViewModel
{
public int[] SelectedIndustriesIds { get; set; }
public IList<SelectListItem> IndustriesList { get; set; }
}
My controller
public ActionResult Create()
{
var industryList = repository.GetAllIndustries();
var model = new CreateJobViewModel
{
IndustriesList = industryList.Select(i => new SelectListItem
{
Value = i.IndustryId.ToString(),
Text = i.Name
}).ToList()
};
return View("~/Views/Dashboard/Job/Create.cshtml", model);
}
My post controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateJobViewModel model)
{
try
{
var job = new Job()
{
Title = "hi",
EmploymentHourId = 1,
LocationId = 1,
Salary = 50,
SalaryPeriodId = 1,
PostCode = 2131,
Role = "world",
Description = "hello",
IsPublished = false,
ShiftId = 1,
WorkDayId = 1,
NumberOfPosition = 5,
Meal = false,
SecondYearVisa = true,
Sponsorship = true,
Accommodation = true,
DurationId = 1,
IndustryExperiencePeriod = 5,
Id = User.Identity.GetUserId(),
};
foreach (int id in model.SelectedIndustriesIds)
{
var industry = repository.Industry(id);
job.Industries.Add(industry);
}
foreach (int id in model.SelectedSpecialRequirementsId)
{
var special = repository.SpecialRequirement(id);
job.SpecialRequirements.Add(special);
}
repository.AddJob(job);
return RedirectToAction("Create");
}
catch
{
return View("~/Views/Dashboard/Job/Create.cshtml");
}
}
Every time i try to submit the selected value, i get Object reference not set to an instance of an object Error on the following line in my view:
#model Taw.WebUI.Models.CreateJobViewModel
#Html.ListBoxFor(m => m.SelectedIndustriesIds, Model.IndustriesList) -- here i get the error
Any reason why?
When you submit the form your throwing an exception (confirmed in the comments) and in the catch block you are returning the view, which throws the exception you are seeing because Model.IndustriesList is null. You need to re-assign the value before you return the view.
Since you need to assign SelectLists in the GET method and in the POST method if you return the view, I tend to re-factor this to a separate method to keep the controller code a bit cleaner. Note the following code is based on your model property being public SelectList IndustriesList { get; set; } which is a bit simpler than building IList<SelectListItem>
private void ConfigureViewModel(CreateJobViewModel model)
{
var industryList = repository.GetAllIndustries();
model.IndustriesList = new SelectList(industryList, "IndustryId", "Name")
// any other common stuff
}
and then in the action methods
public ActionResult Create()
{
var model = new CreateJobViewModel();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(CreateJobViewModel model)
{
try
{
....
}
catch
{
ConfigureViewModel(model);
return View(model);
}
}
Note its also good practice to test if the model is valid before attempting to save it
public ActionResult Create(CreateJobViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model); // return the view so the user can correct validation errors
}
....
I have this first version of a class
public class GenerateAuthorisationWorkflows : IGenerateAuthorisationWorkflows
{
public IList<Guid> FromDtaObjects(IList<DtaObject> dtaObjects, Employee requestingEmployee)
{
foreach (var dtaObject in dtaObjects) { }
return new List<Guid>();
}
public IList<Guid> FromDtaObjects()
{
return new List<Guid>();
}
}
And the MSpec tests for it
public abstract class specification_for_generate_workflows : Specification<GenerateAuthorisationWorkflows>
{
protected static IWorkflowService workflowService;
Establish context = () => { workflowService = DependencyOf<IWorkflowService>(); };
}
[Subject(typeof(GenerateAuthorisationWorkflows))]
public class when_generate_workflows_is_called_with_a_dta_object_list_and_an_employee : specification_for_generate_workflows
{
static IList<Guid> result;
static IList<DtaObject> dtaObjects;
static Employee requestingEmployee;
Establish context = () =>
{
var mocks = new MockRepository();
var stubDtaObject1 = mocks.Stub<DtaObject>();
var stubDtaObject2 = mocks.Stub<DtaObject>();
var dtaObjectEnum = new List<DtaObject>{stubDtaObject1,stubDtaObject2}.GetEnumerator();
dtaObjects = mocks.Stub<IList<DtaObject>>();
dtaObjects.Stub(x => x.GetEnumerator()).Return(dtaObjectEnum).WhenCalled(x => x.ReturnValue = dtaObjectEnum);
requestingEmployee = mocks.Stub<Employee>();
mocks.ReplayAll();
};
Because of = () => result = subject.FromDtaObjects(dtaObjects, requestingEmployee);
It should_enumerate_the_dta_objects = () => dtaObjects.received(x=> x.GetEnumerator());
It should_call_workflow_host_helper = () => workflowService.AssertWasCalled(x => x.StartWorkflow());
}
With this configuration, my first test passes and my second test fails, as expected. I added a constructor to the class to inject the IWorkflowService.
public class GenerateAuthorisationWorkflows : IGenerateAuthorisationWorkflows
{
IWorkflowService _workflowService;
GenerateAuthorisationWorkflows(IWorkflowService workflowService)
{
_workflowService = workflowService;
}
public IList<Guid> FromDtaObjects(IList<DtaObject> dtaObjects, Employee requestingEmployee)
{
foreach (var dtaObject in dtaObjects)
{
Guid workflowKey = _workflowService.StartWorkflow();
}
return new List<Guid>();
}
public IList<Guid> FromDtaObjects()
{
return new List<Guid>();
}
}
Now, when I run the tests, they fail at the Because:
System.InvalidOperationException: Sequence contains no elements
at System.Linq.Enumerable.First(IEnumerable`1 source)
at MSpecTests.EmployeeRequestSystem.Tasks.Workflows.when_generate_workflows_is_called_with_a_dta_object_list_and_an_employee.<.ctor>b__4() in GenerateAuthorisationWorkflowsSpecs.cs: line 76
For clarity, line 76 above is:
Because of = () => result = subject.FromDtaObjects(dtaObjects, requestingEmployee);
I've tried tracing the problem but am having no luck. I have tried setting up a constructor that takes no arguments but it raises the same error. I have similar classes with IoC dependencies that work fine using MSpec/Rhino Mocks, where am I going wrong?
Castle Windsor requires a public constructor to instantiate a class. Adding public to the constructor allows correct operation.
public class GenerateAuthorisationWorkflows : IGenerateAuthorisationWorkflows
{
IWorkflowService _workflowService;
public GenerateAuthorisationWorkflows(IWorkflowService workflowService)
{
_workflowService = workflowService;
}
public IList<Guid> FromDtaObjects(IList<DtaObject> dtaObjects, Employee requestingEmployee)
{
foreach (var dtaObject in dtaObjects)
{
Guid workflowKey = _workflowService.StartWorkflow();
}
return new List<Guid>();
}
public IList<Guid> FromDtaObjects()
{
return new List<Guid>();
}
}
Rowan, looks like you answered your own question. It's good practice to explicitly state the access modifiers! By default, C# chooses private. These kinds of errors are easy to miss!
I can also see that your Establish block is too complicated. You're testing the implementation details and not the behavior. For example, you are
stubbing the GetEnumerator call that's implicitly made inside the foreach loop.
asserting that the workflow service was called only once
mixing MSpec automocking and your own local mocks
You're not actually testing that you got a GUID for every object in the input list. If I were you, I'd test the behavior like this...
public class GenerateAuthorisationWorkflows : IGenerateAuthorisationWorkflows
{
private readonly IWorkflowService _service;
public GenerateAuthorisationWorkflows(IWorkflowService service)
{
_service = service;
}
public List<Guid> FromDtaObjects(List<DtaObject> input, Employee requestor)
{
// I assume that the workflow service generates a new key
// per input object. So, let's pretend the call looks like
// this. Also using some LINQ to avoid the foreach or
// building up a local list.
input.Select(x => _service.StartWorkflow(requestor, x)).ToList();
}
}
[Subject(typeof(GenerateAuthorisationWorkflows))]
public class When_generating_authorisation_keys_for_this_input
: Specification<GenerateAuthorisationWorkflows>
{
private static IWorkflowService _service;
private static Employee _requestor = new Employee();
private static List<DtaObject> _input = new List<DtaObject>()
{
new DtaObject(),
new DtaObject(),
};
private static List<Guid> _expected = new List<Guid>()
{
Guid.NewGuid(),
Guid.NewGuid(),
};
private static List<Guid> _actual = new List<Guid>();
Establish context = () =>
{
// LINQ that takes each item from each list in a pair. So
// the service is stubbed to return a specific GUID per
// input DtaObject.
_input.Zip(_expected, (input, output) =>
{
DependencyOf<IWorkflowService>().Stub(x => x.StartWorkflow(_requestor, input)).Return(output);
});
};
Because of = () => _actual = Subject.FromDtaObjects(_input, _requestor);
// This should be an MSpec collection assertion that
// ensures that the contents of the collections are
// equivalent
It should_get_a_unique_key_per_input = _actual.ShouldEqual(_expected);
}
I've implemented my cascading dropdown list with MVC3 almost exactly as explained in
Easiest way to create a cascade dropdown in ASP.NET MVC 3 with C#
My view had that
<script type="text/javascript">
$(function () {
$('#CategoryID').change(function () {
var selectedCategoryId = $(this).val();
$.getJSON('#Url.Action("SelectCategory")', { categoryid: selectedCategoryId }, function (subcategories) {
var subsSelect = $('#SubCategoryID');
subsSelect.empty();
$.each(subcategories, function (index, subcat) {
subsSelect.append(
$('<option/>')
.attr('value', subcat.SubCategoryID)
.text(subcat.SubCategoryName)
);
});
});
});
});
</script>
My controller had that
public ActionResult SelectCategory(int categoryid)
{
var subs = db.SubCategories.Where(s => s.CategoryID == categoryid).ToList();
return Json(subs, JsonRequestBehavior.AllowGet);
}
And that did not work.
However, it worked when I modified the controller the following way:
public class JsonSubCat
{
public int SubCategoryID { get; set; }
public string SubCategoryName { get; set; }
}
public ActionResult SelectCategory(int categoryid)
{
var subs = db.SubCategories.Where(s => s.CategoryID == categoryid).ToList();
var testsubs = new List<JsonSubCat>();
foreach (var sub in subs)
{
testsubs.Add(new JsonSubCat() { SubCategoryID = sub.SubCategoryID, SubCategoryName = sub.SubCategoryName });
}
return Json(testsubs, JsonRequestBehavior.AllowGet);
}
Looks like a question of converting my entities that I obtain from data source to proper format.
What would be the correct way to implement this?
What would be the correct way to implement this?
I suspect that you have circular references in your domain entities. This is not supported by the JSON serializer, because the JSON format doesn't support circular structures.
You should not pass your domain models to views. Please stop doing this and use view models. Why are you passing your entire subs entity to the view when all that this view cares about is a collection of text and value? That's all a dropdown list needs.
So use view models, not to mention that you already wrote one => the JsonSubCat class which is great:
public ActionResult SelectCategory(int categoryid)
{
var subs = db.SubCategories
.Where(s => s.CategoryID == categoryid)
.ToList()
.Select(x => new JsonSubCat
{
SubCategoryID = x.SubCategoryID,
SubCategoryName = x.SubCategoryName
});
return Json(subs, JsonRequestBehavior.AllowGet);
}