Loop through fields of two single objects entities - asp.net-mvc

[HttpPost]
public ActionResult Edit(orders neworders)
{
string modifields;
orders oldorders = HDB.orders.Where(c => c.id_order == cmd.id_order).First();
foreach (var field in oldorders)
{
if(field!= neworders.samefield)
{
modifields+=nameoffield ;
}
}
}
Q How can i compare a field from the new and from the old record and save the name of the field if the value is changed?

First() returns a single object, so can't be iterated over.
Reading between the lines it looks like you want to iterate over all the fields on the object. In that case you can use reflection to return a list of fields which you can then iterate over.
foreach (var field in oldorders.GetType().GetFields())
{
var value = field.GetValue(oldorders);
}
EDIT: from your other comments it looks like you are wanting to compare the object against another object, in which case you are better off implementing the IEquatable<T> interface. E.g.
class Order : IEquatable<Order>
{
// TODO: add properties of an Order
public string foo { get; set; }
public string bar { get; set; }
public bool Equals(Order other)
{
foreach (var field in typeof(Order).GetFields())
{
if (field.GetValue(this) != field.GetValue(other))
{
return false;
}
}
foreach (var property in typeof(Order).GetProperties())
{
if (property.GetValue(this, null) != property.GetValue(other, null))
{
return false;
}
}
return true;
}
}

