I have a grid view whose data is based on a long value as the key and need that key for doing other operations (to find the record). The value is getting rounded up in the view when passed to an action:
ActionLink("some action", "some action", new{ key = "#=key#"}); // #= # is Kendo syntax
For example:
636280844960803997 is rounded to
636280844960804000
So what's the fix considering that I cannot add any attribute on that property in my model.
The problem is that JavaScript supports only 53-bit integers, which would make the largest supported integer to be 9007199254740991. Your key is larger than that. You can take a look at Number.MAX_SAFE_INTEGER (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)
The solution to this problem is to use string representations of your key on the client side. A good example of this is the Twitter API, where they return both an integer and a string: {"id": 10765432100123456789, "id_str": "10765432100123456789", ...}. There is a good article about it here: http://2ality.com/2012/07/large-integers.html
If you are using ASP.NET MVC I would recommend that you add another property to your model classes, which returns a ToString() version of your key. That way you can use the string version in JavaScript, while taking advantage of the numeric values within your database or server-side code. Here's an example
public class Item
{
public Item()
{
// ... your constructor code here ...
}
// Your long integer key
public Int64 ItemId { get; set; }
// The string version of your key
public string ItemIdString
{
get
{
return this.ItemId.ToString();
}
}
// The rest of your fields
// ...
}
Then you can use ItemIdString, when you bind your Kendo grid or anything else on the client side.
If you cannot modify the original class then I'd recommend that you create a subclass that inherits from the original one and add the ItemIdString property to that. Then just use the subclass to bind your grid.
You can used string instead of int in key field if possible so value can not be rounded
Related
I have an api (ASP.NET Core 3.0) that allows users to search a document database using various query parameters to filter and order the results. One of the parameters is an Order parameter that defines how to order the results. The acceptable values are limited to the values of an enum.
I now need to add more behavior to the enum, so I re-wrote it as an Enumeration Class so that I can add object-oriented behavior to it. The problem I now have is that Swashbuckle flattens out the properties of the Enumeration rather than leaving it as a single parameter. Here are my enumeration and parameter classes:
// Enumeration
public class DocSearchOrder : Enumeration {
public static readonly DocSearchOrder DocType = new DocSearchOrder(2, nameof(DocType));
public static readonly DocSearchOrder DocTypeDesc = new DocSearchOrder(3, nameof(DocTypeDesc));
public static readonly DocSearchOrder DocDate = new DocSearchOrder(4, nameof(DocDate));
public static readonly DocSearchOrder DocDateDesc = new DocSearchOrder(5, nameof(DocDateDesc));
public DocSearchOrder(int value, string name) : base(value, name) {
}
}
// Search Parameters
public class DocSearchParameters {
public DocSearchOrder? Order { get; set; }
// Lots of other search parameters
}
Then the method that uses it:
public async Task<IActionResult> GetAsync([FromQuery] DocSearchParameters searchParams) {
// Do the search
}
Swashbuckle flattens searchParams.Order into DocSearchOrder.Id and DocSearchOrder.Name.
The behavior I want to achieve is for my Swagger UI to continue to show a dropdown of the available values (DocSearchOrder.Name) that a user can select from with the parameter named "Order". You then pass one of those string values and a custom model binder converts the string to the Enumeration class instance.
I've tried writing a custom IOperationFilter but that only seems to work for modifying the schema of types passed to the GetAsync method, I can't intercept the schema generation for searchParams.Order. I thought what I'd be able to do is somehow intercept the schema generation for any property that is an Enumeration and generate an enum schema for it instead of an object schema, but I don't know how to intercept it.
So my question is: is there a way to customize the schema generation for a nested type? Or is there another way to go about this and I'm doing it all wrong? :)
How about a regular enum:
public enum DocSearchOrder
{
DocType = 2,
DocTypeDesc = 3,
DocDate = 4,
DocDateDesc = 5
}
I think that would that be easier, and there should not give you much trouble
Here is an example from one of mine:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=TestEnum#/TestEnum/TestEnum_Get
I've defined my own struct that represents DateTime with TimeZoneInfo so I can work with UTC time while keeping the information about timezone.
I would like to get these objects with OData query, but it fails when I try to use $orderby on this properties of this type. I was able to get results when I queried $orderBy=Timestamp/Value/UniversalTime but I would like to use just $orderBy=Timestamp
Is there any possibility to order collection with this type?
public struct DateTimeWithZone : IComparable<DateTime>, IComparable<DateTimeWithZone>, IFormattable
{
private readonly DateTime _utcDateTime;
private readonly TimeZoneInfo _timeZone;
public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
{
if (timeZone == null)
{
throw new NoNullAllowedException(nameof(timeZone));
}
_utcDateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
_timeZone = timeZone;
}
...
}
With model defined like this:
public class ClientViewModel
{
public string Name { get; set; }
public DateTimeWithZone? Timestamp { get; set; }
}
And this is how it is used:
public IHttpActionResult GetAll(ODataQueryOptions<ClientViewModel> options)
{
var fromService = _clientsClient.GetAllClients().MapTo<ClientViewModel>(MappingStrategy).AsQueryable();
var totalCount = fromService.Count();
var results = options.ApplyTo(fromService); // <-- Fails here
return Ok(new PageResult<ClientViewModel>(
results as IEnumerable<ClientViewModel>,
Request.ODataProperties().NextLink,
totalCount));
}
Fails with The $orderby expression must evaluate to a single value of primitive type.
we had some similar issue with complex type ordering. Maybe this can be of assistance in your scenario as well. In our case (which is not 100% identical) we use a two phase approach:
Rewriting ODataQueryOptions
separating the extneral model (ODATA) and the internal model (EntityFramework in our case)
Rewriting ODataQueryOptions
You mention that the format $orderBy=Timestamp/Value/UniversalTime is accepted and is processed properly by ODATA. So you can rewrite the value basically by extracting the $orderby value and reinserting it with in your working format.
I described two ways on how to do this in my post Modifying ODataQueryOptions on the fly (full code included), which take existing options recreate new options by constructing a new Uri. In your case you would extract Timestamp from $orderBy=Timestamp and reinsert as with $orderBy=Timestamp/Value/UniversalTime.
Separating External and Internal Model
In addition, we used two models for the public facing API and the internal / persistence layer. On the internal side we used different properties which we grouped into a navigation property (which only exists on the public side). With this approach the user is able to specify an option via an $expand=VirtualNavigationProperty/TimeZoneInfo and $orderby=.... Internally you do not have to use the complex data type, but keep using DateTimeOffset which already holds that information. I described this separation and mapping of virtual navigation properties in the following post:
Separating your ODATA Models from the Persistence Layer with AutoMapper
More Fun with your ODATA Models and AutoMapper
According to your question it should be sufficient to rewrite the query options in the controller as you did mention that the (little bit longer) $orderby format is already working as expected and you only wanted a more convenient query syntax.
Regards, Ronald
Is there an annotation that allows me to say that the following property must have a numeric value and then I want to specify a range for that value?
[DataType.???]
[Range(1990, 2015)]
public string AnniversaryYear { get; set;}
There's a reason I need this to be a string.
check the use of Data Annotations Extensions that simply extends the data annotations that have into the framework, also look this article that show how to use them: Introducing Data Annotations Extensions
I am trying to realize valition on data type. I have used DataAnnotations, but for data type it's not showing customized message
for example when I' am trying enter string data into int typed field. How I can customize messages in this case?
If I had to guess, you sound like you want a custom message to display when validating one or more fields in your model. You can subclass the DataAnnotations.ValidationAttribute class and override the IsValid(object) method and finally setting a custom ErrorMessage value (where ErrorMessage already belongs to the ValidationAttribute class)
public class SuperDuperValidator : ValidationAttribute
{
public override bool IsValid(object value)
{
bool valid = false;
// do your validation logic here
return valid;
}
}
Finally, decorate your model property with the attribute
public class MyClass
{
[SuperDuperValidator(ErrorMessage="Something is wrong with MyInt")]
public int MyInt { get; set; }
}
If you're using out-of-the-box MVC3, this should be all you need to propertly validate a model (though your model will probably differ/have more properties, etc) So, in your [HttpPost] controller action, MVC will automagically bind MyClass and you will be able to use ModelState.IsValid to determine whether or not the posted data is, in fact, valid.
Pavel,
The DataAnnotations DataType attribute does not affect validation. It's used to decide how your input is rendered. In such a case, David's solution above works.
However, if you want to use only the built-in validation attributes, you probably need to use the Range attribute like this:
[Range(0, 10, ErrorMessage="Please enter a number between 0 and 10")]
public int MyInt { get ; set ;}
(Of course, you should really be using the ErrorMessageResourceName/Type parameters and extract out hard-coded error message strings into resx files.)
Make sure to let MVC know where to render your error message:
<%= Html.ValidationMessageFor(m => m.MyInt) %>
Or you can just use EditorForModel and it will set it up correctly.
I don't think this has been answered because I have the same issue.
If you have a Model with a property of type int and the user types in a string of "asd" then the MVC3 framework binding/validation steps in and results in your view displaying "The value 'asd' is not valid for <model property name or DisplayName here>".
To me the poster is asking can this message that the MVC3 framework is outputting be customized?
I'd like to know too. Whilst the message is not too bad if you label your field something that easily indicates an number is expected you might still want to include additional reasons so it says something like:
"The value 'asd' is not valid for <fieldname>; must be a positive whole number."
So that the user is not entering value after value and getting different error messages each time.
To put it in basic form, my database table doesn't allow nulls for varchars, it must have blanks. My model doesn't allow nulls so it won't insert a record if I leave form fields empty. If an empty form field appears I want a default value of blank to be used instead. I've tried, for example, the following without any luck:
[Column]
[DisplayName("WMD Company")]
[DefaultValue(" ")]
public string WMDCompany { get; set; }
So instead, in my controller action I have to do a check like the following:
if(myModel.WMDCompany == null) myModel.WMDCompany = " ";
Which is plain nasty to me. Is there any way of getting [DefaultValue(" ")] to work?
Cheers
What about something like this:
private string wmdCompany;
public string WMDCompany
{
get
{
if (this.wmdCompany == null)
{
return string.Empty;
}
return this.wmdCompany;
}
set
{
this.wmdCompany = value;
}
}
The DefaultValue attribute is not used. LINQ to SQL has not support for DB defaults unfortunately. That property is intended for use in API extension if I remember, but I don't know of any that use it.
Two approaches to get around this you could use.
First update your data layer, by appropriately controlling the property, and setting it to null. Use a partial class to extend your data class, and implement the OnCreated() partial method, and in this set the value to String.Empty.
partial void OnCreated()
{
MyProp = String.Empty;
}
Secondly, you could change your DBML representation to allow nulls, but in your database, use a trigger to convert NULLs to empty strings.
I'd go with the first approach myself - assuming you can't just use NULLs as suggested by Adrian
Inserting spaces as a placeholder for NULL seems like a very obscure method to me. Why don't you just change your table design to allow NULL values?