In Mongoose my enum is is defined like this:
personType: {
type:String,
enum: ['Contact','Donor','Resident'],
},
Is there a simple way to make a similar definition in the Breeze metadata?
You can define an enum in a class library:
public enum PersonType
{
Contact = 0,
Donor = 1,
Resident= 2
}
Then, In the conroller:
[HttpGet]
public object Lookups()
{
// Some lookup data go here
var PersonType = Enum.GetValues(typeof(PersonType));
return new {
// Some lookup objects
PersonType }
}
Hence, you can get the PersonType as an object along with the lookups from the client side.
Related
I’m using Swashbuckle 6.1.4 in a .net 5.0 project.
I want to customise the ordering of the elements in a schema. The default order, i.e. that in which the properties are declared) isn’t good because when models extend a base model, the properties of the base model are listed at the bottom.
I’ve managed to apply a document filter to sort properties alphabetically:
public class SchemaSortingFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var descs = context.ApiDescriptions.ToList();
// only applying to the SupporterDTO for now...
string model = "SupporterDTO";
if (swaggerDoc.Components.Schemas.ContainsKey(model))
{
var props = swaggerDoc.Components.Schemas[model].Properties.OrderBy(x => x.Key).ToArray();
swaggerDoc.Components.Schemas[model].Properties.Clear();
foreach (var prop in props)
{
swaggerDoc.Components.Schemas[model].Properties.Add(prop.Key, prop.Value);
}
}
}
}
But what I really want is to use a custom attribute to manage the order. Like this:
public class SwaggerOrderAttribute : Attribute
{
public int Order { get; private set; }
public SwaggerOrderAttribute(int order)
{
Order = order;
}
}
Which I’d use to decorate properties thus:
[SwaggerOrder(1)]
String PropertyZ {get; set;}
[SwaggerOrder(3)]
String PropertyX {get; set;}
[SwaggerOrder(2)]
String PropertyY {get; set;}
My problem is that the list of attribute values that is exposed in my filter via swaggerDoc.Components.Schemas[model].Properties does not include my custom attributes.
During my explorations, and thanks to a hint in this question I’ve tried to get hold of them like this but it didn’t see them:
if (apiDesc.TryGetMethodInfo(out MethodInfo mi))
{
var atts = mi.DeclaringType
.GetCustomAttributes(true)
.OfType<SwaggerOrderAttribute>()
.ToList();
}
How do I bring in my custom attributes for inclusion in my sorting linq query?
You need to implement ISchemaFilter. Then you'll be able to get hold of custom attributes from MemberInfo:
context.MemberInfo.CustomAttributes
Here is an implementation of getting an attribute from MemberInfo:
ISchemaFilter:
public class DefaultValuesSwaggerExtensions : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var attributes = context?.MemberInfo?.GetCustomAttributes(true).OfType<SwaggerDefaultValueAttribute>();
}
}
Swagger
services.AddSwaggerGen(c =>
{
c.SchemaFilter<DefaultValuesSwaggerExtensions>();
});
This question has been asked before on SO and elsewhere in the context of MVC3 and there are bits and bobs about it related to ASP.NET Core RC1 and RC2 but niot a single example that actually shows how to do it the right way in MVC 6.
There are the following classes
public abstract class BankAccountTransactionModel {
public long Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public readonly string ModelType;
public BankAccountTransactionModel(string modelType) {
this.ModelType = modelType;
}
}
public class BankAccountTransactionModel1 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel1():
base(nameof(BankAccountTransactionModel1)) {}
}
public class BankAccountTransactionModel2 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel2():
base(nameof(BankAccountTransactionModel2)) {}
}
In my controller I have something like this
[Route(".../api/[controller]")]
public class BankAccountTransactionsController : ApiBaseController
{
[HttpPost]
public IActionResult Post(BankAccountTransactionModel model) {
try {
if (model == null || !ModelState.IsValid) {
// failed to bind the model
return BadRequest(ModelState);
}
this.bankAccountTransactionRepository.SaveTransaction(model);
return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
} catch (Exception e) {
this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
return StatusCode(500);
}
}
}
My client may post either BankAccountTransactionModel1 or BankAccountTransactionModel2 and I would like to use a custom model binder to determine which concrete model to bind based on the value in the property ModelType which is defined on the abstract base class BankAccountTransactionModel.
Thus I have done the following
1) Coded up a simple Model Binder Provider that checks that the type is BankAccountTransactionModel. If this is the case then an instance of BankAccountTransactionModelBinder is returned.
public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context) {
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var type1 = context.Metadata.ModelType;
var type2 = typeof(BankAccountTransactionModel);
// some other code here?
// tried this but not sure what to do with it!
foreach (var property in context.Metadata.Properties) {
propertyBinders.Add(property, context.CreateBinder(property));
}
if (type1 == type2) {
return new BankAccountTransactionModelBinder(propertyBinders);
}
}
return null;
}
}
2) Coded up the BankAccountTransactionModel
public class BankAccountTransactionModelBinder : IModelBinder {
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
this._propertyBinders = propertyBinders;
}
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// I would like to be able to read the value of the property
// ModelType like this or in some way...
// This does not work and typeValue is...
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
// then once I know whether it is a Model1 or Model2 I would like to
// instantiate one and get the values from the body of the Http
// request into the properties of the instance
var model = Activator.CreateInstance(type);
// read the body of the request in some way and set the
// properties of model
var key = some key?
var result = ModelBindingResult.Success(key, model);
// Job done
return Task.FromResult(result);
}
}
3) Lastly I register the provider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
options.Filters.Add(typeof (SetUserContextAttribute));
});
The whole thing seems OK in that the provider is actually invoked and the same is the case for the model builder. However, I cannot seem to get anywhere with coding the logic in BindModelAsync of the model binder.
As already stated by the comments in the code, all that I'd like to do in my model binder is to read from the body of the http request and in particular the value of ModelType in my JSON. Then on the bases of that I'd like to instantiate either BankAccountTransactionModel1 or BankAccountTransactionModel and finally assign values to the property of this instance by reading them of the JSON in the body.
I know that this is a only a gross approximation of how it should be done but I would greatly appreciate some help and perhaps example of how this could or has been done.
I have come across examples where the line of code below in the ModelBinder
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
is supposed to read the value. However, it does not work in my model binder and typeValue is always something like below
typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable
I have also noticed that
bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}
Which probably means that as it is I do not stand a chance to read anything from the body.
Do I perhaps need a "formatter" in the mix in order to get desired result?
Does a reference implementation for a similar custom model binder already exist somewhere so that I can simply use it, perhaps with some simple mods?
Thank you.
I'm using Knockout JS to build a model to pass to an MVC controller. The ko.observable() items are passed to the controller no problem, however, the ko.observableArray([]) data is appearing as "count=0" at the controller.
Below is the object I am building in my View:
var AddViewModel = function () {
self.ModelRequest = {
Object: {
VarArray: ko.observableArray([]),
Var1: ko.observable(""),
Var2: ko.observable("")
}
};
....
The ModelRequest.Object.VarArray is an ko.observableArray contains a few attributes in the object: Name, Id, Code, Type.
Below is how I'm sending the data via JSON:
p = ko.toJSON(AddViewModel.ModelRequest);
debugger;
$.ajax({
url: url,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON(AddViewModel.ModelRequest),
success: function (data) {
...something...
}
});
When I am debugging the code, I examine the p variable described above and I see the below:
{"Object":{"VarArray":[{"Name":"Name 1", "Id":2, "Code":"50.1", "Type":"A"}],
"Var1":"abc", "Var2":"def"}}
When I examine the object being passed into the controller, Var1 and Var2 have the correct values, however, the VarArray is "Count=0".
Any thoughts? Thanks for taking the time to look at this. I'll try any ideas at this point!!
EDIT 10/6/13:
This is my controller action:
[HttpPost]
public CRUDResponse AddItem(AddRequest ModelRequest)
{
... something here ...
}
At this point when I examine the ModelRequest I see that VarArray is "Count = 0".
Edit 10/8/13:
This is the details of the AddRequest:
#region Public Members
public ObjectType Object { get; set; }
#endregion Public Members
Where the ObjectType is:
#region Public Members
public int Var1 { get; set; }
public int Var2 { get; set; }
public List<SpecType> VarArray { get; set; }
#endregion Public Members
Where the SpecType is
public string Name { get; set; }
public int Id { get; set; }
public string Code { get; set; }
public FieldType Type { get; protected set; }
And the FieldType is a Enum.
UPDATE: I had just found the problem. It looks like the property is not getting serialized properly through JSON when I make a call to my Web API from the UI. The above-mentioned property is of type TypaA which inherits from TypeB. TypeB contains all of the fields needed by TypeA. When I change the property failing to serialize to be of type TypeB, instead of TypeA, it serializes just fine and I get all of the values I need reflected in Web API.
So, basically, JSON fails to serialize a value if it's type is derived from another type. Removing the inheritance by declaring a value to be of base type fixes the issue.
So, is there a way to serialize a property whose type inherits from another class?
Eric
I think the problem is that either A: you are never populating the observableArray, or B: you are not receiving the proper object type back on the controller, either because you are sending it incorrectly or receiving it improperly.
Try doing this -
function testData(name) {
var self = this;
self.Name = ko.observable(name);
}
inside of your view model
var AddViewModel = function () {
self.ModelRequest = {
Object: {
varArray: ko.observableArray([
new testData('Your my boy blue'),
new testData('Frank the tank')
]),
var1: ko.observable(""),
var2: ko.observable("")
}
};
}
And see if your controller action is actually getting your data back.
If not then you are most likely not matching the object you are sending to the controller with an object the controller recognizes.
Im using MVC 4 my ActionController recives the following Json:
{
"MainId": 1,
"Actions": [
{
"Attribute1ClassA": 1,
"Attribute2ClassA": 2
},
{
"Attribute1ClassB": 3,
"Attribute2ClassB": 4
},
{
"Attribute1ClassC": 5
}
]
}
and the Controller:
[HttpPost]
public ActionResult Commit(ActionsSummaryViewModel summary)
{
//DO stuff
}
and declaration for classes:
public ActionsSummaryViewModel
{
public int MainId {get;set;}
public IList<MainClass> {get;set;}
}
public class MainClass
{
}
public class ClassA : MainClass
{
public int Attribute1ClassA {get;set;}
public string Attribute2ClassA {get;set;}
}
public class ClassB : MainClass
{
public int Attribute1ClassB {get;set;}
public string Attribute2ClassB {get;set;}
}
public class ClassC : MainClass
{
public int Attribute1ClassC {get;set;}
}
So now, how can i manage the deserialization for the MainClass when the action controller receive the JSON ? because when i call the action the list items are null.
if part of the solution is Json.NET, how i can implement for MVC 4 controllers?
Thanks for your help.
You need a property or set of properties from which you can determine which type the class is to use this method. Using JSON.NET, I deserialize the incoming JSON as a dynamic object, then check the common property, determine the type, and deserialize the value again this type using my model type:
// I'm assuming here you've already got your raw JSON stored in 'value'
// In my implementation I'm using the Web API so I use a media formatter,
// but the same principle could be applied to a model binder or however
// else you want to read the value.
dynamic result = JsonConvert.DeserializeObject(value);
switch ((string)result.type)
{
case "typeone":
return JsonConvert.DeserializeObject<ModelOne>(value);
// ...
default: return null;
}
There's a little bit of extra overhead here because you're deserializing twice, but it's worth it in most cases to me because it's easy to understand what's going on and add new types as needed.
You could parse JSON into dynamic object instead using Json.NET:
using Newtonsoft.Json.Linq:
dynamic data = JObject.Parse("{ 'Name': 'Jon Smith', 'Address': { 'City': 'New York', 'State': 'NY' }, 'Age': 42 }");
string name = data.Name;
string address = data.Address.City;
I'd like to pass a function to LINQ queries to do operations on existing data. For eg. I have this function:
string doStuff(Product prod)
{
return string.Format("{0} - {1}", prod.f1, prod.s2);
}
When requesting products, i'd like to pass in this function so it returns this string and assign it to a product property NewString.
An example of the POCO object the repository returns:
class Product
{
public string NewString{get; set;};
public string f1 {get; set;};
public string s2 {get; set;};
}
//Service layer
public IList<Product> GetProducts()
{
//I currently have this:
return _repository.GetProducts().ToList();
//OR, is something like this possible?
return _repository.GetProducts().Select(p => p.NewString = doStuff(p));
}
All methods in the repository returns IQuerable<Product>
So basically, I want to generate a new string from existing data from the service layer. How do I accomplish this without looping through the returned list of objects?
If it is calculated based on Product fields and is needed where Product is needed, why not just add this function call to the NewString getter?
//Service layer
public IList<Product> GetProducts()
{
return _repository
.GetProducts()
.Select(p => new Product {
NewString = doStuff(p),
f1 = p.f1,
s2 = p.s2
})
.ToList;
}