An example of the Equals() method override to compare instances of the same class
public override bool Equals(object anObject)
{
if (anObject is Order)
{
Order newOrder = (Order)anObject;
if (newOrder.Name.Equals(this.Name)) //add additional comparisons as needed. You could also use a foreach loop here as Tony shows below
{
return true;
}
foreach (var field in typeof(Order).GetFields())
{
if (field.GetValue(this) != field.GetValue(newOrder)) { return false; }
}
foreach (var property in typeof(Order).GetProperties())
{
if (property.GetValue(this, null) != property.GetValue(newOrder, null)) { return false; }
}
}
return false;
}
Here's how to call in your action:
[HttpPost] public ActionResult Edit(orders neworder)
{
orders oldorder = HDB.orders.Where(c => c.id_order == cmd.id_order).First();
if(oldorder.Equals(newOrder)
{
//do wome work
}
}

I would say your orders class doesn't implement the IEnumerable interface properly. In order to use foreach in this manner you must implement the interface and at least account for all of the methods required to support it.
http://msdn.microsoft.com/en-us/library/9eekhta0.aspx

Your oldrders variable is not Enumerable. The First() method returns a sinlge object. Remove the .First() and you will have an Enumerable object which the foreach loop can use

Related

ASP.NET Core [FromBody] vs MVC 5 binding

I got an MVC 5 application that i'm porting to asp.net Core.
In the MVC application call to controller we're made using AngularJS $resource (sending JSON) and we we're POSTing data doing :
ressource.save({ entries: vm.entries, projectId: vm.project.id }).$promise...
that will send a JSON body like:
{
entries:
[
{
// lots of fields
}
],
projectId:12
}
the MVC controller looked like this :
[HttpPost]
public JsonResult Save(List<EntryViewModel> entries, int projectId) {
// code here
}
How can I replicate the same behaviour with .NET Core since we can't have multiple [FromBody]
you cannot have multiple parameter with the FromBody attibute in an action method. If that is need, use a complex type such as a class with properties equivalent to the parameter or dynamic type like that
[HttpPost("save/{projectId}")]
public JsonResult Save(int projectId, [FromBody] dynamic entries) {
// code here
}
As pointed out in the comment, one possible solution is to unify the properties you're posting onto a single model class.
Something like the following should do the trick:
public class SaveModel
{
public List<EntryViewModel> Entries{get;set;}
public int ProjectId {get;set;}
}
Don't forget to decorate the model with the [FromBody] attribute:
[HttpPost]
public JsonResult Save([FromBody]SaveViewModel model)
{
// code here
}
Hope this helps!
It's still rough but I made a Filter to mimic the feature.
public class OldMVCFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Method != "GET")
{
var body = context.HttpContext.Request.Body;
JToken token = null;
var param = context.ActionDescriptor.Parameters;
using (var reader = new StreamReader(body))
using (var jsonReader = new JsonTextReader(reader))
{
jsonReader.CloseInput = false;
token = JToken.Load(jsonReader);
}
if (token != null)
{
var serializer = new JsonSerializer();
serializer.DefaultValueHandling = DefaultValueHandling.Populate;
serializer.FloatFormatHandling = FloatFormatHandling.DefaultValue;
foreach (var item in param)
{
JToken model = token[item.Name];
if (model == null)
{
// try to cast the full body as the current object
model = token.Root;
}
if (model != null)
{
model = this.RemoveEmptyChildren(model, item.ParameterType);
var res = model.ToObject(item.ParameterType, serializer);
context.ActionArguments[item.Name] = res;
}
}
}
}
}
private JToken RemoveEmptyChildren(JToken token, Type type)
{
var HasBaseType = type.GenericTypeArguments.Count() > 0;
List<PropertyInfo> PIList = new List<PropertyInfo>();
if (HasBaseType)
{
PIList.AddRange(type.GenericTypeArguments.FirstOrDefault().GetProperties().ToList());
}
else
{
PIList.AddRange(type.GetTypeInfo().GetProperties().ToList());
}
if (token != null)
{
if (token.Type == JTokenType.Object)
{
JObject copy = new JObject();
foreach (JProperty jProp in token.Children<JProperty>())
{
var pi = PIList.FirstOrDefault(p => p.Name == jProp.Name);
if (pi != null) // If destination type dont have this property we ignore it
{
JToken child = jProp.Value;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, pi.PropertyType);
}
if (!IsEmpty(child))
{
if (child.Type == JTokenType.Object || child.Type == JTokenType.Array)
{
// nested value has been checked, we add the object
copy.Add(jProp.Name, child);
}
else
{
if (!pi.Name.ToLowerInvariant().Contains("string"))
{
// ignore empty value when type is not string
var Val = (string)child;
if (!string.IsNullOrWhiteSpace(Val))
{
// we add the property only if it contain meningfull data
copy.Add(jProp.Name, child);
}
}
}
}
}
}
return copy;
}
else if (token.Type == JTokenType.Array)
{
JArray copy = new JArray();
foreach (JToken item in token.Children())
{
JToken child = item;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, type);
}
if (!IsEmpty(child))
{
copy.Add(child);
}
}
return copy;
}
return token;
}
return null;
}
private bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined);
}
}

Add Data to a model

I have created a model with a List in it
namespace OMIv2._1KSWilson.Models
{
public class LegendClassVM
{
public List <string> legendValues { get; set; }
}
}
then i have a foreach loop that creates strings that i want to use.
List<string> values = new List<string>();
foreach (Feature f in allFeatures)
{
if (f.ColumnValues.ContainsKey(layercode))
{
if (!values.Contains(f.ColumnValues[layercode].ToString()))
{
values.Add(f.ColumnValues[layercode].ToString());
}
}
}
how would i add these items to the model list. this code executes five times so i need to store alot of data.
any suggestions would be greatly appreciated
try with this
public ActionResult Index()
{
LegendClassVM model = new LegendClassVM();
model.legendValues = new List <string>();
foreach (Feature f in allFeatures)
{
if (f.ColumnValues.ContainsKey(layercode))
{
if (!values.Contains(f.ColumnValues[layercode].ToString()))
{
model.legendValues.Add(f.ColumnValues[layercode].ToString());
}
}
}
return View(model);
}

How to do server-side validation using DataAnnotations

