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.
Related
I have this dropdown that should contain a list of items i.e.
Debug
Information
Warning
Danger
This is how I obtain the list
public IEnumerable<string> GetLogLevels()
{
var data = dbContext.EventLogs.Distinct().ToList();
var modifiedData = data.Select(u => u.Level);
return modifiedData;
}
This is my controller and viewmodel
public IActionResult Index()
{
var levels = new SelectList(logsData.GetLogLevels(),"Level","Level");
var llvm = new LevelsListViewModel
{
Levels = levels
};
return View(llvm);
}
public class LevelsListViewModel
{
public SelectList Levels { get; set; }
}
This is how I declare it in my view
var level = "<div class='col-sm-3 search-spacing'><label for='Level'>Level</label><select asp-for='Level' asp-items='#Model.Levels'></select></div>";
The problem now is that for some reason it loads an empty list. When I debug the controller, I can see my values.
On a sidenote, is that the correct way of obtaining one field and populating the list? I want a distinct value only.
The problem lies in this line:
var levels = new SelectList(logsData.GetLogLevels(),"Level","Level");
The GetLogLevels method returns a collection of string, which does not contain a Level property.
There's two ways of resolving this:
change the return type of the GetLogLevels method so it returns a collection of event logs which have a Level property; or
use a different contructor of the SelectList class which only takes a collection of objects without specifying the value property name nor the text property name, like var levels = new SelectList(logsData.GetLogLevels());
This line causing problem because GetLogLevels() method returns IEnumerable<string>, which doesn't have property named Level in its context:
var levels = new SelectList(logsData.GetLogLevels(),"Level","Level");
There are some solutions to solve this issue:
1) Converting directly to IEnumerable<SelectListItem>
// Controller action
var levels = logsData.GetLogLevels().Select(x => new SelectListItem { Text = x, Value = x }).ToList();
// Model class
public class LevelsListViewModel
{
public List<SelectListItem> Levels { get; set; }
}
2) Using SelectList overload with single parameter
var levels = new SelectList(logsData.GetLogLevels());
3) Using ToDictionary()
var levels = new SelectList(logsData.GetLogLevels().ToDictionary(), "Key", "Value");
Note: If EventLogs contains multiple columns, you can't use Distinct() on that way. You should use GroupBy and Select first distinct values:
var data = dbContext.EventLogs.GroupBy(x => x.Level).Select(gr => gr.FirstOrDefault()).ToList();
return data;
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 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.
I am trying to write some logic to get entity values from the database based on a dynamic table value. Following this post has gotten me to the point of TEntity Entity framework - get entity by name
I not following what is being said here. The following code:
public static void PublishTable(string user, ContentFields tableToPublish)
{
var jsonData = DataAccess.GetEnvironmentJson((int)Environments.Production);
var model = JsonConvert.DeserializeObject<Models.FullDataSetModel>(jsonData);
using (var db = new LoginPageContentEntities())
{
var deployEntry = db.Deployment.Find((int)Environments.Production);
deployEntry.DateUpdated = DateTime.Now;
var pendingChangesFlag = deployEntry.GetType().GetProperty(tableToPublish.ToString());
pendingChangesFlag.SetValue(deployEntry, false);
var publishToModel = model.GetType().GetProperty(tableToPublish.ToString());
var tableValue = (IEnumerable<????>)typeof(LoginPageContentEntities).GetProperty(tableToPublish.ToString()).GetValue(db, null));
publishToModel.SetValue(model, tableValue.ToList());
var json = JsonConvert.SerializeObject(model);
deployEntry.JsonCache = json;
db.SaveChanges();
}
}
For example if I pass the entity of ProductInformation in IEnumerable<ProductInformation> this works when I alter that specific entity.
Runs up to the section of the cast, I do not follow what needs to be done to get the values from the context. They are defining TEntity but there is nothing in the definition of it.
Your question seems to be - What is TEntity?
TEntity is generic. From the link provided in the comment https://msdn.microsoft.com/en-us/library/ms379564%28v=vs.80%29.aspx#csharp_generics_topic2
See the code block
public class Stack<T>
{
T[] m_Items;
public void Push(T item)
{...}
public T Pop()
{...}
}
T is specifying generic. You can plugin class - like the one you have ProductInformation while instantiating Stack. For example -
var stk = new Stack<ProductInformation>();
I managed to solve my issue with some help from the other answers, however i'm posting my solution as it applied to working with EF.
public static void PublishTable<TEntity>()
{
var targetEntity = typeof(TEntity).Name;
var model = JsonConvert.DeserializeObject<Models.FullDataSetModel>(DataAccess.GetEnvironmentJson((int)Environments.Production));
using (var db = new LoginPageContentEntities())
{
var deployEntry = db.Deployment.Find((int)Environments.Production);
deployEntry.DateUpdated = DateTime.Now;
deployEntry.GetType().GetProperty(targetEntity).SetValue(deployEntry, false);
model.GetType().GetProperty(targetEntity).SetValue(model, ((IEnumerable<TEntity>)(typeof(LoginPageContentEntities).GetProperty(targetEntity).GetValue(db, null))).ToList());
deployEntry.JsonCache = JsonConvert.SerializeObject(model);
db.SaveChanges();
}
}
I am trying to do a simple JSON return but I am having issues I have the following below.
public JsonResult GetEventData()
{
var data = Event.Find(x => x.ID != 0);
return Json(data);
}
I get a HTTP 500 with the exception as shown in the title of this question. I also tried
var data = Event.All().ToList()
That gave the same problem.
Is this a bug or my implementation?
It seems that there are circular references in your object hierarchy which is not supported by the JSON serializer. Do you need all the columns? You could pick up only the properties you need in the view:
return Json(new
{
PropertyINeed1 = data.PropertyINeed1,
PropertyINeed2 = data.PropertyINeed2
});
This will make your JSON object lighter and easier to understand. If you have many properties, AutoMapper could be used to automatically map between DTO objects and View objects.
I had the same problem and solved by using Newtonsoft.Json;
var list = JsonConvert.SerializeObject(model,
Formatting.None,
new JsonSerializerSettings() {
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});
return Content(list, "application/json");
This actually happens because the complex objects are what makes the resulting json object fails.
And it fails because when the object is mapped it maps the children, which maps their parents, making a circular reference to occur. Json would take infinite time to serialize it, so it prevents the problem with the exception.
Entity Framework mapping also produces the same behavior, and the solution is to discard all unwanted properties.
Just expliciting the final answer, the whole code would be:
public JsonResult getJson()
{
DataContext db = new DataContext ();
return this.Json(
new {
Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
}
, JsonRequestBehavior.AllowGet
);
}
It could also be the following in case you don't want the objects inside a Result property:
public JsonResult getJson()
{
DataContext db = new DataContext ();
return this.Json(
(from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
, JsonRequestBehavior.AllowGet
);
}
To sum things up, there are 4 solutions to this:
Solution 1: turn off ProxyCreation for the DBContext and restore it in the end.
private DBEntities db = new DBEntities();//dbcontext
public ActionResult Index()
{
bool proxyCreation = db.Configuration.ProxyCreationEnabled;
try
{
//set ProxyCreation to false
db.Configuration.ProxyCreationEnabled = false;
var data = db.Products.ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
finally
{
//restore ProxyCreation to its original state
db.Configuration.ProxyCreationEnabled = proxyCreation;
}
}
Solution 2: Using JsonConvert by Setting ReferenceLoopHandling to ignore on the serializer settings.
//using using Newtonsoft.Json;
private DBEntities db = new DBEntities();//dbcontext
public ActionResult Index()
{
try
{
var data = db.Products.ToList();
JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);
return Json(result, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
Following two solutions are the same, but using a model is better because it's strong typed.
Solution 3: return a Model which includes the needed properties only.
private DBEntities db = new DBEntities();//dbcontext
public class ProductModel
{
public int Product_ID { get; set;}
public string Product_Name { get; set;}
public double Product_Price { get; set;}
}
public ActionResult Index()
{
try
{
var data = db.Products.Select(p => new ProductModel
{
Product_ID = p.Product_ID,
Product_Name = p.Product_Name,
Product_Price = p.Product_Price
}).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
Solution 4: return a new dynamic object which includes the needed properties only.
private DBEntities db = new DBEntities();//dbcontext
public ActionResult Index()
{
try
{
var data = db.Products.Select(p => new
{
Product_ID = p.Product_ID,
Product_Name = p.Product_Name,
Product_Price = p.Product_Price
}).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
JSON, like xml and various other formats, is a tree-based serialization format. It won't love you if you have circular references in your objects, as the "tree" would be:
root B => child A => parent B => child A => parent B => ...
There are often ways of disabling navigation along a certain path; for example, with XmlSerializer you might mark the parent property as XmlIgnore. I don't know if this is possible with the json serializer in question, nor whether DatabaseColumn has suitable markers (very unlikely, as it would need to reference every serialization API)
add [JsonIgnore] to virtuals properties in your model.
Using Newtonsoft.Json: In your Global.asax Application_Start method add this line:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Its because of the new DbContext T4 template that is used for generating the EntityFramework entities. In order to be able to perform the change tracking, this templates uses the Proxy pattern, by wrapping your nice POCOs with them. This then causes the issues when serializing with the JavaScriptSerializer.
So then the 2 solutions are:
Either you just serialize and return the properties you need on the client
You may switch off the automatic generation of proxies by setting it on the context's configuration
context.Configuration.ProxyCreationEnabled = false;
Very well explained in the below article.
http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/
Provided answers are good, but I think they can be improved by adding an "architectural" perspective.
Investigation
MVC's Controller.Json function is doing the job, but it is very poor at providing a relevant error in this case. By using Newtonsoft.Json.JsonConvert.SerializeObject, the error specifies exactly what is the property that is triggering the circular reference. This is particularly useful when serializing more complex object hierarchies.
Proper architecture
One should never try to serialize data models (e.g. EF models), as ORM's navigation properties is the road to perdition when it comes to serialization. Data flow should be the following:
Database -> data models -> service models -> JSON string
Service models can be obtained from data models using auto mappers (e.g. Automapper). While this does not guarantee lack of circular references, proper design should do it: service models should contain exactly what the service consumer requires (i.e. the properties).
In those rare cases, when the client requests a hierarchy involving the same object type on different levels, the service can create a linear structure with parent->child relationship (using just identifiers, not references).
Modern applications tend to avoid loading complex data structures at once and service models should be slim. E.g.:
access an event - only header data (identifier, name, date etc.) is loaded -> service model (JSON) containing only header data
managed attendees list - access a popup and lazy load the list -> service model (JSON) containing only the list of attendees
Avoid converting the table object directly. If relations are set between other tables, it might throw this error.
Rather, you can create a model class, assign values to the class object and then serialize it.
I'm Using the fix, Because Using Knockout in MVC5 views.
On action
return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));
function
public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
{
TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
foreach (var item in Entity.GetType().GetProperties())
{
if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
item.SetValue(Entity_, Entity.GetPropValue(item.Name));
}
return Entity_;
}
You can notice the properties that cause the circular reference. Then you can do something like:
private Object DeCircular(Object object)
{
// Set properties that cause the circular reference to null
return object
}
//first: Create a class as your view model
public class EventViewModel
{
public int Id{get;set}
public string Property1{get;set;}
public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
var events = await db.Event.Find(x => x.ID != 0);
List<EventViewModel> model = events.Select(event => new EventViewModel(){
Id = event.Id,
Property1 = event.Property1,
Property1 = event.Property2
}).ToList();
return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}
An easier alternative to solve this problem is to return an string, and format that string to json with JavaScriptSerializer.
public string GetEntityInJson()
{
JavaScriptSerializer j = new JavaScriptSerializer();
var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
return j.Serialize(entityList );
}
It is important the "Select" part, which choose the properties you want in your view. Some object have a reference for the parent. If you do not choose the attributes, the circular reference may appear, if you just take the tables as a whole.
Do not do this:
public string GetEntityInJson()
{
JavaScriptSerializer j = new JavaScriptSerializer();
var entityList = dataContext.Entitites.toList();
return j.Serialize(entityList );
}
Do this instead if you don't want the whole table:
public string GetEntityInJson()
{
JavaScriptSerializer j = new JavaScriptSerializer();
var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
return j.Serialize(entityList );
}
This helps render a view with less data, just with the attributes you need, and makes your web run faster.