I have the following model:
public class PersonModel {
public int Id {get; set;}
public DateTime BirthDay {get;set;}
....
}
When a make a get request OData returns a DateTimeOffset instead of a DateTime. For instance, the request '/api/rest/people/1' returns:
{
id:1,
birthDay: "12/7/2016 8:57:58 PM +00:00"
....
}
What I want? I just need to return the Date value "12/7/2016". I have tried to implement a custom ODataPayloadValue converter but OData doesn't seem to understand the 'DateTime' type.
public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference)
{
/// There isn't a IEdmtypeRefere.IsDateTime
/// Thus OData always think that DateTime is equals to DateTimeOffset
if (value is DateTime)
{
///Do some stuff here.
}
return base.ConvertToPayloadValue(value, edmTypeReference);
}
The easiest way - extend your model like this:
public class PersonModel {
public int Id {get; set;}
public DateTime BirthDay {get;set;}
// add this property and you get it. You can rename it if you want
public string BirthDate {get {return this.BirthDay.ToShourtDateString(); }}
....
}
After doing some research, I found out that there is a way to achieve what I want by implementing two classes:
// A custom serializer provider to inject the AnnotatingEntitySerializer.
public class CustomODataSerializerProvider : DefaultODataSerializerProvider
{
private AnnotatingEntitySerializer _annotatingEntitySerializer;
public CustomODataSerializerProvider()
{
_annotatingEntitySerializer = new AnnotatingEntitySerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsEntity())
{
return _annotatingEntitySerializer;
}
return base.GetEdmTypeSerializer(edmType);
}
}
// A custom entity serializer that adds the score annotation to document entries.
public class AnnotatingEntitySerializer : ODataEntityTypeSerializer
{
public AnnotatingEntitySerializer(ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
}
public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
{
ODataEntry entry = base.CreateEntry(selectExpandNode, entityInstanceContext);
PersonModel person = entityInstanceContext.EntityInstance as PersonModel;
var property = entry.Properties.Where(x => x.Name == "birthDate").FirstOrDefault();
property.Value = ((DateTimeOffset)property.Value).DateTime.ToShortDateString();
return entry;
}
}
Related
ASP.NET Core introduced custom tag helpers which can be used in views like this:
<country-select value="CountryCode" />
However, I don't understand how can I get model property name in my classes:
public class CountrySelectTagHelper : TagHelper
{
[HtmlAttributeName("value")]
public string Value { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
// Should return property name, which is "CountryCode" in the above example
var propertyName = ???();
base.Process(context, output);
}
}
In the previous version I was able to do this by using ModelMetadata:
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var property = metadata.PropertyName; // return "CountryCode"
How can I do the same in the new ASP.NET tag helpers?
In order to get property name, you should use ModelExpression in your class instead:
public class CountrySelectTagHelper : TagHelper
{
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var propertyName = For.Metadata.PropertyName;
var value = For.Model as string;
...
base.Process(context, output);
}
}
You can pass a string via the tag helper attribute.
<country-select value="#Model.CountryCode" />
The Value property will be populated by Razor with the value of Model.CountryCode by prepending #. So you get the value directly without the need to pass the name of a model property and accessing that afterwards.
I am not sure whether you got what you wanted. If you are looking to pass the complete model from view to the custom tag helper, this is how i do it.
You can pass in your current model from the view using any custom attributes. See the example below.
Assuming your model is Country
public class Country
{
public string Name { get; set; }
public string Code { get; set; }
}
Now declare a property in your custom tag helper of the same type.
public Country CountryModel { get; set; }
Your controller would look something like this
public IActionResult Index()
{
var country= new Country
{
Name = "United States",
Code = "USA"
};
return View("Generic", country);
}
In this setup, to access your model inside the taghelper, just pass it in like any other custom attribute/property
Your view should now look like something like this
<country-select country-model="#Model"></country-select>
You can receive it in your tag helper like any other class property
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
// Should return property name, which is "CountryCode" in the above example
var propertyName = CountryModel.Name;
base.Process(context, output);
}
Happy coding!
I'm trying to allow my entities to be ordered by the Last-Modified property in their metadata, using OData query options.
I tried using a transformer as described in Converting to JSON and accessing metadata, but when I apply ODataQueryOptions to the resulting IQueryable, I get an empty array.
The model and view-model:
public class Foo
{
public int Id { get; set; }
}
public class FooViewModel
{
public string Id { get; set; }
public DateTime LastModified { get; set; }
}
The transformer:
public class Foos_WithLastModified : AbstractTransformerCreationTask<Foo>
{
public Foos_WithLastModified()
{
TransformResults = foos => from foo in foos
let metadata = MetadataFor(foo)
select new
{
Id = foo.Id.ToString(CultureInfo.InvariantCulture),
LastModified = metadata.Value<DateTime>("Last-Modified")
};
}
}
The relevant method in FooController (_session is an IAsyncDocumentSession):
public async Task<ICollection<FooViewModel>> Get(ODataQueryOptions<FooViewModel> options)
{
var settings = new ODataValidationSettings();
settings.AllowedOrderByProperties.Add("LastModified");
options.Validate(settings);
var foos = _session.Query<Foo>()
.TransformWith<Foos_WithLastModified, FooViewModel>();
var odataFoos = (IQueryable<FooViewModel>)options.ApplyTo(foos);
return await odataFoos.ToListAsync();
}
When I hit /api/Foo, the results are as expected:
[
{
"Id": "foos/456",
"LastModified": "2015-11-23T08:43:10.913662Z"
},
{
"Id": "foos/123",
"LastModified": "2015-11-23T08:50:34.0907996Z"
}
]
But when I add OData query options (/api/Foo?$orderby=LastModified), I get an empty array: [].
I also tried changing _session to an IDocumentSession and modifying Get as follows,
[EnableQuery(AllowedOrderByProperties = "LastModified")]
public IQueryable<FooViewModel> Get()
{
return _session.Query<Foo>()
.TransformWith<Foos_WithLastModified, FooViewModel>();
}
but I get the same results.
Are transformers the wrong approach? How can I sort by Last-Modified using OData query options?
I do not know how to handle the OData stuff, never tried that, but in order to query for entities, ordered by the metadata value "Last-Modified" using only RavenDB techniques you can do the following:
Create an index for your entity (in my example a Customer). In this index we add the field LastModified that's using the document's metadata value for Last-Modified.
public class Customer_ByLastModified : AbstractIndexCreationTask<Customer>
{
public class QueryModel
{
public DateTime LastModified { get; set; }
}
public Customer_ByLastModified()
{
Map = customers => from customer in customers
select new
{
LastModified = this.MetadataFor(customer).Value<DateTime>("Last-Modified")
};
}
}
The QueryModel isn't mandatory, but it makes querying via the client API easier, imo. You can then add a Transformer to be able to use the metadata value in your return model:
public class Customers_WithLastModified : AbstractTransformerCreationTask<Customer>
{
public Customers_WithLastModified()
{
TransformResults = results => from customer in results
select new CustomerViewModel
{
Id = customer.Id,
Name = customer.Name,
LastModified = MetadataFor(customer).Value<DateTime>("Last-Modified")
};
}
}
And then query it like this:
using (var session = documentStore.OpenSession())
{
var customers = session.Query<Customer_ByLastModified.QueryModel, Customer_ByLastModified>()
.OrderByDescending(x => x.LastModified)
.TransformWith<Customers_WithLastModified, CustomerViewModel>()
.ToList();
}
Hope this helps!
I would like to use a DataAnnotation Attribute that tells the user that he must select one checkbox of the two following checkbox groups. My model is:
//group T
public bool T0 {get;set;}
public bool T1 {get;set;}
public bool T2 {get;set;}
//group P
public bool P0 {get;set;}
public bool P1 {get;set;}
The user must select at least one of the T properties, and one of the P properties. IS there something that do that on some customized dataannotations or i need to create one from beggining?
Thanks
You may use Fluent Validation
[FluentValidation.Attributes.Validator(typeof(CustomValidator))]
public class YourModel
{
public bool T0 { get; set; }
public bool T1 { get; set; }
public bool T2 { get; set; }
}
public class CustomValidator : AbstractValidator<YourModel>
{
public CustomValidator()
{
RuleFor(x => x.T0).NotEqual(false)
.When(t => t.T1.Equals(false))
.When(t => t.T2.Equals(false))
.WithMessage("You need to select one");
}
}
Here 2 solutions you can choice anyone to use.
1.Use action rule:
a) Set “False” as the check box’s default value.
b) Add following action rule to that check box field.
If check_box_field = “False”
Set check_box_field (itself) = “true”
Then this field can’t be unchecked anymore.
2.Use validation rule. Add following validation rule to that check box field.
If check_box_field = “False”
Show ScreenTip and Message: “Need to be checked”
With this validation rule, if check box has not selected, validation error will be displayed and stop prevent form submission. Let me know if you have any question.
I figure out this solution that worked as I expected but i cant display the error messages on the view.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AtLeastOnePropertyAttribute : ValidationAttribute
{
public AtLeastOnePropertyAttribute(string otherProperties)
{
if (otherProperties == null)
{
throw new ArgumentNullException("otherProperties");
}
OtherProperties = otherProperties;
}
public string OtherProperties { get; private set; }
public string OtherPropertyDisplayName { get; internal set; }
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, OtherPropertyDisplayName ?? OtherProperties);
}
public override bool IsValid(object value)
{
var typeInfo = value.GetType();
var propertiesToGet = OtherProperties.Split(',');
var values = propertiesToGet.Select(propertyName => (bool) typeInfo.GetProperty(propertyName).GetValue(value)).ToList();
return values.Any(v => v);
}
public override object TypeId
{
get
{
return new object();
}
}
}
And in the DTO class:
[AtLeastOneProperty("T0 ,T1,T2", ErrorMessage = #"At least one field should be marked as true.")]
[AtLeastOneProperty("P0,P1", ErrorMessage = #"At least one field should be marked as true.")]
public class TestDTO
{
//Properties
}
Here is another way with FluentValidation.
RuleFor(x => x).Must(x =>
{
if (!x.checkbox1 &&
!x.checkbox2 &&
!x.checkbox3)
{
return false;
}
return true;
})
.WithMessage("Please select at least one checkbox.");
You could also run a foreach loop on a list of checkboxes and verify all boxes are not checked and throw the error.
KeyMaster Model
public class KeyMasterModel
{
public int KeyId { get; set; }
public int TypeId { get; set; }
public string TypeName { get; set;}
}
PagedKeyModel
public class PagedKeyModel
{
// Collection of our KeyMasterModel , trying to populate this and send it to ViewModel to display a Grid//
public IPagedList<Security.Models.KeyMasterModel> pagedkeymaster;
}
//ViewModel//
public class KeyMasterViewModel
{
//tell me how to initialise the KeyMasterViewModel.pagedkeymodel.pagedkeymaster in a constructor here so that I dont get a null
public PagedKeyModel pagedkeymodel;
public KeyMasterModel keymastermodel;
}
//Controller//
[HttpGet]
public ActionResult ListOfKey(string sortOrder, string CurrentSort, int? page)
{
// View Model Object //
KeyMasterViewModel keyMasterViewModel = new KeyMasterViewModel();
// At the gollowing step keyMasterModelObject has values retrieved from db //
//Next aim is to place it into ViewModel Object by assigning the retrieved PagedList of KeyMasterModel to IPagedList<Security.Models.KeyMasterModel> pagedkeymaster
IPagedList<KeyMasterModel> KeyMasterModelObject= datalayercall.GetAll(sortOrder, CurrentSort, page);
// Here is where error is thrown , all of a sudden I get the error , KeyMasterModelObject becomes null .
// I am trying finally to populate everything into ViewModel object
keyMasterViewModel .pagedkeymodel.pagedkeymaster = KeyMasterModelObject;
return View(keyMasterViewModel);
}
// Business Logic Layer
public IPagedList<KeyMasterModel> GetAll(string sortOrder, string CurrentSort, int? page)
{
var retrieveddatalayerobject = datalayerobject.KeyMasters;
// Retrieving Data from db and forming a list according to my model in App//
List<KeyMasterModel> keymastermodellist = new List<KeyMasterModel>();
KeyMasterModel keymastermodelobject=new KeyMasterModel();
foreach(var retrieveditems in retrieveddatalayerobject)
{
keymastermodelobject.KeyId = retrieveditems.KeyId;
keymastermodelobject.TypeName = retrieveditems.TypeMaster.TypeName;
// Create a New List Of KeyMasterModel //
keymastermodellist.Add(keymastermodelobject);
}
// Paged List of KeyMaster Model //
IPagedList<KeyMasterModel> IPagedListKeyMasterModel = null;
switch (sortOrder)
{
case "KeyId":
if (sortOrder.Equals(CurrentSort))
IPagedListKeyMasterModel = keymastermodellist.OrderByDescending
(m => m.KeyId).ToPagedList(pageIndex, pageSize);
}
return IPagedListKeyMasterModel;
}
Your code as I understand it:
KeyMasterViewModel keyMasterViewModel = new KeyMasterViewModel();
[...]
keyMasterViewModel.pagedkeymodel.pagedkeymaster = KeyMasterModelObject;
So here you are using keyMasterViewModel.pagedkeymodel and assuming it is not null (because you are trying to assign to a property on it). However KeyMasterViewModel doesn't have a constructor and doesn't set pagedkeymodel to anything at declaration so it will default to being null. Hence your problem.
DataAnnotations does not work with buddy class. The following code always validate true. Why ?
var isValid = Validator.TryValidateObject(new Customer(), Context, results, true);
and here is the buddy class.
public partial class Customer
{
public string Name { get; set; }
public int Age { get; set; }
}
[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{
public class CustomerMetaData
{
[Required(ErrorMessage = "You must supply a name for a customer.")]
public string Name { get; set; }
}
}
Here is another thread with same question., but no answer.
link text
I found the answer here: http://forums.silverlight.net/forums/p/149264/377212.aspx
MVC recognizes the MetaDataType attribute, but other projects do not. Before validating, you need to manually register the metadata class:
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Customer), typeof(CustomerMetadata)), typeof(Customer));
var isValid = Validator.TryValidateObject(new Customer(), context, results, true);
After some research I couldn't find any reason why TryValidateObject always return true if I use MetadataType (buddy class). But it works with the following code (xVal).
public static IEnumerable<ErrorInfo> GetErrors(object instance, string name)
{
var metadataAttrib = instance.GetType()
.GetCustomAttributes(typeof(MetadataTypeAttribute), true)
.OfType<MetadataTypeAttribute>().FirstOrDefault();
var buddyClassOrModelClass = metadataAttrib != null
? metadataAttrib.MetadataClassType
: instance.GetType();
var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass)
.Cast<PropertyDescriptor>();
var modelClassProperties = TypeDescriptor.GetProperties(instance.GetType())
.Cast<PropertyDescriptor>();
var list = from buddyProp in buddyClassProperties
join modelProp in modelClassProperties on
buddyProp.Name equals modelProp.Name
from attribute in buddyProp.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(modelProp.GetValue(instance))
select new ErrorInfo(
buddyProp.Name,
attribute.FormatErrorMessage(modelProp.Name),
instance);
if (name != null)
list = list.Where(x => x.PropertyName == name);
return list;
}
Although I did not test your code in .NET 4.0, in .NET 3.5 / Silverlight 3, your metadata class should look like this:
[MetadataType(typeof(Customer.CustomerMetaData))]
public partial class Customer
{
internal sealed class CustomerMetaData
{
private CustomerMetaData()
{
}
[Required(ErrorMessage = "You must supply a name for a customer.")]
public string Name;
}
}
There is an issue where the MetadataType attribute is not being recognized by the object context. While you can manually add the type descriptor before validation:
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Customer), typeof(CustomerMetaData)), typeof(Customer));
a more concise way to handle it would be to update the Entity Model .tt file, to add the following to each DTO:
Type currentType = MethodBase.GetCurrentMethod().DeclaringType;
object[] attributes = currentType.GetCustomAttributes(typeof(MetadataTypeAttribute),false);
if(attributes.Length > 0)
{
//MetadataType attribute found!
MetadataTypeAttribute metaDataAttribute = (MetadataTypeAttribute)attributes[0];
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
currentType, metaDataAttribute.MetadataClassType),currentType);
}
This will allow you to add the attributes to the partial classes:
[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{
}
public partial class CustomerMetaData
{
[Required]
public string CustomerName { get; set; }
}