I am writing a custom repoting module using ASP.NET MVC in C#
The user will be able to define a list of the fields they want to see in the report.
I would like to know if it is possible to reference a object field using a string, so that I can enumerate through the list of chosen fields.
for example normally in the view, quite basically I would do the following
#foreach (Title item in Model)
{
#item.Name
#item.Isbn
}
I would be looking for something like
#foreach (Title item in Model)
{
#item.Select("Name")
#item.Select("Isbn")
}
One of the ways to do that is through reflection. Add this helper method somewhere:
private object GetValueByPropertyName<T>(T obj, string propertyName)
{
PropertyInfo propInfo = typeof(T).GetProperty(propertyName);
return propInfo.GetValue(obj);
}
Usage:
#foreach (Title item in Model)
{
var name = GetValueByPropertyName(item, "Name");
var isbn = GetValueByPropertyName(item, "Isbn");
}
Well, i'd strongly recommend against using reflection in View as it breaks main principles of MVC pattern. YES, you should use reflection, but it is better to use it in controller. Let's have a look at simple and working example.
In controller we set up stub data to work with. And in action method About() we obtain a dynamic list of properties that user has selected :
class Title
{
// ctor that generates stub data
public Title()
{
Func<string> f = () => new string(Guid.NewGuid().ToString().Take(5).ToArray());
A = "A : " + f();
B = "B : " + f();
C = "C : " + f();
D = "D : " + f();
}
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
var data = new List<Title>()
{
new Title(), new Title(),
new Title(), new Title()
};
// list of properties to display for user
var fieldsSelectedByUser = new[] { "A", "C" };
// here we obtain a list of propertyinfos from Title class, that user requested
var propsInfo = typeof(Title).GetProperties().Where(p => fieldsSelectedByUser.Any(z => z == p.Name)).ToList();
// query that returns list of properties in List<List<object>> format
var result = data.Select(t => propsInfo.Select(pi => pi.GetValue(t, null)).ToList()).ToList();
return View(result);
}
...
}
And in view we can use it by simply iterating the collection :
#model List<List<object>>
<br/><br />
#foreach (var list in #Model)
{
foreach (var property in list)
{
<p> #property </p>
}
<br/><br />
}
P.S.
According to MVC pattern, view should utilize data returned by controller but should at no circumstances perform any business logic and comprehensive operations inside of it. If view need some data in some format - it should get this data returned by controller exactly in the format it needs.
I'm not experienced with asp, so I don't know for sure if this is possible in your specific context.
But normally you could use reflection for that. But you had to know if you are looking for properties or fields
For fields:
FieldInfo fi = item.GetType().GetField("Name", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
var value = fi.GetValue(item); // read a field
fi.SetValue(item, value); // set a field
For properties:
PropertyInfo pi = item.GetType().GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
var value = pi.GetValue(item); // read a property
pi.SetValue(item, value); // set a property
The word to google for is "Reflection" and most methods can be found in the Type class.
Related
I am using Razor Pages (not MVC) and keep getting the above error on the return statement. There is a similar question out there relating to the MVC model but the answer to that one is to change the class to "Controller". When I try that, page related things break. Any suggestions?
public class VehicleInfoPageModel : PageModel
{
public SelectList ModelNameSL { get; set; }
public JsonResult PopulateModelDropDownList(StockBook.Models.StockBookContext _context,
int selectedMakeID,
object selectedModelID = null)
{
var ModelIDsQuery = from m in _context.VehicleModel
orderby m.ModelID // Sort by ID.
where m.MakeID == selectedMakeID
select m;
ModelNameSL = new SelectList(ModelIDsQuery.AsNoTracking(),
"ModelID", "ModelName", selectedModelID);
return Json(ModelNameSL);
}
You're tried to use Json() method which derived from either System.Web.Mvc.JsonResult or System.Web.Http.ApiController.JsonResult instead of Microsoft.AspNetCore.Mvc.JsonResult namespace, they're all different namespaces. You should use the constructor of Microsoft.AspNetCore.Mvc.JsonResult to create JSON string instead:
public JsonResult PopulateModelDropDownList(StockBook.Models.StockBookContext _context, int selectedMakeID, object selectedModelID = null)
{
var ModelIDsQuery = from m in _context.VehicleModel
orderby m.ModelID // Sort by ID.
where m.MakeID == selectedMakeID
select m;
ModelNameSL = new SelectList(ModelIDsQuery.AsNoTracking(),
"ModelID", "ModelName", selectedModelID);
// return JSON string
return new JsonResult(ModelNameSL);
}
Reference: Working With JSON in Razor Pages
I am having trouble figuring out how to return multiple variables to a view. Something like this. Can I get a little help?
public ActionResult CheatSheet()
{
var var1 = from ts in db.thisdatabase
select ts;
var var2 = from ls in db.thisdatabase
select ls;
var var3 = from d in db.thisdatabase
select d;
return View(var1,var2,var3);
}
Consider Using a ViewModel
You'll want to use a ViewModel to compose all of these different results and pass that model to the View:
public class ExampleViewModel
{
// Example collections for each of your types
public IEnumerable<Something> CollectionA { get; set; }
public IEnumerable<Something> CollectionB { get; set; }
public IEnumerable<Something> CollectionC { get; set; }
}
And then just use something like the following code to perform your specific queries and use the results of those queries to build your model and then pass it along to the View:
// Build the model
var model = new ExampleViewModel(){
// You'll likely want a .ToList() after these to ensure things work as expected
CollectionA = db.thisdatabase.Select(x => x.ts),
CollectionB = db.thisdatabase.Select(x => x.ts),
CollectionC = db.thisdatabase.Select(x => x.ts),
};
// Pass it to your View
return View(model);
Note: This assumes that you aren't actually querying the same exact table with each of your queries. If that was the case, then it may be more efficient to pull back a single collection with each of your properties and then assign your individual collections of properties to the model (instead of performing multiple, possibly redundant, queries.
Then within the View, you can reference the underlying property collections as expected and iterate through them or perform any other types of operations:
#model YourProject.ViewModels.ExampleViewModel
#foreach (var item in Model.CollectionA) { ... }
#foreach (var item in Model.CollectionB) { ... }
#foreach (var item in Model.CollectionC) { ... }
For More Complex Scenarios
If you didn't want to simply access a single column from your database but rather multiple, you'll likely want to create another model/class to map your properties and then store instances of those within your ViewModel.
Let's look at your example and see how that might work. So you currently are looking to store the ts, ls and d properties, so let's make a class to store those in:
public class Example
{
public string Ts { get; set; }
public string Ls { get; set; }
public string D { get; set; }
}
Now, when you perform your query, simply grab all of these and map them within a Select() call:
// This will now be an IEnumerable<Example>
var models = db.thisdatabase.Select(x => new Example()
{
Ts = x.ts,
Ls = x.ls,
D = d
});
You could now pass this along directly to your View if that's all that you needed:
// If you did this, you'd need to adjust your #model declaration
return View(model);
Or you could perform multiple of these if you needed to build different models for different tables and then compose all of those collections into a ViewModel similar to the original example:
var model = new ExampleViewModel(){
CollectionA = db.thisdatabase.Select(x => new Example(x)),
CollectionB = db.othertable.Select(x => new OtherExample(x)),
CollectionC = db.yetanother.Select(x => new LastExample(x))
};
Other Forms Of Storage
There are a few other approaches you could consider depending on your needs, such as using the ViewBag collection. The ViewBag is a dynamic collection that allows you to easily store and access objects within the View:
ViewBag.A = db.thisdatabase.Select(x => x.ts),
ViewBag.B = db.thisdatabase.Select(x => x.ts),
ViewBag.C = db.thisdatabase.Select(x => x.ts),
return View();
This will essentially work the same way, but instead of references #Model within your View, you would just use #ViewBag instead. It's also worth noting that this can also be used in conjunction with an actual model as well.
There are other approaches such as using the ViewData or Session collections, but you really should use a model. It's more in line with the actual MVC pattern, and if you get used to it, it should make your life much easier.
You can pass dynamic model in you view using ExpandoObject.
Example:
Controller:
public ExpandoObject ToExpando( object anonymousObject)
{
IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
IDictionary<string, object> expando = new ExpandoObject();
foreach (var item in anonymousDictionary)
expando.Add(item);
return (ExpandoObject)expando;
}
public ActionResult CheatSheet()
{
var var1 = from ts in db.thisdatabase
select ts;
var var2 = from ls in db.thisdatabase
select ls;
var var3 = from d in db.thisdatabase
select d;
var model= ToExpando(new{ var1 =var1 ,var2 =var2 , var3 =var3})
return View(model);
}
View:
#foreach (var item in Model.var1 ) { ... }
#foreach (var item in Model.var2 ) { ... }
#foreach (var item in Model.var3 ) { ... }
I have a complex object that I've broken down to a ViewModel for form. I'll illustrate a sample of the structure but the number of fields are around 60 total from about 6 different entities intermingled.
//ViewModel
public class SomeViewModel
{
public TabOne TabOne {get;set;}
public TabTwo TabTwo {get;set;}
public SomeViewModel(ComplexObject co)
{
this.TabOne = new TabOne { Name = co.Name, Value = co.Value};
this.TabTwo = new TabTwo { Name = co.Name, Another = co.Another };
}
}
All this works fine and I get to the Controller from the View
//Controller
[HttpPost]
public ActionResult Update(SomeViewModel vm)
{
//TODO: 1. Know which properties in the ViewModel changed for auditing
//TODO: 2. Update all changed EF entities in the database.
}
Before sending the ViewModel to the View I cache the original Value in a Session object and use reflection to compare the two. It's drawn out and I'll have to do a ton of work for any changes. Is there a better way??
The ViewModel is different structure from the EF Models, so it's a bunch of custom mapping that will need to be done to Update the correct entities. I'd like to avoid that because of the number of fields. Is there a better way?
Ok, not sure if this is what you need but for what I understood:
You can get the original and current values like this:
using (var dbCtx = new YourDBEntities())
{
var yourEntity = dbCtx.YourEntity.Find(1);
var entry = dbCtx.Entry(yourEntity);
foreach (var propertyName in entry.CurrentValues.PropertyNames )
{
Console.WriteLine("Property Name: {0}", propertyName);
var originalVal = entry.OriginalValues[propertyName];
Console.WriteLine("Original Value: {0}", originalVal);
var currentVal = entry.CurrentValues[propertyName];
Console.WriteLine("Current Value: {0}", currentVal);
}
}
For mapping ViewModels to your EF Models you can use Automapper, it can be as easy as:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
var model = AutoMapper.Mapper.Map<BookViewModel>(book);
Edit:
Ok, if you need to do it manually, you can use the following method for mapping objects:
public static void MapObjects( object source, object destiny)
{
var modelPropertiesName = new HashSet<string>(source.GetType().GetProperties().Select(x => x.Name));
var entityProperties = destiny.GetType().GetProperties();
var propertyList = entityProperties.Where(p => modelPropertiesName.Contains(p.Name))
.ToList();
foreach (var prop in propertyList)
{
var modelProperty = source.GetType().GetProperty(prop.Name);
var value = modelProperty.GetValue(source);
prop.SetValue(destiny, value, null);
}
}
You just need to pass a destiny object and a source from where to map the properties.
I am busy in my homecontroller.cs (in a MVC Razor project) where I send a class looking like the following one to my view:
private class Evt
{
public SEvent Event { get; set; }
public String Duration { get; set; }
};
public ActionResult Index()
{
List<SEvent> modFromWcf = proxy.GetAllEventsByPeriod(#System.DateTime.Now.Year, #System.DateTime.Now.Year + 1, "EN").ToList();
List<Evt> modTosend = new List<Evt>();
foreach (SEvent item in modFromWcf)
{
Evt ES = new Evt();
ES.Event = item;
ES.Duration = GetDuration(item.StartDate ,item.EndDate);
modTosend.Add(ES);
};
return View("Index", modTosend);
}
SEvent is just another class.
In my view I try to do something with some of the attributes, but I do not know how to retrieve the values of that class Event. This is my code:
#{
foreach (var item in ViewData.Model) {
string year = item.Event.StartDate.Year.ToString();
}
....
}
The error that I receive is: 'object' does not contain a definition for 'Event'. But I CAN debug and see that item DOES consist ofa class Event and a string Duration. I can see the contents too. Does somebody know how I can retrieve e.g. item.Event.StartData??
You need to explicitly define item.
ViewData.Model will return a collection of objects, and objects do not have Event properties.
Something like this should work:
#{
foreach (var item in ViewData.Model) {
if (item is Evt){
Evt itemEvt = (Evt)item;
string year = itemEvt.Event.StartDate.Year.ToString();
}
}
//...
}
Though I usually do something like:
#{
foreach (Evt item in ViewData.Model) {
string year = item.Event.StartDate.Year.ToString();
}
//....
}
You need to cast item to your class.
ViewData is of type ViewDataDictionary, which implements IDictionary<string, Object>, indicating that it is a dictionary matching keys of type string to values of type object.
Use this to make your code run as is:
#{
foreach (var item in ViewData.Model) {
string year = ((Evt)item).Event.StartDate.Year.ToString();
}
....
}
However, you should probably be using a strongly typed view for this instead of shoving data into ViewData for this.
If I have a controller like this:
[HttpPost]
public JsonResult FindStuff(string query)
{
var results = _repo.GetStuff(query);
var jsonResult = results.Select(x => new
{
id = x.Id,
name = x.Foo,
type = x.Bar
}).ToList();
return Json(jsonResult);
}
Basically, I grab stuff from my repository, then project it into a List<T> of anonymous types.
How can I unit-test it?
System.Web.Mvc.JsonResult has a property called Data, but it's of type object, as we expected.
So does that mean if I want to test that the JSON object has the properties I expect ("id", "name", "type"), I have to use reflection?
EDIT:
Here's my test:
// Arrange.
const string autoCompleteQuery = "soho";
// Act.
var actionResult = _controller.FindLocations(autoCompleteQuery);
// Assert.
Assert.IsNotNull(actionResult, "No ActionResult returned from action method.");
dynamic jsonCollection = actionResult.Data;
foreach (dynamic json in jsonCollection)
{
Assert.IsNotNull(json.id,
"JSON record does not contain \"id\" required property.");
Assert.IsNotNull(json.name,
"JSON record does not contain \"name\" required property.");
Assert.IsNotNull(json.type,
"JSON record does not contain \"type\" required property.");
}
But I get a runtime error in the loop, stating "object does not contain a definition for id".
When I breakpoint, actionResult.Data is defined as a List<T> of anonymous types, so I figure if I enumerate through these, I can check the properties. Inside the loop, the object does have a property called "id" - so not sure what the issue is.
I know I'm a bit late on this guys, but I found out why the dynamic solution wasn't working:
JsonResult returns an anonymous object and these are, by default, internal, so they need to be made visible to the tests project.
Open your ASP.NET MVC application project and find AssemblyInfo.cs from folder called Properties. Open AssemblyInfo.cs and add the following line to the end of this file.
[assembly: InternalsVisibleTo("MyProject.Tests")]
Quoted from: http://weblogs.asp.net/gunnarpeipman/archive/2010/07/24/asp-net-mvc-using-dynamic-type-to-test-controller-actions-returning-jsonresult.aspx
I thought it would be nice to have this one for the record. Works like a charm
RPM, you look to be correct. I still have much to learn about dynamic and I cannot get Marc's approach to work either. So here is how I was doing it before. You may find it helpful. I just wrote a simple extension method:
public static object GetReflectedProperty(this object obj, string propertyName)
{
obj.ThrowIfNull("obj");
propertyName.ThrowIfNull("propertyName");
PropertyInfo property = obj.GetType().GetProperty(propertyName);
if (property == null)
{
return null;
}
return property.GetValue(obj, null);
}
Then I just use that to do assertions on my Json data:
JsonResult result = controller.MyAction(...);
...
Assert.That(result.Data, Is.Not.Null, "There should be some data for the JsonResult");
Assert.That(result.Data.GetReflectedProperty("page"), Is.EqualTo(page));
I'm a bit late to the party, but I created a little wrapper that lets me then use dynamic properties. As of this answer I've got this working on ASP.NET Core 1.0 RC2, but I believe if you replace resultObject.Value with resultObject.Data it should work for non-core versions.
public class JsonResultDynamicWrapper : DynamicObject
{
private readonly object _resultObject;
public JsonResultDynamicWrapper([NotNull] JsonResult resultObject)
{
if (resultObject == null) throw new ArgumentNullException(nameof(resultObject));
_resultObject = resultObject.Value;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (string.IsNullOrEmpty(binder.Name))
{
result = null;
return false;
}
PropertyInfo property = _resultObject.GetType().GetProperty(binder.Name);
if (property == null)
{
result = null;
return false;
}
result = property.GetValue(_resultObject, null);
return true;
}
}
Usage, assuming the following controller:
public class FooController : Controller
{
public IActionResult Get()
{
return Json(new {Bar = "Bar", Baz = "Baz"});
}
}
The test (xUnit):
// Arrange
var controller = new FoosController();
// Act
var result = await controller.Get();
// Assert
var resultObject = Assert.IsType<JsonResult>(result);
dynamic resultData = new JsonResultDynamicWrapper(resultObject);
Assert.Equal("Bar", resultData.Bar);
Assert.Equal("Baz", resultData.Baz);
Here's one I use, perhaps it is of use to anyone. It tests an action that returns a JSON object for use in clientside functionality. It uses Moq and FluentAssertions.
[TestMethod]
public void GetActivationcode_Should_Return_JSON_With_Filled_Model()
{
// Arrange...
ActivatiecodeController activatiecodeController = this.ActivatiecodeControllerFactory();
CodeModel model = new CodeModel { Activation = "XYZZY", Lifespan = 10000 };
this.deviceActivatieModelBuilder.Setup(x => x.GenereerNieuweActivatiecode()).Returns(model);
// Act...
var result = activatiecodeController.GetActivationcode() as JsonResult;
// Assert...
((CodeModel)result.Data).Activation.Should().Be("XYZZY");
((CodeModel)result.Data).Lifespan.Should().Be(10000);
}
I extend the solution from Matt Greer and come up with this small extension:
public static JsonResult IsJson(this ActionResult result)
{
Assert.IsInstanceOf<JsonResult>(result);
return (JsonResult) result;
}
public static JsonResult WithModel(this JsonResult result, object model)
{
var props = model.GetType().GetProperties();
foreach (var prop in props)
{
var mv = model.GetReflectedProperty(prop.Name);
var expected = result.Data.GetReflectedProperty(prop.Name);
Assert.AreEqual(expected, mv);
}
return result;
}
And i just run the unittest as this:
- Set the expected data result:
var expected = new
{
Success = false,
Message = "Name is required"
};
- Assert the result:
// Assert
result.IsJson().WithModel(expected);
My solution is to write the extension method:
using System.Reflection;
using System.Web.Mvc;
namespace Tests.Extensions
{
public static class JsonExtensions
{
public static object GetPropertyValue(this JsonResult json, string propertyName)
{
return json.Data.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public).GetValue(json.Data, null);
}
}
}
If in the test you know what exactly the Json data result should be then you can just do something like this:
result.Data.ToString().Should().Be(new { param = value}.ToString());
P.S. This would be if you had used FluentAssertions.Mvc5 - but it shouldn't be hard to convert it to whatever testing tools you use.
This is how I assert it
foreach (var item in jsonResult.Data as dynamic) {
((int)item.Id).ShouldBe( expected Id value );
((string)item.name).ShouldBe( "expected name value" );
}
You can convert the value property of the JsonResult into an instance of a known type and then simply access it's properties.
public static class JsonResultExtensions
{
public static T ExtractType<T>(this JsonResult result)
{
var resultAsJson = JsonSerializer.Serialize(result.Value);
return JsonSerializer.Deserialize<T>(resultAsJson);
}
}
below is an example of the use of the extension method:
MyModel model = jsonResult.ExtractType<MyModel>();
Assert.True(model.Success);