I have the following class definition:
public class CallGroupViewModel
{
public string Name { get; set; }
public string BulkEmails { get; set; }
public List<dynamic> Members {get;set;}
}
The Members property is some dynamic knockout code and the contents change depending on UI stuff. I am wanting to take a short cut since the UI is so up in the air right now.
My problem is that when it get to my action method, everything gets populate, including Members but its a array of objects and I can't get at the individual properties on them.
public ActionResult SaveGroup(CallGroupViewModel group)
group.Members //this has a count
group.Members[0].email //this pukes as 'email' is not valid for 'object'
I'm probably just missing something, any help is appreciated.
Thanks!
The model binding system which populates MVC action method arguments isn't intended to work with dynamic objects. The system requires type information to know which information to bind. Consider changing your model to use strong types instead.
For accessing dynamic properties from the object, in your case you can use :
var email = group.Members[0].GetType()
.GetProperty("Email")
.GetValue(group.Members[0], null);
Related
I typed this out in Notepad++ real quick so please forgive any typos/mistakes. If it's possible, I'd be getting rid of some repetitive work (i.e. a long case statement). Not a huge deal but I'm curious if it's possible and if so, how bad would it be to actually implement the code.
jsonFromWebpage = {
StatusUpdate: {
QueryType: "SomeClassName",
LocalCount: 5,
RemoteCount: 5
},
StatusUpdate: {
QueryType: "AnotherClass",
LocalCount: 29,
RemoteCount: 30
}
}
// Model
public class StatusUpdate
{
public string QueryType { get; set; }
public int LocalCount { get; set; }
public int RemoteCount { get; set; }
}
// Controller
public IActionResult GetStatusUpdate([FromBody] List<StatusUpdate> status)
{
_service.GetStatusUpdate(status);
return status
}
// Service
public List<Status> GetStatusUpdate(List<StatusUpdate> status)
{
foreach(var s in status)
{
var typeArgument = s.QueryType; // <--- Is there a way for this...
status.CurrentCount = GetTotalCount<typeArgument>(); // <--- to work here?
status.RemoteCount = thisworksfineforotherreasons(s.QueryType);
}
}
// Repo
public int GetTotalCount<T>() where T: class
{
var result = _db.GetCount<T>();
return result;
}
EDIT
First, thank you to everyone that has responded. Having read everything so far, I wanted to give a little more context. Here's a different take on the example:
// View
<div class="col-12">
<div class="api-types">History</div>
<div class="progress-bar">50 out of 50 copied</div>
</div>
<div class="col-12">
<div class="api-types">Users</div>
<div class="progress-bar">25 out of 32 copied</div>
</div>
// -- View javascript
var types = [];
$(".api-types").each(function (c, i) {
types.push({ ApiAndClassName: $(i).text() });
});
pushToController(JSON.stringify(types));
// Controller
public IActionResult GetSyncStatus(List<SyncStatusVM> status)
{
_service.GetSyncStatus(status);
return Json(status);
}
// Service
public List<SyncStatusVM> GetSyncStatus(List<SyncStatusVM> status)
{
foreach(var s in status)
{
// LocalCount
var magicTypeFigurator = s.ApiAndClassName
s.LocalCount = _repo.GetCount<magicTypeFigurator>(); <-- "this is a variable but should be a type..."
// Remote
var url = $"https://api.domain.com/{s.ApiAndClassName.ToLower()}"
s.RemoteCount = FetchCountFromApi(url);
}
return status;
}
// Repository
public long GetCount<T>()
{
var result = _orm.Count<T>();
return result;
}
// Models
public class SyncStatusVM
{
public string ApiAndClassName { get; set; }
public int LocalCount { get; set; }
public int RemoteCount { get; set; }
}
public class History
{
public long Id {get;set;}
public DateTime CreatedDate {get;set;}
public string Message {get;set;}
}
public class Users
{
public long Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
}
Using this code, I can just create a section in the view and a class for each type. The class is reused by the ORM and desearializing from the API. The most cumbersome point is having a case statement in the controller that calls the generic method with the correct type, based on the "ApiAndClassName". I could edit the ORM so it's string based instead of generic but I don't like that method for various reasons. I could turn the case statement into a collection in the controller or just move it to the service layer but what I have in place already works. I could also just refactor so the view builds from a collection but there are other data points where that wouldn't be the best option. Unless there's something I'm missing, the generic argument from string thing kinda makes sense. It's a fringe case... and kinda just curious if it can be done well enough.
Generally strong typsisation is your friend. Compile time type checks are a feature, not a enemy to be fought. Without them or with too agressive casting, we get the JavaScript and PHP examples from this comic.
For work with weakly typed langauges or WebServices, .NET has the ExpandoObject. The data can be stored in it, then later transfered into the proper type of instance. Also it looks like your case would fall into JSON deserialisation, wich is a well established code.
Generic is the wrong term. Generics are usually about the type still being known at compile time, so the compile time type checks still work. You are explicitly about the type not being known at compile time, only at runtime. This is very distinct from a generic. Dynamic Types are the proper term afaik. But to not mix it up with the type Dynamic (yes, naming here becomes really confusing).
Reflection is the droid you are looking for. For most purposes, the name of a class or field does not exist at runtime. It is primarily there for you and the compiler to communicate. Now Reflection is the exception. It is all about getting stuff (like instances or property/fields) based on a string representation of their name. The nessesary metadata is baked into the .NET Assemblies, as much as the COM support. But as I support strong typisation, I am not a friend of it.
switch/case statements can usually be replaced with a collection of some sort. Cases are really just a hardcoded way to check a collection of constants. You use the case identifier as the key and whatever else you need for the Value. You can totally use Functions as the value (thanks to delegates). Or the Type type, you then use for the instance creation.
But for your case it sounds like all of this is wrong. Bog standart Inheritance - Inheritance might be the real droid you are looking for. A JSON service would not usually give you different instance in a single collection, unless those instances are related in some way. "SomeClassName" and "AnotherClass" should have another ancestor. Or in fact, they should even be just one class - QueryType is simply a string field of said class.
Assuming that you have a way to map strings to Type objects, yes: you can use MethodInfo.MakeGenericMethod():
var totalCount = (int) (
GetType()
.GetMethod("GetTotalCount")
.MakeGenericMethod(MapStringToType(s.QueryType))
.Invoke(this, null)
);
This assumes the presence of a method Type MapStringToType(string) in the local scope.
One way to map types would be to use a Dictionary<string, Type> and fill it with the allowed types and their respective names that will be used in the JSON data to refer to them.
I'm facing little stranger issue with Web API controller. I have a collection which is being passed in an action of api controller. Object being used is collection is having 4 properties.
My action is able to accept collection parameter when it's properties are in specific order. See below :-
[HttpPost]
public ForexRates UpdateRates([FromBody] Rates rates)
{
// TODO: Obviously code :)
return rates;
}
This code is being place in API controller & calling from Postman. See below:-
<rates>
<rate>
<id>fefef</id>
<rate>35353.333</rate>
<series>dfefge</series>
<series-order>sfefefef</series-order>
</rate></rates>
If I change the order of the properties I started getting null value in my action. Can some one please explain this :)
Models
public class Rate
{
public string Id { get; set; }
public string Date { get; set; }
public double Rate { get; set; }
}
public class Rates : Collection<ForexRate>
{
}
You will need to control the order with which your XML is serialized. Use XmlElementAttribute and specify the Order.
There is a similar question here
FYI, I suppose there is no way for you to change the order of the properties, while you supply from PostMan to your WebApi service. You will need to follow the exact order.
If you don't wanna do that, then pass this Xml as a string parameter and then parse it inside a method.
The default binder can have issues when the same name is used in different places during binding.
In your case you've got Rate.Rate - both class name and property name. Try changing your class to (and corresponding xml for the post) :
public class Rate
{
public string Id { get; set; }
public string Date { get; set; }
public double Value { get; set; }
}
and then try changing the order.
While I don't have a definitive reason why it works in one order and not another, it's likely that when it gets to the Rate(double) value it tries to create a new Rate(object) but doesn't have the correct properties (as its just a double).
A more complicated solution would be to write a specific model binder for the Rate object.
The issue has to do with the DataContractSerializer which expects the elements to occur in a specific order (alphabetical with some consideration given to inheritance). That's the default serializer used when creating a Web API project.
You can override this and specify a different serializer during API Configuration like this:
GlobalConfiguration.Configuration.Formatters.XmlFormatter
.SetSerializer<SomeType>(new XmlSerializer(typeof(SomeType)));
What's the best way to design a class (or classes) that can hold the potential values of item, as well as the one the user actually selected? I've come across this problem before and always feel like I'm missing a core class design feature.
Right now I usually do something like the following
class MultiChoice
Name (I.e. Box Size)
Default Value ("22x15")
PotentialValues ({"10x10","20x20","22x15"})
But that doesn't handle the actual value the user selected, so I add that in.
class MultiChoice
Name (I.e. Box Size)
Default Value ("22x15")
PotentialValues ({"10x10","20x20","22x15"})
SelectedValue
That doesn't feel right though, because when I construct a drop-down I'm filling in stuff with SelectedValue = null. Then when I store the data, I'm storing all the options too, which I don't need.
Is there a better way to handle this with an interface or other language construct? I always feel like I'm missing something blatantly obvious here.
You really have two separate entities here:
MultiChoiceQuestion
MultiChoiceAnswer
Create two separate classes to represent these two separate concepts.
ASP.NET MVC has the SelectList class. While you might not actually be working in ASP.NET MVC, it seems clear that Microsoft felt that the concept of "backing class for a dropdown" was universal enough to warrant its own class.
In whatever you consider the "Model" (that part of your program containing the business domain classes and business logic), there will always exist database tables that serve as lookups for these dropdowns.
tblCountries
CountryID PK
CountryCode string
FullName string
In your ViewModel, there will be a corresponding list of countries from which you can populate the dropdown:
public class InvoiceViewModel
{
...
public int CountryID { get; set; }
public SelectList Countries { get; set; }
// or
public List<Country> Countries { get; set; }
...
}
Of course, by the time you get to the UI, the actual dropdown contains enough plumbing to hold both the select list and the selected value.
You really only need a single Value field. Set it to whatever you want in the constructor (so it's defaulted when the object is created). You can also change your 'potential values' to be static, so it's the same for the entire class.
public class Box
{
public string Value { get; set; }
public static List<string> AllowedValues { get; private set; }
public Box()
{
AllowedValues.AddRange(new string[]{"10x10","20x20","22x15"});
Value = AllowedValues.First();
}
}
Then when a user changes the value, simply update it.
Box thisBox = new Box();
string val = "22x15";
if (Box.AllowedValues.Contains(val))
thisBox.Value = val;
I am new to WebAPI and rest and am trying to do things correctly. By default if I were to access something such as User I would call api/user/5 if I wanted user 5. This would go to my User controller to Get(int num) I think. But I know I will often need other params passed as well. Currently I have Get(JObject data), but that data param is for other parameters. I will need other optional params whether I am sending an ID or wanting a list of everything. How do I go about organizing methods properly with WebAPI? Am I misunderstanding something?
To clarify:
This question is more about REST than dynamic objects, though they play a part:
How do I get a single resource vs a list of resources when I need additional params. I see those concepts as two separate methods, but the additional params complicate it in my mind when routing is involved.
Use attribute routing
For example -
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
or
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
if you need to return a list, create a method that returns a list, otherwise return the specific item requested
Look into using JToken or the even more dynamic 'dynamic' (Taken from here)
"
JSON and JavaScript is really dynamic, though, and often it's a hassle to try to "deserialize" really dynamic JSON objects into strongly-typed .NET structures. JSON.NET and ASP.NET Web API's model binding offer a happy medium - a middle ground - called JToken.
public class ContactController : ApiController
{
public JToken Post(JToken contact)
{
return contact;
}
}
Using JToken gives a dynamic container but also a DOM-like navigation model. But if that's not dynamic enough for me, why can't my method's parameter just take a "dynamic."
C# is statically typed, sure, but that doesn't mean I can't statically type something dynamic. ;)
Again, note the watch window.
Using dynamic to catch JSON post payloads
public class ContactController : ApiController
{
public dynamic Post(dynamic contact)
{
return contact;
}
}
"
I think you should make a new object for each WebAPI function that will handle the request. You can make the parameters optional with nullable properties.
[HttpPost]
public void SampleFunction(SampleFunctionModel model)
{
}
where SampleFunctionModel is:
public class SampleFunctionModel
{
public int? Id { get; set; }
public string Name { get; set; }
}
On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.