I am using ASP.NET MVC 5 and trying to do custom validation using data annotations of a view model object that is instantiated within the controller post method and reloaded from EF. Only a few of the model properties are populated when posting from the client and the rest are reloaded using EF. The model is complex and all parts of it implement IValidatableObject. The code within the Validate method fires for the new view model after everything is loaded correctly, but it does not do anything with the data annotations applied to the model.
So for instance, if my model has the EmailAddress attribute applied to a field, it works great on the client side, but it is ignored on the server side when validating against the new view model. How do I get my Validate method to take the data annotations into account?
Here's the code for the controller action...
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult SignUp(SignUpEM em) {
if (em == null) {
return RedirectToAction(MVC.Sheet.Index());
}
SignUpSheet signUpSheet = db.GetSignUpSheet(em.SheetId);
SignUpEM newEM = Map.ToSignUpEM(signUpSheet);
foreach (var sourceField in em.Fields) {
FieldEM targetField = newEM.Fields.FirstOrDefault(f => f.FieldDefId == sourceField.FieldDefId && f.Id == sourceField.Id);
if (targetField.FieldType.IsEditable()) {
if (targetField.FieldType.IsBoolean()) {
((BooleanFieldEM)targetField).BooleanValue = ((BooleanFieldEM)sourceField).BooleanValue;
}
else {
targetField.Value = sourceField.Value;
}
targetField.SetFullyLoaded();
}
}
// This line is here to simulate an invalid value passed into an email field.
newEM.Fields[0].Value = "invalid";
newEM.SetFullyLoaded();
ModelState.Clear();
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(newEM, new ValidationContext(newEM, null, null), validationResults, true);
foreach (var result in validationResults) {
if (result == null) { continue; } // otherwise we need to avoid duplicates.
if (result.MemberNames.Count() == 0)
ModelState.AddModelError(String.Empty, result.ErrorMessage);
else
foreach (var name in result.MemberNames)
ModelState.AddModelError(name, result.ErrorMessage);
}
if (!ModelState.IsValid) {
return View(newEM);
}
SignUp su = Map.ToSignUp(em);
db.AddSignUp(su);
return RedirectToAction(MVC.SignUp.SignUp(em.SheetId));
}
Here's the validation code for SignUpEM...
private bool fullyLoaded;
internal void SetFullyLoaded() {
fullyLoaded = true;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (fullyLoaded) {
foreach (var f in Fields)
foreach (var vr in f.Validate(validationContext))
yield return vr;
if (ListIntro == null)
yield return new ValidationResult("ListIntro is required for now.");
}
}
And the validation code for FieldEM...
private bool fullyLoaded;
internal void SetFullyLoaded() {
fullyLoaded = true;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (fullyLoaded) {
if (Optional == false && String.IsNullOrEmpty(Value)) {
string message = string.Format("{0} is required.", DisplayName);
yield return new ValidationResult(message);
}
}
}

Get custom attribute for parameter when model binding

I've seen a lot of similar posts on this, but haven't found the answer specific to controller parameters.
I've written a custom attribute called AliasAttribute that allows me to define aliases for parameters during model binding. So for example if I have: public JsonResult EmailCheck(string email) on the server and I want the email parameter to be bound to fields named PrimaryEmail or SomeCrazyEmail I can "map" this using the aliasattribute like this: public JsonResult EmailCheck([Alias(Suffix = "Email")]string email).
The problem: In my custom model binder I can't get a hold of the AliasAttribute class applied to the email parameter. It always returns null.
I've seen what the DefaultModelBinder class is doing to get the BindAttribute in reflector and its the same but doesn't work for me.
Question: How do I get this attribute during binding?
AliasModelBinder:
public class AliasModelBinder : DefaultModelBinder
{
public static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var descriptor = GetTypeDescriptor(bindingContext.ModelType);
/*************************/
// this next statement returns null!
/*************************/
AliasAttribute attr = (AliasAttribute)descriptor.GetAttributes()[typeof(AliasAttribute)];
if (attr == null)
return null;
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var key in request.Form.AllKeys)
{
if (string.IsNullOrEmpty(attr.Prefix) == false)
{
if (key.StartsWith(attr.Prefix, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
return request.Form.Get(key);
}
}
else if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
if (attr.HasIncludes)
{
foreach (var include in attr.InlcludeSplit)
{
if (key.Equals(include, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(include);
}
}
}
}
return null;
}
}
AliasAttribute:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
private string _include;
private string[] _inlcludeSplit = new string[0];
public string Prefix { get; set; }
public string Suffix { get; set; }
public string Include
{
get
{
return _include;
}
set
{
_include = value;
_inlcludeSplit = SplitString(_include);
}
}
public string[] InlcludeSplit
{
get
{
return _inlcludeSplit;
}
}
public bool HasIncludes { get { return InlcludeSplit.Length > 0; } }
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage:
public JsonResult EmailCheck([ModelBinder(typeof(AliasModelBinder)), Alias(Suffix = "Email")]string email)
{
// email will be assigned to any field suffixed with "Email". e.g. PrimaryEmail, SecondaryEmail and so on
}
Gave up on this and then stumbled across the Action Parameter Alias code base that will probably allow me to do this. It's not as flexible as what I started out to write but probably can be modified to allow wild cards.
what I did was make my attribute subclass System.Web.Mvc.CustomModelBinderAttribute which then allows you to return a version of your custom model binder modified with the aliases.
example:
public class AliasAttribute : System.Web.Mvc.CustomModelBinderAttribute
{
public AliasAttribute()
{
}
public AliasAttribute( string alias )
{
Alias = alias;
}
public string Alias { get; set; }
public override IModelBinder GetBinder()
{
var binder = new AliasModelBinder();
if ( !string.IsNullOrEmpty( Alias ) )
binder.Alias = Alias;
return binder;
}
}
which then allows this usage:
public ActionResult Edit( [Alias( "somethingElse" )] string email )
{
// ...
}

Recording changed values with DBContext Entry.OriginalValues and Entry.NewValues

I have a document library site and would like to send an email when a document object is edited, containing a summary of the changes.
The database interaction is Code First Entities Framework using DBContext
Here is what I have so far:
[HttpPost]
public ActionResult Edit(Document document, bool sendEmail, string commentsTextBox)
{
if (ModelState.IsValid)
{
docsDB.Entry(document).State = EntityState.Modified;
foreach (string propertyName in docsDB.Entry(document).OriginalValues.PropertyNames)
{
var OriginalValue = docsDB.Entry(document).OriginalValues.GetValue<object>(propertyName);
var NewValue = docsDB.Entry(document).CurrentValues.GetValue<object>(propertyName);
if (!OriginalValue.Equals(NewValue))
{
//capture the changes
}
}
docsDB.SaveChanges();
if (sendEmail)
{
//sends email
}
return RedirectToAction("Index");
}
However, OriginalValue and NewValue are always the same -- the values of the update.
Is there any way, short of something hacky like writing to a file, to capture the state of the document before the POST?
For the comparison of the updated properties with the values in the database you must reload the document from the database. You can try it this way:
[HttpPost]
public ActionResult Edit(Document document, bool sendEmail,
string commentsTextBox)
{
if (ModelState.IsValid)
{
var documentInDB = docsDB.Documents.Single(d => d.Id == document.Id);
docsDB.Entry(documentInDB).CurrentValues.SetValues(document);
foreach (string propertyName in docsDB.Entry(documentInDB)
.OriginalValues.PropertyNames)
{
var OriginalValue = docsDB.Entry(documentInDB)
.OriginalValues.GetValue<object>(propertyName);
var NewValue = docsDB.Entry(documentInDB)
.CurrentValues.GetValue<object>(propertyName);
if (!OriginalValue.Equals(NewValue))
{
//capture the changes
}
}
docsDB.SaveChanges();
if (sendEmail)
{
//sends email
}
return RedirectToAction("Index");
}
// ...
}

Resources